@sema-agent/core 7.16.0 → 7.17.1
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 +209 -0
- package/dist/agents/builtin-agents.d.ts +3 -3
- package/dist/agents/cross-session-envelope.d.ts +0 -1
- package/dist/agents/cross-session-envelope.js +0 -1
- package/dist/agents/peer-notices.d.ts +0 -9
- package/dist/agents/peer-notices.js +0 -63
- package/dist/agents/subagent.js +7 -15
- package/dist/brain/request-params.d.ts +2 -2
- package/dist/config/defaults.d.ts +0 -7
- package/dist/config/defaults.js +0 -2
- package/dist/core/artifact-host.d.ts +218 -0
- package/dist/core/artifact-host.js +92 -0
- package/dist/core/ask-origin.d.ts +29 -3
- package/dist/core/ask-origin.js +17 -9
- package/dist/core/ask-question.d.ts +25 -0
- package/dist/core/ask-question.js +4 -0
- package/dist/core/ask-unresolvable-notice.d.ts +30 -21
- package/dist/core/ask-unresolvable-notice.js +16 -8
- package/dist/core/auto-compaction.d.ts +10 -10
- package/dist/core/auto-mode-arming.d.ts +2 -2
- package/dist/core/background-agent-store.d.ts +5 -5
- package/dist/core/background-shell.d.ts +1 -1
- package/dist/core/checkpoint-execution-record.d.ts +0 -3
- package/dist/core/checkpoint-execution-record.js +0 -4
- package/dist/core/checkpoint-store.d.ts +71 -99
- package/dist/core/checkpoint-store.js +9 -16
- package/dist/core/context-edit.d.ts +36 -104
- package/dist/core/context-edit.js +37 -60
- package/dist/core/effective-path-target.d.ts +68 -8
- package/dist/core/effective-path-target.js +42 -30
- package/dist/core/engine-notice.d.ts +46 -32
- package/dist/core/env-budget.d.ts +31 -0
- package/dist/core/env-budget.js +39 -0
- package/dist/core/exec-output-tail.d.ts +5 -5
- package/dist/core/file-history-store.js +3 -6
- package/dist/core/fs-write-gate-policy.d.ts +1 -1
- package/dist/core/fs-write-gate-policy.js +3 -3
- package/dist/core/gate-lanes.js +9 -15
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hands-band.d.ts +1 -1
- package/dist/core/hooks.d.ts +18 -17
- package/dist/core/lsp-session.d.ts +1 -1
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/lsp.js +5 -4
- package/dist/core/mcp.d.ts +5 -5
- package/dist/core/mcp.js +4 -48
- package/dist/core/memory-engine/engine.js +2 -1
- package/dist/core/memory-engine/layout.d.ts +18 -6
- package/dist/core/memory-engine/layout.js +40 -21
- package/dist/core/memory-recall.d.ts +0 -8
- package/dist/core/memory.d.ts +1 -1
- package/dist/core/oracle-isolation.js +2 -2
- package/dist/core/parent-spec-seat.d.ts +66 -0
- package/dist/core/parent-spec-seat.js +47 -0
- package/dist/core/permission-rule-model.d.ts +11 -6
- package/dist/core/permission-rule-model.js +53 -37
- package/dist/core/permission-rules.js +8 -3
- package/dist/core/physical-path.d.ts +37 -0
- package/dist/core/physical-path.js +30 -0
- package/dist/core/protocol-naming.d.ts +2 -2
- package/dist/core/remote-env.d.ts +1 -1
- package/dist/core/retired-keys.d.ts +19 -0
- package/dist/core/retired-keys.js +15 -0
- package/dist/core/runner/active-skill-scope.js +3 -3
- package/dist/core/runner/assemble-result.d.ts +15 -13
- package/dist/core/runner/assemble-result.js +5 -4
- package/dist/core/runner/compaction-call-options.d.ts +25 -89
- package/dist/core/runner/contracts.d.ts +11 -10
- package/dist/core/runner/gate-exit.js +15 -1
- package/dist/core/runner/halt-attribution.d.ts +35 -0
- package/dist/core/runner/halt-attribution.js +16 -0
- package/dist/core/runner/memory-consolidation.d.ts +0 -2
- package/dist/core/runner/memory-consolidation.js +0 -1
- package/dist/core/runner/prepare-artifact.d.ts +52 -0
- package/dist/core/runner/prepare-artifact.js +63 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +2 -3
- package/dist/core/runner/prepare-config-doors.d.ts +3 -3
- package/dist/core/runner/prepare-config-doors.js +5 -2
- package/dist/core/runner/prepare-context-lane.d.ts +2 -2
- package/dist/core/runner/prepare-context-lane.js +2 -13
- package/dist/core/runner/prepare-file-history.d.ts +1 -1
- package/dist/core/runner/prepare-file-history.js +3 -52
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -7
- package/dist/core/runner/prepare-hands-readface.js +3 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +5 -2
- package/dist/core/runner/prepare-inherited-gate.js +7 -8
- package/dist/core/runner/prepare-memory.d.ts +4 -6
- package/dist/core/runner/prepare-memory.js +4 -2
- package/dist/core/runner/prepare-policy-chain.d.ts +3 -3
- package/dist/core/runner/prepare-policy-chain.js +15 -30
- package/dist/core/runner/prepare-prompt-assembly.js +1 -1
- package/dist/core/runner/prepare-question-face.js +2 -1
- package/dist/core/runner/prepare-safety-scan.d.ts +1 -1
- package/dist/core/runner/prepare-suspend-saga.js +0 -2
- package/dist/core/runner/prepare-task.js +12 -11
- package/dist/core/runner/prepare-wiring-manifest.js +9 -7
- package/dist/core/runner/resume-admission.js +2 -1
- package/dist/core/runner/resume-internals-and-config.js +4 -0
- package/dist/core/runner/retired-runner-deps.d.ts +3 -0
- package/dist/core/runner/retired-runner-deps.js +7 -0
- package/dist/core/runner/run-harness-handlers.js +5 -3
- package/dist/core/runner/run-settle-and-teardown.d.ts +1 -1
- package/dist/core/runner/run-settle-and-teardown.js +3 -1
- package/dist/core/runner/session-file-state-replay.js +3 -3
- package/dist/core/runner/session-rule-policy.js +3 -4
- package/dist/core/runner/stream-halt-verbs.js +1 -1
- package/dist/core/runner/stream-steer-verb.js +1 -4
- package/dist/core/runner/workspace-path.js +3 -0
- package/dist/core/runner-deps.d.ts +30 -49
- package/dist/core/safe-notify.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/sensitive-path-policy.js +11 -12
- package/dist/core/session.d.ts +2 -2
- package/dist/core/skill-tool-specifier.d.ts +1 -1
- package/dist/core/skills-directory.d.ts +1 -1
- package/dist/core/skills-directory.js +4 -3
- package/dist/core/spec-contract.js +5 -4
- package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
- package/dist/core/strategy-store.d.ts +8 -11
- package/dist/core/task-event.d.ts +20 -17
- package/dist/core/task-limits.d.ts +1 -1
- package/dist/core/task-notification.d.ts +5 -5
- package/dist/core/task-registry-agent.d.ts +5 -5
- package/dist/core/task-registry-monitor.d.ts +2 -2
- package/dist/core/task-registry-shared.d.ts +32 -28
- package/dist/core/task-registry-shared.js +4 -13
- package/dist/core/task-registry.d.ts +8 -8
- package/dist/core/task-result.d.ts +37 -37
- package/dist/core/task-spec.d.ts +5 -5
- package/dist/core/task-stream.d.ts +16 -11
- package/dist/core/tool-catalog-entries.d.ts +1 -0
- package/dist/core/tool-catalog-entries.js +6 -1
- package/dist/core/tool-conformance.d.ts +0 -2
- package/dist/core/tool-conformance.js +1 -3
- package/dist/core/tool-errors.d.ts +5 -0
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +66 -22
- package/dist/core/tool-policy.js +31 -5
- package/dist/core/tool-result-budget.d.ts +1 -1
- package/dist/core/tool-result-store.d.ts +11 -11
- package/dist/core/tool-spec.d.ts +34 -38
- package/dist/core/tools.d.ts +2 -2
- package/dist/core/trace.d.ts +12 -15
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/wiring-manifest.d.ts +4 -3
- package/dist/core/workflow-journal-store.d.ts +11 -11
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -2
- package/dist/engine/execution-env/node-execution-env.js +8 -5
- package/dist/engine/harness/agent-harness.d.ts +47 -4
- package/dist/engine/harness/agent-harness.js +52 -9
- package/dist/engine/loop/agent-loop.js +15 -8
- package/dist/engine/loop/types.d.ts +5 -0
- package/dist/index.d.ts +20 -14
- package/dist/index.js +16 -10
- package/dist/internal/harness.d.ts +1 -0
- package/dist/orchestration/run-spec.d.ts +3 -0
- package/dist/orchestration/run-spec.js +3 -3
- package/dist/orchestration/run-workflow-tool.d.ts +12 -6
- package/dist/orchestration/run-workflow-tool.js +23 -5
- package/dist/orchestration/workflow-primitives.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +1 -4
- package/dist/orchestration/workflow-script-store.js +9 -25
- package/dist/orchestration/workflow-types.d.ts +34 -4
- package/dist/orchestration/workflow-types.js +2 -0
- package/dist/orchestration/workflow.d.ts +39 -7
- package/dist/orchestration/workflow.js +237 -113
- package/dist/prompt-assembly/assemble.d.ts +2 -2
- package/dist/prompt-assembly/assemble.js +9 -9
- package/dist/prompt-assembly/composer.d.ts +6 -7
- package/dist/prompt-assembly/composer.js +10 -10
- package/dist/prompt-assembly/epoch.js +1 -1
- package/dist/prompt-assembly/event-registry.d.ts +0 -21
- package/dist/prompt-assembly/packs/sema-default.js +30 -30
- package/dist/prompt-assembly/types.d.ts +14 -14
- package/dist/prompts/coordinator.d.ts +0 -13
- package/dist/prompts/coordinator.js +0 -3
- package/dist/stores/cc/task-list-store.js +2 -10
- package/dist/stores/file/checkpoint-store.js +1 -5
- package/dist/stores/file/file-history-store.js +2 -2
- package/dist/stores/file/fs-atomic.d.ts +15 -18
- package/dist/stores/file/fs-atomic.js +4 -14
- package/dist/stores/file/mailbox-store.d.ts +7 -11
- package/dist/stores/file/mailbox-store.js +4 -11
- package/dist/stores/file/permission-rule-store.d.ts +4 -35
- package/dist/stores/file/permission-rule-store.js +4 -148
- package/dist/tools/artifact/artifact-text.d.ts +62 -0
- package/dist/tools/artifact/artifact-text.js +68 -0
- package/dist/tools/artifact/artifact-tool.d.ts +52 -0
- package/dist/tools/artifact/artifact-tool.js +351 -0
- package/dist/tools/artifact/local-stub.d.ts +26 -0
- package/dist/tools/artifact/local-stub.js +297 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +90 -8
- package/dist/tools/fs/bash-readonly-classifier.js +294 -70
- package/dist/tools/fs/fs-bash.d.ts +14 -10
- package/dist/tools/fs/fs-bash.js +50 -33
- package/dist/tools/fs/fs-search-tools.js +5 -2
- package/dist/tools/fs/fs-shared.d.ts +2 -0
- package/dist/tools/fs/fs-shared.js +3 -11
- package/dist/tools/fs/index.d.ts +4 -0
- package/dist/tools/fs/index.js +4 -1
- package/dist/tools/fs/safety.d.ts +117 -1
- package/dist/tools/fs/safety.js +131 -31
- package/dist/tools/fs/search.js +8 -6
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +119 -39
|
@@ -3,6 +3,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import { availableParallelism } from "node:os";
|
|
5
5
|
import { uuidv7 } from "../internal/harness.js";
|
|
6
|
+
import { applyParentSpecSeat } from "../core/parent-spec-seat.js";
|
|
6
7
|
import { TERMINAL_CAUSE_IS_REPLAYABLE, isTerminalCauseKind } from "../core/terminal-cause.js";
|
|
7
8
|
import { terminalProjection } from "../core/runner/terminal-projection.js";
|
|
8
9
|
import { builtinAgentDefinitions } from "../agents/builtin-agents.js";
|
|
@@ -219,14 +220,24 @@ export class WorkflowJournalIncompatibleError extends Error {
|
|
|
219
220
|
fromRunId;
|
|
220
221
|
ordinal;
|
|
221
222
|
code = "workflow.journal_incompatible";
|
|
222
|
-
constructor(fromRunId, ordinal
|
|
223
|
-
super(`startWorkflow: resume from "${fromRunId}" was REFUSED —
|
|
223
|
+
constructor(fromRunId, ordinal) {
|
|
224
|
+
super(`startWorkflow: resume from "${fromRunId}" was REFUSED — journal entry #${ordinal} carries no terminal cause (a journal written before TaskResult.terminal, or a malformed entry). ` +
|
|
224
225
|
"Completed work must not run again; start a fresh run instead of resuming this journal.");
|
|
225
226
|
this.fromRunId = fromRunId;
|
|
226
227
|
this.ordinal = ordinal;
|
|
227
228
|
this.name = "WorkflowJournalIncompatibleError";
|
|
228
229
|
}
|
|
229
230
|
}
|
|
231
|
+
class WorkflowParkRefusal extends Error {
|
|
232
|
+
code;
|
|
233
|
+
detail;
|
|
234
|
+
constructor(code, message, detail) {
|
|
235
|
+
super(message);
|
|
236
|
+
this.code = code;
|
|
237
|
+
this.detail = detail;
|
|
238
|
+
this.name = "WorkflowParkRefusal";
|
|
239
|
+
}
|
|
240
|
+
}
|
|
230
241
|
const workflowDepthStore = new AsyncLocalStorage();
|
|
231
242
|
function currentWorkflowDepth() {
|
|
232
243
|
return workflowDepthStore.getStore()?.depth ?? 0;
|
|
@@ -449,6 +460,9 @@ export const workflowResumeClaimFallback = {
|
|
|
449
460
|
table.delete(key);
|
|
450
461
|
},
|
|
451
462
|
};
|
|
463
|
+
function parkLaneArmed(opts) {
|
|
464
|
+
return opts.store !== undefined && opts.checkpointStore !== undefined;
|
|
465
|
+
}
|
|
452
466
|
export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
453
467
|
const alsDepth = currentWorkflowDepth();
|
|
454
468
|
const internalDepth = internals?.workflowDepth;
|
|
@@ -461,8 +475,11 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
461
475
|
if (opts.resumeFromRunId !== undefined && opts.journalStore === undefined) {
|
|
462
476
|
throw new Error("runWorkflow: resumeFromRunId requires a journalStore to load the prior run's journal");
|
|
463
477
|
}
|
|
464
|
-
if (opts.
|
|
465
|
-
throw new
|
|
478
|
+
if (opts.checkpointStore !== undefined && !parkLaneArmed(opts)) {
|
|
479
|
+
throw new WorkflowParkRefusal("workflow.park_requires_run_store", "runWorkflow: checkpointStore (the store this run's children park in) requires a WorkflowRunStore — a workflow child's park is recorded on its wa* row and proven against the checkpoint store at the next resume, and without a run store there is no durable row for a host to route the parked approval by", {});
|
|
480
|
+
}
|
|
481
|
+
if (opts.defaultDurableApproval !== undefined && !parkLaneArmed(opts)) {
|
|
482
|
+
throw new WorkflowParkRefusal("workflow.park_requires_run_store", "runWorkflow: defaultDurableApproval requires a WorkflowRunStore and a checkpointStore — a workflow child's park is recorded on its wa* row and read back from the checkpoint store at the next resume; without both there is no durable row for a host to route the parked approval by, or no way for a resume to prove the park", {});
|
|
466
483
|
}
|
|
467
484
|
if (opts.parkedResume !== undefined && opts.resumeFromRunId === undefined) {
|
|
468
485
|
throw new Error("runWorkflow: parkedResume drives a PARKED ordinal of a prior run and requires resumeFromRunId");
|
|
@@ -507,22 +524,23 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
507
524
|
let persistTail = Promise.resolve();
|
|
508
525
|
const persist = (mode) => {
|
|
509
526
|
if (!store)
|
|
510
|
-
return
|
|
527
|
+
return Promise.resolve({ ok: true });
|
|
511
528
|
const snapshot = structuredClone(run);
|
|
512
529
|
const next = persistTail.then(async () => {
|
|
513
530
|
try {
|
|
514
531
|
if (mode === "create") {
|
|
515
532
|
snapshot.rev = storeRev;
|
|
516
533
|
await store.put(runId, snapshot);
|
|
534
|
+
return { ok: true };
|
|
517
535
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
536
|
+
snapshot.rev = storeRev;
|
|
537
|
+
const won = await store.update(runId, scope, snapshot, { rev: storeRev });
|
|
538
|
+
if (won)
|
|
539
|
+
storeRev += 1;
|
|
540
|
+
return won ? { ok: true } : { ok: false, cause: "cas_lost" };
|
|
524
541
|
}
|
|
525
542
|
catch {
|
|
543
|
+
return { ok: false, cause: "threw" };
|
|
526
544
|
}
|
|
527
545
|
});
|
|
528
546
|
persistTail = next;
|
|
@@ -546,6 +564,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
546
564
|
startedAt: 0,
|
|
547
565
|
})),
|
|
548
566
|
agents: [],
|
|
567
|
+
...(opts.resumeFromRunId === undefined ? { parks: [] } : {}),
|
|
549
568
|
groups: [],
|
|
550
569
|
stats: { tokens: 0, turns: 0, costMicroUsd: 0, nested: { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0 } },
|
|
551
570
|
startedAt: t0,
|
|
@@ -642,6 +661,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
642
661
|
const sem = createSemaphore(concurrency);
|
|
643
662
|
const spawnAttribution = {
|
|
644
663
|
isDelegatedChild: true,
|
|
664
|
+
...(opts.questionFaceStripped === true ? { questionFaceStripped: true } : {}),
|
|
645
665
|
...(opts.interactionPosture !== undefined ? { parentInteractionPosture: opts.interactionPosture } : {}),
|
|
646
666
|
...(opts.parentToolCallId !== undefined ? { parentToolCallId: opts.parentToolCallId } : {}),
|
|
647
667
|
...(opts.parentTaskId !== undefined ? { parentTaskId: opts.parentTaskId } : {}),
|
|
@@ -819,25 +839,92 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
819
839
|
});
|
|
820
840
|
}
|
|
821
841
|
};
|
|
822
|
-
const
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
842
|
+
const parkSeat = opts.checkpointStore;
|
|
843
|
+
const parkedByOrdinal = [];
|
|
844
|
+
const errText = (err) => (err instanceof Error ? err.message : String(err));
|
|
845
|
+
const withoutToken = (text, token) => (token === "" ? text : text.split(token).join("<token>"));
|
|
846
|
+
const parkBindingBroken = (ordinal, why) => new WorkflowParkRefusal("workflow.park_binding_broken", `workflow resume: ordinal ${ordinal}'s park cannot be bound to its checkpoint (${why}) — the recorded park is inconsistent. Decide the checkpoint through the deployment's approval channel and start a fresh run.`, { ordinal });
|
|
847
|
+
const readParkTruth = async (seat, key, ordinal) => {
|
|
848
|
+
if (key.token === "" || key.sessionId === "")
|
|
849
|
+
throw parkBindingBroken(ordinal, "the record names no token or no session for it");
|
|
850
|
+
const read = await finalizeWithin(timers, new Promise((resolve) => resolve(seat.get(key.token))).then((cp) => ({ cp }), (err) => ({ err })), "timeout");
|
|
851
|
+
if (read === "timeout" || "err" in read) {
|
|
852
|
+
throw new WorkflowParkRefusal("workflow.park_truth_unreadable", `workflow resume: ordinal ${ordinal} is recorded as PARKED and its checkpoint could not be read from the checkpoint store (${read === "timeout" ? `the read did not settle within ${WORKFLOW_RESUME_CLAIM_FINALIZE_TIMEOUT_MS}ms` : withoutToken(errText(read.err), key.token)}); the resume is refused rather than run beside a pinned child.`, { reason: "checkpoint_store_threw" });
|
|
853
|
+
}
|
|
854
|
+
const cp = read.cp;
|
|
855
|
+
if (cp === null)
|
|
856
|
+
return { kind: "not_pending", key, status: "absent" };
|
|
857
|
+
if (cp.token !== key.token)
|
|
858
|
+
throw parkBindingBroken(ordinal, "the checkpoint store answered a row under another token");
|
|
859
|
+
if (cp.sessionId !== key.sessionId)
|
|
860
|
+
throw parkBindingBroken(ordinal, "the checkpoint belongs to another session than the parked row");
|
|
861
|
+
if (cp.status !== "pending")
|
|
862
|
+
return { kind: "not_pending", key, status: cp.status, ...(cp.checkpointId !== undefined ? { checkpointId: cp.checkpointId } : {}) };
|
|
863
|
+
return { kind: "pending", key, cp };
|
|
864
|
+
};
|
|
865
|
+
const parkMaterialOf = async (key, ordinal) => {
|
|
866
|
+
let entry;
|
|
867
|
+
if (key.originRunId === opts.resumeFromRunId)
|
|
868
|
+
entry = replayByOrdinal[ordinal];
|
|
869
|
+
else if (journalStore) {
|
|
870
|
+
const loaded = await finalizeWithin(timers, new Promise((resolve) => resolve(journalStore.load(key.originRunId, scope))).then((entries) => ({ entries }), (err) => ({ err })), "timeout");
|
|
871
|
+
if (loaded === "timeout" || "err" in loaded) {
|
|
872
|
+
emitRunLog(`resume: the journal of run "${clampRunIdText(key.originRunId)}" (the origin of the park at ordinal ${ordinal}) could not be read (${loaded === "timeout" ? `the load did not settle within ${WORKFLOW_RESUME_CLAIM_FINALIZE_TIMEOUT_MS}ms` : withoutToken(errText(loaded.err), key.token)}).`);
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
entry = loaded.entries.filter((e) => e.parked !== undefined && callKeyOrdinal(e.callKey) === ordinal).at(-1);
|
|
835
876
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
877
|
+
}
|
|
878
|
+
const p = entry?.parked;
|
|
879
|
+
if (p === undefined || entry === undefined || p.terminal.token !== key.token || p.sessionId !== key.sessionId || entry.callKey !== key.callKey)
|
|
880
|
+
return undefined;
|
|
881
|
+
return p;
|
|
882
|
+
};
|
|
883
|
+
const degradedParkMaterial = (cp) => ({
|
|
884
|
+
taskId: cp.sourceTaskId ?? "",
|
|
885
|
+
sessionId: cp.sessionId,
|
|
886
|
+
result: "",
|
|
887
|
+
stats: { turns: 0, tokens: 0, costMicroUsd: 0, usageMissing: true },
|
|
888
|
+
terminal: { kind: "paused", gate: cp.gate, token: cp.token, ...(cp.checkpointId !== undefined ? { checkpointId: cp.checkpointId } : {}) },
|
|
889
|
+
});
|
|
890
|
+
const disposeParkedOrdinal = (truth, ordinal, callKey) => {
|
|
891
|
+
if (truth.key.callKey !== callKey)
|
|
892
|
+
throw parkedCallChangedError(ordinal);
|
|
893
|
+
const entry = parkedResumeFor(truth.key.token);
|
|
894
|
+
if (truth.kind === "not_pending") {
|
|
895
|
+
if (entry === undefined) {
|
|
896
|
+
throw new WorkflowParkRefusal("workflow.park_not_pending", `workflow resume: ordinal ${ordinal} is recorded as PARKED, but the checkpoint store says its approval checkpoint is ${truth.status}${truth.checkpointId !== undefined ? ` (checkpoint ${truth.checkpointId})` : ""} — decided, expired or reaped outside this workflow. Hand the resume a parkedResume entry for this ordinal's token to settle the ordinal as failed and continue the script, or start a fresh run.`, { ...(truth.checkpointId !== undefined ? { checkpointId: truth.checkpointId } : {}), ordinal, status: truth.status === "absent" ? "absent" : truth.status === "expired" ? "expired" : "resolved" });
|
|
840
897
|
}
|
|
898
|
+
const result = {
|
|
899
|
+
taskId: callKey,
|
|
900
|
+
sessionId: truth.key.sessionId,
|
|
901
|
+
terminal: {
|
|
902
|
+
kind: "failed",
|
|
903
|
+
code: "workflow.park_not_pending",
|
|
904
|
+
message: `the approval checkpoint this call was parked on is ${truth.status}${truth.checkpointId !== undefined ? ` (checkpoint ${truth.checkpointId})` : ""} — decided, expired or reaped outside this workflow; settled as failed on the resume's acknowledgement. The call did not run.`,
|
|
905
|
+
},
|
|
906
|
+
result: "",
|
|
907
|
+
stats: { turns: 0, tokens: 0, costMicroUsd: 0, usageMissing: true },
|
|
908
|
+
};
|
|
909
|
+
return { kind: "settle", result };
|
|
910
|
+
}
|
|
911
|
+
const paused = truth.material ?? degradedParkMaterial(truth.cp);
|
|
912
|
+
if (truth.material === undefined)
|
|
913
|
+
emitRunLog(`resume: ordinal ${ordinal} re-parks from its checkpoint row alone — the origin run's journal entry for the park was not readable or did not match the recorded park (empty result, zero stats).`);
|
|
914
|
+
if (entry?.outcome !== undefined) {
|
|
915
|
+
return { kind: "drive", drive: { token: entry.token, outcome: entry.outcome, ...(entry.inheritedGate !== undefined ? { inheritedGate: entry.inheritedGate } : {}), sessionId: paused.sessionId || undefined, paused } };
|
|
916
|
+
}
|
|
917
|
+
return { kind: "repark", paused };
|
|
918
|
+
};
|
|
919
|
+
const assertChildParkSeat = (s) => {
|
|
920
|
+
const seat = s.checkpointStore;
|
|
921
|
+
if (seat === undefined || seat === "disabled")
|
|
922
|
+
return;
|
|
923
|
+
if (!parkLaneArmed(opts)) {
|
|
924
|
+
throw new WorkflowParkRefusal("workflow.park_requires_run_store", "workflow agent: the child's checkpointStore would let it park where this workflow cannot read the park back — a workflow whose children can park needs a WorkflowRunStore and that checkpoint store as its read seat (RunWorkflowOptions.store + checkpointStore); wire both, or leave the child's seat unset.", {});
|
|
925
|
+
}
|
|
926
|
+
if (seat !== parkSeat) {
|
|
927
|
+
throw Object.assign(new Error("agent.checkpointStore is not the store this run's children park in (the workflow's resolved checkpoint seat, RunWorkflowOptions.checkpointStore) — wire the one store, or leave the field unset and let the seat ride."), { code: "config.invalid_checkpoint_store" });
|
|
841
928
|
}
|
|
842
929
|
};
|
|
843
930
|
const driveNeverStarted = (r) => r.terminal.kind === "failed" && (r.terminal.code === "resume.reopened_unstarted" || r.terminal.code === "resume.reopen_failed_unstarted");
|
|
@@ -895,10 +982,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
895
982
|
if (dbg)
|
|
896
983
|
console.error(`[sema:wf-journal] runId=${runId} callKey=${callKey} TOMBSTONE-ERROR ${err instanceof Error ? err.message : String(err)}`);
|
|
897
984
|
}
|
|
898
|
-
writtenOrdinals.add(callKeyOrdinal(callKey));
|
|
899
985
|
return;
|
|
900
986
|
}
|
|
901
|
-
writtenOrdinals.add(callKeyOrdinal(callKey));
|
|
902
987
|
const p = journalStore.append(runId, scope, { callKey, result: stored });
|
|
903
988
|
journalTail = journalTail.then(() => p).catch(() => undefined);
|
|
904
989
|
try {
|
|
@@ -915,24 +1000,9 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
915
1000
|
const journalAppendParked = async (callKey, paused) => {
|
|
916
1001
|
if (!journalStore)
|
|
917
1002
|
return;
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
const p = journalStore.append(runId, scope, { callKey, parked: paused });
|
|
922
|
-
journalTail = journalTail.then(() => p).catch(() => undefined);
|
|
923
|
-
try {
|
|
924
|
-
await p;
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
catch (err) {
|
|
928
|
-
lastErr = err;
|
|
929
|
-
if (err?.code === JOURNAL_OVERSIZE_ERROR_CODE)
|
|
930
|
-
break;
|
|
931
|
-
if (attempt < 2)
|
|
932
|
-
await new Promise((r) => timers.setTimeout(r, 25 * (attempt + 1)));
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
throw lastErr;
|
|
1003
|
+
const p = journalStore.append(runId, scope, { callKey, parked: paused });
|
|
1004
|
+
journalTail = journalTail.then(() => p).catch(() => undefined);
|
|
1005
|
+
await p;
|
|
936
1006
|
};
|
|
937
1007
|
const parkAgentLeg = async (rec, paused, activityTail, chargeLive) => {
|
|
938
1008
|
const s = paused.stats;
|
|
@@ -951,19 +1021,50 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
951
1021
|
rec.activity = activityTail.slice();
|
|
952
1022
|
foldLegApprovalReport(rec, paused);
|
|
953
1023
|
emit({ type: "agent_end", runId, label: rec.label, phase: rec.phase, ...(rec.groupId !== undefined ? { groupId: rec.groupId } : {}), status: "parked", ...(paused.model !== undefined ? { modelResolved: paused.model } : {}), ts: rec.endedAt });
|
|
954
|
-
void persist("update");
|
|
955
1024
|
bceLive.delete(waIdOf(rec.callKey));
|
|
956
1025
|
if (parkedLeg === undefined)
|
|
957
1026
|
parkedLeg = { label: rec.label, callKey: rec.callKey, paused };
|
|
1027
|
+
const ordinal = callKeyOrdinal(rec.callKey);
|
|
1028
|
+
const parks = run.parks ?? (run.parks = []);
|
|
1029
|
+
const at = parks.findIndex((e) => e.callKey === rec.callKey);
|
|
1030
|
+
const key = { callKey: rec.callKey, sessionId: paused.sessionId, token: paused.terminal.token, originRunId: at === -1 ? runId : parks[at].originRunId };
|
|
1031
|
+
if (at === -1)
|
|
1032
|
+
parks.push(key);
|
|
1033
|
+
else
|
|
1034
|
+
parks[at] = key;
|
|
1035
|
+
void persist("update");
|
|
958
1036
|
try {
|
|
959
1037
|
await journalAppendParked(rec.callKey, paused);
|
|
1038
|
+
key.originRunId = runId;
|
|
960
1039
|
}
|
|
961
1040
|
catch (err) {
|
|
962
|
-
emitRunLog(`resume-journal: the PARKED leg at ordinal ${
|
|
963
|
-
"a resume of this run
|
|
1041
|
+
emitRunLog(`resume-journal: the PARKED leg at ordinal ${ordinal} ("${rec.label.slice(0, 80)}") could not be recorded in this run's journal (${withoutToken(errText(err), key.token)}) — ` +
|
|
1042
|
+
"the park stands: a resume of this run finds it through the run record and the checkpoint store.");
|
|
1043
|
+
}
|
|
1044
|
+
const persisted = await finalizeWithin(timers, persist("update"), { ok: false, cause: "timeout" });
|
|
1045
|
+
if (!persisted.ok) {
|
|
1046
|
+
emitRunLog(`resume-journal: the PARKED leg at ordinal ${ordinal} ("${rec.label.slice(0, 80)}") could not be persisted on the run record (${persisted.cause}) — ` +
|
|
1047
|
+
"the park stands: a resume of this run finds it through the journal and the checkpoint store.");
|
|
964
1048
|
}
|
|
965
1049
|
throw parkedError();
|
|
966
1050
|
};
|
|
1051
|
+
const recordParkDisposition = async (d, call, agentOpts) => {
|
|
1052
|
+
const { label, callKey, groupId, phase, phaseInstance, prompt } = call;
|
|
1053
|
+
const at = now();
|
|
1054
|
+
const model = d.kind === "repark" ? d.paused.model : undefined;
|
|
1055
|
+
const rec = { label, callKey, ...(groupId !== undefined ? { groupId } : {}), phase, prompt, ...(model !== undefined ? { model } : {}), ...(d.kind === "repark" ? { replayed: true } : {}), status: "running", queuedAt: at, startedAt: at };
|
|
1056
|
+
if (d.kind === "repark" && run.resume)
|
|
1057
|
+
run.resume.replayed += 1;
|
|
1058
|
+
run.agents.push(rec);
|
|
1059
|
+
if (phaseInstance)
|
|
1060
|
+
agentPhaseOf.set(rec, phaseInstance);
|
|
1061
|
+
emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(model !== undefined ? { model } : {}), ...(d.kind === "repark" ? { replayed: true } : {}), ts: at });
|
|
1062
|
+
bceSpawn(callKey, label, agentOpts.agentType, d.kind === "repark", (d.kind === "repark" ? d.paused.sessionId : d.result.sessionId) || undefined, model);
|
|
1063
|
+
if (d.kind === "repark")
|
|
1064
|
+
return await parkAgentLeg(rec, d.paused, [], false);
|
|
1065
|
+
await settleAgentResult(rec, d.result, [], agentOpts, "awaited");
|
|
1066
|
+
return d.result;
|
|
1067
|
+
};
|
|
967
1068
|
const createActivityCapture = (callKey, label, groupId, rec) => {
|
|
968
1069
|
const tail = [];
|
|
969
1070
|
let beatCount = 0;
|
|
@@ -1168,10 +1269,12 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1168
1269
|
}
|
|
1169
1270
|
}
|
|
1170
1271
|
const inheritedThinkingSnap = spec.thinking === undefined ? opts.defaultThinking?.() : undefined;
|
|
1272
|
+
const inheritedPrincipal = spec.principal === undefined ? opts.parentSpecSeat?.principal : undefined;
|
|
1171
1273
|
const specForIdentity = {
|
|
1172
1274
|
...spec,
|
|
1173
1275
|
...(inheritedModelSnap !== undefined ? { model: inheritedModelSnap } : {}),
|
|
1174
1276
|
...(inheritedThinkingSnap !== undefined ? { thinking: inheritedThinkingSnap } : {}),
|
|
1277
|
+
...(inheritedPrincipal !== undefined ? { principal: inheritedPrincipal } : {}),
|
|
1175
1278
|
};
|
|
1176
1279
|
const callKey = workflowAgentCallKey(run.agents.length, specForIdentity, agentOpts);
|
|
1177
1280
|
const prompt = boundedRedactedSummary(spec.systemPrompt ? `${spec.systemPrompt}\n\n${spec.objective}` : spec.objective, MAX_TRANSCRIPT_CHARS);
|
|
@@ -1327,27 +1430,12 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1327
1430
|
const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, inheritedThinkingSnap, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
|
|
1328
1431
|
let drive;
|
|
1329
1432
|
const cached = opts.resumeFromRunId !== undefined ? replayByOrdinal[run.agents.length] : undefined;
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
if (finalized)
|
|
1337
|
-
return cached.parked;
|
|
1338
|
-
const at = now();
|
|
1339
|
-
const reparkRec = { label, callKey, ...(groupId !== undefined ? { groupId } : {}), phase, prompt, ...(cached.parked.model !== undefined ? { model: cached.parked.model } : {}), replayed: true, status: "running", queuedAt: at, startedAt: at };
|
|
1340
|
-
if (run.resume)
|
|
1341
|
-
run.resume.replayed += 1;
|
|
1342
|
-
run.agents.push(reparkRec);
|
|
1343
|
-
if (phaseInstance)
|
|
1344
|
-
agentPhaseOf.set(reparkRec, phaseInstance);
|
|
1345
|
-
emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(cached.parked.model !== undefined ? { model: cached.parked.model } : {}), replayed: true, ts: at });
|
|
1346
|
-
bceSpawn(callKey, label, agentOpts.agentType, true, cached.parked.sessionId || undefined, cached.parked.model);
|
|
1347
|
-
return await parkAgentLeg(reparkRec, cached.parked, [], false);
|
|
1348
|
-
}
|
|
1349
|
-
drive = { token: decision.token, outcome: decision.outcome, ...(decision.inheritedGate !== undefined ? { inheritedGate: decision.inheritedGate } : {}), sessionId: cached.parked.sessionId || undefined, paused: cached.parked };
|
|
1350
|
-
}
|
|
1433
|
+
const truth = opts.resumeFromRunId !== undefined ? parkedByOrdinal[run.agents.length] : undefined;
|
|
1434
|
+
if (truth !== undefined) {
|
|
1435
|
+
const d = disposeParkedOrdinal(truth, run.agents.length, callKey);
|
|
1436
|
+
if (d.kind !== "drive")
|
|
1437
|
+
return await recordParkDisposition(d, { label, callKey, groupId, phase, phaseInstance, prompt }, agentOpts);
|
|
1438
|
+
drive = d.drive;
|
|
1351
1439
|
}
|
|
1352
1440
|
if (parkedLeg !== undefined && drive === undefined)
|
|
1353
1441
|
throw parkedError();
|
|
@@ -1442,12 +1530,13 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1442
1530
|
throw inheritedModelError;
|
|
1443
1531
|
const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
|
|
1444
1532
|
const typedSpec = withWorkflowChildSeats(typedSpec0, inheritedModelSnap, inheritedThinkingSnap, runnerRoleMap(runner));
|
|
1445
|
-
const
|
|
1446
|
-
const authInherit =
|
|
1447
|
-
|
|
1533
|
+
const seatedSpec = applyParentSpecSeat(withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema), opts.parentSpecSeat);
|
|
1534
|
+
const authInherit = seatedSpec.getApiKeyAndHeaders === undefined && opts.defaultGetApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.defaultGetApiKeyAndHeaders } : {};
|
|
1535
|
+
assertChildParkSeat(seatedSpec);
|
|
1536
|
+
const parkInherit = durableApprovalInherit(seatedSpec);
|
|
1448
1537
|
const preReviewSpec = agentOpts.schema
|
|
1449
|
-
? { ...
|
|
1450
|
-
: { ...
|
|
1538
|
+
? { ...seatedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
|
|
1539
|
+
: { ...seatedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal };
|
|
1451
1540
|
const runSpec = await reviewSpawnBeforeLaunch("ctx.agent", label, callKey, preReviewSpec, effectiveSignal);
|
|
1452
1541
|
const enrichedForward = opts.onForwardEvent !== undefined
|
|
1453
1542
|
? (e) => {
|
|
@@ -1645,7 +1734,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1645
1734
|
}
|
|
1646
1735
|
catch (err) {
|
|
1647
1736
|
if (drive !== undefined && !finalized && !(err instanceof WorkflowAgentParkedError)) {
|
|
1648
|
-
emitRunLog(`[park] the parked leg "${label}" could not be driven — the resume was refused before the checkpoint was consumed (${err
|
|
1737
|
+
emitRunLog(`[park] the parked leg "${label}" could not be driven — the resume was refused before the checkpoint was consumed (${withoutToken(errText(err), drive.token)}); the park stands.`);
|
|
1649
1738
|
return await parkAgentLeg(rec, drive.paused, activityTail, false);
|
|
1650
1739
|
}
|
|
1651
1740
|
if (!finalized) {
|
|
@@ -1723,24 +1812,14 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1723
1812
|
}
|
|
1724
1813
|
const { label, phase, phaseInstance, groupId, inheritedModelSnap, inheritedModelError, inheritedThinkingSnap, callKey, prompt, model } = prepareAgentCall(spec, agentOpts);
|
|
1725
1814
|
let drive;
|
|
1726
|
-
const
|
|
1727
|
-
if (
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
const at = now();
|
|
1733
|
-
const reparkRec = { label, callKey, ...(groupId !== undefined ? { groupId } : {}), phase, prompt, ...(parkedHere.parked.model !== undefined ? { model: parkedHere.parked.model } : {}), replayed: true, status: "running", queuedAt: at, startedAt: at };
|
|
1734
|
-
if (run.resume)
|
|
1735
|
-
run.resume.replayed += 1;
|
|
1736
|
-
run.agents.push(reparkRec);
|
|
1737
|
-
if (phaseInstance)
|
|
1738
|
-
agentPhaseOf.set(reparkRec, phaseInstance);
|
|
1739
|
-
emit({ type: "agent_start", runId, label, phase, ...(groupId !== undefined ? { groupId } : {}), callKey, prompt, ...(parkedHere.parked.model !== undefined ? { model: parkedHere.parked.model } : {}), replayed: true, ts: at });
|
|
1740
|
-
bceSpawn(callKey, label, agentOpts.agentType, true, parkedHere.parked.sessionId || undefined, parkedHere.parked.model);
|
|
1741
|
-
return await parkAgentLeg(reparkRec, parkedHere.parked, [], false);
|
|
1815
|
+
const truth = opts.resumeFromRunId !== undefined ? parkedByOrdinal[run.agents.length] : undefined;
|
|
1816
|
+
if (truth !== undefined) {
|
|
1817
|
+
const d = disposeParkedOrdinal(truth, run.agents.length, callKey);
|
|
1818
|
+
if (d.kind !== "drive") {
|
|
1819
|
+
const settled = await recordParkDisposition(d, { label, callKey, groupId, phase, phaseInstance, prompt }, agentOpts);
|
|
1820
|
+
return { runId, label, callKey, steer: async () => Promise.reject(new Error(`workflow agent "${label}" settled at its parked ordinal — nothing to steer`)), result: async () => settled };
|
|
1742
1821
|
}
|
|
1743
|
-
drive =
|
|
1822
|
+
drive = d.drive;
|
|
1744
1823
|
}
|
|
1745
1824
|
if (drive === undefined) {
|
|
1746
1825
|
noteDivergence(run.agents.length, "ctx.agentStream results are never replayed");
|
|
@@ -1817,12 +1896,13 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1817
1896
|
throw inheritedModelError;
|
|
1818
1897
|
const typedSpec0 = applyWorkflowAgentType(spec, agentOpts.agentType, agentRegistry);
|
|
1819
1898
|
const typedSpec = withWorkflowChildSeats(typedSpec0, inheritedModelSnap, inheritedThinkingSnap, runnerRoleMap(runner));
|
|
1820
|
-
const
|
|
1821
|
-
const authInherit =
|
|
1822
|
-
|
|
1899
|
+
const seatedSpec = applyParentSpecSeat(withWorkflowChildPersona(typedSpec, agentOpts.schema ?? typedSpec.outputSchema), opts.parentSpecSeat);
|
|
1900
|
+
const authInherit = seatedSpec.getApiKeyAndHeaders === undefined && opts.defaultGetApiKeyAndHeaders !== undefined ? { getApiKeyAndHeaders: opts.defaultGetApiKeyAndHeaders } : {};
|
|
1901
|
+
assertChildParkSeat(seatedSpec);
|
|
1902
|
+
const parkInherit = durableApprovalInherit(seatedSpec);
|
|
1823
1903
|
const baseRunSpec = agentOpts.schema
|
|
1824
|
-
? { ...
|
|
1825
|
-
: { ...
|
|
1904
|
+
? { ...seatedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal, outputSchema: agentOpts.schema }
|
|
1905
|
+
: { ...seatedSpec, ...authInherit, ...parkInherit, signal: effectiveSignal };
|
|
1826
1906
|
const preReviewSpec = childSessionId !== undefined ? { ...baseRunSpec, sessionId: childSessionId } : baseRunSpec;
|
|
1827
1907
|
const runSpec = await reviewSpawnBeforeLaunch("ctx.agentStream", label, callKey, preReviewSpec, effectiveSignal);
|
|
1828
1908
|
const enrichedForwardS = opts.onForwardEvent !== undefined
|
|
@@ -1864,7 +1944,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1864
1944
|
}
|
|
1865
1945
|
catch (err) {
|
|
1866
1946
|
if (drive !== undefined && !finalized) {
|
|
1867
|
-
emitRunLog(`[park] the parked leg "${label}" could not be driven — the resume was refused before the checkpoint was consumed (${err
|
|
1947
|
+
emitRunLog(`[park] the parked leg "${label}" could not be driven — the resume was refused before the checkpoint was consumed (${withoutToken(errText(err), drive.token)}); the park stands.`);
|
|
1868
1948
|
releaseLeg();
|
|
1869
1949
|
return await parkAgentLeg(rec, drive.paused, [], false);
|
|
1870
1950
|
}
|
|
@@ -2116,23 +2196,69 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
2116
2196
|
}
|
|
2117
2197
|
replayByOrdinal[callKeyOrdinal(e.callKey)] = e;
|
|
2118
2198
|
}
|
|
2199
|
+
const fromRunId = clampRunIdText(opts.resumeFromRunId);
|
|
2200
|
+
let prior = null;
|
|
2119
2201
|
if (opts.store !== undefined) {
|
|
2120
|
-
let prior;
|
|
2121
2202
|
try {
|
|
2122
2203
|
prior = await opts.store.get(opts.resumeFromRunId);
|
|
2123
2204
|
}
|
|
2124
2205
|
catch (err) {
|
|
2125
|
-
|
|
2206
|
+
const errCode = err instanceof Error ? err.code : undefined;
|
|
2207
|
+
const errClass = err instanceof Error ? `${err.name}${typeof errCode === "string" ? `/${errCode}` : ""}` : typeof err;
|
|
2208
|
+
throw new WorkflowParkRefusal("workflow.park_truth_unreadable", `startWorkflow: resume from "${fromRunId}" was REFUSED — the prior run's record could not be read from the run store (${errClass}), so its parks cannot be proven.`, { reason: "store_threw" });
|
|
2126
2209
|
}
|
|
2127
|
-
if (prior !== null && prior.scope
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2210
|
+
if (prior !== null && prior.scope !== scope)
|
|
2211
|
+
prior = null;
|
|
2212
|
+
}
|
|
2213
|
+
if (opts.store !== undefined && prior === null)
|
|
2214
|
+
throw new WorkflowParkRefusal("workflow.park_truth_unreadable", `startWorkflow: resume from "${fromRunId}" was REFUSED — the prior run's record is not in the run store under this scope, so its parks cannot be proven.`, { reason: "record_missing" });
|
|
2215
|
+
if (parkSeat !== undefined && prior !== null && prior.status === "running")
|
|
2216
|
+
throw new WorkflowParkRefusal("workflow.park_truth_unreadable", `startWorkflow: resume from "${fromRunId}" was REFUSED — the prior run is still recorded as running; a run that could park must have finished before it is resumed (a crashed park-capable run is decided through the deployment's approval channel, not resumed).`, { reason: "record_not_terminal" });
|
|
2217
|
+
if (prior !== null && !Array.isArray(prior.parks))
|
|
2218
|
+
throw new WorkflowParkRefusal("workflow.park_truth_unreadable", `startWorkflow: resume from "${fromRunId}" was REFUSED — the prior run's record carries no parks list (written by an older engine, or stripped by the store's projection), so its parks cannot be proven.`, { reason: "parks_unreadable" });
|
|
2219
|
+
const candidates = new Map();
|
|
2220
|
+
const recordParks = prior !== null && Array.isArray(prior.parks) ? prior.parks : [];
|
|
2221
|
+
for (const raw of recordParks) {
|
|
2222
|
+
const e = raw;
|
|
2223
|
+
if (e === null || typeof e !== "object" || typeof e.callKey !== "string" || typeof e.sessionId !== "string" || typeof e.token !== "string" || typeof e.originRunId !== "string" || !/^\d+:/.test(e.callKey)) {
|
|
2224
|
+
throw parkBindingBroken(-1, "a recorded park entry is malformed or its call key has no ordinal");
|
|
2225
|
+
}
|
|
2226
|
+
candidates.set(callKeyOrdinal(e.callKey), { callKey: e.callKey, sessionId: e.sessionId, token: e.token, originRunId: e.originRunId });
|
|
2227
|
+
}
|
|
2228
|
+
for (const [ord, e] of replayByOrdinal.entries()) {
|
|
2229
|
+
if (e?.parked === undefined)
|
|
2230
|
+
continue;
|
|
2231
|
+
const fromJournal = { callKey: e.callKey, sessionId: e.parked.sessionId, token: e.parked.terminal.token, originRunId: opts.resumeFromRunId };
|
|
2232
|
+
const recorded = candidates.get(ord);
|
|
2233
|
+
if (recorded === undefined)
|
|
2234
|
+
candidates.set(ord, fromJournal);
|
|
2235
|
+
else if (recorded.token !== fromJournal.token || recorded.sessionId !== fromJournal.sessionId || recorded.callKey !== fromJournal.callKey)
|
|
2236
|
+
throw parkBindingBroken(ord, "the run record and the journal name different tokens, sessions or calls for it");
|
|
2237
|
+
}
|
|
2238
|
+
const inherited = [];
|
|
2239
|
+
for (const [ord, key] of [...candidates.entries()].sort((a, b) => a[0] - b[0])) {
|
|
2240
|
+
if (replayByOrdinal[ord]?.result !== undefined)
|
|
2241
|
+
continue;
|
|
2242
|
+
inherited.push(key);
|
|
2243
|
+
}
|
|
2244
|
+
run.parks = inherited;
|
|
2245
|
+
if (parkSeat === undefined) {
|
|
2246
|
+
if (inherited.length > 0) {
|
|
2247
|
+
throw new WorkflowParkRefusal("workflow.park_truth_unreadable", `startWorkflow: resume from "${fromRunId}" was REFUSED — it is responsible for ${inherited.length} recorded park(s) and this workflow holds no checkpoint store to prove them against (RunWorkflowOptions.checkpointStore); without the seat a parked call would run live beside its pinned child.`, { reason: "capability_not_armed" });
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
else {
|
|
2251
|
+
for (const key of inherited) {
|
|
2252
|
+
const ord = callKeyOrdinal(key.callKey);
|
|
2253
|
+
let truth = await readParkTruth(parkSeat, key, ord);
|
|
2254
|
+
if (truth.kind === "not_pending" && parkedResumeFor(key.token) !== undefined)
|
|
2255
|
+
truth = await readParkTruth(parkSeat, key, ord);
|
|
2256
|
+
if (truth.kind === "pending") {
|
|
2257
|
+
const material = await parkMaterialOf(key, ord);
|
|
2258
|
+
if (material !== undefined)
|
|
2259
|
+
truth = { ...truth, material };
|
|
2135
2260
|
}
|
|
2261
|
+
parkedByOrdinal[ord] = truth;
|
|
2136
2262
|
}
|
|
2137
2263
|
}
|
|
2138
2264
|
run.resume = {
|
|
@@ -2179,7 +2305,6 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
2179
2305
|
await awaitLegsIdle(workflowSignal);
|
|
2180
2306
|
throw parkedError();
|
|
2181
2307
|
}
|
|
2182
|
-
await carryUnresolvedParks();
|
|
2183
2308
|
if (maxResultChars !== undefined) {
|
|
2184
2309
|
let size = 0;
|
|
2185
2310
|
try {
|
|
@@ -2214,7 +2339,6 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
2214
2339
|
catch (err) {
|
|
2215
2340
|
if (parkedLeg !== undefined)
|
|
2216
2341
|
await awaitLegsIdle(workflowSignal);
|
|
2217
|
-
await carryUnresolvedParks();
|
|
2218
2342
|
finalized = true;
|
|
2219
2343
|
const unsettledOnFailure = settleActiveUsageBeats();
|
|
2220
2344
|
stampBudgetOvershoot(unsettledOnFailure);
|
|
@@ -37,8 +37,8 @@ export interface AssembledPrompt {
|
|
|
37
37
|
renderWithDate?: (date: string) => string;
|
|
38
38
|
/** The initially-rendered system prompt. */
|
|
39
39
|
systemPrompt: string;
|
|
40
|
-
/**
|
|
41
|
-
|
|
40
|
+
/** The `prompt.assembled` block view (the id wordlist is a downstream contract). */
|
|
41
|
+
blocks: Array<{
|
|
42
42
|
id: string;
|
|
43
43
|
text: string;
|
|
44
44
|
}>;
|
|
@@ -23,7 +23,7 @@ function subsetPack(base, opts) {
|
|
|
23
23
|
...base,
|
|
24
24
|
sections: base.sections
|
|
25
25
|
.filter((s) => (opts.keep ? opts.keep.has(s.id) : true) && !(opts.exclude?.has(s.id) ?? false))
|
|
26
|
-
.map((s) => (s.id === "core/role.base" && opts.
|
|
26
|
+
.map((s) => (s.id === "core/role.base" && opts.roleBaseBlockId ? { ...s, blockId: opts.roleBaseBlockId } : s)),
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
function verifyDeclaredContentHash(d, onWarn) {
|
|
@@ -65,7 +65,7 @@ function mountDeclarations(basePack, decls, onWarn) {
|
|
|
65
65
|
cacheClass: "prefix-stable",
|
|
66
66
|
carrier: "system.block",
|
|
67
67
|
content: () => d.text,
|
|
68
|
-
|
|
68
|
+
blockId: d.id,
|
|
69
69
|
...(declaredContentHash !== undefined ? { declaredContentHash } : {}),
|
|
70
70
|
origin: "provider",
|
|
71
71
|
});
|
|
@@ -137,7 +137,7 @@ export function assemblePrompt(inputs) {
|
|
|
137
137
|
verifyDeclaredContentHash(assembledIdentity, onWarn);
|
|
138
138
|
onWarn?.(`prompt-provider stableBlocks: contentHash on "${assembledIdentity.id}" is ignored on the pass-through path (the text is emitted as core/role.base — a digest there would carry the wrong identity). Declare ONLY the role layer to keep the reconciliation anchor.`, "prompt-constitution");
|
|
139
139
|
}
|
|
140
|
-
const ptPack0 = subsetPack(SEMA_DEFAULT_PACK, { keep: OPAQUE_KEPT,
|
|
140
|
+
const ptPack0 = subsetPack(SEMA_DEFAULT_PACK, { keep: OPAQUE_KEPT, roleBaseBlockId: "provider.assembled" });
|
|
141
141
|
const ptPack = { ...ptPack0, sections: ptPack0.sections.map((s) => (s.id === "core/role.base" ? { ...s, origin: "provider" } : s)) };
|
|
142
142
|
let lastPT = compose(ptPack, renderInputsPT(inputs.date));
|
|
143
143
|
return {
|
|
@@ -150,8 +150,8 @@ export function assemblePrompt(inputs) {
|
|
|
150
150
|
get systemPrompt() {
|
|
151
151
|
return lastPT.text;
|
|
152
152
|
},
|
|
153
|
-
get
|
|
154
|
-
return lastPT.
|
|
153
|
+
get blocks() {
|
|
154
|
+
return lastPT.blocks;
|
|
155
155
|
},
|
|
156
156
|
get sections() {
|
|
157
157
|
return lastPT.sections;
|
|
@@ -180,13 +180,13 @@ export function assemblePrompt(inputs) {
|
|
|
180
180
|
constitution = "replaced";
|
|
181
181
|
roleBase = roleOut;
|
|
182
182
|
roleBaseOrigin = "provider";
|
|
183
|
-
pack = subsetPack(SEMA_DEFAULT_PACK, { exclude: REPLACE_ALL_EXCLUDED,
|
|
183
|
+
pack = subsetPack(SEMA_DEFAULT_PACK, { exclude: REPLACE_ALL_EXCLUDED, roleBaseBlockId: "provider.replace_all" });
|
|
184
184
|
}
|
|
185
185
|
else if (roleOut.includes(CYBER_RISK) && roleOut.includes(URL_SAFETY) && roleOut.includes(HARNESS_SECTION_ANCHOR)) {
|
|
186
186
|
constitution = "provider-assembled";
|
|
187
187
|
roleBase = roleOut;
|
|
188
188
|
roleBaseOrigin = "provider";
|
|
189
|
-
pack = subsetPack(SEMA_DEFAULT_PACK, { keep: OPAQUE_KEPT,
|
|
189
|
+
pack = subsetPack(SEMA_DEFAULT_PACK, { keep: OPAQUE_KEPT, roleBaseBlockId: "provider.assembled" });
|
|
190
190
|
onWarn?.("prompt-constitution: this PromptProvider returns an already-assembled prompt (constitution anchor found). Core now appends the constitution structurally — return ONLY the role base from stableSystem (or set replaceAll:true to own the whole base). Passed through un-doubled.", "prompt-constitution");
|
|
191
191
|
}
|
|
192
192
|
else {
|
|
@@ -227,8 +227,8 @@ export function assemblePrompt(inputs) {
|
|
|
227
227
|
get systemPrompt() {
|
|
228
228
|
return last.text;
|
|
229
229
|
},
|
|
230
|
-
get
|
|
231
|
-
return last.
|
|
230
|
+
get blocks() {
|
|
231
|
+
return last.blocks;
|
|
232
232
|
},
|
|
233
233
|
get sections() {
|
|
234
234
|
return last.sections;
|