@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.
- package/CHANGELOG.md +224 -0
- package/dist/agents/peer-admission.d.ts +58 -0
- package/dist/agents/peer-admission.js +175 -0
- package/dist/agents/retain-ledger.d.ts +1 -1
- package/dist/agents/retain-ledger.js +9 -1
- package/dist/agents/send-message-tool.d.ts +8 -0
- package/dist/agents/send-message-tool.js +171 -21
- package/dist/agents/subagent.d.ts +13 -0
- package/dist/agents/subagent.js +90 -5
- package/dist/core/ask-question.js +16 -1
- package/dist/core/canonical-json.js +176 -14
- package/dist/core/checkpoint-store.d.ts +14 -0
- package/dist/core/checkpoint-store.js +73 -0
- package/dist/core/hooks.d.ts +4 -1
- package/dist/core/hooks.js +24 -6
- package/dist/core/mailbox-store.d.ts +2 -0
- package/dist/core/mailbox-store.js +2 -2
- package/dist/core/mcp.d.ts +1 -0
- package/dist/core/mcp.js +15 -3
- package/dist/core/runner/prepare-task.d.ts +4 -0
- package/dist/core/runner/prepare-task.js +169 -46
- package/dist/core/runner/runtask.js +13 -1
- package/dist/core/runner/turn-attachments.d.ts +2 -1
- package/dist/core/runner/turn-attachments.js +9 -6
- package/dist/core/session-reconcile.js +19 -1
- package/dist/core/shared-memory/contract.d.ts +17 -0
- package/dist/core/shared-memory/contract.js +138 -0
- package/dist/core/shared-memory/normalize.d.ts +73 -0
- package/dist/core/shared-memory/normalize.js +259 -0
- package/dist/core/shared-memory/tools.d.ts +7 -0
- package/dist/core/shared-memory/tools.js +289 -0
- package/dist/core/shared-memory/types.d.ts +95 -0
- package/dist/core/shared-memory/types.js +18 -0
- package/dist/core/task-notification.d.ts +3 -0
- package/dist/core/task-registry-agent.d.ts +1 -1
- package/dist/core/task-registry-agent.js +2 -1
- package/dist/core/task-registry.d.ts +1 -1
- package/dist/core/task-registry.js +2 -0
- package/dist/core/tool-policy.d.ts +9 -0
- package/dist/core/tool-policy.js +28 -8
- package/dist/core/types.d.ts +8 -0
- package/dist/core/untrusted-text.d.ts +1 -0
- package/dist/core/untrusted-text.js +10 -0
- package/dist/core/wiring-manifest.js +2 -2
- package/dist/engine/harness/agent-harness.d.ts +1 -0
- package/dist/engine/harness/agent-harness.js +21 -2
- package/dist/engine/llm/validation.js +121 -5
- package/dist/engine/loop/agent-loop.d.ts +2 -0
- package/dist/engine/loop/agent-loop.js +17 -4
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/prompts/supervisor.d.ts +1 -1
- package/dist/prompts/supervisor.js +1 -1
- package/dist/stores/cc/mailbox-store.js +4 -0
- package/dist/stores/file/checkpoint-store.d.ts +1 -0
- package/dist/stores/file/checkpoint-store.js +1 -0
- package/dist/stores/file/mailbox-store.js +2 -2
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
- package/dist/tools/fs/bash-readonly-classifier.js +11 -10
- package/dist/tools/fs/fs-bash.js +6 -6
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-read.js +4 -3
- package/dist/tools/fs/fs-shared.d.ts +3 -0
- package/dist/tools/fs/fs-shared.js +8 -1
- package/dist/tools/fs/fs-write.js +8 -8
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/fs/safety.d.ts +1 -0
- package/dist/tools/fs/safety.js +9 -2
- 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: "
|
|
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;
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -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}": ${
|
|
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
|
|
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}": ${
|
|
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
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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;
|