@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
|
@@ -58,6 +58,7 @@ const trust_gate_1 = require("./trust-gate");
|
|
|
58
58
|
const pipeline_1 = require("./pipeline");
|
|
59
59
|
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
60
60
|
const runtime_1 = require("../nerves/runtime");
|
|
61
|
+
const session_transaction_1 = require("../mind/session-transaction");
|
|
61
62
|
const RESPONSE_CAP = 50_000;
|
|
62
63
|
const OUTWARD_DELIVERY_TOOL_ACKS = new Map([
|
|
63
64
|
["settle", "(delivered)"],
|
|
@@ -234,184 +235,190 @@ async function runSenseTurn(options) {
|
|
|
234
235
|
const sessionDir = path.join(agentRoot, "state", "sessions", friendId, channel);
|
|
235
236
|
fs.mkdirSync(sessionDir, { recursive: true });
|
|
236
237
|
const sessPath = path.join(sessionDir, `${(0, config_1.sanitizeKey)(sessionKey)}.json`);
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
onClearText: () => { pendingResponseText = ""; },
|
|
303
|
-
flushNow: () => deliverPending("speak", { throwOnError: true }),
|
|
304
|
-
};
|
|
305
|
-
/* v8 ignore stop */
|
|
306
|
-
// Run the pipeline
|
|
307
|
-
const userMsg = { role: "user", content: userMessage };
|
|
308
|
-
(0, session_events_1.stampIngressTime)(userMsg);
|
|
309
|
-
const turnResult = await (0, pipeline_1.handleInboundTurn)({
|
|
310
|
-
channel,
|
|
311
|
-
latencyMode: options.latencyMode,
|
|
312
|
-
sessionKey,
|
|
313
|
-
capabilities,
|
|
314
|
-
messages: [userMsg],
|
|
315
|
-
callbacks,
|
|
316
|
-
/* v8 ignore start — delegation wrappers; pipeline integration tested separately */
|
|
317
|
-
friendResolver: { resolve: () => resolver.resolve() },
|
|
318
|
-
sessionLoader: {
|
|
319
|
-
loadOrCreate: () => Promise.resolve({
|
|
320
|
-
messages: sessionMessages,
|
|
321
|
-
sessionPath: sessPath,
|
|
322
|
-
state: sessionState,
|
|
323
|
-
events: existing?.events,
|
|
324
|
-
structuredOutputs: existing?.structuredOutputs,
|
|
325
|
-
}),
|
|
326
|
-
},
|
|
327
|
-
/* v8 ignore stop */
|
|
328
|
-
pendingDir,
|
|
329
|
-
friendStore,
|
|
330
|
-
provider: resolverParams.provider,
|
|
331
|
-
externalId: resolverParams.externalId,
|
|
332
|
-
tenantId: resolverParams.tenantId,
|
|
333
|
-
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
334
|
-
drainPending: pending_1.drainPending,
|
|
335
|
-
runAgentOptions: {
|
|
336
|
-
mcpManager,
|
|
337
|
-
...(options.latencyMode === "live" ? { skipKeptNotes: true } : {}),
|
|
338
|
-
toolContext: {
|
|
339
|
-
signin: async () => undefined,
|
|
340
|
-
...(options.toolContext ? options.toolContext : {}),
|
|
341
|
-
currentUserMessage: userMessage,
|
|
238
|
+
const runWithLease = options._withSessionTurnLease ?? session_transaction_1.withSessionTurnLease;
|
|
239
|
+
return runWithLease(sessPath, async (sessionTurnLease) => {
|
|
240
|
+
const existing = (0, context_1.loadSession)(sessPath);
|
|
241
|
+
let sessionState = existing?.state;
|
|
242
|
+
let persistPromise;
|
|
243
|
+
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
244
|
+
? existing.messages
|
|
245
|
+
: [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)(channel, {}, undefined)) }];
|
|
246
|
+
// Pending dir
|
|
247
|
+
const pendingDir = (0, pending_1.getPendingDir)(agentName, friendId, channel, sessionKey);
|
|
248
|
+
// Accumulate outward text through the same callback boundary used by chat
|
|
249
|
+
// channels. `speak` flushes pending text immediately; `settle` is delivered
|
|
250
|
+
// once the turn completes.
|
|
251
|
+
let committedResponseText = "";
|
|
252
|
+
let pendingResponseText = "";
|
|
253
|
+
let terminalDeliveryKind = "text";
|
|
254
|
+
const deliveries = [];
|
|
255
|
+
const deliveryFailures = [];
|
|
256
|
+
const commitResponseText = (text) => {
|
|
257
|
+
const cleaned = stripThinkBlocks(text);
|
|
258
|
+
/* v8 ignore next -- deliverPending strips first; this is a defensive direct-call guard @preserve */
|
|
259
|
+
if (!cleaned)
|
|
260
|
+
return;
|
|
261
|
+
committedResponseText = committedResponseText
|
|
262
|
+
? `${committedResponseText}\n${cleaned}`
|
|
263
|
+
: cleaned;
|
|
264
|
+
};
|
|
265
|
+
const deliveryErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
266
|
+
const deliverPending = async (kind, optionsForDelivery) => {
|
|
267
|
+
const text = stripThinkBlocks(pendingResponseText);
|
|
268
|
+
pendingResponseText = "";
|
|
269
|
+
if (!text)
|
|
270
|
+
return;
|
|
271
|
+
const delivery = { kind, text };
|
|
272
|
+
try {
|
|
273
|
+
await options.deliverySink?.onDelivery(delivery);
|
|
274
|
+
deliveries.push(delivery);
|
|
275
|
+
commitResponseText(text);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
const failure = { ...delivery, error: deliveryErrorMessage(error) };
|
|
279
|
+
deliveryFailures.push(failure);
|
|
280
|
+
(0, runtime_1.emitNervesEvent)({
|
|
281
|
+
level: "error",
|
|
282
|
+
component: "senses",
|
|
283
|
+
event: "senses.shared_turn_delivery_error",
|
|
284
|
+
message: "shared turn outward delivery failed",
|
|
285
|
+
meta: { agentName, channel, sessionKey, friendId, kind, error: failure.error, textLength: text.length },
|
|
286
|
+
});
|
|
287
|
+
if (optionsForDelivery.throwOnError)
|
|
288
|
+
throw error;
|
|
289
|
+
commitResponseText(text);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
/* v8 ignore start — no-op callback stubs; only onTextChunk does real work (covered via mock) */
|
|
293
|
+
const callbacks = {
|
|
294
|
+
settleOutputMode: "retractable_buffer",
|
|
295
|
+
onModelStart: () => { },
|
|
296
|
+
onModelStreamStart: () => { },
|
|
297
|
+
onTextChunk: (chunk) => { pendingResponseText += chunk; },
|
|
298
|
+
onReasoningChunk: () => { },
|
|
299
|
+
onToolStart: () => { },
|
|
300
|
+
onToolEnd: (name, _summary, success) => {
|
|
301
|
+
if (name === "settle" && success)
|
|
302
|
+
terminalDeliveryKind = "settle";
|
|
342
303
|
},
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
347
|
-
const prepared = (0, context_2.postTurnTrim)(turnMessages, usage, hooks);
|
|
348
|
-
sessionState = state;
|
|
349
|
-
persistPromise = (0, context_2.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
|
|
350
|
-
},
|
|
351
|
-
/* v8 ignore stop */
|
|
352
|
-
accumulateFriendTokens: friends_1.accumulateFriendTokens,
|
|
353
|
-
});
|
|
354
|
-
if (turnResult.gateResult && !turnResult.gateResult.allowed) {
|
|
355
|
-
const blockedResponse = "autoReply" in turnResult.gateResult
|
|
356
|
-
? turnResult.gateResult.autoReply
|
|
357
|
-
: `(blocked by trust gate: ${turnResult.gateResult.reason})`;
|
|
358
|
-
return {
|
|
359
|
-
response: blockedResponse,
|
|
360
|
-
ponderDeferred: false,
|
|
361
|
-
deliveries,
|
|
362
|
-
deliveryFailures,
|
|
304
|
+
onError: () => { },
|
|
305
|
+
onClearText: () => { pendingResponseText = ""; },
|
|
306
|
+
flushNow: () => deliverPending("speak", { throwOnError: true }),
|
|
363
307
|
};
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
308
|
+
/* v8 ignore stop */
|
|
309
|
+
// Run the pipeline
|
|
310
|
+
const userMsg = { role: "user", content: userMessage };
|
|
311
|
+
(0, session_events_1.stampIngressTime)(userMsg);
|
|
312
|
+
const turnResult = await (0, pipeline_1.handleInboundTurn)({
|
|
313
|
+
channel,
|
|
314
|
+
latencyMode: options.latencyMode,
|
|
315
|
+
sessionKey,
|
|
316
|
+
capabilities,
|
|
317
|
+
messages: [userMsg],
|
|
318
|
+
callbacks,
|
|
319
|
+
sessionTurnLease,
|
|
320
|
+
/* v8 ignore start — delegation wrappers; pipeline integration tested separately */
|
|
321
|
+
friendResolver: { resolve: () => resolver.resolve() },
|
|
322
|
+
sessionLoader: {
|
|
323
|
+
loadOrCreate: () => Promise.resolve({
|
|
324
|
+
messages: sessionMessages,
|
|
325
|
+
sessionPath: sessPath,
|
|
326
|
+
state: sessionState,
|
|
327
|
+
events: existing?.events,
|
|
328
|
+
structuredOutputs: existing?.structuredOutputs,
|
|
329
|
+
}),
|
|
330
|
+
},
|
|
331
|
+
/* v8 ignore stop */
|
|
332
|
+
pendingDir,
|
|
333
|
+
friendStore,
|
|
334
|
+
provider: resolverParams.provider,
|
|
335
|
+
externalId: resolverParams.externalId,
|
|
336
|
+
tenantId: resolverParams.tenantId,
|
|
337
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
338
|
+
drainPending: pending_1.drainPending,
|
|
339
|
+
runAgentOptions: {
|
|
340
|
+
mcpManager,
|
|
341
|
+
...(options.latencyMode === "live" ? { skipKeptNotes: true } : {}),
|
|
342
|
+
toolContext: {
|
|
343
|
+
signin: async () => undefined,
|
|
344
|
+
...(options.toolContext ? options.toolContext : {}),
|
|
345
|
+
currentUserMessage: userMessage,
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
/* v8 ignore start — delegation wrappers; these just forward to the real functions */
|
|
349
|
+
runAgent: (msgs, cb, ch, sig, opts) => (0, core_1.runAgent)(msgs, cb, ch, sig, opts),
|
|
350
|
+
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
351
|
+
const prepared = (0, context_2.postTurnTrim)(turnMessages, usage, hooks);
|
|
352
|
+
sessionState = state;
|
|
353
|
+
persistPromise = (0, context_2.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
|
|
354
|
+
},
|
|
355
|
+
/* v8 ignore stop */
|
|
356
|
+
accumulateFriendTokens: friends_1.accumulateFriendTokens,
|
|
357
|
+
});
|
|
358
|
+
if (turnResult.gateResult && !turnResult.gateResult.allowed) {
|
|
359
|
+
const blockedResponse = "autoReply" in turnResult.gateResult
|
|
360
|
+
? turnResult.gateResult.autoReply
|
|
361
|
+
: `(blocked by trust gate: ${turnResult.gateResult.reason})`;
|
|
362
|
+
return {
|
|
363
|
+
response: blockedResponse,
|
|
364
|
+
ponderDeferred: false,
|
|
365
|
+
deliveries,
|
|
366
|
+
deliveryFailures,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
373
369
|
if (persistPromise)
|
|
374
370
|
await persistPromise;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
371
|
+
await deliverPending(terminalDeliveryKind, { throwOnError: false });
|
|
372
|
+
const ponderDeferred = false;
|
|
373
|
+
// Build response
|
|
374
|
+
let finalResponse;
|
|
375
|
+
if (committedResponseText.length === 0) {
|
|
376
|
+
// Agent settled but no text came through callbacks — check session transcript for the settle answer
|
|
377
|
+
// Await deferred persist so the session file is up-to-date before readback
|
|
378
|
+
/* v8 ignore next -- persistPromise set inside v8-ignored postTurn callback; tested via pipeline integration @preserve */
|
|
379
|
+
if (persistPromise)
|
|
380
|
+
await persistPromise;
|
|
381
|
+
const postTurnSession = (0, context_1.loadSession)(sessPath);
|
|
382
|
+
if (postTurnSession?.messages) {
|
|
383
|
+
finalResponse = extractOutwardSenseDeliveryText(postTurnSession.messages)
|
|
384
|
+
?? "(agent responded but response was empty)";
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
finalResponse = "(agent responded but response was empty)";
|
|
388
|
+
}
|
|
379
389
|
}
|
|
380
390
|
else {
|
|
381
|
-
finalResponse =
|
|
391
|
+
finalResponse = committedResponseText;
|
|
392
|
+
}
|
|
393
|
+
// Strip MiniMax-style <think>...</think> blocks from the final response.
|
|
394
|
+
// When a reasoning-style model emits only a think block and no final answer
|
|
395
|
+
// (no settle tool call, no post-think text), the readback path above
|
|
396
|
+
// surfaces the raw saved assistant content — which includes the think tags
|
|
397
|
+
// and renders as empty (or as raw reasoning) on MCP/CLI clients. Strip
|
|
398
|
+
// here so the caller sees the actual delivered text. If only reasoning
|
|
399
|
+
// came through and nothing else, surface a clear diagnostic message
|
|
400
|
+
// instead of a blank response so the operator knows what happened.
|
|
401
|
+
finalResponse = stripThinkBlocks(finalResponse);
|
|
402
|
+
if (finalResponse.length === 0) {
|
|
403
|
+
(0, runtime_1.emitNervesEvent)({
|
|
404
|
+
level: "warn",
|
|
405
|
+
component: "senses",
|
|
406
|
+
event: "senses.shared_turn_only_reasoning",
|
|
407
|
+
message: "agent produced only <think> reasoning with no final answer — likely a model that closed the think tag without continuing",
|
|
408
|
+
meta: { agentName, channel, sessionKey, friendId },
|
|
409
|
+
});
|
|
410
|
+
finalResponse = "(agent produced reasoning but no final answer this turn — try again, or check the session transcript for the trace)";
|
|
411
|
+
}
|
|
412
|
+
// Cap response length
|
|
413
|
+
if (finalResponse.length > RESPONSE_CAP) {
|
|
414
|
+
finalResponse = finalResponse.slice(0, RESPONSE_CAP) + "\n\n[truncated — response exceeded 50K characters]";
|
|
382
415
|
}
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
finalResponse = committedResponseText;
|
|
386
|
-
}
|
|
387
|
-
// Strip MiniMax-style <think>...</think> blocks from the final response.
|
|
388
|
-
// When a reasoning-style model emits only a think block and no final answer
|
|
389
|
-
// (no settle tool call, no post-think text), the readback path above
|
|
390
|
-
// surfaces the raw saved assistant content — which includes the think tags
|
|
391
|
-
// and renders as empty (or as raw reasoning) on MCP/CLI clients. Strip
|
|
392
|
-
// here so the caller sees the actual delivered text. If only reasoning
|
|
393
|
-
// came through and nothing else, surface a clear diagnostic message
|
|
394
|
-
// instead of a blank response so the operator knows what happened.
|
|
395
|
-
finalResponse = stripThinkBlocks(finalResponse);
|
|
396
|
-
if (finalResponse.length === 0) {
|
|
397
416
|
(0, runtime_1.emitNervesEvent)({
|
|
398
|
-
level: "warn",
|
|
399
417
|
component: "senses",
|
|
400
|
-
event: "senses.
|
|
401
|
-
message: "
|
|
402
|
-
meta: { agentName, channel, sessionKey, friendId },
|
|
418
|
+
event: "senses.shared_turn_end",
|
|
419
|
+
message: "shared turn runner complete",
|
|
420
|
+
meta: { agentName, channel, sessionKey, friendId, ponderDeferred, responseLength: finalResponse.length },
|
|
403
421
|
});
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
// Cap response length
|
|
407
|
-
if (finalResponse.length > RESPONSE_CAP) {
|
|
408
|
-
finalResponse = finalResponse.slice(0, RESPONSE_CAP) + "\n\n[truncated — response exceeded 50K characters]";
|
|
409
|
-
}
|
|
410
|
-
(0, runtime_1.emitNervesEvent)({
|
|
411
|
-
component: "senses",
|
|
412
|
-
event: "senses.shared_turn_end",
|
|
413
|
-
message: "shared turn runner complete",
|
|
414
|
-
meta: { agentName, channel, sessionKey, friendId, ponderDeferred, responseLength: finalResponse.length },
|
|
422
|
+
return { response: finalResponse, ponderDeferred, deliveries, deliveryFailures };
|
|
415
423
|
});
|
|
416
|
-
return { response: finalResponse, ponderDeferred, deliveries, deliveryFailures };
|
|
417
424
|
}
|