@sema-agent/core 7.9.2 → 7.10.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/agents/child-model-seat.d.ts +31 -1
  3. package/dist/agents/child-model-seat.js +12 -0
  4. package/dist/agents/subagent.d.ts +2 -2
  5. package/dist/agents/subagent.js +6 -4
  6. package/dist/core/ask-unresolvable-notice.d.ts +52 -0
  7. package/dist/core/ask-unresolvable-notice.js +25 -0
  8. package/dist/core/auto-mode.d.ts +62 -3
  9. package/dist/core/auto-mode.js +31 -0
  10. package/dist/core/checkpoint-store.d.ts +14 -0
  11. package/dist/core/checkpoint-store.js +2 -1
  12. package/dist/core/engine-notice.d.ts +28 -7
  13. package/dist/core/gate-lanes.js +15 -0
  14. package/dist/core/governance-codes.d.ts +1 -1
  15. package/dist/core/governance-codes.js +4 -0
  16. package/dist/core/hooks.d.ts +24 -1
  17. package/dist/core/hooks.js +2 -0
  18. package/dist/core/permission-rule-model.d.ts +51 -16
  19. package/dist/core/permission-rule-model.js +55 -21
  20. package/dist/core/permission-rules.d.ts +6 -4
  21. package/dist/core/permission-rules.js +14 -14
  22. package/dist/core/runner/contracts.d.ts +26 -2
  23. package/dist/core/runner/denial-limit-arms.d.ts +14 -3
  24. package/dist/core/runner/denial-limit-arms.js +15 -5
  25. package/dist/core/runner/permission-rule-lanes.d.ts +7 -1
  26. package/dist/core/runner/permission-rule-lanes.js +9 -3
  27. package/dist/core/runner/prepare-caps-and-workflow.d.ts +1 -1
  28. package/dist/core/runner/prepare-caps-and-workflow.js +13 -3
  29. package/dist/core/runner/prepare-gate-stations.d.ts +3 -2
  30. package/dist/core/runner/prepare-gate-stations.js +3 -0
  31. package/dist/core/runner/prepare-policy-chain.js +7 -6
  32. package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -1
  33. package/dist/core/runner/prepare-wiring-manifest.js +8 -1
  34. package/dist/core/runner/runtask.d.ts +33 -32
  35. package/dist/core/runner/runtask.js +63 -33
  36. package/dist/core/runner-deps.d.ts +9 -2
  37. package/dist/core/swappable-deps.d.ts +90 -0
  38. package/dist/core/swappable-deps.js +55 -0
  39. package/dist/core/tool-policy.d.ts +26 -0
  40. package/dist/core/tool-policy.js +5 -1
  41. package/dist/core/wiring-manifest.d.ts +15 -1
  42. package/dist/core/wiring-manifest.js +10 -2
  43. package/dist/core/workflow-journal-store.d.ts +21 -2
  44. package/dist/core/workflow-journal-store.js +1 -1
  45. package/dist/engine/execution-env/node-execution-env.d.ts +2 -0
  46. package/dist/engine/execution-env/node-execution-env.js +2 -1
  47. package/dist/engine/harness/types.d.ts +11 -0
  48. package/dist/index.d.ts +5 -3
  49. package/dist/index.js +5 -3
  50. package/dist/orchestration/run-workflow-tool.d.ts +17 -0
  51. package/dist/orchestration/run-workflow-tool.js +12 -0
  52. package/dist/orchestration/workflow-observe.d.ts +1 -1
  53. package/dist/orchestration/workflow-observe.js +2 -0
  54. package/dist/orchestration/workflow-types.d.ts +37 -2
  55. package/dist/orchestration/workflow-types.js +16 -0
  56. package/dist/orchestration/workflow.d.ts +41 -2
  57. package/dist/orchestration/workflow.js +295 -44
  58. package/dist/stores/file/workflow-journal-store.js +10 -3
  59. package/package.json +1 -1
  60. package/test/export-surface.snapshot.json +51 -5
@@ -244,10 +244,21 @@ export interface WiringManifest {
244
244
  * classifier decider was minted for this leg), and `reason` names the first arm that failed
245
245
  * otherwise — see {@link AUTO_MODE_ARM_REASONS}. A serving layer that used to infer the mode from
246
246
  * spec shape reads it here instead.
247
+ *
248
+ * `breaker` (#616) — the SESSION-level breaker read face: the most recent one-way breaker trip recorded
249
+ * for this session on this Runner (an EARLIER leg's decider — the latch is a per-run fact and a leg-start
250
+ * manifest's own decider is closed by construction, which is why there is no `open` boolean here: it
251
+ * would read `false` on every mint; the reason vocabulary reserves `latch_open` for a mid-leg re-read
252
+ * face nothing mints today). Present ⇔ a trip was recorded (never tripped, evicted or a standalone
253
+ * prepare ⇒ absent). `{ openedAtMs, lastCause, failures, runId }` — see
254
+ * {@link import("./auto-mode.js").AutoModeBreakerTrip}. A per-leg observation like `mcp`, so it is NOT
255
+ * part of {@link configFingerprint}: legs of one assembly must fingerprint alike whether or not a
256
+ * classifier was down.
247
257
  */
248
258
  autoMode?: {
249
259
  armed: boolean;
250
260
  reason: AutoModeArmReason;
261
+ breaker?: import("./auto-mode.js").AutoModeBreakerTrip;
251
262
  };
252
263
  /**
253
264
  * EFFECTIVE half only, and ALWAYS present on an engine-minted effective manifest (the `autoMode`
@@ -336,10 +347,13 @@ export interface WiringFacts {
336
347
  restore: string;
337
348
  };
338
349
  /** Effective half only — see {@link WiringManifest.autoMode}; the static half has no leg to arm.
339
- * `armed` must agree with `reason` (`armed ⇔ reason === "armed"`); a contradicting pair is refused. */
350
+ * `armed` must agree with `reason` (`armed ⇔ reason === "armed"`); a contradicting pair is refused.
351
+ * `breaker` (#616) is the session's most recent recorded trip, or absent; a trip whose cause is outside
352
+ * the closed set or whose numbers are not finite is refused. */
340
353
  autoMode?: {
341
354
  armed: boolean;
342
355
  reason: AutoModeArmReason;
356
+ breaker?: import("./auto-mode.js").AutoModeBreakerTrip;
343
357
  };
344
358
  /** Effective half only — see {@link WiringManifest.mcp}; the static half materializes nothing.
345
359
  * Copied entry-wise onto the manifest (the caller's array is never aliased). */
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { AUTO_MODE_BREAKER_CAUSES, isAutoModeBreakerCause } from "./auto-mode.js";
2
3
  import { canonicalize } from "./canonical-json.js";
3
4
  import { resolveCheckpointStore } from "./checkpoint-store.js";
4
5
  import { isLiveQuestionFace } from "./ask-question.js";
@@ -71,7 +72,13 @@ function readAutoModeFact(fact) {
71
72
  if (!AUTO_MODE_ARM_REASONS.includes(fact.reason) || fact.armed !== (fact.reason === "armed")) {
72
73
  throw new Error(`the auto-mode wiring fact is inconsistent (armed=${String(fact.armed)}, reason=${JSON.stringify(fact.reason)}) — armed must hold exactly when reason is "armed", and reason must be one of ${AUTO_MODE_ARM_REASONS.join("|")}`);
73
74
  }
74
- return { armed: fact.armed, reason: fact.reason };
75
+ const b = fact.breaker;
76
+ if (b === undefined)
77
+ return { armed: fact.armed, reason: fact.reason };
78
+ if (!isAutoModeBreakerCause(b.lastCause) || !Number.isFinite(b.openedAtMs) || !Number.isInteger(b.failures) || b.failures < 1 || typeof b.runId !== "string" || b.runId === "") {
79
+ throw new Error(`the auto-mode breaker fact is malformed (${JSON.stringify(b)}) — lastCause must be one of ${AUTO_MODE_BREAKER_CAUSES.join("|")}, openedAtMs finite, failures a positive integer, runId a non-empty string`);
80
+ }
81
+ return { armed: fact.armed, reason: fact.reason, breaker: { openedAtMs: b.openedAtMs, lastCause: b.lastCause, failures: b.failures, runId: b.runId } };
75
82
  }
76
83
  export function deriveWiringManifest(facts) {
77
84
  if (facts.half === "static" && facts.leg !== undefined) {
@@ -133,8 +140,9 @@ export function deriveWiringManifest(facts) {
133
140
  const { leg: _leg, mcp: _mcp, tools: _tools, ...assembly } = manifest;
134
141
  const { provenance: _askSeat, ...askForHash } = assembly.ask;
135
142
  const { provenance: _questionSeat, ...questionForHash } = assembly.question;
143
+ const { breaker: _breaker, ...autoModeForHash } = assembly.autoMode ?? {};
136
144
  manifest.configFingerprint = createHash("sha256")
137
- .update(canonicalize({ ...assembly, ask: askForHash, question: questionForHash }))
145
+ .update(canonicalize({ ...assembly, ask: askForHash, question: questionForHash, ...(assembly.autoMode !== undefined ? { autoMode: autoModeForHash } : {}) }))
138
146
  .digest("hex")
139
147
  .slice(0, 16);
140
148
  }
@@ -5,12 +5,31 @@ import type { TaskResult } from "./types.js";
5
5
  * longest unchanged PREFIX of these (keyed by the deterministic {@link workflowAgentCallKey}) and runs only the
6
6
  * first changed/new call + everything after it live. Same script + same args → 100% cache hit.
7
7
  */
8
- export interface WorkflowJournalEntry {
8
+ export type WorkflowJournalEntry = {
9
9
  /** The agent's deterministic call key (`ordinal:specIdentityHash`) — the replay match key. */
10
10
  callKey: string;
11
11
  /** The agent's TaskResult, replayed verbatim when a resume's call key matches at the same ordinal. */
12
12
  result: TaskResult;
13
- }
13
+ parked?: never;
14
+ } | {
15
+ callKey: string;
16
+ result?: never;
17
+ /**
18
+ * #642 — the ordinal's leg is PARKED at a durable approval gate: the child's own paused `TaskResult`
19
+ * (its `terminal` carries the checkpoint token + gate, its `sessionId` the pinned session). A resume
20
+ * reaching this ordinal with a matching call key does NOT run the call live (the parked child's
21
+ * session is pinned under a pending checkpoint; a fresh spawn would duplicate it) — it drives the
22
+ * parked child's resume when the caller hands it a decision for the token, else re-parks. Deliberately
23
+ * a DIFFERENT key from `result`: a journal reader from before this entry kind existed finds no
24
+ * `result.terminal` and refuses the whole resume (`WorkflowJournalIncompatibleError`) instead of
25
+ * admitting a paused cause it would re-run live — one-way, loud, like every journal schema step.
26
+ */
27
+ parked: TaskResult & {
28
+ terminal: Extract<TaskResult["terminal"], {
29
+ kind: "paused";
30
+ }>;
31
+ };
32
+ };
14
33
  /** REF-D5: the resume-claim key triple — ONE shape for the interface pair and every implementation
15
34
  * (it was re-inlined seven times; a key-field rename must red every leg at once). */
16
35
  export interface ResumeClaimArgs {
@@ -65,7 +65,7 @@ export class InMemoryWorkflowJournalStore {
65
65
  return [...rec.byOrdinal.entries()].sort((a, b) => a[0] - b[0]).map(([, e]) => snapshot(e));
66
66
  }
67
67
  async append(runId, scope, entry) {
68
- if (oversizeJournalResult(JSON.stringify(entry.result)))
68
+ if (oversizeJournalResult(JSON.stringify(entry.parked !== undefined ? entry.parked : entry.result)))
69
69
  return;
70
70
  let rec = this.runs.get(runId);
71
71
  if (!rec) {
@@ -79,6 +79,8 @@ export declare function openSpoolPair(base: string): {
79
79
  };
80
80
  export declare class NodeExecutionEnv implements ExecutionEnv, BackgroundShellCapability, SchedulerCapability {
81
81
  cwd: string;
82
+ /** #644 — this adapter runs on the engine host, so the host's home IS the environment's (`ExecutionEnv.homeDir`). */
83
+ readonly homeDir: string;
82
84
  private shellPath?;
83
85
  private shellEnv?;
84
86
  private inheritEnv;
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { closeSync, constants, createReadStream, mkdtempSync, openSync, readSync, statSync, truncateSync, unlinkSync } from "node:fs";
4
4
  import { access, appendFile, lstat, mkdir, mkdtemp, open, readdir, readFile, readlink, realpath, rename, rm, unlink, writeFile, } from "node:fs/promises";
5
- import { tmpdir } from "node:os";
5
+ import { homedir, tmpdir } from "node:os";
6
6
  import { isAbsolute, join, resolve } from "node:path";
7
7
  import { createInterface } from "node:readline";
8
8
  import { ExecutionError, err, FileError, ok, toError, } from "../harness/types.js";
@@ -284,6 +284,7 @@ export function openSpoolPair(base) {
284
284
  }
285
285
  export class NodeExecutionEnv {
286
286
  cwd;
287
+ homeDir = homedir();
287
288
  shellPath;
288
289
  shellEnv;
289
290
  inheritEnv;
@@ -498,6 +498,17 @@ export interface ExecutionEnv extends FileSystem, Shell {
498
498
  * SSH env to an organization-managed build host can be non-isolated yet omit this flag.
499
499
  */
500
500
  readonly externalContentTarget?: boolean;
501
+ /**
502
+ * #644 — declared by the ADAPTER: the HOME DIRECTORY of the user this environment runs as, as an absolute
503
+ * path in the environment's own namespace. The base a `~/`-relative permission rule (`Edit(~/.ssh/**)`)
504
+ * resolves against for calls executing in this env. Omitted ⇒ the environment declares no home, and every
505
+ * `~/` deny/ask rule reads UNREADABLE for its calls (a fail-closed ask a person clears), because the only
506
+ * other candidate — the ENGINE PROCESS's home — is the wrong directory on any leg whose environment is not
507
+ * the engine host (a remote executor, a sandbox), and guarding the wrong directory silently was the
508
+ * defect. A local Node environment declares `os.homedir()` (it IS the host). A declared value that is not
509
+ * an absolute path is refused at prepare (`config.execution_env_home_dir_invalid`), never read as absent.
510
+ */
511
+ readonly homeDir?: string;
501
512
  }
502
513
  /** Base fields shared by append-only session tree entries. */
503
514
  export interface SessionTreeEntryBase {
package/dist/index.d.ts CHANGED
@@ -54,6 +54,7 @@ export { pgQuery, mysqlQuery, sqliteQuery } from "./tools/sql-adapters.js";
54
54
  export { materializeMcpTools, MCP_PREFIX, type MaterializedMcp, type McpServerStatus, type McpRefreshResult } from "./core/mcp.js";
55
55
  export { MCP_FAILURE_KINDS, MCP_DELIVERY_VERDICTS, classifyMcpFailure, type McpFailure, type McpFailureKind, type McpDelivered } from "./core/mcp-failure.js";
56
56
  export { MCP_INJECTION_DROP_REASONS, MCP_INJECTION_DROP_TEXT, mcpInjectionDroppedNotice, type McpInjectionDropReason, type McpInjectionDropFacts } from "./core/mcp-injection-drop.js";
57
+ export { askUnresolvableNotice, type AskUnresolvableFacts } from "./core/ask-unresolvable-notice.js";
57
58
  export { materializeA2aTools, A2aRpcError, type MaterializedA2a, type A2aPeerStatus, type A2aRefreshResult, type A2aToolAxis } from "./core/a2a.js";
58
59
  export { PROTOCOL_TABLE, MCP_NAMESPACE, A2A_NAMESPACE, protocolOf, type ProtocolNamespace, type ProtocolId } from "./core/protocol-table.js";
59
60
  export { InMemorySessionPolicyStore, SessionPolicyError, loosenReasons, normalizeRules, stripRev, type SessionPolicyStore, type SessionPermissionRules, type StoredSessionRules, type SessionRulesRecord, type PutRulesOptions, } from "./core/session-policy-store.js";
@@ -166,7 +167,8 @@ export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, create
166
167
  export { type AskOrigin, ASK_ORIGINS, isAskOrigin, classifierMayAnswer, ORIGIN_IMPLIES_REAL_APPROVAL } from "./core/ask-origin.js";
167
168
  export { SETTLEMENT_KINDS, type SettlementKind, isSettlementKind, type Settlement, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, type DeniedBy, isDeniedBy, DENIED_BY_MAY_VETO, type GateDisposition, type GateOutcome, screenGateOutcome } from "./core/gate-outcome.js";
168
169
  export { type AskCarry } from "./core/hooks.js";
169
- export { parseAutoModeResponse, createAutoModeDecider, type AutoModeVerdict, type AutoModeDecider, type AutoModeDeciderOptions, type AutoModeClassified, type AutoModeClassifyFn, type AutoModeClassifyInput, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, type AutoModeDenialTracker, type AutoModeDenialLimitOptions, type DenialLimitCounts, type DenialLimitFallbackFace, type UnarmedDenialLimitFallback, type DenialLimitFallback, type DenialLimitVerdict, } from "./core/auto-mode.js";
170
+ export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps, type SwappableDepSeat, type SwappableDeps } from "./core/swappable-deps.js";
171
+ export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, AUTO_MODE_BREAKER_CAUSES, isAutoModeBreakerCause, AutoModeBreakerLedger, type AutoModeUnavailableCause, type AutoModeBreakerCause, type AutoModeBreakerTrip, type AutoModeVerdict, type AutoModeDecider, type AutoModeDeciderOptions, type AutoModeClassified, type AutoModeClassifyFn, type AutoModeClassifyInput, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, type AutoModeDenialTracker, type AutoModeDenialLimitOptions, type DenialLimitCounts, type DenialLimitFallbackFace, type UnarmedDenialLimitFallback, type DenialLimitFallback, type DenialLimitVerdict, } from "./core/auto-mode.js";
170
172
  export { AUTO_MODE_DENIAL_LIMIT_DEFAULTS, AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS, AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "./core/auto-mode-defaults.js";
171
173
  export { buildAutoModePrompt, renderAutoModeWindow, renderAutoModeAction, AUTO_MODE_DEFAULTS_SENTINEL, type AutoModeRules, type BuildAutoModePromptOptions, type AutoModeWindowOptions, } from "./core/auto-mode-prompt.js";
172
174
  export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/auto-mode-prompt-assets.js";
@@ -188,7 +190,7 @@ export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRul
188
190
  * and nothing more. Removal is exported without ceremony, because narrowing on a user's behalf is
189
191
  * allowed and widening is not.
190
192
  */
191
- export { RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, RULE_BEHAVIORS_BY_PRECEDENCE, isRuleBehavior, readRowBehavior, adjudicatePersistedRules, adjudicatePersistedPathRules, ruleReachesProgramRun, programRunReachOf, PROGRAM_RUN_REACHES, type ProgramRunReach, type ProgramRunReachOutcome, pathRuleReaches, ruleToolGrammarOf, COMMAND_RULE_TOOL, READ_RULE_TOOL, type PathRuleBases, type RuleBehavior, type PersistedRuleVerdict, parseRuleText, formatRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, stripFormatCharacters, UNCOVERED_SEGMENT_REASON_BASELINE, RULE_OFFERS_ABSENCE_BASELINE, type UncoveredSegmentDetail, type EditedRuleBreadthWarning, directoryRuleAdmits, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, type PersistedRule, type RuleTombstone, type RuleScope, type RuleDot, type RuleAdd, type RuleAddOrigin, type RuleOffer, type RuleOfferBatchMember, type SegmentRuleSuggestion, type SegmentCoverage, type RuleReject, type RuleRejectCode, type ParsedRule, type PersistedRuleTool, type PersistedRuleMatch, } from "./core/permission-rule-model.js";
193
+ export { RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, RULE_BEHAVIORS_BY_PRECEDENCE, isRuleBehavior, readRowBehavior, adjudicatePersistedRules, adjudicatePersistedPathRules, ruleReachesProgramRun, programRunReachOf, PROGRAM_RUN_REACHES, type ProgramRunReach, type ProgramRunReachOutcome, pathRuleReachOf, ruleBasesNeeded, PATH_RULE_BASES, PATH_RULE_BASE_LABEL, ruleToolGrammarOf, COMMAND_RULE_TOOL, READ_RULE_TOOL, type PathRuleBase, type PathRuleBases, type RuleBehavior, type PersistedRuleVerdict, parseRuleText, formatRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, stripFormatCharacters, UNCOVERED_SEGMENT_REASON_BASELINE, RULE_OFFERS_ABSENCE_BASELINE, type UncoveredSegmentDetail, type EditedRuleBreadthWarning, directoryRuleAdmits, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, type PersistedRule, type RuleTombstone, type RuleScope, type RuleDot, type RuleAdd, type RuleAddOrigin, type RuleOffer, type RuleOfferBatchMember, type SegmentRuleSuggestion, type SegmentCoverage, type RuleReject, type RuleRejectCode, type ParsedRule, type PersistedRuleTool, type PersistedRuleMatch, } from "./core/permission-rule-model.js";
192
194
  export { removePersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, type DurableRulePartition, type DurableRulePartitionProvider, type RuleWriteOutcome, type StoredRules, type RemoveResult, type PutResult, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, type RuleSyncState, type RuleSyncFrontier, type RuleSyncDrop, type RuleSyncLandingReport, type RuleOwner, type QuarantinedRuleAdd, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, type PermissionRuleWriter, type WritableDurableRulePartition, type RuleWriteDelta, type RuleAddDelta, type RuleDeleteDelta, type RuleSyncJoinDelta, type RawRuleSyncState, type RedemptionAuthorization, } from "./core/permission-rule-store.js";
193
195
  export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, type PermissionRuleSyncTransport, type PermissionRuleSyncResult, type RuleSyncRequestBody, type RuleSyncResponseBody, } from "./core/permission-rule-sync.js";
194
196
  export { createPermissionRuleStoreProvider, effectivePermissionRules, effectiveOrThrow, ruleSourceOf, type PermissionRuleStore, type PermissionRuleStoreProvider, type PermissionRuleStoreConfig, type EffectivePermissionRules, type EffectivePermissionRule, type RemovedPermissionRule, type RuleSource, } from "./core/permission-rule-provider.js";
@@ -228,7 +230,7 @@ export { type ReasoningIntensity, type ReasoningResolution, type ResolvedReasoni
228
230
  export { DESIGN_REVIEW_PROMPTS, CODE_REVIEW_PROMPT, SCENARIO_REGISTRY, runScenario, type ScenarioId, type CodeReviewMode, type ScenarioProfile, type RunScenarioOptions, type RunScenarioResult, } from "./scenarios/scenario-registry.js";
229
231
  export { teacherMode, TEACHER_PROFILE, type TeacherModePair, type TeacherProfile, } from "./scenarios/teacher-quickstart.js";
230
232
  export { loadOrchestrationEnv, DEFAULT_REASONING_INTENSITY, type OrchestrationMode, type OrchestrationEnv, } from "./scenarios/env.js";
231
- export { runWorkflow, startWorkflow, workflowAgentCallKey, WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowAgentBlockedError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE, type WorkflowFanOutSlotError, type WorkflowFanOutOptions, WORKFLOW_SUBAGENT_PROMPT, WORKFLOW_SUBAGENT_PROMPT_SCHEMA, WORKFLOW_SUBAGENT_APPEND, WORKFLOW_SUBAGENT_APPEND_SCHEMA, type WorkflowHandle, MAX_WORKFLOW_ITEMS, type WorkflowRun, type WorkflowRunStatus, type WorkflowItemStatus, type WorkflowPhase, type WorkflowGroup, type WorkflowAgentRun, type WorkflowAgentHandle, type WorkflowRunStats, type WorkflowEvent, type WorkflowBudget, type WorkflowAgentOptions, type WorkflowRunContext, type WorkflowInternals, type RunWorkflowOptions, type RunWorkflowResult, type WorkflowTimers, } from "./orchestration/workflow.js";
233
+ export { runWorkflow, startWorkflow, workflowAgentCallKey, WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowAgentBlockedError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE, WorkflowAgentParkedError, WORKFLOW_AGENT_PARKED_ERROR_CODE, type WorkflowParkedResume, type WorkflowFanOutSlotError, type WorkflowFanOutOptions, WORKFLOW_SUBAGENT_PROMPT, WORKFLOW_SUBAGENT_PROMPT_SCHEMA, WORKFLOW_SUBAGENT_APPEND, WORKFLOW_SUBAGENT_APPEND_SCHEMA, type WorkflowHandle, MAX_WORKFLOW_ITEMS, type WorkflowRun, type WorkflowRunStatus, type WorkflowItemStatus, type WorkflowPhase, type WorkflowGroup, type WorkflowAgentRun, type WorkflowAgentHandle, type WorkflowRunStats, type WorkflowEvent, type WorkflowBudget, type WorkflowAgentOptions, type WorkflowRunContext, type WorkflowInternals, type RunWorkflowOptions, type RunWorkflowResult, type WorkflowTimers, } from "./orchestration/workflow.js";
232
234
  export { listWorkflowRuns, getWorkflowRun, subscribeWorkflow, deriveAgentDisplayStatus, type AgentDisplayStatus } from "./orchestration/workflow-observe.js";
233
235
  export { runGoal, DECLARE_DONE_TOOL_NAME, type GoalSpec, type GoalResult, type GoalStatus, type GoalBudgetCause, type GoalVerdict, type GoalTurnState, type GoalVerificationKind, } from "./orchestration/goal.js";
234
236
  export { emitTaskOutcome, type TaskOutcome } from "./core/task-outcome.js";
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ export { pgQuery, mysqlQuery, sqliteQuery } from "./tools/sql-adapters.js";
41
41
  export { materializeMcpTools, MCP_PREFIX } from "./core/mcp.js";
42
42
  export { MCP_FAILURE_KINDS, MCP_DELIVERY_VERDICTS, classifyMcpFailure } from "./core/mcp-failure.js";
43
43
  export { MCP_INJECTION_DROP_REASONS, MCP_INJECTION_DROP_TEXT, mcpInjectionDroppedNotice } from "./core/mcp-injection-drop.js";
44
+ export { askUnresolvableNotice } from "./core/ask-unresolvable-notice.js";
44
45
  export { materializeA2aTools, A2aRpcError } from "./core/a2a.js";
45
46
  export { PROTOCOL_TABLE, MCP_NAMESPACE, A2A_NAMESPACE, protocolOf } from "./core/protocol-table.js";
46
47
  export { InMemorySessionPolicyStore, SessionPolicyError, loosenReasons, normalizeRules, stripRev, } from "./core/session-policy-store.js";
@@ -140,14 +141,15 @@ export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, create
140
141
  export { ASK_ORIGINS, isAskOrigin, classifierMayAnswer, ORIGIN_IMPLIES_REAL_APPROVAL } from "./core/ask-origin.js";
141
142
  export { SETTLEMENT_KINDS, isSettlementKind, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, isDeniedBy, DENIED_BY_MAY_VETO, screenGateOutcome } from "./core/gate-outcome.js";
142
143
  export {} from "./core/hooks.js";
143
- export { parseAutoModeResponse, createAutoModeDecider, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, } from "./core/auto-mode.js";
144
+ export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps } from "./core/swappable-deps.js";
145
+ export { parseAutoModeResponse, createAutoModeDecider, AUTO_MODE_UNAVAILABLE_CAUSES, isAutoModeUnavailableCause, AUTO_MODE_BREAKER_CAUSES, isAutoModeBreakerCause, AutoModeBreakerLedger, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow, } from "./core/auto-mode.js";
144
146
  export { AUTO_MODE_DENIAL_LIMIT_DEFAULTS, AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS, AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "./core/auto-mode-defaults.js";
145
147
  export { buildAutoModePrompt, renderAutoModeWindow, renderAutoModeAction, AUTO_MODE_DEFAULTS_SENTINEL, } from "./core/auto-mode-prompt.js";
146
148
  export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/auto-mode-prompt-assets.js";
147
149
  export { AUTO_MODE_ARMING_RECIPE_VERSION, autoModeArmingRecipeOf, sanitizeAutoModeArmingRecipe, foldAutoModeArming, } from "./core/auto-mode-arming.js";
148
150
  export { rebuildAutoModeDecider, } from "./core/auto-mode-rebuild.js";
149
151
  export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, isNamespacedCoveringRuleName, namespacedRuleNameCovers, } from "./core/permission-rules.js";
150
- export { RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, RULE_BEHAVIORS_BY_PRECEDENCE, isRuleBehavior, readRowBehavior, adjudicatePersistedRules, adjudicatePersistedPathRules, ruleReachesProgramRun, programRunReachOf, PROGRAM_RUN_REACHES, pathRuleReaches, ruleToolGrammarOf, COMMAND_RULE_TOOL, READ_RULE_TOOL, parseRuleText, formatRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, stripFormatCharacters, UNCOVERED_SEGMENT_REASON_BASELINE, RULE_OFFERS_ABSENCE_BASELINE, directoryRuleAdmits, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, } from "./core/permission-rule-model.js";
152
+ export { RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, RULE_BEHAVIORS_BY_PRECEDENCE, isRuleBehavior, readRowBehavior, adjudicatePersistedRules, adjudicatePersistedPathRules, ruleReachesProgramRun, programRunReachOf, PROGRAM_RUN_REACHES, pathRuleReachOf, ruleBasesNeeded, PATH_RULE_BASES, PATH_RULE_BASE_LABEL, ruleToolGrammarOf, COMMAND_RULE_TOOL, READ_RULE_TOOL, parseRuleText, formatRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, stripFormatCharacters, UNCOVERED_SEGMENT_REASON_BASELINE, RULE_OFFERS_ABSENCE_BASELINE, directoryRuleAdmits, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, } from "./core/permission-rule-model.js";
151
153
  export { removePersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, } from "./core/permission-rule-store.js";
152
154
  export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, } from "./core/permission-rule-sync.js";
153
155
  export { createPermissionRuleStoreProvider, effectivePermissionRules, effectiveOrThrow, ruleSourceOf, } from "./core/permission-rule-provider.js";
@@ -186,7 +188,7 @@ export { DEFAULT_EFFORT_LEVELS, REASONING_BUDGET_SHARE, isThinkingLevel, rankOf,
186
188
  export { DESIGN_REVIEW_PROMPTS, CODE_REVIEW_PROMPT, SCENARIO_REGISTRY, runScenario, } from "./scenarios/scenario-registry.js";
187
189
  export { teacherMode, TEACHER_PROFILE, } from "./scenarios/teacher-quickstart.js";
188
190
  export { loadOrchestrationEnv, DEFAULT_REASONING_INTENSITY, } from "./scenarios/env.js";
189
- export { runWorkflow, startWorkflow, workflowAgentCallKey, WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowAgentBlockedError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE, WORKFLOW_SUBAGENT_PROMPT, WORKFLOW_SUBAGENT_PROMPT_SCHEMA, WORKFLOW_SUBAGENT_APPEND, WORKFLOW_SUBAGENT_APPEND_SCHEMA, MAX_WORKFLOW_ITEMS, } from "./orchestration/workflow.js";
191
+ export { runWorkflow, startWorkflow, workflowAgentCallKey, WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowAgentBlockedError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE, WorkflowAgentParkedError, WORKFLOW_AGENT_PARKED_ERROR_CODE, WORKFLOW_SUBAGENT_PROMPT, WORKFLOW_SUBAGENT_PROMPT_SCHEMA, WORKFLOW_SUBAGENT_APPEND, WORKFLOW_SUBAGENT_APPEND_SCHEMA, MAX_WORKFLOW_ITEMS, } from "./orchestration/workflow.js";
190
192
  export { listWorkflowRuns, getWorkflowRun, subscribeWorkflow, deriveAgentDisplayStatus } from "./orchestration/workflow-observe.js";
191
193
  export { runGoal, DECLARE_DONE_TOOL_NAME, } from "./orchestration/goal.js";
192
194
  export { emitTaskOutcome } from "./core/task-outcome.js";
@@ -307,6 +307,23 @@ export interface RunWorkflowToolDeps {
307
307
  * Absent on both seats ⇒ byte-identical baseline: a child's asks keep the pre-#342 resolution
308
308
  * (`RunnerDeps.onAsk`, else the fail-closed headless auto-deny). */
309
309
  parentOnAsk?: import("../core/tool-policy.js").OnAsk;
310
+ /** #642 — the HOST run's `durableApproval` opt-in (value copy; the Runner mints it from `spec.durableApproval`
311
+ * beside `parentOnAsk`, the same seat the delegation tool reads as `ctx.durableApprovalForChildren`). Folded
312
+ * into `RunWorkflowOptions.defaultDurableApproval` at execute — ONLY when a `store` is wired (the wa* row's
313
+ * durable home; without it the child keeps the pre-#642 lifecycle: its unavailable ask denies fail-closed
314
+ * and the `delegation.ask_unresolvable` notice says so). `ctx.durableApprovalForChildren` wins when a
315
+ * wrapping path provides it; this dep covers the auto-mounted tool's minimal execute ctx, like `parentOnAsk`.
316
+ * Absent on both seats ⇒ byte-identical: a workflow child's ask keeps the host-seat resolution alone. */
317
+ parentDurableApproval?: {
318
+ scope: string;
319
+ ttlMs?: number;
320
+ };
321
+ /** #642 — the decisions a host holds for PARKED wa* rows of a prior run, read at execute when the model
322
+ * re-invokes with `resumeFromRunId` (a call-time getter over the run id: the decisions arrive on the host's
323
+ * trusted run channel, `RunInternals.workflowParkedResume`, and only the ones naming THIS resume's source run
324
+ * apply). Threaded into `RunWorkflowOptions.parkedResume`: the resume drives the parked child on with the
325
+ * decision instead of re-parking. Never a tool argument — a model cannot decide an approval. */
326
+ parkedResume?: (resumeFromRunId: string) => ReadonlyArray<import("./workflow.js").WorkflowParkedResume> | undefined;
310
327
  /** The HOST run's display sink (its `RunInternals.onForwardEvent` behind the runner's ctx wrapper:
311
328
  * `task_progress` always, plus the children's content events — `text_delta`/`text_end`/
312
329
  * `reasoning_delta`/`tool_start`/`tool_end`, UNTRUSTED-RAW: the consumer must redact — when the HOST spec set
@@ -5,6 +5,7 @@ import { redactSecrets, redactHostLeaks, boundedRedactedSummary } from "../core/
5
5
  import { withDelegationProvenance } from "../core/tool-policy.js";
6
6
  import { LAUNCH_RECEIPT_OWN_WORDS_CLAUSE, launchReceiptNoQuoteClause } from "../agents/launch-receipt-contract.js";
7
7
  import { startWorkflow } from "./workflow.js";
8
+ import { WORKFLOW_AGENT_PARKED_ERROR_CODE } from "./workflow-types.js";
8
9
  import { buildWorkflowPrimitives } from "./workflow-primitives.js";
9
10
  import { parseWorkflowMeta, splitWorkflowMeta, workflowScriptReadsClockOrRandom } from "./workflow-meta.js";
10
11
  import { mergeWorkflowArgs, normalizeStringArg } from "./workflow-script-store.js";
@@ -51,6 +52,7 @@ function workflowUsageBlock(run) {
51
52
  agent_count: agents.length,
52
53
  agents_done: agents.filter((a) => a.status === "completed").length,
53
54
  agents_error: agents.filter((a) => a.status === "failed").length,
55
+ ...(agents.some((a) => a.status === "parked") ? { agents_parked: agents.filter((a) => a.status === "parked").length } : {}),
54
56
  agents_empty_result: agents.filter((a) => a.status === "completed" && (a.output === undefined || a.output === "")).length,
55
57
  agents_replayed: agents.filter((a) => a.replayed === true).length,
56
58
  ...(run.journalSkips !== undefined && run.journalSkips > 0 ? { journal_skipped: run.journalSkips } : {}),
@@ -76,6 +78,7 @@ function workflowDiagnostics(runId, journalRef, resumeMiss) {
76
78
  }
77
79
  const SCRIPT_ERROR_CODES = new Set([
78
80
  "workflow.script_error",
81
+ "workflow.parked_call_changed",
79
82
  "workflow.nesting",
80
83
  "workflow.model_not_allowed",
81
84
  "workflow.agent_schema",
@@ -86,6 +89,12 @@ const SCRIPT_ERROR_CODES = new Set([
86
89
  function failureSummary(err, runId) {
87
90
  const e = err;
88
91
  const code = typeof e?.code === "string" ? e.code : undefined;
92
+ if (code === WORKFLOW_AGENT_PARKED_ERROR_CODE) {
93
+ const msg = typeof e?.message === "string" ? redactSecrets(e.message) : "";
94
+ return (`workflow parked (${code}): ${msg.length > 200 ? `${msg.slice(0, 200)}…` : msg} — it did not fail and is not finished` +
95
+ (runId ? `; per-agent rows via TaskOutput("${runId}") (the parked row reads status "parked")` : "") +
96
+ `; do not re-issue the same call — once the approval is decided, the deployment resumes this run${runId ? ` (resumeFromRunId: "${runId}")` : ""} and the script continues from the parked call`);
97
+ }
89
98
  const guide = runId
90
99
  ? `; per-agent rows via TaskOutput("${runId}"); after fixing, re-invoke with resumeFromRunId: "${runId}" (completed agents replay from the journal)`
91
100
  : "";
@@ -465,6 +474,7 @@ export async function createRunWorkflowTool(d) {
465
474
  return structuredError(`workflow script failed to compile: ${err instanceof Error ? err.message : String(err)}`);
466
475
  }
467
476
  const hostOnAsk = ctx.onAsk ?? d.parentOnAsk;
477
+ const hostDurableApproval = ctx.durableApprovalForChildren ?? d.parentDurableApproval;
468
478
  const runGovernance = hostOnAsk !== undefined && governance.baseline.base.onAsk === undefined
469
479
  ? {
470
480
  ...governance,
@@ -537,6 +547,8 @@ export async function createRunWorkflowTool(d) {
537
547
  })(),
538
548
  ...(d.parentModel !== undefined ? { defaultModel: d.parentModel } : {}),
539
549
  ...(d.parentGetApiKeyAndHeaders !== undefined ? { defaultGetApiKeyAndHeaders: d.parentGetApiKeyAndHeaders } : {}),
550
+ ...(hostDurableApproval !== undefined && d.store !== undefined ? { defaultDurableApproval: { ...hostDurableApproval } } : {}),
551
+ ...(resumeFromRunId !== undefined && d.parkedResume !== undefined && d.parkedResume(resumeFromRunId) !== undefined ? { parkedResume: d.parkedResume(resumeFromRunId) } : {}),
540
552
  ...(parentCenterArtifactDigest !== undefined ? { parentCenterArtifactDigest } : {}),
541
553
  ...(parentCenterSourceRevision !== undefined ? { parentCenterSourceRevision } : {}),
542
554
  ...((ctx.forwardEvent ?? d.forwardEvent) !== undefined ? { onForwardEvent: (ctx.forwardEvent ?? d.forwardEvent) } : {}),
@@ -56,7 +56,7 @@ export declare function listWorkflowRuns(store: WorkflowRunStore, scope: string,
56
56
  */
57
57
  export declare function getWorkflowRun(store: WorkflowRunStore, id: string, scope: string): Promise<WorkflowRun | null>;
58
58
  /** design/99 MF-W display status. */
59
- export type AgentDisplayStatus = "queued" | "running" | "done" | "failed" | "interrupted";
59
+ export type AgentDisplayStatus = "queued" | "running" | "done" | "failed" | "interrupted" | "parked";
60
60
  /**
61
61
  * design/99 MF-W (design-review DoR ⑤+⑥): the SHARED, anti-drift projection of a workflow agent's record-level
62
62
  * status → a CC-style display status, derived PURELY from the persisted record (the agent's `status` + `startedAt`
@@ -66,6 +66,8 @@ export function deriveAgentDisplayStatus(agent, runStatus) {
66
66
  return "done";
67
67
  if (agent.status === "failed")
68
68
  return "failed";
69
+ if (agent.status === "parked")
70
+ return "parked";
69
71
  if (runStatus !== "running")
70
72
  return "interrupted";
71
73
  return agent.startedAt === undefined ? "queued" : "running";
@@ -1,7 +1,9 @@
1
1
  import type { TaskResult, TaskStatus, ToolActivity } from "../core/types.js";
2
2
  export type WorkflowRunStatus = "running" | "completed" | "failed";
3
- /** Status of one phase / agent-run within a workflow. */
4
- export type WorkflowItemStatus = "running" | "completed" | "failed";
3
+ /** Status of one phase / agent-run within a workflow. `"parked"` (#642) is an AGENT row's word only — the
4
+ * agent durably paused at an approval gate and the run suspended on it (the row carries the token, see
5
+ * {@link WorkflowAgentRun.parkedCheckpointToken}); a phase or group never parks (a parked agent ends the run). */
6
+ export type WorkflowItemStatus = "running" | "completed" | "failed" | "parked";
5
7
  export interface WorkflowPhase {
6
8
  title: string;
7
9
  /** F7/B7 (CC pretty.js:446478): a phase PRE-REGISTERED from `meta.phases` starts as `"pending"` — the plan
@@ -119,6 +121,16 @@ export interface WorkflowAgentRun {
119
121
  * (`fork(sessionId)→runTask`, or `runTask({sessionId})`) instead of a fresh re-run. An opaque id (not
120
122
  * content) → scope-gated via `getWorkflowRun`, no redaction needed. Absent for a stub run that minted none. */
121
123
  sessionId?: string;
124
+ /**
125
+ * #642 — the durable-approval checkpoint token this agent is PARKED on (`status: "parked"` only): the child
126
+ * durably paused at an approval gate under the host's forwarded `durableApproval`, the run recorded the
127
+ * park here and suspended. The mirror of the background agent row's `parkedCheckpointToken`, and the
128
+ * workflow-origin JOIN a host routes a parked approval by: a checkpoint whose `sourceTaskId` equals this
129
+ * row's {@link sessionId} is a workflow child's park, and this token is the same row's redemption key —
130
+ * the checkpoint row itself carries no workflow key. It is the resume CAPABILITY (never log it or put it
131
+ * in a URL); a durable row is its home exactly as on the background lane. Absent on every other status.
132
+ */
133
+ parkedCheckpointToken?: string;
122
134
  /**
123
135
  * #499 — the ENGINE-minted identity of the task run behind this record (= its `TaskResult.runId`).
124
136
  *
@@ -515,6 +527,29 @@ export declare class WorkflowAgentBlockedError extends Error {
515
527
  /** Human-readable cause, already bounded + redacted by the engine (classifier text is model output). */
516
528
  reason: string);
517
529
  }
530
+ /** #642 — the machine code of a run that SUSPENDED on a parked agent (the thrown error's `code`, the run's
531
+ * failed terminal). Not a script fault and not a host fault: an approval a person still has to decide. */
532
+ export declare const WORKFLOW_AGENT_PARKED_ERROR_CODE = "workflow.agent_parked";
533
+ /**
534
+ * #642 — thrown by `ctx.agent` / `ctx.agentStream().result()` when the child durably PAUSED at an approval gate
535
+ * under the host's forwarded `durableApproval` (the workflow lane's park family, the background delegation's
536
+ * twin): the wa* row records `status:"parked"` + `parkedCheckpointToken`, the journal records the parked leg at
537
+ * its ordinal, and the RUN SUSPENDS — every later spawn is refused with this same error and the run's terminal
538
+ * is this park whatever the script body returns (the team-discussion `teamPause` shape). Nothing after the
539
+ * parked call runs; the script must not retry it (an identical call parks again). A resume of the run
540
+ * (`resumeFromRunId`) with the host's decision for the token drives the parked child on and the script
541
+ * continues from here; without a decision the ordinal re-parks.
542
+ *
543
+ * Carries the non-secret `checkpointId` (display/correlation) and the gate kind — never the token (the resume
544
+ * capability lives on the wa* row, a durable row, exactly as on the background lane).
545
+ */
546
+ export declare class WorkflowAgentParkedError extends Error {
547
+ readonly label: string;
548
+ readonly gateKind: string;
549
+ readonly checkpointId?: string | undefined;
550
+ readonly code = "workflow.agent_parked";
551
+ constructor(label: string, gateKind: string, checkpointId?: string | undefined);
552
+ }
518
553
  /** design/98 §D.6 hard cap: thrown by `ctx.agent` once the workflow has spawned `max` agents (a runaway
519
554
  * LLM-authored script is bounded, not trusted — this counts cumulative spawns, so it binds even when the
520
555
  * token budget has room left, or when no budget was set at all). */
@@ -55,6 +55,22 @@ export class WorkflowAgentBlockedError extends Error {
55
55
  this.name = "WorkflowAgentBlockedError";
56
56
  }
57
57
  }
58
+ export const WORKFLOW_AGENT_PARKED_ERROR_CODE = "workflow.agent_parked";
59
+ export class WorkflowAgentParkedError extends Error {
60
+ label;
61
+ gateKind;
62
+ checkpointId;
63
+ code = WORKFLOW_AGENT_PARKED_ERROR_CODE;
64
+ constructor(label, gateKind, checkpointId) {
65
+ super(`workflow agent "${label}" is parked at an approval gate (${gateKind}${checkpointId !== undefined ? `, checkpoint ${checkpointId}` : ""}) — ` +
66
+ "the workflow is suspended until the approval is decided; nothing after this call runs. Do not re-issue the call: " +
67
+ "a resume of this run carrying the decision continues from here.");
68
+ this.label = label;
69
+ this.gateKind = gateKind;
70
+ this.checkpointId = checkpointId;
71
+ this.name = "WorkflowAgentParkedError";
72
+ }
73
+ }
58
74
  export class WorkflowMaxAgentsError extends Error {
59
75
  max;
60
76
  budgetTotal;
@@ -1,5 +1,6 @@
1
1
  import type { TSchema } from "typebox";
2
- import type { RunnerSelfSeat } from "../core/runner/contracts.js";
2
+ import type { InheritedGate, RunnerSelfSeat } from "../core/runner/contracts.js";
3
+ import type { CheckpointToken, ResumeOutcome } from "../core/checkpoint-store.js";
3
4
  import type { AgentDefinition, TaskEvent, TaskResult, TaskSpec } from "../core/types.js";
4
5
  import type { WorkflowRunStore } from "../core/workflow-run-store.js";
5
6
  import type { WorkflowJournalStore, ResumeClaimArgs } from "../core/workflow-journal-store.js";
@@ -120,7 +121,9 @@ export declare class WorkflowJournalIncompatibleError extends Error {
120
121
  readonly fromRunId: string;
121
122
  readonly ordinal: number;
122
123
  readonly code = "workflow.journal_incompatible";
123
- constructor(fromRunId: string, ordinal: number);
124
+ constructor(fromRunId: string, ordinal: number,
125
+ /** #642: an alternative reason clause (the parked-row cross-check); absent ⇒ the no-terminal-cause wording. */
126
+ reason?: string);
124
127
  }
125
128
  /**
126
129
  * A TRUSTED, run-scoped internal channel carrying the workflow **nesting depth** across a process boundary
@@ -282,6 +285,18 @@ export interface WorkflowRunContext {
282
285
  /** Emit a narrator log line (observability only — not stored on the run). */
283
286
  log(message: string): void;
284
287
  }
288
+ /**
289
+ * #642 — one decision a resume carries for a PARKED ordinal of the prior run ({@link RunWorkflowOptions.parkedResume}):
290
+ * the checkpoint token the wa* row is parked on (the row's `parkedCheckpointToken`, the redemption key), the
291
+ * caller's decision for it (the same {@link ResumeOutcome} a top-level `runner.resume` takes), and the optional
292
+ * cross-process re-supply of the checkpoint's opaque parent constraints. The token is matched by string equality
293
+ * against the journaled parked entry — an entry whose token no decision names re-parks.
294
+ */
295
+ export interface WorkflowParkedResume {
296
+ token: CheckpointToken;
297
+ outcome: ResumeOutcome;
298
+ inheritedGate?: InheritedGate;
299
+ }
285
300
  export interface RunWorkflowOptions {
286
301
  /** Trusted caller-supplied run id. Used by the unified task registry so tool-launched workflows expose
287
302
  * `task_id === runId` (`w*`) without making the model choose an id. Omit for the legacy UUID mint path. */
@@ -435,6 +450,30 @@ export interface RunWorkflowOptions {
435
450
  * credential half of model inheritance; scripts can never set it — governance strips it — so
436
451
  * this is always the host's). NOT call identity (auth, not behavior; a function, not data). */
437
452
  defaultGetApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
453
+ /**
454
+ * #642 — the HOST run's `durableApproval` opt-in (value copy), inherited into every spawned agent whose
455
+ * fold left the seat unpinned (a deployment baseline that pins `durableApproval` wins; scripts can never set
456
+ * it — governance strips it). With it a workflow child's plain policy `ask` — an ask the host's approver
457
+ * answers `unavailable` for, or a durable mandate — PARKS durably instead of denying fail-closed, exactly as
458
+ * a background delegation child's does under the §7.3 park family: the wa* row records `status:"parked"` +
459
+ * the token, the run SUSPENDS (the script cannot continue past a parked call), and a resume drives the
460
+ * parked child on with the caller's decision ({@link parkedResume}). Tighten-only: a park capability,
461
+ * never a widening — the decision stays with a person. REQUIRES {@link store} (the wa* row's durable home;
462
+ * refused loudly at entry without it). Threaded by the Workflow tool from the host's trusted seat; a
463
+ * directly-started workflow passes its own. NOT call identity.
464
+ */
465
+ defaultDurableApproval?: TaskSpec["durableApproval"];
466
+ /**
467
+ * #642 — the decisions a resume ({@link resumeFromRunId}) carries for PARKED ordinals of the prior run: at
468
+ * an ordinal whose journal entry is `parked` (a matching call key), the run drives the parked child's own
469
+ * resume — `runner.resumeStream(token, outcome, …)` on the pinned session, the child's spec rebuilt from
470
+ * the script call exactly as at spawn — and settles its result at that ordinal, so the script continues past
471
+ * the call it parked on. A parked ordinal with NO decision here re-parks (the row and the journal say the
472
+ * same thing again, the run suspends again); it is never run live. `inheritedGate` is the cross-process
473
+ * re-supply of the checkpoint's opaque parent constraints (the same seat the background lane's drive
474
+ * takes). TRUSTED: a host-supplied decision, never a script/model argument; requires `resumeFromRunId`.
475
+ */
476
+ parkedResume?: ReadonlyArray<WorkflowParkedResume>;
438
477
  /** design/148 S1 — the HOST run's adopted center artifact (trusted, threaded by the Workflow tool
439
478
  * from ctx): every spawned agent composes the same closure. */
440
479
  parentCenterArtifactDigest?: string;