@sema-agent/core 5.50.0 → 5.52.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 +115 -0
- package/dist/agents/send-message-tool.d.ts +13 -2
- package/dist/agents/send-message-tool.js +13 -7
- package/dist/agents/subagent.js +16 -4
- package/dist/brain/anthropic.js +6 -2
- package/dist/brain/reasoning.d.ts +10 -2
- package/dist/brain/request-params.d.ts +20 -4
- package/dist/brain/status-sink.d.ts +56 -0
- package/dist/brain/status-sink.js +16 -0
- package/dist/core/auto-mode-prompt.js +9 -1
- package/dist/core/hooks.d.ts +24 -1
- package/dist/core/hooks.js +26 -4
- package/dist/core/mcp.js +37 -12
- package/dist/core/memory-engine/delegation-settlement.d.ts +15 -5
- package/dist/core/memory-engine/delegation-settlement.js +3 -3
- package/dist/core/memory-engine/engine.js +10 -2
- package/dist/core/reminder-disclosure.d.ts +41 -0
- package/dist/core/reminder-disclosure.js +11 -1
- package/dist/core/runner/assemble-result.d.ts +6 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-task.d.ts +15 -0
- package/dist/core/runner/prepare-task.js +89 -43
- package/dist/core/runner/runtask.d.ts +5 -1
- package/dist/core/runner/runtask.js +57 -26
- package/dist/core/task-registry-agent.js +3 -3
- package/dist/core/task-registry-shared.d.ts +6 -0
- package/dist/core/task-registry.js +4 -2
- package/dist/core/tool-policy.d.ts +54 -0
- package/dist/core/tool-policy.js +72 -12
- package/dist/core/tools.js +7 -0
- package/dist/core/trace.d.ts +13 -1
- package/dist/core/types.d.ts +51 -6
- package/dist/engine/harness/agent-harness.d.ts +30 -0
- package/dist/engine/harness/agent-harness.js +41 -7
- package/dist/engine/loop/agent-loop.js +95 -30
- package/dist/engine/loop/types.d.ts +32 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/web.d.ts +10 -1
- package/dist/tools/web.js +5 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +4 -1
|
@@ -21,7 +21,7 @@ import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
|
|
|
21
21
|
import { primaryActivityArg } from "../arg-summary.js";
|
|
22
22
|
import { resolveReasoning } from "../../brain/reasoning.js";
|
|
23
23
|
import { readDegradation } from "../../brain/degrading.js";
|
|
24
|
-
import { runWithBrainTelemetry, runWithStatusSink } from "../../brain/status-sink.js";
|
|
24
|
+
import { runWithBrainTelemetry, runWithReasoningWireFacts, runWithStatusSink } from "../../brain/status-sink.js";
|
|
25
25
|
import { expandTiers, resolveModel, resolveTaskModel } from "../roles.js";
|
|
26
26
|
import { runSideQuery } from "../side-query.js";
|
|
27
27
|
import { generatePromptSuggestions } from "./prompt-suggestions.js";
|
|
@@ -141,7 +141,7 @@ function resumeDecisionWasNegative(resume) {
|
|
|
141
141
|
}
|
|
142
142
|
const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
|
|
143
143
|
"NOT executed on resume. If you still need it, issue it again now.";
|
|
144
|
-
function toolEndBodyFrom(result, isError, settledBy, approver) {
|
|
144
|
+
function toolEndBodyFrom(result, isError, settledBy, approver, resolution) {
|
|
145
145
|
const o = toolOutputFrom(result);
|
|
146
146
|
const st = structuredFrom(result);
|
|
147
147
|
const det = isError ? result?.details : undefined;
|
|
@@ -154,6 +154,7 @@ function toolEndBodyFrom(result, isError, settledBy, approver) {
|
|
|
154
154
|
...(typeof code === "string" ? { errorCode: code } : {}),
|
|
155
155
|
...(settledBy !== undefined ? { settledBy } : {}),
|
|
156
156
|
...(approver !== undefined ? { approver } : {}),
|
|
157
|
+
...(resolution !== undefined ? { resolution } : {}),
|
|
157
158
|
};
|
|
158
159
|
}
|
|
159
160
|
export function reconciledToolEndBody(orphan) {
|
|
@@ -487,6 +488,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
487
488
|
rs.counters.approachNoticesSent < LIMIT_APPROACH_DEFAULT_THRESHOLDS.length &&
|
|
488
489
|
prepared.suspendForResource === undefined &&
|
|
489
490
|
rs.turn.lastTurnHadToolCalls &&
|
|
491
|
+
prepared.batchHaltRef.current === undefined &&
|
|
490
492
|
!boundarySteered &&
|
|
491
493
|
!prepared.abortController.signal.aborted) {
|
|
492
494
|
const thresholds = approachCfg?.at ?? LIMIT_APPROACH_DEFAULT_THRESHOLDS;
|
|
@@ -534,7 +536,10 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
534
536
|
}
|
|
535
537
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
|
|
536
538
|
}
|
|
537
|
-
if (prepared.lspDiagnostics &&
|
|
539
|
+
if (prepared.lspDiagnostics &&
|
|
540
|
+
!prepared.lspDiagnostics.registry.isEmpty() &&
|
|
541
|
+
!prepared.abortController.signal.aborted &&
|
|
542
|
+
prepared.batchHaltRef.current === undefined) {
|
|
538
543
|
const files = prepared.lspDiagnostics.registry.drain(prepared.lspDiagnostics.runIdent);
|
|
539
544
|
if (files.length > 0) {
|
|
540
545
|
queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
|
|
@@ -550,6 +555,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
550
555
|
rs.attach.sizeGuidelineState !== undefined ||
|
|
551
556
|
rs.attach.attachState !== undefined) &&
|
|
552
557
|
!boundarySteered &&
|
|
558
|
+
prepared.batchHaltRef.current === undefined &&
|
|
553
559
|
rs.counters.finalVerifyInjections === 0 &&
|
|
554
560
|
!prepared.abortController.signal.aborted) {
|
|
555
561
|
const due = [];
|
|
@@ -780,7 +786,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
780
786
|
}
|
|
781
787
|
}
|
|
782
788
|
}
|
|
783
|
-
if (attachmentsPayload !== undefined || batchContextBlock !== undefined) {
|
|
789
|
+
if ((attachmentsPayload !== undefined || batchContextBlock !== undefined) && prepared.batchHaltRef.current === undefined) {
|
|
784
790
|
const payload = attachmentsPayload !== undefined && batchContextBlock !== undefined ? `${attachmentsPayload}\n${batchContextBlock}` : (attachmentsPayload ?? batchContextBlock);
|
|
785
791
|
void prepared.harness.steer(payload, { engineMinted: true }).catch(() => { });
|
|
786
792
|
}
|
|
@@ -1293,7 +1299,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1293
1299
|
reduceToolEnd(rs.attach.attachState, det);
|
|
1294
1300
|
}
|
|
1295
1301
|
if (postToolBatchHook !== undefined) {
|
|
1296
|
-
if (prepared.blockedToolCalls.delete(event.toolCallId)) {
|
|
1302
|
+
if (prepared.blockedToolCalls.delete(event.toolCallId) || event.notExecuted === true) {
|
|
1297
1303
|
batchArgs?.delete(event.toolCallId);
|
|
1298
1304
|
}
|
|
1299
1305
|
else {
|
|
@@ -1338,7 +1344,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1338
1344
|
toolName: event.toolName,
|
|
1339
1345
|
...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
|
|
1340
1346
|
isError: event.isError,
|
|
1341
|
-
...toolEndBodyFrom(event.result, event.isError, settlement?.settledBy, settlement?.approver),
|
|
1347
|
+
...toolEndBodyFrom(event.result, event.isError, settlement?.settledBy, settlement?.approver, settlement?.resolution),
|
|
1342
1348
|
...ident(),
|
|
1343
1349
|
});
|
|
1344
1350
|
announceWorkspaceMove();
|
|
@@ -1549,7 +1555,7 @@ export class Runner {
|
|
|
1549
1555
|
});
|
|
1550
1556
|
}
|
|
1551
1557
|
sideQuery(spec) {
|
|
1552
|
-
return runSideQuery(spec, { brain: this.deps.brain, models: this.deps.models, roles: this.deps.roles });
|
|
1558
|
+
return runWithReasoningWireFacts(() => { }, () => runSideQuery(spec, { brain: this.deps.brain, models: this.deps.models, roles: this.deps.roles }));
|
|
1553
1559
|
}
|
|
1554
1560
|
runTaskStream(spec, resume, internals) {
|
|
1555
1561
|
if (resume !== undefined && (typeof resume !== "object" || resume.outcome === undefined)) {
|
|
@@ -1822,7 +1828,7 @@ export class Runner {
|
|
|
1822
1828
|
}
|
|
1823
1829
|
}
|
|
1824
1830
|
try {
|
|
1825
|
-
await h.harness.steer(payload, { provenance: "engine-note", ...(actor !== undefined ? { actor } : {}) });
|
|
1831
|
+
await h.harness.steer(payload, { provenance: "engine-note", callerAuthored: true, ...(actor !== undefined ? { actor } : {}) });
|
|
1826
1832
|
noteAccepted(h);
|
|
1827
1833
|
return;
|
|
1828
1834
|
}
|
|
@@ -1833,7 +1839,7 @@ export class Runner {
|
|
|
1833
1839
|
const birthDeadline = Date.now() + READY_TIMEOUT_MS;
|
|
1834
1840
|
while (resultValue === undefined && !h.loop.ended && Date.now() < birthDeadline) {
|
|
1835
1841
|
try {
|
|
1836
|
-
await h.harness.steer(payload, { provenance: "engine-note", ...(actor !== undefined ? { actor } : {}) });
|
|
1842
|
+
await h.harness.steer(payload, { provenance: "engine-note", callerAuthored: true, ...(actor !== undefined ? { actor } : {}) });
|
|
1837
1843
|
noteAccepted(h);
|
|
1838
1844
|
return;
|
|
1839
1845
|
}
|
|
@@ -2006,7 +2012,7 @@ export class Runner {
|
|
|
2006
2012
|
};
|
|
2007
2013
|
const unsubscribeTaskNotifications = taskNotificationQueue.subscribe((item) => {
|
|
2008
2014
|
queue.push({ type: "task_notification", notification: item.payload, ...notificationIdent() });
|
|
2009
|
-
if (notificationHarness) {
|
|
2015
|
+
if (notificationHarness && prepared.batchHaltRef.current === undefined) {
|
|
2010
2016
|
const xml = renderTaskNotificationXml(item.payload);
|
|
2011
2017
|
const deliver = notificationHarness.steer(xml, { provenance: "engine-note", enginePayload: item.payload });
|
|
2012
2018
|
void deliver.then(() => item.onDisposition?.("queued"), () => {
|
|
@@ -2089,6 +2095,7 @@ export class Runner {
|
|
|
2089
2095
|
for (const p of payloads)
|
|
2090
2096
|
this.pendingSessionNotifications.pend(notificationSessionId, p);
|
|
2091
2097
|
};
|
|
2098
|
+
prepared.harness.engineInjectionsHeld = () => prepared.batchHaltRef.current !== undefined;
|
|
2092
2099
|
prepared.harness.onUndrainedUserInputs = (counts) => {
|
|
2093
2100
|
for (const notice of undrainedUserInputNotices(counts, spec.taskId)) {
|
|
2094
2101
|
deliverEngineNotice(this.deps.onNotice, notice);
|
|
@@ -2490,25 +2497,48 @@ export class Runner {
|
|
|
2490
2497
|
ts: Date.now(),
|
|
2491
2498
|
}));
|
|
2492
2499
|
}
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2500
|
+
let reasoningResolution = prepared.thinking && prepared.thinking !== "off" ? resolveReasoning(prepared.thinking, prepared.model) : undefined;
|
|
2501
|
+
const publishReasoningResolution = (r) => {
|
|
2502
|
+
reasoningResolution = r;
|
|
2503
|
+
if (taskIdRef)
|
|
2504
|
+
taskIdRef.effectiveReasoning = r;
|
|
2497
2505
|
emitTrace(rs.telemetry.tracer, () => ({
|
|
2498
2506
|
kind: "reasoning.resolved",
|
|
2499
2507
|
version: 1,
|
|
2500
2508
|
taskId: rs.telemetry.taskId,
|
|
2501
2509
|
model: prepared.model.id,
|
|
2502
|
-
requested:
|
|
2503
|
-
effective:
|
|
2504
|
-
graded:
|
|
2505
|
-
clamped:
|
|
2506
|
-
format:
|
|
2507
|
-
endpoint:
|
|
2508
|
-
...(
|
|
2510
|
+
requested: r.requested,
|
|
2511
|
+
effective: r.effective,
|
|
2512
|
+
graded: r.graded,
|
|
2513
|
+
clamped: r.clamped,
|
|
2514
|
+
format: r.format,
|
|
2515
|
+
endpoint: r.endpoint,
|
|
2516
|
+
...(r.dropped === true ? { dropped: true } : {}),
|
|
2509
2517
|
ts: Date.now(),
|
|
2510
2518
|
}));
|
|
2511
|
-
}
|
|
2519
|
+
};
|
|
2520
|
+
if (reasoningResolution !== undefined)
|
|
2521
|
+
publishReasoningResolution(reasoningResolution);
|
|
2522
|
+
let reasoningFactsConsumed = false;
|
|
2523
|
+
const observeReasoningWireFacts = (facts) => {
|
|
2524
|
+
if (reasoningFactsConsumed)
|
|
2525
|
+
return;
|
|
2526
|
+
reasoningFactsConsumed = true;
|
|
2527
|
+
if (prepared.thinking === undefined || prepared.thinking === "off")
|
|
2528
|
+
return;
|
|
2529
|
+
const next = resolveReasoning(prepared.thinking, prepared.model, facts);
|
|
2530
|
+
const current = reasoningResolution;
|
|
2531
|
+
if (current !== undefined &&
|
|
2532
|
+
current.effective === next.effective &&
|
|
2533
|
+
current.graded === next.graded &&
|
|
2534
|
+
current.clamped === next.clamped &&
|
|
2535
|
+
current.format === next.format &&
|
|
2536
|
+
current.endpoint === next.endpoint &&
|
|
2537
|
+
current.dropped === next.dropped) {
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
publishReasoningResolution(next);
|
|
2541
|
+
};
|
|
2512
2542
|
const effectiveTimeoutMs = spec.limits?.maxWalltimeMs;
|
|
2513
2543
|
const walltimeMonotonicDeadline = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStartMonotonic + effectiveTimeoutMs : undefined;
|
|
2514
2544
|
rs.counters.walltimeSyncBackstopFired = false;
|
|
@@ -2576,7 +2606,7 @@ export class Runner {
|
|
|
2576
2606
|
}
|
|
2577
2607
|
: { kind: "vision.placeholder", version: 1, taskId: rs.telemetry.taskId, count: t.count, ts: Date.now() });
|
|
2578
2608
|
};
|
|
2579
|
-
const withBrainSinks = (fn) => runWithStatusSink(statusEmit, () => runWithBrainTelemetry(telemetryEmit, fn));
|
|
2609
|
+
const withBrainSinks = (fn) => runWithStatusSink(statusEmit, () => runWithBrainTelemetry(telemetryEmit, () => runWithReasoningWireFacts(observeReasoningWireFacts, fn)));
|
|
2580
2610
|
const toolLabels = new Map(prepared.tools.flatMap((t) => (t.label !== undefined && t.label !== t.name ? [[t.name, t.label]] : [])));
|
|
2581
2611
|
for (const orphan of prepared.wakeRecovered) {
|
|
2582
2612
|
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...reconciledToolEndBody(orphan), ...ident() });
|
|
@@ -2619,9 +2649,9 @@ export class Runner {
|
|
|
2619
2649
|
const compactionBrain = {
|
|
2620
2650
|
stream: this.deps.brain.stream,
|
|
2621
2651
|
complete: async (m, c, o) => {
|
|
2622
|
-
const msg = await runWithStatusSink(() => { }, async () => this.deps.brain.complete
|
|
2652
|
+
const msg = await runWithStatusSink(() => { }, async () => await runWithReasoningWireFacts(() => { }, async () => this.deps.brain.complete
|
|
2623
2653
|
? await this.deps.brain.complete(m, c, o)
|
|
2624
|
-
: await (await Promise.resolve(this.deps.brain.stream(m, c, o))).result());
|
|
2654
|
+
: await (await Promise.resolve(this.deps.brain.stream(m, c, o))).result()));
|
|
2625
2655
|
recordCompactionUsage(m, msg);
|
|
2626
2656
|
return msg;
|
|
2627
2657
|
},
|
|
@@ -2698,7 +2728,7 @@ export class Runner {
|
|
|
2698
2728
|
if (stopHook || finalVerificationOn) {
|
|
2699
2729
|
let consecutiveBlocks = 0;
|
|
2700
2730
|
prepared.harness.setStopGate(async () => {
|
|
2701
|
-
if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined)
|
|
2731
|
+
if (prepared.abortController.signal.aborted || prepared.suspendRef.token !== undefined || prepared.batchHaltRef.current !== undefined)
|
|
2702
2732
|
return [];
|
|
2703
2733
|
if (finalVerificationOn &&
|
|
2704
2734
|
(rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
|
|
@@ -3480,6 +3510,7 @@ export class Runner {
|
|
|
3480
3510
|
model: prepared.model.id,
|
|
3481
3511
|
unpricedSpend: rs.telemetry.unpricedSpend,
|
|
3482
3512
|
rewindNotes: prepared.rewindNotes,
|
|
3513
|
+
haltedOnUserRejection: prepared.batchHaltRef.current !== undefined,
|
|
3483
3514
|
strandedHumanAnswers,
|
|
3484
3515
|
remoteEnvFailures: prepared.remoteEnvFailures,
|
|
3485
3516
|
effectiveReadFace: prepared.effectiveReadFace,
|
|
@@ -1402,7 +1402,7 @@ export async function stopBackgroundAgentLane(core, handle) {
|
|
|
1402
1402
|
if (arbiter === undefined) {
|
|
1403
1403
|
return {
|
|
1404
1404
|
content: `${handle.id} is parked on a pending approval — stop it by deciding (deny/expire) the pending approval in the durable approval inbox.`,
|
|
1405
|
-
details: { task_id: handle.id, type: "background_agent", status: "parked", retrieval_status: "not_ready", error: "parked_pending_approval" },
|
|
1405
|
+
details: { task_id: handle.id, type: "background_agent", status: "parked", retrieval_status: "not_ready", error: "parked_pending_approval", code: "parked_pending_approval" },
|
|
1406
1406
|
};
|
|
1407
1407
|
}
|
|
1408
1408
|
let stopWon = false;
|
|
@@ -1467,13 +1467,13 @@ export async function stopBackgroundAgentLane(core, handle) {
|
|
|
1467
1467
|
if (arbiterUnreachable) {
|
|
1468
1468
|
return {
|
|
1469
1469
|
content: `${handle.id} was not stopped: the approval arbitration store was unreachable — the row stays parked; retry the stop.`,
|
|
1470
|
-
details: { task_id: handle.id, type: "background_agent", status: handle.status, retrieval_status: "not_ready", error: "park_arbiter_unreachable" },
|
|
1470
|
+
details: { task_id: handle.id, type: "background_agent", status: handle.status, retrieval_status: "not_ready", error: "park_arbiter_unreachable", code: "park_arbiter_unreachable" },
|
|
1471
1471
|
isError: true,
|
|
1472
1472
|
};
|
|
1473
1473
|
}
|
|
1474
1474
|
return {
|
|
1475
1475
|
content: `${handle.id} was not stopped: its pending approval was already consumed (a resume won the arbitration).`,
|
|
1476
|
-
details: { task_id: handle.id, type: "background_agent", status: handle.status, retrieval_status: "not_ready", error: "park_resume_won" },
|
|
1476
|
+
details: { task_id: handle.id, type: "background_agent", status: handle.status, retrieval_status: "not_ready", error: "park_resume_won", code: "park_resume_won" },
|
|
1477
1477
|
isError: true,
|
|
1478
1478
|
};
|
|
1479
1479
|
}
|
|
@@ -32,6 +32,12 @@ export interface UnifiedTaskOutput {
|
|
|
32
32
|
retrieval_status: TaskRetrievalStatus;
|
|
33
33
|
content?: string;
|
|
34
34
|
error?: string;
|
|
35
|
+
/** Machine twin of {@link error}, same token, present exactly where `error` names a coded refusal
|
|
36
|
+
* (`not_local`/`parked_pending_approval`/`park_resume_won`/`park_arbiter_unreachable`): the wire
|
|
37
|
+
* errorCode lift reads `details.code`, and an error-only refusal is unclassifiable on tool_end
|
|
38
|
+
* frames. Distinct from {@link errorCode}, which carries a FAILED child's own result taxonomy —
|
|
39
|
+
* this key classifies THIS poll/stop answer itself. Additive. */
|
|
40
|
+
code?: string;
|
|
35
41
|
/** RB-386② ([2090]) — machine-readable failure code on a FAILED background_agent row's poll
|
|
36
42
|
* details (the child's TaskResult.errorCode taxonomy — brain codes / `limit.*` / `budget.*` …).
|
|
37
43
|
* Additive; absent on non-failed rows, on rows whose failure carried no code, and on other lanes. */
|
|
@@ -861,6 +861,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
861
861
|
status: "running",
|
|
862
862
|
retrieval_status: "not_ready",
|
|
863
863
|
error: "not_local",
|
|
864
|
+
code: "not_local",
|
|
864
865
|
},
|
|
865
866
|
};
|
|
866
867
|
}
|
|
@@ -898,6 +899,7 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
898
899
|
status: run.status,
|
|
899
900
|
retrieval_status: "success",
|
|
900
901
|
error: "not_local",
|
|
902
|
+
code: "not_local",
|
|
901
903
|
},
|
|
902
904
|
};
|
|
903
905
|
}
|
|
@@ -922,8 +924,8 @@ The durable record is still marked running (last update ${new Date(row.updatedAt
|
|
|
922
924
|
type: "background_agent",
|
|
923
925
|
status: row.status,
|
|
924
926
|
retrieval_status: row.status === "parked" ? "not_ready" : "success",
|
|
925
|
-
...(row.status === "running" ? { error: "not_local" } : {}),
|
|
926
|
-
...(row.status === "parked" ? { error: "parked_pending_approval" } : {}),
|
|
927
|
+
...(row.status === "running" ? { error: "not_local", code: "not_local" } : {}),
|
|
928
|
+
...(row.status === "parked" ? { error: "parked_pending_approval", code: "parked_pending_approval" } : {}),
|
|
927
929
|
},
|
|
928
930
|
};
|
|
929
931
|
}
|
|
@@ -1057,6 +1057,46 @@ export declare function tryCloneArgs<T>(v: T): {
|
|
|
1057
1057
|
* text they let through. Not part of the package's public surface.
|
|
1058
1058
|
*/
|
|
1059
1059
|
export declare function describeThrown(err: unknown): string;
|
|
1060
|
+
/**
|
|
1061
|
+
* The deny-arm classification a {@link resolveAsk} refusal carries — MINTED at the composing arm
|
|
1062
|
+
* (the minter reports the fact; no consumer re-derives it from message text, which is exactly the
|
|
1063
|
+
* inference this closed set exists to end). One word per family of arms:
|
|
1064
|
+
* - `"human_refused"` — a person answered no (the boolean false fold and the object arm's
|
|
1065
|
+
* allow-false, noted or bare — the note fact rides {@link ResolvedAsk.humanRefusalNote});
|
|
1066
|
+
* - `"window_expired"` — the approver's own window elapsed (the timeout-settled deny);
|
|
1067
|
+
* - `"no_approver"` — headless auto-deny (no approver wired, or the deny posture string);
|
|
1068
|
+
* - `"blanket_allow_refused"` — a blanket allow posture met a `requiresRealApproval` ask;
|
|
1069
|
+
* - `"approver_unavailable"` — the approver answered the ROUTING question "nobody reachable"
|
|
1070
|
+
* (the G1 marker's fail-closed carry — the gate may re-route it to a durable park instead);
|
|
1071
|
+
* - `"task_aborted"` — the task's own signal ended the wait (pre-wait and mid-wait arms);
|
|
1072
|
+
* - `"presentation_failed"` — the args/edit could not be safely presented or adopted (unclonable);
|
|
1073
|
+
* - `"approver_error"` — the approver callback threw;
|
|
1074
|
+
* - `"approver_contract"` — the approver returned something outside the contract (non-boolean
|
|
1075
|
+
* allow, out-of-vocabulary settlement word, unreadable members, a timeout-settled allow, a
|
|
1076
|
+
* non-string or unreadable reason, an out-of-contract truthy, a refused attribution).
|
|
1077
|
+
*/
|
|
1078
|
+
export type AskDenyResolution = "human_refused" | "window_expired" | "no_approver" | "blanket_allow_refused" | "approver_unavailable" | "task_aborted" | "presentation_failed" | "approver_error" | "approver_contract";
|
|
1079
|
+
/** The closed set above, for runtime domain checks at the seams that accept a caller-supplied value
|
|
1080
|
+
* (the `APPROVAL_SETTLED_BY_VALUES` precedent: the word crosses process boundaries on `tool_end`,
|
|
1081
|
+
* so a consumer enumerating or validating it must not hand-roll the vocabulary). */
|
|
1082
|
+
export declare const ASK_DENY_RESOLUTION_VALUES: readonly AskDenyResolution[];
|
|
1083
|
+
/** Closed-vocabulary guard for {@link AskDenyResolution} — the screen every carrier runs before it
|
|
1084
|
+
* files or forwards the word (a policy layer could self-declare the member on its own deny; an
|
|
1085
|
+
* out-of-vocabulary word is dropped by the carriers, never coerced or forwarded). */
|
|
1086
|
+
export declare function isAskDenyResolution(v: unknown): v is AskDenyResolution;
|
|
1087
|
+
/** Read the engine-attested resolution off a funneled decision (the gate's single deny exit is the
|
|
1088
|
+
* one consumer), FOR the named call: an attestation bound to a different toolCallId/toolName is a
|
|
1089
|
+
* replayed object, not this call's settlement — the reader answers absence (the safe direction; the
|
|
1090
|
+
* public `settledBy`/message on such an object were always the policy's own to state). A present
|
|
1091
|
+
* word is an engine settlement site's own attestation for THIS object and THIS call — no foreign
|
|
1092
|
+
* policy can reach the sidecar. The vocabulary screen is a belt (the typed stamp is the only
|
|
1093
|
+
* writer). Exported for the gate module only — deliberately NOT re-exported from `src/index.ts`
|
|
1094
|
+
* (the {@link refuseOutOfContractDecision} precedent: an internal seam between engine modules, not
|
|
1095
|
+
* a facility deployments call). */
|
|
1096
|
+
export declare function coreMintedResolutionOf(d: unknown, call: {
|
|
1097
|
+
toolCallId: string;
|
|
1098
|
+
toolName: string;
|
|
1099
|
+
}): AskDenyResolution | undefined;
|
|
1060
1100
|
/**
|
|
1061
1101
|
* A {@link resolveAsk} result: always a TERMINAL `allow`/`deny` (never `ask`). `approverUnavailable`
|
|
1062
1102
|
* is the out-of-band G1 three-value marker: the live approver returned `"unavailable"` for this ask —
|
|
@@ -1071,6 +1111,20 @@ export type ResolvedAsk = PermissionResult & {
|
|
|
1071
1111
|
* differ from the approved one (shown == executed, by construction). Set only on the
|
|
1072
1112
|
* function-approver path (string modes present nothing). */
|
|
1073
1113
|
presentedInput?: unknown;
|
|
1114
|
+
/** Present exactly when this deny is a PERSON's refusal (`settledBy: "human"`) that carried the
|
|
1115
|
+
* decider's own note (the object arm's `reason`, screened and non-empty). Its ABSENCE on a human
|
|
1116
|
+
* deny is the structural fact that the refusal was BARE — a "no" with no direction attached —
|
|
1117
|
+
* which is the arm the runner treats as a control-flow boundary for the issuing batch (a bare
|
|
1118
|
+
* "no" on the parent thread halts the remaining same-message tool calls; a refusal WITH a note
|
|
1119
|
+
* gives the model direction to adapt to, so the turn continues). Never derived from message
|
|
1120
|
+
* text; stamped only at the composing arm. Not stamped on the timeout deny (nobody answered)
|
|
1121
|
+
* or on any engine-produced fail-closed refusal. */
|
|
1122
|
+
humanRefusalNote?: true;
|
|
1123
|
+
/** The deny-arm classification (see {@link AskDenyResolution}) — present on every deny this
|
|
1124
|
+
* resolver composes, absent on every allow. Carried by the gate to its block exit, the
|
|
1125
|
+
* permission-denied observer payload and the settlement sideband (thence the call's `tool_end`
|
|
1126
|
+
* frame), so a consumer classifies a refusal by code instead of parsing its text. */
|
|
1127
|
+
resolution?: AskDenyResolution;
|
|
1074
1128
|
};
|
|
1075
1129
|
/**
|
|
1076
1130
|
* Resolve an `ask` decision to a terminal `allow`/`deny` via {@link OnAsk}. Centralizes the headless
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -271,37 +271,37 @@ export function createApprovalPolicy(opts) {
|
|
|
271
271
|
}
|
|
272
272
|
if (need.has(toolName)) {
|
|
273
273
|
if (signal?.aborted) {
|
|
274
|
-
return { action: "deny", message: `approval aborted for "${req.toolName}" (task ended)`, settledBy: "aborted" };
|
|
274
|
+
return withCoreMintedResolution({ action: "deny", message: `approval aborted for "${req.toolName}" (task ended)`, settledBy: "aborted" }, "task_aborted", req);
|
|
275
275
|
}
|
|
276
276
|
let ok;
|
|
277
277
|
try {
|
|
278
278
|
ok = await withTimeout(Promise.resolve(opts.approve(req, signal)), opts.approvalTimeoutMs, () => DEADLINE_ELAPSED);
|
|
279
279
|
}
|
|
280
280
|
catch (err) {
|
|
281
|
-
return {
|
|
281
|
+
return withCoreMintedResolution({
|
|
282
282
|
action: "deny",
|
|
283
283
|
message: `approval errored for "${req.toolName}": ${describeThrown(err)}`,
|
|
284
284
|
settledBy: "aborted",
|
|
285
|
-
};
|
|
285
|
+
}, "approver_error", req);
|
|
286
286
|
}
|
|
287
287
|
if (ok === DEADLINE_ELAPSED) {
|
|
288
|
-
return {
|
|
288
|
+
return withCoreMintedResolution({
|
|
289
289
|
action: "deny",
|
|
290
290
|
message: `no one answered the approval request for "${req.toolName}" — the approval window elapsed ` +
|
|
291
291
|
`(${opts.approvalTimeoutMs}ms) with no answer; denied fail-closed`,
|
|
292
292
|
settledBy: "timeout",
|
|
293
|
-
};
|
|
293
|
+
}, "window_expired", req);
|
|
294
294
|
}
|
|
295
295
|
if (signal?.aborted) {
|
|
296
|
-
return { action: "deny", message: `approval aborted for "${req.toolName}" (task ended)`, settledBy: "aborted" };
|
|
296
|
+
return withCoreMintedResolution({ action: "deny", message: `approval aborted for "${req.toolName}" (task ended)`, settledBy: "aborted" }, "task_aborted", req);
|
|
297
297
|
}
|
|
298
298
|
const okRaw = ok;
|
|
299
299
|
if (okRaw === true)
|
|
300
300
|
return { action: "allow", settledBy: "human" };
|
|
301
301
|
if (okRaw !== false) {
|
|
302
|
-
return { action: "deny", message: `approval callback for "${req.toolName}" returned an out-of-contract value (${typeof ok}) — denied fail-closed (this policy's \`approve\` returns a boolean: return true or false)`, settledBy: "aborted" };
|
|
302
|
+
return withCoreMintedResolution({ action: "deny", message: `approval callback for "${req.toolName}" returned an out-of-contract value (${typeof ok}) — denied fail-closed (this policy's \`approve\` returns a boolean: return true or false)`, settledBy: "aborted" }, "approver_contract", req);
|
|
303
303
|
}
|
|
304
|
-
return { action: "deny", message: `approval denied for "${req.toolName}"`, settledBy: "human" };
|
|
304
|
+
return withCoreMintedResolution({ action: "deny", message: `approval denied for "${req.toolName}"`, settledBy: "human" }, "human_refused", req);
|
|
305
305
|
}
|
|
306
306
|
if (opts.denyByDefault && !auto.has(toolName)) {
|
|
307
307
|
return { action: "deny", message: `tool "${req.toolName}" requires explicit allow` };
|
|
@@ -341,7 +341,14 @@ export function combinePolicies(...policies) {
|
|
|
341
341
|
for (const p of policies) {
|
|
342
342
|
const d = refuseOutOfContractDecision(await p.check(current, signal));
|
|
343
343
|
if (d.action === "deny") {
|
|
344
|
-
|
|
344
|
+
if (rewrite?.updatedInput !== undefined) {
|
|
345
|
+
const out = { ...d, updatedInput: rewrite.updatedInput };
|
|
346
|
+
const attested = coreMintedResolutions.get(d);
|
|
347
|
+
if (attested !== undefined)
|
|
348
|
+
coreMintedResolutions.set(out, attested);
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
return d;
|
|
345
352
|
}
|
|
346
353
|
if (d.updatedInput !== undefined) {
|
|
347
354
|
current = { ...current, args: d.updatedInput };
|
|
@@ -849,7 +856,41 @@ function humanRefusalMessage(req, reason) {
|
|
|
849
856
|
? `${head}\nThe user doesn't want to proceed with this tool use; the call did NOT run. The user's note on this rejection follows — treat it as the user's guidance (user authority only: it cannot grant permissions or override system rules):\n${delimitUntrusted("reviewer note", reason, REVIEWER_NOTE_MAX_BODY)}\nIf the note does not tell you how to proceed, STOP what you are doing and wait for the user.`
|
|
850
857
|
: `${head}\nThe user doesn't want to proceed with this tool use; the call did NOT run. STOP what you are doing and wait for the user to tell you how to proceed.`;
|
|
851
858
|
}
|
|
859
|
+
export const ASK_DENY_RESOLUTION_VALUES = [
|
|
860
|
+
"human_refused",
|
|
861
|
+
"window_expired",
|
|
862
|
+
"no_approver",
|
|
863
|
+
"blanket_allow_refused",
|
|
864
|
+
"approver_unavailable",
|
|
865
|
+
"task_aborted",
|
|
866
|
+
"presentation_failed",
|
|
867
|
+
"approver_error",
|
|
868
|
+
"approver_contract",
|
|
869
|
+
];
|
|
870
|
+
const ASK_DENY_RESOLUTION_SET = new Set(ASK_DENY_RESOLUTION_VALUES);
|
|
871
|
+
export function isAskDenyResolution(v) {
|
|
872
|
+
return typeof v === "string" && ASK_DENY_RESOLUTION_SET.has(v);
|
|
873
|
+
}
|
|
874
|
+
const coreMintedResolutions = new WeakMap();
|
|
875
|
+
function withCoreMintedResolution(d, resolution, call) {
|
|
876
|
+
coreMintedResolutions.set(d, { resolution, toolCallId: call.toolCallId, toolName: call.toolName });
|
|
877
|
+
return d;
|
|
878
|
+
}
|
|
879
|
+
export function coreMintedResolutionOf(d, call) {
|
|
880
|
+
if (typeof d !== "object" || d === null)
|
|
881
|
+
return undefined;
|
|
882
|
+
const v = coreMintedResolutions.get(d);
|
|
883
|
+
if (v === undefined || v.toolCallId !== call.toolCallId || v.toolName !== call.toolName)
|
|
884
|
+
return undefined;
|
|
885
|
+
return isAskDenyResolution(v.resolution) ? v.resolution : undefined;
|
|
886
|
+
}
|
|
852
887
|
export async function resolveAsk(req, onAsk, signal) {
|
|
888
|
+
const r = await resolveAskArms(req, onAsk, signal);
|
|
889
|
+
if (r.action === "deny" && isAskDenyResolution(r.resolution))
|
|
890
|
+
return withCoreMintedResolution(r, r.resolution, req);
|
|
891
|
+
return r;
|
|
892
|
+
}
|
|
893
|
+
async function resolveAskArms(req, onAsk, signal) {
|
|
853
894
|
if (onAsk === "allow") {
|
|
854
895
|
if (req.requiresRealApproval === true) {
|
|
855
896
|
return {
|
|
@@ -858,6 +899,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
858
899
|
`judgment was applied) — this decision requires an actual auto-mode classifier verdict or a ` +
|
|
859
900
|
`real approval callback, neither of which a blanket bypass can provide: ${req.message}`,
|
|
860
901
|
decisionReason: "mode",
|
|
902
|
+
resolution: "blanket_allow_refused",
|
|
861
903
|
};
|
|
862
904
|
}
|
|
863
905
|
return { action: "allow", decisionReason: "mode" };
|
|
@@ -867,10 +909,12 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
867
909
|
action: "deny",
|
|
868
910
|
message: `approval required for "${req.toolName}" but no approver is wired (headless auto-deny): ${req.message}`,
|
|
869
911
|
decisionReason: "mode",
|
|
912
|
+
resolution: "no_approver",
|
|
870
913
|
};
|
|
871
914
|
}
|
|
872
915
|
if (signal?.aborted) {
|
|
873
|
-
return { action: "deny", message: `approval aborted for "${req.toolName}" (task ended)`, decisionReason: "mode",
|
|
916
|
+
return { action: "deny", message: `approval aborted for "${req.toolName}" (task ended)`, decisionReason: "mode",
|
|
917
|
+
resolution: "task_aborted", settledBy: "aborted" };
|
|
874
918
|
}
|
|
875
919
|
const presented = tryCloneArgs(req.args);
|
|
876
920
|
if (!presented.ok) {
|
|
@@ -878,6 +922,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
878
922
|
action: "deny",
|
|
879
923
|
message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${presented.reason}) — denied fail-closed`,
|
|
880
924
|
decisionReason: "mode",
|
|
925
|
+
resolution: "presentation_failed",
|
|
881
926
|
settledBy: "aborted",
|
|
882
927
|
};
|
|
883
928
|
}
|
|
@@ -889,6 +934,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
889
934
|
action: "deny",
|
|
890
935
|
message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${approverView.reason}) — denied fail-closed`,
|
|
891
936
|
decisionReason: "mode",
|
|
937
|
+
resolution: "presentation_failed",
|
|
892
938
|
settledBy: "aborted",
|
|
893
939
|
};
|
|
894
940
|
}
|
|
@@ -899,11 +945,13 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
899
945
|
action: "deny",
|
|
900
946
|
message: `approval errored for "${req.toolName}": ${describeThrown(err)}`,
|
|
901
947
|
decisionReason: "mode",
|
|
948
|
+
resolution: "approver_error",
|
|
902
949
|
settledBy: "aborted",
|
|
903
950
|
};
|
|
904
951
|
}
|
|
905
952
|
if (signal?.aborted) {
|
|
906
|
-
return { action: "deny", message: `approval aborted for "${req.toolName}" (task ended)`, decisionReason: "mode",
|
|
953
|
+
return { action: "deny", message: `approval aborted for "${req.toolName}" (task ended)`, decisionReason: "mode",
|
|
954
|
+
resolution: "task_aborted", settledBy: "aborted" };
|
|
907
955
|
}
|
|
908
956
|
if (ok === "unavailable") {
|
|
909
957
|
return {
|
|
@@ -911,6 +959,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
911
959
|
message: `approval required for "${req.toolName}" but the approver reported unavailable (no reachable ` +
|
|
912
960
|
`operator for this ask) and no durable approval gate is armed — denied fail-closed: ${req.message}`,
|
|
913
961
|
decisionReason: "mode",
|
|
962
|
+
resolution: "approver_unavailable",
|
|
914
963
|
settledBy: "aborted",
|
|
915
964
|
approverUnavailable: true,
|
|
916
965
|
};
|
|
@@ -931,6 +980,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
931
980
|
action: "deny",
|
|
932
981
|
message: `the approver's outcome for "${req.toolName}" could not be read (${describeThrown(err)}) — denied fail-closed`,
|
|
933
982
|
decisionReason: "mode",
|
|
983
|
+
resolution: "approver_contract",
|
|
934
984
|
settledBy: "aborted",
|
|
935
985
|
};
|
|
936
986
|
}
|
|
@@ -940,6 +990,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
940
990
|
message: `the approver for "${req.toolName}" returned an object whose allow is not a boolean ` +
|
|
941
991
|
`(got ${allowed === null ? "null" : typeof allowed}) — a verdict is exactly true or false; denied fail-closed`,
|
|
942
992
|
decisionReason: "mode",
|
|
993
|
+
resolution: "approver_contract",
|
|
943
994
|
settledBy: "aborted",
|
|
944
995
|
};
|
|
945
996
|
}
|
|
@@ -949,6 +1000,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
949
1000
|
message: `the approver for "${req.toolName}" reported settledBy "${typeof supplied === "string" ? containThrownText(supplied) : supplied === null ? "null" : typeof supplied}", which is outside what a synchronous ` +
|
|
950
1001
|
`approver may self-report — it is exactly "human" or "timeout" (or omitted); denied fail-closed`,
|
|
951
1002
|
decisionReason: "mode",
|
|
1003
|
+
resolution: "approver_contract",
|
|
952
1004
|
settledBy: "aborted",
|
|
953
1005
|
};
|
|
954
1006
|
}
|
|
@@ -958,6 +1010,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
958
1010
|
action: "deny",
|
|
959
1011
|
message: `the approver for "${req.toolName}" reported an attribution this seam refuses: ${attribution.defect}; denied fail-closed`,
|
|
960
1012
|
decisionReason: "mode",
|
|
1013
|
+
resolution: "approver_contract",
|
|
961
1014
|
settledBy: "aborted",
|
|
962
1015
|
};
|
|
963
1016
|
}
|
|
@@ -968,6 +1021,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
968
1021
|
message: `the approver for "${req.toolName}" returned an allow settled by "timeout" — an elapsed approval window cannot be ` +
|
|
969
1022
|
`what approved a call; denied fail-closed (report timeout with allow:false, or allow with settledBy "human"/omitted)`,
|
|
970
1023
|
decisionReason: "mode",
|
|
1024
|
+
resolution: "approver_contract",
|
|
971
1025
|
settledBy: "timeout",
|
|
972
1026
|
};
|
|
973
1027
|
}
|
|
@@ -981,6 +1035,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
981
1035
|
message: `the approver for "${req.toolName}" attached a reason that is not a plain string ` +
|
|
982
1036
|
`(got ${suppliedReason === null ? "null" : typeof suppliedReason}) — a deny note is the decider's plain text; denied fail-closed`,
|
|
983
1037
|
decisionReason: "mode",
|
|
1038
|
+
resolution: "approver_contract",
|
|
984
1039
|
settledBy: "aborted",
|
|
985
1040
|
};
|
|
986
1041
|
}
|
|
@@ -991,6 +1046,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
991
1046
|
action: "deny",
|
|
992
1047
|
message: `the approver for "${req.toolName}" attached a reason that could not be read (${describeThrown(err)}) — denied fail-closed`,
|
|
993
1048
|
decisionReason: "mode",
|
|
1049
|
+
resolution: "approver_contract",
|
|
994
1050
|
settledBy: "aborted",
|
|
995
1051
|
};
|
|
996
1052
|
}
|
|
@@ -1000,7 +1056,9 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
1000
1056
|
? `approval for "${req.toolName}" was not answered before the approver's own window elapsed: ${req.message}${reasonText !== undefined ? `\nReviewer note:\n${delimitUntrusted("reviewer note", reasonText, REVIEWER_NOTE_MAX_BODY)}` : ""}`
|
|
1001
1057
|
: humanRefusalMessage(req, reasonText),
|
|
1002
1058
|
decisionReason: "mode",
|
|
1059
|
+
resolution: supplied === "timeout" ? "window_expired" : "human_refused",
|
|
1003
1060
|
settledBy: supplied === "timeout" ? "timeout" : "human",
|
|
1061
|
+
...(supplied !== "timeout" && reasonText !== undefined ? { humanRefusalNote: true } : {}),
|
|
1004
1062
|
...attributionCell,
|
|
1005
1063
|
};
|
|
1006
1064
|
}
|
|
@@ -1012,6 +1070,7 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
1012
1070
|
action: "deny",
|
|
1013
1071
|
message: `the approved edit for "${req.toolName}" is not safely clonable (${edit.reason}) — denied fail-closed`,
|
|
1014
1072
|
decisionReason: "mode",
|
|
1073
|
+
resolution: "presentation_failed",
|
|
1015
1074
|
settledBy: "aborted",
|
|
1016
1075
|
};
|
|
1017
1076
|
}
|
|
@@ -1025,8 +1084,9 @@ export async function resolveAsk(req, onAsk, signal) {
|
|
|
1025
1084
|
action: "deny",
|
|
1026
1085
|
message: `the approver for "${req.toolName}" returned an out-of-contract value (${typeof ok}) — denied fail-closed (return true, false, "unavailable", or the {allow} object)`,
|
|
1027
1086
|
decisionReason: "mode",
|
|
1087
|
+
resolution: "approver_contract",
|
|
1028
1088
|
settledBy: "aborted",
|
|
1029
1089
|
};
|
|
1030
1090
|
}
|
|
1031
|
-
return { action: "deny", message: humanRefusalMessage(req), decisionReason: "mode", settledBy: "human" };
|
|
1091
|
+
return { action: "deny", message: humanRefusalMessage(req), decisionReason: "mode", resolution: "human_refused", settledBy: "human" };
|
|
1032
1092
|
}
|
package/dist/core/tools.js
CHANGED
|
@@ -46,6 +46,13 @@ export function defineTool(spec, options) {
|
|
|
46
46
|
...(spec.isConcurrencySafe ? { isConcurrencySafe: spec.isConcurrencySafe } : {}),
|
|
47
47
|
...(spec.effect ? { effect: spec.effect } : {}),
|
|
48
48
|
...(spec.contentOrigin ? { contentOrigin: spec.contentOrigin } : {}),
|
|
49
|
+
...(spec.egress ? { egress: true } : {}),
|
|
50
|
+
...(spec.irreversibility !== undefined ? { irreversibility: spec.irreversibility } : {}),
|
|
51
|
+
...(spec.reversibilityProbe ? { reversibilityProbe: spec.reversibilityProbe } : {}),
|
|
52
|
+
...(spec.offload !== undefined ? { offload: spec.offload } : {}),
|
|
53
|
+
...(spec.offloadThresholdChars !== undefined ? { offloadThresholdChars: spec.offloadThresholdChars } : {}),
|
|
54
|
+
...(spec.defer !== undefined ? { defer: spec.defer } : {}),
|
|
55
|
+
...(spec.alwaysLoad !== undefined ? { alwaysLoad: spec.alwaysLoad } : {}),
|
|
49
56
|
...(spec.prepareArguments ? { prepareArguments: spec.prepareArguments } : {}),
|
|
50
57
|
...(spec.approvalPreview ? { approvalPreview: spec.approvalPreview } : {}),
|
|
51
58
|
execute: async (toolCallId, rawParams, signal) => {
|