Add minimal stock listing app: actions, Postgres schema, stocks page, Claude Agent SDK harness
- 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)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
// 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`,
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user