@sema-agent/core 7.24.4 → 7.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/dist/agents/peer-session-drain.d.ts +16 -1
  3. package/dist/agents/peer-session-drain.js +16 -1
  4. package/dist/agents/send-message-tool.d.ts +10 -2
  5. package/dist/agents/send-message-tool.js +18 -7
  6. package/dist/agents/subagent.js +13 -10
  7. package/dist/core/arg-summary.d.ts +0 -7
  8. package/dist/core/arg-summary.js +2 -1
  9. package/dist/core/background-agent-store.d.ts +3 -3
  10. package/dist/core/checkpoint-store.d.ts +1 -0
  11. package/dist/core/delegation-frames.d.ts +5 -0
  12. package/dist/core/gate-fold.js +85 -0
  13. package/dist/core/gate-lanes.js +23 -1
  14. package/dist/core/gate-outcome.d.ts +8 -2
  15. package/dist/core/gate-outcome.js +3 -1
  16. package/dist/core/hooks.d.ts +29 -0
  17. package/dist/core/hooks.js +2 -0
  18. package/dist/core/mailbox-store.d.ts +5 -0
  19. package/dist/core/mailbox-store.js +1 -1
  20. package/dist/core/parent-spec-seat.d.ts +7 -1
  21. package/dist/core/parent-spec-seat.js +2 -0
  22. package/dist/core/permission-rule-consent.js +29 -47
  23. package/dist/core/permission-rule-provider.js +4 -3
  24. package/dist/core/permission-rule-store.d.ts +143 -7
  25. package/dist/core/permission-rule-store.js +165 -15
  26. package/dist/core/runner/contracts.d.ts +36 -2
  27. package/dist/core/runner/gate-exit.d.ts +14 -0
  28. package/dist/core/runner/gate-exit.js +4 -0
  29. package/dist/core/runner/prepare-delegation-surface.d.ts +4 -4
  30. package/dist/core/runner/prepare-delegation-surface.js +2 -1
  31. package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
  32. package/dist/core/runner/prepare-gate-stations.js +7 -4
  33. package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
  34. package/dist/core/runner/prepare-hands-readface.js +25 -10
  35. package/dist/core/runner/prepare-inherited-gate.d.ts +23 -0
  36. package/dist/core/runner/prepare-inherited-gate.js +11 -6
  37. package/dist/core/runner/prepare-policy-chain.d.ts +15 -1
  38. package/dist/core/runner/prepare-policy-chain.js +14 -4
  39. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -2
  40. package/dist/core/runner/prepare-suspend-saga.js +4 -3
  41. package/dist/core/runner/prepare-task.js +10 -9
  42. package/dist/core/runner/prepare-wiring-manifest.d.ts +11 -4
  43. package/dist/core/runner/prepare-wiring-manifest.js +8 -4
  44. package/dist/core/runner/resume-apply.js +2 -0
  45. package/dist/core/runner/run-harness-handlers.js +1 -0
  46. package/dist/core/runner/run-identity-wiring.d.ts +2 -2
  47. package/dist/core/runner/run-leg.js +26 -0
  48. package/dist/core/runner/run-notification-lane.d.ts +2 -2
  49. package/dist/core/runner/run-notification-lane.js +2 -1
  50. package/dist/core/runner/run-settle-and-teardown.js +1 -0
  51. package/dist/core/runner/session-rule-policy.d.ts +3 -4
  52. package/dist/core/runner/session-rule-policy.js +11 -3
  53. package/dist/core/session-policy-store.d.ts +27 -3
  54. package/dist/core/session-policy-store.js +12 -4
  55. package/dist/core/store-contracts/background-agent-store-contract.js +10 -0
  56. package/dist/core/store-contracts/peer-directory-contract.d.ts +23 -0
  57. package/dist/core/store-contracts/peer-directory-contract.js +82 -0
  58. package/dist/core/task-event.d.ts +6 -0
  59. package/dist/core/task-notification.d.ts +79 -4
  60. package/dist/core/task-notification.js +90 -1
  61. package/dist/core/task-registry-agent.js +1 -2
  62. package/dist/core/task-registry-monitor.js +2 -1
  63. package/dist/core/task-registry-shared.d.ts +53 -5
  64. package/dist/core/task-registry-shared.js +13 -2
  65. package/dist/core/task-registry.js +8 -5
  66. package/dist/core/task-spec.d.ts +12 -5
  67. package/dist/core/tool-catalog-entries.js +1 -0
  68. package/dist/core/tool-policy.js +30 -23
  69. package/dist/core/tool-spec.d.ts +1 -1
  70. package/dist/core/wiring-manifest.d.ts +2 -2
  71. package/dist/engine/loop/agent-loop.js +1 -1
  72. package/dist/engine/loop/types.d.ts +4 -0
  73. package/dist/index.d.ts +6 -3
  74. package/dist/index.js +4 -3
  75. package/dist/prompts/coordinator.d.ts +2 -1
  76. package/dist/stores/file/adoption/adopt.js +23 -1
  77. package/dist/stores/file/permission-rule-store.js +3 -1
  78. package/dist/stores/file/session-policy-store.d.ts +31 -0
  79. package/dist/stores/file/session-policy-store.js +87 -7
  80. package/dist/tools/fs/bash-lexer.d.ts +13 -0
  81. package/dist/tools/fs/bash-lexer.js +61 -0
  82. package/dist/tools/fs/bash-program-position.d.ts +36 -29
  83. package/dist/tools/fs/bash-program-position.js +33 -13
  84. package/dist/tools/fs/bash-readonly-classifier.d.ts +26 -313
  85. package/dist/tools/fs/bash-readonly-classifier.js +81 -782
  86. package/dist/tools/fs/fs-bash.d.ts +43 -207
  87. package/dist/tools/fs/fs-bash.js +20 -341
  88. package/dist/tools/fs/index.d.ts +12 -13
  89. package/dist/tools/fs/index.js +3 -4
  90. package/dist/tools/fs/read-deny.d.ts +25 -0
  91. package/dist/tools/fs/read-deny.js +19 -0
  92. package/dist/tools/fs/safety.js +2 -6
  93. package/dist/tools/fs/shell-read-check.d.ts +99 -0
  94. package/dist/tools/fs/shell-read-check.js +229 -0
  95. package/dist/tools/fs/shell-read-establish.d.ts +70 -115
  96. package/dist/tools/fs/shell-read-establish.js +216 -102
  97. package/dist/tools/fs/shell-read-judge.d.ts +231 -0
  98. package/dist/tools/fs/shell-read-judge.js +170 -0
  99. package/dist/tools/fs/shell-read-plan.d.ts +3 -0
  100. package/dist/tools/fs/shell-read-plan.js +88 -27
  101. package/dist/tools/monitor.d.ts +5 -5
  102. package/dist/tools/monitor.js +3 -5
  103. package/dist/tools/task-list.d.ts +4 -0
  104. package/package.json +1 -1
  105. package/test/export-surface.snapshot.json +19 -3
  106. package/wire-changes/core@7.25.0.json +671 -0
  107. package/wire-changes/core@7.25.1.json +64 -0
@@ -1,6 +1,10 @@
1
1
  import { escapeEnvelopeTag, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
2
2
  import { PEER_MESSAGE_NOTICE } from "../agents/peer-admission.js";
3
3
  import { buildCrossSessionEnvelope, CROSS_SESSION_MESSAGE_NOTICE, neutralizePeerBody } from "../agents/cross-session-envelope.js";
4
+ export const NOTIFICATION_INJECTION_OUTCOMES = ["queued", "parked", "parked_durably", "dropped_duplicate"];
5
+ export function isNotificationInjectionOutcome(value) {
6
+ return typeof value === "string" && NOTIFICATION_INJECTION_OUTCOMES.includes(value);
7
+ }
4
8
  export const SYSTEM_INJECTION_PRIORITIES = ["now", "next", "later"];
5
9
  export function isSystemInjectionPriority(value) {
6
10
  return typeof value === "string" && SYSTEM_INJECTION_PRIORITIES.includes(value);
@@ -107,6 +111,7 @@ const MAX_DEDUP_KEYS = 2_000;
107
111
  export const MAX_PENDING_EVENTS_PER_TASK = 50;
108
112
  export const MAX_PENDING_EVENTS_PER_SESSION = 200;
109
113
  export const MAX_PENDING_SESSIONS = 100;
114
+ export const MAX_SESSION_BOX_BINDINGS = 4096;
110
115
  const MAX_TOMBSTONES = 5_000;
111
116
  const TERMINAL_STATUSES = new Set(["completed", "failed", "killed", "cancelled"]);
112
117
  export function isDelegatedAgentTerminal(n) {
@@ -186,6 +191,22 @@ export class PendingSessionNotifications {
186
191
  droppedSessions = 0;
187
192
  evictedSessions = new Map();
188
193
  unattributedDrops = 0;
194
+ sessionBoxes = new Map();
195
+ bindSessionBox(sessionId, box) {
196
+ this.sessionBoxes.delete(sessionId);
197
+ if (box === undefined)
198
+ return;
199
+ this.sessionBoxes.set(sessionId, box);
200
+ while (this.sessionBoxes.size > MAX_SESSION_BOX_BINDINGS) {
201
+ const oldest = this.sessionBoxes.keys().next().value;
202
+ if (oldest === undefined)
203
+ break;
204
+ this.sessionBoxes.delete(oldest);
205
+ }
206
+ }
207
+ sessionBoxFor(sessionId) {
208
+ return this.sessionBoxes.get(sessionId);
209
+ }
189
210
  pend(sessionId, n, priority) {
190
211
  let s = this.sessions.get(sessionId);
191
212
  if (s === undefined) {
@@ -251,7 +272,36 @@ export class PendingSessionNotifications {
251
272
  }
252
273
  routed = new Map();
253
274
  holdsKey(sessionId, dedupKey) {
254
- return this.sessions.get(sessionId)?.keys.has(dedupKey) === true || this.routed.get(sessionId)?.has(dedupKey) === true;
275
+ return this.sessions.get(sessionId)?.keys.has(dedupKey) === true || this.routed.get(sessionId)?.has(dedupKey) === true || this.durable.get(sessionId)?.has(dedupKey) === true;
276
+ }
277
+ durable = new Map();
278
+ noteDurablePark(sessionId, dedupKey) {
279
+ let mine = this.durable.get(sessionId);
280
+ if (mine === undefined) {
281
+ if (this.durable.size >= MAX_PENDING_SESSIONS) {
282
+ const oldest = this.durable.keys().next().value;
283
+ if (oldest !== undefined)
284
+ this.durable.delete(oldest);
285
+ }
286
+ mine = new Set();
287
+ this.durable.set(sessionId, mine);
288
+ }
289
+ mine.add(dedupKey);
290
+ if (mine.size > MAX_PENDING_EVENTS_PER_SESSION) {
291
+ for (const k of mine) {
292
+ mine.delete(k);
293
+ if (mine.size <= MAX_PENDING_EVENTS_PER_SESSION)
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ forgetDurablePark(sessionId, dedupKey) {
299
+ const mine = this.durable.get(sessionId);
300
+ if (mine === undefined)
301
+ return;
302
+ mine.delete(dedupKey);
303
+ if (mine.size === 0)
304
+ this.durable.delete(sessionId);
255
305
  }
256
306
  markDelivered(sessionId, dedupKey) {
257
307
  let mine = this.routed.get(sessionId);
@@ -285,6 +335,30 @@ export function routeOrParkNotification(store, sessionId, payload, priority, onD
285
335
  }
286
336
  const routed = store.liveLanes.deliver(sessionId, payload, priority, { ...(onDisposition !== undefined ? { onDisposition } : {}) });
287
337
  if (routed === "unavailable") {
338
+ const box = store.sessionBoxFor(sessionId);
339
+ const carrier = payload.agentMessage;
340
+ if (box !== undefined && carrier !== undefined) {
341
+ store.noteDurablePark(sessionId, dedupKey);
342
+ const record = sessionBoxRecordOf({ ...payload, agentMessage: carrier }, box);
343
+ let appended;
344
+ try {
345
+ appended = Promise.resolve(box.mailbox.append(box.scope, box.handle, record));
346
+ }
347
+ catch (e) {
348
+ appended = Promise.reject(e);
349
+ }
350
+ appended.then(() => onDisposition?.("parked_durably"), (e) => {
351
+ store.forgetDurablePark(sessionId, dedupKey);
352
+ try {
353
+ box.onError?.(e);
354
+ }
355
+ catch {
356
+ }
357
+ store.pend(sessionId, payload, priority);
358
+ onDisposition?.("parked");
359
+ });
360
+ return;
361
+ }
288
362
  store.pend(sessionId, payload, priority);
289
363
  onDisposition?.("parked");
290
364
  }
@@ -292,6 +366,21 @@ export function routeOrParkNotification(store, sessionId, payload, priority, onD
292
366
  onDisposition?.("queued");
293
367
  }
294
368
  }
369
+ export function sessionBoxRecordOf(payload, box) {
370
+ const peer = payload.peer;
371
+ return {
372
+ from: payload.agentMessage.from,
373
+ content: payload.agentMessage.body,
374
+ sentAt: Date.now(),
375
+ hopChain: peer !== undefined ? [...peer.hopChain] : [],
376
+ peerMeta: {
377
+ ...(peer?.fromSession !== undefined ? { fromSession: peer.fromSession } : {}),
378
+ ...(peer?.senderKey !== undefined ? { senderKey: peer.senderKey } : {}),
379
+ fromRoot: box.sessionId,
380
+ kind: "peer_message",
381
+ },
382
+ };
383
+ }
295
384
  function evictOldest(s, match) {
296
385
  let idx = s.items.findIndex((i) => match(i) && !TERMINAL_STATUSES.has(i.status));
297
386
  if (idx === -1)
@@ -419,7 +419,6 @@ export function registerBackgroundAgentLane(core, input) {
419
419
  ...(input.agentType !== undefined ? { agentType: input.agentType } : {}),
420
420
  ...(input.description !== undefined ? { description: input.description } : {}),
421
421
  ...(input.model !== undefined ? { model: input.model } : {}),
422
- ...(input.teamName !== undefined ? { teamName: input.teamName } : {}),
423
422
  writerId: core.writerId,
424
423
  writerEpoch: 1,
425
424
  spawnedAt: now,
@@ -1236,7 +1235,7 @@ export async function deliverToRunningAgentLane(core, id, access, notification,
1236
1235
  let result;
1237
1236
  try {
1238
1237
  const d = await handle.notify?.(notification, opts);
1239
- if (d === "parked") {
1238
+ if (d === "parked" || d === "parked_durably") {
1240
1239
  result = handle.retainedContinuation === true ? { ok: true, disposition: "parked" } : { ok: false, reason: "not_running" };
1241
1240
  }
1242
1241
  else {
@@ -1,5 +1,5 @@
1
1
  import { delimitUntrusted } from "./untrusted-text.js";
2
- import { assertOwnership, defaultMonitorTimers, MONITOR_MAX_TIMEOUT_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_BATCH_WINDOW_MS, MONITOR_MAX_BATCHES_PER_MINUTE, MONITOR_STORM_BURST, MONITOR_STORM_KILL_AFTER_MS, MONITOR_LINE_BUF_CAP, MONITOR_SPILL_CAP_CHARS, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, clipMonitorLine, terminalTaskSummary, accountDroppedBytes, renderSpoolBody, rollSpoolText, statusFromBackground, droppedGapNote, firstDropNote, alreadyTerminalStopNote, clipTaskOutput, sleepPollStep, } from "./task-registry-shared.js";
2
+ import { assertOwnership, defaultMonitorTimers, MONITOR_MAX_TIMEOUT_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_BATCH_WINDOW_MS, MONITOR_MAX_BATCHES_PER_MINUTE, MONITOR_STORM_BURST, MONITOR_STORM_KILL_AFTER_MS, MONITOR_LINE_BUF_CAP, MONITOR_SPILL_CAP_CHARS, TASK_OUTPUT_MAX_CHARS, mintCompletionId, clipMonitorEvent, clipMonitorLine, terminalTaskSummary, accountDroppedBytes, renderSpoolBody, SPOOL_REPLAY, rollSpoolText, statusFromBackground, droppedGapNote, firstDropNote, alreadyTerminalStopNote, clipTaskOutput, sleepPollStep, } from "./task-registry-shared.js";
3
3
  import { buildToolResultRef, OFFLOAD_TOOL_NAME, toolResultProvenanceOf } from "./tool-result-store.js";
4
4
  import { thrownText } from "./thrown-value.js";
5
5
  export function registerMonitorLane(core, input) {
@@ -339,6 +339,7 @@ export async function pollMonitorLane(handle, filter, deadline, signal) {
339
339
  details: {
340
340
  status: running ? "running" : handle.status,
341
341
  exitCode: spool.exitCode,
342
+ replay: SPOOL_REPLAY,
342
343
  ...(spool.droppedBytes !== undefined ? { bytesDroppedBeforeCursor: spool.droppedBytes } : {}),
343
344
  ...(spool.timedOut === true ? { timedOut: true, ...(spool.timeoutSec !== undefined ? { timeoutSec: spool.timeoutSec } : {}) } : {}),
344
345
  },
@@ -5,6 +5,7 @@ import type { BackgroundAgentRecord, BackgroundAgentStore } from "./background-a
5
5
  import type { WorkflowHandle, WorkflowRun } from "../orchestration/workflow.js";
6
6
  import type { TaskNotificationPayload } from "./task-notification.js";
7
7
  import type { ToolResultStore } from "./tool-result-store.js";
8
+ import type { AssertAllKeysHandled } from "./ask-origin.js";
8
9
  export type SemaTaskType = "background_bash" | "workflow" | "background_agent" | "monitor";
9
10
  /** design/134 §3.3 (R3): WHO initiated a kill — recorded at the initiation site (first-marker-wins),
10
11
  * because the settle-side then/catch context has already lost it. OPEN ENUM on the wire, now in the
@@ -254,7 +255,7 @@ export interface BackgroundAgentTaskHandle extends SemaTaskHandle {
254
255
  * ends park per session inside the injector itself (the lane-teardown branch). */
255
256
  notify?: (notification: import("./task-notification.js").TaskNotificationPayload, opts?: {
256
257
  priority?: import("./task-notification.js").SystemInjectionPriority;
257
- }) => "queued" | "parked" | "dropped_duplicate" | Promise<"queued" | "parked" | "dropped_duplicate">;
258
+ }) => import("./task-notification.js").NotificationInjectionOutcome | Promise<import("./task-notification.js").NotificationInjectionOutcome>;
258
259
  /** design/147 S2a — messages that arrived in the STARTUP window (row already
259
260
  * running, lane not yet bound). Bounded; flushed in order by attachAgentNotify. Discarded at
260
261
  * terminal settle: a run that failed during prepare never had a lane to park through, and the
@@ -658,6 +659,54 @@ export declare function spoolDropNote(acct: {
658
659
  droppedBytes?: number;
659
660
  dropUnknown?: true;
660
661
  }): string;
662
+ /**
663
+ * HOW a poll's output body may be read AGAIN — the machine word beside `details`, previously legible
664
+ * only from the stdout heading's prose.
665
+ *
666
+ * `full` = the whole output of the task, re-rendered from the registry's own spool on every poll: a
667
+ * later poll answers the same bytes plus whatever arrived, so a consumer REPLACES what it holds.
668
+ * `cursor` = only what is NEW since the previous read: the env cursor was consumed and those bytes
669
+ * cannot be served twice, so a consumer APPENDS.
670
+ *
671
+ * The distinction was machine-invisible and the one key nearby reads the two faces OPPOSITELY:
672
+ * `bytesDroppedBeforeCursor` rides the spool faces only when something was actually dropped, and the
673
+ * increment face unconditionally (value possibly 0). A consumer discriminating on that key's PRESENCE
674
+ * therefore reads a spool that happened to drop bytes as an increment — and appends a body that was a
675
+ * full re-render, printing the same text twice.
676
+ */
677
+ export declare const TASK_OUTPUT_REPLAY_KINDS: readonly ["full", "cursor"];
678
+ export type TaskOutputReplay = (typeof TASK_OUTPUT_REPLAY_KINDS)[number];
679
+ /**
680
+ * The ONE table the word and the prose both come out of. Each face's stdout/stderr headings live here
681
+ * rather than at the three legs that render them, so the banner a person reads and the key a program
682
+ * reads cannot drift — the failure mode this whole field exists to end (the two spool renderers were
683
+ * hand-copied twins before {@link renderSpoolBody}, and their drift was three disclosure bugs).
684
+ *
685
+ * `stdoutWhenGapped` is the heading a face falls back to once a gap has been disclosed. `undefined` is
686
+ * a ROW, not an omission: an increment window claims nothing about completeness to begin with ("new
687
+ * stdout" is true whether or not bytes were lost before it), so it has no claim to downgrade — while a
688
+ * spool advertising itself as "full" with a disclosed gap would be saying something false.
689
+ */
690
+ export declare const REPLAY_FACE: {
691
+ readonly full: {
692
+ readonly stdout: "stdout (full, re-readable)";
693
+ readonly stdoutWhenGapped: "stdout (re-readable spool — INCOMPLETE, see note above)";
694
+ readonly stderr: "stderr";
695
+ };
696
+ readonly cursor: {
697
+ readonly stdout: "new stdout";
698
+ readonly stdoutWhenGapped: undefined;
699
+ readonly stderr: "new stderr";
700
+ };
701
+ };
702
+ export type ReplayHeadingTableCoversEveryReplay = AssertAllKeysHandled<Exclude<TaskOutputReplay, keyof typeof REPLAY_FACE>>;
703
+ /** The re-readable spool face: the monitor lane and the bash spool lane, both rendered by
704
+ * {@link renderSpoolBody}. Named once so the three legs that report it cannot each spell it. */
705
+ export declare const SPOOL_REPLAY = "full";
706
+ /** The consume-once increment face: the bash blocked wait, which accumulates env-cursor increments. */
707
+ export declare const INCREMENT_REPLAY = "cursor";
708
+ /** The stdout heading of a face, downgraded when a gap has been disclosed (see {@link REPLAY_FACE}). */
709
+ export declare function stdoutHeadingOf(replay: TaskOutputReplay, gapDisclosed: boolean): string;
661
710
  /** The re-readable spool fields {@link renderSpoolBody} reads — the intersection of the bash handle's
662
711
  * optional `spool` and the monitor handle's mandatory one (both carry supersets: `mirrorFailed` is
663
712
  * bash-only, `drainFailed`/`dropNotified` monitor-only). Declared as a named shape, not inlined, so
@@ -878,15 +927,14 @@ export interface RegisterBackgroundAgentInput extends TaskAccess {
878
927
  * internals chain; equals parentSessionId at depth 1). Persisted on the handle, the durable row
879
928
  * and the roster so recovery faces enumerate the whole tree under the root without alias walks. */
880
929
  rootSessionId?: string;
881
- /** design/151 S3b — revival lookup keys (CC meta-sidecar shape): the model RECORD KEY and the team
882
- * name, persisted on the durable row so a tier-3 revival rebuilds the spec the way a fresh
883
- * named-teammate spawn would. Lookup keys only, never a serialized spec. `model` is NOT the
930
+ /** design/151 S3b — the revival lookup key (CC meta-sidecar shape): the model RECORD KEY,
931
+ * persisted on the durable row so a tier-3 revival rebuilds the spec the way a fresh
932
+ * named-teammate spawn would. A lookup key only, never a serialized spec. `model` is NOT the
884
933
  * resolved model id: a spawn that named a model in WORDS records the caller's spelling verbatim
885
934
  * (catalog key / tier word / CC alias) so the revival can re-resolve it against the catalog in
886
935
  * force at WAKE time; only a spawn that carried a Model OBJECT records that object's id. Same
887
936
  * value and same rule as the roster row's `model` column. */
888
937
  model?: string;
889
- teamName?: string;
890
938
  toolUseId?: string;
891
939
  abort: AbortController;
892
940
  now?: number;
@@ -101,14 +101,25 @@ export function spoolDropNote(acct) {
101
101
  return "[!] an unknown amount of earlier output was permanently dropped before this window (buffer evicted)\n";
102
102
  return "";
103
103
  }
104
+ export const TASK_OUTPUT_REPLAY_KINDS = ["full", "cursor"];
105
+ export const REPLAY_FACE = {
106
+ full: { stdout: "stdout (full, re-readable)", stdoutWhenGapped: "stdout (re-readable spool — INCOMPLETE, see note above)", stderr: "stderr" },
107
+ cursor: { stdout: "new stdout", stdoutWhenGapped: undefined, stderr: "new stderr" },
108
+ };
109
+ export const SPOOL_REPLAY = "full";
110
+ export const INCREMENT_REPLAY = "cursor";
111
+ export function stdoutHeadingOf(replay, gapDisclosed) {
112
+ const face = REPLAY_FACE[replay];
113
+ return gapDisclosed ? (face.stdoutWhenGapped ?? face.stdout) : face.stdout;
114
+ }
104
115
  export function renderSpoolBody(spool, opts) {
105
116
  const timedOutClause = spool.timedOut === true ? ` (timed out after ${spool.timeoutSec !== undefined ? `${spool.timeoutSec}s` : "its background time budget"})` : "";
106
117
  const displayStatus = (spool.exitCode !== undefined ? `exited(code ${spool.exitCode})` : opts.running ? "running" : opts.handleStatus) + timedOutClause;
107
118
  const rolledNote = spool.rolledChars > 0 ? `[!] ${spool.rolledChars} earlier char(s) dropped from the spool (memory bound)${opts.rolledExtraClause}\n` : "";
108
119
  const droppedNote = spoolDropNote(spool);
109
- const stdoutHeading = droppedNote !== "" || opts.drainNote !== "" ? "stdout (re-readable spool — INCOMPLETE, see note above)" : "stdout (full, re-readable)";
120
+ const stdoutHeading = stdoutHeadingOf(SPOOL_REPLAY, droppedNote !== "" || opts.drainNote !== "");
110
121
  return (`status: ${displayStatus}\n${rolledNote}${droppedNote}${opts.drainNote}--- ${stdoutHeading} ---\n${opts.shape(spool.stdout) || "(none)"}\n` +
111
- `--- stderr ---\n${opts.shape(spool.stderr) || "(none)"}`);
122
+ `--- ${REPLAY_FACE[SPOOL_REPLAY].stderr} ---\n${opts.shape(spool.stderr) || "(none)"}`);
112
123
  }
113
124
  export function droppedGapNote(spool) {
114
125
  if ((spool.droppedBytes ?? 0) > 0)
@@ -8,7 +8,7 @@ import { createSafeNotifier } from "./safe-notify.js";
8
8
  import { stripCwdSentinel } from "../tools/fs/fs-shared.js";
9
9
  import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-registry-monitor.js";
10
10
  import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task-registry-workflow.js";
11
- import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
11
+ import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, stdoutHeadingOf, REPLAY_FACE, SPOOL_REPLAY, INCREMENT_REPLAY, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
12
12
  export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
13
13
  import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
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";
@@ -1075,6 +1075,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
1075
1075
  details: {
1076
1076
  status: running ? "running" : handle.status,
1077
1077
  exitCode: spool.exitCode,
1078
+ replay: SPOOL_REPLAY,
1078
1079
  ...(spool.droppedBytes !== undefined ? { bytesDroppedBeforeCursor: spool.droppedBytes } : {}),
1079
1080
  ...(spool.timedOut === true ? { timedOut: true, ...(spool.timeoutSec !== undefined ? { timeoutSec: spool.timeoutSec } : {}) } : {}),
1080
1081
  },
@@ -1099,12 +1100,13 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
1099
1100
  if (r.error.code === "not_found")
1100
1101
  return noTask(requestedId, runningAgentFooterLane(this.core, access));
1101
1102
  const retrieval_status = r.error.code === "timeout" ? "timeout" : "not_ready";
1102
- const salvaged = stdout.length > 0 || stderr.length > 0
1103
+ const carriedOutput = stdout.length > 0 || stderr.length > 0;
1104
+ const salvaged = carriedOutput
1103
1105
  ? `\n--- output accumulated before the error ---\n${clipTaskOutput(stdout, handle.outputFile)}${stderr ? `\n--- stderr ---\n${clipTaskOutput(stderr, handle.outputFile)}` : ""}`
1104
1106
  : "";
1105
1107
  return {
1106
1108
  content: `Error (TaskOutput): ${r.error.message}${salvaged}`,
1107
- details: { task_id: handle.id, type: "background_bash", status: handle.status, retrieval_status, error: r.error.code },
1109
+ details: { task_id: handle.id, type: "background_bash", status: handle.status, retrieval_status, ...(carriedOutput ? { details: { replay: INCREMENT_REPLAY } } : {}), error: r.error.code },
1108
1110
  isError: true,
1109
1111
  };
1110
1112
  }
@@ -1125,8 +1127,8 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
1125
1127
  const displayStatus = (r.value.status === "exited" ? `exited(code ${r.value.exitCode})` : r.value.status) + timedOutClause;
1126
1128
  const droppedNote = spoolDropNote(dropAcct) +
1127
1129
  (rolledChars > 0 ? `[!] ${rolledChars} accumulated char(s) dropped from the middle during the blocked wait (memory bound)\n` : "");
1128
- const body = `status: ${displayStatus}\n${droppedNote}--- new stdout ---\n${shape(stdout) || "(none)"}\n` +
1129
- `--- new stderr ---\n${shape(stderr) || "(none)"}`;
1130
+ const body = `status: ${displayStatus}\n${droppedNote}--- ${stdoutHeadingOf(INCREMENT_REPLAY, droppedNote !== "")} ---\n${shape(stdout) || "(none)"}\n` +
1131
+ `--- ${REPLAY_FACE[INCREMENT_REPLAY].stderr} ---\n${shape(stderr) || "(none)"}`;
1130
1132
  return {
1131
1133
  content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
1132
1134
  details: {
@@ -1139,6 +1141,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
1139
1141
  details: {
1140
1142
  status: r.value.status,
1141
1143
  exitCode: r.value.exitCode,
1144
+ replay: INCREMENT_REPLAY,
1142
1145
  bytesDroppedBeforeCursor: dropAcct.droppedBytes ?? 0,
1143
1146
  ...(r.value.timedOut === true ? { timedOut: true, ...(r.value.timeoutSec !== undefined ? { timeoutSec: r.value.timeoutSec } : {}) } : {}),
1144
1147
  },
@@ -783,9 +783,12 @@ export interface TaskSpec {
783
783
  /**
784
784
  * design/199 件B — TASK-layer ADDITIONS to the built-in sensitive-path READ deny set
785
785
  * ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}). Judged by the
786
- * structured read faces (Read/Grep/Glob/RepoMap, their traversals, the classify shell gate's
787
- * auto-allow probe, the compaction attachment reader and the `bash_readonly` shell mounted by
788
- * {@link handsReadOnly} — one deny judge for every read face) in BOTH containment modes. Add-only at
786
+ * structured read faces (Read/Grep/Glob/RepoMap, their traversals, the compaction attachment reader) and
787
+ * by the shell READ STATION under every doctrine — the full shell (`Bash`, `Monitor`, whatever
788
+ * {@link shellGate} says) and the `bash_readonly` shell mounted by {@link handsReadOnly} alike: a
789
+ * deny-listed shell read is REFUSED at the gate (`deniedBy: "read_boundary"`), never asked, with the same
790
+ * sentence the structured faces refuse with — one deny judge for every read face — in BOTH containment
791
+ * modes. Add-only at
789
792
  * the task layer: entries here UNION with the active built-ins and the deployment's
790
793
  * {@link RunnerDeps.readDenyPatterns}; `[]` ≡ absent (union identity); NOTHING at the task layer
791
794
  * can remove a built-in — the one removal channel is the DEPLOYMENT's built-in configuration
@@ -873,8 +876,12 @@ export interface TaskSpec {
873
876
  * - `"always"`: every `bash` command tightens to an `irreversible_ask` durable suspend (fail-closed; the
874
877
  * correct default for an unattended/supervisor deployment with no classifier).
875
878
  * - `"classify"`: mark `bash` `irreversibility:"maybe"` with {@link import("../tools/fs/index.js").bashReversibilityProbe}
876
- * — a provably-benign single allowlisted read-only command (no operators) auto-allows; everything else
877
- * (writes, egress, pipes, unknown commands) tightens to ask. An egress command (e.g. `curl`) suspends under
879
+ * — the SHAPE classifier alone: a provably-benign single allowlisted read-only command (no operators)
880
+ * auto-allows; everything else (writes, egress, pipes, unknown commands) tightens to ask. WHERE a command
881
+ * reads is not this doctrine's question under any value: the gate's shell read station judges every
882
+ * shell call's read candidates against the deny set and the containment roots (a deny-listed read is
883
+ * refused, an out-of-root read is a mandated ask) whether `shellGate` is `"off"`, `"classify"` or
884
+ * `"always"`. An egress command (e.g. `curl`) suspends under
878
885
  * the IRREVERSIBLE axis (the shell mark is irreversibility, not egress) — the kind is non-budgetable either
879
886
  * way, so the budget resolver still never auto-approves it; the axis label is informational.
880
887
  * NOT unconditional: a POSITIVE per-tool mark on the shell tool keeps its seat — an explicit
@@ -154,6 +154,7 @@ export const ENGINE_CARDS = Object.freeze({
154
154
  a2a: "src/core/a2a.ts",
155
155
  path_not_in_root: "src/tools/fs/safety.ts",
156
156
  read_path_denied: "src/tools/fs/safety.ts",
157
+ readonly_out_of_root: "src/tools/fs/shell-read-check.ts",
157
158
  });
158
159
  const byId = new Map();
159
160
  for (const e of TOOL_CATALOG_ENTRIES) {
@@ -13,6 +13,7 @@ import { thrownText } from "./thrown-value.js";
13
13
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermissionRule } from "./permission-rules.js";
14
14
  import { protocolOf } from "./protocol-table.js";
15
15
  import { isAbsoluteForFamily, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
16
+ import { ASSIGNMENT_PREFIX, writtenAssignmentPrefix } from "../tools/fs/bash-program-position.js";
16
17
  export function advertisedWritableRootsSeat(seat) {
17
18
  return seat === undefined ? {} : { advertisedWritableRoots: Object.freeze([...seat.roots]) };
18
19
  }
@@ -618,22 +619,28 @@ export function findUnverifiableRecursiveDelete(command, safeVars = new Set(UNVE
618
619
  }
619
620
  return false;
620
621
  };
622
+ const applyAssignmentWord = (words, at) => {
623
+ const written = writtenAssignmentPrefix(words[at]);
624
+ if (written === undefined) {
625
+ assigned.delete(words[at].replace(/\[.*$/, ""));
626
+ return;
627
+ }
628
+ const value = restOfAssignment(words, at, written.prefix.length - 1);
629
+ if ((!written.settlesWholeName && !assigned.has(written.name)) || valueIsOpaque(value, assigned, safeVars))
630
+ assigned.delete(written.name);
631
+ else
632
+ assigned.add(written.name);
633
+ if (valueEscapesRoot(value, safeVars))
634
+ assignedEscapesRoot.add(written.name);
635
+ else if (written.settlesWholeName)
636
+ assignedEscapesRoot.delete(written.name);
637
+ };
621
638
  const statements = command.replace(/\\\r?\n/g, " ").split(STATEMENT_SPLIT);
622
639
  for (const stmt of statements) {
623
640
  const tokens = stmt.trim().split(/\s+/).filter(Boolean);
624
641
  let i = 0;
625
- while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i])) {
626
- const eq = tokens[i].indexOf("=");
627
- const name = tokens[i].slice(0, eq);
628
- const value = restOfAssignment(tokens, i, eq);
629
- if (valueIsOpaque(value, assigned, safeVars))
630
- assigned.delete(name);
631
- else
632
- assigned.add(name);
633
- if (valueEscapesRoot(value, safeVars))
634
- assignedEscapesRoot.add(name);
635
- else
636
- assignedEscapesRoot.delete(name);
642
+ while (i < tokens.length && ASSIGNMENT_PREFIX.test(tokens[i])) {
643
+ applyAssignmentWord(tokens, i);
637
644
  i += assignmentTokenSpan(tokens, i);
638
645
  }
639
646
  let sawWrapper = false;
@@ -646,7 +653,7 @@ export function findUnverifiableRecursiveDelete(command, safeVars = new Set(UNVE
646
653
  if (DELETE_WRAPPERS.has(t)) {
647
654
  sawWrapper = true;
648
655
  i++;
649
- while (i < tokens.length && (/^-/.test(tokens[i]) || /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i])))
656
+ while (i < tokens.length && (/^-/.test(tokens[i]) || ASSIGNMENT_PREFIX.test(tokens[i])))
650
657
  i++;
651
658
  continue;
652
659
  }
@@ -672,16 +679,16 @@ export function findUnverifiableRecursiveDelete(command, safeVars = new Set(UNVE
672
679
  i = headIdx;
673
680
  const head = stripTokenEdges(tokens[i]).replace(/^\\/, "");
674
681
  const headName = head.split("/").pop() ?? head;
675
- let m;
676
- if ((m = stmt.trim().match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/))) {
677
- if (valueIsOpaque(m[2], assigned, safeVars))
678
- assigned.delete(m[1]);
679
- else
680
- assigned.add(m[1]);
681
- if (valueEscapesRoot(m[2], safeVars))
682
- assignedEscapesRoot.add(m[1]);
683
- else
684
- assignedEscapesRoot.delete(m[1]);
682
+ const declStmt = stmt.trim();
683
+ const declaredWords = /^(?:export|declare|typeset|local|readonly)\b/.test(declStmt) ? declStmt.split(/\s+/).filter(Boolean).slice(1) : ASSIGNMENT_PREFIX.test(declStmt) ? [declStmt] : [];
684
+ for (let d = 0; d < declaredWords.length;) {
685
+ if (/^[-+]/.test(declaredWords[d])) {
686
+ d += 1;
687
+ continue;
688
+ }
689
+ declaredWords[d] = stripTokenEdges(declaredWords[d]);
690
+ applyAssignmentWord(declaredWords, d);
691
+ d += assignmentTokenSpan(declaredWords, d);
685
692
  }
686
693
  if (headName === "read") {
687
694
  void 0;
@@ -1180,7 +1180,7 @@ export interface ToolExecuteContext {
1180
1180
  */
1181
1181
  onTaskNotification?: (n: import("./task-notification.js").TaskNotificationPayload, opts?: {
1182
1182
  priority?: import("./task-notification.js").SystemInjectionPriority;
1183
- }) => void;
1183
+ }) => unknown;
1184
1184
  /**
1185
1185
  * design/99 (nested-subagent live observability): the host run's OPT-IN display sink — a deployment sets it (via
1186
1186
  * `RunInternals.onForwardEvent`) to receive a SUBAGENT's live `task_progress` ticks that otherwise stay in the
@@ -432,8 +432,8 @@ export interface WiringFacts {
432
432
  parkDurableApprovalOptIn: boolean;
433
433
  /** `undefined` = unresolved at this half (per-principal runtime caps are a prepare-time fact). */
434
434
  parkForceDurableGate?: boolean;
435
- /** `undefined` = unresolved at this half (the irreversible/egress tool vocabulary is assembled in
436
- * prepare). */
435
+ /** `undefined` = unresolved at this half (the irreversible/egress tool vocabulary and the shell read station —
436
+ * the three sources of a mandated ask — are assembled in prepare). */
437
437
  parkSafetyVocabularyArmed?: boolean;
438
438
  /** Present iff `parkCapable` (the resolved checkpoint store's declaration). */
439
439
  checkpointDurability?: StoreDurability;
@@ -1325,7 +1325,7 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
1325
1325
  };
1326
1326
  }
1327
1327
  if (beforeResult?.block) {
1328
- const blocked = createErrorToolResult(beforeResult.reason || "Tool execution was blocked");
1328
+ const blocked = createErrorToolResult(beforeResult.reason || "Tool execution was blocked", beforeResult.details !== undefined ? { details: beforeResult.details } : undefined);
1329
1329
  if (beforeResult.haltRemaining !== undefined) {
1330
1330
  return { kind: "immediate", result: blocked, isError: true, halt: beforeResult.haltRemaining };
1331
1331
  }
@@ -83,6 +83,10 @@ export type AgentToolCall = Extract<AssistantMessage["content"][number], {
83
83
  export interface BeforeToolCallResult {
84
84
  block?: boolean;
85
85
  reason?: string;
86
+ /** With `block`: the structured details the refusing layer minted, attached to the error result the loop
87
+ * builds from `reason` (a `{ type, code, … }` card reaches `tool_end.structured` / `errorCode` exactly as a
88
+ * tool's own refusal would). Ignored without `block`. */
89
+ details?: unknown;
86
90
  updatedInput?: unknown;
87
91
  haltRemaining?: HaltRemainingDirective;
88
92
  }
package/dist/index.d.ts CHANGED
@@ -100,7 +100,7 @@ export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core
100
100
  export { mintReminderMark, isValidReminderMark, openSystemReminder, mintSystemReminder, reminderMarkDeclaration } from "./core/reminder-mint.js";
101
101
  export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
102
102
  export type { InvariantKind, FunctionContract, Invariant, InvariantViolation, CheckResult, } from "./core/property-harness.js";
103
- export { bashReversibilityProbe, bashReadBoundaryProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
103
+ export { bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
104
104
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, type BashReadonlyRootBoundary, type CompoundReadonlyVerdict, } from "./tools/fs/index.js";
105
105
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
106
106
  export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, type ReadDenyBuiltinTier, type ReadDenyBuiltinRow, type ReadDenyBuiltinConfig, } from "./tools/fs/index.js";
@@ -132,7 +132,9 @@ export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./cor
132
132
  export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE, type FsWriteGatePolicyOptions } from "./core/fs-write-gate-policy.js";
133
133
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
134
134
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
135
- export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, type TaskNotificationPayload, type TaskNotificationStatus, type ExternalNotificationInput, type SystemInjection, type SystemInjectionPriority, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, type SemaProvenance, } from "./core/task-notification.js";
135
+ export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, type TaskNotificationPayload, type TaskNotificationStatus, type ExternalNotificationInput, type SystemInjection, type SystemInjectionPriority, type NotificationInjectionOutcome, // the injector's outcome words (`parked_durably` = the session's durable box took the frame)
136
+ NOTIFICATION_INJECTION_OUTCOMES, // …and their value half + guard, so a host wiring its own uplink validates a reported outcome without duplicating the vocabulary
137
+ isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, type SemaProvenance, } from "./core/task-notification.js";
136
138
  export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, TOOL_PATH_ABSENCES, TOOL_WIRE_NAME_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_CARD_ID_MAX_CHARS, TOOL_KEY_MAX_CHARS, TOOL_KEYS_MAX, RENDER_HINT_MAX_CHARS, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_LIST, type ToolFamily, type ToolMountTag, type ToolApprovalCard, type ToolPathAccess, type ToolPathBase, type ToolPathAbsence, type ToolPathTarget, type ToolCallPathTarget, type ToolRosterPathTarget, pathTargetSlotOnly, type ToolRuleFace, type ToolRenderHints, type ToolFaceDeclaration, type ToolCatalogEntry, } from "./core/tool-face.js";
137
139
  export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, type ToolCallLike, pathConfinableWriteToolNames, skillScopeWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
138
140
  export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, type RosterSource, type ToolIdentity, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, type RosterBoundMembers, type RosterBoundCode, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
@@ -201,7 +203,7 @@ export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRul
201
203
  * allowed and widening is not.
202
204
  */
203
205
  export { RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, RULE_BEHAVIORS_BY_PRECEDENCE, isRuleBehavior, readRowBehavior, adjudicatePersistedRules, adjudicatePersistedPathRules, ruleReachesProgramRun, programRunReachOf, PROGRAM_RUN_REACHES, type ProgramRunReachOutcome, pathRuleReachOf, ruleBasesNeeded, PATH_RULE_BASES, PATH_RULE_BASE_LABEL, ruleToolGrammarOf, COMMAND_RULE_TOOL, READ_RULE_TOOL, type PathRuleBase, type PathRuleBases, type RuleBehavior, type PersistedRuleVerdict, parseRuleText, formatRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, stripFormatCharacters, UNCOVERED_SEGMENT_REASON_BASELINE, RULE_OFFERS_ABSENCE_BASELINE, type UncoveredSegmentDetail, type EditedRuleBreadthWarning, directoryRuleAdmits, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, type PersistedRule, type RuleTombstone, type RuleScope, type RuleDot, type RuleAdd, type RuleAddOrigin, type RuleOffer, type RuleOfferBatchMember, type SegmentRuleSuggestion, type SegmentCoverage, type RuleReject, type RuleRejectCode, type ParsedRule, type PersistedRuleTool, type PersistedRuleMatch, } from "./core/permission-rule-model.js";
204
- export { removePersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, type DurableRulePartition, type DurableRulePartitionProvider, type RuleWriteOutcome, type StoredRules, type RemoveResult, type PutResult, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, type RuleSyncState, type RuleSyncFrontier, type RuleSyncDrop, type RuleSyncLandingReport, type RuleOwner, type QuarantinedRuleAdd, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, type PermissionRuleWriter, type WritableDurableRulePartition, type RuleWriteDelta, type RuleAddDelta, type RuleDeleteDelta, type RuleSyncJoinDelta, type RawRuleSyncState, type RedemptionAuthorization, } from "./core/permission-rule-store.js";
206
+ export { removePersistedRule, addPersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, type DurableRulePartition, type DurableRulePartitionProvider, type RuleWriteOutcome, type StoredRules, type RemoveResult, type AddResult, type PutResult, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, type RuleSyncState, type RuleSyncFrontier, type RuleSyncDrop, type RuleSyncLandingReport, type RuleOwner, type QuarantinedRuleAdd, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, type PermissionRuleWriter, type WritableDurableRulePartition, type RuleWriteDelta, type RuleAddDelta, type RuleTightenAddDelta, type RuleDeleteDelta, type RuleSyncJoinDelta, type RawRuleSyncState, type RedemptionAuthorization, } from "./core/permission-rule-store.js";
205
207
  export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, type PermissionRuleSyncTransport, type PermissionRuleSyncResult, type RuleSyncRequestBody, type RuleSyncResponseBody, } from "./core/permission-rule-sync.js";
206
208
  export { createPermissionRuleStoreProvider, effectivePermissionRules, effectiveOrThrow, ruleSourceOf, type PermissionRuleStore, type PermissionRuleStoreProvider, type PermissionRuleStoreConfig, type EffectivePermissionRules, type EffectivePermissionRule, type RemovedPermissionRule, type RuleSource, } from "./core/permission-rule-provider.js";
207
209
  export { InMemorySessionRulePartition, type SessionRulePartition, type SessionRuleAdd, type SessionRuleApplyResult, } from "./core/permission-rule-session.js";
@@ -277,6 +279,7 @@ export { workflowJournalStoreContract } from "./core/store-contracts/workflow-jo
277
279
  export { permissionRuleSyncContract, type PermissionRuleSyncContractHooks } from "./core/store-contracts/permission-rule-sync-contract.js";
278
280
  export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, mailboxTombstonedRecipientContract, type MailboxTombstonedRecipientContractHooks, mailboxCrossProcessContract, type MailboxCrossProcessContractHooks, } from "./core/store-contracts/mailbox-store-contract.js";
279
281
  export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
282
+ export { PEER_DIRECTORY_CONTRACT_SCOPE, peerDirectoryContract, type PeerDirectoryContractHooks } from "./core/store-contracts/peer-directory-contract.js";
280
283
  export { type BackgroundAgentStore, type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentUsage, canAccessAgentRecord, STALE_RUNNING_REAP_ATTRIBUTION, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, type BackgroundAgentQuery, } from "./core/background-agent-store.js";
281
284
  export { serveDurableAgentRowLane, buildAgentPollDetails, type AgentPollDetailsInput, } from "./core/task-registry-agent.js";
282
285
  export type { UnifiedTaskResult } from "./core/task-registry-shared.js";
package/dist/index.js CHANGED
@@ -78,7 +78,7 @@ export { runExecGate } from "./core/exec-gate.js";
78
78
  export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core/untrusted-text.js";
79
79
  export { mintReminderMark, isValidReminderMark, openSystemReminder, mintSystemReminder, reminderMarkDeclaration } from "./core/reminder-mint.js";
80
80
  export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
81
- export { bashReversibilityProbe, bashReadBoundaryProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
81
+ export { bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
82
82
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, } from "./tools/fs/index.js";
83
83
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
84
84
  export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, } from "./tools/fs/index.js";
@@ -107,7 +107,7 @@ export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./cor
107
107
  export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE } from "./core/fs-write-gate-policy.js";
108
108
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
109
109
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
110
- export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, } from "./core/task-notification.js";
110
+ export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, NOTIFICATION_INJECTION_OUTCOMES, isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, } from "./core/task-notification.js";
111
111
  export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, TOOL_PATH_ABSENCES, TOOL_WIRE_NAME_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_CARD_ID_MAX_CHARS, TOOL_KEY_MAX_CHARS, TOOL_KEYS_MAX, RENDER_HINT_MAX_CHARS, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_LIST, pathTargetSlotOnly, } from "./core/tool-face.js";
112
112
  export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, pathConfinableWriteToolNames, skillScopeWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
113
113
  export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
@@ -159,7 +159,7 @@ export { AUTO_MODE_ARMING_RECIPE_VERSION, autoModeArmingRecipeOf, sanitizeAutoMo
159
159
  export { rebuildAutoModeDecider, } from "./core/auto-mode-rebuild.js";
160
160
  export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, isNamespacedCoveringRuleName, namespacedRuleNameCovers, } from "./core/permission-rules.js";
161
161
  export { RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, RULE_BEHAVIORS_BY_PRECEDENCE, isRuleBehavior, readRowBehavior, adjudicatePersistedRules, adjudicatePersistedPathRules, ruleReachesProgramRun, programRunReachOf, PROGRAM_RUN_REACHES, pathRuleReachOf, ruleBasesNeeded, PATH_RULE_BASES, PATH_RULE_BASE_LABEL, ruleToolGrammarOf, COMMAND_RULE_TOOL, READ_RULE_TOOL, parseRuleText, formatRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, stripFormatCharacters, UNCOVERED_SEGMENT_REASON_BASELINE, RULE_OFFERS_ABSENCE_BASELINE, directoryRuleAdmits, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, } from "./core/permission-rule-model.js";
162
- export { removePersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, } from "./core/permission-rule-store.js";
162
+ export { removePersistedRule, addPersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, } from "./core/permission-rule-store.js";
163
163
  export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, } from "./core/permission-rule-sync.js";
164
164
  export { createPermissionRuleStoreProvider, effectivePermissionRules, effectiveOrThrow, ruleSourceOf, } from "./core/permission-rule-provider.js";
165
165
  export { InMemorySessionRulePartition, } from "./core/permission-rule-session.js";
@@ -233,6 +233,7 @@ export { workflowJournalStoreContract } from "./core/store-contracts/workflow-jo
233
233
  export { permissionRuleSyncContract } from "./core/store-contracts/permission-rule-sync-contract.js";
234
234
  export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, mailboxTombstonedRecipientContract, mailboxCrossProcessContract, } from "./core/store-contracts/mailbox-store-contract.js";
235
235
  export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
236
+ export { PEER_DIRECTORY_CONTRACT_SCOPE, peerDirectoryContract } from "./core/store-contracts/peer-directory-contract.js";
236
237
  export { canAccessAgentRecord, STALE_RUNNING_REAP_ATTRIBUTION, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, } from "./core/background-agent-store.js";
237
238
  export { serveDurableAgentRowLane, buildAgentPollDetails, } from "./core/task-registry-agent.js";
238
239
  export { FileBackgroundAgentStore } from "./stores/file/background-agent-store.js";
@@ -2,7 +2,8 @@
2
2
  * design/147 S3b — the teammate communication addendum (CC 2.1.212
3
3
  * `TEAMMATE_SYSTEM_PROMPT_ADDENDUM`, pretty.js:322948, sema-ized): composed for a NAMED delegated
4
4
  * child (`core/mode.teammate` section, admit = facts.teammateEnabled — the CC injection condition
5
- * `agentId && agentName && teamName` maps to sema's named-spawn internals chain). Divergences:
5
+ * `agentId && agentName && teamName` maps to sema's named-spawn internals chain; sema keeps no team
6
+ * name — one implicit team per session, keyed by the root session). Divergences:
6
7
  * CC's "task system"/"team lead" wording maps to sema's `"main"` reserved lane (the spawning
7
8
  * conversation); and the capability sentences track the shipped contract (S2a
8
9
  * delivered mid-run delivery): a message to a RUNNING teammate is queued and reaches it at its next