@ouro.bot/cli 0.1.0-alpha.810 → 0.1.0-alpha.812
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.json +18 -0
- package/deploy/unraid/README.txt +3 -6
- package/deploy/unraid/sanctuary-acceptance-contract.json +1 -1
- package/deploy/unraid/sanctuary-deployment-target.mjs +1 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +8 -7
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +1 -1
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/config.js +32 -2
- package/dist/heart/core.js +167 -115
- package/dist/heart/daemon/daemon.js +19 -9
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +88 -50
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +53 -15
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +6 -26
- package/dist/heart/frontend-approval-runtime.js +87 -13
- package/dist/heart/identity.js +17 -5
- package/dist/heart/session-events.js +8 -1
- package/dist/heart/tool-approval.js +64 -31
- package/dist/mind/pending.js +4 -4
- package/dist/repertoire/mcp-manager.js +365 -381
- package/dist/repertoire/mcp-tools.js +66 -25
- package/dist/repertoire/plugin-mcp.js +3 -3
- package/dist/repertoire/shell-sessions.js +8 -7
- package/dist/repertoire/tool-arguments.js +30 -8
- package/dist/repertoire/tools-session.js +30 -5
- package/dist/repertoire/tools-shell.js +30 -14
- package/dist/repertoire/tools-voice.js +6 -6
- package/dist/repertoire/tools.js +231 -205
- package/dist/senses/bluebubbles/index.js +4 -2
- package/dist/senses/cli.js +6 -5
- package/dist/senses/private-runtime.js +9 -3
- package/dist/senses/shared-turn.js +12 -9
- package/dist/senses/teams.js +15 -12
- package/dist/senses/telegram-approval-runtime.js +129 -20
- package/dist/senses/telegram-client.js +19 -2
- package/dist/senses/telegram.js +54 -15
- package/dist/senses/voice/twilio-phone.js +123 -173
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -37,6 +37,7 @@ exports.settleFrontendApproval = settleFrontendApproval;
|
|
|
37
37
|
exports.createFrontendApprovalRuntime = createFrontendApprovalRuntime;
|
|
38
38
|
const node_crypto_1 = require("node:crypto");
|
|
39
39
|
const path = __importStar(require("node:path"));
|
|
40
|
+
const friends_1 = require("@ouro.bot/friends");
|
|
40
41
|
const approval_files_1 = require("./approval-files");
|
|
41
42
|
const approval_store_1 = require("./approval-store");
|
|
42
43
|
const tool_approval_1 = require("./tool-approval");
|
|
@@ -52,12 +53,10 @@ const PERMISSION_OPTIONS = [
|
|
|
52
53
|
{ optionId: "allow-once", name: "Allow once", kind: "allow_once" },
|
|
53
54
|
{ optionId: "reject-once", name: "Reject", kind: "reject_once" },
|
|
54
55
|
];
|
|
55
|
-
|
|
56
|
-
async function defaultReleaseRuntimeMcpServers() {
|
|
56
|
+
async function defaultReleaseRuntimeMcpServers(owner) {
|
|
57
57
|
const manager = await Promise.resolve().then(() => __importStar(require("../repertoire/mcp-manager")));
|
|
58
|
-
await manager.releaseRuntimeMcpServers();
|
|
58
|
+
await manager.releaseRuntimeMcpServers(owner);
|
|
59
59
|
}
|
|
60
|
-
/* v8 ignore stop */
|
|
61
60
|
const DEFAULT_SETTLEMENT_DEPENDENCIES = {
|
|
62
61
|
withTurnExecutionLease: turn_execution_lease_1.withTurnExecutionLease,
|
|
63
62
|
setAgentName: identity_1.setAgentName,
|
|
@@ -66,7 +65,10 @@ const DEFAULT_SETTLEMENT_DEPENDENCIES = {
|
|
|
66
65
|
executeApprovalDecision: tool_approval_1.executeApprovalDecision,
|
|
67
66
|
resolveToolDefinition: tools_1.resolveToolDefinition,
|
|
68
67
|
approvalPolicyForInvocation: tools_1.approvalPolicyForInvocation,
|
|
69
|
-
|
|
68
|
+
executeTool: tools_1.executeTool,
|
|
69
|
+
preflightToolCall: tools_1.preflightToolCall,
|
|
70
|
+
selectToolsForChannel: tools_1.selectToolsForChannel,
|
|
71
|
+
getProviderRuntime: core_1.getProviderRuntime,
|
|
70
72
|
getSharedMcpManager: mcp_manager_1.getSharedMcpManager,
|
|
71
73
|
releaseRuntimeMcpServers: defaultReleaseRuntimeMcpServers,
|
|
72
74
|
loadSession: context_1.loadSession,
|
|
@@ -86,15 +88,18 @@ async function settleFrontendApproval(input, dependencies = DEFAULT_SETTLEMENT_D
|
|
|
86
88
|
const record = input.approvalStore.read(input.suspension.approvalId);
|
|
87
89
|
if (!record)
|
|
88
90
|
throw new Error(`frontend approval record is unavailable: ${input.suspension.approvalId}`);
|
|
91
|
+
const owner = Object.freeze({ agentName: input.request.agent, agentRoot: input.agentRoot });
|
|
92
|
+
const toolsDisabled = input.request.disableTools === true;
|
|
93
|
+
const runtimeServers = input.request.runtimeMcpServers;
|
|
89
94
|
return dependencies.withTurnExecutionLease(async () => {
|
|
90
|
-
dependencies.setAgentName(
|
|
95
|
+
dependencies.setAgentName(owner.agentName);
|
|
91
96
|
try {
|
|
92
|
-
const mcpManager = await dependencies.getSharedMcpManager(input.request.runtimeMcpServers ? { runtimeServers: input.request.runtimeMcpServers } : undefined) ?? undefined;
|
|
93
97
|
return await dependencies.withSessionTurnLease(record.sessionPath, async (lease) => {
|
|
94
98
|
const currentRevision = dependencies.readSessionTransaction(record.sessionPath, lease).revision;
|
|
95
99
|
const continuationSignal = input.optionId === "cancelled" ? AbortSignal.abort() : input.signal;
|
|
96
100
|
const liveToolContext = input.approvalRequest.liveToolContext ?? {
|
|
97
101
|
signin: async () => undefined,
|
|
102
|
+
agentName: owner.agentName,
|
|
98
103
|
agentRoot: input.agentRoot,
|
|
99
104
|
currentSession: {
|
|
100
105
|
friendId: input.request.friendId,
|
|
@@ -104,6 +109,47 @@ async function settleFrontendApproval(input, dependencies = DEFAULT_SETTLEMENT_D
|
|
|
104
109
|
},
|
|
105
110
|
currentUserMessage: input.request.message,
|
|
106
111
|
};
|
|
112
|
+
const currentOptions = async () => {
|
|
113
|
+
try {
|
|
114
|
+
if ((liveToolContext.agentName !== undefined && liveToolContext.agentName !== owner.agentName)
|
|
115
|
+
|| (liveToolContext.agentRoot !== undefined && liveToolContext.agentRoot !== owner.agentRoot)) {
|
|
116
|
+
throw new Error("approval context owner coordinates changed");
|
|
117
|
+
}
|
|
118
|
+
let context = { ...liveToolContext, ...owner };
|
|
119
|
+
const original = liveToolContext.relationshipAuthorization;
|
|
120
|
+
if (original) {
|
|
121
|
+
if (liveToolContext.agentName !== owner.agentName || liveToolContext.agentRoot !== owner.agentRoot) {
|
|
122
|
+
throw new Error("approval context owner coordinates changed");
|
|
123
|
+
}
|
|
124
|
+
const current = await original.resolveCurrent?.();
|
|
125
|
+
if (!current?.actor || current.actor.friendId !== input.request.friendId
|
|
126
|
+
|| current.actor.trustLevel !== original.actor?.trustLevel || current.profileId !== original.profileId
|
|
127
|
+
|| original.authorizedContextScopes.some((scope) => !current.authorizedContextScopes.includes(scope))) {
|
|
128
|
+
throw new Error("current approval relationship authority is unavailable");
|
|
129
|
+
}
|
|
130
|
+
context = { ...context, relationshipAuthorization: current };
|
|
131
|
+
}
|
|
132
|
+
const providerRuntime = await dependencies.getProviderRuntime((0, friends_1.channelToFacing)(input.request.channel), owner);
|
|
133
|
+
const mcpManager = toolsDisabled ? undefined : await dependencies.getSharedMcpManager({ ...owner, runtimeServers }) ?? undefined;
|
|
134
|
+
const selectCurrentTools = () => toolsDisabled
|
|
135
|
+
? Object.freeze({ ordinary: Object.freeze([]), engine: Object.freeze([]) })
|
|
136
|
+
: dependencies.selectToolsForChannel((0, friends_1.getChannelCapabilities)(input.request.channel), context.context?.friend.toolPreferences, context.context, providerRuntime.capabilities, mcpManager, providerRuntime.model, context);
|
|
137
|
+
return {
|
|
138
|
+
providerRuntimeOverride: providerRuntime, mcpManager,
|
|
139
|
+
...(toolsDisabled ? { tools: [], hardDisableTools: true } : {}),
|
|
140
|
+
toolContext: { ...context, toolSelection: selectCurrentTools(), selectCurrentTools },
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
(0, runtime_1.emitNervesEvent)({
|
|
145
|
+
level: "warn", component: "heart", event: "heart.frontend_approval_authority_unavailable",
|
|
146
|
+
message: "current approval authority could not be reconstructed; execution remains disabled",
|
|
147
|
+
meta: { approvalId: record.approvalId, category: error instanceof Error ? error.name : "unknown" },
|
|
148
|
+
});
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
let definition;
|
|
107
153
|
const terminal = await dependencies.executeApprovalDecision({
|
|
108
154
|
approvalStore: input.approvalStore,
|
|
109
155
|
checkpointStore: input.checkpointStore,
|
|
@@ -121,11 +167,36 @@ async function settleFrontendApproval(input, dependencies = DEFAULT_SETTLEMENT_D
|
|
|
121
167
|
},
|
|
122
168
|
ownerId: `frontend-decision-${dependencies.randomUUID()}`,
|
|
123
169
|
currentSessionRevision: currentRevision,
|
|
124
|
-
resolveTool:
|
|
125
|
-
|
|
170
|
+
resolveTool: async (name) => {
|
|
171
|
+
const current = await currentOptions();
|
|
172
|
+
definition = current ? dependencies.resolveToolDefinition(name, current.toolContext.toolSelection) : undefined;
|
|
173
|
+
return definition;
|
|
174
|
+
},
|
|
175
|
+
resolveApprovalPolicy: async (name, args) => {
|
|
176
|
+
const current = await currentOptions();
|
|
177
|
+
return current ? dependencies.approvalPolicyForInvocation(name, args, current.toolContext) : { kind: "not_required" };
|
|
178
|
+
},
|
|
126
179
|
liveGuard: async () => ({ ok: true }),
|
|
127
180
|
liveRisk: async () => ({ ok: true }),
|
|
128
|
-
|
|
181
|
+
preflight: async (context) => {
|
|
182
|
+
const current = await currentOptions();
|
|
183
|
+
if (!current)
|
|
184
|
+
return { ok: false, reason: "current tool authority is unavailable" };
|
|
185
|
+
const result = await dependencies.preflightToolCall(context.record.toolName, context.arguments, {
|
|
186
|
+
...current.toolContext,
|
|
187
|
+
toolSelection: Object.freeze({ ordinary: Object.freeze([context.definition]), engine: Object.freeze([]) }),
|
|
188
|
+
});
|
|
189
|
+
return result.kind === "ready" ? { ok: true } : { ok: false, reason: result.text };
|
|
190
|
+
},
|
|
191
|
+
execute: async (name, args) => {
|
|
192
|
+
const current = await currentOptions();
|
|
193
|
+
if (!current || !definition)
|
|
194
|
+
return { kind: "rejected_before_handler", text: "current approved tool authority is unavailable" };
|
|
195
|
+
return dependencies.executeTool(name, args, {
|
|
196
|
+
...current.toolContext,
|
|
197
|
+
toolSelection: Object.freeze({ ordinary: Object.freeze([definition]), engine: Object.freeze([]) }),
|
|
198
|
+
});
|
|
199
|
+
},
|
|
129
200
|
});
|
|
130
201
|
input.tokenStore.remove(record.approvalId);
|
|
131
202
|
const existing = dependencies.loadSession(record.sessionPath);
|
|
@@ -204,10 +275,13 @@ async function settleFrontendApproval(input, dependencies = DEFAULT_SETTLEMENT_D
|
|
|
204
275
|
},
|
|
205
276
|
runAgent: dependencies.runAgent,
|
|
206
277
|
runAgentOptions: {
|
|
207
|
-
mcpManager,
|
|
208
278
|
toolContext: liveToolContext,
|
|
209
279
|
approvalCoordinator: nestedCoordinator,
|
|
210
280
|
},
|
|
281
|
+
revalidate: async () => {
|
|
282
|
+
const current = await currentOptions();
|
|
283
|
+
return current ? { ...current, approvalCoordinator: nestedCoordinator } : null;
|
|
284
|
+
},
|
|
211
285
|
persist,
|
|
212
286
|
deliver: async (text) => {
|
|
213
287
|
response = text;
|
|
@@ -242,8 +316,8 @@ async function settleFrontendApproval(input, dependencies = DEFAULT_SETTLEMENT_D
|
|
|
242
316
|
});
|
|
243
317
|
}
|
|
244
318
|
finally {
|
|
245
|
-
if (
|
|
246
|
-
await dependencies.releaseRuntimeMcpServers();
|
|
319
|
+
if (runtimeServers && !toolsDisabled) {
|
|
320
|
+
await dependencies.releaseRuntimeMcpServers(owner);
|
|
247
321
|
}
|
|
248
322
|
}
|
|
249
323
|
});
|
package/dist/heart/identity.js
CHANGED
|
@@ -386,15 +386,27 @@ function validateFacingConfig(parsed, facingName, configFile) {
|
|
|
386
386
|
}
|
|
387
387
|
/**
|
|
388
388
|
* Load and parse `<agentRoot>/agent.json`.
|
|
389
|
-
* Reads the file fresh
|
|
389
|
+
* Reads the explicit owner's file fresh, or preserves the legacy ambient override when no owner is supplied.
|
|
390
390
|
* If the config is v1, auto-migrates to v2 via migrateAgentConfigV1ToV2 and re-reads.
|
|
391
391
|
* Throws descriptive error if file is missing or contains invalid JSON.
|
|
392
392
|
*/
|
|
393
|
-
function loadAgentConfig() {
|
|
394
|
-
if (
|
|
393
|
+
function loadAgentConfig(owner) {
|
|
394
|
+
if (owner !== undefined && (typeof owner !== "object" || owner === null
|
|
395
|
+
|| typeof owner.agentName !== "string" || owner.agentName.length === 0
|
|
396
|
+
|| typeof owner.agentRoot !== "string" || !path.isAbsolute(owner.agentRoot))) {
|
|
397
|
+
(0, runtime_1.emitNervesEvent)({
|
|
398
|
+
level: "error",
|
|
399
|
+
event: "config_identity.error",
|
|
400
|
+
component: "config/identity",
|
|
401
|
+
message: "invalid agent configuration owner",
|
|
402
|
+
meta: { reason: "explicit owner requires an agent name and absolute root" },
|
|
403
|
+
});
|
|
404
|
+
throw new Error("invalid agent configuration owner");
|
|
405
|
+
}
|
|
406
|
+
if (owner === undefined && _agentConfigOverride) {
|
|
395
407
|
return _agentConfigOverride;
|
|
396
408
|
}
|
|
397
|
-
const agentRoot = getAgentRoot();
|
|
409
|
+
const agentRoot = owner === undefined ? getAgentRoot() : owner.agentRoot;
|
|
398
410
|
const configFile = path.join(agentRoot, "agent.json");
|
|
399
411
|
let parsed = readAndParseAgentJson(configFile);
|
|
400
412
|
// Inline migration: v1 -> v2
|
|
@@ -473,7 +485,7 @@ function loadAgentConfig() {
|
|
|
473
485
|
enabled,
|
|
474
486
|
humanFacing,
|
|
475
487
|
agentFacing,
|
|
476
|
-
habitPaidTurnsPerDay: (0, autonomy_budget_1.normalizeHabitPaidTurnsPerDay)(getAgentName(), parsed.habitPaidTurnsPerDay, configFile),
|
|
488
|
+
habitPaidTurnsPerDay: (0, autonomy_budget_1.normalizeHabitPaidTurnsPerDay)(owner === undefined ? getAgentName() : owner.agentName, parsed.habitPaidTurnsPerDay, configFile),
|
|
477
489
|
senses: normalizeSenses(parsed.senses, configFile),
|
|
478
490
|
phrases: parsed.phrases,
|
|
479
491
|
};
|
|
@@ -127,11 +127,18 @@ function materializeApprovalTerminal(input) {
|
|
|
127
127
|
}
|
|
128
128
|
const advanced = input.currentSessionRevision !== input.checkpoint.suspendedSessionRevision;
|
|
129
129
|
if (advanced) {
|
|
130
|
+
const notice = input.record.state === "succeeded"
|
|
131
|
+
? "the approved action completed, but the session changed; no model continuation was run"
|
|
132
|
+
: input.record.state === "failed"
|
|
133
|
+
? "the approved action failed, but the session changed; no model continuation was run"
|
|
134
|
+
: input.record.state === "attempted_indeterminate"
|
|
135
|
+
? `${APPROVAL_TERMINAL_TEXT.attempted_indeterminate}; the session changed`
|
|
136
|
+
: "the session changed before this approval could be applied; the protected action was not executed";
|
|
130
137
|
return {
|
|
131
138
|
messages,
|
|
132
139
|
materialized: false,
|
|
133
140
|
resumeProvider: false,
|
|
134
|
-
directNotice:
|
|
141
|
+
directNotice: notice,
|
|
135
142
|
revision: messagesRevision(messages),
|
|
136
143
|
};
|
|
137
144
|
}
|
|
@@ -5,6 +5,7 @@ exports.coordinateApprovalDecision = coordinateApprovalDecision;
|
|
|
5
5
|
exports.digestApprovalSuspensionCheckpointPayload = digestApprovalSuspensionCheckpointPayload;
|
|
6
6
|
exports.commitApprovalProposal = commitApprovalProposal;
|
|
7
7
|
exports.recoverApprovalProposals = recoverApprovalProposals;
|
|
8
|
+
exports.digestApprovalToolDefinition = digestApprovalToolDefinition;
|
|
8
9
|
exports.executeApprovalDecision = executeApprovalDecision;
|
|
9
10
|
exports.recoverClaimedApproval = recoverClaimedApproval;
|
|
10
11
|
exports.recoverAttemptedApproval = recoverAttemptedApproval;
|
|
@@ -238,47 +239,65 @@ function terminalizeClaimed(store, claimed, state, reason) {
|
|
|
238
239
|
reason,
|
|
239
240
|
});
|
|
240
241
|
}
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
242
|
+
function digestApprovalToolDefinition(definition, schemaDigest, policyId) {
|
|
243
|
+
const binding = definition.mcpBinding;
|
|
244
|
+
return (0, tool_arguments_1.digestJson)({
|
|
245
|
+
name: definition.tool.function.name, schemaDigest, policyId, advertisedTool: JSON.parse(JSON.stringify(definition.tool)),
|
|
246
|
+
...(binding ? { mcp: {
|
|
247
|
+
agentName: binding.agentName, agentRoot: binding.agentRoot,
|
|
248
|
+
server: binding.server, rawName: binding.rawName, surfacedName: binding.surfacedName,
|
|
249
|
+
source: binding.source, pluginId: binding.pluginId ?? null,
|
|
250
|
+
configDigest: binding.configDigest,
|
|
251
|
+
} } : {}),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async function resolveApprovalTool(options, decided) {
|
|
255
|
+
const definition = await options.resolveTool(decided.toolName);
|
|
254
256
|
if (!definition || definition.tool.function.name !== decided.toolName) {
|
|
255
|
-
return
|
|
257
|
+
return { ok: false, reason: "tool identity drift" };
|
|
256
258
|
}
|
|
257
259
|
const schema = definition.tool.function.parameters;
|
|
258
260
|
if (!schema || typeof schema !== "object") {
|
|
259
|
-
return
|
|
261
|
+
return { ok: false, reason: "advertised schema missing" };
|
|
260
262
|
}
|
|
261
263
|
const validated = (0, tool_arguments_1.validateAdvertisedToolArguments)(JSON.stringify(decided.arguments), schema);
|
|
262
264
|
if (!validated.ok || validated.value.argumentDigest !== decided.argumentDigest
|
|
263
265
|
|| validated.value.schemaDigest !== decided.schemaDigest) {
|
|
264
|
-
return
|
|
266
|
+
return { ok: false, reason: "tool arguments or schema drift" };
|
|
265
267
|
}
|
|
266
268
|
const policy = await options.resolveApprovalPolicy?.(decided.toolName, validated.value.arguments)
|
|
267
269
|
?? definition.approvalPolicy?.(validated.value.arguments)
|
|
268
270
|
?? { kind: "not_required" };
|
|
269
271
|
if (policy.kind !== "required") {
|
|
270
|
-
return
|
|
272
|
+
return { ok: false, reason: "approval policy no longer requires approval" };
|
|
271
273
|
}
|
|
272
|
-
const toolDigest = (
|
|
274
|
+
const toolDigest = digestApprovalToolDefinition(definition, validated.value.schemaDigest, policy.policyId);
|
|
273
275
|
const policyDigest = (0, tool_arguments_1.digestJson)({ policyId: policy.policyId, actionClass: policy.actionClass, classification: "required" });
|
|
274
276
|
if (policy.policyId !== decided.policyId || toolDigest !== decided.toolDigest || policyDigest !== decided.policyDigest) {
|
|
275
|
-
return
|
|
277
|
+
return { ok: false, reason: "approval policy or tool digest drift" };
|
|
276
278
|
}
|
|
279
|
+
return { ok: true, definition, arguments: validated.value.arguments };
|
|
280
|
+
}
|
|
281
|
+
async function executeApprovalDecision(options) {
|
|
282
|
+
const decided = options.approvalStore.decide({ ...options.decision, ownerId: options.ownerId });
|
|
283
|
+
if (decided.state !== "claimed")
|
|
284
|
+
return decided;
|
|
285
|
+
await options.hooks?.afterClaim?.();
|
|
286
|
+
if (options.currentSessionRevision !== decided.suspendedSessionRevision) {
|
|
287
|
+
return terminalizeClaimed(options.approvalStore, decided, "session_head_changed", "suspended session revision changed");
|
|
288
|
+
}
|
|
289
|
+
const checkpoint = options.checkpointStore.read(decided.approvalId);
|
|
290
|
+
if (!checkpoint || !checkpointMatches(decided, checkpoint) || !frozenCallMatches(decided, checkpoint)) {
|
|
291
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", "checkpoint evidence drift");
|
|
292
|
+
}
|
|
293
|
+
const current = await resolveApprovalTool(options, decided);
|
|
294
|
+
if (!current.ok)
|
|
295
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", current.reason);
|
|
277
296
|
const context = {
|
|
278
297
|
record: structuredClone(decided),
|
|
279
298
|
checkpoint: structuredClone(checkpoint),
|
|
280
|
-
definition,
|
|
281
|
-
arguments: structuredClone(
|
|
299
|
+
definition: current.definition,
|
|
300
|
+
arguments: structuredClone(current.arguments),
|
|
282
301
|
};
|
|
283
302
|
const guard = await options.liveGuard(context);
|
|
284
303
|
if (!guard.ok)
|
|
@@ -286,6 +305,14 @@ async function executeApprovalDecision(options) {
|
|
|
286
305
|
const risk = await options.liveRisk(context);
|
|
287
306
|
if (!risk.ok)
|
|
288
307
|
return terminalizeClaimed(options.approvalStore, decided, "drifted", risk.reason);
|
|
308
|
+
const fresh = await resolveApprovalTool(options, decided);
|
|
309
|
+
if (!fresh.ok)
|
|
310
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", fresh.reason);
|
|
311
|
+
const preflight = options.preflight
|
|
312
|
+
? await options.preflight({ ...context, definition: fresh.definition, arguments: structuredClone(fresh.arguments) })
|
|
313
|
+
: { ok: false, reason: "canonical tool preflight is unavailable" };
|
|
314
|
+
if (!preflight.ok)
|
|
315
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", preflight.reason);
|
|
289
316
|
const attempted = options.approvalStore.markAttempted({
|
|
290
317
|
approvalId: decided.approvalId,
|
|
291
318
|
ownerId: decided.ownerId,
|
|
@@ -299,22 +326,28 @@ async function executeApprovalDecision(options) {
|
|
|
299
326
|
catch (error) {
|
|
300
327
|
if (!(error instanceof ApprovalExecutionFailedError))
|
|
301
328
|
throw error;
|
|
302
|
-
|
|
303
|
-
return options.approvalStore.complete({
|
|
304
|
-
approvalId: attempted.approvalId,
|
|
305
|
-
ownerId: attempted.ownerId,
|
|
306
|
-
epoch: attempted.epoch,
|
|
307
|
-
state: "failed",
|
|
308
|
-
result: `error: ${error.message}`,
|
|
309
|
-
});
|
|
329
|
+
result = { kind: "handler_failed", text: `error: ${error.message}`, error };
|
|
310
330
|
}
|
|
331
|
+
if (!result || typeof result.text !== "string"
|
|
332
|
+
|| !["rejected_before_handler", "handler_succeeded", "handler_failed", "handler_indeterminate"].includes(result.kind)) {
|
|
333
|
+
throw new ApprovalExecutionIndeterminateError("executor returned an invalid typed outcome; action was not retried");
|
|
334
|
+
}
|
|
335
|
+
const state = result.kind === "handler_succeeded" ? "succeeded"
|
|
336
|
+
: result.kind === "handler_indeterminate" ? "attempted_indeterminate" : "failed";
|
|
337
|
+
const emptyText = {
|
|
338
|
+
rejected_before_handler: "tool preflight rejected execution",
|
|
339
|
+
handler_succeeded: "action completed without output",
|
|
340
|
+
handler_failed: "action failed without details",
|
|
341
|
+
handler_indeterminate: "execution outcome is indeterminate; action was not retried",
|
|
342
|
+
};
|
|
343
|
+
const text = result.text.trim() ? result.text : emptyText[result.kind];
|
|
311
344
|
await options.hooks?.afterHandler?.();
|
|
312
345
|
return options.approvalStore.complete({
|
|
313
346
|
approvalId: attempted.approvalId,
|
|
314
347
|
ownerId: attempted.ownerId,
|
|
315
348
|
epoch: attempted.epoch,
|
|
316
|
-
state
|
|
317
|
-
result,
|
|
349
|
+
state,
|
|
350
|
+
result: result.kind === "rejected_before_handler" ? `no action was taken: ${text}` : text,
|
|
318
351
|
});
|
|
319
352
|
}
|
|
320
353
|
function recoverClaimedApproval(options) {
|
package/dist/mind/pending.js
CHANGED
|
@@ -49,8 +49,8 @@ const node_crypto_1 = require("node:crypto");
|
|
|
49
49
|
const identity_1 = require("../heart/identity");
|
|
50
50
|
const session_events_1 = require("../heart/session-events");
|
|
51
51
|
const runtime_1 = require("../nerves/runtime");
|
|
52
|
-
function getPendingDir(agentName, friendId, channel, key) {
|
|
53
|
-
return path.join(
|
|
52
|
+
function getPendingDir(agentName, friendId, channel, key, agentRoot = (0, identity_1.getAgentRoot)(agentName)) {
|
|
53
|
+
return path.join(agentRoot, "state", "pending", friendId, channel, key);
|
|
54
54
|
}
|
|
55
55
|
function getDeferredReturnDir(agentName, friendId) {
|
|
56
56
|
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "pending-returns", friendId);
|
|
@@ -58,8 +58,8 @@ function getDeferredReturnDir(agentName, friendId) {
|
|
|
58
58
|
/** Canonical private-runtime pending path segments. The path is historical durable state. */
|
|
59
59
|
exports.PRIVATE_RUNTIME_PENDING = { friendId: "self", channel: "inner", key: "dialog" };
|
|
60
60
|
/** Returns the pending dir for this agent's private runtime. */
|
|
61
|
-
function getPrivateRuntimePendingDir(agentName) {
|
|
62
|
-
return getPendingDir(agentName, exports.PRIVATE_RUNTIME_PENDING.friendId, exports.PRIVATE_RUNTIME_PENDING.channel, exports.PRIVATE_RUNTIME_PENDING.key);
|
|
61
|
+
function getPrivateRuntimePendingDir(agentName, agentRoot) {
|
|
62
|
+
return getPendingDir(agentName, exports.PRIVATE_RUNTIME_PENDING.friendId, exports.PRIVATE_RUNTIME_PENDING.channel, exports.PRIVATE_RUNTIME_PENDING.key, agentRoot);
|
|
63
63
|
}
|
|
64
64
|
function hasPendingMessages(pendingDir) {
|
|
65
65
|
if (!fs.existsSync(pendingDir))
|