@sema-agent/core 5.56.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 (208) hide show
  1. package/CHANGELOG.md +122 -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 +13 -2
  9. package/dist/agents/send-message-tool.js +34 -12
  10. package/dist/agents/subagent-steps.d.ts +1 -1
  11. package/dist/agents/subagent.d.ts +13 -13
  12. package/dist/agents/team.d.ts +15 -6
  13. package/dist/agents/team.js +1 -0
  14. package/dist/agents/tool-filter.d.ts +2 -2
  15. package/dist/agents/verify.d.ts +1 -1
  16. package/dist/bench/metrics.d.ts +35 -35
  17. package/dist/brain/anthropic.js +15 -5
  18. package/dist/brain/circuit-breaker.js +2 -1
  19. package/dist/brain/degrading.d.ts +1 -1
  20. package/dist/brain/degrading.js +4 -1
  21. package/dist/brain/errors.d.ts +3 -3
  22. package/dist/brain/failover.js +16 -1
  23. package/dist/brain/open-responses.js +15 -5
  24. package/dist/brain/openai.js +16 -5
  25. package/dist/brain/reasoning.d.ts +2 -2
  26. package/dist/brain/repetition.d.ts +1 -1
  27. package/dist/brain/request-params.d.ts +30 -27
  28. package/dist/brain/request-params.js +1 -7
  29. package/dist/brain/route-adjudicator.d.ts +190 -0
  30. package/dist/brain/route-adjudicator.js +189 -0
  31. package/dist/brain/route-conformance.d.ts +55 -0
  32. package/dist/brain/route-conformance.js +136 -0
  33. package/dist/brain/routing.js +8 -3
  34. package/dist/brain/status-sink.d.ts +2 -2
  35. package/dist/brain/stream-shared.d.ts +1 -1
  36. package/dist/config/catalog.d.ts +5 -5
  37. package/dist/core/arg-summary.d.ts +4 -4
  38. package/dist/core/ask-class.d.ts +2 -2
  39. package/dist/core/ask-question.d.ts +1 -1
  40. package/dist/core/auto-compaction.d.ts +15 -15
  41. package/dist/core/auto-mode.d.ts +5 -5
  42. package/dist/core/background-agent-store.d.ts +20 -20
  43. package/dist/core/background-shell.d.ts +4 -4
  44. package/dist/core/checkpoint-store.d.ts +35 -27
  45. package/dist/core/context-edit.d.ts +1 -1
  46. package/dist/core/context-guard.d.ts +1 -1
  47. package/dist/core/exec-output-tail.d.ts +6 -6
  48. package/dist/core/file-snapshot-store.d.ts +8 -8
  49. package/dist/core/git-worktree-env.d.ts +3 -3
  50. package/dist/core/governance-codes.js +2 -0
  51. package/dist/core/hooks.d.ts +73 -33
  52. package/dist/core/hooks.js +87 -25
  53. package/dist/core/image-downsample.d.ts +1 -1
  54. package/dist/core/locked-config.d.ts +1 -1
  55. package/dist/core/lsp.d.ts +1 -1
  56. package/dist/core/mailbox-store.d.ts +1 -1
  57. package/dist/core/mcp.d.ts +3 -3
  58. package/dist/core/mcp.js +4 -4
  59. package/dist/core/memory-engine/consolidation-driver.d.ts +207 -0
  60. package/dist/core/memory-engine/consolidation-driver.js +378 -0
  61. package/dist/core/memory-engine/consolidation.d.ts +46 -2
  62. package/dist/core/memory-engine/consolidation.js +1 -0
  63. package/dist/core/memory-engine/data-plane.d.ts +1 -1
  64. package/dist/core/memory-engine/distiller.d.ts +550 -0
  65. package/dist/core/memory-engine/distiller.js +598 -0
  66. package/dist/core/memory-engine/dual-root.d.ts +1 -1
  67. package/dist/core/memory-engine/engine.d.ts +62 -8
  68. package/dist/core/memory-engine/engine.js +40 -4
  69. package/dist/core/memory-engine/file-backend.d.ts +1 -1
  70. package/dist/core/memory-engine/index.d.ts +4 -2
  71. package/dist/core/memory-engine/index.js +4 -2
  72. package/dist/core/memory-engine/origin-clearance.d.ts +1 -1
  73. package/dist/core/memory-engine/scope-contract.d.ts +4 -4
  74. package/dist/core/memory-engine/sync-client.d.ts +16 -16
  75. package/dist/core/memory-engine/sync.d.ts +4 -4
  76. package/dist/core/memory-recall.d.ts +1 -1
  77. package/dist/core/memory.d.ts +2 -2
  78. package/dist/core/permission-rule-consent.d.ts +230 -36
  79. package/dist/core/permission-rule-consent.js +258 -54
  80. package/dist/core/permission-rule-model.d.ts +295 -97
  81. package/dist/core/permission-rule-model.js +132 -41
  82. package/dist/core/permission-rules.d.ts +9 -9
  83. package/dist/core/remote-env.d.ts +8 -8
  84. package/dist/core/roles.d.ts +3 -3
  85. package/dist/core/roles.js +1 -0
  86. package/dist/core/runner/assemble-result.d.ts +2 -2
  87. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  88. package/dist/core/runner/memory-consolidation-driver.d.ts +49 -0
  89. package/dist/core/runner/memory-consolidation-driver.js +60 -0
  90. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  91. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  92. package/dist/core/runner/prepare-task.d.ts +21 -21
  93. package/dist/core/runner/prepare-task.js +54 -18
  94. package/dist/core/runner/prepare-workspace-restore.d.ts +2 -2
  95. package/dist/core/runner/runtask.d.ts +15 -12
  96. package/dist/core/runner/runtask.js +48 -0
  97. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  98. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  99. package/dist/core/runner/tool-disclosure.d.ts +2 -2
  100. package/dist/core/runner/turn-attachments.d.ts +11 -11
  101. package/dist/core/scheduler.d.ts +10 -5
  102. package/dist/core/secret-env.d.ts +1 -1
  103. package/dist/core/sensitive-path-policy.d.ts +1 -1
  104. package/dist/core/session-policy-store.d.ts +2 -2
  105. package/dist/core/session-reconcile.d.ts +2 -2
  106. package/dist/core/session-store.d.ts +3 -3
  107. package/dist/core/session.d.ts +1 -1
  108. package/dist/core/shutdown-debug.d.ts +2 -2
  109. package/dist/core/side-query.d.ts +14 -7
  110. package/dist/core/spec-contract.d.ts +1 -1
  111. package/dist/core/store-contracts/contract-harness.d.ts +2 -2
  112. package/dist/core/store-contracts/contract-kit-version.d.ts +2 -2
  113. package/dist/core/store-contracts/mailbox-store-contract.d.ts +1 -1
  114. package/dist/core/store-contracts/mailbox-store-contract.js +1 -1
  115. package/dist/core/task-notification.d.ts +5 -5
  116. package/dist/core/task-registry-agent.d.ts +12 -12
  117. package/dist/core/task-registry-monitor.d.ts +1 -1
  118. package/dist/core/task-registry-shared.d.ts +41 -41
  119. package/dist/core/task-registry.d.ts +12 -12
  120. package/dist/core/tool-detach.d.ts +2 -2
  121. package/dist/core/tool-errors.d.ts +3 -3
  122. package/dist/core/tool-policy.d.ts +55 -28
  123. package/dist/core/tool-result-budget.d.ts +1 -1
  124. package/dist/core/tool-result-store.d.ts +2 -2
  125. package/dist/core/tools.d.ts +1 -1
  126. package/dist/core/trace.d.ts +26 -23
  127. package/dist/core/types.d.ts +155 -70
  128. package/dist/core/untrusted-egress.d.ts +1 -1
  129. package/dist/core/untrusted-text.d.ts +7 -7
  130. package/dist/core/wiring-manifest.d.ts +5 -5
  131. package/dist/core/workflow-journal-store.d.ts +14 -14
  132. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  133. package/dist/core/workflow-run-store-contract.js +1 -1
  134. package/dist/core/workflow-run-store.d.ts +4 -4
  135. package/dist/engine/compaction/compaction.d.ts +3 -3
  136. package/dist/engine/compaction/utils.d.ts +2 -2
  137. package/dist/engine/execution-env/kill-tree.d.ts +1 -1
  138. package/dist/engine/execution-env/node-execution-env.d.ts +8 -8
  139. package/dist/engine/harness/agent-harness.d.ts +6 -6
  140. package/dist/engine/harness/agent-harness.js +26 -1
  141. package/dist/engine/harness/messages.d.ts +1 -1
  142. package/dist/engine/harness/types.d.ts +15 -11
  143. package/dist/engine/llm/types.d.ts +79 -14
  144. package/dist/engine/loop/agent-loop.d.ts +3 -3
  145. package/dist/engine/loop/types.d.ts +4 -4
  146. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  147. package/dist/engine/session/import-validate.d.ts +1 -1
  148. package/dist/engine/session/log-digest.d.ts +1 -1
  149. package/dist/engine/session/memory-repo.d.ts +2 -2
  150. package/dist/engine/session/session.d.ts +4 -4
  151. package/dist/fixtures/index.d.ts +4 -4
  152. package/dist/index.d.ts +8 -4
  153. package/dist/index.js +5 -2
  154. package/dist/internal/llm.d.ts +1 -1
  155. package/dist/orchestration/goal.d.ts +1 -1
  156. package/dist/orchestration/run-spec.d.ts +1 -1
  157. package/dist/orchestration/run-workflow-tool.d.ts +12 -12
  158. package/dist/orchestration/workflow-governance.d.ts +4 -4
  159. package/dist/orchestration/workflow-observe.d.ts +1 -1
  160. package/dist/orchestration/workflow-script-runner.d.ts +1 -1
  161. package/dist/orchestration/workflow-script-store.d.ts +9 -9
  162. package/dist/orchestration/workflow-size-guideline.d.ts +1 -1
  163. package/dist/orchestration/workflow-types.d.ts +5 -5
  164. package/dist/orchestration/workflow.d.ts +10 -10
  165. package/dist/prompt-assembly/artifact-store.d.ts +1 -1
  166. package/dist/prompt-assembly/artifact.d.ts +1 -1
  167. package/dist/prompt-assembly/assemble.d.ts +1 -1
  168. package/dist/prompt-assembly/composer.d.ts +2 -2
  169. package/dist/prompt-assembly/epoch.d.ts +2 -2
  170. package/dist/prompt-assembly/event-registry.d.ts +1 -1
  171. package/dist/prompt-assembly/explain.d.ts +3 -3
  172. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  173. package/dist/prompt-assembly/turn-snapshot.d.ts +4 -4
  174. package/dist/prompt-assembly/types.d.ts +12 -12
  175. package/dist/prompts/coordinator.d.ts +1 -1
  176. package/dist/prompts/default.d.ts +10 -10
  177. package/dist/prompts/default.js +2 -0
  178. package/dist/prompts/simple-sections.d.ts +3 -3
  179. package/dist/prompts/supervisor.d.ts +2 -2
  180. package/dist/scenarios/full-body.d.ts +3 -3
  181. package/dist/scenarios/scenario-registry.d.ts +6 -2
  182. package/dist/scenarios/scenario-registry.js +4 -2
  183. package/dist/stores/cc/sidecar-transcript.d.ts +3 -3
  184. package/dist/stores/file/fs-atomic.d.ts +2 -2
  185. package/dist/stores/file/index.d.ts +1 -1
  186. package/dist/stores/file/session-store.d.ts +2 -2
  187. package/dist/stores/file/workflow-journal-store.d.ts +4 -4
  188. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  189. package/dist/tools/fs/encoding.d.ts +4 -4
  190. package/dist/tools/fs/fs-bash.d.ts +3 -3
  191. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  192. package/dist/tools/fs/fs-shared.d.ts +6 -6
  193. package/dist/tools/fs/index.d.ts +2 -2
  194. package/dist/tools/fs/index.js +8 -1
  195. package/dist/tools/fs/notebook.d.ts +1 -1
  196. package/dist/tools/fs/pdf.d.ts +1 -1
  197. package/dist/tools/fs/read-deny.d.ts +1 -1
  198. package/dist/tools/fs/safety.d.ts +9 -9
  199. package/dist/tools/fs/search.d.ts +2 -2
  200. package/dist/tools/monitor.d.ts +3 -3
  201. package/dist/tools/scheduler-tools.js +28 -6
  202. package/dist/tools/task-list.d.ts +2 -2
  203. package/dist/tools/web.d.ts +19 -4
  204. package/dist/tools/web.js +8 -2
  205. package/dist/tools/worktree.d.ts +5 -5
  206. package/dist/tools/worktree.js +2 -2
  207. package/package.json +1 -1
  208. package/test/export-surface.snapshot.json +74 -3
@@ -20,26 +20,12 @@ export const SHELL_RESERVED_WORDS = new Set([
20
20
  "function", "if", "in", "select", "then", "time", "until", "while",
21
21
  ]);
22
22
  export const SUGGESTION_LEXICON = [
23
- "git status", "git log", "git diff", "git show", "git branch", "git checkout", "git switch",
24
- "git add", "git commit", "git push", "git pull", "git fetch", "git merge", "git rebase",
25
- "git tag", "git blame", "git describe", "git cherry-pick", "git restore", "git reset",
26
- "git rev-parse", "git ls-files", "git grep",
27
23
  "git stash list", "git stash show", "git stash push", "git stash pop", "git stash drop",
28
24
  "git remote show", "git worktree list", "git submodule update", "git submodule status",
29
- "npm run", "npm test", "npm ci", "npm ls", "npm view", "npm outdated", "npm audit",
30
- "npm pack", "npm publish", "npm version", "npm why",
31
- "pnpm run", "pnpm test", "pnpm ls", "pnpm outdated", "pnpm audit", "pnpm why",
32
- "yarn run", "yarn test", "yarn workspaces list",
33
- "cargo build", "cargo test", "cargo run", "cargo check", "cargo clippy", "cargo fmt",
34
- "cargo doc", "cargo tree", "cargo bench", "cargo update", "cargo metadata",
35
- "go build", "go test", "go vet", "go fmt", "go doc",
25
+ "yarn workspaces list",
36
26
  "go mod tidy", "go mod download", "go mod verify", "go mod graph",
37
- "docker ps", "docker images", "docker logs", "docker inspect", "docker build", "docker pull",
38
- "docker push", "docker stop", "docker start", "docker restart", "docker rm", "docker rmi",
39
27
  "docker compose up", "docker compose down", "docker compose ps", "docker compose logs",
40
28
  "docker compose build", "docker compose pull",
41
- "kubectl get", "kubectl describe", "kubectl logs", "kubectl apply", "kubectl delete",
42
- "kubectl diff", "kubectl explain", "kubectl top",
43
29
  "kubectl rollout status", "kubectl rollout restart", "kubectl rollout history",
44
30
  "gh pr view", "gh pr list", "gh pr diff", "gh pr checks", "gh pr status", "gh pr create",
45
31
  "gh pr merge", "gh issue view", "gh issue list", "gh issue create", "gh repo view",
@@ -48,6 +34,31 @@ export const SUGGESTION_LEXICON = [
48
34
  "gh search repos", "gh search issues", "gh search prs", "gh search code",
49
35
  ];
50
36
  const LEXICON_BODIES = SUGGESTION_LEXICON.map((b) => b.split(" "));
37
+ const GENERIC_SUBCOMMAND_WORD = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
38
+ const SCREENED_HEAD_NAMES = new Set([...BARE_INTERPRETER_NAMES].map((n) => n.toLowerCase()));
39
+ const TRAILING_DOTS = /\.+$/;
40
+ const EXECUTABLE_SUFFIX = /\.(?:exe|com|bat|cmd)$/;
41
+ const PROPOSABLE_HEAD_NAME = /^[A-Za-z0-9._+-]+$/;
42
+ function screenedHeadName(head) {
43
+ let name = commandBasename(head).toLowerCase();
44
+ for (;;) {
45
+ const peeled = name.replace(TRAILING_DOTS, "").replace(EXECUTABLE_SUFFIX, "");
46
+ if (peeled === name || peeled === "")
47
+ return name;
48
+ name = peeled;
49
+ }
50
+ }
51
+ function genericPrefixBody(tokens) {
52
+ const head = tokens[0];
53
+ const second = tokens[1];
54
+ if (head === undefined || second === undefined)
55
+ return undefined;
56
+ if (!PROPOSABLE_HEAD_NAME.test(commandBasename(head)))
57
+ return undefined;
58
+ if (SCREENED_HEAD_NAMES.has(screenedHeadName(head)))
59
+ return undefined;
60
+ return GENERIC_SUBCOMMAND_WORD.test(second) ? `${head} ${second}` : undefined;
61
+ }
51
62
  function longestReviewedBody(tokens) {
52
63
  let best;
53
64
  for (const words of LEXICON_BODIES) {
@@ -151,6 +162,9 @@ export function escapeForDisclosure(value) {
151
162
  });
152
163
  return escaped.length <= DISCLOSED_RULE_TEXT_MAX_CHARS ? escaped : `${escaped.slice(0, DISCLOSED_RULE_TEXT_MAX_CHARS)}…`;
153
164
  }
165
+ export function hasUnrenderableCharacters(text) {
166
+ return CONTROL_CHARS_RE.test(text);
167
+ }
154
168
  export function parseAllowRuleText(text, opts) {
155
169
  if (text.length > MAX_RULE_TEXT_CHARS) {
156
170
  return reject("invalid.too_long", `rule text exceeds ${MAX_RULE_TEXT_CHARS} characters`);
@@ -178,12 +192,11 @@ export function parseAllowRuleText(text, opts) {
178
192
  ? reject("invalid.empty_command", `rule "${text}" names no command`)
179
193
  : reject("invalid.not_simple_command", `rule "${text}" is not a command this lane can name (${shape.reject})`);
180
194
  }
181
- if (match === "prefix" && shape.segments.length > 1) {
182
- return reject("invalid.not_simple_command", `prefix rule "${text}" names more than one command — a prefix admits anything appended to its body, so only the exact form may name a connector chain`);
183
- }
184
- const head = commandBasename(shape.names[0] ?? "");
195
+ const head = commandBasename(shape.names[shape.names.length - 1] ?? "");
185
196
  if (match === "prefix" && BARE_INTERPRETER_NAMES.has(head) && opts?.direction !== "tighten") {
186
- return reject("invalid.bare_interpreter_prefix", `prefix rule "${text}" is headed by the interpreter "${head}" — such a rule authorizes running arbitrary programs, which one approval click cannot be read as having granted (an exact rule naming the whole command line is accepted)`);
197
+ return reject("invalid.bare_interpreter_prefix", shape.segments.length > 1
198
+ ? `prefix rule "${text}" leaves its final segment open-ended under the interpreter "${head}" — such a rule authorizes running arbitrary programs, which one approval click cannot be read as having granted (an exact rule naming the whole command line is accepted)`
199
+ : `prefix rule "${text}" is headed by the interpreter "${head}" — such a rule authorizes running arbitrary programs, which one approval click cannot be read as having granted (an exact rule naming the whole command line is accepted)`);
187
200
  }
188
201
  const command = foldSpacing(body);
189
202
  if (command === undefined) {
@@ -209,7 +222,10 @@ function admitsUnder(rule, command, reading) {
209
222
  return false;
210
223
  if (rule.match === "exact")
211
224
  return folded === rule.command;
212
- if (shape.segments.length > 1)
225
+ const bodyShape = ruleLaneShapeOf(rule.command, reading);
226
+ if ("reject" in bodyShape)
227
+ return false;
228
+ if (shape.segments.length !== bodyShape.segments.length)
213
229
  return false;
214
230
  return folded === rule.command || folded.startsWith(rule.command + " ");
215
231
  }
@@ -231,43 +247,118 @@ export function scopeCoversCwd(scope, cwd) {
231
247
  export function isRuleLive(rule) {
232
248
  return rule.adds.length > 0;
233
249
  }
234
- export function findAdmittingRule(rules, call) {
235
- const shape = ruleLaneShapeOf(call.command, MATCH_READING);
236
- if ("reject" in shape)
237
- 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) {
238
257
  for (const rule of rules) {
239
- if (!isRuleLive(rule))
258
+ if (!eligiblePersisted(rule, call))
240
259
  continue;
241
- if (rule.tool !== call.tool)
242
- continue;
243
- if (!scopeCoversCwd(rule.scope, call.cwd))
244
- continue;
245
- if (ruleAdmitsCommand(rule, call.command))
260
+ if (ruleAdmitsCommand(rule, commandText))
246
261
  return rule;
247
262
  }
248
263
  return undefined;
249
264
  }
250
- 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) {
251
299
  const shape = ruleLaneShapeOf(command, MATCH_READING);
252
300
  if ("reject" in shape)
253
301
  return [];
254
302
  const folded = foldSpacing(command);
255
303
  if (folded === undefined)
256
304
  return [];
257
- const out = [];
305
+ const offers = [];
258
306
  const exact = parseAllowRuleText(formatAllowRuleText(folded, "exact"));
259
307
  if ("rule" in exact && exact.rule.match === "exact" && ruleAdmitsCommand(exact.rule, command)) {
260
- 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 });
309
+ }
310
+ const tokensOf = (s) => s.split(/\s+/).filter((t) => t !== "");
311
+ const bodyOf = (tokens) => longestReviewedBody(tokens) ?? genericPrefixBody(tokens);
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 });
319
+ }
320
+ }
321
+ }
322
+ return offers;
261
323
  }
262
- if (out.length === 1 && shape.segments.length === 1) {
263
- const body = longestReviewedBody(folded.split(" "));
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));
264
333
  if (body !== undefined) {
265
- const text = formatAllowRuleText(body, "prefix");
266
- const parsed = parseAllowRuleText(text);
267
- if ("rule" in parsed && parsed.rule.match === "prefix" && ruleAdmitsCommand(parsed.rule, command)) {
268
- out.push({ rule: parsed.rule.rule, match: "prefix", command: parsed.rule.command });
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 };
269
337
  }
270
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 });
271
362
  }
272
- return out;
363
+ return offers;
273
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
@@ -22,7 +22,7 @@ import type { Prepared } from "./prepare-task.js";
22
22
  /**
23
23
  * Opt-in working-file attachments (LONGRUN-2 / CC 198 post-compact restore parity).
24
24
  *
25
- * Blackboard 2026-07-03 (CC parity): post-compact file re-read is DEFAULT ON (CC hard-codes it;
25
+ * CC parity: post-compact file re-read is DEFAULT ON (CC hard-codes it;
26
26
  * LONGRUN-2 measured ≈2.3 extra read round-trips per compaction without it). `false` opts out; an
27
27
  * object customizes caps. Selection set = most recently READ (CC readFileState semantics) with a
28
28
  * modified-files fallback inside maybeCompact. Returns `undefined` when attachments are off or the
@@ -52,7 +52,7 @@ export declare function buildWorkingFileAttachments(spec: TaskSpec, prepared: Pr
52
52
  */
53
53
  export declare function forkContextOption(prepared: Prepared, disable: boolean): Pick<MaybeCompactOptions, "forkContext">;
54
54
  /**
55
- * RB-31 ([1068]) — compaction-boundary center-candidate adoption. Absent (an empty object, NOT a
55
+ * RB-31 — compaction-boundary center-candidate adoption. Absent (an empty object, NOT a
56
56
  * `centerAdoption: undefined` key) = restate as before; a boundary re-pin hashes the same artifact
57
57
  * identity on every lane.
58
58
  */
@@ -106,7 +106,7 @@ export declare function projectStaleToolResults(context: Context, cfg: ResolvedS
106
106
  * write-then-discard round-trip per stale result per turn. Caller owns the Set's lifetime. */
107
107
  writtenRefs: Set<string>): Promise<Context>;
108
108
  /**
109
- * MF-18 修② ([496]③) — fidelity disclosure: the summary INPUT was clipped to the compaction model's
109
+ * MF-18 修② — fidelity disclosure: the summary INPUT was clipped to the compaction model's
110
110
  * window. Fires pre-call, so the disclosure survives a failed attempt. All three lanes emit the SAME
111
111
  * frame; the end-of-task lane passes `spec.tracer ?? deps.tracer` / `spec.taskId ?? sessionId`
112
112
  * explicitly because `RunState.telemetry` (which holds exactly those two resolved values) is not in
@@ -0,0 +1,49 @@
1
+ /**
2
+ * design/376 片② — the RUNNER convenience half of the consolidation driver: resolve the model
3
+ * seat out of a deployment's `RunnerDeps` (explicit chat seat wins; else the `consolidate` role
4
+ * chain) into the options `runMemoryConsolidationDriver` eats.
5
+ *
6
+ * Role resolution lives HERE and not in the engine-layer verb on purpose (design/339 §1.1 + the
7
+ * d2 ruling): the driver function's own seat is an explicit chat function — models, roles and
8
+ * tiers are runner vocabulary. The chain is `consolidate → summarize → coded refusal`, and the
9
+ * refusal is deliberate: one consolidation run reads the whole library (~10⁵ prompt tokens), so
10
+ * an unset role silently escalating to the most expensive `default` model would be the
11
+ * bad-value-silence family. A deployment that already declared a cheap `summarize` tier has made
12
+ * exactly the cost choice this role wants, so that hop stays; pointing `roles.consolidate` at the
13
+ * main model is a legal explicit choice.
14
+ */
15
+ import type { Model } from "../../internal/llm.js";
16
+ import type { Brain, ModelRoles, ModelRef, TaskSpec } from "../types.js";
17
+ import { type DistillerChatFn } from "../memory-engine/distiller.js";
18
+ import type { RunMemoryConsolidationOptions } from "../memory-engine/consolidation-driver.js";
19
+ import type { MemoryConsolidationOptions } from "../memory-engine/consolidation.js";
20
+ import type { LlmDistillerContract } from "../memory-engine/distiller.js";
21
+ /** The slice of `RunnerDeps` this resolver reads (pass the deps object itself). */
22
+ export interface MemoryConsolidationDriverDeps {
23
+ brain: Brain;
24
+ models?: Record<string, Model>;
25
+ tiers?: Record<string, ModelRef>;
26
+ roles?: ModelRoles;
27
+ memoryConsolidation?: MemoryConsolidationOptions;
28
+ memoryConsolidationDriver?: {
29
+ chat?: {
30
+ baseUrl: string;
31
+ apiKey: string;
32
+ model: string;
33
+ } | DistillerChatFn;
34
+ model?: string;
35
+ contract?: LlmDistillerContract;
36
+ maxCycles?: number;
37
+ mintRepairBudget?: number;
38
+ };
39
+ /** Optional per-model auth hook (the TaskSpec-shaped seat) for the role-resolved path. */
40
+ getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
41
+ }
42
+ /**
43
+ * Resolve a deployment's driver options: `{ chat, model }` from the explicit seat or the role
44
+ * chain, plus the deployment knobs (contract / maxCycles / mintRepairBudget) and the protocol
45
+ * parameters (`memoryConsolidation`) threaded through verbatim. The result feeds
46
+ * `runMemoryConsolidationDriver(engine, scope, resolved)` directly; every bad value refuses
47
+ * coded here or in the verb's own screen (#123), never folds to a default.
48
+ */
49
+ export declare function resolveMemoryConsolidationDriver(deps: MemoryConsolidationDriverDeps): RunMemoryConsolidationOptions;
@@ -0,0 +1,60 @@
1
+ import { brainToRuntime } from "../runtime.js";
2
+ import { expandTiers, roleModelIfSet } from "../roles.js";
3
+ import { isAliasModelId, openAiCompatChatSeat } from "../memory-engine/distiller.js";
4
+ function seatRefusal(message) {
5
+ const e = new Error(message);
6
+ e.code = "config.memory_consolidation_driver";
7
+ return e;
8
+ }
9
+ export function resolveMemoryConsolidationDriver(deps) {
10
+ const seat = deps.memoryConsolidationDriver;
11
+ let chat;
12
+ let model;
13
+ if (typeof seat?.chat === "function") {
14
+ if (typeof seat.model !== "string" || seat.model.trim() === "" || isAliasModelId(seat.model)) {
15
+ throw seatRefusal(`memoryConsolidationDriver.chat is a bare function, so memoryConsolidationDriver.model must carry the EXPLICIT model id it serves (got ${JSON.stringify(seat.model)}).`);
16
+ }
17
+ chat = seat.chat;
18
+ model = seat.model;
19
+ }
20
+ else if (seat?.chat !== undefined) {
21
+ chat = openAiCompatChatSeat(seat.chat);
22
+ model = seat.chat.model;
23
+ }
24
+ else {
25
+ const models = expandTiers(deps.models, deps.tiers);
26
+ const resolved = roleModelIfSet("consolidate", {}, { models, roles: deps.roles }) ?? roleModelIfSet("summarize", {}, { models, roles: deps.roles });
27
+ if (resolved === undefined) {
28
+ throw seatRefusal('memory consolidation has no model seat: configure RunnerDeps.memoryConsolidationDriver.chat (an endpoint triple or a chat function), or declare roles.consolidate / roles.summarize (a tier-expanded catalog\'s "flash" binding also satisfies the summarize hop). The chain deliberately does NOT fall back to the default main model — a whole-library distillation must not silently ride the most expensive seat.');
29
+ }
30
+ if (isAliasModelId(resolved.id)) {
31
+ throw seatRefusal(`the resolved consolidation model id ${JSON.stringify(resolved.id)} is an alias spelling — an alias can be re-pointed upstream and would move the run with no trace in the archive; bind an explicit id.`);
32
+ }
33
+ model = resolved.id;
34
+ const runtime = brainToRuntime(deps.brain);
35
+ chat = async (req) => {
36
+ const auth = await deps.getApiKeyAndHeaders?.(resolved);
37
+ const message = await runtime.completeSimple(resolved, { systemPrompt: req.system, messages: [{ role: "user", content: req.user, timestamp: Date.now() }] }, { temperature: req.temperature, maxTokens: req.maxTokens, apiKey: auth?.apiKey, headers: auth?.headers });
38
+ if (message.stopReason === "error" || message.stopReason === "aborted") {
39
+ throw new Error(message.errorMessage ?? `model completion ${message.stopReason}`);
40
+ }
41
+ return {
42
+ content: message.content
43
+ .filter((c) => c.type === "text")
44
+ .map((c) => c.text)
45
+ .join(""),
46
+ finishReason: message.stopReason,
47
+ usage: { promptTokens: message.usage.input, completionTokens: message.usage.output },
48
+ model: resolved.id,
49
+ };
50
+ };
51
+ }
52
+ return {
53
+ chat,
54
+ model,
55
+ ...(seat?.contract !== undefined ? { contract: seat.contract } : {}),
56
+ ...(seat?.maxCycles !== undefined ? { maxCycles: seat.maxCycles } : {}),
57
+ ...(seat?.mintRepairBudget !== undefined ? { mintRepairBudget: seat.mintRepairBudget } : {}),
58
+ ...(deps.memoryConsolidation !== undefined ? { consolidation: deps.memoryConsolidation } : {}),
59
+ };
60
+ }
@@ -27,7 +27,7 @@ export declare const DEFAULT_CONSOLIDATION_MAX_NOTES = 50;
27
27
  export declare const DEFAULT_CONSOLIDATION_TIMEOUT_SEC = 30;
28
28
  /** Normalize a note body for the EXACT-restatement check in the `<lo` near-dup cheap path. Case- + whitespace-
29
29
  * insensitive ONLY (lower-case, collapse whitespace runs, trim); it PRESERVES every symbol/punctuation so it can
30
- * NOT false-equate semantically-distinct facts. (service [365] falsification-style: the earlier `[^\p{L}\p{N}]`-stripping
30
+ * NOT false-equate semantically-distinct facts. (falsification-style: the earlier `[^\p{L}\p{N}]`-stripping
31
31
  * normalization made "C#"≡"C", "delta +5"≡"delta -5", "100%"≡"100" — and a textual-CONTAINMENT test on top of it
32
32
  * also mis-judged "rate limit 1000" ⊇ "rate limit 100" (word boundary). A fire-on-every-note, no-LLM path must not
33
33
  * make a semantic-subset call, so the cheap path now folds ONLY an exact restatement; every real near-dup defers
@@ -46,17 +46,17 @@ export declare function limitConfigError(code: string, message: string): Error &
46
46
  */
47
47
  export declare function resolveTaskLimits(limits: TaskLimits | undefined): TaskLimits | undefined;
48
48
  /**
49
- * R2 双形轴(clay 追加令 2026-07-18): CC 2.1.212's fable-variant prompt gate (b9e —
49
+ * R2 双形轴(追加令 2026-07-18): CC 2.1.212's fable-variant prompt gate (b9e —
50
50
  * `fable_5_mitigations` capability / claude-mythos-5), ORTHOGONAL to the simple/classic profile.
51
51
  * sema is BYOM, so the id may carry provider prefixes ("anthropic/claude-fable-5",
52
52
  * "openrouter/anthropic/claude-fable-5"): BOUNDARY-AWARE family match on the last path segment
53
- * (codex 统一复审 F3 — raw substring classified "vendor/not-claude-fable-5" and "claude-mythos-50"
53
+ * (统一复审 F3 — raw substring classified "vendor/not-claude-fable-5" and "claude-mythos-50"
54
54
  * as fable), case-normalized. Recognition set = CC's _Nr (startsWith "claude-fable-") + b9e
55
55
  * (mythos-5). R3 system sections fork on the resulting fact.
56
56
  */
57
57
  export declare function isFableFamilyModelId(id: string): boolean;
58
58
  /**
59
- * RB-50 (CC 2.1.220 启示①, clay 2026-07-25): the SINGLE decision point for the two prompt-shape axes.
59
+ * RB-50 (CC 2.1.220 启示①, 2026-07-25): the SINGLE decision point for the two prompt-shape axes.
60
60
  * Both were resolved in separate places with different mechanisms — `promptProfile` off a TaskSpec field,
61
61
  * `fableMitigations` off a raw model-id prefix test — so "which shape does this task speak" had no one
62
62
  * place to read. CC 2.1.220's counterpart is a model-registry `capabilities` array (one table drives