@sema-agent/core 7.40.0 → 7.41.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 (137) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +10 -2
  3. package/dist/agents/agent-transcript-tool.js +52 -26
  4. package/dist/agents/list-agents-tool.d.ts +17 -2
  5. package/dist/agents/list-agents-tool.js +28 -16
  6. package/dist/agents/peer-session-drain.d.ts +3 -1
  7. package/dist/agents/peer-session-drain.js +1 -0
  8. package/dist/agents/subagent-steps.d.ts +12 -2
  9. package/dist/agents/subagent-steps.js +8 -4
  10. package/dist/agents/subagent.d.ts +1 -17
  11. package/dist/agents/subagent.js +12 -24
  12. package/dist/core/agent-definition.d.ts +19 -40
  13. package/dist/core/agent-state.d.ts +91 -0
  14. package/dist/core/agent-state.js +79 -0
  15. package/dist/core/ask-origin.d.ts +28 -6
  16. package/dist/core/ask-origin.js +9 -5
  17. package/dist/core/ask-unresolvable-notice.d.ts +27 -4
  18. package/dist/core/ask-unresolvable-notice.js +8 -0
  19. package/dist/core/auto-promote.d.ts +14 -27
  20. package/dist/core/auto-promote.js +5 -5
  21. package/dist/core/checkpoint-store.d.ts +19 -20
  22. package/dist/core/decision-receipt.d.ts +5 -2
  23. package/dist/core/decision-receipt.js +1 -1
  24. package/dist/core/engine-notice.d.ts +7 -1
  25. package/dist/core/gate-crash-trace.d.ts +1 -1
  26. package/dist/core/gate-fold.js +7 -3
  27. package/dist/core/gate-lanes.js +22 -3
  28. package/dist/core/governance-codes.d.ts +3 -1
  29. package/dist/core/governance-codes.js +3 -0
  30. package/dist/core/hooks.d.ts +20 -6
  31. package/dist/core/hooks.js +28 -28
  32. package/dist/core/mcp-tool-meta.d.ts +24 -0
  33. package/dist/core/mcp-tool-meta.js +17 -0
  34. package/dist/core/mcp.d.ts +1 -6
  35. package/dist/core/mcp.js +6 -7
  36. package/dist/core/mode-policy.d.ts +25 -0
  37. package/dist/core/mode-policy.js +8 -0
  38. package/dist/core/protocol-table.d.ts +3 -0
  39. package/dist/core/protocol-table.js +3 -0
  40. package/dist/core/reminder-mint.d.ts +21 -1
  41. package/dist/core/reminder-mint.js +20 -0
  42. package/dist/core/retired-keys.d.ts +2 -1
  43. package/dist/core/retired-keys.js +13 -2
  44. package/dist/core/rule-denial-sentence.d.ts +2 -2
  45. package/dist/core/runner/call-hook-stations.d.ts +106 -0
  46. package/dist/core/runner/call-hook-stations.js +118 -0
  47. package/dist/core/runner/contracts.d.ts +35 -25
  48. package/dist/core/runner/gate-exit.d.ts +5 -1
  49. package/dist/core/runner/gate-exit.js +10 -6
  50. package/dist/core/runner/prepare-delegation-surface.d.ts +1 -0
  51. package/dist/core/runner/prepare-delegation-surface.js +4 -4
  52. package/dist/core/runner/prepare-gate-stations.d.ts +8 -3
  53. package/dist/core/runner/prepare-gate-stations.js +13 -70
  54. package/dist/core/runner/prepare-inherited-gate.d.ts +6 -3
  55. package/dist/core/runner/prepare-inherited-gate.js +5 -3
  56. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  57. package/dist/core/runner/prepare-park-ask.js +4 -1
  58. package/dist/core/runner/prepare-policy-chain.d.ts +6 -5
  59. package/dist/core/runner/prepare-policy-chain.js +14 -25
  60. package/dist/core/runner/prepare-project-context.d.ts +15 -16
  61. package/dist/core/runner/prepare-project-context.js +15 -11
  62. package/dist/core/runner/prepare-run-refs.d.ts +8 -8
  63. package/dist/core/runner/prepare-run-refs.js +3 -3
  64. package/dist/core/runner/prepare-safety-scan.js +3 -3
  65. package/dist/core/runner/prepare-suspend-saga.d.ts +2 -1
  66. package/dist/core/runner/prepare-suspend-saga.js +2 -1
  67. package/dist/core/runner/prepare-task.js +14 -12
  68. package/dist/core/runner/prepare-turn-wiring.d.ts +3 -1
  69. package/dist/core/runner/prepare-turn-wiring.js +16 -13
  70. package/dist/core/runner/resume-admission.js +2 -1
  71. package/dist/core/runner/resume-apply.js +15 -26
  72. package/dist/core/runner/resume-checkpoint-screen.js +3 -0
  73. package/dist/core/runner/resume-preflight.js +3 -3
  74. package/dist/core/runner/run-leg.js +61 -47
  75. package/dist/core/runner/runtask.d.ts +1 -1
  76. package/dist/core/runner/session-rule-policy.d.ts +7 -19
  77. package/dist/core/runner/session-rule-policy.js +1 -2
  78. package/dist/core/runner/skill-base-dir.d.ts +10 -0
  79. package/dist/core/runner/skill-base-dir.js +4 -0
  80. package/dist/core/runner/synthetic-tools.d.ts +11 -17
  81. package/dist/core/runner/synthetic-tools.js +7 -8
  82. package/dist/core/runner-deps.d.ts +3 -1
  83. package/dist/core/safety-axis-vocab.d.ts +2 -2
  84. package/dist/core/safety-axis-vocab.js +2 -0
  85. package/dist/core/session-policy-store.d.ts +63 -11
  86. package/dist/core/session-policy-store.js +52 -6
  87. package/dist/core/skill-grant.d.ts +239 -0
  88. package/dist/core/skill-grant.js +313 -0
  89. package/dist/core/skills-directory.d.ts +5 -34
  90. package/dist/core/skills-directory.js +9 -58
  91. package/dist/core/task-registry-agent.d.ts +14 -2
  92. package/dist/core/task-registry-agent.js +23 -8
  93. package/dist/core/task-registry-shared.d.ts +6 -0
  94. package/dist/core/task-registry.d.ts +16 -0
  95. package/dist/core/task-registry.js +19 -6
  96. package/dist/core/task-spec.d.ts +10 -0
  97. package/dist/core/tool-face.d.ts +3 -2
  98. package/dist/core/tool-name-aliases.d.ts +38 -12
  99. package/dist/core/tool-name-aliases.js +16 -0
  100. package/dist/core/tool-name-readings.d.ts +6 -2
  101. package/dist/core/tool-name-readings.js +6 -4
  102. package/dist/core/tool-policy.d.ts +2 -2
  103. package/dist/core/tool-registry.d.ts +1 -4
  104. package/dist/core/tool-registry.js +0 -3
  105. package/dist/core/tool-spec.d.ts +11 -13
  106. package/dist/core/tools.d.ts +1 -1
  107. package/dist/core/trace.d.ts +15 -0
  108. package/dist/core/types.d.ts +1 -1
  109. package/dist/engine/harness/agent-harness.d.ts +12 -1
  110. package/dist/engine/harness/agent-harness.js +17 -13
  111. package/dist/engine/loop/agent-loop.d.ts +16 -1
  112. package/dist/engine/loop/agent-loop.js +9 -15
  113. package/dist/engine/loop/types.d.ts +2 -0
  114. package/dist/index.d.ts +9 -4
  115. package/dist/index.js +4 -2
  116. package/dist/stores/file/adoption/adopt.js +23 -16
  117. package/dist/stores/file/index.d.ts +8 -2
  118. package/dist/stores/file/index.js +2 -2
  119. package/dist/stores/file/mailbox-store.js +2 -2
  120. package/dist/stores/file/permission-rule-adopt.js +10 -1
  121. package/dist/stores/file/permission-rule-store.d.ts +6 -4
  122. package/dist/stores/file/permission-rule-store.js +127 -59
  123. package/dist/stores/file/session-policy-store.d.ts +86 -14
  124. package/dist/stores/file/session-policy-store.js +135 -118
  125. package/dist/stores/file/task-list-store.d.ts +5 -0
  126. package/dist/stores/file/task-list-store.js +18 -4
  127. package/dist/stores/file/usage-window-store.d.ts +9 -1
  128. package/dist/stores/file/usage-window-store.js +47 -33
  129. package/dist/tools/fs/safety.d.ts +1 -1
  130. package/package.json +1 -1
  131. package/test/export-surface.snapshot.json +45 -5
  132. package/wire-changes/core@7.40.1.json +39 -0
  133. package/wire-changes/core@7.41.0.json +1865 -0
  134. package/dist/core/runner/active-skill-scope.d.ts +0 -58
  135. package/dist/core/runner/active-skill-scope.js +0 -133
  136. package/dist/core/skill-tool-specifier.d.ts +0 -80
  137. package/dist/core/skill-tool-specifier.js +0 -57
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { isAbsolute, join, relative } from "node:path";
3
3
  import { physicalPathOf } from "./physical-path.js";
4
- import { isSkillSpecifierEnforced, parseSkillToolEntry } from "./skill-tool-specifier.js";
4
+ import { assertNoRetiredKeys } from "./retired-keys.js";
5
5
  import { thrownText } from "./thrown-value.js";
6
6
  const SKILL_FILE = "SKILL.md";
7
7
  const RESOURCE_DIRS = ["assets", "references", "scripts"];
@@ -285,7 +285,6 @@ function isDecodableText(bytes) {
285
285
  function errText(err) {
286
286
  return thrownText(err);
287
287
  }
288
- const ALL_TOOLS_WILDCARD = "*";
289
288
  function splitToolNames(declared) {
290
289
  let text = declared.trim();
291
290
  if (text.startsWith("[") && text.endsWith("]"))
@@ -319,52 +318,11 @@ function splitToolNames(declared) {
319
318
  flush();
320
319
  return names;
321
320
  }
322
- function manifestFromAllowedTools(declared, disallowed, skillName, deployedTools, warn) {
323
- const names = [];
324
- const seen = new Set();
325
- for (const n of splitToolNames(declared)) {
326
- const entry = parseSkillToolEntry(n);
327
- const key = entry.specifier === undefined ? entry.name : `${entry.name}(${entry.specifier})`;
328
- if (seen.has(key))
329
- continue;
330
- seen.add(key);
331
- names.push(n);
332
- }
333
- for (const n of names) {
334
- const entry = parseSkillToolEntry(n);
335
- if (entry.specifier !== undefined && !isSkillSpecifierEnforced(entry.name)) {
336
- warn({
337
- code: "allowed_tool_pattern_unsupported",
338
- skill: skillName,
339
- detail: `allowed-tools entry "${n}" narrows a tool whose calls cannot be matched against a command pattern — only shell tools carry one, so the entry is not enforced and the tool is DISABLED inside this skill frame. Use the bare tool name to allow it fully.`,
340
- });
341
- }
342
- }
343
- if (names.length === 0)
344
- return undefined;
345
- if (names.some((n) => n === ALL_TOOLS_WILDCARD))
346
- return undefined;
347
- let allowTools = names;
348
- if (deployedTools !== undefined) {
349
- const mounted = new Set(deployedTools);
350
- allowTools = names.filter((n) => mounted.has(parseSkillToolEntry(n).name));
351
- for (const n of names) {
352
- if (!mounted.has(parseSkillToolEntry(n).name)) {
353
- warn({
354
- code: "allowed_tool_not_mounted",
355
- skill: skillName,
356
- detail: `allowed-tools names "${n}", which this deployment does not mount — dropped (a skill declaration may only narrow capability, never add a tool)`,
357
- });
358
- }
359
- }
360
- }
361
- if (disallowed.length > 0) {
362
- const denied = new Set(disallowed);
363
- allowTools = allowTools.filter((n) => !denied.has(parseSkillToolEntry(n).name));
364
- }
365
- return { allowTools, lineageId: `skill:${skillName}` };
366
- }
321
+ const RETIRED_SKILLS_DIRECTORY_OPTION_KEYS = Object.freeze({
322
+ deployedTools: "the loader copies a skill's `allowed-tools` / `disallowed-tools` into `SkillSpec.allowedTools` / `disallowedTools` as written and the run's grant table judges them (an entry naming a tool nothing mounts simply never matches) — drop the option",
323
+ });
367
324
  export function createSkillsFromDirectory(dir, options = {}) {
325
+ assertNoRetiredKeys(options, RETIRED_SKILLS_DIRECTORY_OPTION_KEYS, "SkillsDirectoryOptions");
368
326
  const warn = (w) => {
369
327
  options.onWarning?.(w);
370
328
  };
@@ -438,16 +396,8 @@ export function createSkillsFromDirectory(dir, options = {}) {
438
396
  warn({ code: "description_too_long", skill: name, detail: `\`description\` is ${description.length} chars, over the ${DESCRIPTION_MAX_CHARS} cap — skipped` });
439
397
  continue;
440
398
  }
441
- const allowed = parsed.fields.get("allowed-tools");
442
- const disallowed = splitToolNames(parsed.fields.get("disallowed-tools") ?? "");
443
- const manifest = allowed === undefined ? undefined : manifestFromAllowedTools(allowed, disallowed, name, options.deployedTools, warn);
444
- if (disallowed.length > 0 && manifest === undefined) {
445
- warn({
446
- code: "disallowed_tools_unenforced",
447
- skill: name,
448
- detail: `disallowed-tools names ${disallowed.map((n) => `"${n}"`).join(", ")}, but this skill produced no allowlist to subtract from (allowed-tools is absent or claims every tool) — the deny declaration is NOT enforced (express it in the task's own tool policy)`,
449
- });
450
- }
399
+ const allowedTools = splitToolNames(parsed.fields.get("allowed-tools") ?? "");
400
+ const disallowedTools = splitToolNames(parsed.fields.get("disallowed-tools") ?? "");
451
401
  const baseDir = skillPhysicalRoot(skillDir, name, warn);
452
402
  const files = baseDir === undefined ? [] : readAttachments(skillDir, baseDir, name, budget, warn);
453
403
  skills.push({
@@ -455,7 +405,8 @@ export function createSkillsFromDirectory(dir, options = {}) {
455
405
  description,
456
406
  content: parsed.body,
457
407
  ...(baseDir !== undefined ? { baseDir } : {}),
458
- ...(manifest !== undefined ? { manifest } : {}),
408
+ ...(allowedTools.length > 0 ? { allowedTools } : {}),
409
+ ...(disallowedTools.length > 0 ? { disallowedTools } : {}),
459
410
  ...(files.length > 0 ? { files } : {}),
460
411
  });
461
412
  }
@@ -1,6 +1,8 @@
1
1
  import { type BackgroundAgentRecord, type BackgroundAgentStore } from "./background-agent-store.js";
2
2
  import { type StopSource, type TaskAccess, type TaskRetrievalStatus, type UnifiedTaskOutput, type UnifiedTaskResult, type BackgroundAgentTaskHandle, type DurableAgentCore, type ParkedClaimTicket, type RegisterBackgroundAgentInput } from "./task-registry-shared.js";
3
3
  import { type ToolResultStore } from "./tool-result-store.js";
4
+ import { type AgentState } from "./agent-state.js";
5
+ import type { CheckpointSeat } from "./checkpoint-seat.js";
4
6
  /**
5
7
  * Subagent transcript persistence (delegation entry caps) — the ACTIVE handles of one delegation
6
8
  * tree in THIS process's registry: `background_agent` handles whose status is running/pending
@@ -493,6 +495,9 @@ export interface AgentPollDetailsInput {
493
495
  errorRetryAfterMs?: number;
494
496
  resultIsPartial?: boolean;
495
497
  completionId?: string;
498
+ /** The agent's current state ({@link import("./agent-state.js").readAgentState}) — rides as
499
+ * `agentState` when given; the poll arms always give it, a serving layer may omit it. */
500
+ agentState?: AgentState;
496
501
  }
497
502
  /** PUBLIC (see the doc above): a serving layer that renders its own protocol projection of a row
498
503
  * builds the structured facts HERE, so the status gates and the untrusted-text fencing apply to it
@@ -510,7 +515,11 @@ export declare function buildAgentPollDetails(input: AgentPollDetailsInput): Uni
510
515
  * caller has ALREADY read and authorized — it reads no store and applies no predicate, so a
511
516
  * deployment passes every row through
512
517
  * {@link import("./background-agent-store.js").canAccessAgentRecord} before it gets here. */
513
- export declare function serveDurableAgentRowLane(row: BackgroundAgentRecord): UnifiedTaskResult;
518
+ export declare function serveDurableAgentRowLane(row: BackgroundAgentRecord,
519
+ /** The row's current state ({@link import("./agent-state.js").readAgentState} over
520
+ * {@link import("./agent-state.js").agentStateSourceOfRecord}(row)): rendered as the `state:` line and
521
+ * carried as `details.agentState`. Absent ⇒ neither appears (a serving layer that does not read it). */
522
+ agentState?: AgentState): UnifiedTaskResult;
514
523
  /**
515
524
  * design/158 S1 (RB-205-B 半) — spill the FULL text behind a CLIPPED background_agent result to the
516
525
  * offload store, and append a ref-disclosure so the middle `clipTaskOutput` drops is not gone for good.
@@ -553,5 +562,8 @@ oneShot?: boolean,
553
562
  store?: ToolResultStore,
554
563
  /** design/158 S1 — the caller's session id, folded into the minted ref for global uniqueness (same
555
564
  * role `sessionId` plays at every other {@link buildToolResultRef} call site). */
556
- sessionId?: string): Promise<UnifiedTaskResult>;
565
+ sessionId?: string,
566
+ /** The run's checkpoint seat — the store a parked row's approval card is read from
567
+ * ({@link import("./agent-state.js").readAgentState}). Absent ⇒ a parked row reads `approval unreadable`. */
568
+ checkpointSeat?: CheckpointSeat): Promise<UnifiedTaskResult>;
557
569
  export declare function stopBackgroundAgentLane(core: DurableAgentCore, handle: BackgroundAgentTaskHandle): Promise<UnifiedTaskResult>;
@@ -8,6 +8,7 @@ import { boundedRedactedSummary } from "./untrusted-egress.js";
8
8
  import { mintCompletionId, commitCompletionIdIfEmpty, clipTaskOutput, assertOwnership, sleepPollStep, alreadyTerminalStopNote, canAccess, normalizeAgentName, closestName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR, } from "./task-registry-shared.js";
9
9
  import { buildToolResultRef, OFFLOAD_TOOL_NAME, toolResultProvenanceOf } from "./tool-result-store.js";
10
10
  import { thrownLabel } from "./thrown-value.js";
11
+ import { agentStateSourceOfHandle, agentStateSummary, readAgentState } from "./agent-state.js";
11
12
  export function activeDelegationHandlesLane(core, scope, rootSessionId) {
12
13
  const out = [];
13
14
  for (const h of core.handles.values()) {
@@ -1292,18 +1293,21 @@ export function buildAgentPollDetails(input) {
1292
1293
  ...(failed && input.errorRetryAfterMs !== undefined ? { retryAfterMs: input.errorRetryAfterMs } : {}),
1293
1294
  ...(input.resultIsPartial === true ? { partial_result: true } : {}),
1294
1295
  ...(input.completionId !== undefined ? { completionId: input.completionId } : {}),
1296
+ ...(input.agentState !== undefined ? { agentState: input.agentState } : {}),
1295
1297
  };
1296
1298
  }
1297
- export function serveDurableAgentRowLane(row) {
1299
+ export function serveDurableAgentRowLane(row, agentState) {
1300
+ const stateLine = agentState !== undefined ? `state: ${agentStateSummary(agentState)}\n` : "";
1298
1301
  if (row.status === "parked") {
1299
1302
  return {
1300
1303
  content: delimitUntrusted(`TaskOutput ${row.handle}`, `status: parked
1301
- The agent is durably suspended, waiting for an approval decision. It resumes when the pending approval is decided (durable approval inbox), or lands failed if the approval expires.`),
1304
+ ${stateLine}The agent is durably suspended, waiting for an approval decision. It resumes when the pending approval is decided (durable approval inbox), or lands failed if the approval expires.`),
1302
1305
  details: buildAgentPollDetails({
1303
1306
  taskId: row.handle,
1304
1307
  status: "parked",
1305
1308
  retrievalStatus: "success",
1306
1309
  ...(row.seq !== undefined ? { seq: row.seq } : {}),
1310
+ ...(agentState !== undefined ? { agentState } : {}),
1307
1311
  }),
1308
1312
  };
1309
1313
  }
@@ -1311,7 +1315,7 @@ The agent is durably suspended, waiting for an approval decision. It resumes whe
1311
1315
  ? ` (error_kind: ${row.errorKind}, retryable: ${row.errorRetryable}${row.errorRetryAfterMs !== undefined ? `, retry_after_ms: ${row.errorRetryAfterMs}` : ""})`
1312
1316
  : "";
1313
1317
  const body = `status: ${row.status}
1314
- ${row.error ? `error: ${row.error}${kindClause}
1318
+ ${stateLine}${row.error ? `error: ${row.error}${kindClause}
1315
1319
  ` : ""}${row.finalOutput ? `--- result${row.resultIsPartial ? " (partial — produced before the task was stopped)" : ""} ---
1316
1320
  ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}`;
1317
1321
  return {
@@ -1328,6 +1332,7 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
1328
1332
  ...(row.errorRetryAfterMs !== undefined ? { errorRetryAfterMs: row.errorRetryAfterMs } : {}),
1329
1333
  ...(row.resultIsPartial === true ? { resultIsPartial: true } : {}),
1330
1334
  ...(row.completionId !== undefined ? { completionId: row.completionId } : {}),
1335
+ ...(agentState !== undefined ? { agentState } : {}),
1331
1336
  }),
1332
1337
  ...(row.status === "failed" ? { isError: true } : {}),
1333
1338
  };
@@ -1360,21 +1365,26 @@ export async function spillClippedAgentResult(handle, full, clipped, store, sess
1360
1365
  handle.spillRef = ref;
1361
1366
  return disclose(ref);
1362
1367
  }
1363
- export async function pollBackgroundAgentLane(handle, deadline, signal, oneShot, store, sessionId) {
1368
+ export async function pollBackgroundAgentLane(handle, deadline, signal, oneShot, store, sessionId, checkpointSeat) {
1364
1369
  while (handle.status === "running" && deadline !== undefined && Date.now() < deadline && !signal?.aborted) {
1365
1370
  await sleepPollStep(deadline, signal);
1366
1371
  }
1367
1372
  const running = handle.status === "running";
1368
1373
  const retrieval = running && deadline !== undefined && Date.now() >= deadline ? "timeout" : "success";
1369
1374
  if (handle.status === "parked") {
1375
+ const parkedSource = agentStateSourceOfHandle(handle);
1376
+ const parkedSeq = handle.cycleSeq;
1377
+ const agentState = await readAgentState(parkedSource, checkpointSeat, Date.now());
1370
1378
  return {
1371
1379
  content: delimitUntrusted(`TaskOutput ${handle.id}`, `status: parked
1380
+ state: ${agentStateSummary(agentState)}
1372
1381
  The agent is durably suspended, waiting for an approval decision. It resumes when the pending approval is decided (durable approval inbox), or lands failed if the approval expires.`),
1373
1382
  details: buildAgentPollDetails({
1374
1383
  taskId: handle.id,
1375
1384
  status: "parked",
1376
1385
  retrievalStatus: "success",
1377
- ...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
1386
+ ...(parkedSeq !== undefined ? { seq: parkedSeq } : {}),
1387
+ agentState,
1378
1388
  }),
1379
1389
  };
1380
1390
  }
@@ -1390,20 +1400,24 @@ The agent is durably suspended, waiting for an approval decision. It resumes whe
1390
1400
  cycleSeq: handle.cycleSeq,
1391
1401
  stoppedBy: handle.stoppedBy,
1392
1402
  completionId: handle.completionId,
1403
+ updatedAt: handle.updatedAt,
1393
1404
  };
1405
+ const stateRead = readAgentState(agentStateSourceOfHandle({ status: snap.status, updatedAt: snap.updatedAt }), checkpointSeat, Date.now());
1406
+ const agentState = stateRead instanceof Promise ? await stateRead : stateRead;
1394
1407
  const fullResult = snap.result ? (handle.resultFull ?? snap.result) : undefined;
1395
1408
  const resultText = fullResult !== undefined ? await spillClippedAgentResult(handle, fullResult, clipTaskOutput(fullResult, handle.outputFile), store, sessionId) : undefined;
1396
1409
  const kindClause = snap.status === "failed" && snap.errorKind !== undefined && snap.errorRetryable !== undefined
1397
1410
  ? ` (error_kind: ${snap.errorKind}, retryable: ${snap.errorRetryable}${snap.errorRetryAfterMs !== undefined ? `, retry_after_ms: ${snap.errorRetryAfterMs}` : ""})`
1398
1411
  : "";
1412
+ const stateLine = `state: ${agentStateSummary(agentState)}\n`;
1399
1413
  const body = running
1400
1414
  ? oneShot === true
1401
1415
  ? `status: running
1402
- This is a ONE-SHOT submission — there is no later turn for a background notification to land in, so do NOT end your turn expecting one. Actively wait instead: TaskOutput({ task_id: "${handle.id}", block: true }). If it is still running after the wait, wait again (bounded) rather than ending the turn, or write out your best available answer now if you are near your own time budget.`
1416
+ ${stateLine}This is a ONE-SHOT submission — there is no later turn for a background notification to land in, so do NOT end your turn expecting one. Actively wait instead: TaskOutput({ task_id: "${handle.id}", block: true }). If it is still running after the wait, wait again (bounded) rather than ending the turn, or write out your best available answer now if you are near your own time budget.`
1403
1417
  : `status: running
1404
- The agent is still working — you will be notified when it completes.`
1418
+ ${stateLine}The agent is still working — you will be notified when it completes.`
1405
1419
  : `status: ${snap.status}
1406
- ${snap.error ? `error: ${snap.error}${kindClause}
1420
+ ${stateLine}${snap.error ? `error: ${snap.error}${kindClause}
1407
1421
  ` : ""}${snap.result ? `--- result${snap.resultIsPartial ? " (partial — produced before the task was stopped)" : ""} ---
1408
1422
  ${resultText}` : "(no result text)"}`;
1409
1423
  return {
@@ -1420,6 +1434,7 @@ ${resultText}` : "(no result text)"}`;
1420
1434
  ...(snap.errorRetryAfterMs !== undefined ? { errorRetryAfterMs: snap.errorRetryAfterMs } : {}),
1421
1435
  ...(snap.resultIsPartial === true ? { resultIsPartial: true } : {}),
1422
1436
  ...(snap.completionId !== undefined ? { completionId: snap.completionId } : {}),
1437
+ agentState,
1423
1438
  }),
1424
1439
  ...(snap.status === "failed" ? { isError: true } : {}),
1425
1440
  };
@@ -72,6 +72,12 @@ export interface UnifiedTaskOutput {
72
72
  * not contractually required to stay one); absent when the underlying task hasn't reached a terminal
73
73
  * settle yet, or on a pre-P1-3 core build that never minted one. */
74
74
  completionId?: string;
75
+ /** A background agent's current state (status word, last activity, the approval card a parked row
76
+ * waits on) — {@link import("./agent-state.js").readAgentState}, the one reading AgentTranscript and
77
+ * ListAgents render too. Present on every background_agent POLL answer that resolved a row (live,
78
+ * durable terminal, durable parked, durable still-running); absent on other lanes, on not-found, and on
79
+ * the stop face. */
80
+ agentState?: import("./agent-state.js").AgentState;
75
81
  details?: unknown;
76
82
  }
77
83
  /**
@@ -9,6 +9,8 @@ import { type SemaTaskType, type StopSource, type SemaTaskStatus, type TaskRetri
9
9
  export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
10
10
  export type { ParkedClaimTicket, RegisterBackgroundAgentInput } from "./task-registry-shared.js";
11
11
  import type { RegisterBackgroundAgentInput, ParkedClaimTicket } from "./task-registry-shared.js";
12
+ import { type AgentStateSource } from "./agent-state.js";
13
+ import type { CheckpointSeat } from "./checkpoint-seat.js";
12
14
  export type { SemaTaskType, StopSource, SemaTaskStatus, TaskRetrievalStatus, UnifiedTaskOutput, SemaTaskHandle, TaskAccess, MonitorTimers, RegisterMonitorInput, RegisterWorkflowInput, };
13
15
  export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
14
16
  export interface TaskKind {
@@ -78,6 +80,10 @@ export interface TaskPollOptions {
78
80
  * good (see {@link import("./task-registry-agent.js").spillClippedAgentResult}). Absent (a
79
81
  * deployment that never wired a tool-result store) ⇒ legal degrade, byte-identical to pre-S1 output. */
80
82
  toolResultStore?: ToolResultStore;
83
+ /** The run's checkpoint seat: a PARKED background_agent row's approval card is read from it for the
84
+ * answer's `agentState` ({@link import("./agent-state.js").readAgentState}). Absent ⇒ the card reads
85
+ * `unreadable`; nothing else changes. */
86
+ checkpointSeat?: CheckpointSeat;
81
87
  }
82
88
  export interface TaskStopOptions {
83
89
  workflowStore?: WorkflowRunStore;
@@ -116,6 +122,9 @@ export interface TaskToolOptions extends TaskAccess {
116
122
  * spills to — see {@link TaskPollOptions.toolResultStore} (forwarded verbatim into `pollTask`).
117
123
  * Absent ⇒ legal degrade, byte-identical to pre-S1 output. */
118
124
  toolResultStore?: ToolResultStore;
125
+ /** The run's checkpoint seat — see {@link TaskPollOptions.checkpointSeat} (forwarded verbatim into
126
+ * `pollTask`). The first-party mount passes the same seat the Agent tool parks children into. */
127
+ checkpointSeat?: CheckpointSeat;
119
128
  }
120
129
  /**
121
130
  * RB-49 B-3 (2026-07-25): the row-metadata projection `getAccessibleTask` returns. Was spelled out as a
@@ -281,7 +290,14 @@ export declare class TaskRegistry {
281
290
  skipSessionScoped?: boolean;
282
291
  sessionScopedOnly?: boolean;
283
292
  }): number;
293
+ /** A durable row's answer with its state: only a parked row waits on the card read; any other row is
294
+ * answered in place. */
284
295
  private serveDurableAgentRow;
296
+ /** The row facts {@link import("./agent-state.js").readAgentState} reads for a background agent this
297
+ * caller can see — the live handle only (a durable-only row is projected by its reader from the record,
298
+ * {@link import("./agent-state.js").agentStateSourceOfRecord}). Same access guard as a poll; unknown and
299
+ * out-of-scope ids read identically (undefined). */
300
+ agentStateSource(taskId: string, access: TaskAccess): AgentStateSource | undefined;
285
301
  resolveBackgroundAgentByName(name: string, access: TaskAccess, opts?: {
286
302
  preferRunning?: boolean;
287
303
  }): {
@@ -14,6 +14,7 @@ import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_MISSING_ID_MESS
14
14
  import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, recordBackgroundAgentOrgAdmissionLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, backgroundAgentCycleSeqLane, activeDelegationHandlesLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
15
15
  import { toolFace } from "./tool-catalog-entries.js";
16
16
  import { thrownText } from "./thrown-value.js";
17
+ import { agentStateSourceOfHandle, agentStateSourceOfRecord, agentStateSummary, readAgentState } from "./agent-state.js";
17
18
  export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
18
19
  function shellRowRetained(h) {
19
20
  return retainsBackgroundProcesses({ marked: h.retained === true, env: envDeclaresRetain(h.env) });
@@ -188,8 +189,15 @@ export class TaskRegistry {
188
189
  abortBackgroundAgentsForOwner(access, opts) {
189
190
  return abortBackgroundAgentsForOwnerLane(this.core, access, opts);
190
191
  }
191
- serveDurableAgentRow(row) {
192
- return serveDurableAgentRowLane(row);
192
+ serveDurableAgentRow(row, checkpointSeat) {
193
+ const read = readAgentState(agentStateSourceOfRecord(row), checkpointSeat, Date.now());
194
+ return read instanceof Promise ? read.then((agentState) => serveDurableAgentRowLane(row, agentState)) : serveDurableAgentRowLane(row, read);
195
+ }
196
+ agentStateSource(taskId, access) {
197
+ const h = this.handles.get(taskId);
198
+ if (!h || h.type !== "background_agent" || !canAccess(h, access))
199
+ return undefined;
200
+ return agentStateSourceOfHandle(h);
193
201
  }
194
202
  resolveBackgroundAgentByName(name, access, opts) {
195
203
  return resolveBackgroundAgentByNameLane(this.core, name, access, opts);
@@ -238,8 +246,8 @@ export class TaskRegistry {
238
246
  runningBackgroundAgentLabels(access) {
239
247
  return runningBackgroundAgentLabelsLane(this.core, access);
240
248
  }
241
- async pollBackgroundAgent(handle, deadline, signal, oneShot, toolResultStore, sessionId) {
242
- return pollBackgroundAgentLane(handle, deadline, signal, oneShot, toolResultStore, sessionId);
249
+ async pollBackgroundAgent(handle, deadline, signal, oneShot, toolResultStore, sessionId, checkpointSeat) {
250
+ return pollBackgroundAgentLane(handle, deadline, signal, oneShot, toolResultStore, sessionId, checkpointSeat);
243
251
  }
244
252
  async stopBackgroundAgent(handle) {
245
253
  return stopBackgroundAgentLane(this.core, handle);
@@ -863,7 +871,7 @@ export class TaskRegistry {
863
871
  if (handle.type === "background_bash")
864
872
  return this.pollBackgroundBash(handle, taskId, access, opts.filter, deadline, opts.signal);
865
873
  if (handle.type === "background_agent")
866
- return this.pollBackgroundAgent(handle, deadline, opts.signal, opts.oneShot, opts.toolResultStore, access.sessionId);
874
+ return this.pollBackgroundAgent(handle, deadline, opts.signal, opts.oneShot, opts.toolResultStore, access.sessionId, opts.checkpointSeat);
867
875
  if (handle.type === "monitor")
868
876
  return this.pollMonitor(handle, opts.filter, deadline, opts.signal);
869
877
  return this.pollWorkflow(handle, access, deadline, opts.signal);
@@ -896,9 +904,12 @@ export class TaskRegistry {
896
904
  if (row.status === "running" && row.writerId === this.writerId)
897
905
  break;
898
906
  if (row.status !== "running")
899
- return this.serveDurableAgentRow(row);
907
+ return this.serveDurableAgentRow(row, opts.checkpointSeat);
900
908
  if (deadline === undefined || Date.now() >= deadline || opts.signal?.aborted) {
909
+ const stateRead = readAgentState(agentStateSourceOfRecord(row), opts.checkpointSeat, Date.now());
910
+ const agentState = stateRead instanceof Promise ? await stateRead : stateRead;
901
911
  const body = `status: unknown (not running on this engine instance)
912
+ state: ${agentStateSummary(agentState)}
902
913
  The durable record is still marked running (last update ${new Date(row.updatedAt).toISOString()}), but this engine instance is not executing it — the outcome is unknown here. If its host process is gone, a retention sweep (staleRunningMaxAgeMs) settles the record as interrupted.`;
903
914
  return {
904
915
  content: delimitUntrusted(`TaskOutput ${row.handle}`, body),
@@ -910,6 +921,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
910
921
  retrieval_status: "not_ready",
911
922
  error: "not_local",
912
923
  code: "not_local",
924
+ agentState,
913
925
  },
914
926
  };
915
927
  }
@@ -1321,6 +1333,7 @@ export function createTaskOutputTool(opts) {
1321
1333
  signal: ctx.signal,
1322
1334
  oneShot: opts.oneShot,
1323
1335
  toolResultStore: opts.toolResultStore,
1336
+ checkpointSeat: opts.checkpointSeat,
1324
1337
  });
1325
1338
  const { type: taskType, ...rest } = r.details;
1326
1339
  return {
@@ -965,6 +965,16 @@ export interface TaskSpec {
965
965
  * end-user-authored skills are responsible for vetting them: a skill IS instructions by design.
966
966
  */
967
967
  skills?: SkillSpec[];
968
+ /**
969
+ * The skills THIS run's human input invoked by name (a shell that parsed `/name` from the person's line), by
970
+ * {@link SkillSpec.name}. Each one's {@link SkillSpec.allowedTools} / {@link SkillSpec.disallowedTools} take effect
971
+ * at the start of the run with no `Skill` call and no card — the person's own invocation is the statement — and last
972
+ * for this run (a steer does not end them; the next runTask starts without them), like a grant the model's activation wrote. Delivering the skill's body
973
+ * stays the caller's business (this key carries names only). Every name must be one of this run's {@link skills};
974
+ * anything else refuses the run before it starts (`config.user_invoked_skill_unknown`), never dropped. Present only
975
+ * when the person actually invoked one; a mid-turn invocation (one riding a steer) grants nothing.
976
+ */
977
+ userInvokedSkills?: string[];
968
978
  /**
969
979
  * Fraction of the model's context window the `<skills>` listing may spend. Absent ⇒ `0.01` (1%).
970
980
  * Domain `(0, 1]`; anything else — including `0`, a string, `NaN` — REFUSES at the door with
@@ -76,8 +76,9 @@ export type ToolPathAbsence = (typeof TOOL_PATH_ABSENCES)[number];
76
76
  * slot RESOLVES — as a READER sees it, whichever side it came from. Present ⇔ the tool can be confirmed BY
77
77
  * PATH (write confinement, sensitive-path guards, edited-file tracking, the transcript-directory gate, the
78
78
  * permission-rule path family). `param` outranks every alias (NotebookEdit's `notebook_path` beats a decoy
79
- * `file_path`). `skillScopeEligible` (default false) is the SEPARATE authorization domain of the
80
- * active-skill `allowPaths` face — a confinable write tool is not automatically eligible there.
79
+ * `file_path`). `skillScopeEligible` (default false) is a SEPARATE authorization domain (its one reader, the
80
+ * skill-scope write fence, is retired and no reader remains; the bit stays a declared member of the roster row) — a
81
+ * confinable write tool is not automatically eligible there.
81
82
  *
82
83
  * ONE shape, two spellings: a DECLARATION ({@link ToolPathTarget}, what a catalog row / a `ToolSpec` / an
83
84
  * MCP overlay writes) and the roster row's NORMALIZED form (`aliases` always an array, `skillScopeEligible`
@@ -12,17 +12,43 @@
12
12
  * - `TOMBSTONED_TOOLS` (per-name re-education messages) is gone: a retired name now gets the same
13
13
  * loud generic roster miss as any unknown tool.
14
14
  *
15
- * What REMAINS is validation data only: {@link RETIRED_TOOL_NAMES} maps every name the engine ever
16
- * retired to a one-line guidance string. It exists because deleting the mapping flips stale POLICY
17
- * entries from "silently matched" to "silently matches nothing" — and for a deny/requireApproval
18
- * entry that direction WIDENS the surface. The prepare-time policy-name audit (runner/prepare-task.ts)
19
- * escalates a retired name that matches nothing in the run's roster from advisory to a HARD,
20
- * coordinates-carrying failure (`config.legacy_tool_name`) — `validatePermissionRules`
21
- * (permission-rules.ts) does NOT classify retired names itself; a retired-name entry is a raw,
22
- * ordinary-looking tool name to its compile pass by design (RB-476-A: that classification is the
23
- * roster-aware prepare-time audit's job, not the compile pass's, since compile time cannot know
24
- * which names a deployment mounts its OWN tool under). A deployment that mounts its OWN tool under
25
- * one of these names is unaffected either way: the name is then in the roster, matches, and is never
26
- * audited as stale.
15
+ * What REMAINS is validation data: {@link RETIRED_TOOL_NAMES} maps every name the engine ever
16
+ * retired to a one-line guidance string, and {@link judgeRuleToolName} is the ONE reading of it a rule's
17
+ * tool name gets. They exist because deleting the mapping flips stale POLICY entries from "silently
18
+ * matched" to "silently matches nothing" — and for a deny/requireApproval entry that direction WIDENS
19
+ * the surface. `validatePermissionRules` (permission-rules.ts) does NOT classify retired names itself:
20
+ * which names a deployment mounts its OWN tool under is a roster fact, and the judgment takes the roster.
21
+ * A deployment that mounts its OWN tool under one of these names is unaffected: the name is then in the
22
+ * roster and the judgment answers `current`.
23
+ *
24
+ * GRAMMAR of a guidance string, read by {@link judgeRuleToolName} and held by a pin: it opens with
25
+ * `renamed "<name>"` (a 1:1 successor — the judgment's `currentName`), `folded into "<name>"` (merged into a
26
+ * wider tool: writing it would deny more than the entry did, so it is no current name), or `removed — `.
27
27
  */
28
28
  export declare const RETIRED_TOOL_NAMES: ReadonlyMap<string, string>;
29
+ /** The three answers {@link judgeRuleToolName} gives — a closed set. */
30
+ export declare const RULE_TOOL_NAME_VERDICTS: readonly ["current", "retired", "unprefixed_namespace"];
31
+ /**
32
+ * Whether a tool name written in a rule is a stale name — one that, against `roster`, names no tool and never can:
33
+ * - `current` — not a stale name: the roster mounts it, or it is neither retired nor a prefix-less `__` name. (Whether a
34
+ * current name matches anything THIS run is a different question — an unknown name is the prepare audit's advisory.)
35
+ * - `retired` — a name in {@link RETIRED_TOOL_NAMES} the roster does not mount; `guidance` is that table's line, and
36
+ * `currentName` the 1:1 successor when there is one (see the module header's grammar).
37
+ * - `unprefixed_namespace` — carries the `__` namespace separator with no protocol prefix. The roster cannot clear it:
38
+ * a caller tool name or alias carrying `__` is refused at prepare, every engine tool name is `__`-free, and every
39
+ * protocol tool is minted with its prefix.
40
+ *
41
+ * `roster` = the tool names (aliases included) the reader knows are mounted: a run's assembled roster at prepare, the
42
+ * names a deployment DECLARES it mounts its own tools under at a session-rule store (an empty set when it declares none).
43
+ */
44
+ export type RuleToolNameVerdict = {
45
+ readonly kind: "current";
46
+ } | {
47
+ readonly kind: "retired";
48
+ readonly guidance: string;
49
+ readonly currentName?: string;
50
+ } | {
51
+ readonly kind: "unprefixed_namespace";
52
+ };
53
+ /** See {@link RuleToolNameVerdict}. Pure; the one answer to "is this rule's tool name a retired or prefix-less name". */
54
+ export declare function judgeRuleToolName(name: string, roster: ReadonlySet<string>): RuleToolNameVerdict;
@@ -1,3 +1,4 @@
1
+ import { carriesNamespaceSeparator, protocolOf } from "./protocol-table.js";
1
2
  export const RETIRED_TOOL_NAMES = new Map([
2
3
  ["read_file", 'renamed "Read"'],
3
4
  ["edit_file", 'renamed "Edit"'],
@@ -40,3 +41,18 @@ export const RETIRED_TOOL_NAMES = new Map([
40
41
  ["Recall", "removed — memory is file-based; read the memory directory with the ordinary file tools"],
41
42
  ["recall", "removed — memory is file-based; read the memory directory with the ordinary file tools"],
42
43
  ]);
44
+ export const RULE_TOOL_NAME_VERDICTS = Object.freeze(["current", "retired", "unprefixed_namespace"]);
45
+ const CURRENT = Object.freeze({ kind: "current" });
46
+ const UNPREFIXED = Object.freeze({ kind: "unprefixed_namespace" });
47
+ export function judgeRuleToolName(name, roster) {
48
+ if (roster.has(name))
49
+ return CURRENT;
50
+ const guidance = RETIRED_TOOL_NAMES.get(name);
51
+ if (guidance !== undefined) {
52
+ const currentName = /^renamed "([^"]+)"/.exec(guidance)?.[1];
53
+ return currentName !== undefined ? { kind: "retired", guidance, currentName } : { kind: "retired", guidance };
54
+ }
55
+ if (carriesNamespaceSeparator(name) && protocolOf(name) === undefined)
56
+ return UNPREFIXED;
57
+ return CURRENT;
58
+ }
@@ -130,11 +130,15 @@ export declare const NAME_READINGS: {
130
130
  readonly kind: "wire";
131
131
  readonly file: string;
132
132
  };
133
- readonly "policy.skillScope": {
133
+ readonly "skillGrant.names": {
134
134
  readonly kind: "any";
135
135
  readonly file: string;
136
136
  };
137
- readonly "policy.skillScope.effect": {
137
+ readonly "skillGrant.binding": {
138
+ readonly kind: "wire";
139
+ readonly file: string;
140
+ };
141
+ readonly "gate.skillActivationSeat": {
138
142
  readonly kind: "wire";
139
143
  readonly file: string;
140
144
  };
@@ -5,6 +5,7 @@ const POLICY = "src/core/tool-policy.ts";
5
5
  const OFFER = "src/core/runner/permission-rule-lanes.ts";
6
6
  const PARK = "src/core/runner/prepare-park-ask.ts";
7
7
  const HANDLERS = "src/core/runner/run-harness-handlers.ts";
8
+ const STATIONS = "src/core/runner/call-hook-stations.ts";
8
9
  const any = (file) => ({ kind: "any", file });
9
10
  const wire = (file) => ({ kind: "wire", file });
10
11
  const spelling = (file) => ({ kind: "spelling", file });
@@ -33,8 +34,9 @@ export const NAME_READINGS = {
33
34
  "policy.projectionReplay": any(POLICY),
34
35
  "policy.sessionRules": any("src/core/runner/session-rule-policy.ts"),
35
36
  "policy.sessionRules.effect": wire("src/core/runner/session-rule-policy.ts"),
36
- "policy.skillScope": any("src/core/runner/active-skill-scope.ts"),
37
- "policy.skillScope.effect": wire("src/core/runner/active-skill-scope.ts"),
37
+ "skillGrant.names": any("src/core/skill-grant.ts"),
38
+ "skillGrant.binding": wire("src/core/skill-grant.ts"),
39
+ "gate.skillActivationSeat": wire(GATE),
38
40
  "policy.permissionRules": any("src/core/permission-rules.ts"),
39
41
  "policy.persistedGrammar": any("src/core/permission-rule-model.ts"),
40
42
  "policy.durableQuestion": any("src/core/ask-question.ts"),
@@ -54,8 +56,8 @@ export const NAME_READINGS = {
54
56
  "verify.wroteThisRun": wire(HANDLERS),
55
57
  "verify.wroteThisRunSeed": wire("src/core/runner/run-attachment-seats.ts"),
56
58
  "event.toolStartEnd": spelling(HANDLERS),
57
- "hook.postToolUse": wire(GATE),
58
- "hook.postToolUseFailure": wire(GATE),
59
+ "hook.postToolUse": wire(STATIONS),
60
+ "hook.postToolUseFailure": wire(STATIONS),
59
61
  "hook.postToolBatch": spelling(HANDLERS),
60
62
  "stats.subagentToolCounter": spelling("src/agents/subagent.ts"),
61
63
  "activity.sink": spelling(HANDLERS),
@@ -42,7 +42,7 @@ export interface ToolCallRequest {
42
42
  * against, read at adjudication time from the same tracked `cwdRef` the fs hand tools resolve with
43
43
  * (design/64 §16.3: a resident shell's `cd` persists and moves the fs tools' relative base with it).
44
44
  *
45
- * WHY IT EXISTS: every path guard here (sensitive-path, fs-write gate, session rules, skill scope,
45
+ * WHY IT EXISTS: every path guard here (sensitive-path, fs-write gate, session rules,
46
46
  * transcript integrity) re-resolves the model's spelling itself, and each was handed a STATIC root at
47
47
  * wiring time. After a `cd` the two sides of a single call disagreed about which file it was: the tool
48
48
  * wrote `<liveCwd>/pre-commit` while the guard judged `<wiringRoot>/pre-commit` — measured on a host
@@ -50,7 +50,7 @@ export interface ToolCallRequest {
50
50
  * Reading THIS field makes the guard's resolution base the same value the tool will use.
51
51
  *
52
52
  * Applies to the call's TARGET only. A guard's own configured directories (accept/exempt domains,
53
- * `allowDirs`, manifest `allowPaths`) are DEPLOYMENT text and keep resolving against the static root —
53
+ * `allowDirs`) are DEPLOYMENT text and keep resolving against the static root —
54
54
  * a shell `cd` must never relocate an allow-domain.
55
55
  *
56
56
  * ABSENT when the run has no tracked cwd (read-only hands, no execution env) or when a policy is
@@ -23,9 +23,6 @@ export declare function catalogToolNames(): ReadonlySet<string>;
23
23
  /** A-11/12/13/14 — tools this engine can CONFINE BY PATH on a write: `pathTarget.access ≠ read`.
24
24
  * Today {Write, Edit, NotebookEdit}. */
25
25
  export declare function pathConfinableWriteToolNames(): ReadonlySet<string>;
26
- /** A-11 (the SECOND authorization domain) — the active-skill `allowPaths` face: confinable AND the
27
- * definition opted into skill scoping. Today {Write, Edit} — NotebookEdit is deliberately outside. */
28
- export declare function skillScopeWriteToolNames(): ReadonlySet<string>;
29
26
  /** A-8 — every definition that names a path target at all (read or write): the file-tool glob lane. */
30
27
  export declare function pathTargetingToolNames(): ReadonlySet<string>;
31
28
  /** The tools whose declared path target is a READ (a file read, a search root): the file read station's membership. */
@@ -75,7 +72,7 @@ export declare function protectivePathTargetOf(call: ToolCallLike): CallPathTarg
75
72
  export declare const BARE_REQUEST_CODE = "policy.bare_request";
76
73
  /**
77
74
  * The path target a CONTAINMENT fence reads (a grant admitting a write BY its path: session `allowDirs`, the fs-write
78
- * gate's accept/exempt dirs; the active-skill allowPaths face reads its own eligibility bit the same way): the object
75
+ * gate's accept/exempt dirs): the object
79
76
  * face's OWN declaration only — a same-name tool that declares no path target is an UNCONFIRMABLE write (the catalog's
80
77
  * slot may not be the one it writes; a decoy in that slot would buy an admission), so the fence fails closed.
81
78
  *
@@ -26,9 +26,6 @@ const names = (pred) => {
26
26
  export function pathConfinableWriteToolNames() {
27
27
  return names((e) => e.face.pathTarget !== undefined && e.face.pathTarget.access !== "read");
28
28
  }
29
- export function skillScopeWriteToolNames() {
30
- return names((e) => e.face.pathTarget !== undefined && e.face.pathTarget.access !== "read" && e.face.pathTarget.skillScopeEligible === true);
31
- }
32
29
  export function pathTargetingToolNames() {
33
30
  return names((e) => e.face.pathTarget !== undefined);
34
31
  }