@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
|
@@ -20,7 +20,7 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial } from "../../agents
|
|
|
20
20
|
import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
|
|
21
21
|
import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
|
|
22
22
|
import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
23
|
-
import { askApproverIdentity, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
|
|
23
|
+
import { askApproverIdentity, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
|
|
24
24
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
25
25
|
import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
|
|
26
26
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
@@ -39,6 +39,8 @@ import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, createOffloadPersist, firstPartyOf
|
|
|
39
39
|
import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createOutputTool, createReportBlockedTool, createReportFindingsTool, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
|
|
40
40
|
import { compileOutputSchema } from "./strict-output-schema.js";
|
|
41
41
|
import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, staticSchemaRenderable, } from "./tool-disclosure.js";
|
|
42
|
+
import { createSharedMemoryTools } from "../shared-memory/tools.js";
|
|
43
|
+
import { SHARED_MEMORY_TOOL_NAMES } from "../shared-memory/types.js";
|
|
42
44
|
import { composeMemoryBlock } from "../memory.js";
|
|
43
45
|
import { preflightLockedConfig } from "../locked-config.js";
|
|
44
46
|
import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
|
|
@@ -75,7 +77,7 @@ import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-
|
|
|
75
77
|
import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
|
|
76
78
|
import { resolveKey } from "../../tools/fs/safety.js";
|
|
77
79
|
import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
|
|
78
|
-
import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, resolveCheckpointStore, } from "../checkpoint-store.js";
|
|
80
|
+
import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, encodeAtFidelity, remainingBudgetMicroUsd, resolveCheckpointStore, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
|
|
79
81
|
import { boundInputHashOf } from "../canonical-json.js";
|
|
80
82
|
import { countElicitOptIns, deriveAskEffective, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
|
|
81
83
|
import { GLOBAL_USAGE_KEY, resolveUsageWindows, usageRetryAfterMs } from "../usage-window-store.js";
|
|
@@ -166,6 +168,9 @@ export function resolveEnvLifetimeExpiry(env, observedAt) {
|
|
|
166
168
|
return { unanchored: true };
|
|
167
169
|
return { expiresAt: anchor + lifetimeMs };
|
|
168
170
|
}
|
|
171
|
+
function isLiveApproverSeat(onAsk) {
|
|
172
|
+
return typeof onAsk === "function";
|
|
173
|
+
}
|
|
169
174
|
const DEFAULT_UNATTENDED_APPROVAL_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
170
175
|
function sanitizedTtlMs(ttlMs) {
|
|
171
176
|
if (ttlMs === undefined)
|
|
@@ -180,6 +185,12 @@ export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
|
|
|
180
185
|
export function checkpointScopeOf(spec) {
|
|
181
186
|
return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
|
|
182
187
|
}
|
|
188
|
+
class ParkRefusal extends Error {
|
|
189
|
+
constructor(message, options) {
|
|
190
|
+
super(message, options);
|
|
191
|
+
this.name = "ParkRefusal";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
183
194
|
export { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
184
195
|
export function isFableFamilyModelId(id) {
|
|
185
196
|
const tail = id.toLowerCase().split("/").pop() ?? "";
|
|
@@ -619,6 +630,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
619
630
|
}
|
|
620
631
|
}
|
|
621
632
|
const taskScope = internals?.registryScope ?? spec.principal ?? "default";
|
|
633
|
+
internals?.peerSelfRef?.addAxis("s", sessionId);
|
|
634
|
+
internals?.peerSelfRef?.addAxis("t", hostTaskId);
|
|
622
635
|
const offloadScope = spec.principal ?? "default";
|
|
623
636
|
if (internals?.registryScope !== undefined && internals.registryScope !== offloadScope) {
|
|
624
637
|
deps.onError?.(new Error(`offload/tool-result namespace note: this run mounts in registry domain "${internals.registryScope}" but its offloaded tool results live under the TRUST identity "${offloadScope}" (principal${spec.principal === undefined ? " absent → default" : ""}). Before 2.13.0 the registry domain named this namespace too; if a deployment used registryScope as its ref-tenancy boundary, that boundary is now the principal — pass one, or read refs under "${offloadScope}".`), { phase: "config", sessionId });
|
|
@@ -1046,7 +1059,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1046
1059
|
...(ownSessionRulesRef.current !== undefined ? [ownSessionRulesRef.current] : []),
|
|
1047
1060
|
];
|
|
1048
1061
|
const ownCallerPolicy = lockedPreflight.toolPolicy;
|
|
1049
|
-
const durableMandate = runtimeCaps?.forceDurableGate === true ||
|
|
1062
|
+
const durableMandate = runtimeCaps?.forceDurableGate === true ||
|
|
1063
|
+
(spec.durableApproval !== undefined && !isLiveApproverSeat(frozenOnAsk));
|
|
1050
1064
|
const parentConstraints = [
|
|
1051
1065
|
...(inheritedParentConstraints ?? []),
|
|
1052
1066
|
...(ownCallerPolicy !== undefined
|
|
@@ -1103,6 +1117,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1103
1117
|
...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
|
|
1104
1118
|
...(internals?.rootSessionId !== undefined ? { rootSessionId: internals.rootSessionId } : {}),
|
|
1105
1119
|
...(internals?.explicitAgentName !== undefined ? { spawnedAgentName: internals.explicitAgentName } : {}),
|
|
1120
|
+
...(internals?.peerSelfRef !== undefined ? { peerSelfRef: internals.peerSelfRef } : {}),
|
|
1121
|
+
...(internals?.peerInboundChainRef !== undefined ? { peerInboundChainRef: internals.peerInboundChainRef } : {}),
|
|
1106
1122
|
...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}),
|
|
1107
1123
|
...(sessions.fork !== undefined
|
|
1108
1124
|
? {
|
|
@@ -1589,6 +1605,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1589
1605
|
...(deps.hands?.commitCoAuthor !== undefined ? { commitCoAuthor: deps.hands.commitCoAuthor } : {}),
|
|
1590
1606
|
...(deps.hands?.readImageDownsampler !== undefined ? { readImageDownsampler: deps.hands.readImageDownsampler } : {}),
|
|
1591
1607
|
...(deps.hands?.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: deps.hands.autoBackgroundOnTimeout } : {}),
|
|
1608
|
+
...(deps.hands?.readCyberReminder !== undefined ? { readCyberReminder: deps.hands.readCyberReminder } : {}),
|
|
1592
1609
|
beforeWrite: async (w) => {
|
|
1593
1610
|
const deploymentGate = deps.hands?.beforeWrite;
|
|
1594
1611
|
if (deploymentGate) {
|
|
@@ -1650,7 +1667,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1650
1667
|
...(req.row.agentType !== undefined ? { subagent_type: req.row.agentType } : {}),
|
|
1651
1668
|
...(req.row.name !== undefined ? { name: req.row.name } : {}),
|
|
1652
1669
|
...(req.row.model !== undefined ? { model: req.row.model } : {}),
|
|
1653
|
-
}, enrichSpecToolCtx({ toolCallId: `rv-${randomBytes(8).toString("hex")}`, reviveClaim: { row: req.row, rev: req.rev } }));
|
|
1670
|
+
}, enrichSpecToolCtx({ toolCallId: `rv-${randomBytes(8).toString("hex")}`, reviveClaim: { row: req.row, rev: req.rev, ...(req.peerSeed !== undefined ? { peerSeed: req.peerSeed } : {}) } }));
|
|
1654
1671
|
const o = (typeof out === "string" ? { content: out } : out);
|
|
1655
1672
|
return {
|
|
1656
1673
|
content: typeof o.content === "string" ? o.content : JSON.stringify(o.content),
|
|
@@ -1668,13 +1685,18 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1668
1685
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1669
1686
|
...(internals?.onTaskNotification !== undefined ? { notify: internals.onTaskNotification } : {}),
|
|
1670
1687
|
...(internals?.onSubagentSpawn !== undefined ? { sink: internals.onSubagentSpawn } : {}),
|
|
1671
|
-
...(internals?.parentNotify !== undefined
|
|
1688
|
+
...(internals?.parentNotify !== undefined
|
|
1689
|
+
? { uplink: internals.parentNotify, ...(internals.parentPeerRef !== undefined ? { uplinkRecipient: internals.parentPeerRef } : {}) }
|
|
1690
|
+
: {}),
|
|
1672
1691
|
...(internals?.explicitAgentName !== undefined ? { senderName: internals.explicitAgentName } : {}),
|
|
1673
1692
|
...(internals?.parentRetainLedger !== undefined ? { siblingRetain: internals.parentRetainLedger } : {}),
|
|
1674
1693
|
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
|
1675
1694
|
...(internals?.parentSessionId !== undefined ? { parentSessionId: internals.parentSessionId } : {}),
|
|
1676
1695
|
enrichCtx: enrichSpecToolCtx,
|
|
1677
1696
|
...(deps.rosterStore !== undefined ? { roster: deps.rosterStore } : {}),
|
|
1697
|
+
...(deps.peerAdmission !== undefined ? { admission: deps.peerAdmission } : {}),
|
|
1698
|
+
...(internals?.peerSelfRef !== undefined ? { peerSelf: internals.peerSelfRef } : {}),
|
|
1699
|
+
...(internals?.peerInboundChainRef !== undefined ? { peerInbound: internals.peerInboundChainRef } : {}),
|
|
1678
1700
|
...(deps.onBackgroundChildEvent ? { onBackgroundChildEvent: deps.onBackgroundChildEvent } : {}),
|
|
1679
1701
|
...(deps.backgroundAgentStore !== undefined ? { agentStore: deps.backgroundAgentStore } : {}),
|
|
1680
1702
|
...(deps.mailboxStore !== undefined ? { mailbox: deps.mailboxStore } : {}),
|
|
@@ -1963,6 +1985,33 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1963
1985
|
}
|
|
1964
1986
|
tools.push(createSkillTool(skillSpecs, skillScope));
|
|
1965
1987
|
}
|
|
1988
|
+
const sharedMemoryProvider = deps.sharedMemoryStores;
|
|
1989
|
+
let sharedMemoryPairMounted = false;
|
|
1990
|
+
if (sharedMemoryProvider !== undefined) {
|
|
1991
|
+
const excluded = new Set(toolFaceSnapshot.exclude ?? []);
|
|
1992
|
+
const mountedNameDomain = new Set();
|
|
1993
|
+
for (const t of tools) {
|
|
1994
|
+
mountedNameDomain.add(t.name);
|
|
1995
|
+
for (const alias of t.aliases ?? [])
|
|
1996
|
+
mountedNameDomain.add(alias);
|
|
1997
|
+
}
|
|
1998
|
+
const excludedName = SHARED_MEMORY_TOOL_NAMES.find((n) => excluded.has(n));
|
|
1999
|
+
const occupiedName = SHARED_MEMORY_TOOL_NAMES.find((n) => mountedNameDomain.has(n));
|
|
2000
|
+
if (excludedName !== undefined || occupiedName !== undefined) {
|
|
2001
|
+
deps.onError?.(new Error(`Shared memory tools ${SHARED_MEMORY_TOOL_NAMES.join("/")} were NOT mounted: ` +
|
|
2002
|
+
(excludedName !== undefined
|
|
2003
|
+
? `excludeTools removes "${excludedName}"`
|
|
2004
|
+
: `this task already declares a tool named "${occupiedName}" (the built-in yields to it)`) +
|
|
2005
|
+
" — the pair mounts together or not at all."), { phase: "config", sessionId, classification: "shared-memory-not-mounted" });
|
|
2006
|
+
}
|
|
2007
|
+
else {
|
|
2008
|
+
tools.push(...createSharedMemoryTools({
|
|
2009
|
+
provider: sharedMemoryProvider,
|
|
2010
|
+
context: { sessionId, taskId: hostTaskId, ...(spec.principal !== undefined ? { principal: spec.principal } : {}) },
|
|
2011
|
+
}).map((s) => defineTool(s)));
|
|
2012
|
+
sharedMemoryPairMounted = true;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
1966
2015
|
const skillsListing = skillSpecs.length > 0
|
|
1967
2016
|
? {
|
|
1968
2017
|
entries: skillSpecs.map((s) => ({
|
|
@@ -2142,6 +2191,56 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2142
2191
|
tools[i] = { ...t, description: t.descriptionClassic };
|
|
2143
2192
|
}
|
|
2144
2193
|
}
|
|
2194
|
+
const userToolNames = (spec.tools ?? []).map((t) => t.name);
|
|
2195
|
+
const protocolToolNames = [...mcp.tools.map((t) => t.name), ...a2a.tools.map((t) => t.name)];
|
|
2196
|
+
const mcpAlwaysLoadNames = mcp.tools
|
|
2197
|
+
.filter((t) => t.mcpAlwaysLoad === true && !(toolFaceSnapshot.defer ?? []).includes(t.name))
|
|
2198
|
+
.map((t) => t.name);
|
|
2199
|
+
const classifyDeferredOverFace = (face, builtinDeferNames) => {
|
|
2200
|
+
const deferredSet = classifyDeferred({
|
|
2201
|
+
specs: spec.tools ?? [],
|
|
2202
|
+
protocolToolNames,
|
|
2203
|
+
fullTools: face.filter((t) => userToolNames.includes(t.name) || protocolToolNames.includes(t.name)),
|
|
2204
|
+
deferMode: deps.deferMode,
|
|
2205
|
+
model,
|
|
2206
|
+
deferNames: [...(toolFaceSnapshot.defer ?? []), ...builtinDeferNames].filter((n) => face.some((t) => t.name === n)),
|
|
2207
|
+
alwaysLoadNames: [
|
|
2208
|
+
ASK_USER_QUESTION_TOOL_NAME,
|
|
2209
|
+
...(toolFaceSnapshot.alwaysLoad ?? []),
|
|
2210
|
+
...mcpAlwaysLoadNames,
|
|
2211
|
+
],
|
|
2212
|
+
});
|
|
2213
|
+
for (const n of [...deferredSet]) {
|
|
2214
|
+
if (!face.some((t) => t.name === n))
|
|
2215
|
+
deferredSet.delete(n);
|
|
2216
|
+
}
|
|
2217
|
+
return deferredSet;
|
|
2218
|
+
};
|
|
2219
|
+
const sharedMemoryPair = SHARED_MEMORY_TOOL_NAMES.filter((n) => tools.some((t) => t.name === n));
|
|
2220
|
+
let deferred;
|
|
2221
|
+
if (sharedMemoryPairMounted && sharedMemoryPair.length > 0) {
|
|
2222
|
+
const withoutPair = tools.filter((t) => !sharedMemoryPair.some((n) => n === t.name));
|
|
2223
|
+
const d1 = classifyDeferredOverFace(tools, sharedMemoryPair);
|
|
2224
|
+
const d0 = classifyDeferredOverFace(withoutPair, []);
|
|
2225
|
+
const soleCause = d0.size === 0 && d1.size > 0;
|
|
2226
|
+
const supportNameTaken = tools.some((t) => t.name === TOOL_SEARCH_NAME || (t.aliases ?? []).includes(TOOL_SEARCH_NAME));
|
|
2227
|
+
if (soleCause && supportNameTaken) {
|
|
2228
|
+
for (let i = tools.length - 1; i >= 0; i--) {
|
|
2229
|
+
if (sharedMemoryPair.some((n) => n === tools[i].name))
|
|
2230
|
+
tools.splice(i, 1);
|
|
2231
|
+
}
|
|
2232
|
+
sharedMemoryPairMounted = false;
|
|
2233
|
+
deps.onError?.(new Error(`Shared memory tools ${SHARED_MEMORY_TOOL_NAMES.join("/")} were NOT mounted: mounting them would inject ` +
|
|
2234
|
+
`the "${TOOL_SEARCH_NAME}" tool, whose name this task already declares — the pair mounts together or not at all.`), { phase: "config", sessionId, classification: "shared-memory-not-mounted" });
|
|
2235
|
+
deferred = d0;
|
|
2236
|
+
}
|
|
2237
|
+
else {
|
|
2238
|
+
deferred = d1;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
else {
|
|
2242
|
+
deferred = classifyDeferredOverFace(tools, []);
|
|
2243
|
+
}
|
|
2145
2244
|
let centerAdoption;
|
|
2146
2245
|
let centerAdoptionFresh = false;
|
|
2147
2246
|
{
|
|
@@ -2181,7 +2280,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2181
2280
|
isDefaultProvider: provider === defaultPromptProvider,
|
|
2182
2281
|
userSystemPrompt,
|
|
2183
2282
|
userAppendSystemPrompt,
|
|
2184
|
-
tools,
|
|
2283
|
+
tools: tools.map((t) => (t.aliases === undefined ? { ...t } : { ...t, aliases: [...t.aliases] })),
|
|
2185
2284
|
facts: {
|
|
2186
2285
|
supervisorEnabled: false,
|
|
2187
2286
|
teammateEnabled: internals?.explicitAgentName !== undefined && tools.some((t) => t.name === SEND_MESSAGE_TOOL_NAME),
|
|
@@ -2261,27 +2360,6 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2261
2360
|
});
|
|
2262
2361
|
}
|
|
2263
2362
|
}
|
|
2264
|
-
const userToolNames = (spec.tools ?? []).map((t) => t.name);
|
|
2265
|
-
const protocolToolNames = [...mcp.tools.map((t) => t.name), ...a2a.tools.map((t) => t.name)];
|
|
2266
|
-
const deferred = classifyDeferred({
|
|
2267
|
-
specs: spec.tools ?? [],
|
|
2268
|
-
protocolToolNames,
|
|
2269
|
-
fullTools: tools.filter((t) => userToolNames.includes(t.name) || protocolToolNames.includes(t.name)),
|
|
2270
|
-
deferMode: deps.deferMode,
|
|
2271
|
-
model,
|
|
2272
|
-
deferNames: (toolFaceSnapshot.defer ?? []).filter((n) => tools.some((t) => t.name === n)),
|
|
2273
|
-
alwaysLoadNames: [
|
|
2274
|
-
ASK_USER_QUESTION_TOOL_NAME,
|
|
2275
|
-
...(toolFaceSnapshot.alwaysLoad ?? []),
|
|
2276
|
-
...mcp.tools
|
|
2277
|
-
.filter((t) => t.mcpAlwaysLoad === true && !(toolFaceSnapshot.defer ?? []).includes(t.name))
|
|
2278
|
-
.map((t) => t.name),
|
|
2279
|
-
],
|
|
2280
|
-
});
|
|
2281
|
-
for (const n of [...deferred]) {
|
|
2282
|
-
if (!tools.some((t) => t.name === n))
|
|
2283
|
-
deferred.delete(n);
|
|
2284
|
-
}
|
|
2285
2363
|
const activeTools = new Set();
|
|
2286
2364
|
const fpRef = {};
|
|
2287
2365
|
const turnSnapshotRef = {};
|
|
@@ -2291,6 +2369,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2291
2369
|
.map((s) => ({ name: inlineUntrusted(s.name, 160), ...(s.error !== undefined ? { error: inlineUntrusted(s.error, 240) } : {}) }));
|
|
2292
2370
|
let toolsDeltaRef;
|
|
2293
2371
|
let toolMaterializeStatic = false;
|
|
2372
|
+
let deferDirectCall = false;
|
|
2294
2373
|
const staticFaceForRef = {};
|
|
2295
2374
|
if (deferred.size > 0 || failedMcpServers.length > 0) {
|
|
2296
2375
|
toolsDeltaRef = { pending: [], pendingRemoved: [], pendingReadded: [], pendingFailed: failedMcpServers };
|
|
@@ -2313,6 +2392,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2313
2392
|
const laneDegrade = requestedStrategy === "static" && spec.deferSelfResolve === false;
|
|
2314
2393
|
const materializeStatic = requestedStrategy === "static" && !laneDegrade;
|
|
2315
2394
|
toolMaterializeStatic = materializeStatic;
|
|
2395
|
+
deferDirectCall = spec.deferSelfResolve !== false;
|
|
2316
2396
|
promptManifest.toolDisclosure = {
|
|
2317
2397
|
deferredTools: deferred.size,
|
|
2318
2398
|
strategy: materializeStatic ? "static" : "swap",
|
|
@@ -3336,15 +3416,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3336
3416
|
});
|
|
3337
3417
|
const commitSuspendSaga = async (token, cp, remoteEnv, remoteHandle) => {
|
|
3338
3418
|
if (!checkpointStore)
|
|
3339
|
-
return false;
|
|
3419
|
+
return { ok: false };
|
|
3340
3420
|
if (memoryEngineSession)
|
|
3341
3421
|
await memoryEngineSession.harvest("checkpoint");
|
|
3342
3422
|
try {
|
|
3343
3423
|
await checkpointStore.put(token, cp);
|
|
3344
|
-
return true;
|
|
3424
|
+
return { ok: true };
|
|
3345
3425
|
}
|
|
3346
3426
|
catch (putErr) {
|
|
3347
3427
|
deps.onError?.(putErr, { phase: "config", sessionId });
|
|
3428
|
+
const reason = "the approval checkpoint could not be persisted (the store rejected the write; the deployment's error face carries the store's own message)";
|
|
3348
3429
|
if (remoteEnv !== undefined && remoteHandle?.snapshotId !== undefined) {
|
|
3349
3430
|
const { outcome: back, attempts: backAttempts } = await restoreWorkspaceWithRetry(remoteEnv, remoteHandle.snapshotId, {
|
|
3350
3431
|
abortSignal: abortController.signal,
|
|
@@ -3352,7 +3433,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3352
3433
|
if (back.ok) {
|
|
3353
3434
|
const init = await remoteEnv.postResumeInit();
|
|
3354
3435
|
if (init.ok)
|
|
3355
|
-
return false;
|
|
3436
|
+
return { ok: false, reason };
|
|
3356
3437
|
remoteEnvFailures.push(remoteEnvFailureNote("postResumeInit", init.error, 1));
|
|
3357
3438
|
deps.onError?.(init.error, { phase: "config", sessionId });
|
|
3358
3439
|
}
|
|
@@ -3362,9 +3443,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3362
3443
|
}
|
|
3363
3444
|
abortController.abort();
|
|
3364
3445
|
void harness.abort();
|
|
3365
|
-
return false;
|
|
3446
|
+
return { ok: false, reason };
|
|
3366
3447
|
}
|
|
3367
|
-
return false;
|
|
3448
|
+
return { ok: false, reason };
|
|
3368
3449
|
}
|
|
3369
3450
|
};
|
|
3370
3451
|
const publishCommittedSuspend = (token, gate, scope, remoteHandle) => {
|
|
@@ -3452,7 +3533,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3452
3533
|
resourceLedger: resourceLedgerOut,
|
|
3453
3534
|
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
3454
3535
|
};
|
|
3455
|
-
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)))
|
|
3536
|
+
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).ok)
|
|
3456
3537
|
return false;
|
|
3457
3538
|
publishCommittedSuspend(token, gate, scope, remoteHandle);
|
|
3458
3539
|
try {
|
|
@@ -3537,7 +3618,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3537
3618
|
...(spec.principal ? { principal: spec.principal } : {}),
|
|
3538
3619
|
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
3539
3620
|
};
|
|
3540
|
-
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)))
|
|
3621
|
+
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).ok)
|
|
3541
3622
|
return false;
|
|
3542
3623
|
publishCommittedSuspend(token, gate, scope, remoteHandle);
|
|
3543
3624
|
try {
|
|
@@ -3633,7 +3714,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3633
3714
|
};
|
|
3634
3715
|
const suspendAsk = parkLaneArmed && checkpointStore !== undefined
|
|
3635
3716
|
? async (req, postHookArgs, safety, liveFaceUnavailable) => {
|
|
3636
|
-
const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : onAsk
|
|
3717
|
+
const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
|
|
3637
3718
|
if (syncFirstEligible &&
|
|
3638
3719
|
runtimeCaps?.forceDurableGate !== true &&
|
|
3639
3720
|
liveFaceUnavailable !== true &&
|
|
@@ -3646,13 +3727,50 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3646
3727
|
let remoteHandle;
|
|
3647
3728
|
const remoteEnv = suspendableEnv;
|
|
3648
3729
|
try {
|
|
3730
|
+
const parked = tryCloneArgs(postHookArgs);
|
|
3731
|
+
if (!parked.ok) {
|
|
3732
|
+
throw new ParkRefusal(`the arguments of "${req.toolName}" cannot be captured onto an approval checkpoint (unclonable value: ${parked.reason}) — ` +
|
|
3733
|
+
`a durable approval row must hold an inert snapshot of exactly what a human approves and a resume executes`, { cause: parked.cause });
|
|
3734
|
+
}
|
|
3735
|
+
let backendFidelity;
|
|
3736
|
+
try {
|
|
3737
|
+
backendFidelity = resolveDeclaredFidelity(checkpointStore, "checkpointStore");
|
|
3738
|
+
}
|
|
3739
|
+
catch (err) {
|
|
3740
|
+
throw new ParkRefusal(`the checkpoint backend declares an unrecognized encoding fidelity, so the width of the snapshot this row may hold is unknown (${describeThrown(err)}) — ` +
|
|
3741
|
+
`refusing to park rather than guess which values would survive it`, { cause: err });
|
|
3742
|
+
}
|
|
3743
|
+
const captured = encodeAtFidelity(backendFidelity, parked.value);
|
|
3744
|
+
if (!captured.ok) {
|
|
3745
|
+
throw new ParkRefusal(`the arguments of "${req.toolName}" cannot be encoded by this checkpoint backend (${describeThrown(captured.cause)}) — ` +
|
|
3746
|
+
`a durable approval row must hold an inert snapshot of exactly what a human approves and a resume executes`, { cause: captured.cause });
|
|
3747
|
+
}
|
|
3748
|
+
const parkedArgs = captured.value;
|
|
3749
|
+
const projectionMovedArgs = backendFidelity !== "structured-clone" && !samePlainValue(parked.value, parkedArgs);
|
|
3750
|
+
if (projectionMovedArgs && basePolicyForResumeEdit !== undefined) {
|
|
3751
|
+
const presented = encodeAtFidelity(backendFidelity, parkedArgs);
|
|
3752
|
+
if (!presented.ok) {
|
|
3753
|
+
throw new ParkRefusal(`the stored form of "${req.toolName}"'s arguments could not be presented for re-adjudication (${describeThrown(presented.cause)})`, { cause: presented.cause });
|
|
3754
|
+
}
|
|
3755
|
+
const reprojected = refuseOutOfContractDecision(await basePolicyForResumeEdit.check({ toolName: req.toolName, args: presented.value, toolCallId: req.toolCallId }, abortController.signal));
|
|
3756
|
+
const demanded = reprojected.updatedInput !== undefined ? tryCloneArgs(reprojected.updatedInput) : undefined;
|
|
3757
|
+
const rewroteTheFiledValue = reprojected.updatedInput !== undefined &&
|
|
3758
|
+
!(demanded?.ok === true &&
|
|
3759
|
+
samePlainValue(demanded.value, parkedArgs) &&
|
|
3760
|
+
samePlainValue(reprojected.updatedInput, parkedArgs));
|
|
3761
|
+
if (reprojected.action === "deny" || rewroteTheFiledValue) {
|
|
3762
|
+
throw new ParkRefusal(`the arguments of "${req.toolName}" change shape when stored by this checkpoint backend, and the deployment's tool policy does not accept the stored shape` +
|
|
3763
|
+
`${reprojected.action === "deny" && reprojected.message ? `: ${reprojected.message}` : ""} — ` +
|
|
3764
|
+
`refusing to file a row whose executable form nothing adjudicated`);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3649
3767
|
if (offloadStore && isVolatileOffloadStore(offloadStore) && !offloadStore.isEmpty()) {
|
|
3650
|
-
throw new
|
|
3768
|
+
throw new ParkRefusal("durable suspend requires a durable toolResultStore (the default InMemoryToolResultStore " +
|
|
3651
3769
|
"holds offloaded results that are lost across a cross-replica resume); inject " +
|
|
3652
3770
|
"RunnerDeps.toolResultStore or disable offload.");
|
|
3653
3771
|
}
|
|
3654
3772
|
if (ownedEnv !== undefined && remoteEnv === undefined && parkOnlyRemoteEnv === undefined) {
|
|
3655
|
-
throw new
|
|
3773
|
+
throw new ParkRefusal(incompleteSuspendAdapter !== undefined
|
|
3656
3774
|
? `durable suspend is not supported with this per-task executionEnvFactory env: it declares capabilities.suspendable but its adapter does not implement ${incompleteSuspendAdapter.join(" or ")}, so a snapshot taken now could never be restored. Implement the full RemoteExecutionEnv restore surface, or declare capabilities.suspendable:false if the workspace is externally durable (the park-only lane).`
|
|
3657
3775
|
: "durable suspend is not supported with a non-remote per-task executionEnvFactory env: the " +
|
|
3658
3776
|
"minted env is destroyed on suspend, so a resumed file/shell tool would act on a fresh " +
|
|
@@ -3661,7 +3779,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3661
3779
|
}
|
|
3662
3780
|
const leafId = await session.getLeafId();
|
|
3663
3781
|
if (!leafId) {
|
|
3664
|
-
throw new
|
|
3782
|
+
throw new ParkRefusal("cannot suspend: session has no committed leaf to resume from");
|
|
3665
3783
|
}
|
|
3666
3784
|
const { messages } = await session.buildContext();
|
|
3667
3785
|
const { batchToolCallIds, completedCallIds } = batchContextAt(messages, req.toolCallId);
|
|
@@ -3688,7 +3806,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3688
3806
|
token = mintCheckpointToken();
|
|
3689
3807
|
const riskDescriptor = buildRiskDescriptor({
|
|
3690
3808
|
toolName: req.toolName,
|
|
3691
|
-
args:
|
|
3809
|
+
args: parkedArgs,
|
|
3692
3810
|
safety,
|
|
3693
3811
|
shellGated: (req.toolName === "Bash" && shellGatedBash) || (req.toolName === "Monitor" && shellGatedMonitor),
|
|
3694
3812
|
...(effectiveShellGate !== "off" ? { shellGateDoctrine: effectiveShellGate } : {}),
|
|
@@ -3731,12 +3849,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3731
3849
|
kind: "tool_approval",
|
|
3732
3850
|
toolCallId: req.toolCallId,
|
|
3733
3851
|
toolName: req.toolName,
|
|
3734
|
-
args:
|
|
3852
|
+
args: parkedArgs,
|
|
3735
3853
|
...(() => {
|
|
3736
|
-
const preview = approvalPreviewOf(req.toolName,
|
|
3854
|
+
const preview = approvalPreviewOf(req.toolName, parkedArgs);
|
|
3737
3855
|
return preview !== undefined ? { preview } : {};
|
|
3738
3856
|
})(),
|
|
3739
|
-
boundInputHash: boundInputHashOf(
|
|
3857
|
+
boundInputHash: boundInputHashOf(parkedArgs),
|
|
3740
3858
|
batchToolCallIds,
|
|
3741
3859
|
completedCallIds,
|
|
3742
3860
|
},
|
|
@@ -3761,10 +3879,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3761
3879
|
}
|
|
3762
3880
|
catch (err) {
|
|
3763
3881
|
deps.onError?.(err, { phase: "config", sessionId });
|
|
3764
|
-
return
|
|
3882
|
+
return {
|
|
3883
|
+
parkFailed: err instanceof ParkRefusal
|
|
3884
|
+
? err.message
|
|
3885
|
+
: "the durable approval park could not be prepared (the deployment's error face carries the exception)",
|
|
3886
|
+
};
|
|
3765
3887
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
3888
|
+
const committed = await commitSuspendSaga(token, cp, remoteEnv, remoteHandle);
|
|
3889
|
+
if (!committed.ok) {
|
|
3890
|
+
return committed.reason !== undefined ? { parkFailed: committed.reason } : undefined;
|
|
3768
3891
|
}
|
|
3769
3892
|
publishCommittedSuspend(token, gate, cp.scope, remoteHandle);
|
|
3770
3893
|
try {
|
|
@@ -4139,7 +4262,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4139
4262
|
: undefined;
|
|
4140
4263
|
overheadState.promptChars = systemPrompt.length;
|
|
4141
4264
|
const preparedHolder = {};
|
|
4142
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4265
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4143
4266
|
const prepared = buildPrepared();
|
|
4144
4267
|
preparedHolder.current = prepared;
|
|
4145
4268
|
return prepared;
|
|
@@ -42,6 +42,7 @@ import { refuseOutOfContractDecision, toolPolicyNameSets } from "../tool-policy.
|
|
|
42
42
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
43
43
|
import { discloseDroppedPending, isDelegatedAgentTerminal, PendingSessionNotifications, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
44
44
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
45
|
+
import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
|
|
45
46
|
import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
|
|
46
47
|
import { structuredFrom, toolOutputFrom } from "./tool-output-projection.js";
|
|
47
48
|
function createRunState() {
|
|
@@ -576,7 +577,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
576
577
|
skillsListing: rs.attach.skillsListingOn,
|
|
577
578
|
mcpInstructions: mcpInstructionsOn,
|
|
578
579
|
},
|
|
579
|
-
...(undiscoveredTools !== undefined && undiscoveredTools.length > 0
|
|
580
|
+
...(undiscoveredTools !== undefined && undiscoveredTools.length > 0
|
|
581
|
+
? { undiscoveredTools, deferDirectCall: prepared.deferDirectCall }
|
|
582
|
+
: {}),
|
|
580
583
|
...(changed !== undefined ? { changedFiles: changed } : {}),
|
|
581
584
|
...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
|
|
582
585
|
? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
|
|
@@ -1821,8 +1824,12 @@ export class Runner {
|
|
|
1821
1824
|
resolve("queued");
|
|
1822
1825
|
});
|
|
1823
1826
|
};
|
|
1827
|
+
const peerSelfRef = internals?.peerSelfRef ?? createPeerSelfRef(internals?.registryScope ?? spec.principal ?? "default");
|
|
1828
|
+
const peerInboundChainRef = internals?.peerInboundChainRef ?? createPeerInboundChainRef();
|
|
1824
1829
|
const prepared = await prepareTask(spec, this.deps, this.sessions, prepareResume, {
|
|
1825
1830
|
...(internals ?? {}),
|
|
1831
|
+
peerSelfRef,
|
|
1832
|
+
peerInboundChainRef,
|
|
1826
1833
|
detachHub,
|
|
1827
1834
|
onTaskNotification: (notification, opts) => {
|
|
1828
1835
|
try {
|
|
@@ -1849,6 +1856,11 @@ export class Runner {
|
|
|
1849
1856
|
for (const p of payloads)
|
|
1850
1857
|
this.pendingSessionNotifications.pend(notificationSessionId, p);
|
|
1851
1858
|
};
|
|
1859
|
+
prepared.harness.onEngineNoteConsumed = (p) => {
|
|
1860
|
+
const peer = p?.peer;
|
|
1861
|
+
if (peer !== undefined && Array.isArray(peer.hopChain))
|
|
1862
|
+
peerInboundChainRef.current = [...peer.hopChain];
|
|
1863
|
+
};
|
|
1852
1864
|
if (notifyRef)
|
|
1853
1865
|
notifyRef.inject = injectTaskNotification;
|
|
1854
1866
|
try {
|
|
@@ -94,6 +94,7 @@ export interface AttachmentInputs {
|
|
|
94
94
|
mcpInstructions?: boolean;
|
|
95
95
|
};
|
|
96
96
|
undiscoveredTools?: readonly string[];
|
|
97
|
+
deferDirectCall?: boolean;
|
|
97
98
|
changedFiles?: ReadonlyArray<{
|
|
98
99
|
path: string;
|
|
99
100
|
mtimeMs: number;
|
|
@@ -118,7 +119,7 @@ export interface AttachmentInputs {
|
|
|
118
119
|
mcpDroppedTools?: ReadonlyArray<McpDroppedTool>;
|
|
119
120
|
}
|
|
120
121
|
export declare function collectDueAttachments(state: AttachmentState, inp: AttachmentInputs): readonly TurnAttachment[];
|
|
121
|
-
export declare function renderToolSearchUsageReminder(undiscovered: readonly string[]): string;
|
|
122
|
+
export declare function renderToolSearchUsageReminder(undiscovered: readonly string[], directCallEnabled?: boolean): string;
|
|
122
123
|
export declare function renderDateChange(newDate: string): string;
|
|
123
124
|
export declare function collectDateChange(state: DateChangeState, today: string): TurnAttachment | undefined;
|
|
124
125
|
export declare function collectInstructionsChange(state: InstructionsChangeState, probed: ReadonlyArray<{
|
|
@@ -145,7 +145,7 @@ export function collectDueAttachments(state, inp) {
|
|
|
145
145
|
const sinceUse = state.toolSearchLastUseTurn === undefined ? Number.POSITIVE_INFINITY : cadenceNow - state.toolSearchLastUseTurn;
|
|
146
146
|
if (sinceUse >= TOOL_SEARCH_REMINDER_CONFIG.EVERY_N_TURNS && cadenceNow - state.lastToolSearchReminderTurn >= TOOL_SEARCH_REMINDER_CONFIG.EVERY_N_TURNS) {
|
|
147
147
|
state.lastToolSearchReminderTurn = cadenceNow;
|
|
148
|
-
(out ??= []).push({ source: "tool_search_usage_reminder", body: renderToolSearchUsageReminder(inp.undiscoveredTools) });
|
|
148
|
+
(out ??= []).push({ source: "tool_search_usage_reminder", body: renderToolSearchUsageReminder(inp.undiscoveredTools, inp.deferDirectCall !== false) });
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
if (inp.config.planModeReminder && inp.planActive) {
|
|
@@ -248,14 +248,17 @@ function renderTaskReminder(state) {
|
|
|
248
248
|
}
|
|
249
249
|
return message;
|
|
250
250
|
}
|
|
251
|
-
export function renderToolSearchUsageReminder(undiscovered) {
|
|
251
|
+
export function renderToolSearchUsageReminder(undiscovered, directCallEnabled = true) {
|
|
252
252
|
const shown = undiscovered.slice(0, TOOL_SEARCH_REMINDER_CONFIG.MAX_NAMES);
|
|
253
253
|
const more = undiscovered.length - shown.length;
|
|
254
254
|
const names = `${shown.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`;
|
|
255
255
|
return (`Some available tools' schemas are not loaded in this conversation yet: ${names}. Before concluding a ` +
|
|
256
256
|
`capability is missing or building a workaround, use ${TOOL_SEARCH_TOOL_NAME} to find and load relevant tools — ` +
|
|
257
|
-
`keywords to search, or query "select:<name>[,<name>...]" for specific tools.
|
|
258
|
-
|
|
257
|
+
`keywords to search, or query "select:<name>[,<name>...]" for specific tools. ` +
|
|
258
|
+
(directCallEnabled
|
|
259
|
+
? `You do not have these tools' parameters, so activate one rather than guessing its arguments. `
|
|
260
|
+
: `Calling a tool before its schema is loaded will fail. `) +
|
|
261
|
+
`This is just a gentle reminder - ignore if not applicable to the current work.`);
|
|
259
262
|
}
|
|
260
263
|
export function renderDateChange(newDate) {
|
|
261
264
|
return `The date has changed. Today's date is now ${newDate}. DO NOT mention this to the user explicitly because they are already aware.`;
|
|
@@ -400,8 +403,8 @@ export function renderToolsDelta(input) {
|
|
|
400
403
|
if (added.length > 0) {
|
|
401
404
|
const swapped = input.staticFace === true ? (input.swappedUnderStatic ?? []).filter((n) => added.includes(n)) : [];
|
|
402
405
|
blocks.push((input.staticFace === true
|
|
403
|
-
? "The following deferred tools are now active — call them directly.
|
|
404
|
-
|
|
406
|
+
? "The following deferred tools are now active — call them directly. The tools list keeps compact " +
|
|
407
|
+
'placeholder entries, so re-run ToolSearch ("select:<name>") if you need a parameter schema:\n'
|
|
405
408
|
: "The following deferred tools are now available. Their full schemas are loaded — call them " +
|
|
406
409
|
"directly like any other tool:\n") +
|
|
407
410
|
added.map((n) => `- ${n}`).join("\n") +
|
|
@@ -10,6 +10,20 @@ const INTERRUPTED_IDEMPOTENT = "[INTERRUPTED] The previous run ended before this
|
|
|
10
10
|
"what you intended. If you decide not to re-issue it, verify the current state first.";
|
|
11
11
|
const INTERRUPTED_NEVER_STARTED = "[INTERRUPTED] The run was aborted before this tool call started. It was never executed and had " +
|
|
12
12
|
"no side effects — it is safe to re-issue this call if you still need it.";
|
|
13
|
+
const INTERRUPTED_REPEAT_UNKNOWN = "[INTERRUPTED] Same as an earlier interrupted call in this batch: no result was recorded and the outcome is " +
|
|
14
|
+
"UNKNOWN — verify before relying on it.";
|
|
15
|
+
const INTERRUPTED_REPEAT_SAFE = "[INTERRUPTED] Same as an earlier interrupted call in this batch: this tool is read-only/idempotent, so just " +
|
|
16
|
+
"call it again if you still need the result.";
|
|
17
|
+
const INTERRUPTED_REPEAT_IDEMPOTENT = "[INTERRUPTED] Same as an earlier interrupted call in this batch: it is safe to REPLAY — re-issue it, or verify " +
|
|
18
|
+
"the current state if you decide not to.";
|
|
19
|
+
const INTERRUPTED_REPEAT_NEVER_STARTED = "[INTERRUPTED] Same as an earlier interrupted call in this batch: it never started and had no side effects — " +
|
|
20
|
+
"safe to re-issue.";
|
|
21
|
+
const REPEAT_TEXT = {
|
|
22
|
+
never_started: INTERRUPTED_REPEAT_NEVER_STARTED,
|
|
23
|
+
read: INTERRUPTED_REPEAT_SAFE,
|
|
24
|
+
idempotent: INTERRUPTED_REPEAT_IDEMPOTENT,
|
|
25
|
+
unknown: INTERRUPTED_REPEAT_UNKNOWN,
|
|
26
|
+
};
|
|
13
27
|
export function findOrphanToolCalls(messages, suspendedBatch) {
|
|
14
28
|
const resolvedAt = new Map();
|
|
15
29
|
const callSitesAt = new Map();
|
|
@@ -70,11 +84,15 @@ export async function reconcileInterruptedSession(session, toolEffects, suspende
|
|
|
70
84
|
const { messages } = await session.buildContext();
|
|
71
85
|
const orphans = findOrphanToolCalls(messages, suspendedBatch).filter((o) => o.kind !== "result");
|
|
72
86
|
const recovered = [];
|
|
87
|
+
const statedInFull = new Set();
|
|
73
88
|
for (const orphan of orphans) {
|
|
74
89
|
const effect = toolEffects?.get(orphan.toolName) ?? "write";
|
|
75
90
|
const effectText = effect === "read" ? INTERRUPTED_SAFE : effect === "idempotent" ? INTERRUPTED_IDEMPOTENT : INTERRUPTED_UNKNOWN;
|
|
76
91
|
const neverStarted = startedToolCallIds !== undefined && !startedToolCallIds.has(orphan.toolCallId);
|
|
77
|
-
const
|
|
92
|
+
const fullText = neverStarted ? INTERRUPTED_NEVER_STARTED : effectText;
|
|
93
|
+
const cls = neverStarted ? "never_started" : effect === "read" ? "read" : effect === "idempotent" ? "idempotent" : "unknown";
|
|
94
|
+
const text = statedInFull.has(cls) ? REPEAT_TEXT[cls] : fullText;
|
|
95
|
+
statedInFull.add(cls);
|
|
78
96
|
const errorKind = neverStarted ? "interrupted_never_started" : "interrupted_outcome_unknown";
|
|
79
97
|
const entryId = await session.appendMessage({
|
|
80
98
|
role: "toolResult",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SharedMemorySnapshot, SharedMemoryStoreInfo, SharedMemoryStoreProvider } from "./types.js";
|
|
2
|
+
export interface SharedMemoryFixture {
|
|
3
|
+
state: SharedMemorySnapshot["state"];
|
|
4
|
+
stores: Array<{
|
|
5
|
+
info: SharedMemoryStoreInfo;
|
|
6
|
+
documents: Array<{
|
|
7
|
+
path: string;
|
|
8
|
+
content: string;
|
|
9
|
+
updatedAt?: string;
|
|
10
|
+
}>;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export interface SharedMemoryStoreContractHooks {
|
|
14
|
+
make: (fixture: SharedMemoryFixture) => Promise<SharedMemoryStoreProvider> | SharedMemoryStoreProvider;
|
|
15
|
+
runAssertion?: (name: string, fn: () => Promise<void>) => void | Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare function sharedMemoryStoreContract(hooks: SharedMemoryStoreContractHooks): Promise<void>;
|