@namzu/sdk 59.0.0 → 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 +102 -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/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/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/public-runtime.d.ts +5 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +5 -4
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +1 -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 +4 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +90 -6
- 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/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/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 +264 -0
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +32 -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/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/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/model-window-suffix.ts +84 -0
- package/src/public-runtime.ts +21 -2
- package/src/public-types.ts +1 -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 +92 -10
- 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/plugin/index.ts +8 -0
- package/src/types/sandbox/index.ts +40 -1
- package/src/types/session/records.ts +36 -2
- package/src/types/tool/index.ts +30 -0
- package/src/types/tool/presentation.ts +8 -0
|
@@ -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({
|
|
@@ -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,
|
|
@@ -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
|
+
}
|