@namzu/sdk 58.1.1 → 60.0.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.
- package/CHANGELOG.md +124 -0
- package/dist/account/binding.d.ts +20 -0
- package/dist/account/binding.d.ts.map +1 -0
- package/dist/account/binding.js +27 -0
- package/dist/account/binding.js.map +1 -0
- package/dist/account/index.d.ts +9 -0
- package/dist/account/index.d.ts.map +1 -0
- package/dist/account/index.js +5 -0
- package/dist/account/index.js.map +1 -0
- package/dist/account/lock.d.ts +33 -0
- package/dist/account/lock.d.ts.map +1 -0
- package/dist/account/lock.js +170 -0
- package/dist/account/lock.js.map +1 -0
- package/dist/account/registry.d.ts +21 -0
- package/dist/account/registry.d.ts.map +1 -0
- package/dist/account/registry.js +340 -0
- package/dist/account/registry.js.map +1 -0
- package/dist/account/types.d.ts +124 -0
- package/dist/account/types.d.ts.map +1 -0
- package/dist/account/types.js +35 -0
- package/dist/account/types.js.map +1 -0
- package/dist/compaction/context-window.d.ts +53 -0
- package/dist/compaction/context-window.d.ts.map +1 -1
- package/dist/compaction/context-window.js +141 -2
- package/dist/compaction/context-window.js.map +1 -1
- package/dist/execution/workflow/index.d.ts +3 -1
- package/dist/execution/workflow/index.d.ts.map +1 -1
- package/dist/execution/workflow/index.js +2 -1
- package/dist/execution/workflow/index.js.map +1 -1
- package/dist/execution/workflow/journal.d.ts +24 -4
- package/dist/execution/workflow/journal.d.ts.map +1 -1
- package/dist/execution/workflow/journal.js +131 -9
- package/dist/execution/workflow/journal.js.map +1 -1
- package/dist/execution/workflow/prelude.d.ts +2 -0
- package/dist/execution/workflow/prelude.d.ts.map +1 -1
- package/dist/execution/workflow/prelude.js +49 -8
- package/dist/execution/workflow/prelude.js.map +1 -1
- package/dist/execution/workflow/resume.d.ts +59 -0
- package/dist/execution/workflow/resume.d.ts.map +1 -0
- package/dist/execution/workflow/resume.js +273 -0
- package/dist/execution/workflow/resume.js.map +1 -0
- package/dist/execution/workflow/runtime.d.ts.map +1 -1
- package/dist/execution/workflow/runtime.js +112 -8
- package/dist/execution/workflow/runtime.js.map +1 -1
- package/dist/execution/workflow/types.d.ts +105 -1
- package/dist/execution/workflow/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/pals/communication/ingress-schema.d.ts +38 -38
- package/dist/peers/record.d.ts +98 -0
- package/dist/peers/record.d.ts.map +1 -1
- package/dist/peers/record.js +17 -0
- package/dist/peers/record.js.map +1 -1
- package/dist/peers/registry.d.ts.map +1 -1
- package/dist/peers/registry.js +2 -2
- package/dist/peers/registry.js.map +1 -1
- package/dist/plugin/compat.d.ts +24 -0
- package/dist/plugin/compat.d.ts.map +1 -0
- package/dist/plugin/compat.js +149 -0
- package/dist/plugin/compat.js.map +1 -0
- package/dist/plugin/loader.d.ts +2 -1
- package/dist/plugin/loader.d.ts.map +1 -1
- package/dist/plugin/loader.js +6 -0
- package/dist/plugin/loader.js.map +1 -1
- package/dist/pricing/index.d.ts.map +1 -1
- package/dist/pricing/index.js +2 -1
- package/dist/pricing/index.js.map +1 -1
- package/dist/process/kill-tree.d.ts +44 -0
- package/dist/process/kill-tree.d.ts.map +1 -1
- package/dist/process/kill-tree.js +102 -4
- package/dist/process/kill-tree.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +9 -1
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +25 -2
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/model-window-suffix.d.ts +18 -0
- package/dist/provider/model-window-suffix.d.ts.map +1 -0
- package/dist/provider/model-window-suffix.js +77 -0
- package/dist/provider/model-window-suffix.js.map +1 -0
- package/dist/provider/token-budget.d.ts.map +1 -1
- package/dist/provider/token-budget.js +9 -1
- package/dist/provider/token-budget.js.map +1 -1
- package/dist/public-runtime.d.ts +6 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +6 -4
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +3 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/registry/command/kernel-commands.d.ts +1 -4
- package/dist/registry/command/kernel-commands.d.ts.map +1 -1
- package/dist/registry/command/kernel-commands.js +0 -28
- package/dist/registry/command/kernel-commands.js.map +1 -1
- package/dist/runtime/jobs/awaited-jobs.d.ts +6 -0
- package/dist/runtime/jobs/awaited-jobs.d.ts.map +1 -1
- package/dist/runtime/jobs/awaited-jobs.js +11 -0
- package/dist/runtime/jobs/awaited-jobs.js.map +1 -1
- package/dist/runtime/jobs/host-exit-finalizer.d.ts +20 -0
- package/dist/runtime/jobs/host-exit-finalizer.d.ts.map +1 -0
- package/dist/runtime/jobs/host-exit-finalizer.js +57 -0
- package/dist/runtime/jobs/host-exit-finalizer.js.map +1 -0
- package/dist/runtime/jobs/liveness.d.ts +67 -0
- package/dist/runtime/jobs/liveness.d.ts.map +1 -0
- package/dist/runtime/jobs/liveness.js +94 -0
- package/dist/runtime/jobs/liveness.js.map +1 -0
- package/dist/runtime/jobs/registry.d.ts +21 -0
- package/dist/runtime/jobs/registry.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.js +104 -2
- package/dist/runtime/jobs/registry.js.map +1 -1
- package/dist/runtime/jobs/wake-budget.d.ts +35 -0
- package/dist/runtime/jobs/wake-budget.d.ts.map +1 -0
- package/dist/runtime/jobs/wake-budget.js +48 -0
- package/dist/runtime/jobs/wake-budget.js.map +1 -0
- package/dist/runtime/query/detached-calls.d.ts +65 -0
- package/dist/runtime/query/detached-calls.d.ts.map +1 -0
- package/dist/runtime/query/detached-calls.js +116 -0
- package/dist/runtime/query/detached-calls.js.map +1 -0
- package/dist/runtime/query/executor.d.ts +11 -3
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +100 -8
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +20 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +31 -5
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/outstanding-work.d.ts.map +1 -1
- package/dist/runtime/query/iteration/outstanding-work.js +11 -3
- package/dist/runtime/query/iteration/outstanding-work.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +7 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +63 -1
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/session-storage.d.ts.map +1 -1
- package/dist/runtime/query/session-storage.js +8 -3
- package/dist/runtime/query/session-storage.js.map +1 -1
- package/dist/runtime/query/steering.d.ts +29 -3
- package/dist/runtime/query/steering.d.ts.map +1 -1
- package/dist/runtime/query/steering.js +58 -3
- package/dist/runtime/query/steering.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +1 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +1 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/runtime/system-events.d.ts +5 -0
- package/dist/runtime/system-events.d.ts.map +1 -1
- package/dist/runtime/system-events.js +7 -1
- package/dist/runtime/system-events.js.map +1 -1
- package/dist/sandbox/denial.d.ts +11 -0
- package/dist/sandbox/denial.d.ts.map +1 -0
- package/dist/sandbox/denial.js +61 -0
- package/dist/sandbox/denial.js.map +1 -0
- package/dist/sandbox/file-walk-program.d.ts.map +1 -1
- package/dist/sandbox/file-walk-program.js +31 -13
- package/dist/sandbox/file-walk-program.js.map +1 -1
- package/dist/sandbox/file-walk.d.ts +2 -0
- package/dist/sandbox/file-walk.d.ts.map +1 -1
- package/dist/sandbox/file-walk.js +64 -13
- package/dist/sandbox/file-walk.js.map +1 -1
- package/dist/sandbox/ignore-matcher.d.ts +30 -0
- package/dist/sandbox/ignore-matcher.d.ts.map +1 -0
- package/dist/sandbox/ignore-matcher.js +11 -0
- package/dist/sandbox/ignore-matcher.js.map +1 -0
- package/dist/sandbox/ignore-source.d.ts +26 -0
- package/dist/sandbox/ignore-source.d.ts.map +1 -0
- package/dist/sandbox/ignore-source.js +221 -0
- package/dist/sandbox/ignore-source.js.map +1 -0
- package/dist/sandbox/provider/local.d.ts.map +1 -1
- package/dist/sandbox/provider/local.js +11 -1
- package/dist/sandbox/provider/local.js.map +1 -1
- package/dist/scheduler/completion-inbox.d.ts.map +1 -1
- package/dist/scheduler/completion-inbox.js +1 -0
- package/dist/scheduler/completion-inbox.js.map +1 -1
- package/dist/session/log-hash.d.ts.map +1 -1
- package/dist/session/log-hash.js +18 -1
- package/dist/session/log-hash.js.map +1 -1
- package/dist/store/session-index/sqlite.d.ts.map +1 -1
- package/dist/store/session-index/sqlite.js +169 -16
- package/dist/store/session-index/sqlite.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +10 -4
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts +35 -0
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +512 -67
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/tools/builtins/glob.d.ts +1 -0
- package/dist/tools/builtins/glob.d.ts.map +1 -1
- package/dist/tools/builtins/glob.js +65 -28
- package/dist/tools/builtins/glob.js.map +1 -1
- package/dist/tools/builtins/grep.d.ts +2 -0
- package/dist/tools/builtins/grep.d.ts.map +1 -1
- package/dist/tools/builtins/grep.js +94 -40
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/job.d.ts +1 -1
- package/dist/tools/builtins/job.d.ts.map +1 -1
- package/dist/tools/builtins/job.js +34 -7
- package/dist/tools/builtins/job.js.map +1 -1
- package/dist/tools/builtins/search-support.d.ts +44 -0
- package/dist/tools/builtins/search-support.d.ts.map +1 -0
- package/dist/tools/builtins/search-support.js +89 -0
- package/dist/tools/builtins/search-support.js.map +1 -0
- package/dist/tools/builtins/wait-for-job-bounds.d.ts +2 -0
- package/dist/tools/builtins/wait-for-job-bounds.d.ts.map +1 -1
- package/dist/tools/builtins/wait-for-job-bounds.js +3 -1
- package/dist/tools/builtins/wait-for-job-bounds.js.map +1 -1
- package/dist/tools/builtins/wait-for-job.d.ts +1 -1
- package/dist/tools/builtins/wait-for-job.d.ts.map +1 -1
- package/dist/tools/builtins/wait-for-job.js +13 -2
- package/dist/tools/builtins/wait-for-job.js.map +1 -1
- package/dist/tools/command-shell.d.ts.map +1 -1
- package/dist/tools/command-shell.js +14 -5
- package/dist/tools/command-shell.js.map +1 -1
- package/dist/types/computer-use/index.d.ts +129 -13
- package/dist/types/computer-use/index.d.ts.map +1 -1
- package/dist/types/computer-use/index.js.map +1 -1
- package/dist/types/errors/catalog.d.ts.map +1 -1
- package/dist/types/errors/catalog.js +6 -0
- package/dist/types/errors/catalog.js.map +1 -1
- package/dist/types/plugin/index.d.ts +24 -0
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +4 -0
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/provider/errors.d.ts +7 -0
- package/dist/types/provider/errors.d.ts.map +1 -1
- package/dist/types/provider/errors.js +27 -3
- package/dist/types/provider/errors.js.map +1 -1
- package/dist/types/sandbox/index.d.ts +38 -1
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/session/records.d.ts +479 -1
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +45 -1
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/tool/index.d.ts +30 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +8 -0
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/account/binding.ts +43 -0
- package/src/account/index.ts +32 -0
- package/src/account/lock.ts +184 -0
- package/src/account/registry.ts +413 -0
- package/src/account/types.ts +137 -0
- package/src/compaction/context-window.ts +163 -2
- package/src/execution/workflow/index.ts +14 -1
- package/src/execution/workflow/journal.ts +161 -11
- package/src/execution/workflow/prelude.ts +51 -8
- package/src/execution/workflow/resume.ts +313 -0
- package/src/execution/workflow/runtime.ts +127 -9
- package/src/execution/workflow/types.ts +109 -1
- package/src/index.ts +17 -0
- package/src/peers/record.ts +18 -0
- package/src/peers/registry.ts +2 -2
- package/src/plugin/compat.ts +173 -0
- package/src/plugin/loader.ts +7 -1
- package/src/pricing/index.ts +2 -1
- package/src/process/kill-tree.ts +106 -4
- package/src/prompt/coding-agent-doctrine.ts +9 -1
- package/src/provider/errors.ts +27 -1
- package/src/provider/model-window-suffix.ts +84 -0
- package/src/provider/token-budget.ts +9 -3
- package/src/public-runtime.ts +38 -2
- package/src/public-types.ts +16 -0
- package/src/registry/command/kernel-commands.ts +1 -32
- package/src/runtime/jobs/awaited-jobs.ts +11 -0
- package/src/runtime/jobs/host-exit-finalizer.ts +67 -0
- package/src/runtime/jobs/liveness.ts +144 -0
- package/src/runtime/jobs/registry.ts +114 -2
- package/src/runtime/jobs/wake-budget.ts +55 -0
- package/src/runtime/query/detached-calls.ts +135 -0
- package/src/runtime/query/executor.ts +102 -12
- package/src/runtime/query/index.ts +50 -7
- package/src/runtime/query/iteration/outstanding-work.ts +12 -3
- package/src/runtime/query/iteration/phases/context.ts +7 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +73 -8
- package/src/runtime/query/session-storage.ts +8 -3
- package/src/runtime/query/steering.ts +77 -6
- package/src/runtime/query/tooling.ts +2 -0
- package/src/runtime/system-events.ts +12 -1
- package/src/sandbox/denial.ts +79 -0
- package/src/sandbox/file-walk-program.ts +32 -13
- package/src/sandbox/file-walk.ts +75 -12
- package/src/sandbox/ignore-matcher.ts +46 -0
- package/src/sandbox/ignore-source.ts +220 -0
- package/src/sandbox/provider/local.ts +11 -1
- package/src/scheduler/completion-inbox.ts +1 -0
- package/src/session/log-hash.ts +21 -0
- package/src/store/session-index/sqlite.ts +171 -16
- package/src/tools/builtins/bash.ts +11 -4
- package/src/tools/builtins/computer-use.ts +593 -68
- package/src/tools/builtins/glob.ts +79 -32
- package/src/tools/builtins/grep.ts +107 -43
- package/src/tools/builtins/job.ts +42 -12
- package/src/tools/builtins/search-support.ts +107 -0
- package/src/tools/builtins/wait-for-job-bounds.ts +5 -1
- package/src/tools/builtins/wait-for-job.ts +14 -2
- package/src/tools/command-shell.ts +13 -5
- package/src/types/computer-use/index.ts +157 -15
- package/src/types/errors/catalog.ts +6 -0
- package/src/types/plugin/index.ts +8 -0
- package/src/types/provider/errors.ts +40 -3
- package/src/types/sandbox/index.ts +40 -1
- package/src/types/session/records.ts +52 -2
- package/src/types/tool/index.ts +30 -0
- package/src/types/tool/presentation.ts +8 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { createHash, createHmac, timingSafeEqual } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ChainedWorkflowJournalRecord,
|
|
5
|
+
CheckedWorkflowJournal,
|
|
6
|
+
WorkflowFinishStatus,
|
|
7
|
+
WorkflowJournalRecord,
|
|
8
|
+
WorkflowResumeState,
|
|
9
|
+
} from './types.js'
|
|
10
|
+
|
|
11
|
+
/** The key version. A change to how keys are made bumps it, so old keys simply never match. */
|
|
12
|
+
const KEY_VERSION = 'k1'
|
|
13
|
+
|
|
14
|
+
function sha256Hex(text: string): string {
|
|
15
|
+
return createHash('sha256').update(text).digest('hex')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** JSON with every object's keys sorted and `__proto__` dropped, so equal values give equal text. */
|
|
19
|
+
function canonicalJson(value: unknown): string {
|
|
20
|
+
if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item)).join(',')}]`
|
|
21
|
+
if (typeof value === 'object' && value !== null) {
|
|
22
|
+
const record = value as Record<string, unknown>
|
|
23
|
+
const keys = Object.keys(record)
|
|
24
|
+
.filter((key) => key !== '__proto__' && record[key] !== undefined)
|
|
25
|
+
.sort()
|
|
26
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`
|
|
27
|
+
}
|
|
28
|
+
return JSON.stringify(value) ?? 'null'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** What of an `agent()` call decides its result. `label`, `phase` and progress callbacks do not. */
|
|
32
|
+
export interface WorkflowCallIdentity {
|
|
33
|
+
readonly schema?: unknown
|
|
34
|
+
readonly model?: string
|
|
35
|
+
readonly effort?: string
|
|
36
|
+
readonly agentType?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The key a call is saved and looked up under: the prompt, the options that
|
|
41
|
+
* change what the agent does (schema, model, effort, agent type) and how many
|
|
42
|
+
* identical calls came before it in the same pass. Each part is length-prefixed
|
|
43
|
+
* so one cannot be read as part of another.
|
|
44
|
+
*/
|
|
45
|
+
export function workflowCallKey(
|
|
46
|
+
prompt: string,
|
|
47
|
+
call: WorkflowCallIdentity,
|
|
48
|
+
ordinal: number,
|
|
49
|
+
): string {
|
|
50
|
+
return `${KEY_VERSION}:${sha256Hex(`${workflowCallContent(prompt, call)}${ordinal}`)}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* What makes two calls the same call: the prompt and the options that change
|
|
55
|
+
* the agent, in a form where key order and `undefined` do not matter. The
|
|
56
|
+
* runtime counts repeats of this text to give identical calls distinct slots,
|
|
57
|
+
* and the key hashes the same text, so the two can never disagree.
|
|
58
|
+
*/
|
|
59
|
+
export function workflowCallContent(prompt: string, call: WorkflowCallIdentity): string {
|
|
60
|
+
const options = canonicalJson({
|
|
61
|
+
schema: call.schema,
|
|
62
|
+
model: call.model,
|
|
63
|
+
effort: call.effort,
|
|
64
|
+
agentType: call.agentType,
|
|
65
|
+
})
|
|
66
|
+
return `${prompt.length}:${prompt}${options.length}:${options}`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The hash the first line of a run's journal chains from. It binds the file to its run id. */
|
|
70
|
+
export function workflowJournalSeed(runId: string): string {
|
|
71
|
+
return sha256Hex(`namzu-workflow-journal\0${runId}`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The hash a line contributes to the next one: its exact bytes, newline included. */
|
|
75
|
+
export function workflowJournalLineHash(line: string): string {
|
|
76
|
+
return sha256Hex(`${line}\n`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The line `record` is written as. With a key, a `mac` over the rest of the line is added last. */
|
|
80
|
+
export function formatWorkflowJournalLine(
|
|
81
|
+
record: WorkflowJournalRecord,
|
|
82
|
+
seq: number,
|
|
83
|
+
prev: string,
|
|
84
|
+
key?: string | Uint8Array,
|
|
85
|
+
): string {
|
|
86
|
+
const body = { seq, prev, ...record }
|
|
87
|
+
if (key === undefined) return JSON.stringify(body)
|
|
88
|
+
const mac = createHmac('sha256', key).update(JSON.stringify(body)).digest('hex')
|
|
89
|
+
return JSON.stringify({ ...body, mac })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function macMatches(record: Record<string, unknown>, key: string | Uint8Array): boolean {
|
|
93
|
+
const { mac, ...rest } = record
|
|
94
|
+
if (typeof mac !== 'string') return false
|
|
95
|
+
const expected = createHmac('sha256', key).update(JSON.stringify(rest)).digest()
|
|
96
|
+
const actual = Buffer.from(mac, 'hex')
|
|
97
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseLine(line: string): Record<string, unknown> | undefined {
|
|
101
|
+
try {
|
|
102
|
+
const value: unknown = JSON.parse(line)
|
|
103
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
104
|
+
? (value as Record<string, unknown>)
|
|
105
|
+
: undefined
|
|
106
|
+
} catch {
|
|
107
|
+
return undefined
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Reads a journal's text and keeps only the records that can be trusted.
|
|
113
|
+
*
|
|
114
|
+
* Every line carries `seq` (counting trusted lines from 1) and `prev` (the hash
|
|
115
|
+
* of the line before; the first chains from a seed bound to the run id). A
|
|
116
|
+
* line that does not chain, a line that does not parse, and anything after
|
|
117
|
+
* one is dropped until a `run_started` of a resumed pass picks the chain up
|
|
118
|
+
* again at the last trusted line. A last line without its newline that is not
|
|
119
|
+
* whole JSON is a torn write and is ignored; one that is whole JSON is read
|
|
120
|
+
* like any other line. The file is never rewritten.
|
|
121
|
+
*
|
|
122
|
+
* The chain alone cannot notice an edit to the final line (nothing chains from
|
|
123
|
+
* it yet). With `key`, every line must also carry a `mac` made with that
|
|
124
|
+
* secret, which closes that gap against anyone who does not hold the key.
|
|
125
|
+
*/
|
|
126
|
+
export function readWorkflowJournalChecked(
|
|
127
|
+
text: string,
|
|
128
|
+
runId: string,
|
|
129
|
+
options: { readonly key?: string | Uint8Array } = {},
|
|
130
|
+
): CheckedWorkflowJournal {
|
|
131
|
+
const endsWithNewline = text === '' || text.endsWith('\n')
|
|
132
|
+
const pieces = text.split('\n')
|
|
133
|
+
const fragment = endsWithNewline ? undefined : pieces.pop()
|
|
134
|
+
if (endsWithNewline) pieces.pop()
|
|
135
|
+
// A last line without its newline that is whole JSON was only missing the newline: it is
|
|
136
|
+
// judged like any other line (the writer adds the newline before the next record, so the
|
|
137
|
+
// hash it contributes is the one of the line with its newline). Anything else is a torn write.
|
|
138
|
+
const wholeFragment =
|
|
139
|
+
fragment !== undefined && fragment !== '' && parseLine(fragment) !== undefined
|
|
140
|
+
if (wholeFragment) pieces.push(fragment as string)
|
|
141
|
+
const tornTail = fragment !== undefined && fragment !== '' && !wholeFragment
|
|
142
|
+
|
|
143
|
+
const parsed = pieces.map((line) => parseLine(line))
|
|
144
|
+
const firstRecord = parsed.find((record) => record !== undefined)
|
|
145
|
+
if (firstRecord !== undefined && typeof firstRecord.seq !== 'number') {
|
|
146
|
+
return {
|
|
147
|
+
records: [],
|
|
148
|
+
legacy: true,
|
|
149
|
+
tornTail,
|
|
150
|
+
lastSeq: 0,
|
|
151
|
+
lastHash: workflowJournalSeed(runId),
|
|
152
|
+
endsWithNewline,
|
|
153
|
+
untrusted: parsed.filter((record) => record !== undefined) as WorkflowJournalRecord[],
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const records: ChainedWorkflowJournalRecord[] = []
|
|
158
|
+
let lastSeq = 0
|
|
159
|
+
let lastHash = workflowJournalSeed(runId)
|
|
160
|
+
let gapStart: number | undefined
|
|
161
|
+
let gapLines = 0
|
|
162
|
+
let gapParsed = false
|
|
163
|
+
let brokenAt: number | undefined
|
|
164
|
+
const closeGap = (): void => {
|
|
165
|
+
if (gapStart !== undefined && (gapLines > 1 || gapParsed)) brokenAt ??= gapStart
|
|
166
|
+
gapStart = undefined
|
|
167
|
+
gapLines = 0
|
|
168
|
+
gapParsed = false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < pieces.length; i++) {
|
|
172
|
+
const line = pieces[i] as string
|
|
173
|
+
const record = parsed[i]
|
|
174
|
+
const chains =
|
|
175
|
+
record !== undefined &&
|
|
176
|
+
record.seq === lastSeq + 1 &&
|
|
177
|
+
record.prev === lastHash &&
|
|
178
|
+
(options.key === undefined || macMatches(record, options.key))
|
|
179
|
+
const first = records.length === 0
|
|
180
|
+
const acceptable =
|
|
181
|
+
chains &&
|
|
182
|
+
(first
|
|
183
|
+
? record.type === 'run_started' && record.runId === runId
|
|
184
|
+
: gapStart === undefined || record.type === 'run_started')
|
|
185
|
+
if (acceptable) {
|
|
186
|
+
closeGap()
|
|
187
|
+
records.push(record as unknown as ChainedWorkflowJournalRecord)
|
|
188
|
+
lastSeq = record.seq as number
|
|
189
|
+
lastHash = workflowJournalLineHash(line)
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
if (gapStart === undefined) gapStart = i + 1
|
|
193
|
+
gapLines++
|
|
194
|
+
if (record !== undefined) gapParsed = true
|
|
195
|
+
}
|
|
196
|
+
// A gap that runs to the end is a change, except a single line that does not parse
|
|
197
|
+
// (what a torn write leaves once the next pass closed it with a newline).
|
|
198
|
+
closeGap()
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
records,
|
|
202
|
+
legacy: false,
|
|
203
|
+
tornTail,
|
|
204
|
+
...(brokenAt !== undefined ? { brokenAt } : {}),
|
|
205
|
+
lastSeq,
|
|
206
|
+
lastHash,
|
|
207
|
+
endsWithNewline,
|
|
208
|
+
untrusted: [],
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function hasResult(record: { readonly result?: unknown }): boolean {
|
|
213
|
+
return Object.hasOwn(record, 'result') && record.result !== undefined && record.result !== null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Folds a checked journal into what a resumed pass needs.
|
|
218
|
+
*
|
|
219
|
+
* The last saved result for a key wins. An agent that was asked for again
|
|
220
|
+
* after a result was saved gives that result up until it finishes again. A
|
|
221
|
+
* failed or skipped outcome removes the result and marks the key failed; an
|
|
222
|
+
* outcome the run recorded as interrupted (stopped, not failed) leaves the key
|
|
223
|
+
* as seen but unfinished. Lines marked `cached` repeat an earlier result and
|
|
224
|
+
* are skipped, and so is any agent record from a pass older than the latest
|
|
225
|
+
* `run_started`, which a slow agent of an earlier pass can still write.
|
|
226
|
+
*/
|
|
227
|
+
export function loadWorkflowResume(checked: CheckedWorkflowJournal): WorkflowResumeState {
|
|
228
|
+
const results = new Map<
|
|
229
|
+
string,
|
|
230
|
+
{ value: unknown; pass: number; index: number; childSessionId?: string }
|
|
231
|
+
>()
|
|
232
|
+
const seen = new Set<string>()
|
|
233
|
+
const failed = new Set<string>()
|
|
234
|
+
const demoted = new Set<string>()
|
|
235
|
+
const problems: string[] = []
|
|
236
|
+
const keysByIndex = new Map<string, string>()
|
|
237
|
+
let currentPass = 0
|
|
238
|
+
let scriptSha256: string | undefined
|
|
239
|
+
let previousFinish: WorkflowFinishStatus | 'unfinished' = 'unfinished'
|
|
240
|
+
|
|
241
|
+
for (const record of checked.records) {
|
|
242
|
+
if (record.type === 'run_started') {
|
|
243
|
+
currentPass = record.resume?.pass ?? 1
|
|
244
|
+
scriptSha256 = record.scriptSha256
|
|
245
|
+
previousFinish = 'unfinished'
|
|
246
|
+
keysByIndex.clear()
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
if (record.type === 'run_finished') {
|
|
250
|
+
previousFinish = record.status
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
253
|
+
if (record.type !== 'agent_request' && record.type !== 'agent_outcome') continue
|
|
254
|
+
const pass = record.pass ?? 1
|
|
255
|
+
if (pass < currentPass || record.cached === true) continue
|
|
256
|
+
if (record.type === 'agent_request') {
|
|
257
|
+
if (record.key === undefined) continue
|
|
258
|
+
keysByIndex.set(`${pass}:${record.index}`, record.key)
|
|
259
|
+
seen.add(record.key)
|
|
260
|
+
if (results.delete(record.key)) demoted.add(record.key)
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
const key = record.key ?? keysByIndex.get(`${pass}:${record.index}`)
|
|
264
|
+
if (key === undefined) continue
|
|
265
|
+
seen.add(key)
|
|
266
|
+
if (record.state === 'done' && hasResult(record)) {
|
|
267
|
+
results.set(key, {
|
|
268
|
+
value: record.result,
|
|
269
|
+
pass,
|
|
270
|
+
index: record.index,
|
|
271
|
+
...(record.childSessionId !== undefined ? { childSessionId: record.childSessionId } : {}),
|
|
272
|
+
})
|
|
273
|
+
failed.delete(key)
|
|
274
|
+
demoted.delete(key)
|
|
275
|
+
} else if (record.state === 'done' && record.unsaved === true) {
|
|
276
|
+
results.delete(key)
|
|
277
|
+
demoted.add(key)
|
|
278
|
+
} else if (record.interrupted === true) {
|
|
279
|
+
// Stopped, not failed: the agent runs again and the cache stays open.
|
|
280
|
+
results.delete(key)
|
|
281
|
+
} else {
|
|
282
|
+
results.delete(key)
|
|
283
|
+
demoted.delete(key)
|
|
284
|
+
failed.add(key)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (checked.legacy) {
|
|
289
|
+
problems.push(
|
|
290
|
+
'This run was saved before resuming was possible, so its agents cannot be reused.',
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
if (checked.brokenAt !== undefined) {
|
|
294
|
+
problems.push(
|
|
295
|
+
`The journal was changed or cut short at line ${checked.brokenAt}. Results after that point are not trusted and those agents will run again.`,
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
const started = checked.records.some((record) => record.type === 'run_started')
|
|
299
|
+
if (!checked.legacy && !started) problems.push('The journal has no usable record of the run.')
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
pass: currentPass + 1,
|
|
303
|
+
results,
|
|
304
|
+
seen,
|
|
305
|
+
failed,
|
|
306
|
+
demoted,
|
|
307
|
+
...(scriptSha256 !== undefined ? { scriptSha256 } : {}),
|
|
308
|
+
previousFinish,
|
|
309
|
+
problems,
|
|
310
|
+
resumable: !checked.legacy && started,
|
|
311
|
+
...(checked.brokenAt !== undefined ? { brokenAt: checked.brokenAt } : {}),
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -9,6 +9,7 @@ import { preview, sha256 } from './journal.js'
|
|
|
9
9
|
import { splitWorkflowScript } from './meta.js'
|
|
10
10
|
import { type PhaseAgentView, PhaseRegistry, summarizeWorkflowPhases } from './phases.js'
|
|
11
11
|
import { buildPrelude, preludeLineCount } from './prelude.js'
|
|
12
|
+
import { workflowCallContent, workflowCallKey } from './resume.js'
|
|
12
13
|
import { parseJsonText, validateAgainstSchema } from './schema.js'
|
|
13
14
|
import type {
|
|
14
15
|
WorkflowAgentEvent,
|
|
@@ -83,7 +84,9 @@ export function resolveWorkflowLimits(limits: WorkflowLimits = {}): WorkflowReso
|
|
|
83
84
|
16 * 1024 * 1024,
|
|
84
85
|
),
|
|
85
86
|
...(limits.tokenBudget !== undefined
|
|
86
|
-
? {
|
|
87
|
+
? {
|
|
88
|
+
tokenBudget: integer('tokenBudget', limits.tokenBudget, 1, Number.MAX_SAFE_INTEGER),
|
|
89
|
+
}
|
|
87
90
|
: {}),
|
|
88
91
|
maxDurationMs: integer(
|
|
89
92
|
'maxDurationMs',
|
|
@@ -198,6 +201,14 @@ interface AgentRecord {
|
|
|
198
201
|
error?: string
|
|
199
202
|
skipped: boolean
|
|
200
203
|
userSkipped: boolean
|
|
204
|
+
key?: string
|
|
205
|
+
/** Set when the result came from an earlier pass instead of a run. */
|
|
206
|
+
cached?: {
|
|
207
|
+
readonly pass: number
|
|
208
|
+
readonly index: number
|
|
209
|
+
readonly childSessionId?: string
|
|
210
|
+
}
|
|
211
|
+
childSessionId?: string
|
|
201
212
|
}
|
|
202
213
|
|
|
203
214
|
interface Waiter {
|
|
@@ -283,6 +294,10 @@ export class WorkflowRuntime {
|
|
|
283
294
|
const runId =
|
|
284
295
|
options.runId ?? `w${[...randomBytes(8)].map((b) => (b % 36).toString(36)).join('')}`
|
|
285
296
|
const startedAt = now()
|
|
297
|
+
const pass = options.resume?.pass ?? 1
|
|
298
|
+
const resume = options.resume
|
|
299
|
+
let latch = false
|
|
300
|
+
const seenContent = new Map<string, number>()
|
|
286
301
|
const phases = new PhaseRegistry()
|
|
287
302
|
const agents = new Map<number, AgentRecord>()
|
|
288
303
|
const inFlight = new Set<Promise<unknown>>()
|
|
@@ -335,7 +350,13 @@ export class WorkflowRuntime {
|
|
|
335
350
|
declared,
|
|
336
351
|
at,
|
|
337
352
|
})
|
|
338
|
-
journal({
|
|
353
|
+
journal({
|
|
354
|
+
type: 'phase',
|
|
355
|
+
at,
|
|
356
|
+
index: phase.index,
|
|
357
|
+
title: phase.title,
|
|
358
|
+
declared,
|
|
359
|
+
})
|
|
339
360
|
}
|
|
340
361
|
return phase
|
|
341
362
|
}
|
|
@@ -361,6 +382,7 @@ export class WorkflowRuntime {
|
|
|
361
382
|
...(agent.resultPreview !== undefined ? { resultPreview: agent.resultPreview } : {}),
|
|
362
383
|
...(agent.error !== undefined ? { error: agent.error } : {}),
|
|
363
384
|
...(agent.skipped ? { skipped: true } : {}),
|
|
385
|
+
...(agent.cached !== undefined ? { cached: true } : {}),
|
|
364
386
|
})
|
|
365
387
|
|
|
366
388
|
// FIFO admission: an agent starts when fewer than maxConcurrency run.
|
|
@@ -411,7 +433,13 @@ export class WorkflowRuntime {
|
|
|
411
433
|
|
|
412
434
|
const settle = (
|
|
413
435
|
agent: AgentRecord,
|
|
414
|
-
outcome: {
|
|
436
|
+
outcome: {
|
|
437
|
+
state: 'done' | 'error'
|
|
438
|
+
skipped?: boolean
|
|
439
|
+
value?: unknown
|
|
440
|
+
error?: string
|
|
441
|
+
interrupted?: boolean
|
|
442
|
+
},
|
|
415
443
|
): HostCallResult => {
|
|
416
444
|
const at = now()
|
|
417
445
|
agent.state = outcome.state
|
|
@@ -434,11 +462,25 @@ export class WorkflowRuntime {
|
|
|
434
462
|
value = null
|
|
435
463
|
}
|
|
436
464
|
emit(agentEvent(agent))
|
|
465
|
+
const finalState = agent.state === 'error' ? 'error' : agent.skipped ? 'skipped' : 'done'
|
|
466
|
+
let saved: { result: unknown } | { unsaved: true } | undefined
|
|
467
|
+
if (finalState === 'done' && value !== null && agent.cached === undefined) {
|
|
468
|
+
try {
|
|
469
|
+
saved = { result: JSON.parse(JSON.stringify(value)) as unknown }
|
|
470
|
+
} catch {
|
|
471
|
+
saved = { unsaved: true }
|
|
472
|
+
journal({
|
|
473
|
+
type: 'log',
|
|
474
|
+
at,
|
|
475
|
+
message: "An agent's result could not be saved, so a resume will run it again.",
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
}
|
|
437
479
|
journal({
|
|
438
480
|
type: 'agent_outcome',
|
|
439
481
|
at,
|
|
440
482
|
index: agent.index,
|
|
441
|
-
state:
|
|
483
|
+
state: finalState,
|
|
442
484
|
tokens: agent.tokens,
|
|
443
485
|
toolCalls: agent.toolCalls,
|
|
444
486
|
queuedAt: agent.queuedAt,
|
|
@@ -446,6 +488,22 @@ export class WorkflowRuntime {
|
|
|
446
488
|
finishedAt: at,
|
|
447
489
|
...(agent.resultPreview !== undefined ? { resultPreview: agent.resultPreview } : {}),
|
|
448
490
|
...(agent.error !== undefined ? { error: agent.error } : {}),
|
|
491
|
+
...(agent.key !== undefined ? { key: agent.key } : {}),
|
|
492
|
+
pass,
|
|
493
|
+
...(agent.cached !== undefined
|
|
494
|
+
? {
|
|
495
|
+
cached: true as const,
|
|
496
|
+
resultFrom: {
|
|
497
|
+
pass: agent.cached.pass,
|
|
498
|
+
index: agent.cached.index,
|
|
499
|
+
},
|
|
500
|
+
}
|
|
501
|
+
: {}),
|
|
502
|
+
...(saved ?? {}),
|
|
503
|
+
...(outcome.interrupted === true && finalState === 'error'
|
|
504
|
+
? { interrupted: true as const }
|
|
505
|
+
: {}),
|
|
506
|
+
...(agent.childSessionId !== undefined ? { childSessionId: agent.childSessionId } : {}),
|
|
449
507
|
})
|
|
450
508
|
return { ok: true, value: { v: value, t: totalTokens() } }
|
|
451
509
|
}
|
|
@@ -457,6 +515,7 @@ export class WorkflowRuntime {
|
|
|
457
515
|
): HostCallResult => {
|
|
458
516
|
agent.tokens = count(outcome.tokens)
|
|
459
517
|
agent.toolCalls = count(outcome.toolCalls)
|
|
518
|
+
if (outcome.sessionId !== undefined) agent.childSessionId = outcome.sessionId
|
|
460
519
|
if (agent.userSkipped || outcome.state === 'skipped') {
|
|
461
520
|
return settle(agent, { state: 'done', skipped: true })
|
|
462
521
|
}
|
|
@@ -464,10 +523,14 @@ export class WorkflowRuntime {
|
|
|
464
523
|
return settle(agent, {
|
|
465
524
|
state: 'error',
|
|
466
525
|
error: 'The workflow was stopped before this agent finished.',
|
|
526
|
+
interrupted: true,
|
|
467
527
|
})
|
|
468
528
|
}
|
|
469
529
|
if (outcome.state === 'error') {
|
|
470
|
-
return settle(agent, {
|
|
530
|
+
return settle(agent, {
|
|
531
|
+
state: 'error',
|
|
532
|
+
error: outcome.error ?? 'The agent failed.',
|
|
533
|
+
})
|
|
471
534
|
}
|
|
472
535
|
if (call.schema === undefined) {
|
|
473
536
|
return settle(agent, { state: 'done', value: outcome.text ?? '' })
|
|
@@ -503,6 +566,18 @@ export class WorkflowRuntime {
|
|
|
503
566
|
return { ok: false, error: reason }
|
|
504
567
|
}
|
|
505
568
|
const index = ++requested
|
|
569
|
+
const contentId = workflowCallContent(call.prompt, call)
|
|
570
|
+
const ordinal = seenContent.get(contentId) ?? 0
|
|
571
|
+
seenContent.set(contentId, ordinal + 1)
|
|
572
|
+
const key = workflowCallKey(call.prompt, call, ordinal)
|
|
573
|
+
const hit = latch ? undefined : resume?.results.get(key)
|
|
574
|
+
if (
|
|
575
|
+
resume !== undefined &&
|
|
576
|
+
hit === undefined &&
|
|
577
|
+
!(resume.seen.has(key) && !resume.failed.has(key) && !resume.demoted.has(key))
|
|
578
|
+
) {
|
|
579
|
+
latch = true
|
|
580
|
+
}
|
|
506
581
|
const phaseTitle = call.phase ?? call.cursor
|
|
507
582
|
const phase = phaseTitle?.trim() ? announce(phaseTitle, false) : undefined
|
|
508
583
|
const queuedAt = now()
|
|
@@ -521,13 +596,26 @@ export class WorkflowRuntime {
|
|
|
521
596
|
toolCalls: 0,
|
|
522
597
|
skipped: false,
|
|
523
598
|
userSkipped: false,
|
|
599
|
+
key,
|
|
524
600
|
}
|
|
525
601
|
agents.set(index, agent)
|
|
602
|
+
if (hit !== undefined) {
|
|
603
|
+
agent.cached = {
|
|
604
|
+
pass: hit.pass,
|
|
605
|
+
index: hit.index,
|
|
606
|
+
...(hit.childSessionId !== undefined ? { childSessionId: hit.childSessionId } : {}),
|
|
607
|
+
}
|
|
608
|
+
if (hit.childSessionId !== undefined) agent.childSessionId = hit.childSessionId
|
|
609
|
+
agent.startedAt = queuedAt
|
|
610
|
+
}
|
|
526
611
|
emit(agentEvent(agent))
|
|
527
612
|
journal({
|
|
528
613
|
type: 'agent_request',
|
|
529
614
|
at: queuedAt,
|
|
530
615
|
index,
|
|
616
|
+
key,
|
|
617
|
+
pass,
|
|
618
|
+
...(hit !== undefined ? { cached: true as const } : {}),
|
|
531
619
|
label: agent.label,
|
|
532
620
|
...(phase ? { phaseIndex: phase.index, phaseTitle: phase.title } : {}),
|
|
533
621
|
promptPreview: preview(call.prompt),
|
|
@@ -540,6 +628,12 @@ export class WorkflowRuntime {
|
|
|
540
628
|
},
|
|
541
629
|
})
|
|
542
630
|
|
|
631
|
+
if (hit !== undefined) {
|
|
632
|
+
return settle(agent, {
|
|
633
|
+
state: 'done',
|
|
634
|
+
value: structuredClone(hit.value),
|
|
635
|
+
})
|
|
636
|
+
}
|
|
543
637
|
const admitted = await acquire(agent)
|
|
544
638
|
if (!admitted) {
|
|
545
639
|
return agent.userSkipped
|
|
@@ -547,6 +641,7 @@ export class WorkflowRuntime {
|
|
|
547
641
|
: settle(agent, {
|
|
548
642
|
state: 'error',
|
|
549
643
|
error: 'The workflow was stopped before this agent started.',
|
|
644
|
+
interrupted: true,
|
|
550
645
|
})
|
|
551
646
|
}
|
|
552
647
|
try {
|
|
@@ -554,6 +649,7 @@ export class WorkflowRuntime {
|
|
|
554
649
|
return settle(agent, {
|
|
555
650
|
state: 'error',
|
|
556
651
|
error: `Not started: the token budget for this workflow (${limits.tokenBudget} tokens) is spent.`,
|
|
652
|
+
interrupted: true,
|
|
557
653
|
})
|
|
558
654
|
}
|
|
559
655
|
if (controller.signal.aborted || agent.userSkipped) {
|
|
@@ -562,6 +658,7 @@ export class WorkflowRuntime {
|
|
|
562
658
|
: settle(agent, {
|
|
563
659
|
state: 'error',
|
|
564
660
|
error: 'The workflow was stopped before this agent started.',
|
|
661
|
+
interrupted: true,
|
|
565
662
|
})
|
|
566
663
|
}
|
|
567
664
|
agent.startedAt = now()
|
|
@@ -630,7 +727,7 @@ export class WorkflowRuntime {
|
|
|
630
727
|
// --- start ---
|
|
631
728
|
if (options.journal?.writeScript) {
|
|
632
729
|
try {
|
|
633
|
-
await options.journal.writeScript(options.script, sha256(options.script))
|
|
730
|
+
await options.journal.writeScript(options.script, sha256(options.script), pass)
|
|
634
731
|
} catch (error) {
|
|
635
732
|
journalError = error instanceof Error ? error.message : String(error)
|
|
636
733
|
}
|
|
@@ -644,6 +741,17 @@ export class WorkflowRuntime {
|
|
|
644
741
|
meta,
|
|
645
742
|
args,
|
|
646
743
|
limits,
|
|
744
|
+
...(resume !== undefined
|
|
745
|
+
? {
|
|
746
|
+
resume: {
|
|
747
|
+
pass,
|
|
748
|
+
previousFinish: resume.previousFinish,
|
|
749
|
+
scriptSha256Changed:
|
|
750
|
+
resume.scriptSha256 !== undefined && resume.scriptSha256 !== sha256(options.script),
|
|
751
|
+
},
|
|
752
|
+
}
|
|
753
|
+
: {}),
|
|
754
|
+
...(options.scriptPath !== undefined ? { scriptPath: options.scriptPath } : {}),
|
|
647
755
|
})
|
|
648
756
|
emit({
|
|
649
757
|
type: 'workflow_started',
|
|
@@ -665,7 +773,11 @@ export class WorkflowRuntime {
|
|
|
665
773
|
maxPendingHostCalls: limits.maxAgents + 100,
|
|
666
774
|
})
|
|
667
775
|
|
|
668
|
-
const preludeConfig = {
|
|
776
|
+
const preludeConfig = {
|
|
777
|
+
args,
|
|
778
|
+
tokenLimit: limits.tokenBudget ?? null,
|
|
779
|
+
deterministic: options.deterministic === true,
|
|
780
|
+
}
|
|
669
781
|
const runResult = await codeRuntime.run({
|
|
670
782
|
source: `${buildPrelude(preludeConfig)}\n${body}`,
|
|
671
783
|
allowedCalls: [WORKFLOW_HOST_CALL],
|
|
@@ -676,7 +788,10 @@ export class WorkflowRuntime {
|
|
|
676
788
|
onOutput,
|
|
677
789
|
onHostCall: (request) => {
|
|
678
790
|
if (request.name !== WORKFLOW_HOST_CALL) {
|
|
679
|
-
return Promise.resolve({
|
|
791
|
+
return Promise.resolve({
|
|
792
|
+
ok: false,
|
|
793
|
+
error: `Unknown host call "${request.name}".`,
|
|
794
|
+
})
|
|
680
795
|
}
|
|
681
796
|
let call: AgentCall
|
|
682
797
|
try {
|
|
@@ -738,7 +853,7 @@ export class WorkflowRuntime {
|
|
|
738
853
|
const progress =
|
|
739
854
|
begun.length === 0
|
|
740
855
|
? 'No agent had started.'
|
|
741
|
-
: `Before it failed: ${begun.length} agent${begun.length === 1 ? '' : 's'} started, ${finished} finished, ${failedAgents} failed. A rerun starts the whole script again, so finished agents run again
|
|
856
|
+
: `Before it failed: ${begun.length} agent${begun.length === 1 ? '' : 's'} started, ${finished} finished, ${failedAgents} failed. ${options.canResume === true ? 'Resume the run to continue: agents whose prompt and options are unchanged return their saved results.' : 'A rerun starts the whole script again, so finished agents run again.'}`
|
|
742
857
|
error = `${describeScriptFailure(outcome, body, preludeLineCount(preludeConfig))}\n${progress}${scriptPhase !== undefined ? ` Last phase: "${scriptPhase}".` : ''}`
|
|
743
858
|
}
|
|
744
859
|
|
|
@@ -751,6 +866,9 @@ export class WorkflowRuntime {
|
|
|
751
866
|
tokens: all.reduce((sum, a) => sum + a.tokens, 0),
|
|
752
867
|
toolCalls: all.reduce((sum, a) => sum + a.toolCalls, 0),
|
|
753
868
|
elapsedMs: Math.max(0, now() - startedAt),
|
|
869
|
+
...(all.some((a) => a.cached !== undefined)
|
|
870
|
+
? { cachedAgents: all.filter((a) => a.cached !== undefined).length }
|
|
871
|
+
: {}),
|
|
754
872
|
}
|
|
755
873
|
const at = now()
|
|
756
874
|
emit({
|