- Add stocks table (server/db) with list/get/add/update/delete actions, scoped per user, backing both the chat agent and the /stocks UI via useActionQuery/useActionMutation - Add mobile-first /stocks page (shadcn table/cards, watchlist, search) and wire it into the sidebar, navigation state, and view-screen context - Install the platform's Claude Agent SDK harness (server/plugins, server/routes/api/claude-agent) so the app agent runs on the platform's own Claude subscription instead of the Builder.io gateway; fix the vendored turn.post.ts route to resolve the session and wrap it in runWithRequestContext, since custom /api/ routes aren't auto-wrapped in request context the way /_agent-native/actions/* are - Add shadcn table/badge/dialog/select components (Tabler icons, not lucide)
100 lines
3.7 KiB
TypeScript
100 lines
3.7 KiB
TypeScript
// Vendored by builder-app alongside claude-agent-sdk-harness.ts. See that
|
|
// file's header for why the harness is vendored rather than installed.
|
|
//
|
|
// Starts one Claude Agent SDK turn for a thread and returns its runId. The turn
|
|
// itself streams over the framework's existing run routes — there is no second
|
|
// streaming mechanism here:
|
|
//
|
|
// POST /api/claude-agent/turn { threadId, prompt } -> { runId, sessionId }
|
|
// GET /_agent-native/runs/<runId>/events (SSE transcript)
|
|
// POST /_agent-native/runs/<runId>/abort (cancel)
|
|
//
|
|
// A streaming route is one of the documented exceptions to actions-first; the
|
|
// start call is kept here with it so the two stay in one place.
|
|
|
|
import { randomUUID } from "node:crypto";
|
|
|
|
import {
|
|
ensureAgentHarnessSessionTables,
|
|
getLatestAgentHarnessSessionForThread,
|
|
resolveAgentHarness,
|
|
startAgentHarnessRun,
|
|
} from "@agent-native/core/agent/harness";
|
|
import { getSession } from "@agent-native/core/server";
|
|
import { runWithRequestContext } from "@agent-native/core/server/request-context";
|
|
import { defineEventHandler, readBody, setResponseStatus } from "h3";
|
|
|
|
/** Where the agent works. Defaults to the app's own directory. */
|
|
const WORKSPACE_DIR = process.env.CLAUDE_AGENT_WORKSPACE_DIR || process.cwd();
|
|
|
|
// Custom `/api/*` routes are not wrapped in the framework's per-request
|
|
// AsyncLocalStorage context the way `/_agent-native/actions/*` are — only the
|
|
// action dispatcher resolves the session and calls `runWithRequestContext`
|
|
// itself. Do the same resolution here so `ownerEmail`/`orgId` are correct
|
|
// for both a real signed-in session and the AUTH_DISABLED dev fallback.
|
|
export default defineEventHandler(async (event) => {
|
|
const session = await getSession(event);
|
|
const ownerEmail = session?.email;
|
|
if (!ownerEmail) {
|
|
setResponseStatus(event, 401);
|
|
return { error: "Sign in to run the agent." };
|
|
}
|
|
|
|
return runWithRequestContext(
|
|
{ userEmail: ownerEmail, orgId: session?.orgId ?? undefined },
|
|
async () => {
|
|
const body = (await readBody(event)) as {
|
|
threadId?: unknown;
|
|
prompt?: unknown;
|
|
} | null;
|
|
const threadId = typeof body?.threadId === "string" ? body.threadId : "";
|
|
const prompt =
|
|
typeof body?.prompt === "string" ? body.prompt.trim() : "";
|
|
if (!threadId || !prompt) {
|
|
setResponseStatus(event, 400);
|
|
return { error: "threadId and prompt are required." };
|
|
}
|
|
|
|
await ensureAgentHarnessSessionTables();
|
|
|
|
// Resume this thread's previous SDK session instead of replaying history.
|
|
// Scoped to this harness by name: a thread that also ran another harness
|
|
// would otherwise hand us that harness's resumeState, which this one
|
|
// cannot interpret. A thread with no prior session starts fresh.
|
|
const previous = await getLatestAgentHarnessSessionForThread(
|
|
threadId,
|
|
"claude-agent-sdk",
|
|
);
|
|
const resumeState = previous?.resumeState;
|
|
|
|
const adapter = resolveAgentHarness("claude-agent-sdk", {
|
|
cwd: WORKSPACE_DIR,
|
|
// Reads run; edits and shell prompt. Loosen per app only deliberately.
|
|
permissionMode: "allow-reads",
|
|
});
|
|
|
|
const runId = randomUUID();
|
|
startAgentHarnessRun({
|
|
runId,
|
|
threadId,
|
|
adapter,
|
|
input: { prompt },
|
|
createSession: {
|
|
sessionId: previous?.id,
|
|
resumeState,
|
|
cwd: WORKSPACE_DIR,
|
|
permissionMode: "allow-reads",
|
|
},
|
|
ownerEmail,
|
|
orgId: session?.orgId ?? null,
|
|
});
|
|
|
|
return {
|
|
runId,
|
|
sessionId: previous?.id ?? null,
|
|
events: `/_agent-native/runs/${runId}/events`,
|
|
};
|
|
},
|
|
);
|
|
});
|