@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
|
@@ -39,20 +39,10 @@ function encode(value, depth = 0) {
|
|
|
39
39
|
out += encode(el, depth + 1);
|
|
40
40
|
return `${out}]`;
|
|
41
41
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
let encoded;
|
|
47
|
-
try {
|
|
48
|
-
encoded = encode(obj[k], depth + 1);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
encoded = "<unreadable-property>";
|
|
52
|
-
}
|
|
53
|
-
out += `${Buffer.byteLength(k, "utf8")}:${k}=${encoded}`;
|
|
54
|
-
}
|
|
55
|
-
return `${out}}`;
|
|
42
|
+
const branded = encodeBranded(value, depth);
|
|
43
|
+
if (branded !== undefined)
|
|
44
|
+
return branded;
|
|
45
|
+
return encodeOwnKeys(value, depth);
|
|
56
46
|
}
|
|
57
47
|
catch {
|
|
58
48
|
return "<unserializable>";
|
|
@@ -62,6 +52,178 @@ function encode(value, depth = 0) {
|
|
|
62
52
|
return "U";
|
|
63
53
|
}
|
|
64
54
|
}
|
|
55
|
+
function encodeOwnKeys(source, depth, skipIndicesBelow = 0) {
|
|
56
|
+
const obj = source;
|
|
57
|
+
const all = Object.keys(obj);
|
|
58
|
+
const keys = (skipIndicesBelow > 0 ? all.filter((k) => !isIndexBelow(k, skipIndicesBelow)) : all).sort();
|
|
59
|
+
let out = `o${keys.length}:{`;
|
|
60
|
+
for (const k of keys) {
|
|
61
|
+
let encoded;
|
|
62
|
+
try {
|
|
63
|
+
encoded = encode(obj[k], depth + 1);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
encoded = "<unreadable-property>";
|
|
67
|
+
}
|
|
68
|
+
out += `${Buffer.byteLength(k, "utf8")}:${k}=${encoded}`;
|
|
69
|
+
}
|
|
70
|
+
return `${out}}`;
|
|
71
|
+
}
|
|
72
|
+
function isIndexBelow(key, limit) {
|
|
73
|
+
const n = Number(key);
|
|
74
|
+
return Number.isInteger(n) && n >= 0 && n < limit && String(n) === key;
|
|
75
|
+
}
|
|
76
|
+
function brandOf(value) {
|
|
77
|
+
try {
|
|
78
|
+
const s = Object.prototype.toString.call(value);
|
|
79
|
+
return s.startsWith("[object ") && s.endsWith("]") ? s.slice(8, -1) : "Object";
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return "Object";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function encodeBranded(value, depth) {
|
|
86
|
+
const brand = brandOf(value);
|
|
87
|
+
if (brand === "Object")
|
|
88
|
+
return undefined;
|
|
89
|
+
if (ArrayBuffer.isView(value)) {
|
|
90
|
+
try {
|
|
91
|
+
const { buffer, byteOffset, byteLength, elements, family } = viewRange(value);
|
|
92
|
+
const bytesToken = digestBytes(new Uint8Array(buffer, byteOffset, byteLength));
|
|
93
|
+
return `V:${family}:${brand}:${byteLength}:${bytesToken}${encodeOwnKeys(value, depth, elements)}`;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const own = encodeOwnKeys(value, depth);
|
|
99
|
+
try {
|
|
100
|
+
switch (brand) {
|
|
101
|
+
case "Date": {
|
|
102
|
+
const t = Date.prototype.getTime.call(value);
|
|
103
|
+
return `D:${Number.isNaN(t) ? "Invalid" : encodeNumber(t)}${own}`;
|
|
104
|
+
}
|
|
105
|
+
case "Map": {
|
|
106
|
+
const size = Number(mapSizeGetter.call(value));
|
|
107
|
+
let out = `m${size}:{`;
|
|
108
|
+
let seen = 0;
|
|
109
|
+
for (const [k, v] of mapEntries.call(value)) {
|
|
110
|
+
if (seen++ >= size)
|
|
111
|
+
break;
|
|
112
|
+
out += `${encode(k, depth + 1)}=${encode(v, depth + 1)}`;
|
|
113
|
+
}
|
|
114
|
+
return `${out}}${own}`;
|
|
115
|
+
}
|
|
116
|
+
case "Set": {
|
|
117
|
+
const size = Number(setSizeGetter.call(value));
|
|
118
|
+
let out = `t${size}:[`;
|
|
119
|
+
let seen = 0;
|
|
120
|
+
for (const v of setValues.call(value)) {
|
|
121
|
+
if (seen++ >= size)
|
|
122
|
+
break;
|
|
123
|
+
out += encode(v, depth + 1);
|
|
124
|
+
}
|
|
125
|
+
return `${out}]${own}`;
|
|
126
|
+
}
|
|
127
|
+
case "RegExp": {
|
|
128
|
+
const source = String(regexpSourceGetter.call(value));
|
|
129
|
+
const flags = String(regexpFlagsGetter.call(value));
|
|
130
|
+
return `r${Buffer.byteLength(source, "utf8")}:${source}/${flags}${own}`;
|
|
131
|
+
}
|
|
132
|
+
case "Error": {
|
|
133
|
+
const name = String(value.name);
|
|
134
|
+
const message = String(value.message);
|
|
135
|
+
return `E${Buffer.byteLength(name, "utf8")}:${name}:${Buffer.byteLength(message, "utf8")}:${message}${own}`;
|
|
136
|
+
}
|
|
137
|
+
case "URL": {
|
|
138
|
+
const href = String(urlHrefGetter.call(value));
|
|
139
|
+
return `L${Buffer.byteLength(href, "utf8")}:${href}${own}`;
|
|
140
|
+
}
|
|
141
|
+
case "Boolean":
|
|
142
|
+
case "Number":
|
|
143
|
+
case "String":
|
|
144
|
+
case "BigInt":
|
|
145
|
+
case "Symbol": {
|
|
146
|
+
const unboxed = boxedValueOf[brand].call(value);
|
|
147
|
+
return `B:${encode(unboxed, depth + 1)}${own}`;
|
|
148
|
+
}
|
|
149
|
+
case "ArrayBuffer":
|
|
150
|
+
case "SharedArrayBuffer": {
|
|
151
|
+
const byteLength = Number((brand === "ArrayBuffer" ? arrayBufferByteLengthGetter : sharedArrayBufferByteLengthGetter).call(value));
|
|
152
|
+
return `A:${brand}:${byteLength}:${digestBytes(new Uint8Array(value))}${own}`;
|
|
153
|
+
}
|
|
154
|
+
default:
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
}
|
|
160
|
+
return `x${Buffer.byteLength(brand, "utf8")}:${brand}${own}`;
|
|
161
|
+
}
|
|
162
|
+
function primordialGetter(proto, key) {
|
|
163
|
+
const get = Object.getOwnPropertyDescriptor(proto, key)?.get;
|
|
164
|
+
if (!get) {
|
|
165
|
+
return () => {
|
|
166
|
+
throw new Error(`no primordial getter for ${key}`);
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return get;
|
|
170
|
+
}
|
|
171
|
+
const mapEntries = Map.prototype.entries;
|
|
172
|
+
const setValues = Set.prototype.values;
|
|
173
|
+
const mapSizeGetter = primordialGetter(Map.prototype, "size");
|
|
174
|
+
const setSizeGetter = primordialGetter(Set.prototype, "size");
|
|
175
|
+
const regexpSourceGetter = primordialGetter(RegExp.prototype, "source");
|
|
176
|
+
const regexpFlagsGetter = primordialGetter(RegExp.prototype, "flags");
|
|
177
|
+
const urlHrefGetter = primordialGetter(URL.prototype, "href");
|
|
178
|
+
const arrayBufferByteLengthGetter = primordialGetter(ArrayBuffer.prototype, "byteLength");
|
|
179
|
+
const sharedArrayBufferByteLengthGetter = typeof SharedArrayBuffer !== "undefined"
|
|
180
|
+
? primordialGetter(SharedArrayBuffer.prototype, "byteLength")
|
|
181
|
+
: primordialGetter({}, "byteLength");
|
|
182
|
+
const boxedValueOf = {
|
|
183
|
+
Boolean: Boolean.prototype.valueOf,
|
|
184
|
+
Number: Number.prototype.valueOf,
|
|
185
|
+
String: String.prototype.valueOf,
|
|
186
|
+
BigInt: BigInt.prototype.valueOf,
|
|
187
|
+
Symbol: Symbol.prototype.valueOf,
|
|
188
|
+
};
|
|
189
|
+
const typedArrayProto = Object.getPrototypeOf(Uint8Array.prototype);
|
|
190
|
+
const typedArrayLengthGetter = primordialGetter(typedArrayProto, "length");
|
|
191
|
+
const viewGetters = {
|
|
192
|
+
typed: {
|
|
193
|
+
buffer: primordialGetter(typedArrayProto, "buffer"),
|
|
194
|
+
byteOffset: primordialGetter(typedArrayProto, "byteOffset"),
|
|
195
|
+
byteLength: primordialGetter(typedArrayProto, "byteLength"),
|
|
196
|
+
},
|
|
197
|
+
dataView: {
|
|
198
|
+
buffer: primordialGetter(DataView.prototype, "buffer"),
|
|
199
|
+
byteOffset: primordialGetter(DataView.prototype, "byteOffset"),
|
|
200
|
+
byteLength: primordialGetter(DataView.prototype, "byteLength"),
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
function viewRange(view) {
|
|
204
|
+
for (const family of ["typed", "dataView"]) {
|
|
205
|
+
const g = viewGetters[family];
|
|
206
|
+
try {
|
|
207
|
+
const range = {
|
|
208
|
+
buffer: g.buffer.call(view),
|
|
209
|
+
byteOffset: Number(g.byteOffset.call(view)),
|
|
210
|
+
byteLength: Number(g.byteLength.call(view)),
|
|
211
|
+
elements: 0,
|
|
212
|
+
family,
|
|
213
|
+
};
|
|
214
|
+
if (family === "typed")
|
|
215
|
+
range.elements = Number(typedArrayLengthGetter.call(view));
|
|
216
|
+
return range;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
throw new Error("not a readable ArrayBuffer view");
|
|
223
|
+
}
|
|
224
|
+
function digestBytes(bytes) {
|
|
225
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
226
|
+
}
|
|
65
227
|
function encodeNumber(n) {
|
|
66
228
|
if (Number.isNaN(n))
|
|
67
229
|
return "NaN";
|
|
@@ -283,9 +283,22 @@ export declare class CheckpointError extends Error {
|
|
|
283
283
|
} | undefined);
|
|
284
284
|
}
|
|
285
285
|
export type StoreDurability = "durable" | "process-local";
|
|
286
|
+
export type StoreFidelity = "structured-clone" | "json";
|
|
287
|
+
export declare function resolveDeclaredFidelity(store: {
|
|
288
|
+
readonly fidelity?: StoreFidelity;
|
|
289
|
+
} | undefined, storeName: string): StoreFidelity;
|
|
290
|
+
export declare function encodeAtFidelity(fidelity: StoreFidelity, value: unknown): {
|
|
291
|
+
ok: true;
|
|
292
|
+
value: unknown;
|
|
293
|
+
} | {
|
|
294
|
+
ok: false;
|
|
295
|
+
cause: unknown;
|
|
296
|
+
};
|
|
297
|
+
export declare function samePlainValue(a: unknown, b: unknown): boolean;
|
|
286
298
|
export interface CheckpointStore {
|
|
287
299
|
readonly retention?: import("./retention.js").RetentionDeclaration;
|
|
288
300
|
readonly durability?: StoreDurability;
|
|
301
|
+
readonly fidelity?: StoreFidelity;
|
|
289
302
|
put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
|
|
290
303
|
get(token: CheckpointToken): Promise<Checkpoint | null>;
|
|
291
304
|
resolve(token: CheckpointToken, scope: string, outcome: ResumeOutcome, expect?: ResolveExpectation): Promise<boolean>;
|
|
@@ -315,6 +328,7 @@ export type CheckpointFaultMode = "resolve-after-commit" | "resolve-before-commi
|
|
|
315
328
|
export declare class InMemoryCheckpointStore implements CheckpointStore {
|
|
316
329
|
readonly retention: "none";
|
|
317
330
|
readonly durability: "process-local";
|
|
331
|
+
readonly fidelity: "structured-clone";
|
|
318
332
|
private cps;
|
|
319
333
|
private fault;
|
|
320
334
|
put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
|
|
@@ -218,6 +218,78 @@ export class CheckpointError extends Error {
|
|
|
218
218
|
this.name = "CheckpointError";
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
+
export function resolveDeclaredFidelity(store, storeName) {
|
|
222
|
+
const declared = store?.fidelity;
|
|
223
|
+
if (declared === undefined)
|
|
224
|
+
return "json";
|
|
225
|
+
if (declared === "structured-clone" || declared === "json")
|
|
226
|
+
return declared;
|
|
227
|
+
const e = new Error(`${storeName}.fidelity declares ${JSON.stringify(declared)} — not a recognized StoreFidelity ` +
|
|
228
|
+
`("structured-clone" | "json"). Fix the declaration; an unparseable fidelity cannot be folded to either arm.`);
|
|
229
|
+
e.code = "config.store_fidelity_invalid";
|
|
230
|
+
throw e;
|
|
231
|
+
}
|
|
232
|
+
export function encodeAtFidelity(fidelity, value) {
|
|
233
|
+
if (fidelity === "structured-clone")
|
|
234
|
+
return { ok: true, value };
|
|
235
|
+
try {
|
|
236
|
+
const encoded = JSON.stringify(value);
|
|
237
|
+
return { ok: true, value: encoded === undefined ? undefined : JSON.parse(encoded) };
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
return { ok: false, cause: err };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const MAX_SAME_VALUE_DEPTH = 256;
|
|
244
|
+
export function samePlainValue(a, b) {
|
|
245
|
+
return sameValueAt(a, b, 0, new Map(), new Map());
|
|
246
|
+
}
|
|
247
|
+
function sameValueAt(a, b, depth, aToB, bToA) {
|
|
248
|
+
if (Object.is(a, b))
|
|
249
|
+
return true;
|
|
250
|
+
if (depth >= MAX_SAME_VALUE_DEPTH)
|
|
251
|
+
return false;
|
|
252
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object")
|
|
253
|
+
return false;
|
|
254
|
+
try {
|
|
255
|
+
const pairedWithA = aToB.get(a);
|
|
256
|
+
const pairedWithB = bToA.get(b);
|
|
257
|
+
if (pairedWithA !== undefined || pairedWithB !== undefined)
|
|
258
|
+
return pairedWithA === b && pairedWithB === a;
|
|
259
|
+
aToB.set(a, b);
|
|
260
|
+
bToA.set(b, a);
|
|
261
|
+
const proto = Object.getPrototypeOf(a);
|
|
262
|
+
if (proto !== Object.getPrototypeOf(b))
|
|
263
|
+
return false;
|
|
264
|
+
if (proto !== Object.prototype && proto !== Array.prototype)
|
|
265
|
+
return false;
|
|
266
|
+
if (Object.isExtensible(a) !== Object.isExtensible(b))
|
|
267
|
+
return false;
|
|
268
|
+
const ka = Reflect.ownKeys(a);
|
|
269
|
+
const kb = Reflect.ownKeys(b);
|
|
270
|
+
if (ka.length !== kb.length)
|
|
271
|
+
return false;
|
|
272
|
+
for (let i = 0; i < ka.length; i++) {
|
|
273
|
+
const k = ka[i];
|
|
274
|
+
if (k !== kb[i])
|
|
275
|
+
return false;
|
|
276
|
+
const da = Object.getOwnPropertyDescriptor(a, k);
|
|
277
|
+
const db = Object.getOwnPropertyDescriptor(b, k);
|
|
278
|
+
if (da === undefined || db === undefined)
|
|
279
|
+
return false;
|
|
280
|
+
if (!("value" in da) || !("value" in db))
|
|
281
|
+
return false;
|
|
282
|
+
if (da.enumerable !== db.enumerable || da.writable !== db.writable || da.configurable !== db.configurable)
|
|
283
|
+
return false;
|
|
284
|
+
if (!sameValueAt(da.value, db.value, depth + 1, aToB, bToA))
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
221
293
|
export function resolveCheckpointStore(spec, deps) {
|
|
222
294
|
if (spec.checkpointStore === null)
|
|
223
295
|
return undefined;
|
|
@@ -385,6 +457,7 @@ export function checkpointOccMatches(cp, expect) {
|
|
|
385
457
|
export class InMemoryCheckpointStore {
|
|
386
458
|
retention = "none";
|
|
387
459
|
durability = "process-local";
|
|
460
|
+
fidelity = "structured-clone";
|
|
388
461
|
cps = new Map();
|
|
389
462
|
fault = null;
|
|
390
463
|
async put(token, cp) {
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -123,6 +123,9 @@ export type ContentAskOutcome = {
|
|
|
123
123
|
code: string;
|
|
124
124
|
presentedInput?: unknown;
|
|
125
125
|
};
|
|
126
|
+
export interface ParkAttemptFailed {
|
|
127
|
+
parkFailed: string;
|
|
128
|
+
}
|
|
126
129
|
export interface ToolGateInput {
|
|
127
130
|
onNotifyError?: (failure: import("./safe-notify.js").SafeNotifyFailure) => void;
|
|
128
131
|
event: {
|
|
@@ -134,7 +137,7 @@ export interface ToolGateInput {
|
|
|
134
137
|
hookEnv?: HookEnvCapabilities;
|
|
135
138
|
adjudicate?: (req: ToolCallRequest) => Promise<PermissionResult>;
|
|
136
139
|
resolveAsk: (decision: PermissionResult, req: ToolCallRequest) => Promise<ResolvedAsk>;
|
|
137
|
-
suspendAsk?: (req: ToolCallRequest, postHookArgs: unknown, safety?: import("./checkpoint-store.js").SafetyAxis, liveFaceUnavailable?: boolean) => Promise<ToolGateResult["suspend"] | undefined>;
|
|
140
|
+
suspendAsk?: (req: ToolCallRequest, postHookArgs: unknown, safety?: import("./checkpoint-store.js").SafetyAxis, liveFaceUnavailable?: boolean) => Promise<ToolGateResult["suspend"] | ParkAttemptFailed | undefined>;
|
|
138
141
|
resolveContentAsk?: (req: ToolCallRequest) => Promise<ContentAskOutcome>;
|
|
139
142
|
egress?: boolean;
|
|
140
143
|
irreversibility?: "never" | "maybe" | "always";
|
package/dist/core/hooks.js
CHANGED
|
@@ -78,6 +78,14 @@ export function createHookEnvCapabilities(env) {
|
|
|
78
78
|
export function formatHookFeedback(text) {
|
|
79
79
|
return `<system-reminder>\n${text}\n</system-reminder>`;
|
|
80
80
|
}
|
|
81
|
+
const PARK_FAILURE_CAUSE_MAX = 600;
|
|
82
|
+
function withParkFailureCause(reason, parkFailed) {
|
|
83
|
+
if (parkFailed === undefined)
|
|
84
|
+
return reason;
|
|
85
|
+
return (`${reason} — note: a durable approval park was attempted for this call FIRST and could not be minted ` +
|
|
86
|
+
`(${inlineUntrusted(parkFailed, PARK_FAILURE_CAUSE_MAX)}), so the refusal above is what the fallback had ` +
|
|
87
|
+
`left to say, not the reason the call stopped.`);
|
|
88
|
+
}
|
|
81
89
|
function preToolUseCrashReason(subject, err) {
|
|
82
90
|
const raw = err instanceof Error ? err.message.trim() || err.name : String(err);
|
|
83
91
|
const cause = inlineUntrusted(raw, 200);
|
|
@@ -126,6 +134,7 @@ export async function runToolGate(input) {
|
|
|
126
134
|
let currentInput = event.input;
|
|
127
135
|
const preToolContext = [];
|
|
128
136
|
let hookAsk;
|
|
137
|
+
let parkFailed;
|
|
129
138
|
const notifier = createSafeNotifier(input.onNotifyError !== undefined ? { onError: input.onNotifyError } : undefined);
|
|
130
139
|
if (preToolUse) {
|
|
131
140
|
let r;
|
|
@@ -241,7 +250,10 @@ export async function runToolGate(input) {
|
|
|
241
250
|
if (suspendAsk && decision.action === "ask") {
|
|
242
251
|
const suspended = await suspendAsk(req, currentInput, safety);
|
|
243
252
|
if (suspended) {
|
|
244
|
-
|
|
253
|
+
if ("parkFailed" in suspended)
|
|
254
|
+
parkFailed = suspended.parkFailed;
|
|
255
|
+
else
|
|
256
|
+
return { suspend: suspended, preToolContext };
|
|
245
257
|
}
|
|
246
258
|
}
|
|
247
259
|
if (decision.action === "ask" && req.toolName === ASK_USER_QUESTION_TOOL_NAME) {
|
|
@@ -254,10 +266,13 @@ export async function runToolGate(input) {
|
|
|
254
266
|
currentInput = outcome.presentedInput;
|
|
255
267
|
req.args = outcome.presentedInput;
|
|
256
268
|
}
|
|
257
|
-
if (suspendAsk && outcome.parkDeclined) {
|
|
269
|
+
if (suspendAsk && outcome.parkDeclined && parkFailed === undefined) {
|
|
258
270
|
const suspended = await suspendAsk(req, currentInput, safety, true);
|
|
259
271
|
if (suspended) {
|
|
260
|
-
|
|
272
|
+
if ("parkFailed" in suspended)
|
|
273
|
+
parkFailed = suspended.parkFailed;
|
|
274
|
+
else
|
|
275
|
+
return { suspend: suspended, preToolContext };
|
|
261
276
|
}
|
|
262
277
|
}
|
|
263
278
|
decision = {
|
|
@@ -278,10 +293,13 @@ export async function runToolGate(input) {
|
|
|
278
293
|
if (decision.action === "ask") {
|
|
279
294
|
const resolved = await resolveAsk(decision, req);
|
|
280
295
|
decision = resolved;
|
|
281
|
-
if (resolved.action === "deny" && resolved.approverUnavailable === true && suspendAsk) {
|
|
296
|
+
if (resolved.action === "deny" && resolved.approverUnavailable === true && suspendAsk && parkFailed === undefined) {
|
|
282
297
|
const suspended = await suspendAsk(req, currentInput, safety, true);
|
|
283
298
|
if (suspended) {
|
|
284
|
-
|
|
299
|
+
if ("parkFailed" in suspended)
|
|
300
|
+
parkFailed = suspended.parkFailed;
|
|
301
|
+
else
|
|
302
|
+
return { suspend: suspended, preToolContext };
|
|
285
303
|
}
|
|
286
304
|
}
|
|
287
305
|
if (decision.action === "allow" && decision.updatedInput !== undefined) {
|
|
@@ -369,7 +387,7 @@ export async function runToolGate(input) {
|
|
|
369
387
|
}
|
|
370
388
|
}
|
|
371
389
|
if (decision.action === "deny") {
|
|
372
|
-
const denyReason = decisionText(decision) ?? `tool "${toolName}" denied by policy
|
|
390
|
+
const denyReason = withParkFailureCause(decisionText(decision) ?? `tool "${toolName}" denied by policy`, parkFailed);
|
|
373
391
|
if (decision.updatedInput !== undefined) {
|
|
374
392
|
currentInput = decision.updatedInput;
|
|
375
393
|
}
|
|
@@ -3,6 +3,7 @@ export interface MailboxMessage {
|
|
|
3
3
|
from?: string;
|
|
4
4
|
content: string;
|
|
5
5
|
sentAt: number;
|
|
6
|
+
hopChain?: string[];
|
|
6
7
|
}
|
|
7
8
|
export interface MailboxLease {
|
|
8
9
|
messages: MailboxMessage[];
|
|
@@ -12,6 +13,7 @@ export interface MailboxAppendMessage {
|
|
|
12
13
|
from?: string;
|
|
13
14
|
content: string;
|
|
14
15
|
sentAt: number;
|
|
16
|
+
hopChain?: string[];
|
|
15
17
|
}
|
|
16
18
|
export interface MailboxStore {
|
|
17
19
|
append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
|
|
@@ -30,7 +30,7 @@ export class InMemoryMailboxStore {
|
|
|
30
30
|
throw new Error("MailboxStore.append: refusing a message without a scope");
|
|
31
31
|
const b = this.box(scope, handle);
|
|
32
32
|
const seq = b.nextSeq++;
|
|
33
|
-
b.messages.push({ seq, ...(msg.from !== undefined ? { from: msg.from } : {}), content: msg.content, sentAt: msg.sentAt });
|
|
33
|
+
b.messages.push({ seq, ...(msg.from !== undefined ? { from: msg.from } : {}), content: msg.content, sentAt: msg.sentAt, ...(msg.hopChain !== undefined ? { hopChain: [...msg.hopChain] } : {}) });
|
|
34
34
|
return seq;
|
|
35
35
|
}
|
|
36
36
|
async claimLease(scope, handle, owner, ttlMs, now = Date.now()) {
|
|
@@ -41,7 +41,7 @@ export class InMemoryMailboxStore {
|
|
|
41
41
|
return null;
|
|
42
42
|
const maxSeq = b.messages[b.messages.length - 1].seq;
|
|
43
43
|
b.lease = { owner, expiresAt: now + ttlMs, maxSeq };
|
|
44
|
-
return { messages: b.messages.map((m) => ({ ...m })), maxSeq };
|
|
44
|
+
return { messages: b.messages.map((m) => ({ ...m, ...(m.hopChain !== undefined ? { hopChain: [...m.hopChain] } : {}) })), maxSeq };
|
|
45
45
|
}
|
|
46
46
|
async ack(scope, handle, owner, upToSeq) {
|
|
47
47
|
const b = this.boxes.get(this.key(scope, handle));
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -60,6 +60,7 @@ export interface McpServerStatus {
|
|
|
60
60
|
export declare const MCP_PREFIX: "mcp__";
|
|
61
61
|
export declare function resolveMcpDeclaredResultSize(meta: Record<string, unknown> | undefined): number | undefined;
|
|
62
62
|
export declare function gateMcpOutput(content: Array<TextContent | ImageContent>, limitTokens?: number): Array<TextContent | ImageContent>;
|
|
63
|
+
export declare function structuredContentErrorLine(structuredContent: unknown, collectedText: string): string | undefined;
|
|
63
64
|
export declare function truncateMcpErrorText(s: string): string;
|
|
64
65
|
export declare const MCP_TOOL_TIMEOUT_DEFAULT_MS = 100000000;
|
|
65
66
|
export declare function mcpToolTimeoutMs(): number;
|
package/dist/core/mcp.js
CHANGED
|
@@ -69,6 +69,18 @@ export function gateMcpOutput(content, limitTokens = mcpMaxOutputTokens()) {
|
|
|
69
69
|
}
|
|
70
70
|
const MCP_ERROR_HEAD_CHARS = 8_000;
|
|
71
71
|
const MCP_ERROR_TAIL_CHARS = 2_000;
|
|
72
|
+
const STRUCTURED_CONTENT_DEDUP_MIN_CHARS = 32;
|
|
73
|
+
export function structuredContentErrorLine(structuredContent, collectedText) {
|
|
74
|
+
if (structuredContent === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
const json = JSON.stringify(structuredContent);
|
|
77
|
+
if (json === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
const distinctive = json.length >= STRUCTURED_CONTENT_DEDUP_MIN_CHARS && (json.startsWith("{") || json.startsWith("["));
|
|
80
|
+
if (distinctive && truncateMcpErrorText(collectedText.trim()).includes(json))
|
|
81
|
+
return undefined;
|
|
82
|
+
return `[structuredContent] ${truncateMcpErrorText(json)}`;
|
|
83
|
+
}
|
|
72
84
|
export function truncateMcpErrorText(s) {
|
|
73
85
|
const max = MCP_ERROR_HEAD_CHARS + MCP_ERROR_TAIL_CHARS;
|
|
74
86
|
if (s.length <= max)
|
|
@@ -1198,9 +1210,9 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
|
|
|
1198
1210
|
else
|
|
1199
1211
|
parts.push(`[${block.type} block omitted from error result]`);
|
|
1200
1212
|
}
|
|
1201
|
-
const
|
|
1202
|
-
if (
|
|
1203
|
-
parts.push(
|
|
1213
|
+
const errScLine = structuredContentErrorLine(res.structuredContent, parts.join("\n"));
|
|
1214
|
+
if (errScLine !== undefined)
|
|
1215
|
+
parts.push(errScLine);
|
|
1204
1216
|
const body = truncateMcpErrorText(parts.join("\n").trim());
|
|
1205
1217
|
const msg = body
|
|
1206
1218
|
? `MCP tool ${inlineUntrusted(remoteName)} reported an error. The server's error content follows as external/untrusted data:\n${delimitUntrusted(`${spec.name} tool error`, body)}`
|
|
@@ -127,6 +127,7 @@ export interface Prepared {
|
|
|
127
127
|
activeTools: Set<string>;
|
|
128
128
|
deferredToolNames?: ReadonlySet<string>;
|
|
129
129
|
toolMaterializeStatic: boolean;
|
|
130
|
+
deferDirectCall: boolean;
|
|
130
131
|
staticFaceFor?: (name: string) => boolean;
|
|
131
132
|
memoryEngineSession?: {
|
|
132
133
|
engine: MemoryEngine;
|
|
@@ -339,6 +340,9 @@ export interface RunInternals {
|
|
|
339
340
|
ownOrgAdmissionRef?: {
|
|
340
341
|
current: import("../memory-admission.js").OwnOrgAdmissionVerdict | undefined;
|
|
341
342
|
};
|
|
343
|
+
peerSelfRef?: import("../../agents/peer-admission.js").PeerSelfRef;
|
|
344
|
+
peerInboundChainRef?: import("../../agents/peer-admission.js").PeerInboundChainRef;
|
|
345
|
+
parentPeerRef?: import("../../agents/peer-admission.js").PeerSelfRef;
|
|
342
346
|
workflowDepth?: number;
|
|
343
347
|
insideFork?: boolean;
|
|
344
348
|
isDelegatedChild?: boolean;
|