Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions packages/core/sdk/src/core-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,75 @@ export const coreSchema = {
updated_at: { type: "date", required: true },
},
},
// Execution history — one row per `engine.execute()` /
// `engine.executeWithPause()`. Captures the submitted code, final
// status/result, and trigger metadata. Tool calls and interactions
// link back via `execution_id`.
execution: {
fields: {
id: { type: "string", required: true },
scope_id: { type: "string", required: true, index: true },
status: { type: "string", required: true, index: true },
code: { type: "string", required: true },
result_json: { type: "string", required: false },
error_text: { type: "string", required: false },
logs_json: { type: "string", required: false },
/** Epoch ms — the point the engine accepted the code. */
started_at: { type: "number", required: false },
/** Epoch ms — the point the engine reached a terminal status. */
completed_at: { type: "number", required: false },
/** Free-form trigger kind attributed by the host — `"cli"`,
* `"http"`, `"mcp"`, etc. Null when the host didn't attribute
* one. Indexed so filter facets scan fast. */
trigger_kind: { type: "string", required: false, index: true },
/** Opaque host-owned JSON for per-trigger details. */
trigger_meta_json: { type: "string", required: false },
tool_call_count: { type: "number", required: true, defaultValue: 0 },
created_at: { type: "date", required: true, index: true },
updated_at: { type: "date", required: true },
},
},
// Per-execution interaction rows — elicitation requests and their
// resolutions. A pending row is the hook the runs UI uses to render
// the "waiting for input" state; once resolved, `response_json`
// captures the user's answer for replay / auditing.
execution_interaction: {
fields: {
id: { type: "string", required: true },
execution_id: { type: "string", required: true, index: true },
status: { type: "string", required: true, index: true },
kind: { type: "string", required: true },
purpose: { type: "string", required: false },
payload_json: { type: "string", required: false },
response_json: { type: "string", required: false },
/** Stores sensitive per-response data (e.g. raw form values) that
* should not be replayed in the public interaction log. */
response_private_json: { type: "string", required: false },
created_at: { type: "date", required: true },
updated_at: { type: "date", required: true },
},
},
// Per-execution tool-call rows — one per `executor.tools.invoke` that
// ran inside the sandboxed execution. Used to build the tool-call
// timeline shown in the runs UI.
execution_tool_call: {
fields: {
id: { type: "string", required: true },
execution_id: { type: "string", required: true, index: true },
status: { type: "string", required: true },
/** Dotted tool path (e.g. `github.issues.create`). Indexed so the
* facets query in the runs UI resolves without a table scan. */
tool_path: { type: "string", required: true, index: true },
/** First path segment, pre-computed for cheap faceting. */
namespace: { type: "string", required: false, index: true },
args_json: { type: "string", required: false },
result_json: { type: "string", required: false },
error_text: { type: "string", required: false },
started_at: { type: "number", required: true },
completed_at: { type: "number", required: false },
duration_ms: { type: "number", required: false },
},
},
} as const satisfies DBSchema;

export type CoreSchema = typeof coreSchema;
Expand Down Expand Up @@ -176,6 +245,21 @@ export type ConnectionRow = InferDBFieldsOutput<
> &
Record<string, unknown>;

export type ExecutionRow = InferDBFieldsOutput<
CoreSchema["execution"]["fields"]
> &
Record<string, unknown>;

export type ExecutionInteractionRow = InferDBFieldsOutput<
CoreSchema["execution_interaction"]["fields"]
> &
Record<string, unknown>;

export type ExecutionToolCallRow = InferDBFieldsOutput<
CoreSchema["execution_tool_call"]["fields"]
> &
Record<string, unknown>;

// ---------------------------------------------------------------------------
// Tool annotations — default-policy metadata the executor consults
// before invocation. Returned by `plugin.resolveAnnotations` (dynamic
Expand Down
44 changes: 44 additions & 0 deletions packages/core/sdk/src/cursor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// ---------------------------------------------------------------------------
// Opaque cursor helpers for ExecutionStore.list pagination.
//
// Cursors encode `{ createdAt, id }` — the tuple adapter backends need to
// resume a scan on `ORDER BY created_at DESC, id DESC`. Encoded as
// url-safe base64 so callers can pass them through query params without
// thinking about escaping.
// ---------------------------------------------------------------------------

export interface CursorPayload {
readonly createdAt: number;
readonly id: string;
}

const toBase64Url = (value: string): string => {
const bytes = new TextEncoder().encode(value);
let binary = "";
for (const b of bytes) binary += String.fromCharCode(b);
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
};

const fromBase64Url = (value: string): string => {
const pad = value.length % 4 === 0 ? 0 : 4 - (value.length % 4);
const normalized = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat(pad);
const binary = atob(normalized);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
return new TextDecoder().decode(bytes);
};

export const encodeCursor = (payload: CursorPayload): string =>
toBase64Url(JSON.stringify(payload));

export const decodeCursor = (raw: string): CursorPayload | null => {
try {
const parsed = JSON.parse(fromBase64Url(raw)) as Record<string, unknown>;
if (typeof parsed.createdAt !== "number" || typeof parsed.id !== "string") {
return null;
}
return { createdAt: parsed.createdAt, id: parsed.id };
} catch {
return null;
}
};
Loading
Loading