@ouro.bot/cli 0.1.0-alpha.729 → 0.1.0-alpha.730
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 +7 -0
- package/dist/heart/approval-store.js +791 -0
- package/dist/heart/core.js +177 -0
- package/dist/heart/session-events.js +167 -0
- package/dist/heart/tool-approval.js +339 -0
- package/dist/mind/context.js +80 -67
- package/dist/mind/session-transaction.js +379 -0
- package/dist/repertoire/stripe-client.js +2 -2
- package/dist/repertoire/tool-arguments.js +104 -0
- package/dist/repertoire/tools-shell.js +13 -0
- package/dist/repertoire/tools.js +4 -0
- package/dist/senses/bluebubbles/index.js +504 -493
- package/dist/senses/cli.js +234 -214
- package/dist/senses/pipeline.js +23 -0
- package/dist/senses/private-runtime.js +330 -325
- package/dist/senses/shared-turn.js +176 -169
- package/dist/senses/teams.js +175 -163
- package/package.json +2 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApprovalExecutionFailedError = exports.ApprovalExecutionIndeterminateError = void 0;
|
|
4
|
+
exports.coordinateApprovalDecision = coordinateApprovalDecision;
|
|
5
|
+
exports.digestApprovalSuspensionCheckpointPayload = digestApprovalSuspensionCheckpointPayload;
|
|
6
|
+
exports.commitApprovalProposal = commitApprovalProposal;
|
|
7
|
+
exports.recoverApprovalProposals = recoverApprovalProposals;
|
|
8
|
+
exports.executeApprovalDecision = executeApprovalDecision;
|
|
9
|
+
exports.recoverClaimedApproval = recoverClaimedApproval;
|
|
10
|
+
exports.recoverAttemptedApproval = recoverAttemptedApproval;
|
|
11
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
const approval_store_1 = require("./approval-store");
|
|
13
|
+
const runtime_1 = require("../nerves/runtime");
|
|
14
|
+
const tool_arguments_1 = require("../repertoire/tool-arguments");
|
|
15
|
+
class ApprovalExecutionIndeterminateError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "ApprovalExecutionIndeterminateError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.ApprovalExecutionIndeterminateError = ApprovalExecutionIndeterminateError;
|
|
22
|
+
class ApprovalExecutionFailedError extends Error {
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "ApprovalExecutionFailedError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.ApprovalExecutionFailedError = ApprovalExecutionFailedError;
|
|
29
|
+
async function coordinateApprovalDecision(options) {
|
|
30
|
+
return options.withSessionLease(async (lease) => {
|
|
31
|
+
let currentSessionRevision = options.readCurrentRevision();
|
|
32
|
+
const record = await options.decideAndExecute({
|
|
33
|
+
currentSessionRevision,
|
|
34
|
+
lease,
|
|
35
|
+
hooks: {
|
|
36
|
+
afterClaim: async () => {
|
|
37
|
+
currentSessionRevision = options.readCurrentRevision();
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
await options.persist?.(record);
|
|
42
|
+
await options.resume(record);
|
|
43
|
+
(0, runtime_1.emitNervesEvent)({
|
|
44
|
+
component: "engine",
|
|
45
|
+
event: "engine.approval_decision_coordinated",
|
|
46
|
+
message: "approval decision coordinated under session turn lease",
|
|
47
|
+
meta: { approvalId: record.approvalId, state: record.state },
|
|
48
|
+
});
|
|
49
|
+
return { record };
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function checkpointMatches(record, checkpoint) {
|
|
53
|
+
return checkpoint.approvalId === record.approvalId
|
|
54
|
+
&& checkpoint.checkpointDigest === record.checkpointDigest
|
|
55
|
+
&& checkpoint.baseSessionRevision === record.baseSessionRevision
|
|
56
|
+
&& (record.state === "preparing" || checkpoint.suspendedSessionRevision === record.suspendedSessionRevision)
|
|
57
|
+
&& checkpoint.argumentDigest === record.argumentDigest
|
|
58
|
+
&& checkpoint.schemaDigest === record.schemaDigest
|
|
59
|
+
&& checkpoint.toolDigest === record.toolDigest
|
|
60
|
+
&& checkpoint.policyDigest === record.policyDigest
|
|
61
|
+
&& checkpoint.preCallDigest === digestTranscript(checkpoint.preCallMessages)
|
|
62
|
+
&& record.checkpointDigest === digestApprovalSuspensionCheckpointPayload(checkpoint)
|
|
63
|
+
&& JSON.stringify(checkpoint.frozenAssistantMessage) === JSON.stringify(record.frozenAssistantMessage);
|
|
64
|
+
}
|
|
65
|
+
function frozenCallMatches(record, checkpoint) {
|
|
66
|
+
const message = checkpoint.frozenAssistantMessage;
|
|
67
|
+
if (message.role !== "assistant")
|
|
68
|
+
return false;
|
|
69
|
+
if (!Array.isArray(message.tool_calls) || message.tool_calls.length !== 1)
|
|
70
|
+
return false;
|
|
71
|
+
const call = message.tool_calls[0];
|
|
72
|
+
if (!call || typeof call !== "object" || Array.isArray(call))
|
|
73
|
+
return false;
|
|
74
|
+
const callRecord = call;
|
|
75
|
+
if (callRecord.type !== "function")
|
|
76
|
+
return false;
|
|
77
|
+
const fn = callRecord.function;
|
|
78
|
+
if (!fn || typeof fn !== "object" || Array.isArray(fn))
|
|
79
|
+
return false;
|
|
80
|
+
const functionRecord = fn;
|
|
81
|
+
if (callRecord.id !== record.toolCallId || functionRecord.name !== record.toolName || typeof functionRecord.arguments !== "string")
|
|
82
|
+
return false;
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(functionRecord.arguments);
|
|
85
|
+
return JSON.stringify(parsed) === JSON.stringify(record.arguments);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function digestTranscript(messages) {
|
|
92
|
+
return (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(messages), "utf8").digest("hex");
|
|
93
|
+
}
|
|
94
|
+
function digestApprovalSuspensionCheckpointPayload(checkpoint) {
|
|
95
|
+
return (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({
|
|
96
|
+
baseSessionRevision: checkpoint.baseSessionRevision,
|
|
97
|
+
argumentDigest: checkpoint.argumentDigest,
|
|
98
|
+
schemaDigest: checkpoint.schemaDigest,
|
|
99
|
+
toolDigest: checkpoint.toolDigest,
|
|
100
|
+
policyDigest: checkpoint.policyDigest,
|
|
101
|
+
preCallDigest: checkpoint.preCallDigest,
|
|
102
|
+
preCallMessages: checkpoint.preCallMessages,
|
|
103
|
+
frozenAssistantMessage: checkpoint.frozenAssistantMessage,
|
|
104
|
+
}), "utf8").digest("hex");
|
|
105
|
+
}
|
|
106
|
+
function digestDecisionToken(token) {
|
|
107
|
+
return (0, node_crypto_1.createHash)("sha256").update(token, "utf8").digest("hex");
|
|
108
|
+
}
|
|
109
|
+
function commitApprovalProposal(options) {
|
|
110
|
+
(0, runtime_1.emitNervesEvent)({
|
|
111
|
+
component: "engine",
|
|
112
|
+
event: "engine.approval_proposal_commit_start",
|
|
113
|
+
message: "approval proposal commit started",
|
|
114
|
+
meta: { toolName: options.proposal.toolName, toolCallId: options.proposal.toolCallId },
|
|
115
|
+
});
|
|
116
|
+
const preCallDigest = digestTranscript(options.preCallMessages);
|
|
117
|
+
const checkpointDigest = digestApprovalSuspensionCheckpointPayload({
|
|
118
|
+
baseSessionRevision: options.proposal.baseSessionRevision,
|
|
119
|
+
argumentDigest: (0, approval_store_1.canonicalApprovalArguments)(options.proposal.arguments).digest,
|
|
120
|
+
schemaDigest: options.proposal.schemaDigest,
|
|
121
|
+
toolDigest: options.proposal.toolDigest,
|
|
122
|
+
policyDigest: options.proposal.policyDigest,
|
|
123
|
+
preCallDigest,
|
|
124
|
+
preCallMessages: options.preCallMessages,
|
|
125
|
+
frozenAssistantMessage: options.proposal.frozenAssistantMessage,
|
|
126
|
+
});
|
|
127
|
+
const prepared = options.approvalStore.prepare({ ...options.proposal, checkpointDigest });
|
|
128
|
+
options.hooks?.afterJournalPrepare?.();
|
|
129
|
+
options.tokenStore.put(prepared.record.approvalId, prepared.decisionToken);
|
|
130
|
+
options.hooks?.afterTokenPersist?.();
|
|
131
|
+
const attestation = options.checkpointStore.write({
|
|
132
|
+
approvalId: prepared.record.approvalId,
|
|
133
|
+
baseSessionRevision: prepared.record.baseSessionRevision,
|
|
134
|
+
argumentDigest: prepared.record.argumentDigest,
|
|
135
|
+
schemaDigest: prepared.record.schemaDigest,
|
|
136
|
+
toolDigest: prepared.record.toolDigest,
|
|
137
|
+
policyDigest: prepared.record.policyDigest,
|
|
138
|
+
preCallDigest,
|
|
139
|
+
preCallMessages: structuredClone(options.preCallMessages),
|
|
140
|
+
frozenAssistantMessage: structuredClone(prepared.record.frozenAssistantMessage),
|
|
141
|
+
});
|
|
142
|
+
options.hooks?.afterCheckpointWrite?.();
|
|
143
|
+
if (attestation.checkpointDigest !== prepared.record.checkpointDigest) {
|
|
144
|
+
options.tokenStore.remove(prepared.record.approvalId);
|
|
145
|
+
options.checkpointStore.remove(prepared.record.approvalId);
|
|
146
|
+
options.approvalStore.recoverPreparing({
|
|
147
|
+
approvalId: prepared.record.approvalId,
|
|
148
|
+
state: "drifted",
|
|
149
|
+
reason: "checkpoint attestation digest mismatch",
|
|
150
|
+
});
|
|
151
|
+
(0, runtime_1.emitNervesEvent)({
|
|
152
|
+
level: "error",
|
|
153
|
+
component: "engine",
|
|
154
|
+
event: "engine.approval_checkpoint_attestation_mismatch",
|
|
155
|
+
message: "approval checkpoint durability attestation did not match proposal",
|
|
156
|
+
meta: { approvalId: prepared.record.approvalId },
|
|
157
|
+
});
|
|
158
|
+
throw new approval_store_1.ApprovalStoreError("checkpoint_attestation_mismatch");
|
|
159
|
+
}
|
|
160
|
+
const record = options.approvalStore.activate({
|
|
161
|
+
approvalId: prepared.record.approvalId,
|
|
162
|
+
checkpointDigest: attestation.checkpointDigest,
|
|
163
|
+
suspendedSessionRevision: attestation.suspendedSessionRevision,
|
|
164
|
+
});
|
|
165
|
+
(0, runtime_1.emitNervesEvent)({
|
|
166
|
+
component: "engine",
|
|
167
|
+
event: "engine.approval_proposal_commit_end",
|
|
168
|
+
message: "approval proposal checkpoint activated",
|
|
169
|
+
meta: { approvalId: record.approvalId },
|
|
170
|
+
});
|
|
171
|
+
return { record, decisionToken: prepared.decisionToken };
|
|
172
|
+
}
|
|
173
|
+
function recoverApprovalProposals(options) {
|
|
174
|
+
const recovered = [];
|
|
175
|
+
const preparing = options.approvalStore.listPreparing();
|
|
176
|
+
const preparingIds = new Set(preparing.map((record) => record.approvalId));
|
|
177
|
+
for (const checkpoint of options.checkpointStore.list()) {
|
|
178
|
+
if (preparingIds.has(checkpoint.approvalId) || options.approvalStore.read(checkpoint.approvalId))
|
|
179
|
+
continue;
|
|
180
|
+
options.checkpointStore.remove(checkpoint.approvalId);
|
|
181
|
+
recovered.push({ approvalId: checkpoint.approvalId, state: "orphan_checkpoint_removed" });
|
|
182
|
+
(0, runtime_1.emitNervesEvent)({
|
|
183
|
+
level: "warn",
|
|
184
|
+
component: "engine",
|
|
185
|
+
event: "engine.approval_orphan_checkpoint_removed",
|
|
186
|
+
message: "removed approval checkpoint with no journal authority",
|
|
187
|
+
meta: { approvalId: checkpoint.approvalId },
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
for (const record of preparing) {
|
|
191
|
+
const checkpoint = options.checkpointStore.read(record.approvalId);
|
|
192
|
+
const token = options.tokenStore.get(record.approvalId);
|
|
193
|
+
const hasToken = token !== null;
|
|
194
|
+
const tokenMatches = token !== null && digestDecisionToken(token) === record.decisionTokenDigest;
|
|
195
|
+
if (checkpoint && tokenMatches && checkpointMatches(record, checkpoint)) {
|
|
196
|
+
const activated = options.approvalStore.activate({
|
|
197
|
+
approvalId: record.approvalId,
|
|
198
|
+
checkpointDigest: checkpoint.checkpointDigest,
|
|
199
|
+
suspendedSessionRevision: checkpoint.suspendedSessionRevision,
|
|
200
|
+
});
|
|
201
|
+
recovered.push({ approvalId: record.approvalId, state: activated.state });
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
options.tokenStore.remove(record.approvalId);
|
|
205
|
+
if (checkpoint)
|
|
206
|
+
options.checkpointStore.remove(record.approvalId);
|
|
207
|
+
const checkpointMismatch = checkpoint !== null && !checkpointMatches(record, checkpoint);
|
|
208
|
+
const tokenMismatch = hasToken && !tokenMatches;
|
|
209
|
+
const mismatch = checkpointMismatch || tokenMismatch;
|
|
210
|
+
const terminal = options.approvalStore.recoverPreparing({
|
|
211
|
+
approvalId: record.approvalId,
|
|
212
|
+
state: mismatch ? "drifted" : "abandoned_before_attempt",
|
|
213
|
+
reason: tokenMismatch ? "decision token evidence mismatch" : mismatch ? "checkpoint evidence mismatch" : "incomplete proposal commit",
|
|
214
|
+
});
|
|
215
|
+
recovered.push({ approvalId: record.approvalId, state: terminal.state });
|
|
216
|
+
}
|
|
217
|
+
(0, runtime_1.emitNervesEvent)({
|
|
218
|
+
component: "engine",
|
|
219
|
+
event: "engine.approval_proposal_recovery",
|
|
220
|
+
message: "approval proposal recovery scan completed",
|
|
221
|
+
meta: { recoveredCount: recovered.length },
|
|
222
|
+
});
|
|
223
|
+
return recovered;
|
|
224
|
+
}
|
|
225
|
+
function terminalizeClaimed(store, claimed, state, reason) {
|
|
226
|
+
(0, runtime_1.emitNervesEvent)({
|
|
227
|
+
level: "warn",
|
|
228
|
+
component: "engine",
|
|
229
|
+
event: "engine.approval_pre_attempt_terminalized",
|
|
230
|
+
message: "approval stopped during live revalidation",
|
|
231
|
+
meta: { approvalId: claimed.approvalId, state, reason },
|
|
232
|
+
});
|
|
233
|
+
return store.terminalizeBeforeAttempt({
|
|
234
|
+
approvalId: claimed.approvalId,
|
|
235
|
+
ownerId: claimed.ownerId,
|
|
236
|
+
epoch: claimed.epoch,
|
|
237
|
+
state,
|
|
238
|
+
reason,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async function executeApprovalDecision(options) {
|
|
242
|
+
const decided = options.approvalStore.decide({ ...options.decision, ownerId: options.ownerId });
|
|
243
|
+
if (decided.state !== "claimed")
|
|
244
|
+
return decided;
|
|
245
|
+
await options.hooks?.afterClaim?.();
|
|
246
|
+
if (options.currentSessionRevision !== decided.suspendedSessionRevision) {
|
|
247
|
+
return terminalizeClaimed(options.approvalStore, decided, "session_head_changed", "suspended session revision changed");
|
|
248
|
+
}
|
|
249
|
+
const checkpoint = options.checkpointStore.read(decided.approvalId);
|
|
250
|
+
if (!checkpoint || !checkpointMatches(decided, checkpoint) || !frozenCallMatches(decided, checkpoint)) {
|
|
251
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", "checkpoint evidence drift");
|
|
252
|
+
}
|
|
253
|
+
const definition = options.resolveTool(decided.toolName);
|
|
254
|
+
if (!definition || definition.tool.function.name !== decided.toolName) {
|
|
255
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", "tool identity drift");
|
|
256
|
+
}
|
|
257
|
+
const schema = definition.tool.function.parameters;
|
|
258
|
+
if (!schema || typeof schema !== "object") {
|
|
259
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", "advertised schema missing");
|
|
260
|
+
}
|
|
261
|
+
const validated = (0, tool_arguments_1.validateAdvertisedToolArguments)(JSON.stringify(decided.arguments), schema);
|
|
262
|
+
if (!validated.ok || validated.value.argumentDigest !== decided.argumentDigest
|
|
263
|
+
|| validated.value.schemaDigest !== decided.schemaDigest) {
|
|
264
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", "tool arguments or schema drift");
|
|
265
|
+
}
|
|
266
|
+
const policy = definition.approvalPolicy?.(validated.value.arguments) ?? { kind: "not_required" };
|
|
267
|
+
if (policy.kind !== "required") {
|
|
268
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", "approval policy no longer requires approval");
|
|
269
|
+
}
|
|
270
|
+
const toolDigest = (0, tool_arguments_1.digestJson)({ name: decided.toolName, schemaDigest: validated.value.schemaDigest, policyId: policy.policyId });
|
|
271
|
+
const policyDigest = (0, tool_arguments_1.digestJson)({ policyId: policy.policyId, actionClass: policy.actionClass, classification: "required" });
|
|
272
|
+
if (policy.policyId !== decided.policyId || toolDigest !== decided.toolDigest || policyDigest !== decided.policyDigest) {
|
|
273
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", "approval policy or tool digest drift");
|
|
274
|
+
}
|
|
275
|
+
const context = {
|
|
276
|
+
record: structuredClone(decided),
|
|
277
|
+
checkpoint: structuredClone(checkpoint),
|
|
278
|
+
definition,
|
|
279
|
+
arguments: structuredClone(validated.value.arguments),
|
|
280
|
+
};
|
|
281
|
+
const guard = await options.liveGuard(context);
|
|
282
|
+
if (!guard.ok)
|
|
283
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", guard.reason);
|
|
284
|
+
const risk = await options.liveRisk(context);
|
|
285
|
+
if (!risk.ok)
|
|
286
|
+
return terminalizeClaimed(options.approvalStore, decided, "drifted", risk.reason);
|
|
287
|
+
const attempted = options.approvalStore.markAttempted({
|
|
288
|
+
approvalId: decided.approvalId,
|
|
289
|
+
ownerId: decided.ownerId,
|
|
290
|
+
epoch: decided.epoch,
|
|
291
|
+
});
|
|
292
|
+
await options.hooks?.afterAttempt?.();
|
|
293
|
+
let result;
|
|
294
|
+
try {
|
|
295
|
+
result = await options.execute(attempted.toolName, structuredClone(attempted.arguments));
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
if (!(error instanceof ApprovalExecutionFailedError))
|
|
299
|
+
throw error;
|
|
300
|
+
return options.approvalStore.complete({
|
|
301
|
+
approvalId: attempted.approvalId,
|
|
302
|
+
ownerId: attempted.ownerId,
|
|
303
|
+
epoch: attempted.epoch,
|
|
304
|
+
state: "failed",
|
|
305
|
+
result: `error: ${error.message}`,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
await options.hooks?.afterHandler?.();
|
|
309
|
+
return options.approvalStore.complete({
|
|
310
|
+
approvalId: attempted.approvalId,
|
|
311
|
+
ownerId: attempted.ownerId,
|
|
312
|
+
epoch: attempted.epoch,
|
|
313
|
+
state: "succeeded",
|
|
314
|
+
result,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function recoverClaimedApproval(options) {
|
|
318
|
+
const record = options.approvalStore.read(options.approvalId);
|
|
319
|
+
if (!record || record.state !== "claimed" || !record.ownerId)
|
|
320
|
+
throw new approval_store_1.ApprovalStoreError("claimed_recovery_not_eligible");
|
|
321
|
+
return options.approvalStore.abandonBeforeAttempt({
|
|
322
|
+
approvalId: record.approvalId,
|
|
323
|
+
ownerId: record.ownerId,
|
|
324
|
+
epoch: record.epoch,
|
|
325
|
+
reason: options.reason,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function recoverAttemptedApproval(options) {
|
|
329
|
+
const record = options.approvalStore.read(options.approvalId);
|
|
330
|
+
if (!record || record.state !== "attempted" || !record.ownerId)
|
|
331
|
+
throw new approval_store_1.ApprovalStoreError("attempted_recovery_not_eligible");
|
|
332
|
+
return options.approvalStore.complete({
|
|
333
|
+
approvalId: record.approvalId,
|
|
334
|
+
ownerId: record.ownerId,
|
|
335
|
+
epoch: record.epoch,
|
|
336
|
+
state: "attempted_indeterminate",
|
|
337
|
+
result: "execution outcome is indeterminate after restart; action was not retried",
|
|
338
|
+
});
|
|
339
|
+
}
|
package/dist/mind/context.js
CHANGED
|
@@ -47,7 +47,7 @@ const config_1 = require("../heart/config");
|
|
|
47
47
|
const session_events_1 = require("../heart/session-events");
|
|
48
48
|
const runtime_1 = require("../nerves/runtime");
|
|
49
49
|
const fs = __importStar(require("fs"));
|
|
50
|
-
const
|
|
50
|
+
const session_transaction_1 = require("./session-transaction");
|
|
51
51
|
const token_estimate_1 = require("./token-estimate");
|
|
52
52
|
var session_events_2 = require("../heart/session-events");
|
|
53
53
|
Object.defineProperty(exports, "detectDuplicateToolCallIds", { enumerable: true, get: function () { return session_events_2.detectDuplicateToolCallIds; } });
|
|
@@ -274,48 +274,65 @@ function denormalizeContinuityState(state) {
|
|
|
274
274
|
...(typeof state.lastFriendActivityAt === "string" ? { lastFriendActivityAt: state.lastFriendActivityAt } : {}),
|
|
275
275
|
};
|
|
276
276
|
}
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
function withMutationLease(filePath, lease, work) {
|
|
278
|
+
if (lease) {
|
|
279
|
+
(0, session_transaction_1.assertSessionTurnLease)(filePath, lease);
|
|
280
|
+
return work(lease);
|
|
281
|
+
}
|
|
282
|
+
const contextual = (0, session_transaction_1.currentSessionTurnLease)(filePath);
|
|
283
|
+
if (contextual)
|
|
284
|
+
return work(contextual);
|
|
285
|
+
return (0, session_transaction_1.withImmediateSessionTurnLease)(filePath, work);
|
|
280
286
|
}
|
|
281
|
-
function
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
287
|
+
function writeSessionEnvelopeUnderLease(filePath, envelope, lease, expectedRevision) {
|
|
288
|
+
const current = (0, session_transaction_1.readSessionTransaction)(filePath, lease);
|
|
289
|
+
(0, session_transaction_1.writeSessionTransaction)(filePath, envelope, {
|
|
290
|
+
lease,
|
|
291
|
+
expectedRevision: expectedRevision ?? current.revision,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
function saveSession(filePath, messages, lastUsage, state, lease) {
|
|
295
|
+
withMutationLease(filePath, lease, (owned) => {
|
|
296
|
+
const existing = (0, session_events_1.loadSessionEnvelopeFile)(filePath);
|
|
297
|
+
const previousMessages = existing ? (0, session_events_1.projectProviderMessages)(existing) : [];
|
|
298
|
+
const currentIngressTimes = messages.map(session_events_1.getIngressTime);
|
|
299
|
+
const sanitized = (0, session_events_1.sanitizeProviderMessages)(messages);
|
|
300
|
+
const { envelope } = (0, session_events_1.buildCanonicalSessionEnvelope)({
|
|
301
|
+
existing,
|
|
302
|
+
previousMessages,
|
|
303
|
+
currentMessages: sanitized,
|
|
304
|
+
trimmedMessages: sanitized,
|
|
305
|
+
currentIngressTimes,
|
|
306
|
+
recordedAt: new Date().toISOString(),
|
|
307
|
+
lastUsage: lastUsage ?? null,
|
|
308
|
+
state,
|
|
309
|
+
projectionBasis: {
|
|
310
|
+
maxTokens: null,
|
|
311
|
+
contextMargin: null,
|
|
312
|
+
inputTokens: lastUsage?.input_tokens ?? null,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
writeSessionEnvelopeUnderLease(filePath, envelope, owned);
|
|
300
316
|
});
|
|
301
|
-
writeSessionEnvelope(filePath, envelope);
|
|
302
317
|
}
|
|
303
|
-
function appendSyntheticAssistantMessage(filePath, content) {
|
|
318
|
+
function appendSyntheticAssistantMessage(filePath, content, lease) {
|
|
304
319
|
try {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
320
|
+
return withMutationLease(filePath, lease, (owned) => {
|
|
321
|
+
if (!fs.existsSync(filePath))
|
|
322
|
+
return false;
|
|
323
|
+
const envelope = (0, session_events_1.loadSessionEnvelopeFile)(filePath);
|
|
324
|
+
if (!envelope)
|
|
325
|
+
return false;
|
|
326
|
+
const updated = (0, session_events_1.appendSyntheticAssistantEvent)(envelope, content, new Date().toISOString());
|
|
327
|
+
writeSessionEnvelopeUnderLease(filePath, updated, owned);
|
|
328
|
+
(0, runtime_1.emitNervesEvent)({
|
|
329
|
+
component: "mind",
|
|
330
|
+
event: "mind.session_synthetic_message_appended",
|
|
331
|
+
message: "appended synthetic assistant message to session",
|
|
332
|
+
meta: { path: filePath, contentLength: content.length },
|
|
333
|
+
});
|
|
334
|
+
return true;
|
|
317
335
|
});
|
|
318
|
-
return true;
|
|
319
336
|
}
|
|
320
337
|
catch {
|
|
321
338
|
return false;
|
|
@@ -379,26 +396,28 @@ function postTurnTrim(messages, usage, hooks) {
|
|
|
379
396
|
/**
|
|
380
397
|
* Synchronous persist: load existing envelope, build canonical envelope, write to disk.
|
|
381
398
|
*/
|
|
382
|
-
function postTurnPersist(sessPath, prepared, usage, state) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
+
function postTurnPersist(sessPath, prepared, usage, state, lease, expectedRevision) {
|
|
400
|
+
return withMutationLease(sessPath, lease, (owned) => {
|
|
401
|
+
const existing = (0, session_events_1.loadSessionEnvelopeFile)(sessPath);
|
|
402
|
+
const previousMessages = existing ? (0, session_events_1.projectProviderMessages)(existing) : [];
|
|
403
|
+
const { envelope } = (0, session_events_1.buildCanonicalSessionEnvelope)({
|
|
404
|
+
existing,
|
|
405
|
+
previousMessages,
|
|
406
|
+
currentMessages: prepared.currentMessages,
|
|
407
|
+
trimmedMessages: prepared.trimmedMessages,
|
|
408
|
+
currentIngressTimes: prepared.currentIngressTimes,
|
|
409
|
+
recordedAt: new Date().toISOString(),
|
|
410
|
+
lastUsage: usage ?? null,
|
|
411
|
+
state,
|
|
412
|
+
projectionBasis: {
|
|
413
|
+
maxTokens: prepared.maxTokens,
|
|
414
|
+
contextMargin: prepared.contextMargin,
|
|
415
|
+
inputTokens: usage?.input_tokens ?? null,
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
writeSessionEnvelopeUnderLease(sessPath, envelope, owned, expectedRevision);
|
|
419
|
+
return envelope.events;
|
|
399
420
|
});
|
|
400
|
-
writeSessionEnvelope(sessPath, envelope);
|
|
401
|
-
return envelope.events;
|
|
402
421
|
}
|
|
403
422
|
/**
|
|
404
423
|
* Per-sessPath serialization queue. Without this, two concurrent
|
|
@@ -436,11 +455,11 @@ function enqueueSessionPersist(sessPath, fn) {
|
|
|
436
455
|
* sessPath, so concurrent turns cannot race and produce duplicate event ids
|
|
437
456
|
* in the saved session.
|
|
438
457
|
*/
|
|
439
|
-
function deferPostTurnPersist(sessPath, prepared, usage, state) {
|
|
458
|
+
function deferPostTurnPersist(sessPath, prepared, usage, state, lease, expectedRevision) {
|
|
440
459
|
return enqueueSessionPersist(sessPath, () => new Promise((resolve) => {
|
|
441
460
|
setImmediate(() => {
|
|
442
461
|
try {
|
|
443
|
-
const events = postTurnPersist(sessPath, prepared, usage, state);
|
|
462
|
+
const events = postTurnPersist(sessPath, prepared, usage, state, lease, expectedRevision);
|
|
444
463
|
resolve(events);
|
|
445
464
|
}
|
|
446
465
|
catch (err) {
|
|
@@ -456,12 +475,6 @@ function deferPostTurnPersist(sessPath, prepared, usage, state) {
|
|
|
456
475
|
});
|
|
457
476
|
}));
|
|
458
477
|
}
|
|
459
|
-
function deleteSession(filePath) {
|
|
460
|
-
|
|
461
|
-
fs.unlinkSync(filePath);
|
|
462
|
-
}
|
|
463
|
-
catch (e) {
|
|
464
|
-
if (e instanceof Error && e.code !== "ENOENT")
|
|
465
|
-
throw e;
|
|
466
|
-
}
|
|
478
|
+
function deleteSession(filePath, lease) {
|
|
479
|
+
withMutationLease(filePath, lease, (owned) => (0, session_transaction_1.deleteSessionTransaction)(filePath, owned));
|
|
467
480
|
}
|