@vercel/factory 0.0.17 → 0.0.18
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.
- package/CHANGELOG.md +46 -0
- package/dist/api-contracts.d.mts +665 -715
- package/dist/api-contracts.mjs +91 -4
- package/dist/api-contracts.mjs.map +1 -1
- package/dist/api-task-graph.mjs +14 -5
- package/dist/api-task-graph.mjs.map +1 -1
- package/dist/api.d.mts +58 -11
- package/dist/api.mjs +715 -399
- package/dist/api.mjs.map +1 -1
- package/dist/approval-access.d.mts +1 -1
- package/dist/approval-contracts.d.mts +3 -4
- package/dist/approval-contracts.mjs +1 -2
- package/dist/approval-contracts.mjs.map +1 -1
- package/dist/blob/index.d.mts +1 -1
- package/dist/budget.mjs +20 -3
- package/dist/budget.mjs.map +1 -1
- package/dist/change-verification/brief.d.mts +1 -1
- package/dist/change-verification/dispatch.mjs +8 -1
- package/dist/change-verification/dispatch.mjs.map +1 -1
- package/dist/changes/eve-record-change.mjs +4 -1
- package/dist/changes/eve-record-change.mjs.map +1 -1
- package/dist/client-stream.d.mts +19 -2
- package/dist/client-stream.mjs +115 -53
- package/dist/client-stream.mjs.map +1 -1
- package/dist/client-transcript.d.mts +25 -2
- package/dist/client-transcript.mjs +95 -13
- package/dist/client-transcript.mjs.map +1 -1
- package/dist/client.d.mts +145 -19
- package/dist/client.mjs +89 -16
- package/dist/client.mjs.map +1 -1
- package/dist/code-review/contracts.d.mts +1 -0
- package/dist/code-review/eve-post-review.d.mts +4 -2
- package/dist/code-review/eve-post-review.mjs +8 -0
- package/dist/code-review/eve-post-review.mjs.map +1 -1
- package/dist/code-review/github-reporter.d.mts +6 -2
- package/dist/code-review/github-reporter.mjs +86 -35
- package/dist/code-review/github-reporter.mjs.map +1 -1
- package/dist/code-review/preparation.d.mts +2 -1
- package/dist/code-review/preparation.mjs +2 -1
- package/dist/code-review/preparation.mjs.map +1 -1
- package/dist/code-review/stall.mjs +11 -2
- package/dist/code-review/stall.mjs.map +1 -1
- package/dist/connectors/github.d.mts +1 -1
- package/dist/connectors/index.d.mts +1 -1
- package/dist/connectors/slack.d.mts +1 -1
- package/dist/delivery-metrics/metrics.d.mts +1 -1
- package/dist/dispatch.mjs +3 -3
- package/dist/dispatch.mjs.map +1 -1
- package/dist/eve/index.d.mts +1 -1
- package/dist/eve/index.mjs +8 -1
- package/dist/eve/index.mjs.map +1 -1
- package/dist/eve/task-execution.d.mts +4 -4
- package/dist/eve/transcript.d.mts +3 -1
- package/dist/eve/transcript.mjs.map +1 -1
- package/dist/finding-remediation/admission.d.mts +1 -1
- package/dist/finding-remediation/brief.d.mts +1 -1
- package/dist/github-app.d.mts +1 -1
- package/dist/github-publication.d.mts +1 -1
- package/dist/github.d.mts +9 -1
- package/dist/github.mjs +29 -11
- package/dist/github.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/intake-contracts.d.mts +1 -0
- package/dist/intake-contracts.mjs +2 -1
- package/dist/intake-contracts.mjs.map +1 -1
- package/dist/integrations/github-issues.d.mts +53 -0
- package/dist/integrations/github-issues.mjs +170 -0
- package/dist/integrations/github-issues.mjs.map +1 -0
- package/dist/integrations/github.d.mts +2 -1
- package/dist/integrations/github.mjs +2 -1
- package/dist/multi-repository-changes/coordination.mjs +1 -1
- package/dist/planning/reconcile.mjs +15 -2
- package/dist/planning/reconcile.mjs.map +1 -1
- package/dist/postgres/index.d.mts +2 -1
- package/dist/postgres/index.mjs +11 -5
- package/dist/postgres/index.mjs.map +1 -1
- package/dist/postgres/indexed-codec.mjs +137 -0
- package/dist/postgres/indexed-codec.mjs.map +1 -0
- package/dist/postgres/indexed-schema.mjs +99 -0
- package/dist/postgres/indexed-schema.mjs.map +1 -0
- package/dist/postgres/indexed.d.mts +28 -0
- package/dist/postgres/indexed.mjs +277 -0
- package/dist/postgres/indexed.mjs.map +1 -0
- package/dist/postgres/record-predicates.mjs +23 -0
- package/dist/postgres/record-predicates.mjs.map +1 -0
- package/dist/presets/software-development/deepsec-brief.d.mts +1 -1
- package/dist/presets/software-development/dispatch.mjs +1 -1
- package/dist/presets/software-development/dispatch.mjs.map +1 -1
- package/dist/presets/software-development/recovery.mjs +9 -2
- package/dist/presets/software-development/recovery.mjs.map +1 -1
- package/dist/pull-requests/eve-fetch.d.mts +4 -1
- package/dist/pull-requests/eve-fetch.mjs +9 -0
- package/dist/pull-requests/eve-fetch.mjs.map +1 -1
- package/dist/pull-requests/github-publisher.d.mts +1 -1
- package/dist/pull-requests/github-publisher.mjs +19 -7
- package/dist/pull-requests/github-publisher.mjs.map +1 -1
- package/dist/repositories.mjs +20 -13
- package/dist/repositories.mjs.map +1 -1
- package/dist/sandbox/execution-run-ledger.d.mts +2 -2
- package/dist/sandbox/execution-run-ledger.mjs.map +1 -1
- package/dist/sandbox/execution.d.mts +2 -2
- package/dist/sandbox/execution.mjs.map +1 -1
- package/dist/schema/catalog.mjs +1 -1
- package/dist/schema/change.d.mts +1 -1
- package/dist/schema/communication.d.mts +2 -2
- package/dist/schema/factory-config.d.mts +1 -1
- package/dist/schema/outbox.d.mts +6 -6
- package/dist/schema/session-summary.d.mts +218 -0
- package/dist/schema/session-summary.mjs +68 -0
- package/dist/schema/session-summary.mjs.map +1 -0
- package/dist/schema/session.d.mts +39 -59
- package/dist/schema/session.mjs +6 -6
- package/dist/schema/session.mjs.map +1 -1
- package/dist/schema/task-graph.d.mts +24 -24
- package/dist/schema/transcript.d.mts +141 -3
- package/dist/schema/transcript.mjs +19 -3
- package/dist/schema/transcript.mjs.map +1 -1
- package/dist/schema/transitions.d.mts +1 -1
- package/dist/schema/transitions.mjs +1 -1
- package/dist/schema/transitions.mjs.map +1 -1
- package/dist/signals.d.mts +1 -1
- package/dist/stall.mjs +10 -2
- package/dist/stall.mjs.map +1 -1
- package/dist/storage.d.mts +10 -1
- package/dist/storage.mjs +7 -1
- package/dist/store/approvals.d.mts +14 -0
- package/dist/store/approvals.mjs +78 -0
- package/dist/store/approvals.mjs.map +1 -0
- package/dist/store/conversations.d.mts +59 -0
- package/dist/store/conversations.mjs +549 -0
- package/dist/store/conversations.mjs.map +1 -0
- package/dist/store/driver.d.mts +1 -1
- package/dist/store/driver.mjs.map +1 -1
- package/dist/store/engine.d.mts +38 -16
- package/dist/store/engine.mjs +229 -106
- package/dist/store/engine.mjs.map +1 -1
- package/dist/store/indexed-engine.d.mts +57 -0
- package/dist/store/indexed-engine.mjs +155 -0
- package/dist/store/indexed-engine.mjs.map +1 -0
- package/dist/store/indexed-memory.d.mts +7 -0
- package/dist/store/indexed-memory.mjs +200 -0
- package/dist/store/indexed-memory.mjs.map +1 -0
- package/dist/store/indexed-projections.d.mts +280 -0
- package/dist/store/indexed-projections.mjs +481 -0
- package/dist/store/indexed-projections.mjs.map +1 -0
- package/dist/store/indexed.d.mts +101 -0
- package/dist/store/indexed.mjs +19 -0
- package/dist/store/indexed.mjs.map +1 -0
- package/dist/store/iterate.d.mts +9 -0
- package/dist/store/iterate.mjs +23 -0
- package/dist/store/iterate.mjs.map +1 -0
- package/dist/store/query-scope.mjs +62 -0
- package/dist/store/query-scope.mjs.map +1 -0
- package/dist/store/query.d.mts +32 -0
- package/dist/store/query.mjs +103 -0
- package/dist/store/query.mjs.map +1 -0
- package/dist/store/session-history.mjs +24 -0
- package/dist/store/session-history.mjs.map +1 -0
- package/dist/store/slack-pr-notifications.d.mts +2 -2
- package/dist/store/slack-pr-notifications.mjs.map +1 -1
- package/dist/store/state.d.mts +26 -0
- package/dist/store/state.mjs +95 -0
- package/dist/store/state.mjs.map +1 -0
- package/dist/store/task-effects.d.mts +1 -1
- package/dist/store/task-graphs.d.mts +1 -1
- package/dist/store/task-messages.d.mts +1 -1
- package/dist/store/task-work.d.mts +1 -1
- package/dist/store/task-work.mjs +1 -1
- package/dist/store/transcripts.mjs +308 -0
- package/dist/store/transcripts.mjs.map +1 -0
- package/dist/sweep.d.mts +1 -1
- package/dist/sweep.mjs +12 -7
- package/dist/sweep.mjs.map +1 -1
- package/dist/task-communication.mjs +15 -1
- package/dist/task-communication.mjs.map +1 -1
- package/dist/task-protocols.d.mts +3 -3
- package/dist/tasks.d.mts +1 -1
- package/dist/vercel.d.mts +1 -1
- package/dist/work-triage/find-existing-work.d.mts +1 -1
- package/dist/work-triage/find-existing-work.mjs +12 -2
- package/dist/work-triage/find-existing-work.mjs.map +1 -1
- package/docs/index.md +4 -0
- package/docs/recipes/persistence-recovery.md +15 -9
- package/docs/recipes/postgres-indexed-engine.md +108 -0
- package/docs/recipes/postgres-indexed-storage.md +145 -0
- package/package.json +3 -1
package/dist/repositories.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "./schema/common.mjs";
|
|
2
2
|
import { repositorySlugSchema } from "./schema/repository.mjs";
|
|
3
|
+
import { RecordExistsError, VersionConflictError } from "./store/driver.mjs";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
4
5
|
//#region src/repositories.ts
|
|
5
6
|
/** Derives a stable repository ID from a validated canonical repository slug. */
|
|
@@ -13,19 +14,25 @@ async function ensureConfiguredRepository(stores, input) {
|
|
|
13
14
|
if (!input.configuredSlugs.map((configured) => repositorySlugSchema.parse(configured.toLowerCase())).includes(slug)) throw new Error(`Repository ${slug} is not configured for this factory.`);
|
|
14
15
|
const id = repositoryIdForSlug(slug);
|
|
15
16
|
const dependsOn = input.dependsOn.map((dependency) => repositorySlugSchema.parse(dependency.toLowerCase()));
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
for (let retry = 0;; retry += 1) {
|
|
18
|
+
const existing = await stores.repositories.get(id);
|
|
19
|
+
if (existing !== null && existing.defaultBranch === input.defaultBranch && existing.dependsOn.length === dependsOn.length && existing.dependsOn.every((dependency, index) => dependency === dependsOn[index])) return existing;
|
|
20
|
+
try {
|
|
21
|
+
return await stores.repositories.put({
|
|
22
|
+
...existing ?? {
|
|
23
|
+
schemaVersion: 1,
|
|
24
|
+
id,
|
|
25
|
+
slug,
|
|
26
|
+
enabled: true,
|
|
27
|
+
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28
|
+
},
|
|
29
|
+
defaultBranch: input.defaultBranch,
|
|
30
|
+
dependsOn
|
|
31
|
+
});
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (!(error instanceof RecordExistsError || error instanceof VersionConflictError) || retry === 4) throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
29
36
|
}
|
|
30
37
|
//#endregion
|
|
31
38
|
export { ensureConfiguredRepository, repositoryIdForSlug };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repositories.mjs","names":[],"sources":["../src/repositories.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { SCHEMA_VERSION } from \"./schema/common\";\nimport { repositorySlugSchema, type Repository, type RepositorySlug } from \"./schema/repository\";\nimport type { RepositoryId } from \"./schema/id\";\nimport type { FactoryStores } from \"./store/engine\";\n\n/** Derives a stable repository ID from a validated canonical repository slug. */\nexport function repositoryIdForSlug(slug: RepositorySlug): RepositoryId {\n const canonical = repositorySlugSchema.parse(slug.toLowerCase());\n return `repo_${createHash(\"sha256\").update(canonical).digest(\"hex\").slice(0, 32)}`;\n}\n\n/** Repository metadata and configured allowlist used to admit or refresh one repository. */\nexport interface EnsureConfiguredRepositoryInput {\n readonly slug: RepositorySlug;\n readonly configuredSlugs: readonly RepositorySlug[];\n readonly defaultBranch: string;\n readonly dependsOn: readonly RepositorySlug[];\n}\n\n/** Admits or refreshes a repository only when its slug is explicitly configured. */\nexport async function ensureConfiguredRepository(\n stores: Pick<FactoryStores, \"repositories\">,\n input: EnsureConfiguredRepositoryInput,\n): Promise<Repository> {\n const slug = repositorySlugSchema.parse(input.slug.toLowerCase());\n const configuredSlugs = input.configuredSlugs.map((configured) =>\n repositorySlugSchema.parse(configured.toLowerCase()),\n );\n if (!configuredSlugs.includes(slug)) {\n throw new Error(`Repository ${slug} is not configured for this factory.`);\n }\n const id = repositoryIdForSlug(slug);\n const dependsOn = input.dependsOn.map((dependency) =>\n repositorySlugSchema.parse(dependency.toLowerCase()),\n );\n const existing = await stores.repositories.get(id);\n
|
|
1
|
+
{"version":3,"file":"repositories.mjs","names":[],"sources":["../src/repositories.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { SCHEMA_VERSION } from \"./schema/common\";\nimport { repositorySlugSchema, type Repository, type RepositorySlug } from \"./schema/repository\";\nimport type { RepositoryId } from \"./schema/id\";\nimport { RecordExistsError, VersionConflictError } from \"./store/driver\";\nimport type { FactoryStores } from \"./store/engine\";\n\n/** Derives a stable repository ID from a validated canonical repository slug. */\nexport function repositoryIdForSlug(slug: RepositorySlug): RepositoryId {\n const canonical = repositorySlugSchema.parse(slug.toLowerCase());\n return `repo_${createHash(\"sha256\").update(canonical).digest(\"hex\").slice(0, 32)}`;\n}\n\n/** Repository metadata and configured allowlist used to admit or refresh one repository. */\nexport interface EnsureConfiguredRepositoryInput {\n readonly slug: RepositorySlug;\n readonly configuredSlugs: readonly RepositorySlug[];\n readonly defaultBranch: string;\n readonly dependsOn: readonly RepositorySlug[];\n}\n\n/** Admits or refreshes a repository only when its slug is explicitly configured. */\nexport async function ensureConfiguredRepository(\n stores: Pick<FactoryStores, \"repositories\">,\n input: EnsureConfiguredRepositoryInput,\n): Promise<Repository> {\n const slug = repositorySlugSchema.parse(input.slug.toLowerCase());\n const configuredSlugs = input.configuredSlugs.map((configured) =>\n repositorySlugSchema.parse(configured.toLowerCase()),\n );\n if (!configuredSlugs.includes(slug)) {\n throw new Error(`Repository ${slug} is not configured for this factory.`);\n }\n const id = repositoryIdForSlug(slug);\n const dependsOn = input.dependsOn.map((dependency) =>\n repositorySlugSchema.parse(dependency.toLowerCase()),\n );\n for (let retry = 0; ; retry += 1) {\n const existing = await stores.repositories.get(id);\n if (\n existing !== null &&\n existing.defaultBranch === input.defaultBranch &&\n existing.dependsOn.length === dependsOn.length &&\n existing.dependsOn.every((dependency, index) => dependency === dependsOn[index])\n ) {\n return existing;\n }\n try {\n return await stores.repositories.put({\n ...(existing ?? {\n schemaVersion: SCHEMA_VERSION,\n id,\n slug,\n enabled: true,\n connectedAt: new Date().toISOString(),\n }),\n defaultBranch: input.defaultBranch,\n dependsOn,\n });\n } catch (error) {\n if (\n !(error instanceof RecordExistsError || error instanceof VersionConflictError) ||\n retry === 4\n )\n throw error;\n }\n }\n}\n"],"mappings":";;;;;;AAQA,SAAgB,oBAAoB,MAAoC;CACtE,MAAM,YAAY,qBAAqB,MAAM,KAAK,YAAY,CAAC;CAC/D,OAAO,QAAQ,WAAW,QAAQ,CAAC,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;AACjF;;AAWA,eAAsB,2BACpB,QACA,OACqB;CACrB,MAAM,OAAO,qBAAqB,MAAM,MAAM,KAAK,YAAY,CAAC;CAIhE,IAAI,CAHoB,MAAM,gBAAgB,KAAK,eACjD,qBAAqB,MAAM,WAAW,YAAY,CAAC,CAElC,CAAC,CAAC,SAAS,IAAI,GAChC,MAAM,IAAI,MAAM,cAAc,KAAK,qCAAqC;CAE1E,MAAM,KAAK,oBAAoB,IAAI;CACnC,MAAM,YAAY,MAAM,UAAU,KAAK,eACrC,qBAAqB,MAAM,WAAW,YAAY,CAAC,CACrD;CACA,KAAK,IAAI,QAAQ,IAAK,SAAS,GAAG;EAChC,MAAM,WAAW,MAAM,OAAO,aAAa,IAAI,EAAE;EACjD,IACE,aAAa,QACb,SAAS,kBAAkB,MAAM,iBACjC,SAAS,UAAU,WAAW,UAAU,UACxC,SAAS,UAAU,OAAO,YAAY,UAAU,eAAe,UAAU,MAAM,GAE/E,OAAO;EAET,IAAI;GACF,OAAO,MAAM,OAAO,aAAa,IAAI;IACnC,GAAI,YAAY;KACd,eAAA;KACA;KACA;KACA,SAAS;KACT,8BAAa,IAAI,KAAK,EAAA,CAAE,YAAY;IACtC;IACA,eAAe,MAAM;IACrB;GACF,CAAC;EACH,SAAS,OAAO;GACd,IACE,EAAE,iBAAiB,qBAAqB,iBAAiB,yBACzD,UAAU,GAEV,MAAM;EACV;CACF;AACF"}
|
|
@@ -3,7 +3,7 @@ import { ExecutionReference } from "../schema/task.mjs";
|
|
|
3
3
|
import "../workflows.mjs";
|
|
4
4
|
import { MillisecondClock } from "../schema/common.mjs";
|
|
5
5
|
import { CodingExecutionRequest, ExecutionLaunchKey, ExecutionResult } from "../schema/execution.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { ConditionalStateStore } from "../store/state.mjs";
|
|
7
7
|
import { HarnessTurnUsage } from "./harness-runner.mjs";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
//#region src/sandbox/execution-run-ledger.d.ts
|
|
@@ -72,7 +72,7 @@ type WithoutObservedExecution<Result extends ExecutionResult> = Result extends E
|
|
|
72
72
|
type ExecutionOutcome = WithoutObservedExecution<ExecutionResult>;
|
|
73
73
|
/** Durable state, clock, and model pricing used to construct an execution ledger. */
|
|
74
74
|
interface ExecutionRunLedgerOptions {
|
|
75
|
-
state:
|
|
75
|
+
state: ConditionalStateStore;
|
|
76
76
|
now?: MillisecondClock;
|
|
77
77
|
calculateCostUsd?(model: string, turns: readonly HarnessTurnUsage[]): number | undefined;
|
|
78
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execution-run-ledger.mjs","names":[],"sources":["../../src/sandbox/execution-run-ledger.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport { z } from \"zod\";\nimport { ExecutionLaunchPendingError } from \"../dispatch\";\nimport type { MillisecondClock } from \"../schema/common\";\nimport {\n codingExecutionRequestSchema,\n executionLaunchKeySchema,\n executionResultSchema,\n type CodingExecutionRequest,\n type ExecutionLaunchKey,\n type ExecutionResult,\n} from \"../schema/execution\";\nimport { executionReferenceSchema, type ExecutionReference } from \"../schema/task\";\nimport { jsonValueSchema } from \"../schema/work\";\nimport type { BlobClient } from \"../blob/index\";\nimport type { HarnessTurnUsage } from \"./harness-runner\";\n\nconst turnUsageSchema = z.strictObject({\n inputTokens: z.number().int().nonnegative(),\n noCacheInputTokens: z.number().int().nonnegative(),\n cacheReadTokens: z.number().int().nonnegative(),\n cacheWriteTokens: z.number().int().nonnegative(),\n outputTokens: z.number().int().nonnegative(),\n complete: z.boolean().optional(),\n});\n\nconst runRecordSchema = z.strictObject({\n key: executionLaunchKeySchema,\n request: codingExecutionRequestSchema,\n execution: executionReferenceSchema,\n harnessSessionId: z.string().min(1),\n model: z.string().min(1),\n reportSchema: jsonValueSchema,\n createdAtMs: z.number().int().nonnegative(),\n launched: z.boolean(),\n startedAtMs: z.number().int().nonnegative().optional(),\n /** Set durably right after the prompt was accepted by the runtime. */\n promptedAtMs: z.number().int().nonnegative().optional(),\n resumeState: jsonValueSchema.optional(),\n turns: z.array(turnUsageSchema).readonly(),\n /** Unobserved spend makes cost permanently unknown, even if later usage is measured. */\n lostUsage: z.boolean().optional(),\n result: executionResultSchema.optional(),\n});\n\n/** Durable identity, continuation, usage, and terminal result for one sandbox execution. */\nexport type RunRecord = z.infer<typeof runRecordSchema>;\n\nconst sameJson = (left: unknown, right: unknown) =>\n isDeepStrictEqual(\n left === undefined ? undefined : JSON.parse(JSON.stringify(left)),\n right === undefined ? undefined : JSON.parse(JSON.stringify(right)),\n );\n// SDK values can carry non-plain properties; validate their persisted JSON form.\nconst asStoredJson = (value: unknown) => JSON.parse(JSON.stringify(value ?? null)) as unknown;\n\n/** A run record paired with the ETag required for its next confirmed CAS update. */\nexport interface RunSnapshot {\n record: RunRecord;\n etag: string;\n}\n\n/** Workflow-owned outcome fields combined with observed execution identity and usage at finalize time. */\ntype WithoutObservedExecution<Result extends ExecutionResult> = Result extends ExecutionResult\n ? Omit<Result, \"execution\" | \"usage\">\n : never;\n\n/** Workflow-owned outcome fields before observed execution identity and usage are attached. */\nexport type ExecutionOutcome = WithoutObservedExecution<ExecutionResult>;\nconst runPrefix = \"factory-executions/\";\n\n/** Durable state, clock, and model pricing used to construct an execution ledger. */\nexport interface ExecutionRunLedgerOptions {\n state: BlobClient;\n now?: MillisecondClock;\n calculateCostUsd?(model: string, turns: readonly HarnessTurnUsage[]): number | undefined;\n}\n\n/** Immutable admission arguments for one stable sandbox execution key. */\nexport interface AdmitExecutionRunInput {\n request: CodingExecutionRequest;\n key: ExecutionLaunchKey;\n model: string;\n environmentId: string;\n reportSchema?: unknown;\n}\n\n/** Identifies the prompt acceptance to persist for one admitted execution run. */\nexport interface MarkExecutionRunPromptedInput {\n readonly base: RunSnapshot;\n readonly resumeState: unknown;\n readonly origin: \"this-driver\" | \"recovered\";\n}\n\n/** Identifies the first measured turn to persist for one admitted execution run. */\nexport interface RecordExecutionRunTurnInput {\n readonly base: RunSnapshot;\n readonly turn: HarnessTurnUsage;\n readonly resumeState: unknown;\n readonly origin: \"this-driver\" | \"recovered\";\n}\n\n/** CAS-backed operations for one sandbox execution's admission and continuation state. */\nexport interface ExecutionRunLedger {\n usage(record: RunRecord): ExecutionResult[\"usage\"];\n read(execution: ExecutionReference): Promise<RunSnapshot | null>;\n admit(input: AdmitExecutionRunInput): Promise<RunSnapshot>;\n markLaunched(base: RunSnapshot, resumeState: unknown): Promise<RunSnapshot>;\n markPrompted(input: MarkExecutionRunPromptedInput): Promise<RunSnapshot>;\n recordTurn(input: RecordExecutionRunTurnInput): Promise<RunSnapshot>;\n finalize(base: RunSnapshot, outcome: ExecutionOutcome): Promise<ExecutionResult>;\n pending(): Promise<readonly ExecutionReference[]>;\n}\n\n/** Create a CAS-backed ledger for idempotent execution admission, continuation, and finalization. */\nexport function createExecutionRunLedger(input: ExecutionRunLedgerOptions): ExecutionRunLedger {\n const { state } = input;\n const now = input.now ?? Date.now;\n const runPath = (sessionId: string) => `${runPrefix}${sessionId}/run`;\n async function readRun(sessionId: string): Promise<{ record: RunRecord; etag: string } | null> {\n const stored = await state.read({ pathname: runPath(sessionId) });\n if (!stored) return null;\n return { record: runRecordSchema.parse(JSON.parse(stored.body)), etag: stored.etag };\n }\n\n async function writeRun(record: RunRecord, etag?: string): Promise<void> {\n await state.write({\n pathname: runPath(record.execution.sessionId),\n body: JSON.stringify(record),\n overwrite: etag !== undefined,\n ...(etag === undefined ? {} : { ifMatch: etag }),\n });\n }\n\n // A lost write response is ambiguous; confirm durable state before the next provider effect.\n async function persistRun(\n base: { record: RunRecord; etag: string },\n apply: (record: RunRecord) => RunRecord,\n verify: (record: RunRecord) => boolean,\n ): Promise<{ record: RunRecord; etag: string }> {\n try {\n await writeRun(apply(base.record), base.etag);\n } catch {\n // Confirmed or retried below.\n }\n let current = await readRun(base.record.execution.sessionId);\n if (current && !current.record.result && !verify(current.record)) {\n try {\n await writeRun(apply(current.record), current.etag);\n } catch {\n // Confirmed below.\n }\n current = await readRun(base.record.execution.sessionId);\n }\n if (current && (current.record.result || verify(current.record))) return current;\n throw new Error(\"Execution state could not be persisted; drive again with the same reference\");\n }\n\n function usage(record: RunRecord): ExecutionResult[\"usage\"] {\n const totals = {\n inputTokens: record.turns.reduce((sum, turn) => sum + turn.inputTokens, 0),\n outputTokens: record.turns.reduce((sum, turn) => sum + turn.outputTokens, 0),\n };\n const costUsd =\n record.lostUsage !== true && (!record.launched || record.turns.length > 0)\n ? input.calculateCostUsd?.(record.model, record.turns)\n : undefined;\n return {\n computeMs: Math.max(0, now() - (record.startedAtMs ?? record.createdAtMs)),\n ...totals,\n ...(costUsd === undefined ? {} : { costUsd }),\n };\n }\n\n return {\n usage,\n async read(execution: ExecutionReference): Promise<RunSnapshot | null> {\n const stored = await readRun(execution.sessionId);\n return stored && sameJson(stored.record.execution, execution) ? stored : null;\n },\n async admit(input: AdmitExecutionRunInput): Promise<RunSnapshot> {\n const request = codingExecutionRequestSchema.parse(input.request);\n if (input.key !== `factory-task:${request.taskId}:${request.attempt}`)\n throw new Error(\"Execution key must be derived from the request's Task and attempt\");\n const sessionId = createHash(\"sha256\").update(input.key).digest(\"hex\");\n const execution = executionReferenceSchema.parse({\n provider: \"vercel-sandbox\",\n sessionId,\n environmentId: input.environmentId,\n harness: request.harness,\n });\n const reportSchema = jsonValueSchema.parse(asStoredJson(input.reportSchema ?? {}));\n const matches = (record: RunRecord) =>\n sameJson(record.request, request) &&\n record.model === input.model &&\n sameJson(record.reportSchema, reportSchema) &&\n sameJson(record.execution, execution);\n const existing = await readRun(sessionId);\n if (existing) {\n if (!matches(existing.record))\n throw new Error(\"Launch key is already bound to a different execution request\");\n return existing;\n }\n const record: RunRecord = {\n key: input.key,\n request,\n execution,\n model: input.model,\n reportSchema,\n harnessSessionId: `factory-${sessionId.slice(0, 24)}`,\n createdAtMs: now(),\n launched: false,\n turns: [],\n };\n let writeError: unknown;\n try {\n await writeRun(record);\n } catch (error) {\n writeError = error;\n }\n let stored: RunSnapshot | null;\n try {\n stored = await readRun(sessionId);\n } catch (cause) {\n throw new ExecutionLaunchPendingError(\n \"Execution admission requires recovery with its original key\",\n { cause },\n );\n }\n if (!stored) {\n if (writeError) throw writeError;\n throw new ExecutionLaunchPendingError(\"Execution admission could not be confirmed\");\n }\n if (!matches(stored.record))\n throw new Error(\"Launch key is already bound to a different execution request\");\n return stored;\n },\n markLaunched(base: RunSnapshot, resumeState: unknown): Promise<RunSnapshot> {\n const normalized = jsonValueSchema.parse(asStoredJson(resumeState));\n return persistRun(\n base,\n (record) =>\n record.launched\n ? record\n : {\n ...record,\n launched: true,\n resumeState: normalized,\n startedAtMs: now(),\n },\n (record) => record.launched,\n );\n },\n markPrompted({\n base,\n resumeState,\n origin,\n }: MarkExecutionRunPromptedInput): Promise<RunSnapshot> {\n if (!base.record.launched)\n throw new Error(\"The execution must be launched before recording a prompt\");\n const normalized = jsonValueSchema.parse(asStoredJson(resumeState));\n return persistRun(\n base,\n (record) =>\n record.promptedAtMs !== undefined\n ? record\n : {\n ...record,\n promptedAtMs: now(),\n resumeState: normalized,\n ...(origin === \"recovered\" || record.lostUsage ? { lostUsage: true } : {}),\n },\n (record) => record.promptedAtMs !== undefined,\n );\n },\n recordTurn({\n base,\n turn,\n resumeState,\n origin,\n }: RecordExecutionRunTurnInput): Promise<RunSnapshot> {\n if (!base.record.launched)\n throw new Error(\"The execution must be launched before recording a turn\");\n const normalized = jsonValueSchema.parse(asStoredJson(resumeState));\n const measured = turnUsageSchema.parse(turn);\n return persistRun(\n base,\n (record) =>\n record.turns.length > 0\n ? record\n : {\n ...record,\n promptedAtMs: record.promptedAtMs ?? now(),\n turns: [measured],\n resumeState: normalized,\n ...(origin === \"recovered\" || record.lostUsage ? { lostUsage: true } : {}),\n },\n (record) => record.turns.length > 0 && record.promptedAtMs !== undefined,\n );\n },\n async finalize(base: RunSnapshot, outcome: ExecutionOutcome): Promise<ExecutionResult> {\n if (base.record.result) return base.record.result;\n const record = base.record;\n const observed = usage(record);\n const cancelledUsage = record.launched ? observed : { computeMs: 0, costUsd: 0 };\n const result = executionResultSchema.parse({\n ...outcome,\n execution: record.execution,\n usage: outcome.status === \"cancelled\" ? cancelledUsage : observed,\n });\n try {\n await writeRun({ ...record, result }, base.etag);\n } catch {\n const latest = await readRun(record.execution.sessionId);\n if (latest?.record.result) return latest.record.result;\n throw new Error(\"Execution result could not be recorded; retry with the same key\");\n }\n const confirmed = await readRun(record.execution.sessionId);\n if (confirmed?.record.result) return confirmed.record.result;\n throw new Error(\"Execution result could not be recorded; retry with the same key\");\n },\n async pending(): Promise<readonly ExecutionReference[]> {\n const references: ExecutionReference[] = [];\n for (const path of await state.listPaths({ prefix: runPrefix })) {\n const stored = await readRun(path.slice(runPrefix.length).split(\"/\")[0]!);\n if (stored && !stored.record.result) references.push(stored.record.execution);\n }\n return references;\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"execution-run-ledger.mjs","names":[],"sources":["../../src/sandbox/execution-run-ledger.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport { z } from \"zod\";\nimport { ExecutionLaunchPendingError } from \"../dispatch\";\nimport type { MillisecondClock } from \"../schema/common\";\nimport {\n codingExecutionRequestSchema,\n executionLaunchKeySchema,\n executionResultSchema,\n type CodingExecutionRequest,\n type ExecutionLaunchKey,\n type ExecutionResult,\n} from \"../schema/execution\";\nimport { executionReferenceSchema, type ExecutionReference } from \"../schema/task\";\nimport { jsonValueSchema } from \"../schema/work\";\nimport type { ConditionalStateStore } from \"../store/state\";\nimport type { HarnessTurnUsage } from \"./harness-runner\";\n\nconst turnUsageSchema = z.strictObject({\n inputTokens: z.number().int().nonnegative(),\n noCacheInputTokens: z.number().int().nonnegative(),\n cacheReadTokens: z.number().int().nonnegative(),\n cacheWriteTokens: z.number().int().nonnegative(),\n outputTokens: z.number().int().nonnegative(),\n complete: z.boolean().optional(),\n});\n\nconst runRecordSchema = z.strictObject({\n key: executionLaunchKeySchema,\n request: codingExecutionRequestSchema,\n execution: executionReferenceSchema,\n harnessSessionId: z.string().min(1),\n model: z.string().min(1),\n reportSchema: jsonValueSchema,\n createdAtMs: z.number().int().nonnegative(),\n launched: z.boolean(),\n startedAtMs: z.number().int().nonnegative().optional(),\n /** Set durably right after the prompt was accepted by the runtime. */\n promptedAtMs: z.number().int().nonnegative().optional(),\n resumeState: jsonValueSchema.optional(),\n turns: z.array(turnUsageSchema).readonly(),\n /** Unobserved spend makes cost permanently unknown, even if later usage is measured. */\n lostUsage: z.boolean().optional(),\n result: executionResultSchema.optional(),\n});\n\n/** Durable identity, continuation, usage, and terminal result for one sandbox execution. */\nexport type RunRecord = z.infer<typeof runRecordSchema>;\n\nconst sameJson = (left: unknown, right: unknown) =>\n isDeepStrictEqual(\n left === undefined ? undefined : JSON.parse(JSON.stringify(left)),\n right === undefined ? undefined : JSON.parse(JSON.stringify(right)),\n );\n// SDK values can carry non-plain properties; validate their persisted JSON form.\nconst asStoredJson = (value: unknown) => JSON.parse(JSON.stringify(value ?? null)) as unknown;\n\n/** A run record paired with the ETag required for its next confirmed CAS update. */\nexport interface RunSnapshot {\n record: RunRecord;\n etag: string;\n}\n\n/** Workflow-owned outcome fields combined with observed execution identity and usage at finalize time. */\ntype WithoutObservedExecution<Result extends ExecutionResult> = Result extends ExecutionResult\n ? Omit<Result, \"execution\" | \"usage\">\n : never;\n\n/** Workflow-owned outcome fields before observed execution identity and usage are attached. */\nexport type ExecutionOutcome = WithoutObservedExecution<ExecutionResult>;\nconst runPrefix = \"factory-executions/\";\n\n/** Durable state, clock, and model pricing used to construct an execution ledger. */\nexport interface ExecutionRunLedgerOptions {\n state: ConditionalStateStore;\n now?: MillisecondClock;\n calculateCostUsd?(model: string, turns: readonly HarnessTurnUsage[]): number | undefined;\n}\n\n/** Immutable admission arguments for one stable sandbox execution key. */\nexport interface AdmitExecutionRunInput {\n request: CodingExecutionRequest;\n key: ExecutionLaunchKey;\n model: string;\n environmentId: string;\n reportSchema?: unknown;\n}\n\n/** Identifies the prompt acceptance to persist for one admitted execution run. */\nexport interface MarkExecutionRunPromptedInput {\n readonly base: RunSnapshot;\n readonly resumeState: unknown;\n readonly origin: \"this-driver\" | \"recovered\";\n}\n\n/** Identifies the first measured turn to persist for one admitted execution run. */\nexport interface RecordExecutionRunTurnInput {\n readonly base: RunSnapshot;\n readonly turn: HarnessTurnUsage;\n readonly resumeState: unknown;\n readonly origin: \"this-driver\" | \"recovered\";\n}\n\n/** CAS-backed operations for one sandbox execution's admission and continuation state. */\nexport interface ExecutionRunLedger {\n usage(record: RunRecord): ExecutionResult[\"usage\"];\n read(execution: ExecutionReference): Promise<RunSnapshot | null>;\n admit(input: AdmitExecutionRunInput): Promise<RunSnapshot>;\n markLaunched(base: RunSnapshot, resumeState: unknown): Promise<RunSnapshot>;\n markPrompted(input: MarkExecutionRunPromptedInput): Promise<RunSnapshot>;\n recordTurn(input: RecordExecutionRunTurnInput): Promise<RunSnapshot>;\n finalize(base: RunSnapshot, outcome: ExecutionOutcome): Promise<ExecutionResult>;\n pending(): Promise<readonly ExecutionReference[]>;\n}\n\n/** Create a CAS-backed ledger for idempotent execution admission, continuation, and finalization. */\nexport function createExecutionRunLedger(input: ExecutionRunLedgerOptions): ExecutionRunLedger {\n const { state } = input;\n const now = input.now ?? Date.now;\n const runPath = (sessionId: string) => `${runPrefix}${sessionId}/run`;\n async function readRun(sessionId: string): Promise<{ record: RunRecord; etag: string } | null> {\n const stored = await state.read({ pathname: runPath(sessionId) });\n if (!stored) return null;\n return { record: runRecordSchema.parse(JSON.parse(stored.body)), etag: stored.etag };\n }\n\n async function writeRun(record: RunRecord, etag?: string): Promise<void> {\n await state.write({\n pathname: runPath(record.execution.sessionId),\n body: JSON.stringify(record),\n overwrite: etag !== undefined,\n ...(etag === undefined ? {} : { ifMatch: etag }),\n });\n }\n\n // A lost write response is ambiguous; confirm durable state before the next provider effect.\n async function persistRun(\n base: { record: RunRecord; etag: string },\n apply: (record: RunRecord) => RunRecord,\n verify: (record: RunRecord) => boolean,\n ): Promise<{ record: RunRecord; etag: string }> {\n try {\n await writeRun(apply(base.record), base.etag);\n } catch {\n // Confirmed or retried below.\n }\n let current = await readRun(base.record.execution.sessionId);\n if (current && !current.record.result && !verify(current.record)) {\n try {\n await writeRun(apply(current.record), current.etag);\n } catch {\n // Confirmed below.\n }\n current = await readRun(base.record.execution.sessionId);\n }\n if (current && (current.record.result || verify(current.record))) return current;\n throw new Error(\"Execution state could not be persisted; drive again with the same reference\");\n }\n\n function usage(record: RunRecord): ExecutionResult[\"usage\"] {\n const totals = {\n inputTokens: record.turns.reduce((sum, turn) => sum + turn.inputTokens, 0),\n outputTokens: record.turns.reduce((sum, turn) => sum + turn.outputTokens, 0),\n };\n const costUsd =\n record.lostUsage !== true && (!record.launched || record.turns.length > 0)\n ? input.calculateCostUsd?.(record.model, record.turns)\n : undefined;\n return {\n computeMs: Math.max(0, now() - (record.startedAtMs ?? record.createdAtMs)),\n ...totals,\n ...(costUsd === undefined ? {} : { costUsd }),\n };\n }\n\n return {\n usage,\n async read(execution: ExecutionReference): Promise<RunSnapshot | null> {\n const stored = await readRun(execution.sessionId);\n return stored && sameJson(stored.record.execution, execution) ? stored : null;\n },\n async admit(input: AdmitExecutionRunInput): Promise<RunSnapshot> {\n const request = codingExecutionRequestSchema.parse(input.request);\n if (input.key !== `factory-task:${request.taskId}:${request.attempt}`)\n throw new Error(\"Execution key must be derived from the request's Task and attempt\");\n const sessionId = createHash(\"sha256\").update(input.key).digest(\"hex\");\n const execution = executionReferenceSchema.parse({\n provider: \"vercel-sandbox\",\n sessionId,\n environmentId: input.environmentId,\n harness: request.harness,\n });\n const reportSchema = jsonValueSchema.parse(asStoredJson(input.reportSchema ?? {}));\n const matches = (record: RunRecord) =>\n sameJson(record.request, request) &&\n record.model === input.model &&\n sameJson(record.reportSchema, reportSchema) &&\n sameJson(record.execution, execution);\n const existing = await readRun(sessionId);\n if (existing) {\n if (!matches(existing.record))\n throw new Error(\"Launch key is already bound to a different execution request\");\n return existing;\n }\n const record: RunRecord = {\n key: input.key,\n request,\n execution,\n model: input.model,\n reportSchema,\n harnessSessionId: `factory-${sessionId.slice(0, 24)}`,\n createdAtMs: now(),\n launched: false,\n turns: [],\n };\n let writeError: unknown;\n try {\n await writeRun(record);\n } catch (error) {\n writeError = error;\n }\n let stored: RunSnapshot | null;\n try {\n stored = await readRun(sessionId);\n } catch (cause) {\n throw new ExecutionLaunchPendingError(\n \"Execution admission requires recovery with its original key\",\n { cause },\n );\n }\n if (!stored) {\n if (writeError) throw writeError;\n throw new ExecutionLaunchPendingError(\"Execution admission could not be confirmed\");\n }\n if (!matches(stored.record))\n throw new Error(\"Launch key is already bound to a different execution request\");\n return stored;\n },\n markLaunched(base: RunSnapshot, resumeState: unknown): Promise<RunSnapshot> {\n const normalized = jsonValueSchema.parse(asStoredJson(resumeState));\n return persistRun(\n base,\n (record) =>\n record.launched\n ? record\n : {\n ...record,\n launched: true,\n resumeState: normalized,\n startedAtMs: now(),\n },\n (record) => record.launched,\n );\n },\n markPrompted({\n base,\n resumeState,\n origin,\n }: MarkExecutionRunPromptedInput): Promise<RunSnapshot> {\n if (!base.record.launched)\n throw new Error(\"The execution must be launched before recording a prompt\");\n const normalized = jsonValueSchema.parse(asStoredJson(resumeState));\n return persistRun(\n base,\n (record) =>\n record.promptedAtMs !== undefined\n ? record\n : {\n ...record,\n promptedAtMs: now(),\n resumeState: normalized,\n ...(origin === \"recovered\" || record.lostUsage ? { lostUsage: true } : {}),\n },\n (record) => record.promptedAtMs !== undefined,\n );\n },\n recordTurn({\n base,\n turn,\n resumeState,\n origin,\n }: RecordExecutionRunTurnInput): Promise<RunSnapshot> {\n if (!base.record.launched)\n throw new Error(\"The execution must be launched before recording a turn\");\n const normalized = jsonValueSchema.parse(asStoredJson(resumeState));\n const measured = turnUsageSchema.parse(turn);\n return persistRun(\n base,\n (record) =>\n record.turns.length > 0\n ? record\n : {\n ...record,\n promptedAtMs: record.promptedAtMs ?? now(),\n turns: [measured],\n resumeState: normalized,\n ...(origin === \"recovered\" || record.lostUsage ? { lostUsage: true } : {}),\n },\n (record) => record.turns.length > 0 && record.promptedAtMs !== undefined,\n );\n },\n async finalize(base: RunSnapshot, outcome: ExecutionOutcome): Promise<ExecutionResult> {\n if (base.record.result) return base.record.result;\n const record = base.record;\n const observed = usage(record);\n const cancelledUsage = record.launched ? observed : { computeMs: 0, costUsd: 0 };\n const result = executionResultSchema.parse({\n ...outcome,\n execution: record.execution,\n usage: outcome.status === \"cancelled\" ? cancelledUsage : observed,\n });\n try {\n await writeRun({ ...record, result }, base.etag);\n } catch {\n const latest = await readRun(record.execution.sessionId);\n if (latest?.record.result) return latest.record.result;\n throw new Error(\"Execution result could not be recorded; retry with the same key\");\n }\n const confirmed = await readRun(record.execution.sessionId);\n if (confirmed?.record.result) return confirmed.record.result;\n throw new Error(\"Execution result could not be recorded; retry with the same key\");\n },\n async pending(): Promise<readonly ExecutionReference[]> {\n const references: ExecutionReference[] = [];\n for (const path of await state.listPaths({ prefix: runPrefix })) {\n const stored = await readRun(path.slice(runPrefix.length).split(\"/\")[0]!);\n if (stored && !stored.record.result) references.push(stored.record.execution);\n }\n return references;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ExecutionReference } from "../schema/task.mjs";
|
|
2
2
|
import { MillisecondClock } from "../schema/common.mjs";
|
|
3
3
|
import { CodingExecutionRequest, ExecutionLaunchKey, ExecutionResult } from "../schema/execution.mjs";
|
|
4
|
-
import { BlobClient } from "../blob/index.mjs";
|
|
5
4
|
import { ExecutionAdapter } from "../execution.mjs";
|
|
5
|
+
import { ConditionalStateStore } from "../store/state.mjs";
|
|
6
6
|
import { ConfiguredHarnessRunner, HarnessTurnUsage, SandboxHandle } from "./harness-runner.mjs";
|
|
7
7
|
import { ExecutionEnvironment } from "./execution-environment.mjs";
|
|
8
8
|
import { ExecutionOutcome } from "./execution-run-ledger.mjs";
|
|
@@ -20,7 +20,7 @@ interface SandboxExecutionOutcomeContext {
|
|
|
20
20
|
}
|
|
21
21
|
/** Configures durable state, harnesses, environments, prompts, evidence, and usage pricing. */
|
|
22
22
|
interface SandboxExecutionOptions {
|
|
23
|
-
state:
|
|
23
|
+
state: ConditionalStateStore;
|
|
24
24
|
harnesses: Partial<Record<CodingExecutionRequest["harness"], ConfiguredHarnessRunner>>;
|
|
25
25
|
environment: ExecutionEnvironment;
|
|
26
26
|
buildPrompt(context: SandboxExecutionPromptContext): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execution.mjs","names":[],"sources":["../../src/sandbox/execution.ts"],"sourcesContent":["import { ExecutionLaunchPendingError } from \"../dispatch\";\nimport {\n codingExecutionRequestSchema,\n executionResultSchema,\n type CodingExecutionRequest,\n type ExecutionLaunchKey,\n type ExecutionResult,\n} from \"../schema/execution\";\nimport { type ExecutionAdapter } from \"../execution\";\nimport type { MillisecondClock } from \"../schema/common\";\nimport type { ExecutionReference } from \"../schema/task\";\nimport type { BlobClient } from \"../blob\";\nimport { ExecutionTimeoutError, withExecutionAbort } from \"./execution-abort\";\nimport type { ExecutionEnvironment } from \"./execution-environment\";\nimport {\n createExecutionRunLedger,\n type RunRecord,\n type ExecutionOutcome,\n} from \"./execution-run-ledger\";\nimport type {\n ConfiguredHarnessRunner,\n HarnessTurn,\n HarnessTurnUsage,\n SandboxHandle,\n} from \"./harness-runner\";\n\nconst defaultSandboxTimeoutMarginMs = 300_000;\n\n/** Context used to construct the immutable prompt for an admitted sandbox execution. */\nexport interface SandboxExecutionPromptContext {\n request: CodingExecutionRequest;\n reportSchema: unknown;\n sandbox: SandboxHandle;\n}\n\n/** Context used to validate and extract artifacts after a sandbox turn completes. */\nexport interface SandboxExecutionOutcomeContext {\n request: CodingExecutionRequest;\n sandbox: SandboxHandle;\n}\n\n/** Configures durable state, harnesses, environments, prompts, evidence, and usage pricing. */\nexport interface SandboxExecutionOptions {\n state: BlobClient;\n harnesses: Partial<Record<CodingExecutionRequest[\"harness\"], ConfiguredHarnessRunner>>;\n environment: ExecutionEnvironment;\n buildPrompt(context: SandboxExecutionPromptContext): string;\n extractOutcome(context: SandboxExecutionOutcomeContext): Promise<ExecutionOutcome>;\n calculateCostUsd?(model: string, turns: readonly HarnessTurnUsage[]): number | undefined;\n sandboxTimeoutMarginMs?: number;\n now?: MillisecondClock;\n}\n\n/** Coding request, stable launch key, and optional report contract for sandbox admission. */\nexport interface StartSandboxExecutionInput {\n request: CodingExecutionRequest;\n launchKey: ExecutionLaunchKey;\n context?: SandboxExecutionStartContext;\n}\n\n/** Optional report contract associated with one sandbox execution launch. */\nexport interface SandboxExecutionStartContext {\n reportSchema: unknown;\n}\n\n/** Execution adapter with explicit serial driving and pending-run discovery for sandbox work. */\nexport interface SandboxExecutionAdapter extends ExecutionAdapter {\n start(input: StartSandboxExecutionInput): Promise<ExecutionReference>;\n /** Serialize calls per execution; ledger CAS does not fence provider effects. */\n drive(execution: ExecutionReference): Promise<ExecutionResult>;\n /** Launch keys with admitted but unfinished runs, for drivers and drills. */\n pending(): Promise<readonly ExecutionReference[]>;\n}\n\nconst bounded = (reason: string) => reason.slice(0, 2000);\n\n/** Create a durable execution adapter that runs configured harnesses in Task-scoped sandboxes. */\nexport function createSandboxExecutionAdapter(\n options: SandboxExecutionOptions,\n): SandboxExecutionAdapter {\n const now = options.now ?? Date.now;\n const ledger = createExecutionRunLedger({\n state: options.state,\n now,\n ...(options.calculateCostUsd ? { calculateCostUsd: options.calculateCostUsd } : {}),\n });\n\n function configuredHarness(harness: CodingExecutionRequest[\"harness\"]) {\n const configured = options.harnesses[harness];\n if (!configured || !configured.model.trim()) {\n throw new Error(`Execution harness ${harness} has no configured runner and model`);\n }\n return configured;\n }\n\n const keyOf = (record: RunRecord) => ({\n taskId: record.request.taskId,\n attempt: record.request.attempt,\n });\n\n const sandboxTimeout = (request: CodingExecutionRequest) =>\n request.environment.timeoutMs +\n (options.sandboxTimeoutMarginMs ?? defaultSandboxTimeoutMarginMs);\n\n async function extract(sandbox: SandboxHandle, record: RunRecord): Promise<ExecutionOutcome> {\n const request = record.request;\n const failed = (reason: string): ExecutionOutcome => ({\n status: \"failed\",\n artifacts: [],\n reason: bounded(reason),\n });\n // Post-turn checks detect overruns; they cannot enforce a hard cap.\n const usage = ledger.usage(record);\n const { maxCostUsd, maxTokens } = request.limits;\n if (maxCostUsd !== undefined && usage.costUsd !== undefined && usage.costUsd > maxCostUsd) {\n return failed(\n `Execution spent $${usage.costUsd}, exceeding its maxCostUsd of $${maxCostUsd}`,\n );\n }\n if (\n maxTokens !== undefined &&\n (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0) > maxTokens\n ) {\n return failed(\n `Execution used ${(usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)} tokens, exceeding its maxTokens of ${maxTokens}`,\n );\n }\n return options.extractOutcome({ sandbox, request });\n }\n\n /** Best-effort idempotent environment shutdown; ambiguity propagates for retry. */\n async function stopEnvironment(record: RunRecord): Promise<void> {\n const sandbox = await options.environment.find(keyOf(record));\n if (sandbox) await options.environment.stop(sandbox.sandbox);\n }\n\n return {\n async start({ request, launchKey, context }) {\n const parsed = codingExecutionRequestSchema.parse(request);\n const configured = configuredHarness(parsed.harness);\n const keyParts = { taskId: parsed.taskId, attempt: parsed.attempt };\n const stored = await ledger.admit({\n request: parsed,\n key: launchKey,\n model: configured.model,\n environmentId: options.environment.name(keyParts),\n reportSchema: context?.reportSchema,\n });\n if (stored.record.launched || stored.record.result) return stored.record.execution;\n try {\n await options.environment.ensure(keyParts, {\n timeoutMs: sandboxTimeout(parsed),\n request: parsed,\n });\n } catch (cause) {\n // Admission already exists; provisioning failure must recover with the same key.\n throw new ExecutionLaunchPendingError(\n \"Execution environment provisioning is unresolved; recover with the original key\",\n { cause },\n );\n }\n return stored.record.execution;\n },\n\n async inspect(execution) {\n const stored = await ledger.read(execution);\n if (!stored) return { status: \"expired\" };\n if (stored.record.result) return { status: \"finished\", result: stored.record.result };\n const sandbox = await options.environment.find(keyOf(stored.record));\n if (!sandbox || sandbox.liveness === \"terminal\") {\n return { status: \"expired\" };\n }\n if (sandbox.liveness !== \"live\")\n throw new Error(\"Execution environment is transitioning; inspect again shortly\");\n return { status: \"active\" };\n },\n\n async stop(execution) {\n const stored = await ledger.read(execution);\n if (!stored) {\n return executionResultSchema.parse({\n status: \"cancelled\",\n execution,\n artifacts: [],\n usage: { computeMs: 0 },\n reason: \"No admitted execution matches this reference; its cost is unknown\",\n });\n }\n const { record } = stored;\n if (record.result) {\n // A final result does not imply the sandbox has stopped.\n await stopEnvironment(record);\n return record.result;\n }\n await stopEnvironment(record);\n return ledger.finalize(stored, {\n status: \"cancelled\",\n artifacts: [],\n reason: record.launched\n ? record.turns.length > 0\n ? \"Execution was stopped after its completed turn was recorded\"\n : \"Execution was stopped after launch; unfinished turn cost is unknown\"\n : \"Execution was stopped before its prompt was sent\",\n });\n },\n\n async drive(execution) {\n const admitted = await ledger.read(execution);\n if (!admitted) {\n throw new Error(\"No admitted execution matches this reference\");\n }\n let stored = admitted;\n if (stored.record.result) return stored.record.result;\n const request = stored.record.request;\n const configured = configuredHarness(request.harness);\n if (configured.model !== stored.record.model) {\n throw new Error(\"Execution model changed; restore its admitted harness configuration\");\n }\n // A launched attempt must never recreate its environment.\n let sandbox: SandboxHandle;\n if (!stored.record.launched) {\n const observed = await options.environment.ensure(keyOf(stored.record), {\n timeoutMs: sandboxTimeout(request),\n request,\n });\n if (observed.liveness !== \"live\") throw new Error(\"Execution environment is not live\");\n sandbox = observed.sandbox;\n } else {\n const found = await options.environment.find(keyOf(stored.record));\n if (!found || found.liveness === \"terminal\") {\n return ledger.finalize(stored, {\n status: \"failed\",\n artifacts: [],\n reason:\n \"Execution environment expired before a result was extracted; replacement requires a new attempt\",\n });\n }\n if (found.liveness !== \"live\") {\n throw new Error(\"Execution environment is transitioning; drive again shortly\");\n }\n sandbox = found.sandbox;\n }\n const remainingMs = Math.max(\n 0,\n request.environment.timeoutMs - (now() - (stored.record.startedAtMs ?? now())),\n );\n try {\n return await withExecutionAbort({\n timeoutMs: remainingMs,\n isStopped: async () => {\n const latest = await ledger.read(execution);\n if (!latest) throw new Error(\"The admitted execution disappeared\");\n return latest.record.result !== undefined;\n },\n run: async (abortSignal) => {\n if (remainingMs === 0) throw new ExecutionTimeoutError();\n let observedFlip = false;\n if (!stored.record.launched) {\n // Confirm launch intent durably before sending any prompt.\n const session = await configured.runner.open({\n sandbox,\n sessionId: stored.record.harnessSessionId,\n abortSignal,\n });\n stored = await ledger.markLaunched(stored, await session.detach());\n if (stored.record.result) return stored.record.result;\n observedFlip = true;\n }\n let turn: HarnessTurn | undefined;\n let promptedHere = false;\n if (stored.record.promptedAtMs === undefined && stored.record.turns.length === 0) {\n const session = await configured.runner.open({\n sandbox,\n sessionId: stored.record.harnessSessionId,\n resumeFrom: stored.record.resumeState,\n abortSignal,\n });\n const prompt = options.buildPrompt({\n request,\n reportSchema: stored.record.reportSchema,\n sandbox,\n });\n // An unmarked prompt from another driver may have spent money already.\n const foreignFlip = !observedFlip;\n try {\n const inflight = await session.beginPrompt(prompt);\n stored = await ledger.markPrompted({\n base: stored,\n resumeState: inflight,\n origin: foreignFlip ? \"recovered\" : \"this-driver\",\n });\n promptedHere = true;\n } catch (error) {\n if (!(error instanceof Error && /unfinished turn/iu.test(error.message)))\n throw error;\n // Drain the existing turn; its earlier usage was not observed.\n turn = await session.continueTurn();\n stored = await ledger.recordTurn({\n base: stored,\n turn: turn.usage,\n resumeState: await session.detach(),\n origin: \"recovered\",\n });\n }\n if (stored.record.result) return stored.record.result;\n }\n if (turn === undefined && stored.record.turns.length === 0) {\n const session = await configured.runner.open({\n sandbox,\n sessionId: stored.record.harnessSessionId,\n resumeFrom: stored.record.resumeState,\n abortSignal,\n });\n turn = await session.continueTurn();\n stored = await ledger.recordTurn({\n base: stored,\n turn: turn.usage,\n resumeState: await session.detach(),\n origin: promptedHere ? \"this-driver\" : \"recovered\",\n });\n if (stored.record.result) return stored.record.result;\n }\n const result = await extract(sandbox, stored.record);\n abortSignal.throwIfAborted();\n return ledger.finalize(stored, result);\n },\n });\n } catch (error) {\n const latest = await ledger.read(execution);\n if (latest?.record.result) return latest.record.result;\n if (!(error instanceof ExecutionTimeoutError) || !latest) throw error;\n await stopEnvironment(latest.record);\n return ledger.finalize(latest, {\n status: \"failed\",\n artifacts: [],\n reason: error.message,\n });\n }\n },\n\n pending: ledger.pending,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"execution.mjs","names":[],"sources":["../../src/sandbox/execution.ts"],"sourcesContent":["import { ExecutionLaunchPendingError } from \"../dispatch\";\nimport {\n codingExecutionRequestSchema,\n executionResultSchema,\n type CodingExecutionRequest,\n type ExecutionLaunchKey,\n type ExecutionResult,\n} from \"../schema/execution\";\nimport { type ExecutionAdapter } from \"../execution\";\nimport type { MillisecondClock } from \"../schema/common\";\nimport type { ExecutionReference } from \"../schema/task\";\nimport type { ConditionalStateStore } from \"../store/state\";\nimport { ExecutionTimeoutError, withExecutionAbort } from \"./execution-abort\";\nimport type { ExecutionEnvironment } from \"./execution-environment\";\nimport {\n createExecutionRunLedger,\n type RunRecord,\n type ExecutionOutcome,\n} from \"./execution-run-ledger\";\nimport type {\n ConfiguredHarnessRunner,\n HarnessTurn,\n HarnessTurnUsage,\n SandboxHandle,\n} from \"./harness-runner\";\n\nconst defaultSandboxTimeoutMarginMs = 300_000;\n\n/** Context used to construct the immutable prompt for an admitted sandbox execution. */\nexport interface SandboxExecutionPromptContext {\n request: CodingExecutionRequest;\n reportSchema: unknown;\n sandbox: SandboxHandle;\n}\n\n/** Context used to validate and extract artifacts after a sandbox turn completes. */\nexport interface SandboxExecutionOutcomeContext {\n request: CodingExecutionRequest;\n sandbox: SandboxHandle;\n}\n\n/** Configures durable state, harnesses, environments, prompts, evidence, and usage pricing. */\nexport interface SandboxExecutionOptions {\n state: ConditionalStateStore;\n harnesses: Partial<Record<CodingExecutionRequest[\"harness\"], ConfiguredHarnessRunner>>;\n environment: ExecutionEnvironment;\n buildPrompt(context: SandboxExecutionPromptContext): string;\n extractOutcome(context: SandboxExecutionOutcomeContext): Promise<ExecutionOutcome>;\n calculateCostUsd?(model: string, turns: readonly HarnessTurnUsage[]): number | undefined;\n sandboxTimeoutMarginMs?: number;\n now?: MillisecondClock;\n}\n\n/** Coding request, stable launch key, and optional report contract for sandbox admission. */\nexport interface StartSandboxExecutionInput {\n request: CodingExecutionRequest;\n launchKey: ExecutionLaunchKey;\n context?: SandboxExecutionStartContext;\n}\n\n/** Optional report contract associated with one sandbox execution launch. */\nexport interface SandboxExecutionStartContext {\n reportSchema: unknown;\n}\n\n/** Execution adapter with explicit serial driving and pending-run discovery for sandbox work. */\nexport interface SandboxExecutionAdapter extends ExecutionAdapter {\n start(input: StartSandboxExecutionInput): Promise<ExecutionReference>;\n /** Serialize calls per execution; ledger CAS does not fence provider effects. */\n drive(execution: ExecutionReference): Promise<ExecutionResult>;\n /** Launch keys with admitted but unfinished runs, for drivers and drills. */\n pending(): Promise<readonly ExecutionReference[]>;\n}\n\nconst bounded = (reason: string) => reason.slice(0, 2000);\n\n/** Create a durable execution adapter that runs configured harnesses in Task-scoped sandboxes. */\nexport function createSandboxExecutionAdapter(\n options: SandboxExecutionOptions,\n): SandboxExecutionAdapter {\n const now = options.now ?? Date.now;\n const ledger = createExecutionRunLedger({\n state: options.state,\n now,\n ...(options.calculateCostUsd ? { calculateCostUsd: options.calculateCostUsd } : {}),\n });\n\n function configuredHarness(harness: CodingExecutionRequest[\"harness\"]) {\n const configured = options.harnesses[harness];\n if (!configured || !configured.model.trim()) {\n throw new Error(`Execution harness ${harness} has no configured runner and model`);\n }\n return configured;\n }\n\n const keyOf = (record: RunRecord) => ({\n taskId: record.request.taskId,\n attempt: record.request.attempt,\n });\n\n const sandboxTimeout = (request: CodingExecutionRequest) =>\n request.environment.timeoutMs +\n (options.sandboxTimeoutMarginMs ?? defaultSandboxTimeoutMarginMs);\n\n async function extract(sandbox: SandboxHandle, record: RunRecord): Promise<ExecutionOutcome> {\n const request = record.request;\n const failed = (reason: string): ExecutionOutcome => ({\n status: \"failed\",\n artifacts: [],\n reason: bounded(reason),\n });\n // Post-turn checks detect overruns; they cannot enforce a hard cap.\n const usage = ledger.usage(record);\n const { maxCostUsd, maxTokens } = request.limits;\n if (maxCostUsd !== undefined && usage.costUsd !== undefined && usage.costUsd > maxCostUsd) {\n return failed(\n `Execution spent $${usage.costUsd}, exceeding its maxCostUsd of $${maxCostUsd}`,\n );\n }\n if (\n maxTokens !== undefined &&\n (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0) > maxTokens\n ) {\n return failed(\n `Execution used ${(usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)} tokens, exceeding its maxTokens of ${maxTokens}`,\n );\n }\n return options.extractOutcome({ sandbox, request });\n }\n\n /** Best-effort idempotent environment shutdown; ambiguity propagates for retry. */\n async function stopEnvironment(record: RunRecord): Promise<void> {\n const sandbox = await options.environment.find(keyOf(record));\n if (sandbox) await options.environment.stop(sandbox.sandbox);\n }\n\n return {\n async start({ request, launchKey, context }) {\n const parsed = codingExecutionRequestSchema.parse(request);\n const configured = configuredHarness(parsed.harness);\n const keyParts = { taskId: parsed.taskId, attempt: parsed.attempt };\n const stored = await ledger.admit({\n request: parsed,\n key: launchKey,\n model: configured.model,\n environmentId: options.environment.name(keyParts),\n reportSchema: context?.reportSchema,\n });\n if (stored.record.launched || stored.record.result) return stored.record.execution;\n try {\n await options.environment.ensure(keyParts, {\n timeoutMs: sandboxTimeout(parsed),\n request: parsed,\n });\n } catch (cause) {\n // Admission already exists; provisioning failure must recover with the same key.\n throw new ExecutionLaunchPendingError(\n \"Execution environment provisioning is unresolved; recover with the original key\",\n { cause },\n );\n }\n return stored.record.execution;\n },\n\n async inspect(execution) {\n const stored = await ledger.read(execution);\n if (!stored) return { status: \"expired\" };\n if (stored.record.result) return { status: \"finished\", result: stored.record.result };\n const sandbox = await options.environment.find(keyOf(stored.record));\n if (!sandbox || sandbox.liveness === \"terminal\") {\n return { status: \"expired\" };\n }\n if (sandbox.liveness !== \"live\")\n throw new Error(\"Execution environment is transitioning; inspect again shortly\");\n return { status: \"active\" };\n },\n\n async stop(execution) {\n const stored = await ledger.read(execution);\n if (!stored) {\n return executionResultSchema.parse({\n status: \"cancelled\",\n execution,\n artifacts: [],\n usage: { computeMs: 0 },\n reason: \"No admitted execution matches this reference; its cost is unknown\",\n });\n }\n const { record } = stored;\n if (record.result) {\n // A final result does not imply the sandbox has stopped.\n await stopEnvironment(record);\n return record.result;\n }\n await stopEnvironment(record);\n return ledger.finalize(stored, {\n status: \"cancelled\",\n artifacts: [],\n reason: record.launched\n ? record.turns.length > 0\n ? \"Execution was stopped after its completed turn was recorded\"\n : \"Execution was stopped after launch; unfinished turn cost is unknown\"\n : \"Execution was stopped before its prompt was sent\",\n });\n },\n\n async drive(execution) {\n const admitted = await ledger.read(execution);\n if (!admitted) {\n throw new Error(\"No admitted execution matches this reference\");\n }\n let stored = admitted;\n if (stored.record.result) return stored.record.result;\n const request = stored.record.request;\n const configured = configuredHarness(request.harness);\n if (configured.model !== stored.record.model) {\n throw new Error(\"Execution model changed; restore its admitted harness configuration\");\n }\n // A launched attempt must never recreate its environment.\n let sandbox: SandboxHandle;\n if (!stored.record.launched) {\n const observed = await options.environment.ensure(keyOf(stored.record), {\n timeoutMs: sandboxTimeout(request),\n request,\n });\n if (observed.liveness !== \"live\") throw new Error(\"Execution environment is not live\");\n sandbox = observed.sandbox;\n } else {\n const found = await options.environment.find(keyOf(stored.record));\n if (!found || found.liveness === \"terminal\") {\n return ledger.finalize(stored, {\n status: \"failed\",\n artifacts: [],\n reason:\n \"Execution environment expired before a result was extracted; replacement requires a new attempt\",\n });\n }\n if (found.liveness !== \"live\") {\n throw new Error(\"Execution environment is transitioning; drive again shortly\");\n }\n sandbox = found.sandbox;\n }\n const remainingMs = Math.max(\n 0,\n request.environment.timeoutMs - (now() - (stored.record.startedAtMs ?? now())),\n );\n try {\n return await withExecutionAbort({\n timeoutMs: remainingMs,\n isStopped: async () => {\n const latest = await ledger.read(execution);\n if (!latest) throw new Error(\"The admitted execution disappeared\");\n return latest.record.result !== undefined;\n },\n run: async (abortSignal) => {\n if (remainingMs === 0) throw new ExecutionTimeoutError();\n let observedFlip = false;\n if (!stored.record.launched) {\n // Confirm launch intent durably before sending any prompt.\n const session = await configured.runner.open({\n sandbox,\n sessionId: stored.record.harnessSessionId,\n abortSignal,\n });\n stored = await ledger.markLaunched(stored, await session.detach());\n if (stored.record.result) return stored.record.result;\n observedFlip = true;\n }\n let turn: HarnessTurn | undefined;\n let promptedHere = false;\n if (stored.record.promptedAtMs === undefined && stored.record.turns.length === 0) {\n const session = await configured.runner.open({\n sandbox,\n sessionId: stored.record.harnessSessionId,\n resumeFrom: stored.record.resumeState,\n abortSignal,\n });\n const prompt = options.buildPrompt({\n request,\n reportSchema: stored.record.reportSchema,\n sandbox,\n });\n // An unmarked prompt from another driver may have spent money already.\n const foreignFlip = !observedFlip;\n try {\n const inflight = await session.beginPrompt(prompt);\n stored = await ledger.markPrompted({\n base: stored,\n resumeState: inflight,\n origin: foreignFlip ? \"recovered\" : \"this-driver\",\n });\n promptedHere = true;\n } catch (error) {\n if (!(error instanceof Error && /unfinished turn/iu.test(error.message)))\n throw error;\n // Drain the existing turn; its earlier usage was not observed.\n turn = await session.continueTurn();\n stored = await ledger.recordTurn({\n base: stored,\n turn: turn.usage,\n resumeState: await session.detach(),\n origin: \"recovered\",\n });\n }\n if (stored.record.result) return stored.record.result;\n }\n if (turn === undefined && stored.record.turns.length === 0) {\n const session = await configured.runner.open({\n sandbox,\n sessionId: stored.record.harnessSessionId,\n resumeFrom: stored.record.resumeState,\n abortSignal,\n });\n turn = await session.continueTurn();\n stored = await ledger.recordTurn({\n base: stored,\n turn: turn.usage,\n resumeState: await session.detach(),\n origin: promptedHere ? \"this-driver\" : \"recovered\",\n });\n if (stored.record.result) return stored.record.result;\n }\n const result = await extract(sandbox, stored.record);\n abortSignal.throwIfAborted();\n return ledger.finalize(stored, result);\n },\n });\n } catch (error) {\n const latest = await ledger.read(execution);\n if (latest?.record.result) return latest.record.result;\n if (!(error instanceof ExecutionTimeoutError) || !latest) throw error;\n await stopEnvironment(latest.record);\n return ledger.finalize(latest, {\n status: \"failed\",\n artifacts: [],\n reason: error.message,\n });\n }\n },\n\n pending: ledger.pending,\n };\n}\n"],"mappings":"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"}
|
package/dist/schema/catalog.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isoDateTimeSchema, replyToSchema, schemaVersionSchema } from "./common.mjs";
|
|
2
|
-
import { externalSessionSchema, storedSessionSchema } from "./stored-session.mjs";
|
|
3
2
|
import { changeSchema } from "./change.mjs";
|
|
3
|
+
import { externalSessionSchema, storedSessionSchema } from "./stored-session.mjs";
|
|
4
4
|
import { storedTaskSchema } from "./stored-task.mjs";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region src/schema/catalog.ts
|
package/dist/schema/change.d.mts
CHANGED
|
@@ -10,11 +10,11 @@ declare const changeLevelSchema: z.ZodEnum<{
|
|
|
10
10
|
declare const changeStateSchema: z.ZodEnum<{
|
|
11
11
|
verifying: "verifying";
|
|
12
12
|
ready: "ready";
|
|
13
|
+
closed: "closed";
|
|
13
14
|
merged: "merged";
|
|
14
15
|
deployed: "deployed";
|
|
15
16
|
rolled_out: "rolled_out";
|
|
16
17
|
rolled_back: "rolled_back";
|
|
17
|
-
closed: "closed";
|
|
18
18
|
}>;
|
|
19
19
|
/** Validates the verification evidence used to assess a Change. */
|
|
20
20
|
declare const changeEvidenceSchema: z.ZodObject<{
|
|
@@ -10,8 +10,8 @@ declare const communicationOutcomeSchema: z.ZodEnum<{
|
|
|
10
10
|
needs_human: "needs_human";
|
|
11
11
|
cancelled: "cancelled";
|
|
12
12
|
accepted: "accepted";
|
|
13
|
-
question: "question";
|
|
14
13
|
rolled_back: "rolled_back";
|
|
14
|
+
question: "question";
|
|
15
15
|
started: "started";
|
|
16
16
|
digest: "digest";
|
|
17
17
|
budget_warning: "budget_warning";
|
|
@@ -34,8 +34,8 @@ declare const communicationEventSchema: z.ZodObject<{
|
|
|
34
34
|
needs_human: "needs_human";
|
|
35
35
|
cancelled: "cancelled";
|
|
36
36
|
accepted: "accepted";
|
|
37
|
-
question: "question";
|
|
38
37
|
rolled_back: "rolled_back";
|
|
38
|
+
question: "question";
|
|
39
39
|
started: "started";
|
|
40
40
|
digest: "digest";
|
|
41
41
|
budget_warning: "budget_warning";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RepositorySlug } from "./repository.mjs";
|
|
2
1
|
import { TaskKind } from "./task.mjs";
|
|
2
|
+
import { RepositorySlug } from "./repository.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/schema/factory-config.d.ts
|
|
5
5
|
/** Validates one repository allowlist entry and its configured dependency ordering. */
|
package/dist/schema/outbox.d.mts
CHANGED
|
@@ -29,8 +29,8 @@ declare const outboxMessageSchema: z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodO
|
|
|
29
29
|
needs_human: "needs_human";
|
|
30
30
|
cancelled: "cancelled";
|
|
31
31
|
accepted: "accepted";
|
|
32
|
-
question: "question";
|
|
33
32
|
rolled_back: "rolled_back";
|
|
33
|
+
question: "question";
|
|
34
34
|
started: "started";
|
|
35
35
|
digest: "digest";
|
|
36
36
|
budget_warning: "budget_warning";
|
|
@@ -65,8 +65,8 @@ declare const outboxMessageSchema: z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodO
|
|
|
65
65
|
needs_human: "needs_human";
|
|
66
66
|
cancelled: "cancelled";
|
|
67
67
|
accepted: "accepted";
|
|
68
|
-
question: "question";
|
|
69
68
|
rolled_back: "rolled_back";
|
|
69
|
+
question: "question";
|
|
70
70
|
started: "started";
|
|
71
71
|
digest: "digest";
|
|
72
72
|
budget_warning: "budget_warning";
|
|
@@ -101,8 +101,8 @@ declare const outboxMessageSchema: z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodO
|
|
|
101
101
|
needs_human: "needs_human";
|
|
102
102
|
cancelled: "cancelled";
|
|
103
103
|
accepted: "accepted";
|
|
104
|
-
question: "question";
|
|
105
104
|
rolled_back: "rolled_back";
|
|
105
|
+
question: "question";
|
|
106
106
|
started: "started";
|
|
107
107
|
digest: "digest";
|
|
108
108
|
budget_warning: "budget_warning";
|
|
@@ -144,8 +144,8 @@ declare const outboxQueueSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
144
144
|
needs_human: "needs_human";
|
|
145
145
|
cancelled: "cancelled";
|
|
146
146
|
accepted: "accepted";
|
|
147
|
-
question: "question";
|
|
148
147
|
rolled_back: "rolled_back";
|
|
148
|
+
question: "question";
|
|
149
149
|
started: "started";
|
|
150
150
|
digest: "digest";
|
|
151
151
|
budget_warning: "budget_warning";
|
|
@@ -180,8 +180,8 @@ declare const outboxQueueSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
180
180
|
needs_human: "needs_human";
|
|
181
181
|
cancelled: "cancelled";
|
|
182
182
|
accepted: "accepted";
|
|
183
|
-
question: "question";
|
|
184
183
|
rolled_back: "rolled_back";
|
|
184
|
+
question: "question";
|
|
185
185
|
started: "started";
|
|
186
186
|
digest: "digest";
|
|
187
187
|
budget_warning: "budget_warning";
|
|
@@ -216,8 +216,8 @@ declare const outboxQueueSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
216
216
|
needs_human: "needs_human";
|
|
217
217
|
cancelled: "cancelled";
|
|
218
218
|
accepted: "accepted";
|
|
219
|
-
question: "question";
|
|
220
219
|
rolled_back: "rolled_back";
|
|
220
|
+
question: "question";
|
|
221
221
|
started: "started";
|
|
222
222
|
digest: "digest";
|
|
223
223
|
budget_warning: "budget_warning";
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { TaskKind } from "./task.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/schema/session-summary.d.ts
|
|
4
|
+
/** Compact Task fields needed to render a conversation card, without execution inputs or outputs. */
|
|
5
|
+
declare const sessionTaskSummarySchema: z.ZodObject<{
|
|
6
|
+
id: z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
7
|
+
replyTo: z.ZodObject<{
|
|
8
|
+
channel: z.ZodString;
|
|
9
|
+
address: z.ZodString;
|
|
10
|
+
}, z.core.$strict>;
|
|
11
|
+
attempt: z.ZodNumber;
|
|
12
|
+
state: z.ZodEnum<{
|
|
13
|
+
queued: "queued";
|
|
14
|
+
running: "running";
|
|
15
|
+
stalled: "stalled";
|
|
16
|
+
verifying: "verifying";
|
|
17
|
+
succeeded: "succeeded";
|
|
18
|
+
failed: "failed";
|
|
19
|
+
needs_human: "needs_human";
|
|
20
|
+
cancelled: "cancelled";
|
|
21
|
+
}>;
|
|
22
|
+
updatedAt: z.ZodISODateTime;
|
|
23
|
+
createdAt: z.ZodISODateTime;
|
|
24
|
+
repositoryIds: z.ZodReadonly<z.ZodTuple<[z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>], z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>>>;
|
|
25
|
+
kind: z.ZodType<TaskKind, unknown, z.core.$ZodTypeInternals<TaskKind, unknown>>;
|
|
26
|
+
approval: z.ZodOptional<z.ZodEnum<{
|
|
27
|
+
required: "required";
|
|
28
|
+
granted: "granted";
|
|
29
|
+
denied: "denied";
|
|
30
|
+
}>>;
|
|
31
|
+
rootTaskId: z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
32
|
+
parentTaskId: z.ZodOptional<z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>;
|
|
33
|
+
stalledReason: z.ZodOptional<z.ZodString>;
|
|
34
|
+
needsHumanAction: z.ZodOptional<z.ZodString>;
|
|
35
|
+
changeId: z.ZodOptional<z.ZodType<`chg_${string}`, string, z.core.$ZodTypeInternals<`chg_${string}`, string>>>;
|
|
36
|
+
work: z.ZodObject<{
|
|
37
|
+
title: z.ZodString;
|
|
38
|
+
route: z.ZodOptional<z.core.$ZodBranded<z.ZodObject<{
|
|
39
|
+
id: z.ZodString;
|
|
40
|
+
version: z.ZodNumber;
|
|
41
|
+
}, z.core.$strict>, "AgentRouteBinding", "out">>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
/** Compact Task state on an operator conversation card. */
|
|
45
|
+
type SessionTaskSummary = z.infer<typeof sessionTaskSummarySchema>;
|
|
46
|
+
/** A conversation card deliberately excludes canonical Task bodies, histories, and transcript content. */
|
|
47
|
+
declare const conversationSummarySchema: z.ZodObject<{
|
|
48
|
+
session: z.ZodObject<{
|
|
49
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
50
|
+
id: z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
|
|
51
|
+
owner: z.ZodString;
|
|
52
|
+
agent: z.ZodString;
|
|
53
|
+
repositoryId: z.ZodOptional<z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>>;
|
|
54
|
+
route: z.ZodOptional<z.core.$ZodBranded<z.ZodObject<{
|
|
55
|
+
id: z.ZodString;
|
|
56
|
+
version: z.ZodNumber;
|
|
57
|
+
}, z.core.$strict>, "AgentRouteBinding", "out">>;
|
|
58
|
+
model: z.ZodOptional<z.ZodString>;
|
|
59
|
+
attachments: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
60
|
+
id: z.ZodType<`img_${string}`, string, z.core.$ZodTypeInternals<`img_${string}`, string>>;
|
|
61
|
+
filename: z.ZodString;
|
|
62
|
+
mediaType: z.ZodEnum<{
|
|
63
|
+
"image/png": "image/png";
|
|
64
|
+
"image/jpeg": "image/jpeg";
|
|
65
|
+
"image/webp": "image/webp";
|
|
66
|
+
}>;
|
|
67
|
+
size: z.ZodNumber;
|
|
68
|
+
}, z.core.$strict>>>>;
|
|
69
|
+
changeId: z.ZodOptional<z.ZodType<`chg_${string}`, string, z.core.$ZodTypeInternals<`chg_${string}`, string>>>;
|
|
70
|
+
taskId: z.ZodOptional<z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>;
|
|
71
|
+
attached: z.ZodOptional<z.ZodBoolean>;
|
|
72
|
+
latestContinuationId: z.ZodOptional<z.ZodString>;
|
|
73
|
+
execution: z.ZodOptional<z.ZodObject<{
|
|
74
|
+
provider: z.ZodString;
|
|
75
|
+
sessionId: z.ZodString;
|
|
76
|
+
environmentId: z.ZodOptional<z.ZodString>;
|
|
77
|
+
harness: z.ZodOptional<z.ZodString>;
|
|
78
|
+
traceId: z.ZodOptional<z.ZodString>;
|
|
79
|
+
}, z.core.$strict>>;
|
|
80
|
+
external: z.ZodOptional<z.ZodReadonly<z.ZodObject<{
|
|
81
|
+
replyTo: z.ZodObject<{
|
|
82
|
+
channel: z.ZodString;
|
|
83
|
+
address: z.ZodString;
|
|
84
|
+
}, z.core.$strict>;
|
|
85
|
+
repositoryIds: z.ZodReadonly<z.ZodTuple<[z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>], z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>>>;
|
|
86
|
+
url: z.ZodOptional<z.ZodURL>;
|
|
87
|
+
}, z.core.$strict>>>;
|
|
88
|
+
latestNotificationId: z.ZodOptional<z.ZodString>;
|
|
89
|
+
createdAt: z.ZodISODateTime;
|
|
90
|
+
message: z.ZodString;
|
|
91
|
+
}, z.core.$strip>;
|
|
92
|
+
task: z.ZodNullable<z.ZodObject<{
|
|
93
|
+
id: z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
94
|
+
replyTo: z.ZodObject<{
|
|
95
|
+
channel: z.ZodString;
|
|
96
|
+
address: z.ZodString;
|
|
97
|
+
}, z.core.$strict>;
|
|
98
|
+
attempt: z.ZodNumber;
|
|
99
|
+
state: z.ZodEnum<{
|
|
100
|
+
queued: "queued";
|
|
101
|
+
running: "running";
|
|
102
|
+
stalled: "stalled";
|
|
103
|
+
verifying: "verifying";
|
|
104
|
+
succeeded: "succeeded";
|
|
105
|
+
failed: "failed";
|
|
106
|
+
needs_human: "needs_human";
|
|
107
|
+
cancelled: "cancelled";
|
|
108
|
+
}>;
|
|
109
|
+
updatedAt: z.ZodISODateTime;
|
|
110
|
+
createdAt: z.ZodISODateTime;
|
|
111
|
+
repositoryIds: z.ZodReadonly<z.ZodTuple<[z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>], z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>>>;
|
|
112
|
+
kind: z.ZodType<TaskKind, unknown, z.core.$ZodTypeInternals<TaskKind, unknown>>;
|
|
113
|
+
approval: z.ZodOptional<z.ZodEnum<{
|
|
114
|
+
required: "required";
|
|
115
|
+
granted: "granted";
|
|
116
|
+
denied: "denied";
|
|
117
|
+
}>>;
|
|
118
|
+
rootTaskId: z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
119
|
+
parentTaskId: z.ZodOptional<z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>;
|
|
120
|
+
stalledReason: z.ZodOptional<z.ZodString>;
|
|
121
|
+
needsHumanAction: z.ZodOptional<z.ZodString>;
|
|
122
|
+
changeId: z.ZodOptional<z.ZodType<`chg_${string}`, string, z.core.$ZodTypeInternals<`chg_${string}`, string>>>;
|
|
123
|
+
work: z.ZodObject<{
|
|
124
|
+
title: z.ZodString;
|
|
125
|
+
route: z.ZodOptional<z.core.$ZodBranded<z.ZodObject<{
|
|
126
|
+
id: z.ZodString;
|
|
127
|
+
version: z.ZodNumber;
|
|
128
|
+
}, z.core.$strict>, "AgentRouteBinding", "out">>;
|
|
129
|
+
}, z.core.$strip>;
|
|
130
|
+
}, z.core.$strip>>;
|
|
131
|
+
question: z.ZodNullable<z.ZodObject<{
|
|
132
|
+
id: z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
133
|
+
updatedAt: z.ZodISODateTime;
|
|
134
|
+
question: z.ZodOptional<z.ZodObject<{
|
|
135
|
+
text: z.ZodString;
|
|
136
|
+
options: z.ZodReadonly<z.ZodArray<z.ZodString>>;
|
|
137
|
+
closingOptions: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
138
|
+
answer: z.ZodOptional<z.ZodString>;
|
|
139
|
+
answeredBy: z.ZodOptional<z.ZodString>;
|
|
140
|
+
}, z.core.$strict>>;
|
|
141
|
+
}, z.core.$strip>>;
|
|
142
|
+
execution: z.ZodNullable<z.ZodObject<{
|
|
143
|
+
provider: z.ZodString;
|
|
144
|
+
sessionId: z.ZodString;
|
|
145
|
+
environmentId: z.ZodOptional<z.ZodString>;
|
|
146
|
+
harness: z.ZodOptional<z.ZodString>;
|
|
147
|
+
traceId: z.ZodOptional<z.ZodString>;
|
|
148
|
+
}, z.core.$strict>>;
|
|
149
|
+
activeAgent: z.ZodOptional<z.ZodString>;
|
|
150
|
+
source: z.ZodOptional<z.ZodString>;
|
|
151
|
+
prUrl: z.ZodOptional<z.ZodString>;
|
|
152
|
+
attention: z.ZodOptional<z.ZodEnum<{
|
|
153
|
+
stalled: "stalled";
|
|
154
|
+
failed: "failed";
|
|
155
|
+
question: "question";
|
|
156
|
+
review: "review";
|
|
157
|
+
conversation: "conversation";
|
|
158
|
+
}>>;
|
|
159
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
160
|
+
stalled: "stalled";
|
|
161
|
+
verifying: "verifying";
|
|
162
|
+
failed: "failed";
|
|
163
|
+
ready: "ready";
|
|
164
|
+
working: "working";
|
|
165
|
+
delegated: "delegated";
|
|
166
|
+
needs_input: "needs_input";
|
|
167
|
+
awaiting_approval: "awaiting_approval";
|
|
168
|
+
}>>;
|
|
169
|
+
statusRevision: z.ZodOptional<z.ZodString>;
|
|
170
|
+
hasWorkflowFailure: z.ZodOptional<z.ZodBoolean>;
|
|
171
|
+
canCancel: z.ZodOptional<z.ZodBoolean>;
|
|
172
|
+
canFollowUp: z.ZodOptional<z.ZodBoolean>;
|
|
173
|
+
dependencyTaskIds: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>>>;
|
|
174
|
+
relatedTasks: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
175
|
+
id: z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
176
|
+
replyTo: z.ZodObject<{
|
|
177
|
+
channel: z.ZodString;
|
|
178
|
+
address: z.ZodString;
|
|
179
|
+
}, z.core.$strict>;
|
|
180
|
+
attempt: z.ZodNumber;
|
|
181
|
+
state: z.ZodEnum<{
|
|
182
|
+
queued: "queued";
|
|
183
|
+
running: "running";
|
|
184
|
+
stalled: "stalled";
|
|
185
|
+
verifying: "verifying";
|
|
186
|
+
succeeded: "succeeded";
|
|
187
|
+
failed: "failed";
|
|
188
|
+
needs_human: "needs_human";
|
|
189
|
+
cancelled: "cancelled";
|
|
190
|
+
}>;
|
|
191
|
+
updatedAt: z.ZodISODateTime;
|
|
192
|
+
createdAt: z.ZodISODateTime;
|
|
193
|
+
repositoryIds: z.ZodReadonly<z.ZodTuple<[z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>], z.ZodType<`repo_${string}`, string, z.core.$ZodTypeInternals<`repo_${string}`, string>>>>;
|
|
194
|
+
kind: z.ZodType<TaskKind, unknown, z.core.$ZodTypeInternals<TaskKind, unknown>>;
|
|
195
|
+
approval: z.ZodOptional<z.ZodEnum<{
|
|
196
|
+
required: "required";
|
|
197
|
+
granted: "granted";
|
|
198
|
+
denied: "denied";
|
|
199
|
+
}>>;
|
|
200
|
+
rootTaskId: z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>;
|
|
201
|
+
parentTaskId: z.ZodOptional<z.ZodType<`task_${string}`, string, z.core.$ZodTypeInternals<`task_${string}`, string>>>;
|
|
202
|
+
stalledReason: z.ZodOptional<z.ZodString>;
|
|
203
|
+
needsHumanAction: z.ZodOptional<z.ZodString>;
|
|
204
|
+
changeId: z.ZodOptional<z.ZodType<`chg_${string}`, string, z.core.$ZodTypeInternals<`chg_${string}`, string>>>;
|
|
205
|
+
work: z.ZodObject<{
|
|
206
|
+
title: z.ZodString;
|
|
207
|
+
route: z.ZodOptional<z.core.$ZodBranded<z.ZodObject<{
|
|
208
|
+
id: z.ZodString;
|
|
209
|
+
version: z.ZodNumber;
|
|
210
|
+
}, z.core.$strict>, "AgentRouteBinding", "out">>;
|
|
211
|
+
}, z.core.$strip>;
|
|
212
|
+
}, z.core.$strip>>>>;
|
|
213
|
+
}, z.core.$strip>;
|
|
214
|
+
/** Stored conversation card before request-time operator enrichment. */
|
|
215
|
+
type ConversationSummary = z.infer<typeof conversationSummarySchema>;
|
|
216
|
+
//#endregion
|
|
217
|
+
export { ConversationSummary, SessionTaskSummary, conversationSummarySchema, sessionTaskSummarySchema };
|
|
218
|
+
//# sourceMappingURL=session-summary.d.mts.map
|