@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
|
@@ -53,6 +53,8 @@ export interface WorkflowAgentOutcome {
|
|
|
53
53
|
readonly tokens: number
|
|
54
54
|
readonly toolCalls: number
|
|
55
55
|
readonly error?: string
|
|
56
|
+
/** The id of the child session the host ran this agent in, recorded in the journal. */
|
|
57
|
+
readonly sessionId?: string
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
/**
|
|
@@ -113,6 +115,8 @@ export interface WorkflowAgentEvent {
|
|
|
113
115
|
readonly resultPreview?: string
|
|
114
116
|
readonly error?: string
|
|
115
117
|
readonly skipped?: boolean
|
|
118
|
+
/** True when the agent did not run: a resumed run returned the result it saved earlier. */
|
|
119
|
+
readonly cached?: boolean
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
export interface WorkflowLogEvent {
|
|
@@ -133,6 +137,8 @@ export interface WorkflowUsage {
|
|
|
133
137
|
readonly tokens: number
|
|
134
138
|
readonly toolCalls: number
|
|
135
139
|
readonly elapsedMs: number
|
|
140
|
+
/** Agents that returned a result saved by an earlier pass instead of running (a subset of `agentsDone`). */
|
|
141
|
+
readonly cachedAgents?: number
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
export interface WorkflowFinishedEvent {
|
|
@@ -177,6 +183,14 @@ export type WorkflowJournalRecord =
|
|
|
177
183
|
readonly meta: WorkflowMeta
|
|
178
184
|
readonly args: unknown
|
|
179
185
|
readonly limits: WorkflowResolvedLimits
|
|
186
|
+
/** Present on every pass after the first; a resumed run keeps its run id and journal. */
|
|
187
|
+
readonly resume?: {
|
|
188
|
+
readonly pass: number
|
|
189
|
+
readonly previousFinish: WorkflowFinishStatus | 'unfinished'
|
|
190
|
+
readonly scriptSha256Changed: boolean
|
|
191
|
+
}
|
|
192
|
+
/** Where the script came from when the host read it from a file. */
|
|
193
|
+
readonly scriptPath?: string
|
|
180
194
|
}
|
|
181
195
|
| {
|
|
182
196
|
readonly type: 'phase'
|
|
@@ -200,6 +214,12 @@ export type WorkflowJournalRecord =
|
|
|
200
214
|
readonly effort?: string
|
|
201
215
|
readonly schema: boolean
|
|
202
216
|
}
|
|
217
|
+
/** Identifies the call across passes; see `workflowCallKey`. */
|
|
218
|
+
readonly key?: string
|
|
219
|
+
/** 1 for the first pass. A record from an older pass than the latest `run_started` is stale. */
|
|
220
|
+
readonly pass?: number
|
|
221
|
+
/** The agent did not run; its result came from an earlier pass. */
|
|
222
|
+
readonly cached?: true
|
|
203
223
|
}
|
|
204
224
|
| {
|
|
205
225
|
readonly type: 'agent_outcome'
|
|
@@ -213,6 +233,18 @@ export type WorkflowJournalRecord =
|
|
|
213
233
|
readonly finishedAt: number
|
|
214
234
|
readonly resultPreview?: string
|
|
215
235
|
readonly error?: string
|
|
236
|
+
readonly key?: string
|
|
237
|
+
readonly pass?: number
|
|
238
|
+
readonly cached?: true
|
|
239
|
+
/** The agent's full value (after schema validation); present only for a finished, non-null result. */
|
|
240
|
+
readonly result?: unknown
|
|
241
|
+
/** A value existed but could not be saved; a resume runs this agent again. */
|
|
242
|
+
readonly unsaved?: true
|
|
243
|
+
/** The run or the agent was stopped before the agent finished; a resume runs it again and keeps its saved results valid. */
|
|
244
|
+
readonly interrupted?: true
|
|
245
|
+
/** Where a cached result was first recorded. */
|
|
246
|
+
readonly resultFrom?: { readonly pass: number; readonly index: number }
|
|
247
|
+
readonly childSessionId?: string
|
|
216
248
|
}
|
|
217
249
|
| { readonly type: 'log'; readonly at: number; readonly message: string }
|
|
218
250
|
| {
|
|
@@ -222,12 +254,14 @@ export type WorkflowJournalRecord =
|
|
|
222
254
|
readonly result?: unknown
|
|
223
255
|
readonly error?: string
|
|
224
256
|
readonly usage: WorkflowUsage
|
|
257
|
+
/** Written by a later session for a run whose process ended without finishing it. */
|
|
258
|
+
readonly recovered?: true
|
|
225
259
|
}
|
|
226
260
|
|
|
227
261
|
/** Where a run's audit trail goes. Injected so the SDK stays host-agnostic. */
|
|
228
262
|
export interface WorkflowJournal {
|
|
229
263
|
/** Called once, before the first record, with the exact script text. */
|
|
230
|
-
writeScript?(source: string, sha256: string): Promise<void> | void
|
|
264
|
+
writeScript?(source: string, sha256: string, pass?: number): Promise<void> | void
|
|
231
265
|
append(record: WorkflowJournalRecord): Promise<void> | void
|
|
232
266
|
}
|
|
233
267
|
|
|
@@ -283,6 +317,80 @@ export interface WorkflowRunOptions {
|
|
|
283
317
|
readonly onControl?: (control: WorkflowControl) => void
|
|
284
318
|
/** Default: a fresh `w` + 8 base36 id. */
|
|
285
319
|
readonly runId?: string
|
|
320
|
+
/**
|
|
321
|
+
* Continue an earlier run: agents whose prompt and options are unchanged
|
|
322
|
+
* return the result saved in the journal instead of running. Build it with
|
|
323
|
+
* `loadWorkflowResume`, and pass the same `runId`.
|
|
324
|
+
*/
|
|
325
|
+
readonly resume?: WorkflowResumeState
|
|
326
|
+
/**
|
|
327
|
+
* Make the script repeatable, which resuming needs: `Date.now()`,
|
|
328
|
+
* `new Date()` with no argument and `Math.random()` throw, and `eval` is
|
|
329
|
+
* removed. Default false.
|
|
330
|
+
*/
|
|
331
|
+
readonly deterministic?: boolean
|
|
332
|
+
/** Say in a failure that the run can be resumed. Default false. */
|
|
333
|
+
readonly canResume?: boolean
|
|
334
|
+
/** Recorded in `run_started` when the host read the script from a file. */
|
|
335
|
+
readonly scriptPath?: string
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Everything a resumed pass needs from the earlier passes of one run. */
|
|
339
|
+
export interface WorkflowResumeState {
|
|
340
|
+
/** The number of the pass about to run (2 for the first resume). */
|
|
341
|
+
readonly pass: number
|
|
342
|
+
readonly results: ReadonlyMap<
|
|
343
|
+
string,
|
|
344
|
+
{
|
|
345
|
+
readonly value: unknown
|
|
346
|
+
readonly pass: number
|
|
347
|
+
readonly index: number
|
|
348
|
+
readonly childSessionId?: string
|
|
349
|
+
}
|
|
350
|
+
>
|
|
351
|
+
/** Keys the earlier passes asked for, finished or not. */
|
|
352
|
+
readonly seen: ReadonlySet<string>
|
|
353
|
+
/** Keys whose last outcome was a failure or a skip. */
|
|
354
|
+
readonly failed: ReadonlySet<string>
|
|
355
|
+
/** Keys that were asked for again after a result was saved and never finished since. */
|
|
356
|
+
readonly demoted: ReadonlySet<string>
|
|
357
|
+
readonly scriptSha256?: string
|
|
358
|
+
readonly previousFinish: WorkflowFinishStatus | 'unfinished'
|
|
359
|
+
/** Plain-language findings about the journal (changed, cut short, saved before resuming existed). */
|
|
360
|
+
readonly problems: readonly string[]
|
|
361
|
+
readonly resumable: boolean
|
|
362
|
+
/** First line (1-based) the integrity check could not trust, when any. */
|
|
363
|
+
readonly brokenAt?: number
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** A journal that can be read back, with its integrity checked. */
|
|
367
|
+
export interface WorkflowJournalReader {
|
|
368
|
+
read(): Promise<CheckedWorkflowJournal>
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** A journal record as written by `FileWorkflowJournal`: numbered and chained to the line before it. */
|
|
372
|
+
export type ChainedWorkflowJournalRecord = WorkflowJournalRecord & {
|
|
373
|
+
readonly seq: number
|
|
374
|
+
readonly prev: string
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** What reading a journal found. Only `records` can be trusted. */
|
|
378
|
+
export interface CheckedWorkflowJournal {
|
|
379
|
+
/** Records that passed the integrity check, in order. */
|
|
380
|
+
readonly records: readonly ChainedWorkflowJournalRecord[]
|
|
381
|
+
/** The journal was written before records were chained; it can be shown but not resumed. */
|
|
382
|
+
readonly legacy: boolean
|
|
383
|
+
/** The last write was cut off mid-line and was ignored. */
|
|
384
|
+
readonly tornTail: boolean
|
|
385
|
+
/** First line (1-based) that failed the check, when lines were changed, removed or cut short. */
|
|
386
|
+
readonly brokenAt?: number
|
|
387
|
+
/** Sequence number and hash the next record must chain from. */
|
|
388
|
+
readonly lastSeq: number
|
|
389
|
+
readonly lastHash: string
|
|
390
|
+
/** The file ends in a newline (false after a torn write). */
|
|
391
|
+
readonly endsWithNewline: boolean
|
|
392
|
+
/** Records of a legacy journal, for display only. */
|
|
393
|
+
readonly untrusted: readonly WorkflowJournalRecord[]
|
|
286
394
|
}
|
|
287
395
|
|
|
288
396
|
export interface WorkflowRunResult {
|
package/src/index.ts
CHANGED
|
@@ -41,10 +41,27 @@ export type {
|
|
|
41
41
|
MonitorStreamOptions,
|
|
42
42
|
MonitorStreamStats,
|
|
43
43
|
} from './runtime/jobs/monitor-stream.js'
|
|
44
|
+
export { WakeBudget, wakeBudgetFromEnv } from './runtime/jobs/wake-budget.js'
|
|
44
45
|
export {
|
|
45
46
|
createBackgroundJobId,
|
|
46
47
|
isBackgroundJobId,
|
|
47
48
|
} from './runtime/jobs/job-id.js'
|
|
49
|
+
// Three facts about a running item (where it is, when it last progressed,
|
|
50
|
+
// whether it is alive) and the one verdict derived from them.
|
|
51
|
+
export {
|
|
52
|
+
LIVENESS_QUIET_MS,
|
|
53
|
+
LIVENESS_STALLED_MS,
|
|
54
|
+
formatLivenessAge,
|
|
55
|
+
formatLivenessLine,
|
|
56
|
+
judgeLiveness,
|
|
57
|
+
} from './runtime/jobs/liveness.js'
|
|
58
|
+
export type {
|
|
59
|
+
Liveness,
|
|
60
|
+
LivenessInput,
|
|
61
|
+
LivenessKind,
|
|
62
|
+
LivenessThresholds,
|
|
63
|
+
LivenessVerdict,
|
|
64
|
+
} from './runtime/jobs/liveness.js'
|
|
48
65
|
export type {
|
|
49
66
|
BackgroundJob,
|
|
50
67
|
ForegroundCommand,
|
package/src/peers/record.ts
CHANGED
|
@@ -43,9 +43,27 @@ export const PeerRecordSchema = z
|
|
|
43
43
|
token: z.string().min(1),
|
|
44
44
|
protocol: z.literal(PEER_PROTOCOL_VERSION),
|
|
45
45
|
cliVersion: z.string(),
|
|
46
|
+
/** The slot a spaces host gave this terminal at launch; how the host finds its own sessions. */
|
|
47
|
+
hostSlot: z.string().min(1).max(128).optional(),
|
|
48
|
+
/** The conversation this terminal is on, once it has one. */
|
|
49
|
+
conversationId: z.string().min(1).optional(),
|
|
50
|
+
/** The label of the non-default account the conversation is bound to; absent for the default. */
|
|
51
|
+
account: z.string().min(1).optional(),
|
|
52
|
+
/** True while the bound account is out of allowance and the terminal is waiting for a choice. */
|
|
53
|
+
limited: z.boolean().optional(),
|
|
54
|
+
/** When the allowance is expected back (epoch ms); absent when the provider did not say. */
|
|
55
|
+
limitedUntil: z.number().int().positive().optional(),
|
|
46
56
|
})
|
|
47
57
|
.strict()
|
|
48
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The reader's view of a record: the same fields, but a key this build does
|
|
61
|
+
* not know is dropped instead of failing the whole record. A record written by
|
|
62
|
+
* a newer build (extra optional fields) stays visible to an older reader.
|
|
63
|
+
* Writers keep using the strict `PeerRecordSchema`.
|
|
64
|
+
*/
|
|
65
|
+
export const PeerRecordReaderSchema = PeerRecordSchema.strip()
|
|
66
|
+
|
|
49
67
|
export type PeerRecord = z.infer<typeof PeerRecordSchema>
|
|
50
68
|
|
|
51
69
|
const PEER_REF_HEX_LENGTH = 6
|
package/src/peers/registry.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import { basename, dirname, join } from 'node:path'
|
|
21
21
|
import { parsePeerAddress } from './address.js'
|
|
22
22
|
import { pingPeer } from './client.js'
|
|
23
|
-
import { type PeerRecord, PeerRecordSchema } from './record.js'
|
|
23
|
+
import { type PeerRecord, PeerRecordReaderSchema, PeerRecordSchema } from './record.js'
|
|
24
24
|
|
|
25
25
|
const RECORD_FILE_MODE = 0o600
|
|
26
26
|
|
|
@@ -75,7 +75,7 @@ function parseRecordText(text: string): PeerRecord | undefined {
|
|
|
75
75
|
} catch {
|
|
76
76
|
return undefined
|
|
77
77
|
}
|
|
78
|
-
const result =
|
|
78
|
+
const result = PeerRecordReaderSchema.safeParse(parsed)
|
|
79
79
|
return result.success ? result.data : undefined
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { PluginManifest } from '../types/plugin/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether a plugin can run on this machine and this runtime, decided from what
|
|
5
|
+
* its manifest declares. A manifest that declares nothing is compatible with
|
|
6
|
+
* everything, so every plugin written before these fields existed loads as it did.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type Triple = readonly [number, number, number]
|
|
10
|
+
|
|
11
|
+
interface Parsed {
|
|
12
|
+
readonly triple: Triple
|
|
13
|
+
/** The prerelease tag (`rc.1`), or undefined for a release. */
|
|
14
|
+
readonly pre: string | undefined
|
|
15
|
+
/** How many numeric parts were written: `1` is 1, `1.4` is 2, `1.4.2` is 3. */
|
|
16
|
+
readonly written: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const VERSION_PATTERN =
|
|
20
|
+
/^v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/i
|
|
21
|
+
|
|
22
|
+
function parseVersion(text: string): Parsed | undefined {
|
|
23
|
+
const match = VERSION_PATTERN.exec(text.trim())
|
|
24
|
+
if (!match) return undefined
|
|
25
|
+
const part = (value: string | undefined): number =>
|
|
26
|
+
value === undefined || value === 'x' || value === '*' ? 0 : Number(value)
|
|
27
|
+
const written = [match[1], match[2], match[3]].filter(
|
|
28
|
+
(p) => p !== undefined && p !== 'x' && p !== '*',
|
|
29
|
+
).length
|
|
30
|
+
return {
|
|
31
|
+
triple: [Number(match[1]), part(match[2]), part(match[3])],
|
|
32
|
+
pre: match[4],
|
|
33
|
+
written,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function compareTriples(a: Triple, b: Triple): number {
|
|
38
|
+
for (let i = 0; i < 3; i++) {
|
|
39
|
+
const diff = (a[i] as number) - (b[i] as number)
|
|
40
|
+
if (diff !== 0) return diff
|
|
41
|
+
}
|
|
42
|
+
return 0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Full precedence: a release is above any prerelease of the same numbers. */
|
|
46
|
+
function compare(a: Parsed, b: Parsed): number {
|
|
47
|
+
const order = compareTriples(a.triple, b.triple)
|
|
48
|
+
if (order !== 0) return order
|
|
49
|
+
if (a.pre === b.pre) return 0
|
|
50
|
+
if (a.pre === undefined) return 1
|
|
51
|
+
if (b.pre === undefined) return -1
|
|
52
|
+
return a.pre < b.pre ? -1 : 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The first version a `^` or `~` range excludes, from how many parts were
|
|
57
|
+
* written. `^1` and `~1` stop below 2.0.0; `~1.4` and `~1.4.2` below 1.5.0;
|
|
58
|
+
* `^0` below 1.0.0, `^0.2` below 0.3.0, `^0.0.3` below 0.0.4.
|
|
59
|
+
*/
|
|
60
|
+
function upperBound(operator: '^' | '~', target: Parsed): Triple {
|
|
61
|
+
const [major, minor, patch] = target.triple
|
|
62
|
+
if (operator === '~') return target.written <= 1 ? [major + 1, 0, 0] : [major, minor + 1, 0]
|
|
63
|
+
if (major > 0 || target.written <= 1) return [major + 1, 0, 0]
|
|
64
|
+
if (minor > 0 || target.written === 2) return [0, minor + 1, 0]
|
|
65
|
+
return [0, 0, patch + 1]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** One comparator such as `>=1.2.0`, `^2`, `~1.4` or `3.1.0`; undefined when unreadable. */
|
|
69
|
+
function satisfiesComparator(version: Parsed, token: string): boolean | undefined {
|
|
70
|
+
const op = /^(>=|<=|>|<|=|\^|~)?\s*(.+)$/.exec(token)
|
|
71
|
+
if (!op) return undefined
|
|
72
|
+
const operator = op[1] ?? '='
|
|
73
|
+
const target = parseVersion(op[2] as string)
|
|
74
|
+
if (!target) return undefined
|
|
75
|
+
const order = compare(version, target)
|
|
76
|
+
switch (operator) {
|
|
77
|
+
case '>=':
|
|
78
|
+
return order >= 0
|
|
79
|
+
case '<=':
|
|
80
|
+
return order <= 0
|
|
81
|
+
case '>':
|
|
82
|
+
return order > 0
|
|
83
|
+
case '<':
|
|
84
|
+
return order < 0
|
|
85
|
+
case '^':
|
|
86
|
+
case '~':
|
|
87
|
+
// The upper bound is compared on the numbers alone, so a prerelease of the
|
|
88
|
+
// next major (`2.0.0-rc.1`) is not inside `^1`.
|
|
89
|
+
return order >= 0 && compareTriples(version.triple, upperBound(operator, target)) < 0
|
|
90
|
+
default: {
|
|
91
|
+
// A bare `1` or `1.4` names a whole major or minor.
|
|
92
|
+
if (version.pre !== target.pre) return false
|
|
93
|
+
const written = (op[2] as string).replace(/^v/i, '').split(/[-+]/)[0] as string
|
|
94
|
+
const parts = written.split('.').filter((p) => p !== 'x' && p !== '*')
|
|
95
|
+
return parts.every((p, i) => Number(p) === version.triple[i])
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Whether `version` satisfies `range`. Supports space-separated comparators
|
|
102
|
+
* that must all hold, `||` between alternatives, and the operators `>=`, `>`,
|
|
103
|
+
* `<=`, `<`, `=`, `^` and `~`. Returns undefined when the range cannot be read.
|
|
104
|
+
*/
|
|
105
|
+
export function versionSatisfies(versionText: string, range: string): boolean | undefined {
|
|
106
|
+
const version = parseVersion(versionText)
|
|
107
|
+
if (!version) return undefined
|
|
108
|
+
let readable = true
|
|
109
|
+
let any = false
|
|
110
|
+
for (const alternative of range.split('||')) {
|
|
111
|
+
// Join an operator written apart from its number (">= 1.2") first.
|
|
112
|
+
const tokens = alternative
|
|
113
|
+
.trim()
|
|
114
|
+
.replace(/(>=|<=|>|<|=|\^|~)\s+/g, '$1')
|
|
115
|
+
.split(/\s+/)
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
if (tokens.length === 0) {
|
|
118
|
+
readable = false
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
let all = true
|
|
122
|
+
for (const token of tokens) {
|
|
123
|
+
const ok = satisfiesComparator(version, token)
|
|
124
|
+
if (ok === undefined) {
|
|
125
|
+
readable = false
|
|
126
|
+
all = false
|
|
127
|
+
break
|
|
128
|
+
}
|
|
129
|
+
if (!ok) all = false
|
|
130
|
+
}
|
|
131
|
+
if (all) any = true
|
|
132
|
+
}
|
|
133
|
+
if (any) return true
|
|
134
|
+
return readable ? false : undefined
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface PluginHostFacts {
|
|
138
|
+
/** The operating system, as `process.platform` names it. Default: this process. */
|
|
139
|
+
readonly platform?: string
|
|
140
|
+
/** The Namzu runtime version. Default: this package's own version. */
|
|
141
|
+
readonly namzuVersion?: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Refuse a plugin that declares it does not support this platform or this
|
|
146
|
+
* Namzu version. The reason names the plugin, what it declares and what this
|
|
147
|
+
* machine is, so a person can act on it without reading the manifest.
|
|
148
|
+
*
|
|
149
|
+
* A runtime whose own version is unknown (`0.0.0`, the placeholder a bundle
|
|
150
|
+
* reports when it cannot read its package file) is not held against the range:
|
|
151
|
+
* refusing every plugin on a guess would be worse than admitting one.
|
|
152
|
+
*/
|
|
153
|
+
export function assertCompatible(manifest: PluginManifest, host: Required<PluginHostFacts>): void {
|
|
154
|
+
const platforms = manifest.platforms
|
|
155
|
+
if (platforms && platforms.length > 0 && !platforms.includes(host.platform)) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Plugin "${manifest.name}" supports only ${platforms.join(', ')}, and this machine runs ${host.platform}, so it was not loaded.`,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
const range = manifest.engines?.namzu
|
|
161
|
+
if (range === undefined || host.namzuVersion === '0.0.0') return
|
|
162
|
+
const ok = versionSatisfies(host.namzuVersion, range)
|
|
163
|
+
if (ok === undefined) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Plugin "${manifest.name}" declares the Namzu version range "${range}", which cannot be read, so it was not loaded. Write it like ">=60.0.0" or "^60.1.0".`,
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
if (!ok) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Plugin "${manifest.name}" needs Namzu ${range}, and this is Namzu ${host.namzuVersion}, so it was not loaded. Update Namzu or use a plugin version made for this one.`,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
}
|
package/src/plugin/loader.ts
CHANGED
|
@@ -11,6 +11,8 @@ import { type PluginManifest, PluginManifestSchema } from '../types/plugin/index
|
|
|
11
11
|
import { toErrorMessage } from '../utils/error.js'
|
|
12
12
|
import { SCOPE_ATTRIBUTE } from '../utils/log/types.js'
|
|
13
13
|
import { type Logger, resolveLogger } from '../utils/logger.js'
|
|
14
|
+
import { VERSION } from '../version.js'
|
|
15
|
+
import { type PluginHostFacts, assertCompatible } from './compat.js'
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Scans a directory for subdirectories containing a plugin manifest.
|
|
@@ -161,7 +163,7 @@ const REGISTRY_BACKED_CONTRIBUTIONS = ['skills'] as const
|
|
|
161
163
|
* through host configuration; what does not exist is the manifest path
|
|
162
164
|
* into them. Naming the types is what makes that actionable.
|
|
163
165
|
*/
|
|
164
|
-
export interface PluginEnablementCapabilities {
|
|
166
|
+
export interface PluginEnablementCapabilities extends PluginHostFacts {
|
|
165
167
|
/**
|
|
166
168
|
* Whether the host wired a `SkillRegistry`.
|
|
167
169
|
*
|
|
@@ -178,6 +180,10 @@ export function assertEnableable(
|
|
|
178
180
|
manifest: PluginManifest,
|
|
179
181
|
capabilities: PluginEnablementCapabilities = {},
|
|
180
182
|
): void {
|
|
183
|
+
assertCompatible(manifest, {
|
|
184
|
+
platform: capabilities.platform ?? platform(),
|
|
185
|
+
namzuVersion: capabilities.namzuVersion ?? VERSION,
|
|
186
|
+
})
|
|
181
187
|
const record = manifest as unknown as Record<string, unknown[] | undefined>
|
|
182
188
|
// One ordered pass over all three rather than two group filters
|
|
183
189
|
// concatenated, so the message keeps naming them in the order the
|
package/src/pricing/index.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* than refreshed, and `rates.source.json` for the rates themselves.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { stripModelWindowSuffix } from '../compaction/context-window.js'
|
|
17
18
|
import type { ModelPricing } from '../utils/cost.js'
|
|
18
19
|
import { VENDOR_RATES } from './catalogue.generated.js'
|
|
19
20
|
|
|
@@ -35,7 +36,7 @@ const SNAPSHOT_SUFFIX = /[-@]\d{8}$/
|
|
|
35
36
|
|
|
36
37
|
/** Lowercase, snapshot suffix removed. Exported because tests assert it. */
|
|
37
38
|
export function normaliseModelId(model: string): string {
|
|
38
|
-
return model.toLowerCase().replace(SNAPSHOT_SUFFIX, '')
|
|
39
|
+
return stripModelWindowSuffix(model).toLowerCase().replace(SNAPSHOT_SUFFIX, '')
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/**
|
package/src/process/kill-tree.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { readFileSync, readdirSync } from 'node:fs'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Git Bash children that the Windows process tree cannot reach.
|
|
@@ -11,13 +12,82 @@ import { execFileSync } from 'node:child_process'
|
|
|
11
12
|
* in its environment, which every descendant inherits however it was started;
|
|
12
13
|
* the tag is how the tree is found again.
|
|
13
14
|
*/
|
|
14
|
-
const MSYS_SHELLS = new WeakMap<object, { readonly bash
|
|
15
|
+
const MSYS_SHELLS = new WeakMap<object, { readonly bash?: string; readonly tag: string }>()
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether a pid may be signalled, or addressed as a group with `-pid`.
|
|
19
|
+
*
|
|
20
|
+
* `kill(-1, sig)` signals every process the user may signal and `kill(0, sig)`
|
|
21
|
+
* signals the caller's own process group, so a recorded pid of 1, 0, a negative
|
|
22
|
+
* or a fractional number (a test double, a corrupt record, `NaN`) would take
|
|
23
|
+
* down the host's whole session. Only an integer above 1 that is not this
|
|
24
|
+
* process is a real, separate process. A refused pid is treated as already gone.
|
|
25
|
+
*/
|
|
26
|
+
export function isSignalablePid(pid: unknown): pid is number {
|
|
27
|
+
return typeof pid === 'number' && Number.isInteger(pid) && pid > 1 && pid !== process.pid
|
|
28
|
+
}
|
|
15
29
|
|
|
16
30
|
/** Remember how to find every process a Git Bash child started. Called by the spawn. */
|
|
17
31
|
export function registerMsysShell(child: object, shell: { bash: string; tag: string }): void {
|
|
18
32
|
MSYS_SHELLS.set(child, shell)
|
|
19
33
|
}
|
|
20
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Remember the tag a POSIX child's environment carries, so a kill can also find
|
|
37
|
+
* a descendant that left the child's process group (`setsid`, a double fork
|
|
38
|
+
* that starts its own session). Such a process has no group to signal, but it
|
|
39
|
+
* inherited the environment, and the tag is how it is found again.
|
|
40
|
+
*
|
|
41
|
+
* The limit is the same as the group's own: a process that clears its
|
|
42
|
+
* environment before it forks escapes this too.
|
|
43
|
+
*/
|
|
44
|
+
export function registerProcessTag(child: object, tag: string): void {
|
|
45
|
+
MSYS_SHELLS.set(child, { tag })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Signal every process of this user whose environment carries one of the tags,
|
|
50
|
+
* by reading `/proc/<pid>/environ`. Linux only: other POSIX systems have no
|
|
51
|
+
* `/proc`, and there the process group is all a kill can reach. A process whose
|
|
52
|
+
* environment cannot be read (another user's, or one that has just ended) is
|
|
53
|
+
* skipped without further work.
|
|
54
|
+
*
|
|
55
|
+
* Several tags are looked for in one pass, so stopping many jobs at once reads
|
|
56
|
+
* each environment once, not once per job. The scan is synchronous and its cost
|
|
57
|
+
* grows with the number of processes on the machine. There is also a window
|
|
58
|
+
* between reading an environment and signalling its pid in which the pid could
|
|
59
|
+
* be reused by an unrelated process of the same user; the window is the length
|
|
60
|
+
* of one read, and the signal goes only to a pid whose environment carried the
|
|
61
|
+
* tag a moment before.
|
|
62
|
+
*/
|
|
63
|
+
export function signalTagged(tags: string | readonly string[], signal: NodeJS.Signals): number {
|
|
64
|
+
const needles = (typeof tags === 'string' ? [tags] : tags).map(
|
|
65
|
+
(tag) => `${MSYS_TAG_VARIABLE}=${tag}\0`,
|
|
66
|
+
)
|
|
67
|
+
if (needles.length === 0) return 0
|
|
68
|
+
let entries: string[]
|
|
69
|
+
try {
|
|
70
|
+
entries = readdirSync('/proc')
|
|
71
|
+
} catch {
|
|
72
|
+
return 0
|
|
73
|
+
}
|
|
74
|
+
let signalled = 0
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
if (!/^\d+$/.test(entry)) continue
|
|
77
|
+
const pid = Number(entry)
|
|
78
|
+
if (!isSignalablePid(pid)) continue
|
|
79
|
+
try {
|
|
80
|
+
const environ = readFileSync(`/proc/${entry}/environ`, 'latin1')
|
|
81
|
+
if (!needles.some((needle) => environ.includes(needle))) continue
|
|
82
|
+
process.kill(pid, signal)
|
|
83
|
+
signalled++
|
|
84
|
+
} catch {
|
|
85
|
+
// Not ours to read, or gone since the listing.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return signalled
|
|
89
|
+
}
|
|
90
|
+
|
|
21
91
|
/** The environment variable that carries the tag. */
|
|
22
92
|
export const MSYS_TAG_VARIABLE = 'NAMZU_SHELL_TAG'
|
|
23
93
|
|
|
@@ -86,17 +156,49 @@ export function killTree(
|
|
|
86
156
|
child: { readonly pid?: number | undefined },
|
|
87
157
|
signal: NodeJS.Signals,
|
|
88
158
|
): void {
|
|
89
|
-
|
|
159
|
+
killTrees([child], signal)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* `killTree` for several children at once: each child's group is signalled, then
|
|
164
|
+
* the environment scan runs once for all their tags. Used where the number of
|
|
165
|
+
* kills is unbounded and cannot wait, such as the host's exit.
|
|
166
|
+
*/
|
|
167
|
+
export function killTrees(
|
|
168
|
+
children: readonly { readonly pid?: number | undefined }[],
|
|
169
|
+
signal: NodeJS.Signals,
|
|
170
|
+
): void {
|
|
171
|
+
const tags: string[] = []
|
|
172
|
+
for (const child of children) {
|
|
173
|
+
if (!isSignalablePid(child.pid)) continue
|
|
174
|
+
killOne(child, signal, tags)
|
|
175
|
+
}
|
|
176
|
+
if (tags.length > 0 && process.platform === 'linux') signalTagged(tags, signal)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function killOne(
|
|
180
|
+
child: { readonly pid?: number | undefined },
|
|
181
|
+
signal: NodeJS.Signals,
|
|
182
|
+
tags: string[],
|
|
183
|
+
): void {
|
|
184
|
+
if (!isSignalablePid(child.pid)) return
|
|
90
185
|
try {
|
|
91
186
|
if (process.platform === 'win32') {
|
|
92
187
|
const msys = MSYS_SHELLS.get(child)
|
|
93
|
-
if (msys !== undefined) killMsysTagged(msys)
|
|
188
|
+
if (msys?.bash !== undefined) killMsysTagged({ bash: msys.bash, tag: msys.tag })
|
|
94
189
|
execFileSync(taskkillPath(), ['/pid', String(child.pid), '/t', '/f'], {
|
|
95
190
|
stdio: 'ignore',
|
|
96
191
|
windowsHide: true,
|
|
97
192
|
})
|
|
98
193
|
} else {
|
|
99
|
-
|
|
194
|
+
try {
|
|
195
|
+
process.kill(-child.pid, signal)
|
|
196
|
+
} finally {
|
|
197
|
+
// After the group, which is cheap and exact: what is left is only
|
|
198
|
+
// what started its own session. Also when the group is already gone.
|
|
199
|
+
const tagged = MSYS_SHELLS.get(child)
|
|
200
|
+
if (tagged !== undefined) tags.push(tagged.tag)
|
|
201
|
+
}
|
|
100
202
|
}
|
|
101
203
|
} catch {
|
|
102
204
|
// Group (or tree) already gone
|
|
@@ -82,7 +82,15 @@ export const CODING_AGENT_DELEGATION_DOCTRINE = `### Planning and delegating
|
|
|
82
82
|
- When the \`Agent\` tool is available, delegate independent work through it and give each delegation a short, specific description; the user watches those descriptions, not the prompts. Brief each sub-agent with four things: the objective; what is out of bounds; which tools and sources to use; and the shape of the reply you want, such as a short summary with a file path for anything long. A fresh sub-agent starts with none of your context and returns what you ask for.
|
|
83
83
|
- When the \`Agent\` tool lists a \`fork\` type, use \`subagent_type: "fork"\` when a side task would fill your context with output you will not need again, such as an open-ended survey. A fork starts from this conversation under your own instructions, so its prompt is only the directive: what to do and where its scope ends, without the background. Never write or guess what a fork will find before its report arrives. A fork cannot start forks of its own.
|
|
84
84
|
- When delegated agents finish, tell the user what each one found in your next message. Their results reach you and not the user, so a result you do not pass on stays unseen.
|
|
85
|
-
- Delegate lookups — where something is defined, which files reference it, how a module works — with \`subagent_type: "explore"\`: it has reading and searching tools only and never interrupts the user for permission. Reserve the default sub-agent for work that changes files or runs commands
|
|
85
|
+
- Delegate lookups — where something is defined, which files reference it, how a module works — with \`subagent_type: "explore"\`: it has reading and searching tools only and never interrupts the user for permission. Reserve the default sub-agent for work that changes files or runs commands.
|
|
86
|
+
|
|
87
|
+
### Answering how work is going
|
|
88
|
+
- When asked how things stand while background work runs, read the note about running work on your request first if the host gives you one, and add one status read from the tool that started an item only when the note does not say enough. The answer needs no waiting or polling and leaves the work running, because stopping or restarting an item to answer would lose what it has done.
|
|
89
|
+
- Give each item the state you were given for it (progressing, waiting, stalled or dead) and how long ago it last moved, so the person can judge it for themselves. Stalled means only quiet for a while, not stuck.
|
|
90
|
+
- Group the answer as finished (with its result), running (and where it is), queued, and needing a decision. Lead with the outcome, keep it short, and write in the language the person used, states included.
|
|
91
|
+
- Name each item by what it is doing, not by its id: an id means nothing to the person, so give one only when they ask for it or need it for a command.
|
|
92
|
+
- A result exists only once its completion message has arrived, so for an unfinished item say it is still running; a predicted result reads as a fact and may be wrong.
|
|
93
|
+
- For an item reported dead, say it ended without a result and offer to run it again; starting the same work again unasked could repeat what it already did.`
|
|
86
94
|
|
|
87
95
|
/**
|
|
88
96
|
* For a session whose permission layer refuses every mutation. Says what that
|
package/src/provider/errors.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { LOG_SECRET_PATTERNS } from '../constants/secret-patterns.js'
|
|
|
33
33
|
import type { ProviderErrorKind, ProviderRequestErrorInit } from '../types/provider/error.js'
|
|
34
34
|
import {
|
|
35
35
|
type TransportCause,
|
|
36
|
+
isUsageLimitSignal,
|
|
36
37
|
readCertificateFailure,
|
|
37
38
|
readTransportCause,
|
|
38
39
|
} from '../types/provider/errors.js'
|
|
@@ -130,6 +131,31 @@ function providerCode(value: unknown): string | undefined {
|
|
|
130
131
|
return safeProviderCode(record.code ?? nested)
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
/**
|
|
135
|
+
* A vendor can name a spent allowance in `type` instead of `code`
|
|
136
|
+
* (`{"error":{"type":"usage_limit_reached"}}`, `{"error":{"type":"billing_error"}}`).
|
|
137
|
+
* Only those identifiers are lifted to a provider code; any other `type` is a
|
|
138
|
+
* category and stays where it was.
|
|
139
|
+
*/
|
|
140
|
+
function usageLimitTypeOf(error: unknown): string | undefined {
|
|
141
|
+
if (typeof error !== 'object' || error === null) return undefined
|
|
142
|
+
const outer = error as { type?: unknown; error?: unknown }
|
|
143
|
+
const inner =
|
|
144
|
+
typeof outer.error === 'object' && outer.error !== null
|
|
145
|
+
? (outer.error as { type?: unknown; error?: unknown })
|
|
146
|
+
: undefined
|
|
147
|
+
const innermost =
|
|
148
|
+
typeof inner?.error === 'object' && inner.error !== null
|
|
149
|
+
? (inner.error as { type?: unknown })
|
|
150
|
+
: undefined
|
|
151
|
+
for (const candidate of [outer.type, inner?.type, innermost?.type]) {
|
|
152
|
+
if (typeof candidate === 'string' && isUsageLimitSignal(candidate, undefined)) {
|
|
153
|
+
return safeProviderCode(candidate)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return undefined
|
|
157
|
+
}
|
|
158
|
+
|
|
133
159
|
/**
|
|
134
160
|
* Credential shapes to scrub before a provider's words are kept.
|
|
135
161
|
*
|
|
@@ -524,7 +550,7 @@ export function providerVendorError(input: {
|
|
|
524
550
|
}): ProviderRequestError {
|
|
525
551
|
const { error } = input
|
|
526
552
|
const status = vendorErrorStatus(error)
|
|
527
|
-
const exactProviderCode = providerCode(error)
|
|
553
|
+
const exactProviderCode = providerCode(error) ?? usageLimitTypeOf(error)
|
|
528
554
|
const name = error instanceof Error ? error.name : ''
|
|
529
555
|
const message = error instanceof Error ? error.message : ''
|
|
530
556
|
|