@sema-agent/server 1.313.0 → 1.315.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/approval-hmac.d.ts +17 -0
  2. package/dist/approval-hmac.js +27 -0
  3. package/dist/auth-bridge.d.ts +3 -2
  4. package/dist/bench/s1/live-deps.js +2 -2
  5. package/dist/budget.d.ts +4 -76
  6. package/dist/budget.js +4 -83
  7. package/dist/capabilities/sandbox-file-send.d.ts +2 -1
  8. package/dist/config-center/apply-effective.d.ts +22 -0
  9. package/dist/config-center/apply-effective.js +283 -0
  10. package/dist/config-center/http-client.d.ts +23 -0
  11. package/dist/config-center/http-client.js +109 -0
  12. package/dist/config-center/restart-signal.d.ts +12 -0
  13. package/dist/config-center/restart-signal.js +70 -0
  14. package/dist/config-center/skills-mcp.d.ts +13 -0
  15. package/dist/config-center/skills-mcp.js +113 -0
  16. package/dist/config-center/types.d.ts +143 -0
  17. package/dist/config-center/types.js +2 -0
  18. package/dist/config-types.d.ts +1 -1
  19. package/dist/elicitation.d.ts +5 -3
  20. package/dist/elicitation.js +3 -2
  21. package/dist/fleet/fleet-bus.js +92 -83
  22. package/dist/fleet-lease.d.ts +1 -1
  23. package/dist/fleet-lease.js +1 -1
  24. package/dist/hooks/hook-llm.d.ts +5 -4
  25. package/dist/hooks/hook-runner.d.ts +1 -1
  26. package/dist/hooks/hook-runner.js +19 -10
  27. package/dist/http/server.d.ts +95 -69
  28. package/dist/http/server.js +75 -86
  29. package/dist/index.d.ts +1 -1
  30. package/dist/key-resolver.d.ts +1 -1
  31. package/dist/key-resolver.js +1 -1
  32. package/dist/leader/grader-env-factory.d.ts +1 -1
  33. package/dist/leader/grader-env-factory.js +2 -2
  34. package/dist/leader/leader.js +5 -2
  35. package/dist/leader/wire.d.ts +1 -1
  36. package/dist/leader/wire.js +170 -166
  37. package/dist/main.js +487 -476
  38. package/dist/memory-scope.d.ts +18 -0
  39. package/dist/memory-scope.js +38 -0
  40. package/dist/observability/cost-taxonomy.d.ts +34 -0
  41. package/dist/observability/cost-taxonomy.js +26 -0
  42. package/dist/observability/prompt-manifest.d.ts +75 -0
  43. package/dist/observability/prompt-manifest.js +82 -0
  44. package/dist/plugins/checkpoint-store-sql.d.ts +67 -0
  45. package/dist/plugins/checkpoint-store-sql.js +224 -0
  46. package/dist/plugins/image-bake-store-sql.d.ts +53 -0
  47. package/dist/plugins/image-bake-store-sql.js +463 -0
  48. package/dist/plugins/k8s-bg-scripts.d.ts +19 -0
  49. package/dist/plugins/k8s-bg-scripts.js +129 -0
  50. package/dist/plugins/k8s-exec-protocol.d.ts +20 -0
  51. package/dist/plugins/k8s-exec-protocol.js +87 -0
  52. package/dist/plugins/pg-checkpoint-store.d.ts +1 -33
  53. package/dist/plugins/pg-checkpoint-store.js +1 -189
  54. package/dist/plugins/pg-cost-quota.js +3 -143
  55. package/dist/plugins/pg-image-bake.d.ts +1 -40
  56. package/dist/plugins/pg-image-bake.js +1 -420
  57. package/dist/plugins/pg-rate-limiter.d.ts +2 -32
  58. package/dist/plugins/pg-rate-limiter.js +4 -147
  59. package/dist/plugins/remote-env-k8s.d.ts +5 -38
  60. package/dist/plugins/remote-env-k8s.js +7 -213
  61. package/dist/plugins/sql-driver.d.ts +25 -0
  62. package/dist/plugins/sql-driver.js +59 -0
  63. package/dist/plugins/tidb-checkpoint-store.d.ts +1 -49
  64. package/dist/plugins/tidb-checkpoint-store.js +1 -191
  65. package/dist/plugins/tidb-image-bake.d.ts +1 -38
  66. package/dist/plugins/tidb-image-bake.js +1 -348
  67. package/dist/plugins/web-search.d.ts +4 -3
  68. package/dist/plugins/write-behind-counter.d.ts +14 -3
  69. package/dist/plugins/write-behind-counter.js +39 -12
  70. package/dist/principal-jwt.d.ts +44 -0
  71. package/dist/principal-jwt.js +95 -0
  72. package/dist/question.d.ts +2 -1
  73. package/dist/question.js +3 -2
  74. package/dist/run-local.js +2 -2
  75. package/dist/runtime-caps-resolver.d.ts +7 -7
  76. package/dist/runtime-caps-resolver.js +3 -3
  77. package/dist/security.d.ts +4 -74
  78. package/dist/security.js +6 -155
  79. package/dist/sema-registry.d.ts +5 -200
  80. package/dist/sema-registry.js +4 -567
  81. package/dist/tool-approval.d.ts +2 -1
  82. package/dist/tool-approval.js +3 -2
  83. package/package.json +1 -1
@@ -0,0 +1,18 @@
1
+ import { FileMemoryEngineBackend } from "@sema-agent/core";
2
+ import type { ServiceConfig } from "./config-types.js";
3
+ export declare function memoryScopeFor(config: ServiceConfig, principal?: string, projectId?: string): string | undefined;
4
+ export declare function memoryEngineRemoteLanePosture(config: ServiceConfig): {
5
+ lane: string;
6
+ posture: "dark" | "forced";
7
+ } | undefined;
8
+ export declare function memoryEngineBackendFor(config: ServiceConfig, fallbackRoot?: string): {
9
+ backend: FileMemoryEngineBackend;
10
+ root: string;
11
+ } | undefined;
12
+ export declare function memorySpecForRequest(scope: string | undefined, memoryWrite: boolean | undefined, defaultScopes?: string[]): {
13
+ scope: string;
14
+ scopes?: string[];
15
+ writeScope?: string | null;
16
+ scopeContract?: "v2";
17
+ } | undefined;
18
+ //# sourceMappingURL=memory-scope.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { join } from "node:path";
2
+ import { FileMemoryEngineBackend, resolveMemoryEngineRoot, deriveControlPlaneDir, formatUserScope, formatProjScope } from "@sema-agent/core";
3
+ export function memoryScopeFor(config, principal, projectId) {
4
+ if (config.requirePrincipal === true) {
5
+ if (config.memoryEngineBackend === "pg" || config.memoryEngineBackend === "tidb") {
6
+ if (!principal)
7
+ return undefined;
8
+ return projectId ? formatProjScope(principal, projectId) : formatUserScope(principal);
9
+ }
10
+ return undefined;
11
+ }
12
+ return config.memoryScope;
13
+ }
14
+ export function memoryEngineRemoteLanePosture(config) {
15
+ const lane = config.remoteExec?.provider;
16
+ if (lane === undefined || lane === "host")
17
+ return undefined;
18
+ return { lane, posture: config.memoryEngineRemoteLaneAllowed === true ? "forced" : "dark" };
19
+ }
20
+ export function memoryEngineBackendFor(config, fallbackRoot) {
21
+ if (config.requirePrincipal === true || !config.memoryEngineEnabled)
22
+ return undefined;
23
+ if (memoryEngineRemoteLanePosture(config)?.posture === "dark")
24
+ return undefined;
25
+ const root = resolveMemoryEngineRoot(config.memoryEngineDir ?? fallbackRoot);
26
+ const dir = join(root, "memory");
27
+ return { backend: new FileMemoryEngineBackend(dir, { controlDir: deriveControlPlaneDir(root, dir) }), root };
28
+ }
29
+ export function memorySpecForRequest(scope, memoryWrite, defaultScopes) {
30
+ if (!scope)
31
+ return undefined;
32
+ const isV2 = (k) => k.startsWith("user:") || k.startsWith("org:") || k.startsWith("proj:") || k.startsWith("userproj:");
33
+ const extras = (defaultScopes ?? []).filter((s, i, arr) => typeof s === "string" && s.length > 0 && s !== scope && arr.indexOf(s) === i);
34
+ const layered = extras.length > 0 ? { scopes: [scope, ...extras], writeScope: scope } : {};
35
+ const v2 = isV2(scope) || extras.some(isV2) ? { scopeContract: "v2" } : {};
36
+ return memoryWrite === false ? { scope, ...layered, writeScope: null, ...v2 } : { scope, ...layered, ...v2 };
37
+ }
38
+ //# sourceMappingURL=memory-scope.js.map
@@ -0,0 +1,34 @@
1
+ export interface InfraCostRates {
2
+ toolCallMicroUsd: number;
3
+ sandboxSecMicroUsd: number;
4
+ egressGbMicroUsd: number;
5
+ }
6
+ export interface InfraUsage {
7
+ toolCalls: number;
8
+ sandboxWalltimeMs: number;
9
+ egressBytes: number;
10
+ }
11
+ export interface InfraCostBreakdown {
12
+ toolCallMicroUsd: number;
13
+ sandboxWalltimeMicroUsd: number;
14
+ egressMicroUsd: number;
15
+ totalMicroUsd: number;
16
+ }
17
+ export interface LlmCostBreakdown {
18
+ llmRootMicroUsd: number;
19
+ nestedSubagentMicroUsd: number;
20
+ memoryConsolidationMicroUsd: number;
21
+ compactionMicroUsd: number;
22
+ }
23
+ export interface SupervisorCostBreakdown {
24
+ llm: LlmCostBreakdown | null;
25
+ infra: InfraCostBreakdown;
26
+ totalMicroUsd: number;
27
+ }
28
+ export declare function infraCost(usage: InfraUsage, rates: InfraCostRates): InfraCostBreakdown;
29
+ export declare function composeSupervisorCost(llm: LlmCostBreakdown | null, llmTotalMicroUsd: number, infra: InfraCostBreakdown): SupervisorCostBreakdown;
30
+ export declare function hasInfraPricing(r: InfraCostRates): boolean;
31
+ export declare function infraUsageFromEvents(events: ReadonlyArray<{
32
+ type?: string;
33
+ }>, runDurationMs: number): InfraUsage;
34
+ //# sourceMappingURL=cost-taxonomy.d.ts.map
@@ -0,0 +1,26 @@
1
+ const nonNeg = (n) => (Number.isFinite(n) && n > 0 ? n : 0);
2
+ export function infraCost(usage, rates) {
3
+ const toolCallMicroUsd = nonNeg(usage.toolCalls) * nonNeg(rates.toolCallMicroUsd);
4
+ const sandboxWalltimeMicroUsd = (nonNeg(usage.sandboxWalltimeMs) / 1000) * nonNeg(rates.sandboxSecMicroUsd);
5
+ const egressMicroUsd = (nonNeg(usage.egressBytes) / 1_000_000_000) * nonNeg(rates.egressGbMicroUsd);
6
+ return {
7
+ toolCallMicroUsd,
8
+ sandboxWalltimeMicroUsd,
9
+ egressMicroUsd,
10
+ totalMicroUsd: toolCallMicroUsd + sandboxWalltimeMicroUsd + egressMicroUsd,
11
+ };
12
+ }
13
+ export function composeSupervisorCost(llm, llmTotalMicroUsd, infra) {
14
+ return { llm, infra, totalMicroUsd: nonNeg(llmTotalMicroUsd) + infra.totalMicroUsd };
15
+ }
16
+ export function hasInfraPricing(r) {
17
+ return r.toolCallMicroUsd > 0 || r.sandboxSecMicroUsd > 0 || r.egressGbMicroUsd > 0;
18
+ }
19
+ export function infraUsageFromEvents(events, runDurationMs) {
20
+ return {
21
+ toolCalls: events.reduce((n, e) => (e.type === "tool_start" ? n + 1 : n), 0),
22
+ sandboxWalltimeMs: nonNeg(runDurationMs),
23
+ egressBytes: 0,
24
+ };
25
+ }
26
+ //# sourceMappingURL=cost-taxonomy.js.map
@@ -0,0 +1,75 @@
1
+ export interface PromptManifestRecord {
2
+ constitution: string;
3
+ blocks: Array<{
4
+ id: string;
5
+ chars: number;
6
+ hash: string;
7
+ }>;
8
+ sections?: Array<{
9
+ id: string;
10
+ slot: string;
11
+ carrier: string;
12
+ cadence: string;
13
+ cacheClass: string;
14
+ chars: number;
15
+ hash: string;
16
+ contentHash?: string;
17
+ }>;
18
+ tools?: Array<{
19
+ wireName: string;
20
+ aliases: string[];
21
+ contractId: string;
22
+ implementationRevision: string;
23
+ cardId: string;
24
+ shapeDigest: string;
25
+ wireSchemaDigest: string;
26
+ }>;
27
+ totalChars: number;
28
+ }
29
+ export declare function promptManifestRecordOf(e: {
30
+ constitution: string;
31
+ blocks: Array<{
32
+ id: string;
33
+ chars: number;
34
+ hash: string;
35
+ }>;
36
+ sections?: Array<{
37
+ id: string;
38
+ slot: string;
39
+ carrier: string;
40
+ cadence: string;
41
+ cacheClass: string;
42
+ chars: number;
43
+ hash: string;
44
+ contentHash?: string;
45
+ }>;
46
+ tools?: Array<{
47
+ wireName: string;
48
+ aliases: string[];
49
+ contractId: string;
50
+ implementationRevision: string;
51
+ cardId: string;
52
+ shapeDigest: string;
53
+ wireSchemaDigest: string;
54
+ }>;
55
+ totalChars: number;
56
+ }): PromptManifestRecord;
57
+ export declare function configAssembledRecordOf(e: {
58
+ catalogVersion?: unknown;
59
+ fields?: unknown;
60
+ overrideReasons?: unknown;
61
+ ts?: unknown;
62
+ }): Record<string, unknown> | undefined;
63
+ export declare class PromptManifestTracker {
64
+ private readonly byTask;
65
+ private readonly configByTask;
66
+ private readonly active;
67
+ private leaseCounter;
68
+ register(taskId: string): number;
69
+ record(taskId: string, m: PromptManifestRecord): void;
70
+ recordConfig(taskId: string, rec: Record<string, unknown>): void;
71
+ drainConfig(taskId: string): Array<Record<string, unknown>> | undefined;
72
+ drain(taskId: string): PromptManifestRecord[] | undefined;
73
+ clear(taskId: string, lease: number): void;
74
+ }
75
+ //# sourceMappingURL=prompt-manifest.d.ts.map
@@ -0,0 +1,82 @@
1
+ export function promptManifestRecordOf(e) {
2
+ return {
3
+ constitution: String(e.constitution),
4
+ blocks: (Array.isArray(e.blocks) ? e.blocks : []).map((b) => ({ id: String(b.id), chars: b.chars, hash: String(b.hash) })),
5
+ ...(Array.isArray(e.sections)
6
+ ? { sections: e.sections.map((s) => ({ id: String(s.id), slot: String(s.slot), carrier: String(s.carrier), cadence: String(s.cadence), cacheClass: String(s.cacheClass), chars: s.chars, hash: String(s.hash), ...(typeof s.contentHash === "string" ? { contentHash: s.contentHash } : {}) })) }
7
+ : {}),
8
+ ...(Array.isArray(e.tools)
9
+ ? { tools: e.tools.map((t) => ({ wireName: String(t.wireName), aliases: (Array.isArray(t.aliases) ? t.aliases : []).map(String), contractId: String(t.contractId), implementationRevision: String(t.implementationRevision), cardId: String(t.cardId), shapeDigest: String(t.shapeDigest), wireSchemaDigest: String(t.wireSchemaDigest) })) }
10
+ : {}),
11
+ totalChars: e.totalChars,
12
+ };
13
+ }
14
+ export function configAssembledRecordOf(e) {
15
+ const rec = {
16
+ catalogVersion: e.catalogVersion,
17
+ fields: e.fields,
18
+ ...(e.overrideReasons !== undefined ? { overrideReasons: e.overrideReasons } : {}),
19
+ ts: e.ts,
20
+ };
21
+ try {
22
+ if (JSON.stringify(rec).length > 32_768)
23
+ return undefined;
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ return rec;
29
+ }
30
+ export class PromptManifestTracker {
31
+ byTask = new Map();
32
+ configByTask = new Map();
33
+ active = new Map();
34
+ leaseCounter = 0;
35
+ register(taskId) {
36
+ if (!taskId)
37
+ return 0;
38
+ const lease = ++this.leaseCounter;
39
+ this.active.set(taskId, lease);
40
+ return lease;
41
+ }
42
+ record(taskId, m) {
43
+ if (!taskId || !this.active.has(taskId))
44
+ return;
45
+ const q = this.byTask.get(taskId);
46
+ if (q)
47
+ q.push(m);
48
+ else
49
+ this.byTask.set(taskId, [m]);
50
+ }
51
+ recordConfig(taskId, rec) {
52
+ if (!taskId || !this.active.has(taskId))
53
+ return;
54
+ const q = this.configByTask.get(taskId);
55
+ if (q)
56
+ q.push(rec);
57
+ else
58
+ this.configByTask.set(taskId, [rec]);
59
+ }
60
+ drainConfig(taskId) {
61
+ const q = this.configByTask.get(taskId);
62
+ if (!q || q.length === 0)
63
+ return undefined;
64
+ this.configByTask.delete(taskId);
65
+ return q;
66
+ }
67
+ drain(taskId) {
68
+ const q = this.byTask.get(taskId);
69
+ if (!q || q.length === 0)
70
+ return undefined;
71
+ this.byTask.delete(taskId);
72
+ return q;
73
+ }
74
+ clear(taskId, lease) {
75
+ if (this.active.get(taskId) !== lease)
76
+ return;
77
+ this.byTask.delete(taskId);
78
+ this.active.delete(taskId);
79
+ this.configByTask.delete(taskId);
80
+ }
81
+ }
82
+ //# sourceMappingURL=prompt-manifest.js.map
@@ -0,0 +1,67 @@
1
+ import type { Pool as MySqlPool } from "mysql2/promise";
2
+ import type { Pool as PgPool } from "pg";
3
+ import { type Checkpoint, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type ResumeOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor } from "@sema-agent/core";
4
+ import { type SqlDriver } from "./sql-driver.js";
5
+ export declare const TERMINAL_BACKSTOP_MS: number;
6
+ export declare const TERMINAL_GRACE_MS = 3600000;
7
+ export declare function tokenFingerprint(token: string): string;
8
+ export interface PendingCheckpoint {
9
+ sessionId: string;
10
+ scope: string;
11
+ toolName: string | null;
12
+ toolCallId: string | null;
13
+ boundCallId: string | null;
14
+ boundInputHash: string | null;
15
+ taskId: string | null;
16
+ input: unknown;
17
+ createdAt: number;
18
+ deadline: number | null;
19
+ riskDescriptor: RiskDescriptor | null;
20
+ }
21
+ export declare function boundedToolInput(args: unknown): unknown;
22
+ export declare class SqlCheckpointStore implements CheckpointStore {
23
+ protected readonly db: SqlDriver;
24
+ protected readonly logger?: {
25
+ info?(msg: string, meta?: unknown): void;
26
+ } | undefined;
27
+ constructor(db: SqlDriver, logger?: {
28
+ info?(msg: string, meta?: unknown): void;
29
+ } | undefined);
30
+ private q;
31
+ private json;
32
+ private isDupKey;
33
+ put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
34
+ get(token: CheckpointToken): Promise<Checkpoint | null>;
35
+ resolve(token: CheckpointToken, scope: string, outcome: ResumeOutcome, expect?: ResolveExpectation): Promise<boolean>;
36
+ reopen(token: CheckpointToken, scope: string, reason: ReopenReason): Promise<boolean>;
37
+ setPendingSteer(token: CheckpointToken, scope: string, steer: {
38
+ text: string;
39
+ trusted: boolean;
40
+ }): Promise<boolean>;
41
+ expire(token: CheckpointToken, scope: string): Promise<boolean>;
42
+ reap(scope: string, cutoff: number): Promise<number>;
43
+ reapExpired(cutoff: number): Promise<number>;
44
+ listExpiredApprovalGates(cutoff: number, limit?: number): Promise<Array<{
45
+ sessionId: string;
46
+ scope: string;
47
+ }>>;
48
+ listPending(scope?: string): Promise<PendingCheckpoint[]>;
49
+ listByScope(scope: string): Promise<CheckpointSummary[]>;
50
+ findPendingTokenBySession(sessionId: string, scope?: string): Promise<CheckpointToken | null>;
51
+ peekPendingScope(sessionId: string): Promise<string | null | undefined>;
52
+ putCtx(sessionId: string, ctx: unknown): Promise<void>;
53
+ getCtx<T = unknown>(sessionId: string): Promise<T | null>;
54
+ reapCtx(cutoff: number): Promise<number>;
55
+ deleteBySession(sessionId: string, owner: string | null): Promise<number>;
56
+ }
57
+ export declare class TiDBCheckpointStore extends SqlCheckpointStore {
58
+ constructor(pool: MySqlPool, logger?: {
59
+ info?(msg: string, meta?: unknown): void;
60
+ });
61
+ }
62
+ export declare class PgCheckpointStore extends SqlCheckpointStore {
63
+ constructor(pool: PgPool, logger?: {
64
+ info?(msg: string, meta?: unknown): void;
65
+ });
66
+ }
67
+ //# sourceMappingURL=checkpoint-store-sql.d.ts.map
@@ -0,0 +1,224 @@
1
+ import { createHash } from "node:crypto";
2
+ import { CheckpointError, validatePendingSteer, checkpointVersionOf, winnerFromOutcome, summarizeCheckpoint, MAX_SUPPORTED_CHECKPOINT_VERSION, } from "@sema-agent/core";
3
+ import { redactDeep } from "../trace/redact.js";
4
+ import { pgProtocolJsonStringify } from "./pg-safe-json.js";
5
+ import { parseJsonStrict as parseJson } from "./sql-row-helpers.js";
6
+ import { mysqlDriver, pgDriver } from "./sql-driver.js";
7
+ const MAX_TOOL_INPUT_CHARS = 8192;
8
+ export const TERMINAL_BACKSTOP_MS = Math.max(60_000, Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS) || 30 * 86_400_000);
9
+ export const TERMINAL_GRACE_MS = 3_600_000;
10
+ export function tokenFingerprint(token) {
11
+ return "sha256:" + createHash("sha256").update(token).digest("hex").slice(0, 12);
12
+ }
13
+ export function boundedToolInput(args) {
14
+ if (args === undefined)
15
+ return null;
16
+ const redacted = redactDeep(args);
17
+ const json = JSON.stringify(redacted);
18
+ if (json === undefined)
19
+ return null;
20
+ if (json.length > MAX_TOOL_INPUT_CHARS)
21
+ return { truncated: true, bytes: json.length };
22
+ return redacted;
23
+ }
24
+ const DUP_ENTRY = 1062;
25
+ const PG_UNIQUE_VIOLATION = "23505";
26
+ export class SqlCheckpointStore {
27
+ db;
28
+ logger;
29
+ constructor(db, logger) {
30
+ this.db = db;
31
+ this.logger = logger;
32
+ }
33
+ q(tidb, pg) {
34
+ return this.db.dialect === "tidb" ? tidb : pg;
35
+ }
36
+ json(value, label) {
37
+ return this.db.dialect === "tidb" ? JSON.stringify(value) : pgProtocolJsonStringify(value, label);
38
+ }
39
+ isDupKey(e) {
40
+ return this.db.dialect === "tidb"
41
+ ? e?.errno === DUP_ENTRY
42
+ : e?.code === PG_UNIQUE_VIOLATION;
43
+ }
44
+ async put(token, cp) {
45
+ const pa = cp.pendingAction;
46
+ const version = checkpointVersionOf(cp);
47
+ const toolInput = boundedToolInput(pa?.args);
48
+ try {
49
+ await this.db.query(this.q("INSERT INTO checkpoint (token, scope, session_id, version, status, tool_name, tool_call_id, tool_input, checkpoint, deadline, created_at, terminal_at, gate_kind, bound_input_hash, risk_descriptor) " +
50
+ "VALUES (?,?,?,?,'pending',?,?,?,?,?,?,?,?,?,?)", "INSERT INTO checkpoint (token, scope, session_id, version, status, tool_name, tool_call_id, tool_input, checkpoint, deadline, created_at, terminal_at, gate_kind, bound_input_hash, risk_descriptor) " +
51
+ "VALUES ($1,$2,$3,$4,'pending',$5,$6,$7::jsonb,$8::jsonb,$9,$10,$11,$12,$13,$14)"), [
52
+ token,
53
+ cp.scope,
54
+ cp.sessionId,
55
+ version,
56
+ pa?.toolName ?? null,
57
+ pa?.toolCallId ?? null,
58
+ toolInput === null ? null : this.json(toolInput, "approval tool_input"),
59
+ this.json(cp, "checkpoint"),
60
+ cp.deadline ?? null,
61
+ cp.createdAt,
62
+ Math.max(cp.createdAt + TERMINAL_BACKSTOP_MS, (cp.deadline ?? 0) + TERMINAL_GRACE_MS),
63
+ cp.gate?.kind ?? null,
64
+ pa?.boundInputHash ?? null,
65
+ ((g) => (g?.riskDescriptor ? this.json(g.riskDescriptor, "risk descriptor") : null))(cp.gate),
66
+ ]);
67
+ }
68
+ catch (e) {
69
+ if (this.isDupKey(e)) {
70
+ throw new CheckpointError("checkpoint.already_exists", `checkpoint token already exists`);
71
+ }
72
+ throw e;
73
+ }
74
+ }
75
+ async get(token) {
76
+ const { rows } = await this.db.query(this.q("SELECT version, status, checkpoint, outcome, reopen_reason, rev, pending_steer FROM checkpoint WHERE token = ?", "SELECT version, status, checkpoint, outcome, reopen_reason, rev, pending_steer FROM checkpoint WHERE token = $1"), [token]);
77
+ const r = rows[0];
78
+ if (!r)
79
+ return null;
80
+ const version = Number(r.version);
81
+ if (version > MAX_SUPPORTED_CHECKPOINT_VERSION) {
82
+ throw new Error(`checkpoint format version ${version} is newer than this build supports (max ${MAX_SUPPORTED_CHECKPOINT_VERSION}) — upgrade the service to resume it`);
83
+ }
84
+ const cp = parseJson(r.checkpoint);
85
+ cp.status = r.status;
86
+ const outcomeRaw = parseJson(r.outcome);
87
+ if (outcomeRaw)
88
+ cp.resolvedOutcome = winnerFromOutcome(outcomeRaw);
89
+ cp.reopenReason = r.reopen_reason ?? undefined;
90
+ cp.rev = r.rev == null ? 0 : Number(r.rev);
91
+ const steerRaw = parseJson(r.pending_steer);
92
+ if (steerRaw && cp.state)
93
+ cp.state.pendingSteer = steerRaw;
94
+ this.logger?.info?.("checkpoint_get", {
95
+ tokenFp: tokenFingerprint(token),
96
+ status: cp.status,
97
+ version,
98
+ hasWorkspaceHandle: !!cp.state?.workspaceHandle,
99
+ snapshotId: cp.state?.workspaceHandle?.snapshotId,
100
+ });
101
+ return cp;
102
+ }
103
+ async resolve(token, scope, outcome, expect) {
104
+ const params = [this.json(outcome, "checkpoint outcome"), Date.now(), token, scope];
105
+ if (expect)
106
+ params.push(expect.rev);
107
+ const res = await this.db.query(this.q("UPDATE checkpoint SET status = 'resolved', outcome = ?, decided_at = ?, rev = rev + 1, reopen_reason = NULL WHERE token = ? AND scope = ? AND status = 'pending'" +
108
+ (expect ? " AND rev = ?" : ""), "UPDATE checkpoint SET status = 'resolved', outcome = $1::jsonb, decided_at = $2, rev = rev + 1, reopen_reason = NULL WHERE token = $3 AND scope = $4 AND status = 'pending'" +
109
+ (expect ? " AND rev = $5" : "")), params);
110
+ return res.affected === 1;
111
+ }
112
+ async reopen(token, scope, reason) {
113
+ const res = await this.db.query(this.q("UPDATE checkpoint SET status = 'pending', reopen_reason = ?, rev = rev + 1 WHERE token = ? AND scope = ? AND status = 'resolved'", "UPDATE checkpoint SET status = 'pending', reopen_reason = $1, rev = rev + 1 WHERE token = $2 AND scope = $3 AND status = 'resolved'"), [reason, token, scope]);
114
+ return res.affected === 1;
115
+ }
116
+ async setPendingSteer(token, scope, steer) {
117
+ const clean = validatePendingSteer(steer);
118
+ const res = await this.db.query(this.q("UPDATE checkpoint SET pending_steer = ? WHERE token = ? AND scope = ? AND status = 'pending'", "UPDATE checkpoint SET pending_steer = $1 WHERE token = $2 AND scope = $3 AND status = 'pending'"), [this.json(clean, "pending steer"), token, scope]);
119
+ return res.affected > 0;
120
+ }
121
+ async expire(token, scope) {
122
+ const res = await this.db.query(this.q("UPDATE checkpoint SET status = 'expired', decided_at = ? WHERE token = ? AND scope = ? AND status = 'pending'", "UPDATE checkpoint SET status = 'expired', decided_at = $1 WHERE token = $2 AND scope = $3 AND status = 'pending'"), [Date.now(), token, scope]);
123
+ return res.affected === 1;
124
+ }
125
+ async reap(scope, cutoff) {
126
+ const res = await this.db.query(this.q("UPDATE checkpoint SET status = 'expired', decided_at = ? WHERE scope = ? AND status = 'pending' AND deadline IS NOT NULL AND deadline <= ?", "UPDATE checkpoint SET status = 'expired', decided_at = $1 WHERE scope = $2 AND status = 'pending' AND deadline IS NOT NULL AND deadline <= $3"), [Date.now(), scope, cutoff]);
127
+ return res.affected;
128
+ }
129
+ async reapExpired(cutoff) {
130
+ const res = await this.db.query(this.q("UPDATE checkpoint SET status = 'expired', decided_at = ? " +
131
+ "WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= ? AND (gate_kind IS NULL OR gate_kind NOT IN ('human','irreversible_ask') OR COALESCE(tool_name,'') = 'AskUserQuestion')) OR (terminal_at IS NOT NULL AND terminal_at <= ?))", "UPDATE checkpoint SET status = 'expired', decided_at = $1 " +
132
+ "WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= $2 AND (gate_kind IS NULL OR gate_kind NOT IN ('human','irreversible_ask') OR COALESCE(tool_name,'') = 'AskUserQuestion')) OR (terminal_at IS NOT NULL AND terminal_at <= $3))"), [Date.now(), cutoff, cutoff]);
133
+ return res.affected;
134
+ }
135
+ async listExpiredApprovalGates(cutoff, limit = 100) {
136
+ const { rows } = await this.db.query(this.q("SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND gate_kind IN ('human','irreversible_ask') " +
137
+ "AND COALESCE(tool_name,'') <> 'AskUserQuestion' AND deadline IS NOT NULL AND deadline <= ? ORDER BY deadline ASC LIMIT ?", "SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND gate_kind IN ('human','irreversible_ask') " +
138
+ "AND COALESCE(tool_name,'') <> 'AskUserQuestion' AND deadline IS NOT NULL AND deadline <= $1 ORDER BY deadline ASC LIMIT $2"), [cutoff, limit]);
139
+ return rows.map((r) => ({ sessionId: String(r.session_id), scope: String(r.scope) }));
140
+ }
141
+ async listPending(scope) {
142
+ const base = "SELECT c.session_id, c.scope, c.tool_name, c.tool_call_id, c.tool_input, c.bound_input_hash, c.risk_descriptor, c.created_at, c.deadline, ta.task_id " +
143
+ "FROM checkpoint c LEFT JOIN task_active ta ON ta.session_id = c.session_id WHERE c.status='pending'";
144
+ const { rows } = scope
145
+ ? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at ASC`, [scope])
146
+ : await this.db.query(`${base} ORDER BY c.created_at ASC`);
147
+ const out = rows.map((r) => {
148
+ const toolCallId = r.tool_call_id ?? null;
149
+ return {
150
+ sessionId: String(r.session_id),
151
+ scope: String(r.scope),
152
+ toolName: r.tool_name ?? null,
153
+ toolCallId,
154
+ boundCallId: toolCallId,
155
+ boundInputHash: r.bound_input_hash ?? null,
156
+ taskId: r.task_id ?? null,
157
+ input: r.tool_input ?? null,
158
+ createdAt: Number(r.created_at),
159
+ deadline: r.deadline == null ? null : Number(r.deadline),
160
+ riskDescriptor: parseJson(r.risk_descriptor),
161
+ };
162
+ });
163
+ return out.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
164
+ }
165
+ async listByScope(scope) {
166
+ const { rows } = await this.db.query(this.q("SELECT checkpoint, status FROM checkpoint WHERE status = 'pending' AND scope = ? ORDER BY created_at ASC LIMIT 500", "SELECT checkpoint, status FROM checkpoint WHERE status = 'pending' AND scope = $1 ORDER BY created_at ASC LIMIT 500"), [scope]);
167
+ const out = [];
168
+ for (const r of rows) {
169
+ const cp = parseJson(r.checkpoint);
170
+ if (!cp)
171
+ continue;
172
+ cp.status = r.status;
173
+ out.push(summarizeCheckpoint(cp));
174
+ }
175
+ return out;
176
+ }
177
+ async findPendingTokenBySession(sessionId, scope) {
178
+ const { rows } = scope
179
+ ? await this.db.query(this.q("SELECT token FROM checkpoint WHERE session_id=? AND scope=? AND status='pending' LIMIT 1", "SELECT token FROM checkpoint WHERE session_id=$1 AND scope=$2 AND status='pending' LIMIT 1"), [sessionId, scope])
180
+ : await this.db.query(this.q("SELECT token FROM checkpoint WHERE session_id=? AND status='pending' LIMIT 1", "SELECT token FROM checkpoint WHERE session_id=$1 AND status='pending' LIMIT 1"), [sessionId]);
181
+ return rows[0] ? String(rows[0].token) : null;
182
+ }
183
+ async peekPendingScope(sessionId) {
184
+ const { rows } = await this.db.query(this.q("SELECT scope FROM checkpoint WHERE session_id=? AND status='pending' LIMIT 1", "SELECT scope FROM checkpoint WHERE session_id=$1 AND status='pending' LIMIT 1"), [sessionId]);
185
+ if (!rows[0])
186
+ return undefined;
187
+ return rows[0].scope ?? null;
188
+ }
189
+ async putCtx(sessionId, ctx) {
190
+ await this.db.query(this.q("INSERT INTO checkpoint_ctx (session_id, ctx, updated_at) VALUES (?,?,?) " +
191
+ "ON DUPLICATE KEY UPDATE ctx = VALUES(ctx), updated_at = VALUES(updated_at)", "INSERT INTO checkpoint_ctx (session_id, ctx, updated_at) VALUES ($1,$2::jsonb,$3) " +
192
+ "ON CONFLICT (session_id) DO UPDATE SET ctx = EXCLUDED.ctx, updated_at = EXCLUDED.updated_at"), [sessionId, this.json(ctx, "checkpoint ctx"), Date.now()]);
193
+ }
194
+ async getCtx(sessionId) {
195
+ const { rows } = await this.db.query(this.q("SELECT ctx FROM checkpoint_ctx WHERE session_id=?", "SELECT ctx FROM checkpoint_ctx WHERE session_id=$1"), [sessionId]);
196
+ return rows[0] ? parseJson(rows[0].ctx) : null;
197
+ }
198
+ async reapCtx(cutoff) {
199
+ const res = await this.db.query(this.q("DELETE FROM checkpoint_ctx WHERE updated_at < ? " +
200
+ "AND session_id NOT IN (SELECT session_id FROM checkpoint WHERE status='pending') " +
201
+ "AND session_id NOT IN (SELECT session_id FROM task_active)", "DELETE FROM checkpoint_ctx WHERE updated_at < $1 " +
202
+ "AND session_id NOT IN (SELECT session_id FROM checkpoint WHERE status='pending') " +
203
+ "AND session_id NOT IN (SELECT session_id FROM task_active)"), [cutoff]);
204
+ return res.affected;
205
+ }
206
+ async deleteBySession(sessionId, owner) {
207
+ const ownerGuard = this.q("EXISTS (SELECT 1 FROM session_meta sm WHERE sm.session_id = ? AND sm.owner <=> ?)", "EXISTS (SELECT 1 FROM session_meta sm WHERE sm.session_id = $1 AND sm.owner IS NOT DISTINCT FROM $2)");
208
+ const params = this.db.dialect === "tidb" ? [sessionId, sessionId, owner] : [sessionId, owner];
209
+ const res = await this.db.query(this.q(`DELETE FROM checkpoint WHERE session_id = ? AND ${ownerGuard}`, `DELETE FROM checkpoint WHERE session_id = $1 AND ${ownerGuard}`), params);
210
+ await this.db.query(this.q(`DELETE FROM checkpoint_ctx WHERE session_id = ? AND ${ownerGuard}`, `DELETE FROM checkpoint_ctx WHERE session_id = $1 AND ${ownerGuard}`), params);
211
+ return res.affected;
212
+ }
213
+ }
214
+ export class TiDBCheckpointStore extends SqlCheckpointStore {
215
+ constructor(pool, logger) {
216
+ super(mysqlDriver(pool), logger);
217
+ }
218
+ }
219
+ export class PgCheckpointStore extends SqlCheckpointStore {
220
+ constructor(pool, logger) {
221
+ super(pgDriver(pool), logger);
222
+ }
223
+ }
224
+ //# sourceMappingURL=checkpoint-store-sql.js.map
@@ -0,0 +1,53 @@
1
+ import type { Pool as MySqlPool } from "mysql2/promise";
2
+ import type { Pool as PgPool, PoolClient } from "pg";
3
+ import { type SqlDriver } from "./sql-driver.js";
4
+ import { type BakeStatus, type BakeState, type BakeRecord, type BakeEvent, type CreateBakeInput, type BakeTerminal } from "./store-contracts.js";
5
+ export declare const PG_IMAGE_BAKE_SCHEMA: string[];
6
+ export declare function ensurePgImageBakeSchema(pool: PgPool | PoolClient, poolName?: string): Promise<void>;
7
+ export declare class SqlImageBake {
8
+ protected readonly db: SqlDriver;
9
+ protected readonly poolName: string;
10
+ constructor(db: SqlDriver, poolName?: string);
11
+ private q;
12
+ private insertParams;
13
+ createBake(input: CreateBakeInput): Promise<{
14
+ created: boolean;
15
+ bake: BakeRecord;
16
+ }>;
17
+ createBakeIfIdle(input: CreateBakeInput): Promise<{
18
+ created: boolean;
19
+ bake: BakeRecord | null;
20
+ }>;
21
+ getBake(bakeId: string): Promise<BakeRecord | null>;
22
+ getBakeByIdem(idemKey: string): Promise<BakeRecord | null>;
23
+ findActiveByArgv(argv: string[]): Promise<BakeRecord | null>;
24
+ findActiveAny(): Promise<BakeRecord | null>;
25
+ findNextQueuedId(): Promise<string | null>;
26
+ claimBake(bakeId: string, runnerId: string, leaseMs: number): Promise<BakeRecord | null>;
27
+ appendEvent(bakeId: string, kind: string, data: unknown, opts?: {
28
+ lineOrd?: number | null;
29
+ level?: string | null;
30
+ }): Promise<number | null>;
31
+ private lineOrdExists;
32
+ maxSeq(bakeId: string): Promise<number>;
33
+ retainedFrom(bakeId: string): Promise<number>;
34
+ getEvents(bakeId: string, afterSeq: number): Promise<BakeEvent[]>;
35
+ setStatus(bakeId: string, status: BakeStatus): Promise<void>;
36
+ setState(bakeId: string, state: BakeState): Promise<void>;
37
+ setTerminal(bakeId: string, t: BakeTerminal): Promise<number>;
38
+ private terminalParamsTidb;
39
+ private terminalParamsPg;
40
+ setIndexId(bakeId: string, indexId: string): Promise<boolean>;
41
+ heartbeatLease(bakeId: string, runnerId: string, leaseMs: number): Promise<boolean>;
42
+ requestCancel(bakeId: string): Promise<boolean>;
43
+ isCancelRequested(bakeId: string): Promise<boolean>;
44
+ reapStaleBakes(olderThanMs: number): Promise<number>;
45
+ private tx;
46
+ }
47
+ export declare class TiDBImageBake extends SqlImageBake {
48
+ constructor(pool: MySqlPool, poolName?: string);
49
+ }
50
+ export declare class PgImageBake extends SqlImageBake {
51
+ constructor(pool: PgPool, poolName?: string);
52
+ }
53
+ //# sourceMappingURL=image-bake-store-sql.d.ts.map