@sema-agent/core 5.16.0 → 5.17.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 (70) hide show
  1. package/CHANGELOG.md +224 -0
  2. package/dist/agents/peer-admission.d.ts +58 -0
  3. package/dist/agents/peer-admission.js +175 -0
  4. package/dist/agents/retain-ledger.d.ts +1 -1
  5. package/dist/agents/retain-ledger.js +9 -1
  6. package/dist/agents/send-message-tool.d.ts +8 -0
  7. package/dist/agents/send-message-tool.js +171 -21
  8. package/dist/agents/subagent.d.ts +13 -0
  9. package/dist/agents/subagent.js +90 -5
  10. package/dist/core/ask-question.js +16 -1
  11. package/dist/core/canonical-json.js +176 -14
  12. package/dist/core/checkpoint-store.d.ts +14 -0
  13. package/dist/core/checkpoint-store.js +73 -0
  14. package/dist/core/hooks.d.ts +4 -1
  15. package/dist/core/hooks.js +24 -6
  16. package/dist/core/mailbox-store.d.ts +2 -0
  17. package/dist/core/mailbox-store.js +2 -2
  18. package/dist/core/mcp.d.ts +1 -0
  19. package/dist/core/mcp.js +15 -3
  20. package/dist/core/runner/prepare-task.d.ts +4 -0
  21. package/dist/core/runner/prepare-task.js +169 -46
  22. package/dist/core/runner/runtask.js +13 -1
  23. package/dist/core/runner/turn-attachments.d.ts +2 -1
  24. package/dist/core/runner/turn-attachments.js +9 -6
  25. package/dist/core/session-reconcile.js +19 -1
  26. package/dist/core/shared-memory/contract.d.ts +17 -0
  27. package/dist/core/shared-memory/contract.js +138 -0
  28. package/dist/core/shared-memory/normalize.d.ts +73 -0
  29. package/dist/core/shared-memory/normalize.js +259 -0
  30. package/dist/core/shared-memory/tools.d.ts +7 -0
  31. package/dist/core/shared-memory/tools.js +289 -0
  32. package/dist/core/shared-memory/types.d.ts +95 -0
  33. package/dist/core/shared-memory/types.js +18 -0
  34. package/dist/core/task-notification.d.ts +3 -0
  35. package/dist/core/task-registry-agent.d.ts +1 -1
  36. package/dist/core/task-registry-agent.js +2 -1
  37. package/dist/core/task-registry.d.ts +1 -1
  38. package/dist/core/task-registry.js +2 -0
  39. package/dist/core/tool-policy.d.ts +9 -0
  40. package/dist/core/tool-policy.js +28 -8
  41. package/dist/core/types.d.ts +8 -0
  42. package/dist/core/untrusted-text.d.ts +1 -0
  43. package/dist/core/untrusted-text.js +10 -0
  44. package/dist/core/wiring-manifest.js +2 -2
  45. package/dist/engine/harness/agent-harness.d.ts +1 -0
  46. package/dist/engine/harness/agent-harness.js +21 -2
  47. package/dist/engine/llm/validation.js +121 -5
  48. package/dist/engine/loop/agent-loop.d.ts +2 -0
  49. package/dist/engine/loop/agent-loop.js +17 -4
  50. package/dist/index.d.ts +4 -0
  51. package/dist/index.js +4 -0
  52. package/dist/prompts/supervisor.d.ts +1 -1
  53. package/dist/prompts/supervisor.js +1 -1
  54. package/dist/stores/cc/mailbox-store.js +4 -0
  55. package/dist/stores/file/checkpoint-store.d.ts +1 -0
  56. package/dist/stores/file/checkpoint-store.js +1 -0
  57. package/dist/stores/file/mailbox-store.js +2 -2
  58. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
  59. package/dist/tools/fs/bash-readonly-classifier.js +11 -10
  60. package/dist/tools/fs/fs-bash.js +6 -6
  61. package/dist/tools/fs/fs-read.d.ts +1 -1
  62. package/dist/tools/fs/fs-read.js +4 -3
  63. package/dist/tools/fs/fs-shared.d.ts +3 -0
  64. package/dist/tools/fs/fs-shared.js +8 -1
  65. package/dist/tools/fs/fs-write.js +8 -8
  66. package/dist/tools/fs/index.d.ts +1 -0
  67. package/dist/tools/fs/index.js +1 -1
  68. package/dist/tools/fs/safety.d.ts +1 -0
  69. package/dist/tools/fs/safety.js +9 -2
  70. package/package.json +1 -1
@@ -0,0 +1,289 @@
1
+ import { Type } from "typebox";
2
+ import { errorResult } from "../tools.js";
3
+ import { CONNECTING_MESSAGE, GENERIC_FAILURE_MESSAGE, NO_STORES_MESSAGE, SHARED_STORE_FRAME, UNAVAILABLE_MESSAGE, datePrefixOf, echoUntrusted, foldReaderError, listPrefixArgument, normalizeEntries, normalizeMemoryPath, normalizeReadValue, normalizeSnapshot, pageDocuments, relativePathRefusal, sanitizeSharedContent, validateDocumentPath, validatePathPrefix, } from "./normalize.js";
4
+ import { SHARED_MEMORY_LIST_TOOL_NAME, SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_READ_TOOL_NAME, } from "./types.js";
5
+ const LIST_HINT = "List shared memory stores and their documents — check here before telling the user you do not have something.";
6
+ const LIST_DESCRIPTION = [
7
+ LIST_HINT,
8
+ "",
9
+ `List the memory documents in a memory store, sorted by path — each line gives a document's path, size, ` +
10
+ `and last-updated date, but no content (use ${SHARED_MEMORY_READ_TOOL_NAME} for that). Pass store (the store's id) ` +
11
+ `to choose the store, path_prefix to list one directory, and the cursor from a previous call to continue a ` +
12
+ `long listing. Call with no arguments at all to list the memory stores connected to this session — their ids, ` +
13
+ `a one-line description, and whether each is writable or read-only; that set can change during the session, ` +
14
+ `so re-check it whenever you are unsure which store to use.`,
15
+ "",
16
+ "When store is omitted this call lists the stores themselves, and path_prefix and cursor are ignored.",
17
+ "",
18
+ `Call ${SHARED_MEMORY_LIST_TOOL_NAME} early when context about the project or the work in it would help — and ` +
19
+ `always before telling the user you do not have something. If a listed document looks relevant, ` +
20
+ `${SHARED_MEMORY_READ_TOOL_NAME} it.`,
21
+ ].join("\n");
22
+ const READ_HINT = "Read one shared memory document by store id and path — treat it as a past snapshot, not the answer.";
23
+ const READ_DESCRIPTION = [
24
+ READ_HINT,
25
+ "",
26
+ "Read one memory document from a memory store by its store id and path.",
27
+ "",
28
+ "Read your memory often so corrections stick. Treat memories as past snapshots to verify against current sources, not the definitive answer.",
29
+ "",
30
+ "## When to access memories",
31
+ "- When memories seem relevant, or the user references prior work with them or others in their organization.",
32
+ "- You MUST access memory when the user explicitly asks you to check, recall, or remember.",
33
+ `- Call ${SHARED_MEMORY_LIST_TOOL_NAME} early when context about the project would help, and always before telling ` +
34
+ `the user you do not have something; if a listed document looks relevant, ${SHARED_MEMORY_READ_TOOL_NAME} it.`,
35
+ "- If the user says to *ignore* or *not use* memory: Do not apply remembered facts, cite, compare against, or mention memory content.",
36
+ "- Memory records can become stale over time. Use memory as context for what was true at a given point in time. Before answering the user or building assumptions based solely on information in memory records, verify that the memory is still correct and up-to-date by reading the current state of the files or resources. If a recalled memory conflicts with current information, trust what you observe now — and update or remove the stale memory rather than acting on it.",
37
+ "",
38
+ "## Before recommending from memory",
39
+ "",
40
+ "A memory that names a specific function, file, or flag is a claim that it existed *when the memory was written*. It may have been renamed, removed, or never merged. Before recommending it:",
41
+ "",
42
+ "- If the memory names a file path: check the file exists.",
43
+ "- If the memory names a function or flag: grep for it.",
44
+ "- If the user is about to act on your recommendation (not just asking about history), verify first.",
45
+ "",
46
+ '"The memory says X exists" is not the same as "X exists now."',
47
+ "",
48
+ "A memory that summarizes repo state (activity logs, architecture snapshots) is frozen in time. If the user asks about *recent* or *current* state, prefer `git log` or reading the code over recalling the snapshot.",
49
+ ].join("\n");
50
+ function renderStoreLine(info) {
51
+ return `${info.id} (${info.description}, ${info.writable ? "writable" : "read-only"})`;
52
+ }
53
+ function renderEntryLine(entry) {
54
+ const size = entry.sizeBytes === undefined ? "" : `${entry.sizeBytes} bytes`;
55
+ const date = datePrefixOf(entry.updatedAt);
56
+ const updated = date === undefined ? "" : `updated ${date}`;
57
+ const meta = [size, updated].filter((s) => s !== "").join(", ");
58
+ const path = JSON.stringify(entry.path);
59
+ return meta === "" ? path : `${path} (${meta})`;
60
+ }
61
+ function renderDocumentPage(page, remaining) {
62
+ if (page.length === 0)
63
+ return "(empty)";
64
+ const footer = remaining > 0
65
+ ? `\n… ${remaining} more — call again with cursor=${JSON.stringify(page.at(-1)?.path ?? "")} to continue.`
66
+ : "";
67
+ return `${SHARED_STORE_FRAME}\n\n${page.map(renderEntryLine).join("\n")}${footer}`;
68
+ }
69
+ function renderDocument(content, updatedAt) {
70
+ return `${SHARED_STORE_FRAME}\n[updated: ${datePrefixOf(updatedAt) ?? "unknown"}]\n${sanitizeSharedContent(content)}`;
71
+ }
72
+ function diagnosticsOf(droppedStores, droppedEntries) {
73
+ return {
74
+ ...(droppedStores > 0 ? { droppedStores } : {}),
75
+ ...(droppedEntries !== undefined && droppedEntries > 0 ? { droppedEntries } : {}),
76
+ };
77
+ }
78
+ const FAILED_ERROR = { reason: "error", message: GENERIC_FAILURE_MESSAGE };
79
+ export function createSharedMemoryTools(opts) {
80
+ const { provider, context } = opts;
81
+ const takeSnapshot = async (signal) => {
82
+ try {
83
+ return normalizeSnapshot(await provider.snapshot({ ...context }, { signal }));
84
+ }
85
+ catch (err) {
86
+ if (signal?.aborted === true)
87
+ throw err;
88
+ return { kind: "malformed" };
89
+ }
90
+ };
91
+ const resolveStore = async (storeId, signal) => {
92
+ const snapshot = await takeSnapshot(signal);
93
+ if (snapshot.kind === "malformed")
94
+ return { kind: "failed", refusal: FAILED_ERROR, diagnostics: {} };
95
+ if (snapshot.kind === "unavailable") {
96
+ return {
97
+ kind: "refused",
98
+ refusal: { reason: "unavailable", message: snapshot.message ?? UNAVAILABLE_MESSAGE },
99
+ diagnostics: {},
100
+ };
101
+ }
102
+ if (snapshot.kind === "connecting") {
103
+ return { kind: "refused", refusal: { reason: "unbound", message: CONNECTING_MESSAGE }, diagnostics: {} };
104
+ }
105
+ const diagnostics = diagnosticsOf(snapshot.droppedStores);
106
+ if (snapshot.stores.length === 0) {
107
+ return { kind: "refused", refusal: { reason: "unbound", message: NO_STORES_MESSAGE }, diagnostics };
108
+ }
109
+ const store = snapshot.stores.find((s) => s.info.id === storeId);
110
+ if (store === undefined) {
111
+ const connected = snapshot.stores.map((s) => s.info.id).join(", ");
112
+ return {
113
+ kind: "refused",
114
+ refusal: {
115
+ reason: "unknown_store",
116
+ message: `No memory store with id "${echoUntrusted(storeId)}" is connected to this session. ` +
117
+ `Connected stores: ${connected}. Call ${SHARED_MEMORY_LIST_TOOL_NAME} with no arguments to list them.`,
118
+ },
119
+ diagnostics,
120
+ };
121
+ }
122
+ return { kind: "resolved", store, diagnostics };
123
+ };
124
+ const listTool = {
125
+ name: SHARED_MEMORY_LIST_TOOL_NAME,
126
+ description: LIST_DESCRIPTION,
127
+ effect: "read",
128
+ defer: true,
129
+ offload: false,
130
+ contract: { contractId: "core.memory_list@1", implementationRevision: "1" },
131
+ parameters: Type.Object({
132
+ store: Type.Optional(Type.String({
133
+ description: "Id of the connected memory store to list. Omit to list the memory stores connected to this session (id, description, and writable or read-only).",
134
+ })),
135
+ path_prefix: Type.Optional(Type.String({
136
+ description: "Optional directory prefix to list only documents under it (e.g. /project/<project-id>/ lists only that project). Matching is directory-aligned (/x is the same as /x/). Omit to list the whole store.",
137
+ })),
138
+ cursor: Type.Optional(Type.String({ description: "Path of the last entry from a previous call. Returns entries after this path." })),
139
+ }, { additionalProperties: false }),
140
+ execute: async (args, ctx) => {
141
+ const { store, path_prefix: pathPrefix, cursor } = args;
142
+ const signal = ctx.signal;
143
+ if (store === undefined) {
144
+ const snapshot = await takeSnapshot(signal);
145
+ if (snapshot.kind === "malformed") {
146
+ return refusedList(FAILED_ERROR, "failed", {}, undefined);
147
+ }
148
+ if (snapshot.kind === "unavailable") {
149
+ return refusedList({ reason: "unavailable", message: snapshot.message ?? UNAVAILABLE_MESSAGE }, "refused", {}, undefined);
150
+ }
151
+ if (snapshot.kind === "connecting") {
152
+ const details = { outcome: "ok", mode: "stores", stores: [], connecting: true };
153
+ return { content: CONNECTING_MESSAGE, details };
154
+ }
155
+ const diagnostics = diagnosticsOf(snapshot.droppedStores);
156
+ const stores = snapshot.stores.map((s) => s.info);
157
+ const details = { ...diagnostics, outcome: "ok", mode: "stores", stores };
158
+ if (stores.length === 0)
159
+ return { content: NO_STORES_MESSAGE, details };
160
+ return {
161
+ content: `Connected memory stores — pass an id as store to list or read one:\n${stores.map(renderStoreLine).join("\n")}`,
162
+ details,
163
+ };
164
+ }
165
+ const resolution = await resolveStore(store, signal);
166
+ if (resolution.kind !== "resolved") {
167
+ return refusedList(resolution.refusal, resolution.kind, resolution.diagnostics, store);
168
+ }
169
+ let normalizedPrefix;
170
+ if (pathPrefix !== undefined) {
171
+ normalizedPrefix = normalizeMemoryPath(pathPrefix);
172
+ const refusal = relativePathRefusal(pathPrefix) ?? validatePathPrefix(normalizedPrefix);
173
+ if (refusal)
174
+ return refusedList(refusal, "refused", resolution.diagnostics, store);
175
+ }
176
+ let raw;
177
+ try {
178
+ raw = await resolution.store.list(normalizedPrefix === undefined ? undefined : listPrefixArgument(normalizedPrefix), { signal });
179
+ }
180
+ catch (err) {
181
+ if (signal?.aborted === true)
182
+ throw err;
183
+ return refusedList(foldReaderError(err), "failed", resolution.diagnostics, store);
184
+ }
185
+ const normalized = normalizeEntries(raw);
186
+ if (normalized.kind === "malformed")
187
+ return refusedList(FAILED_ERROR, "failed", resolution.diagnostics, store);
188
+ const { page, remaining } = pageDocuments(normalized.entries, {
189
+ ...(normalizedPrefix !== undefined ? { normalizedPrefix } : {}),
190
+ ...(cursor !== undefined ? { cursor } : {}),
191
+ });
192
+ const details = {
193
+ ...diagnosticsOf(resolution.diagnostics.droppedStores ?? 0, normalized.droppedEntries),
194
+ outcome: "ok",
195
+ mode: "documents",
196
+ store,
197
+ entries: page,
198
+ remaining,
199
+ };
200
+ return { content: renderDocumentPage(page, remaining), details };
201
+ },
202
+ };
203
+ const readTool = {
204
+ name: SHARED_MEMORY_READ_TOOL_NAME,
205
+ description: READ_DESCRIPTION,
206
+ effect: "read",
207
+ defer: true,
208
+ offload: false,
209
+ contract: { contractId: "core.memory_read@1", implementationRevision: "1" },
210
+ parameters: Type.Object({
211
+ store: Type.String({
212
+ description: `Id of the connected memory store to read from (call ${SHARED_MEMORY_LIST_TOOL_NAME} with no arguments to see the connected stores).`,
213
+ }),
214
+ path: Type.String({
215
+ description: "Path of the memory document to read (e.g. /project/<project-id>/MEMORY.md).",
216
+ }),
217
+ }, { additionalProperties: false }),
218
+ execute: async (args, ctx) => {
219
+ const { store, path } = args;
220
+ const signal = ctx.signal;
221
+ const resolution = await resolveStore(store, signal);
222
+ if (resolution.kind !== "resolved") {
223
+ return refusedRead(resolution.refusal, resolution.kind, resolution.diagnostics, store, path);
224
+ }
225
+ const normalizedPath = normalizeMemoryPath(path);
226
+ const refusal = relativePathRefusal(path) ?? validateDocumentPath(normalizedPath);
227
+ if (refusal)
228
+ return refusedRead(refusal, "refused", resolution.diagnostics, store, normalizedPath);
229
+ let raw;
230
+ try {
231
+ raw = await resolution.store.read(normalizedPath, { signal });
232
+ }
233
+ catch (err) {
234
+ if (signal?.aborted === true)
235
+ throw err;
236
+ return refusedRead(foldReaderError(err), "failed", resolution.diagnostics, store, normalizedPath);
237
+ }
238
+ const normalized = normalizeReadValue(raw);
239
+ if (normalized.kind === "malformed") {
240
+ return refusedRead(FAILED_ERROR, "failed", resolution.diagnostics, store, normalizedPath);
241
+ }
242
+ if (normalized.value === null) {
243
+ const details = {
244
+ ...resolution.diagnostics,
245
+ outcome: "not_found",
246
+ store,
247
+ path: normalizedPath,
248
+ };
249
+ return errorResult(`${SHARED_MEMORY_READ_TOOL_NAME} failed: not found`, details);
250
+ }
251
+ const bytes = Buffer.byteLength(normalized.value.content, "utf8");
252
+ if (bytes > SHARED_MEMORY_READ_CAP_BYTES) {
253
+ return refusedRead({
254
+ reason: "too_large",
255
+ message: `"${normalizedPath}" is ${bytes} bytes, over the ${SHARED_MEMORY_READ_CAP_BYTES}-byte read cap, so its content is not returned.`,
256
+ }, "refused", resolution.diagnostics, store, normalizedPath);
257
+ }
258
+ const details = {
259
+ ...resolution.diagnostics,
260
+ outcome: "ok",
261
+ store,
262
+ path: normalizedPath,
263
+ bytes,
264
+ ...(normalized.value.updatedAt !== undefined ? { updatedAt: normalized.value.updatedAt } : {}),
265
+ };
266
+ return { content: renderDocument(normalized.value.content, normalized.value.updatedAt), details };
267
+ },
268
+ };
269
+ return [listTool, readTool];
270
+ }
271
+ function refusedList(refusal, outcome, diagnostics, store) {
272
+ const details = {
273
+ ...diagnostics,
274
+ outcome,
275
+ reason: refusal.reason,
276
+ ...(store !== undefined ? { store } : {}),
277
+ };
278
+ return errorResult(refusal.message, details);
279
+ }
280
+ function refusedRead(refusal, outcome, diagnostics, store, path) {
281
+ const details = {
282
+ ...diagnostics,
283
+ outcome,
284
+ reason: refusal.reason,
285
+ ...(store !== undefined ? { store } : {}),
286
+ ...(path !== undefined ? { path } : {}),
287
+ };
288
+ return errorResult(refusal.message, details);
289
+ }
@@ -0,0 +1,95 @@
1
+ export declare const SHARED_MEMORY_LIST_TOOL_NAME = "memory_list";
2
+ export declare const SHARED_MEMORY_READ_TOOL_NAME = "memory_read";
3
+ export declare const SHARED_MEMORY_TOOL_NAMES: readonly ["memory_list", "memory_read"];
4
+ export declare const SHARED_MEMORY_READ_CAP_BYTES = 102400;
5
+ export declare const SHARED_MEMORY_LIST_PAGE_SIZE = 50;
6
+ export declare const SHARED_MEMORY_MAX_STORES = 64;
7
+ export declare const SHARED_MEMORY_DESCRIPTION_MAX = 160;
8
+ export interface SharedMemoryStoreInfo {
9
+ id: string;
10
+ description: string;
11
+ writable: boolean;
12
+ }
13
+ export interface SharedMemoryDocumentEntry {
14
+ path: string;
15
+ sizeBytes?: number;
16
+ updatedAt?: string;
17
+ }
18
+ export interface SharedMemoryStoreReader {
19
+ list(prefix: string | undefined, opts: {
20
+ signal?: AbortSignal;
21
+ }): Promise<SharedMemoryDocumentEntry[]>;
22
+ read(path: string, opts: {
23
+ signal?: AbortSignal;
24
+ }): Promise<{
25
+ content: string;
26
+ updatedAt?: string;
27
+ } | null>;
28
+ }
29
+ export interface SharedMemoryRequestContext {
30
+ sessionId: string;
31
+ taskId: string;
32
+ principal?: string;
33
+ }
34
+ export type SharedMemorySnapshot = {
35
+ state: "connecting";
36
+ } | {
37
+ state: "unavailable";
38
+ message?: string;
39
+ } | {
40
+ state: "connected";
41
+ stores: ReadonlyArray<{
42
+ info: SharedMemoryStoreInfo;
43
+ reader: SharedMemoryStoreReader;
44
+ }>;
45
+ };
46
+ export interface SharedMemoryStoreProvider {
47
+ snapshot(ctx: SharedMemoryRequestContext, opts: {
48
+ signal?: AbortSignal;
49
+ }): Promise<SharedMemorySnapshot> | SharedMemorySnapshot;
50
+ }
51
+ export declare class SharedMemoryStoreError extends Error {
52
+ readonly kind: "store_not_found" | "refused" | "transient";
53
+ readonly reason?: string;
54
+ constructor(kind: SharedMemoryStoreError["kind"], opts?: {
55
+ reason?: string;
56
+ message?: string;
57
+ cause?: unknown;
58
+ });
59
+ }
60
+ export interface SharedMemoryDiagnostics {
61
+ droppedStores?: number;
62
+ droppedEntries?: number;
63
+ }
64
+ export type MemoryListDetails = SharedMemoryDiagnostics & ({
65
+ outcome: "ok";
66
+ mode: "stores";
67
+ stores: SharedMemoryStoreInfo[];
68
+ connecting?: true;
69
+ } | {
70
+ outcome: "ok";
71
+ mode: "documents";
72
+ store: string;
73
+ entries: SharedMemoryDocumentEntry[];
74
+ remaining: number;
75
+ } | {
76
+ outcome: "refused" | "failed";
77
+ reason: string;
78
+ store?: string;
79
+ });
80
+ export type MemoryReadDetails = SharedMemoryDiagnostics & ({
81
+ outcome: "ok";
82
+ store: string;
83
+ path: string;
84
+ bytes: number;
85
+ updatedAt?: string;
86
+ } | {
87
+ outcome: "not_found";
88
+ store: string;
89
+ path: string;
90
+ } | {
91
+ outcome: "refused" | "failed";
92
+ reason: string;
93
+ store?: string;
94
+ path?: string;
95
+ });
@@ -0,0 +1,18 @@
1
+ export const SHARED_MEMORY_LIST_TOOL_NAME = "memory_list";
2
+ export const SHARED_MEMORY_READ_TOOL_NAME = "memory_read";
3
+ export const SHARED_MEMORY_TOOL_NAMES = [SHARED_MEMORY_LIST_TOOL_NAME, SHARED_MEMORY_READ_TOOL_NAME];
4
+ export const SHARED_MEMORY_READ_CAP_BYTES = 102400;
5
+ export const SHARED_MEMORY_LIST_PAGE_SIZE = 50;
6
+ export const SHARED_MEMORY_MAX_STORES = 64;
7
+ export const SHARED_MEMORY_DESCRIPTION_MAX = 160;
8
+ export class SharedMemoryStoreError extends Error {
9
+ kind;
10
+ reason;
11
+ constructor(kind, opts) {
12
+ super(opts?.message ?? kind, opts?.cause !== undefined ? { cause: opts.cause } : undefined);
13
+ this.name = "SharedMemoryStoreError";
14
+ this.kind = kind;
15
+ if (opts?.reason !== undefined)
16
+ this.reason = opts.reason;
17
+ }
18
+ }
@@ -23,6 +23,9 @@ export interface TaskNotificationPayload {
23
23
  editedFiles?: import("../agents/subagent-steps.js").SubagentEditedFile[];
24
24
  resumable?: boolean;
25
25
  completionId?: string;
26
+ peer?: {
27
+ hopChain: string[];
28
+ };
26
29
  }
27
30
  export interface ExternalNotificationInput {
28
31
  task_id: string;
@@ -120,7 +120,7 @@ export declare function deliverToRunningAgentLane(core: DurableAgentCore, id: st
120
120
  disposition: "queued" | "parked" | "pending";
121
121
  } | {
122
122
  ok: false;
123
- reason: "not_found" | "not_running" | "no_channel";
123
+ reason: "not_found" | "not_running" | "no_channel" | "queue_full";
124
124
  }>;
125
125
  export declare function runningBackgroundAgentLabelsLane(core: DurableAgentCore, access: TaskAccess): string[];
126
126
  export declare function runningAgentFooterLane(core: DurableAgentCore, access: TaskAccess): {
@@ -982,6 +982,7 @@ export async function reviveBackgroundAgentLane(core, id, access, abort) {
982
982
  handle.errorRetryAfterMs = undefined;
983
983
  handle.resultIsPartial = undefined;
984
984
  handle.stopSource = undefined;
985
+ handle.stoppedBy = undefined;
985
986
  handle.completionId = undefined;
986
987
  handle.terminalNotified = undefined;
987
988
  handle.reviveCycle = (handle.reviveCycle ?? 0) + 1;
@@ -1102,7 +1103,7 @@ export async function deliverToRunningAgentLane(core, id, access, notification,
1102
1103
  return { ok: false, reason: "no_channel" };
1103
1104
  const q = (handle.preAttachQueue ??= []);
1104
1105
  if (q.length >= 8)
1105
- return { ok: false, reason: "no_channel" };
1106
+ return { ok: false, reason: "queue_full" };
1106
1107
  return bounded(new Promise((resolve) => {
1107
1108
  q.push([notification, opts, resolve]);
1108
1109
  }), { ok: true, disposition: "pending" });
@@ -195,7 +195,7 @@ export declare class TaskRegistry {
195
195
  disposition: "queued" | "parked" | "pending";
196
196
  } | {
197
197
  ok: false;
198
- reason: "not_found" | "not_running" | "no_channel";
198
+ reason: "not_found" | "not_running" | "no_channel" | "queue_full";
199
199
  }>;
200
200
  runningBackgroundAgentLabels(access: TaskAccess): string[];
201
201
  private pollBackgroundAgent;
@@ -957,6 +957,8 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
957
957
  for (const [id, handle] of this.handles) {
958
958
  if (handle.status === "running" || handle.status === "pending")
959
959
  continue;
960
+ if (this.claimingHandles.has(id) || this.reapingHandles.has(id))
961
+ continue;
960
962
  if (now - handle.updatedAt < terminalTtlMs)
961
963
  continue;
962
964
  this.handles.delete(id);
@@ -100,6 +100,15 @@ export type AskOutcome = boolean | "unavailable" | {
100
100
  };
101
101
  export declare function withDelegationProvenance(onAsk: OnAsk, delegation: AskDelegationProvenance): OnAsk;
102
102
  export declare function askApproverIdentity(onAsk: unknown): unknown;
103
+ export declare function tryCloneArgs<T>(v: T): {
104
+ ok: true;
105
+ value: T;
106
+ } | {
107
+ ok: false;
108
+ reason: string;
109
+ cause?: unknown;
110
+ };
111
+ export declare function describeThrown(err: unknown): string;
103
112
  export type ResolvedAsk = PermissionResult & {
104
113
  approverUnavailable?: true;
105
114
  presentedInput?: unknown;
@@ -2,6 +2,7 @@ import { homedir } from "node:os";
2
2
  import { isAbsolute, join, normalize as normalizePath, sep } from "node:path";
3
3
  import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
4
4
  import { boundInputHashOf } from "./canonical-json.js";
5
+ import { inlineUntrusted } from "./untrusted-text.js";
5
6
  import { writeTargetPath } from "../tools/fs/safety.js";
6
7
  export function decisionText(d) {
7
8
  return d.message;
@@ -90,7 +91,7 @@ export function createApprovalPolicy(opts) {
90
91
  catch (err) {
91
92
  return {
92
93
  action: "deny",
93
- message: `approval errored for "${req.toolName}": ${err instanceof Error ? err.message : String(err)}`,
94
+ message: `approval errored for "${req.toolName}": ${describeThrown(err)}`,
94
95
  };
95
96
  }
96
97
  const okRaw = ok;
@@ -528,17 +529,36 @@ export function withDelegationProvenance(onAsk, delegation) {
528
529
  export function askApproverIdentity(onAsk) {
529
530
  return typeof onAsk === "function" ? (delegatedApproverRoot.get(onAsk) ?? onAsk) : onAsk;
530
531
  }
531
- function tryCloneArgs(v) {
532
+ export function tryCloneArgs(v) {
532
533
  try {
533
534
  const value = structuredClone(v);
534
535
  if (containsSharedMemory(value))
535
- return { ok: false };
536
+ return { ok: false, reason: "the value carries shared memory (SharedArrayBuffer), which a clone still aliases" };
536
537
  return { ok: true, value };
537
538
  }
539
+ catch (err) {
540
+ return { ok: false, reason: describeThrown(err), cause: err };
541
+ }
542
+ }
543
+ export function describeThrown(err) {
544
+ try {
545
+ if (typeof err === "string")
546
+ return containThrownText(err);
547
+ if (err instanceof Error) {
548
+ const message = err.message;
549
+ if (typeof message === "string" && message !== "")
550
+ return containThrownText(message);
551
+ }
552
+ return `a non-Error value was thrown (${err === null ? "null" : typeof err})`;
553
+ }
538
554
  catch {
539
- return { ok: false };
555
+ return "the value could not be described";
540
556
  }
541
557
  }
558
+ const THROWN_TEXT_MAX = 300;
559
+ function containThrownText(text) {
560
+ return inlineUntrusted(text, THROWN_TEXT_MAX);
561
+ }
542
562
  function containsSharedMemory(v, seen = new Set()) {
543
563
  if (typeof SharedArrayBuffer !== "undefined" && v instanceof SharedArrayBuffer)
544
564
  return true;
@@ -593,7 +613,7 @@ export async function resolveAsk(req, onAsk, signal) {
593
613
  if (!presented.ok) {
594
614
  return {
595
615
  action: "deny",
596
- message: `approval for "${req.toolName}" could not present the args safely (unclonable value) — denied fail-closed`,
616
+ message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${presented.reason}) — denied fail-closed`,
597
617
  decisionReason: "mode",
598
618
  };
599
619
  }
@@ -603,7 +623,7 @@ export async function resolveAsk(req, onAsk, signal) {
603
623
  if (!approverView.ok) {
604
624
  return {
605
625
  action: "deny",
606
- message: `approval for "${req.toolName}" could not present the args safely (unclonable value) — denied fail-closed`,
626
+ message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${approverView.reason}) — denied fail-closed`,
607
627
  decisionReason: "mode",
608
628
  };
609
629
  }
@@ -612,7 +632,7 @@ export async function resolveAsk(req, onAsk, signal) {
612
632
  catch (err) {
613
633
  return {
614
634
  action: "deny",
615
- message: `approval errored for "${req.toolName}": ${err instanceof Error ? err.message : String(err)}`,
635
+ message: `approval errored for "${req.toolName}": ${describeThrown(err)}`,
616
636
  decisionReason: "mode",
617
637
  };
618
638
  }
@@ -635,7 +655,7 @@ export async function resolveAsk(req, onAsk, signal) {
635
655
  if (!edit.ok) {
636
656
  return {
637
657
  action: "deny",
638
- message: `the approved edit for "${req.toolName}" is not safely clonable — denied fail-closed`,
658
+ message: `the approved edit for "${req.toolName}" is not safely clonable (${edit.reason}) — denied fail-closed`,
639
659
  decisionReason: "mode",
640
660
  };
641
661
  }
@@ -90,8 +90,13 @@ export interface ToolExecuteContext {
90
90
  outcome: import("./checkpoint-store.js").ResumeOutcome;
91
91
  inheritedGate?: import("./runner/prepare-task.js").InheritedGate;
92
92
  };
93
+ peerSeed?: {
94
+ hopChain: string[];
95
+ };
93
96
  };
94
97
  spawnedAgentName?: string;
98
+ peerSelfRef?: import("../agents/peer-admission.js").PeerSelfRef;
99
+ peerInboundChainRef?: import("../agents/peer-admission.js").PeerInboundChainRef;
95
100
  roster?: import("../agents/roster-store.js").RosterStore;
96
101
  hostSessionFork?: () => Promise<{
97
102
  sessionId: string;
@@ -169,6 +174,7 @@ export interface HandsBandOptions {
169
174
  readImageDownsampler?: ((input: Buffer, mimeType: string) => Promise<import("./mcp.js").DownsampledImage | undefined>) | false;
170
175
  beforeWrite?: BeforeWriteHook;
171
176
  autoBackgroundOnTimeout?: boolean;
177
+ readCyberReminder?: boolean;
172
178
  }
173
179
  export interface AgentDefinition {
174
180
  name: string;
@@ -792,6 +798,7 @@ export interface RunnerDeps {
792
798
  workflowRunStore?: import("./workflow-run-store.js").WorkflowRunStore;
793
799
  backgroundAgentStore?: import("./background-agent-store.js").BackgroundAgentStore;
794
800
  mailboxStore?: import("./mailbox-store.js").MailboxStore;
801
+ peerAdmission?: Partial<import("../agents/peer-admission.js").PeerAdmissionConfig>;
795
802
  usageWindows?: readonly import("./usage-window-store.js").UsageWindow[];
796
803
  usageWindowStore?: import("./usage-window-store.js").UsageWindowStore;
797
804
  workflowJournalStore?: import("./workflow-journal-store.js").WorkflowJournalStore;
@@ -802,6 +809,7 @@ export interface RunnerDeps {
802
809
  brainCallGuardrailMs?: import("../brain/timeout.js").BrainCallGuardrailKnob;
803
810
  now?: () => number;
804
811
  memoryBackend?: import("./memory-engine/types.js").MemoryBackend;
812
+ sharedMemoryStores?: import("./shared-memory/types.js").SharedMemoryStoreProvider;
805
813
  memoryEngineDir?: string;
806
814
  onMemoryHarvestReport?: (report: import("./memory-engine/types.js").HarvestReport, info: {
807
815
  sessionId: string;
@@ -23,5 +23,6 @@ export declare function cutEngineSegments(text: string, segments: unknown): {
23
23
  };
24
24
  export declare function flattenableUserText(content: unknown): string | undefined;
25
25
  export declare function defuseFenceMarkers(body: string): string;
26
+ export declare function defuseControlChars(text: string): string;
26
27
  export declare function inlineUntrusted(text: string, maxLen?: number): string;
27
28
  export declare function delimitUntrusted(label: string, text: string, maxBody?: number): string;
@@ -87,6 +87,16 @@ export function flattenableUserText(content) {
87
87
  export function defuseFenceMarkers(body) {
88
88
  return body.replace(/<{3,}|>{3,}/g, (run) => run.match(/.{1,2}/g).join(ZWSP));
89
89
  }
90
+ const REPLACEMENT = "�";
91
+ export function defuseControlChars(text) {
92
+ let out = "";
93
+ for (const ch of text.replace(/\r\n?/g, "\n")) {
94
+ const cp = ch.codePointAt(0) ?? 0;
95
+ const control = cp !== 9 && cp !== 10 && (cp < 32 || (cp >= 127 && cp <= 159));
96
+ out += control ? REPLACEMENT : ch;
97
+ }
98
+ return out;
99
+ }
90
100
  const LABEL_MAX = 160;
91
101
  export function inlineUntrusted(text, maxLen = LABEL_MAX) {
92
102
  const oneLine = text.replace(/[\s\u0000-\u001f\u007f\u0085]+/g, " ").trim();
@@ -19,9 +19,9 @@ export function deriveAskEffective(form, parkEffective) {
19
19
  case "callback":
20
20
  return "human_reachable";
21
21
  case "allow":
22
- return "auto_allow";
22
+ return parkEffective === true ? "park_only" : parkEffective === "unresolved" ? "unresolved" : "auto_allow";
23
23
  case "deny":
24
- return "auto_deny";
24
+ return parkEffective === true ? "park_only" : parkEffective === "unresolved" ? "unresolved" : "auto_deny";
25
25
  case "absent":
26
26
  return parkEffective === true ? "park_only" : parkEffective === "unresolved" ? "unresolved" : "auto_deny";
27
27
  default: {
@@ -36,6 +36,7 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
36
36
  private model;
37
37
  private thinkingLevel;
38
38
  onUndrainedEngineNotes?: (payloads: unknown[]) => void;
39
+ onEngineNoteConsumed?: (payload: unknown) => void;
39
40
  recoverUndrainedEngineNotes(): void;
40
41
  private sweepUndrainedEngineNotes;
41
42
  private systemPrompt;