@sema-agent/core 5.57.0 → 5.58.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 (183) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/cumulative-stats.d.ts +1 -1
  4. package/dist/agents/observer.d.ts +2 -2
  5. package/dist/agents/peer-admission.d.ts +1 -1
  6. package/dist/agents/retain-ledger.d.ts +2 -2
  7. package/dist/agents/roster-store.d.ts +8 -8
  8. package/dist/agents/send-message-tool.d.ts +2 -2
  9. package/dist/agents/subagent-steps.d.ts +1 -1
  10. package/dist/agents/subagent.d.ts +13 -13
  11. package/dist/agents/team.d.ts +5 -5
  12. package/dist/agents/tool-filter.d.ts +2 -2
  13. package/dist/agents/verify.d.ts +1 -1
  14. package/dist/bench/metrics.d.ts +35 -35
  15. package/dist/brain/degrading.d.ts +1 -1
  16. package/dist/brain/errors.d.ts +3 -3
  17. package/dist/brain/reasoning.d.ts +2 -2
  18. package/dist/brain/repetition.d.ts +1 -1
  19. package/dist/brain/status-sink.d.ts +2 -2
  20. package/dist/brain/stream-shared.d.ts +1 -1
  21. package/dist/config/catalog.d.ts +5 -5
  22. package/dist/core/arg-summary.d.ts +4 -4
  23. package/dist/core/ask-class.d.ts +2 -2
  24. package/dist/core/ask-question.d.ts +1 -1
  25. package/dist/core/auto-compaction.d.ts +15 -15
  26. package/dist/core/auto-mode.d.ts +5 -5
  27. package/dist/core/background-agent-store.d.ts +20 -20
  28. package/dist/core/background-shell.d.ts +4 -4
  29. package/dist/core/checkpoint-store.d.ts +35 -27
  30. package/dist/core/context-edit.d.ts +1 -1
  31. package/dist/core/context-guard.d.ts +1 -1
  32. package/dist/core/exec-output-tail.d.ts +6 -6
  33. package/dist/core/file-snapshot-store.d.ts +8 -8
  34. package/dist/core/git-worktree-env.d.ts +3 -3
  35. package/dist/core/governance-codes.js +2 -0
  36. package/dist/core/hooks.d.ts +73 -33
  37. package/dist/core/hooks.js +87 -25
  38. package/dist/core/image-downsample.d.ts +1 -1
  39. package/dist/core/locked-config.d.ts +1 -1
  40. package/dist/core/lsp.d.ts +1 -1
  41. package/dist/core/mailbox-store.d.ts +1 -1
  42. package/dist/core/mcp.d.ts +3 -3
  43. package/dist/core/memory-engine/consolidation-driver.d.ts +207 -0
  44. package/dist/core/memory-engine/consolidation-driver.js +378 -0
  45. package/dist/core/memory-engine/consolidation.d.ts +46 -2
  46. package/dist/core/memory-engine/consolidation.js +1 -0
  47. package/dist/core/memory-engine/data-plane.d.ts +1 -1
  48. package/dist/core/memory-engine/distiller.d.ts +550 -0
  49. package/dist/core/memory-engine/distiller.js +598 -0
  50. package/dist/core/memory-engine/dual-root.d.ts +1 -1
  51. package/dist/core/memory-engine/engine.d.ts +47 -3
  52. package/dist/core/memory-engine/engine.js +37 -3
  53. package/dist/core/memory-engine/file-backend.d.ts +1 -1
  54. package/dist/core/memory-engine/index.d.ts +4 -2
  55. package/dist/core/memory-engine/index.js +4 -2
  56. package/dist/core/memory-engine/origin-clearance.d.ts +1 -1
  57. package/dist/core/memory-engine/scope-contract.d.ts +4 -4
  58. package/dist/core/memory-engine/sync-client.d.ts +16 -16
  59. package/dist/core/memory-engine/sync.d.ts +4 -4
  60. package/dist/core/memory-recall.d.ts +1 -1
  61. package/dist/core/memory.d.ts +2 -2
  62. package/dist/core/permission-rule-consent.d.ts +185 -36
  63. package/dist/core/permission-rule-consent.js +219 -44
  64. package/dist/core/permission-rule-model.d.ts +194 -31
  65. package/dist/core/permission-rule-model.js +93 -35
  66. package/dist/core/permission-rules.d.ts +9 -9
  67. package/dist/core/remote-env.d.ts +8 -8
  68. package/dist/core/roles.d.ts +3 -3
  69. package/dist/core/roles.js +1 -0
  70. package/dist/core/runner/assemble-result.d.ts +2 -2
  71. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  72. package/dist/core/runner/memory-consolidation-driver.d.ts +49 -0
  73. package/dist/core/runner/memory-consolidation-driver.js +60 -0
  74. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  75. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  76. package/dist/core/runner/prepare-task.d.ts +21 -21
  77. package/dist/core/runner/prepare-task.js +21 -14
  78. package/dist/core/runner/prepare-workspace-restore.d.ts +2 -2
  79. package/dist/core/runner/runtask.d.ts +11 -11
  80. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  81. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  82. package/dist/core/runner/tool-disclosure.d.ts +2 -2
  83. package/dist/core/runner/turn-attachments.d.ts +11 -11
  84. package/dist/core/scheduler.d.ts +5 -5
  85. package/dist/core/secret-env.d.ts +1 -1
  86. package/dist/core/sensitive-path-policy.d.ts +1 -1
  87. package/dist/core/session-policy-store.d.ts +2 -2
  88. package/dist/core/session-reconcile.d.ts +2 -2
  89. package/dist/core/session-store.d.ts +3 -3
  90. package/dist/core/session.d.ts +1 -1
  91. package/dist/core/shutdown-debug.d.ts +2 -2
  92. package/dist/core/side-query.d.ts +2 -2
  93. package/dist/core/spec-contract.d.ts +1 -1
  94. package/dist/core/store-contracts/contract-harness.d.ts +2 -2
  95. package/dist/core/store-contracts/contract-kit-version.d.ts +2 -2
  96. package/dist/core/store-contracts/mailbox-store-contract.d.ts +1 -1
  97. package/dist/core/store-contracts/mailbox-store-contract.js +1 -1
  98. package/dist/core/task-notification.d.ts +5 -5
  99. package/dist/core/task-registry-agent.d.ts +12 -12
  100. package/dist/core/task-registry-monitor.d.ts +1 -1
  101. package/dist/core/task-registry-shared.d.ts +41 -41
  102. package/dist/core/task-registry.d.ts +12 -12
  103. package/dist/core/tool-detach.d.ts +2 -2
  104. package/dist/core/tool-errors.d.ts +3 -3
  105. package/dist/core/tool-policy.d.ts +55 -28
  106. package/dist/core/tool-result-budget.d.ts +1 -1
  107. package/dist/core/tool-result-store.d.ts +2 -2
  108. package/dist/core/tools.d.ts +1 -1
  109. package/dist/core/trace.d.ts +26 -23
  110. package/dist/core/types.d.ts +123 -70
  111. package/dist/core/untrusted-egress.d.ts +1 -1
  112. package/dist/core/untrusted-text.d.ts +7 -7
  113. package/dist/core/wiring-manifest.d.ts +5 -5
  114. package/dist/core/workflow-journal-store.d.ts +14 -14
  115. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  116. package/dist/core/workflow-run-store-contract.js +1 -1
  117. package/dist/core/workflow-run-store.d.ts +4 -4
  118. package/dist/engine/compaction/compaction.d.ts +3 -3
  119. package/dist/engine/compaction/utils.d.ts +2 -2
  120. package/dist/engine/execution-env/kill-tree.d.ts +1 -1
  121. package/dist/engine/execution-env/node-execution-env.d.ts +8 -8
  122. package/dist/engine/harness/agent-harness.d.ts +6 -6
  123. package/dist/engine/harness/messages.d.ts +1 -1
  124. package/dist/engine/harness/types.d.ts +10 -10
  125. package/dist/engine/llm/types.d.ts +14 -14
  126. package/dist/engine/loop/agent-loop.d.ts +3 -3
  127. package/dist/engine/loop/types.d.ts +4 -4
  128. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  129. package/dist/engine/session/import-validate.d.ts +1 -1
  130. package/dist/engine/session/log-digest.d.ts +1 -1
  131. package/dist/engine/session/memory-repo.d.ts +2 -2
  132. package/dist/engine/session/session.d.ts +4 -4
  133. package/dist/fixtures/index.d.ts +4 -4
  134. package/dist/index.d.ts +5 -4
  135. package/dist/index.js +3 -2
  136. package/dist/orchestration/goal.d.ts +1 -1
  137. package/dist/orchestration/run-spec.d.ts +1 -1
  138. package/dist/orchestration/run-workflow-tool.d.ts +12 -12
  139. package/dist/orchestration/workflow-governance.d.ts +4 -4
  140. package/dist/orchestration/workflow-observe.d.ts +1 -1
  141. package/dist/orchestration/workflow-script-runner.d.ts +1 -1
  142. package/dist/orchestration/workflow-script-store.d.ts +9 -9
  143. package/dist/orchestration/workflow-size-guideline.d.ts +1 -1
  144. package/dist/orchestration/workflow-types.d.ts +5 -5
  145. package/dist/orchestration/workflow.d.ts +10 -10
  146. package/dist/prompt-assembly/artifact-store.d.ts +1 -1
  147. package/dist/prompt-assembly/artifact.d.ts +1 -1
  148. package/dist/prompt-assembly/assemble.d.ts +1 -1
  149. package/dist/prompt-assembly/composer.d.ts +2 -2
  150. package/dist/prompt-assembly/epoch.d.ts +2 -2
  151. package/dist/prompt-assembly/event-registry.d.ts +1 -1
  152. package/dist/prompt-assembly/explain.d.ts +3 -3
  153. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  154. package/dist/prompt-assembly/turn-snapshot.d.ts +4 -4
  155. package/dist/prompt-assembly/types.d.ts +12 -12
  156. package/dist/prompts/coordinator.d.ts +1 -1
  157. package/dist/prompts/default.d.ts +8 -8
  158. package/dist/prompts/simple-sections.d.ts +3 -3
  159. package/dist/prompts/supervisor.d.ts +2 -2
  160. package/dist/scenarios/full-body.d.ts +3 -3
  161. package/dist/scenarios/scenario-registry.d.ts +1 -1
  162. package/dist/stores/cc/sidecar-transcript.d.ts +3 -3
  163. package/dist/stores/file/fs-atomic.d.ts +2 -2
  164. package/dist/stores/file/index.d.ts +1 -1
  165. package/dist/stores/file/session-store.d.ts +2 -2
  166. package/dist/stores/file/workflow-journal-store.d.ts +4 -4
  167. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  168. package/dist/tools/fs/encoding.d.ts +4 -4
  169. package/dist/tools/fs/fs-bash.d.ts +3 -3
  170. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  171. package/dist/tools/fs/fs-shared.d.ts +6 -6
  172. package/dist/tools/fs/index.d.ts +2 -2
  173. package/dist/tools/fs/notebook.d.ts +1 -1
  174. package/dist/tools/fs/pdf.d.ts +1 -1
  175. package/dist/tools/fs/read-deny.d.ts +1 -1
  176. package/dist/tools/fs/safety.d.ts +9 -9
  177. package/dist/tools/fs/search.d.ts +2 -2
  178. package/dist/tools/monitor.d.ts +3 -3
  179. package/dist/tools/task-list.d.ts +2 -2
  180. package/dist/tools/web.d.ts +4 -4
  181. package/dist/tools/worktree.d.ts +5 -5
  182. package/package.json +1 -1
  183. package/test/export-surface.snapshot.json +56 -3
@@ -32,6 +32,12 @@
32
32
  * deny/ask layer (`permission-rule-org.ts`) judges every segment and runs ahead of the allow lane at
33
33
  * the gate — a compound-prefix allow can therefore never outrun a deny on any segment it admits.
34
34
  *
35
+ * design/375 adds upstream's OTHER half of that per-segment evaluation on the allow side: a compound
36
+ * whose EVERY segment is admitted by some eligible rule is covered by the CONJUNCTION of those rules
37
+ * (`findAdmittingRule` answers with the coverage SET). No single rule widens: the matcher predicates
38
+ * above are untouched, and the set form is the decision layer's answer, fenced by the same
39
+ * ahead-running per-segment deny/ask layer.
40
+ *
35
41
  * The floor is `parseLeadingCommandName` + `splitShellCompoundSegments` — the one simple-command parser
36
42
  * and the one segmentation, both already shared with the read-only classifier, the reversibility probe,
37
43
  * the coarse command-name policy and the skill tool specifier. A second tokenizer would drift, and drift
@@ -367,35 +373,181 @@ export declare function scopeCoversCwd(scope: RuleScope, cwd: string | undefined
367
373
  * when tombstones are applied, so a rule with an empty `adds` is a rule that no longer exists. */
368
374
  export declare function isRuleLive(rule: PersistedAllowRule): boolean;
369
375
  /**
370
- * Find the rule that admits this command for a task in `cwd`, or `undefined`.
376
+ * design/375 §5.1 — MAY this rule speak about this call's CONTEXT? Tool identity plus scope coverage,
377
+ * and nothing about the rule's liveness: the CONTEXT question is the half a not-yet-persisted rule (a
378
+ * card's proposed candidate, which has no add dots yet) can already answer.
379
+ *
380
+ * The tool conjunct is not decoration: more than one tool takes a shell command string, and matching on
381
+ * the command alone would let an approval given on one surface resolve a call on another. The scope
382
+ * conjunct is the project fence — project A's approval is not project B's, and a project rule with no
383
+ * cwd to compare against covers nothing (fail-closed, {@link scopeCoversCwd}).
371
384
  *
372
- * Order is defined only for reporting (the first match wins and its text is what the decision names);
373
- * no rule is stronger than another they are all the same one answer, "the person already said yes to
374
- * this".
385
+ * `scope` rides beside the rule rather than inside it for the PROPOSED half's sake: a
386
+ * {@link ParsedAllowRule} carries no scope, so a proposal is judged against the scope it would LAND in.
387
+ */
388
+ export declare function eligibleContext(rule: {
389
+ tool: string;
390
+ scope: RuleScope;
391
+ }, call: {
392
+ tool: string;
393
+ cwd: string | undefined;
394
+ }): boolean;
395
+ /**
396
+ * design/375 §5.1 — MAY this PERSISTED rule participate in admitting this call at all? The liveness
397
+ * layer on top of {@link eligibleContext}: a tombstoned rule (zero surviving adds) is a rule that no
398
+ * longer exists and must not count as coverage anywhere.
399
+ *
400
+ * THE single eligibility predicate for every consumer that reads the persisted store — the gate's
401
+ * conjunction arm and its coverage-set selection here, the suggestion face's coverage judgement and the
402
+ * consent prepare's ({@link segmentCoverageOf} is the shared instrument). One predicate, so a rule that
403
+ * one consumer refuses cannot be a rule another counts: the historically inlined trio of checks
404
+ * (live / tool / scope) is this function, verbatim.
405
+ */
406
+ export declare function eligiblePersisted(rule: PersistedAllowRule, call: {
407
+ tool: string;
408
+ cwd: string | undefined;
409
+ }): boolean;
410
+ /**
411
+ * Find the rules that admit this command for a task in `cwd`, or `undefined`.
412
+ *
413
+ * THE ANSWER IS A COVERAGE SET (design/375 §5.1, the upstream per-segment form): a single rule that
414
+ * admits the whole command is a one-element set — the historical arm, table order preserved verbatim —
415
+ * and a COMPOUND whose every segment is admitted by some eligible rule is covered by the CONJUNCTION of
416
+ * those rules, returned as the set that covered it. The judging ladder, in order:
417
+ * 1. the fail-closed floor (a shape the lane cannot read matches nothing), evaluated once;
418
+ * 2. the single-rule arm — whole-string exact, single/compound-prefix — exactly the historical loop:
419
+ * first table-order match wins and is what the decision names. A single rule naming the command is
420
+ * found BEFORE any conjunction: reporting one rule is narrower than reporting a set;
421
+ * 3. the conjunction arm, compounds only: every segment must be admitted by some eligible rule under
422
+ * the MATCH reading (the allow side's own under-approximating instrument — never the deny side's
423
+ * wider PROGRAM_RUNS reading). Per segment the FIRST table-order admitting rule is selected, and
424
+ * the coverage set is those selections in segment order, de-duplicated — the same command against
425
+ * the same rule table always names the same set (reporting-order determinism, the historical
426
+ * "order is defined only for reporting" contract extended to sets).
427
+ *
428
+ * What the conjunction arm deliberately does NOT change: {@link ruleAdmitsCommand} itself. A
429
+ * single-command prefix body still never admits a chain — `Bash(npm:*)` does not admit
430
+ * `npm test && curl evil.example` — but the chain is now admitted when EVERY segment has a standing
431
+ * yes, which is the upstream decision form (per-segment allow, any-segment deny decided strictly
432
+ * earlier at the gate's org layer). A trailing-terminator spelling (`a && b ;`) keeps the conservative
433
+ * answer by construction: under the MATCH reading its tail is an empty segment the floor refuses, so
434
+ * the whole command is outside the lane and asks.
435
+ *
436
+ * Order is defined only for reporting (what the decision names); no rule is stronger than another —
437
+ * they are all the same one answer, "the person already said yes to this".
375
438
  */
376
439
  export declare function findAdmittingRule(rules: readonly PersistedAllowRule[], call: {
377
440
  tool: string;
378
441
  command: string;
379
442
  cwd: string | undefined;
380
- }): PersistedAllowRule | undefined;
381
- /** A rule the engine offers to mint, as carried on an approval card. */
382
- export interface RuleSuggestion {
443
+ }): readonly PersistedAllowRule[] | undefined;
444
+ /**
445
+ * design/375 §5.2 — the per-segment coverage table for `command`: which segments an eligible rule
446
+ * already admits, judged with the SAME split, the same MATCH reading and the same eligibility
447
+ * predicates as the gate's conjunction arm — one instrument, so the suggestion face and the consent
448
+ * prepare can never call a segment covered that the gate would ask about (or vice versa).
449
+ *
450
+ * TWO SOURCES, two predicates, and the seat labels are the contract:
451
+ * · `persisted` — rules already in the store, judged {@link eligiblePersisted} ∧ admit: a tombstoned
452
+ * rule, another project's rule and another tool's rule are uniformly NOT coverage;
453
+ * · `proposed` — rules of the CURRENT batch that are not yet persisted (no add dots yet), judged
454
+ * {@link eligibleContext} ∧ admit against the scope each would land in. Judging these with the
455
+ * persisted predicate would make "this batch, once redeemed, covers the command" permanently false
456
+ * — the liveness layer is the one thing a proposal legitimately lacks.
457
+ *
458
+ * Returns `undefined` for a command the lane cannot read at all (the fail-closed floor — the caller's
459
+ * honest answer is then "no coverage table", which every consumer must treat as all-uncovered). The
460
+ * reported `segment` texts are the FOLDED spellings, trimmed — display/correlation seats, never
461
+ * adjudication inputs.
462
+ */
463
+ export declare function segmentCoverageOf(command: string, rules: {
464
+ persisted: readonly PersistedAllowRule[];
465
+ proposed?: readonly {
466
+ rule: ParsedAllowRule;
467
+ scope: RuleScope;
468
+ }[];
469
+ }, call: {
470
+ tool: string;
471
+ cwd: string | undefined;
472
+ }): readonly SegmentCoverage[] | undefined;
473
+ /** One row of a per-segment coverage table (see {@link segmentCoverageOf}): the FOLDED, trimmed
474
+ * segment text (a display/correlation seat, never adjudication input) and whether an eligible rule
475
+ * already admits it. */
476
+ export interface SegmentCoverage {
477
+ readonly segment: string;
478
+ readonly covered: boolean;
479
+ }
480
+ /** One member of a {@link RuleOffer} batch: a rule the engine offers to mint for ONE segment of a
481
+ * compound command. `segment` is the FOLDED segment text the member was minted from — a rendering
482
+ * seat, never adjudication input. */
483
+ export interface SegmentRuleSuggestion {
383
484
  /** The canonical rule text the host would redeem. */
384
- rule: string;
385
- match: PersistedRuleMatch;
485
+ readonly rule: string;
486
+ readonly match: PersistedRuleMatch;
386
487
  /** The command pattern in the rule, for a host that wants to render it without re-parsing. */
387
- command: string;
488
+ readonly command: string;
489
+ /** The (folded) segment this suggestion came from. Raw post-rewrite command bytes — a substring
490
+ * of the same call's `args`/`command` values riding beside it on every surface that carries
491
+ * this seat — so render it with contextual escaping, exactly like those. */
492
+ readonly segment: string;
388
493
  }
389
494
  /**
390
- * The 1-2 candidates offered on an approval card for `command`.
495
+ * design/375 §3.1 ONE "don't ask again" option on an approval card. A CLOSED discriminated union
496
+ * (`kind`); consumers parse it per element, and an element whose `kind` they do not know is dropped
497
+ * (single-row degrade) while keeping the ORIGINAL wire index for every element they keep — selection
498
+ * and redemption are keyed on original indices, so a consumer that cannot preserve the mapping must
499
+ * suppress its persistence actions entirely (fail toward asking).
500
+ *
501
+ * · `kind: "single"` — one rule covering the WHOLE command (the exact spelling, or a broader
502
+ * reviewed prefix form for a simple command).
503
+ * · `kind: "batch"` — a CONJUNCTION batch of per-segment rules for a compound command. Choosing it
504
+ * is one yes to ALL of `rules` at once — there is no per-member selection inside a batch; a
505
+ * consumer that wants a narrower answer picks the single offer or approves once without a rule.
506
+ * `rules` has 1..5 members (the card-lane minting cap), in segment order, deduplicated by rule
507
+ * text. `uncoveredSegments` is the honest surplus disclosure of the cap: judged at MINT time
508
+ * against the coverage snapshot the card was drawn from ∪ this batch's FINAL (deduplicated,
509
+ * capped) rules, it counts the violating segments still admitted by neither — 0 means "once this
510
+ * batch is redeemed, this compound is fully covered by that snapshot's lights". It is a statement
511
+ * about the minting snapshot, not a standing guarantee: a concurrent rule removal can date it.
512
+ *
513
+ * Array contract (`ruleOffers`, the durable park row, and the consent record's projection while
514
+ * the card is PENDING all share it): at most 2 offers; the whole-string exact single is index 0
515
+ * whenever present; the batch offer — at most one — is always last. It is a draw-time contract,
516
+ * not a settled-record invariant: confirming an EDITED candidate appends a third single offer
517
+ * AFTER the batch in the same write that settles the record, so a SETTLED record's projection may
518
+ * carry it. ADVISORY display metadata, never adjudication input, and never a
519
+ * rule by itself: minting is a separate act that goes through the approval-record protocol, so a
520
+ * surface that ignores offers entirely changes nothing about what is allowed. The authoritative
521
+ * render source for a card is the PREPARED record's own offers (record-is-what-you-see); the ask-time
522
+ * projection here may drift from it across a coverage change, and the drift direction is harmless
523
+ * (the record is what gets confirmed).
524
+ */
525
+ export type RuleOffer = {
526
+ readonly kind: "single";
527
+ /** The canonical rule text the host would redeem. */
528
+ readonly rule: string;
529
+ readonly match: PersistedRuleMatch;
530
+ /** The command pattern in the rule, for a host that wants to render it without re-parsing. */
531
+ readonly command: string;
532
+ } | {
533
+ readonly kind: "batch";
534
+ /** 1..5 per-segment rules, segment order, deduplicated by rule text. */
535
+ readonly rules: readonly SegmentRuleSuggestion[];
536
+ /** Mint-time honest surplus: violating segments admitted by neither the coverage snapshot nor
537
+ * this batch's final rules (see the union doc above for the exact arithmetic). */
538
+ readonly uncoveredSegments: number;
539
+ };
540
+ /**
541
+ * The offers presented on an approval card for `command` (design/375 §5.2).
391
542
  *
392
- * **Array order is a documented CONTRACT, not an implementation accident**: display order = array
393
- * order = narrowest first. The EXACT form (this whole command line) is always index 0 whenever
394
- * anything is offered at all; a broader reviewed PREFIX form — at most one follows at index 1.
395
- * Selection indices and redemption tickets are index-keyed against this order (a card's
396
- * `selectedCandidate` and its `rt.<index>.` tickets), so consumers may rely on it.
543
+ * **Array order is a documented CONTRACT, not an implementation accident** (see {@link RuleOffer}):
544
+ * the whole-string EXACT single is index 0 whenever present; a SIMPLE command may carry a broader
545
+ * reviewed PREFIX single after it; a COMPOUND carries at most one BATCH offer, always last.
546
+ * Selection indices are keyed against this order, so consumers may rely on it; redemption TICKETS
547
+ * are NOT they key on the consent record's flat CANDIDATE list, and a chosen offer is redeemed
548
+ * via `redeemRuleBatch` (see the consent record's own doc for the two index spaces).
397
549
  *
398
- * The prefix candidate is the LONGEST body two sources agree to offer: {@link SUGGESTION_LEXICON}'s
550
+ * The prefix single is the LONGEST body two sources agree to offer: {@link SUGGESTION_LEXICON}'s
399
551
  * deeper-than-two-words rows first, and otherwise {@link genericPrefixBody}'s general two-word form
400
552
  * (`argv[0]` + a token shaped like a subcommand). Read those two for the grammar, its adopted origin,
401
553
  * and the operand false positive it accepts; the short version is that a bare verb (`git`), an
@@ -407,9 +559,10 @@ export interface RuleSuggestion {
407
559
  * "no prefix".
408
560
  *
409
561
  * Every produced candidate must survive the round trip — parse as a rule, come back as the match form
410
- * this seat is offering, AND admit the very command it was minted from. Enforced on BOTH seats,
411
- * fail-closed: a candidate that would not round-trip is silently not offered, since offering an option
412
- * redemption would refuse is worse than offering one fewer.
562
+ * its seat is offering, AND admit the very text it was minted from (the whole command for a single,
563
+ * its own segment for a batch member). Enforced on EVERY seat, fail-closed: a candidate that would
564
+ * not round-trip is silently not offered, since offering an option redemption would refuse is worse
565
+ * than offering one fewer.
413
566
  *
414
567
  * The FORM half of that check is not decoration. A command may end in the rule grammar's own prefix
415
568
  * marker — `rm :*` is a legal thing to type — and wrapping it as an exact rule produces the text
@@ -419,19 +572,29 @@ export interface RuleSuggestion {
419
572
  * for a lexicon command like `git status :*`, emitted twice — once mislabelled, once as the real prefix
420
573
  * candidate). The seat therefore believes the PARSER about what it got back, never its own request.
421
574
  *
422
- * A COMPOUND (`./gradlew build && ./gradlew test`) fills the exact seat — the offered rule spells the
423
- * whole chain and admits exactly it (the shape this seat was missing: the ordinary build invocation is
424
- * a connector chain, and a card that could offer nothing for it made every such command a fresh
425
- * question forever) and, when its FINAL segment yields a prefix body under the very same grammar the
426
- * single-command seat uses, a COMPOUND-PREFIX candidate besides: the leading segments verbatim,
427
- * connectors and all, with the final segment cut down to its body (`cd /tmp && adb pull /sdcard/x .`
428
- * offers `Bash(cd /tmp && adb pull:*)`). A final segment the grammar declines a flag in second
429
- * position, a screened head, one tokenleaves the chain with its exact seat alone, exactly as the
430
- * same shape leaves a simple command. No seat is ever minted off a NON-final segment: those are the
431
- * byte-exact half of the rule, so there is nothing to widen.
575
+ * A COMPOUND (`./gradlew build && ./gradlew test`) fills the exact single — the offered rule spells
576
+ * the whole chain and admits exactly it and a BATCH offer besides: every segment NOT already
577
+ * covered (per `ctx.coverage`) yields its own single-segment best form a prefix when the
578
+ * single-command grammar grants one for that segment's tokens (the interception screens apply
579
+ * unchanged), otherwise the segment's exact deduplicated by rule text in segment order and capped
580
+ * at FIVE (upstream's own batch cap; the overflow is disclosed via `uncoveredSegments` rather than
581
+ * silently truncated away). A segment whose every form the floor refuses simply contributes no
582
+ * member (and stays counted as uncovered — the honest gap). The COMPOUND-PREFIX candidate this seat
583
+ * used to mint here is deliberately NO LONGER OFFERED (design/375 §5.3): the batch takes its card
584
+ * position, while the compound-prefix GRAMMAR remains a fully supported rule family the matcher,
585
+ * the hand-written/edited/import channels and every persisted such rule are untouched.
586
+ *
587
+ * `ctx.coverage` is the per-segment coverage table minted by the SAME store read that adjudicated
588
+ * the call (see {@link segmentCoverageOf} and the gate's engine-stamped seat) — positional over this
589
+ * function's own folded segments, belt-checked by text: a table whose length or segment texts do not
590
+ * line up is discarded whole, and an absent or discarded table reads as ALL-UNCOVERED (fail toward
591
+ * over-offering; every offered rule still passes the validator at redemption, so an over-offer widens
592
+ * nothing).
432
593
  *
433
594
  * Returns an empty array for anything the rule lane cannot speak for (redirections, substitutions,
434
595
  * subshells, backgrounding) — the card then simply carries no "don't ask again" option, which is the
435
596
  * honest answer.
436
597
  */
437
- export declare function suggestRulesForCommand(command: string): RuleSuggestion[];
598
+ export declare function suggestRulesForCommand(command: string, ctx?: {
599
+ readonly coverage?: readonly SegmentCoverage[];
600
+ }): RuleOffer[];
@@ -247,60 +247,118 @@ export function scopeCoversCwd(scope, cwd) {
247
247
  export function isRuleLive(rule) {
248
248
  return rule.adds.length > 0;
249
249
  }
250
- export function findAdmittingRule(rules, call) {
251
- const shape = ruleLaneShapeOf(call.command, MATCH_READING);
252
- if ("reject" in shape)
253
- return undefined;
250
+ export function eligibleContext(rule, call) {
251
+ return rule.tool === call.tool && scopeCoversCwd(rule.scope, call.cwd);
252
+ }
253
+ export function eligiblePersisted(rule, call) {
254
+ return isRuleLive(rule) && eligibleContext(rule, call);
255
+ }
256
+ function firstEligibleAdmittingRule(rules, commandText, call) {
254
257
  for (const rule of rules) {
255
- if (!isRuleLive(rule))
256
- continue;
257
- if (rule.tool !== call.tool)
258
- continue;
259
- if (!scopeCoversCwd(rule.scope, call.cwd))
258
+ if (!eligiblePersisted(rule, call))
260
259
  continue;
261
- if (ruleAdmitsCommand(rule, call.command))
260
+ if (ruleAdmitsCommand(rule, commandText))
262
261
  return rule;
263
262
  }
264
263
  return undefined;
265
264
  }
266
- export function suggestRulesForCommand(command) {
265
+ export function findAdmittingRule(rules, call) {
266
+ const shape = ruleLaneShapeOf(call.command, MATCH_READING);
267
+ if ("reject" in shape)
268
+ return undefined;
269
+ const whole = firstEligibleAdmittingRule(rules, call.command, call);
270
+ if (whole !== undefined)
271
+ return [whole];
272
+ if (shape.segments.length < 2)
273
+ return undefined;
274
+ const covering = [];
275
+ for (const segment of shape.segments) {
276
+ const first = firstEligibleAdmittingRule(rules, segment, call);
277
+ if (first === undefined)
278
+ return undefined;
279
+ if (!covering.includes(first))
280
+ covering.push(first);
281
+ }
282
+ return covering;
283
+ }
284
+ export function segmentCoverageOf(command, rules, call) {
285
+ const folded = foldSpacing(command);
286
+ if (folded === undefined)
287
+ return undefined;
288
+ const shape = ruleLaneShapeOf(folded, MATCH_READING);
289
+ if ("reject" in shape)
290
+ return undefined;
291
+ const proposed = rules.proposed ?? [];
292
+ return shape.segments.map((segment) => ({
293
+ segment: segment.trim(),
294
+ covered: firstEligibleAdmittingRule(rules.persisted, segment, call) !== undefined ||
295
+ proposed.some((p) => eligibleContext({ tool: p.rule.tool, scope: p.scope }, call) && ruleAdmitsCommand(p.rule, segment)),
296
+ }));
297
+ }
298
+ export function suggestRulesForCommand(command, ctx) {
267
299
  const shape = ruleLaneShapeOf(command, MATCH_READING);
268
300
  if ("reject" in shape)
269
301
  return [];
270
302
  const folded = foldSpacing(command);
271
303
  if (folded === undefined)
272
304
  return [];
273
- const out = [];
305
+ const offers = [];
274
306
  const exact = parseAllowRuleText(formatAllowRuleText(folded, "exact"));
275
307
  if ("rule" in exact && exact.rule.match === "exact" && ruleAdmitsCommand(exact.rule, command)) {
276
- out.push({ rule: exact.rule.rule, match: "exact", command: exact.rule.command });
308
+ offers.push({ kind: "single", rule: exact.rule.rule, match: "exact", command: exact.rule.command });
277
309
  }
278
- const offerPrefix = (body) => {
279
- if (body === undefined)
280
- return;
281
- const text = formatAllowRuleText(body, "prefix");
282
- const parsed = parseAllowRuleText(text);
283
- if ("rule" in parsed && parsed.rule.match === "prefix" && ruleAdmitsCommand(parsed.rule, command)) {
284
- out.push({ rule: parsed.rule.rule, match: "prefix", command: parsed.rule.command });
285
- }
286
- };
287
310
  const tokensOf = (s) => s.split(/\s+/).filter((t) => t !== "");
288
311
  const bodyOf = (tokens) => longestReviewedBody(tokens) ?? genericPrefixBody(tokens);
289
- if (out.length === 1) {
290
- if (shape.segments.length === 1) {
291
- offerPrefix(bodyOf(tokensOf(folded)));
292
- }
293
- else {
294
- const foldedShape = ruleLaneShapeOf(folded, MATCH_READING);
295
- if (!("reject" in foldedShape) && foldedShape.segments.length === shape.segments.length) {
296
- const rawLast = foldedShape.segments[foldedShape.segments.length - 1] ?? "";
297
- const lastBody = bodyOf(tokensOf(rawLast));
298
- if (lastBody !== undefined) {
299
- const cut = folded.length - rawLast.length + (rawLast.length - rawLast.trimStart().length);
300
- offerPrefix(folded.slice(0, cut) + lastBody);
312
+ if (shape.segments.length === 1) {
313
+ if (offers.length === 1) {
314
+ const body = bodyOf(tokensOf(folded));
315
+ if (body !== undefined) {
316
+ const parsed = parseAllowRuleText(formatAllowRuleText(body, "prefix"));
317
+ if ("rule" in parsed && parsed.rule.match === "prefix" && ruleAdmitsCommand(parsed.rule, command)) {
318
+ offers.push({ kind: "single", rule: parsed.rule.rule, match: "prefix", command: parsed.rule.command });
301
319
  }
302
320
  }
303
321
  }
322
+ return offers;
323
+ }
324
+ const foldedShape = ruleLaneShapeOf(folded, MATCH_READING);
325
+ if ("reject" in foldedShape || foldedShape.segments.length !== shape.segments.length)
326
+ return offers;
327
+ const segments = foldedShape.segments.map((s) => s.trim());
328
+ const table = ctx?.coverage;
329
+ const alignedTable = table !== undefined && table.length === segments.length && table.every((row, i) => row.segment === segments[i]) ? table : undefined;
330
+ const coveredAt = (i) => alignedTable?.[i]?.covered === true;
331
+ const mintSegmentRule = (segment) => {
332
+ const body = bodyOf(tokensOf(segment));
333
+ if (body !== undefined) {
334
+ const parsed = parseAllowRuleText(formatAllowRuleText(body, "prefix"));
335
+ if ("rule" in parsed && parsed.rule.match === "prefix" && ruleAdmitsCommand(parsed.rule, segment)) {
336
+ return { rule: parsed.rule.rule, match: "prefix", command: parsed.rule.command };
337
+ }
338
+ }
339
+ const segExact = parseAllowRuleText(formatAllowRuleText(segment, "exact"));
340
+ if ("rule" in segExact && segExact.rule.match === "exact" && ruleAdmitsCommand(segExact.rule, segment)) {
341
+ return { rule: segExact.rule.rule, match: "exact", command: segExact.rule.command };
342
+ }
343
+ return undefined;
344
+ };
345
+ const minted = [];
346
+ for (let i = 0; i < segments.length; i++) {
347
+ if (coveredAt(i))
348
+ continue;
349
+ const segment = segments[i];
350
+ const rule = mintSegmentRule(segment);
351
+ if (rule !== undefined && !minted.some((m) => m.rule === rule.rule))
352
+ minted.push({ ...rule, segment });
353
+ }
354
+ const batchRules = minted.slice(0, 5);
355
+ if (batchRules.length > 0) {
356
+ const parsedBatch = batchRules.flatMap((r) => {
357
+ const p = parseAllowRuleText(r.rule);
358
+ return "rule" in p ? [p.rule] : [];
359
+ });
360
+ const uncoveredSegments = segments.filter((segment, i) => !coveredAt(i) && !parsedBatch.some((p) => ruleAdmitsCommand(p, segment))).length;
361
+ offers.push({ kind: "batch", rules: batchRules, uncoveredSegments });
304
362
  }
305
- return out;
363
+ return offers;
306
364
  }
@@ -7,7 +7,7 @@
7
7
  * run-loop 介入的「声明式数据→ToolPolicy」工厂(`createCoarseCommandNamePolicy` 同形态先例)。
8
8
  * core 不读设置文件、不管 source 层级合并 —— Settings-loader 把各层合并成**单一规则表**后编译
9
9
  * **一个** policy(不要按层各编译再组合:combinePolicies 的 fold 里 ask 胜 allow,跨 policy 的
10
- * 显式 allow 规则会被另一 policy 的 ask 吞掉 —— codex 127 审 B3)。
10
+ * 显式 allow 规则会被另一 policy 的 ask 吞掉)。
11
11
  *
12
12
  * 🔴 与 CC 的三处有意差(design/127 r2):
13
13
  * - CC 参数级 generic 道无 allow lane(gnn 只有 deny/ask)—— 我们同样:`allow` + 参数级 content =
@@ -16,7 +16,7 @@
16
16
  * **工厂 validator strict**:降级即 invalid(fail-fast 世界观,写错的规则不能静默变宽)。
17
17
  * - 已知主字段参数规则(如 `Bash(command:…)`)默认 **reject**:通配是 trim 后**整串 lexical**
18
18
  * 匹配,不是 shell AST / 前缀 lane —— `Bash(command:git push*)` 匹配不了 `cd /x && git push`,
19
- * 「以为挡住了」比没挡更危险(codex 127 审 M2)。`primaryFieldGeneric:"allow-lexical"` 显式解锁。
19
+ * 「以为挡住了」比没挡更危险。`primaryFieldGeneric:"allow-lexical"` 显式解锁。
20
20
  */
21
21
  import type { NamedToolPolicy } from "./tool-policy.js";
22
22
  /** [K-PLATFORM-SWEEP] MINOR#11: Bash's REAL non-primary arg names, accepted as generic param rules
@@ -28,7 +28,7 @@ export interface PermissionRule {
28
28
  /** CC 语法:`"Tool"` 或 `"Tool(param:pattern)"`。`Tool()`/`Tool(*)` 归一为裸规则;括号可 `\(`/`\)` 转义。 */
29
29
  rule: string;
30
30
  behavior: "deny" | "ask" | "allow";
31
- /** Provenance(codex 127 审 M7):进 deny/ask 的 message 供审计(e.g. `"policySettings"`)。层级
31
+ /** Provenance:进 deny/ask 的 message 供审计(e.g. `"policySettings"`)。层级
32
32
  * 优先级/tighten-only 语义在 Settings-loader,不在这里。 */
33
33
  source?: string;
34
34
  }
@@ -40,7 +40,7 @@ export interface ParsedPermissionRule {
40
40
  }
41
41
  export interface PermissionRuleIssue {
42
42
  rule: string;
43
- /** `invalid.*` = 语法/语义错;`unsupported.*` = CC 合法但 v1 不支持的 lane(codex 127 审 B5:独立
43
+ /** `invalid.*` = 语法/语义错;`unsupported.*` = CC 合法但 v1 不支持的 lane(独立
44
44
  * 分类,绝不静默 no-op —— `Bash(npm run:*)` 被当 generic 参数规则接受后永不匹配是最危险的失败形态)。 */
45
45
  code: "invalid.empty_tool" | "invalid.paren" | "invalid.param_split" | "invalid.allow_param_rule" | "invalid.primary_field" | "invalid.cap_exceeded" | "unsupported.bash_prefix"
46
46
  /** design/179 D7: a Bash prefix rule under `bashPrefixLane:"rules-store"` — reported so a migration
@@ -65,20 +65,20 @@ export interface PermissionRuleCaps {
65
65
  maxStars: number;
66
66
  /** Scalar arg values longer than this are NOT matched textually — the rule **fail-closes** to its
67
67
  * lane (a deny rule targeting an oversized value denies; an ask rule asks). 租户规则 × 模型巨串
68
- * 的热路径成本上界(codex 127 审 M6)。 */
68
+ * 的热路径成本上界。 */
69
69
  maxScalarValueChars: number;
70
70
  }
71
71
  export interface PermissionRulePolicyOptions {
72
72
  /** Invalid/unsupported rules: `"throw"`(default — 部署作者写错要立刻知道)aggregates ALL issues
73
73
  * into one error; `"skip"` drops them (pair with {@link validatePermissionRules} for a report, and/or
74
- * supply {@link onIssue}). Exception(codex 实现审 M3):`invalid.cap_exceeded` on the WHOLE rule set
74
+ * supply {@link onIssue}). Exception(实现审 M3):`invalid.cap_exceeded` on the WHOLE rule set
75
75
  * (maxRules) throws EVEN under `"skip"` — skipping it would compile an EMPTY table and fail-open. */
76
76
  onInvalidRule?: "throw" | "skip";
77
77
  /** Called once per dropped rule under `"skip"` (audit surface — silent drops are how a deployment
78
78
  * discovers, months later, that its deny never existed). Default: one aggregated `console.warn`. */
79
79
  onIssue?: (issue: PermissionRuleIssue) => void;
80
80
  /** What the policy returns when NO rule matches. Default `"allow"` = 本 policy 无意见(组合语义)。
81
- * default-closed 部署用 `"ask"`/`"deny"` —— 必须在这 INSIDE the policy(codex 127 审 B3:链尾
81
+ * default-closed 部署用 `"ask"`/`"deny"` —— 必须在这 INSIDE the policy(链尾
82
82
  * ask policy 会在 fold 里吞掉本 policy 的显式 allow)。 */
83
83
  defaultAction?: "allow" | "ask" | "deny";
84
84
  /** 已知主字段参数规则(`Bash(command:…)` 等)的处置。默认 `"reject"`(整串 lexical ≠ 前缀/AST,
@@ -136,7 +136,7 @@ export declare function namespacedRuleNameCovers(ruleName: string, toolName: str
136
136
  export declare function isNamespacedCoveringRuleName(name: string): boolean;
137
137
  /** Exported for the lockstep guard only (see {@link FILE_TOOL_PARAMS}) — not part of the rule DSL. */
138
138
  export declare const fileToolParamVocabulary: (canonicalTool: string) => ReadonlySet<string> | undefined;
139
- /** Dry-run 校验/迁移报告(codex 127 审 B5):不 throw,返回全部 issues(含 `unsupported.*` 分类,
139
+ /** Dry-run 校验/迁移报告:不 throw,返回全部 issues(含 `unsupported.*` 分类,
140
140
  * 供从 CC settings 迁移的部署逐条对照)。 */
141
141
  export declare function validatePermissionRules(rules: PermissionRule[], opts?: Pick<PermissionRulePolicyOptions, "primaryFieldGeneric" | "caps" | "bashPrefixLane">): PermissionRuleIssue[];
142
142
  /**
@@ -145,7 +145,7 @@ export declare function validatePermissionRules(rules: PermissionRule[], opts?:
145
145
  * bare allow → `defaultAction`. `check()` is idempotent and side-effect-free (combinePolicies 契约);
146
146
  * all parsing/validation happens HERE, the hot path never re-parses.
147
147
  *
148
- * ⚠️ Composition note (codex 127 审 B3): an `allow` from this policy is "no objection", NOT a
148
+ * ⚠️ Composition note: an `allow` from this policy is "no objection", NOT a
149
149
  * cross-policy override — another policy's ask/deny still outranks it in the combinePolicies fold.
150
150
  * Express default-closed via `defaultAction`, never via a trailing always-ask policy.
151
151
  */
@@ -52,7 +52,7 @@ export interface WorkspaceHandle {
52
52
  snapshotId?: SnapshotId;
53
53
  /** Token for {@link RemoteExecutionEnv.reconnect} when the VM is still running (vs suspended to a snapshot). */
54
54
  sessionToken?: SessionToken;
55
- /** How a checkpointed workspace is restored on resume (1.257.2 hardening, codex review of [500]②):
55
+ /** How a checkpointed workspace is restored on resume (1.257.2 hardening):
56
56
  * `"park_only"` = durable-park-only suspend degrade (non-suspendable env; workspace persists on the
57
57
  * target, resume skips `resumeVM`). Absent = legacy/snapshot handle — resume treats a MISSING
58
58
  * `snapshotId` as corruption (fail-closed) unless the resumed env is itself non-suspendable
@@ -98,7 +98,7 @@ export interface VmLifecycleOptions {
98
98
  /** Abort a hung pause/restore. */
99
99
  abortSignal?: AbortSignal;
100
100
  /**
101
- * [1461]④a (resume workspace-root fidelity) — the CHECKPOINTED {@link WorkspaceHandle} of the workspace
101
+ * (resume workspace-root fidelity) — the CHECKPOINTED {@link WorkspaceHandle} of the workspace
102
102
  * being restored, passed by the engine on the `resumeVM` leg. An adapter SHOULD bind the restored
103
103
  * workspace root (cwd / mount) to `priorHandle.mountPath` rather than its CURRENT deployment
104
104
  * configuration: across a config-knob change window (e.g. a mount-path rotation) the resumed task's
@@ -289,7 +289,7 @@ export interface RemoteExecutionEnv extends ExecutionEnv {
289
289
  *
290
290
  * ⚠️ LOAD-BEARING INVARIANT (opus review 1.257.2): `suspendable:false` on a remote env also asserts
291
291
  * the workspace is EXTERNALLY DURABLE — it persists on the target across the env object's lifetime
292
- * (true for SSH hosts / ADB devices). The durable-park-only suspend degrade (service [500]②) rests
292
+ * (true for SSH hosts / ADB devices). The durable-park-only suspend degrade rests
293
293
  * on this: it skips `suspendVM` and trusts the factory to reconnect to the SAME workspace on resume.
294
294
  * An adapter for a non-suspendable EPHEMERAL backend (e.g. a snapshot-less container torn down with
295
295
  * the transport) must NOT be modeled as `suspendable:false` remote — it would silently take the
@@ -359,7 +359,7 @@ export interface RemoteExecutionEnv extends ExecutionEnv {
359
359
  * expired while suspended). Any failed step → `Err("post_resume_failed")` and the caller MUST destroy the env.
360
360
  *
361
361
  * Note: a resumed guest's network/long-lived connections (git remote / API / registry) are NOT guaranteed to
362
- * survive the snapshot (clay decision 2), so this is also where the VM-internal agent rebuilds those.
362
+ * survive the snapshot (decision 2), so this is also where the VM-internal agent rebuilds those.
363
363
  *
364
364
  * 🔴 Ordering red line (design/48 §5/#6): at-rest encryption of the memory snapshot must be ensured BEFORE
365
365
  * secrets are injected — never let plaintext credentials land in an unencrypted snapshot. That encryption is a
@@ -391,10 +391,10 @@ export interface RemoteExecutionEnv extends ExecutionEnv {
391
391
  * once on task end, and `prepareTask` calls it on a prepare-time throw; a remote impl may also be reaped,
392
392
  * so a defensive double-call must not error or double-bill.
393
393
  *
394
- * 飞轮 [519] contract note: on a NON-isolated env (no `capabilities.isolation` — host lane, SSH host),
394
+ * contract note: on a NON-isolated env (no `capabilities.isolation` — host lane, SSH host),
395
395
  * destroy() is workspace/object-level cleanup and must NOT reap still-running background processes —
396
396
  * that is `disposeBackgroundShells`' job, which honours the timeout/session keep-alive except-list
397
- * ([511]③ monitor timeout anchor). An isolated env (container/VM) naturally takes everything down;
397
+ * (monitor timeout anchor). An isolated env (container/VM) naturally takes everything down;
398
398
  * the Runner's envDying settle accounts for that with an accurate killed receipt beforehand.
399
399
  */
400
400
  destroy(): Promise<void>;
@@ -423,7 +423,7 @@ export interface ExecutionEnvFactoryContext {
423
423
  */
424
424
  isolation?: "worktree";
425
425
  /**
426
- * Blackboard 2026-07-03 (clay dogfood — sub-agents landing in an EMPTY sandbox): the PARENT task's
426
+ * Field-reported (sub-agents landing in an EMPTY sandbox): the PARENT task's
427
427
  * effective working root, threaded (like `isolation`) via the TRUSTED `RunInternals` channel when this
428
428
  * task is a sub-agent (workflow `ctx.agent` / Task delegation). CC parity: a Task sub-agent inherits the
429
429
  * main session's cwd. A single-user/TOC factory SHOULD root the child env here (unless `isolation`
@@ -436,7 +436,7 @@ export interface ExecutionEnvFactoryContext {
436
436
  /**
437
437
  * A **trusted control-plane** factory that mints a per-task {@link ExecutionEnv} (design/48 §5 answers core
438
438
  * gap-a / §7 Q7). Lives on `RunnerDeps` (deployment-level) — NOT on `TaskSpec` — so an untrusted caller can
439
- * never inject its own env and escape the sandbox (design/44 §7 Q4 red line; clay decision 3: the Docker
439
+ * never inject its own env and escape the sandbox (design/44 §7 Q4 red line; decision 3: the Docker
440
440
  * fast-lane must be control-plane-assigned, never task-selectable).
441
441
  *
442
442
  * The remote model is "one container per task", so the factory is invoked once per task. The Runner owns the
@@ -24,10 +24,10 @@ export declare const CC_MODEL_TIER_ALIASES: Readonly<Record<string, string>>;
24
24
  * - a binding may itself be a catalog name or a Model object (resolved through `resolveModel`);
25
25
  * an unknown binding name throws at construction (config error surfaces at boot, not first use).
26
26
  */
27
- /** [1613] the DISPLAY face of a string model ref: a CC tier ALIAS (haiku/sonnet/…) resolves to its
27
+ /** the DISPLAY face of a string model ref: a CC tier ALIAS (haiku/sonnet/…) resolves to its
28
28
  * sema tier name (lite/flash/…) for labels — the alias verbatim reads as a strong claim about a
29
29
  * specific vendor's model while the actual routing may land anywhere ("haiku" showing over a
30
- * DeepSeek run, clay's field report). A non-alias string is the deployment's own model key — its
30
+ * DeepSeek run, an operator field report). A non-alias string is the deployment's own model key — its
31
31
  * honest name — and passes through unchanged. Display only; routing (`expandTiers`) is untouched. */
32
32
  export declare function resolveModelDisplayLabel(ref: string): string;
33
33
  export declare function expandTiers(models: Record<string, Model> | undefined, tiers: Record<string, ModelRef> | undefined): Record<string, Model> | undefined;
@@ -49,7 +49,7 @@ export interface ModelMention {
49
49
  */
50
50
  export declare function parseModelMention(text: string, allowedNames: Iterable<string>): ModelMention;
51
51
  /**
52
- * Role → 档位默认表(clay 2026-07-11:「role 体系用户不感知」)。配了 `RunnerDeps.tiers` 的部署,
52
+ * Role → 档位默认表(裁定 2026-07-11:「role 体系用户不感知」)。配了 `RunnerDeps.tiers` 的部署,
53
53
  * 各 role 未显式配置时自动落到语义档位——用户/壳只感知档位,roles 降级为内部路由层:
54
54
  * - `default` → `pro`(主力档兜底;用户/部署 roles.default / TaskSpec.model 恒可覆盖);
55
55
  * - `summarize` → `flash`(压缩本该便宜;flash 未绑沿降档链到 lite);
@@ -102,6 +102,7 @@ const FALLBACK = {
102
102
  advisor: ["default"],
103
103
  verifier: ["default"],
104
104
  classifier: ["summarize", "default"],
105
+ consolidate: ["summarize"],
105
106
  };
106
107
  function specParts(s) {
107
108
  if (s == null) {
@@ -42,7 +42,7 @@ export interface Stats {
42
42
  toolArg?: string;
43
43
  }>;
44
44
  };
45
- /** TB telemetry B2 (service [397]): engine-mechanism engagement counters. Set by the run loop
45
+ /** TB telemetry B2: engine-mechanism engagement counters. Set by the run loop
46
46
  * only when ≥1 engaged; flows straight through to `TaskResult.stats.mechanisms` via the
47
47
  * public-stats spread. */
48
48
  mechanisms?: {
@@ -102,7 +102,7 @@ export interface ResultFlags {
102
102
  threw: unknown;
103
103
  /** design/99 MF-25: the EFFECTIVE (resolved) model id that served the task — echoed on `TaskResult.model`. */
104
104
  model?: string;
105
- /** RB-368 ([2076]): some spend was accounted while the serving model had NO configured price table
105
+ /** RB-368: some spend was accounted while the serving model had NO configured price table
106
106
  * (no `RunnerDeps.pricing` entry, no `Model.cost`). The numeric total is then a fabricated
107
107
  * undercount — `stats.costMicroUsd`/`costBreakdown` are OMITTED from the public result instead of
108
108
  * reporting a 0 indistinguishable from "declared free". The internal accumulator (budget gate's