@ouro.bot/cli 0.1.0-alpha.801 → 0.1.0-alpha.803
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 +12 -0
- package/deploy/unraid/README.txt +3 -3
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/session-events.js +7 -5
- package/dist/mind/context.js +1 -0
- package/dist/senses/shared-turn.js +139 -68
- package/dist/senses/telegram.js +21 -11
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.803",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Make Sanctuary update topology absence checks fail closed."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.802",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Prefer validated completion text for settled replies and bind delivery causality only when the newest validated current-turn session event matches, preventing stale or duplicate Telegram answers."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
4
16
|
{
|
|
5
17
|
"version": "0.1.0-alpha.801",
|
|
6
18
|
"changes": [
|
package/deploy/unraid/README.txt
CHANGED
|
@@ -2293,7 +2293,7 @@ ouro-butler-rollback
|
|
|
2293
2293
|
test "$CURRENT_ROLLBACK_IMAGE_ID" = "$ROLLBACK_IMAGE_ID"
|
|
2294
2294
|
docker rm --force ouro-butler-rollback >/dev/null 2>&1 || true
|
|
2295
2295
|
fi
|
|
2296
|
-
! docker container inspect ouro-butler-rollback >/dev/null 2>&1
|
|
2296
|
+
! docker container inspect ouro-butler-rollback >/dev/null 2>&1 || exit 1
|
|
2297
2297
|
rollback_sanctuary_bundle_if_pending "$IMAGE_ID"
|
|
2298
2298
|
assert_update_source "$ROLLBACK_IMAGE_ID" "$AUDIT_RUNNER_IMAGE_ID"
|
|
2299
2299
|
start_only_butler_for_recovery
|
|
@@ -2348,7 +2348,7 @@ ouro-butler-rollback
|
|
|
2348
2348
|
test "$PARTIAL_PRODUCTION_IMAGE_ID" = "$IMAGE_ID"
|
|
2349
2349
|
docker rm --force ouro-butler >/dev/null 2>&1 || true
|
|
2350
2350
|
fi
|
|
2351
|
-
! docker container inspect ouro-butler >/dev/null 2>&1
|
|
2351
|
+
! docker container inspect ouro-butler >/dev/null 2>&1 || exit 1
|
|
2352
2352
|
CURRENT_ROLLBACK_IMAGE_ID=$(docker inspect --format '{{.Image}}' ouro-butler-rollback)
|
|
2353
2353
|
test "$CURRENT_ROLLBACK_IMAGE_ID" = "$ROLLBACK_IMAGE_ID"
|
|
2354
2354
|
migrate_sanctuary_package_managed_bundle "$IMAGE_ID" rollback
|
|
@@ -2674,7 +2674,7 @@ Restore:
|
|
|
2674
2674
|
test "$PARTIAL_RESTORE_IMAGE_ID" = "$IMAGE_ID"
|
|
2675
2675
|
docker rm --force ouro-butler >/dev/null 2>&1 || true
|
|
2676
2676
|
fi
|
|
2677
|
-
! docker container inspect ouro-butler >/dev/null 2>&1
|
|
2677
|
+
! docker container inspect ouro-butler >/dev/null 2>&1 || exit 1
|
|
2678
2678
|
rm -f "$HOST_RESTORE_INSTALLER"
|
|
2679
2679
|
rm -f -- "$RESTORE_INSTALL_PROOF_ROOT/install.json"
|
|
2680
2680
|
rmdir -- "$RESTORE_INSTALL_PROOF_ROOT"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>ouro-butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.803</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -52,6 +52,7 @@ exports.stampIngressTime = stampIngressTime;
|
|
|
52
52
|
exports.getIngressTime = getIngressTime;
|
|
53
53
|
exports.stampIngressRelations = stampIngressRelations;
|
|
54
54
|
exports.getIngressRelations = getIngressRelations;
|
|
55
|
+
exports.projectedSessionEventIds = projectedSessionEventIds;
|
|
55
56
|
exports.projectProviderMessages = projectProviderMessages;
|
|
56
57
|
exports.annotateMessageTimestamps = annotateMessageTimestamps;
|
|
57
58
|
exports.bestEventTimestamp = bestEventTimestamp;
|
|
@@ -936,10 +937,13 @@ function buildEventFromMessage(message, sequence, recordedAt, captureKind, sourc
|
|
|
936
937
|
},
|
|
937
938
|
};
|
|
938
939
|
}
|
|
939
|
-
function
|
|
940
|
-
|
|
940
|
+
function projectedSessionEventIds(envelope) {
|
|
941
|
+
return envelope.projection.eventIds.length > 0
|
|
941
942
|
? envelope.projection.eventIds
|
|
942
943
|
: envelope.events.map((event) => event.id);
|
|
944
|
+
}
|
|
945
|
+
function projectProviderMessages(envelope) {
|
|
946
|
+
const eventIds = projectedSessionEventIds(envelope);
|
|
943
947
|
const byId = new Map(envelope.events.map((event) => [event.id, event]));
|
|
944
948
|
return eventIds
|
|
945
949
|
.map((id) => byId.get(id))
|
|
@@ -958,9 +962,7 @@ function projectProviderMessages(envelope) {
|
|
|
958
962
|
* System and tool messages are untouched.
|
|
959
963
|
*/
|
|
960
964
|
function annotateMessageTimestamps(envelope, messages, nowMs = Date.now()) {
|
|
961
|
-
const eventIds = envelope
|
|
962
|
-
? envelope.projection.eventIds
|
|
963
|
-
: envelope.events.map((event) => event.id);
|
|
965
|
+
const eventIds = projectedSessionEventIds(envelope);
|
|
964
966
|
const byId = new Map(envelope.events.map((event) => [event.id, event]));
|
|
965
967
|
const events = eventIds
|
|
966
968
|
.map((id) => byId.get(id))
|
package/dist/mind/context.js
CHANGED
|
@@ -349,6 +349,7 @@ function loadSession(filePath) {
|
|
|
349
349
|
return {
|
|
350
350
|
messages: (0, session_events_1.sanitizeProviderMessages)((0, session_events_1.projectProviderMessages)(envelope)),
|
|
351
351
|
events: envelope.events,
|
|
352
|
+
projectionEventIds: [...(0, session_events_1.projectedSessionEventIds)(envelope)],
|
|
352
353
|
structuredOutputs: envelope.structuredOutputs ?? [],
|
|
353
354
|
lastUsage: envelope.lastUsage ?? undefined,
|
|
354
355
|
state: denormalizeContinuityState(envelope.state),
|
|
@@ -177,70 +177,134 @@ function extractOutwardSenseDeliveryText(messages) {
|
|
|
177
177
|
: assistantContentText(assistant.content);
|
|
178
178
|
}
|
|
179
179
|
function hasAcceptedOutwardSessionAck(events, assistantIndex, toolCallId, toolName) {
|
|
180
|
+
if (typeof toolCallId !== "string" || !toolCallId.trim())
|
|
181
|
+
return false;
|
|
180
182
|
const expectedAck = OUTWARD_DELIVERY_TOOL_ACKS.get(toolName);
|
|
181
183
|
for (let index = assistantIndex + 1; index < events.length; index++) {
|
|
182
184
|
const candidate = events[index];
|
|
183
185
|
if (candidate.role !== "tool")
|
|
184
186
|
return false;
|
|
185
|
-
if (candidate.toolCallId
|
|
186
|
-
|
|
187
|
+
if (candidate.toolCallId !== toolCallId && candidate.relations?.toolCallId !== toolCallId)
|
|
188
|
+
continue;
|
|
189
|
+
return candidate.toolCallId === toolCallId
|
|
190
|
+
&& candidate.relations?.toolCallId === toolCallId
|
|
191
|
+
&& candidate.provenance?.captureKind === "live"
|
|
192
|
+
&& Array.isArray(candidate.toolCalls)
|
|
193
|
+
&& candidate.toolCalls.length === 0
|
|
194
|
+
&& typeof candidate.content === "string"
|
|
195
|
+
&& candidate.content === expectedAck;
|
|
187
196
|
}
|
|
188
197
|
return false;
|
|
189
198
|
}
|
|
190
|
-
function newOutwardCoordinates(events, existingEventIds,
|
|
191
|
-
const boundaryIndex = events.findIndex((event) => event.id === afterEventId);
|
|
192
|
-
if (boundaryIndex < 0)
|
|
193
|
-
return [];
|
|
199
|
+
function newOutwardCoordinates(events, existingEventIds, boundaryIndex) {
|
|
194
200
|
return events.flatMap((event, eventIndex) => {
|
|
195
|
-
if (eventIndex <= boundaryIndex || existingEventIds.has(event.id) || event.role !== "assistant" || event.provenance?.captureKind
|
|
201
|
+
if (eventIndex <= boundaryIndex || existingEventIds.has(event.id) || event.role !== "assistant" || event.provenance?.captureKind !== "live" || !Array.isArray(event.toolCalls))
|
|
196
202
|
return [];
|
|
197
203
|
const outwardTools = event.toolCalls.flatMap((call) => {
|
|
198
|
-
|
|
204
|
+
const toolName = call?.function?.name;
|
|
205
|
+
if (toolName !== "speak" && toolName !== "settle")
|
|
199
206
|
return [];
|
|
200
|
-
if (!hasAcceptedOutwardSessionAck(events, eventIndex, call.id,
|
|
207
|
+
if (!hasAcceptedOutwardSessionAck(events, eventIndex, call.id, toolName))
|
|
201
208
|
return [];
|
|
202
|
-
const text = stripThinkBlocks(parseToolStringArg(call,
|
|
203
|
-
return text ? [{ kind:
|
|
209
|
+
const text = stripThinkBlocks(parseToolStringArg(call, toolName, toolName === "speak" ? "message" : "answer") ?? "");
|
|
210
|
+
return text ? [{ kind: toolName, eventId: event.id, eventIndex, text }] : [];
|
|
204
211
|
});
|
|
205
212
|
if (outwardTools.length > 0)
|
|
206
213
|
return outwardTools;
|
|
207
214
|
if (event.toolCalls.length > 0)
|
|
208
215
|
return [];
|
|
209
216
|
const text = stripThinkBlocks(typeof event.content === "string" ? event.content : "");
|
|
210
|
-
return text ? [{ kind: "text", eventId: event.id, text }] : [];
|
|
217
|
+
return text ? [{ kind: "text", eventId: event.id, eventIndex, text }] : [];
|
|
211
218
|
});
|
|
212
219
|
}
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
&& (!("tool_calls" in candidate) || !Array.isArray(candidate.tool_calls) || candidate.tool_calls.length === 0)
|
|
216
|
-
&& typeof candidate.content === "string"
|
|
217
|
-
&& candidate.content.trim().length > 0);
|
|
218
|
-
return message ? assistantContentText(message.content) : null;
|
|
219
|
-
}
|
|
220
|
-
function causalSessionEventIds(events, existingEventIds, attempts, afterEventId) {
|
|
221
|
-
if (!afterEventId)
|
|
220
|
+
function alignedDeliveryCoordinates(view, attempts, finalAttemptIndex, finalCoordinate) {
|
|
221
|
+
if (!view)
|
|
222
222
|
return attempts.flatMap((attempt) => attempt.delivered ? [null] : []);
|
|
223
|
-
const
|
|
223
|
+
const terminalCoordinate = finalCoordinate ?? (finalAttemptIndex === undefined ? view.terminal : undefined);
|
|
224
|
+
const reservedAttemptIndex = terminalCoordinate
|
|
225
|
+
? finalAttemptIndex !== undefined
|
|
226
|
+
? (attempts[finalAttemptIndex]?.delivered && attempts[finalAttemptIndex]?.kind === terminalCoordinate.kind && attempts[finalAttemptIndex]?.text === terminalCoordinate.text ? finalAttemptIndex : -1)
|
|
227
|
+
: attempts.findLastIndex((attempt) => attempt.delivered && attempt.kind === terminalCoordinate.kind && attempt.text === terminalCoordinate.text)
|
|
228
|
+
: -1;
|
|
229
|
+
const availableCoordinates = terminalCoordinate ? view.coordinates.filter((coordinate) => coordinate !== terminalCoordinate) : view.coordinates;
|
|
224
230
|
let nextCoordinate = 0;
|
|
225
|
-
return attempts.flatMap((attempt) => {
|
|
231
|
+
return attempts.flatMap((attempt, attemptIndex) => {
|
|
226
232
|
if (!attempt.delivered)
|
|
227
233
|
return [];
|
|
228
|
-
|
|
234
|
+
if (attemptIndex === finalAttemptIndex)
|
|
235
|
+
return finalCoordinate && finalCoordinate.kind === attempt.kind && finalCoordinate.text === attempt.text ? [finalCoordinate] : [null];
|
|
236
|
+
if (attemptIndex === reservedAttemptIndex && terminalCoordinate)
|
|
237
|
+
return [terminalCoordinate];
|
|
238
|
+
const coordinateIndex = availableCoordinates.findIndex((coordinate, index) => index >= nextCoordinate && coordinate.kind === attempt.kind && coordinate.text === attempt.text);
|
|
229
239
|
if (coordinateIndex < 0)
|
|
230
240
|
return [null];
|
|
231
241
|
nextCoordinate = coordinateIndex + 1;
|
|
232
|
-
return [
|
|
242
|
+
return [availableCoordinates[coordinateIndex]];
|
|
233
243
|
});
|
|
234
244
|
}
|
|
245
|
+
function causalSessionEventIds(view, attempts, finalAttemptIndex, finalCoordinate) {
|
|
246
|
+
return alignedDeliveryCoordinates(view, attempts, finalAttemptIndex, finalCoordinate).map((coordinate) => coordinate?.eventId ?? null);
|
|
247
|
+
}
|
|
235
248
|
function currentIngressEventId(events, existingEventIds, userMessage, precommittedIngress, ingressRelations) {
|
|
236
|
-
if (precommittedIngress)
|
|
237
|
-
return precommittedIngress.eventId;
|
|
238
249
|
const reference = ingressRelations?.references[0];
|
|
239
|
-
|
|
240
|
-
|
|
250
|
+
const carriesReference = (event, expected) => Array.isArray(event.relations?.references) && event.relations.references.includes(expected);
|
|
251
|
+
const matches = events.filter((event) => (event.role === "user"
|
|
241
252
|
&& event.content === userMessage
|
|
242
|
-
&& event.provenance?.captureKind
|
|
243
|
-
&& (
|
|
253
|
+
&& event.provenance?.captureKind === "live"
|
|
254
|
+
&& (precommittedIngress
|
|
255
|
+
? ((event.id === precommittedIngress.eventId && carriesReference(event, precommittedIngress.reference))
|
|
256
|
+
|| (!existingEventIds.has(event.id) && carriesReference(event, precommittedIngress.reference)))
|
|
257
|
+
: !existingEventIds.has(event.id) && (!reference || carriesReference(event, reference)))));
|
|
258
|
+
return matches.length === 1 ? matches[0].id : undefined;
|
|
259
|
+
}
|
|
260
|
+
function rawSessionEvents(value) {
|
|
261
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
262
|
+
return [];
|
|
263
|
+
const record = value;
|
|
264
|
+
return record.version === 2 && Array.isArray(record.events) ? record.events : [];
|
|
265
|
+
}
|
|
266
|
+
function exactProjectedIngressMessage(existing, messages, eventId) {
|
|
267
|
+
const eventsById = new Map(existing.events.map((event) => [event.id, event]));
|
|
268
|
+
if (eventsById.size !== existing.events.length)
|
|
269
|
+
return null;
|
|
270
|
+
const seenProjectionIds = new Set();
|
|
271
|
+
const projectedEvents = [];
|
|
272
|
+
for (const projectedId of existing.projectionEventIds) {
|
|
273
|
+
if (typeof projectedId !== "string" || !projectedId.trim() || seenProjectionIds.has(projectedId))
|
|
274
|
+
return null;
|
|
275
|
+
const event = eventsById.get(projectedId);
|
|
276
|
+
if (!event)
|
|
277
|
+
return null;
|
|
278
|
+
seenProjectionIds.add(projectedId);
|
|
279
|
+
projectedEvents.push(event);
|
|
280
|
+
}
|
|
281
|
+
if (existing.projectionEventIds.filter((projectedId) => projectedId === eventId).length !== 1)
|
|
282
|
+
return null;
|
|
283
|
+
const projectedUsers = projectedEvents.filter((event) => event.role === "user");
|
|
284
|
+
const providerUsers = messages.filter((message) => message.role === "user");
|
|
285
|
+
if (projectedUsers.at(-1)?.id !== eventId || projectedUsers.length !== providerUsers.length)
|
|
286
|
+
return null;
|
|
287
|
+
return providerUsers.at(-1);
|
|
288
|
+
}
|
|
289
|
+
function currentTurnEventView(events, existingEventIds, userMessage, precommittedIngress, ingressRelations) {
|
|
290
|
+
const eventIds = new Set();
|
|
291
|
+
let previousSequence = 0;
|
|
292
|
+
for (const event of events) {
|
|
293
|
+
if (!event || typeof event !== "object" || typeof event.id !== "string" || !event.id.trim() || eventIds.has(event.id) || !Number.isSafeInteger(event.sequence) || event.sequence <= previousSequence)
|
|
294
|
+
return null;
|
|
295
|
+
eventIds.add(event.id);
|
|
296
|
+
previousSequence = event.sequence;
|
|
297
|
+
}
|
|
298
|
+
const ingressEventId = currentIngressEventId(events, existingEventIds, userMessage, precommittedIngress, ingressRelations);
|
|
299
|
+
if (!ingressEventId)
|
|
300
|
+
return null;
|
|
301
|
+
const ingressIndex = events.findIndex((event) => event.id === ingressEventId);
|
|
302
|
+
if (events.some((event, eventIndex) => eventIndex > ingressIndex && existingEventIds.has(event.id)))
|
|
303
|
+
return null;
|
|
304
|
+
const coordinates = newOutwardCoordinates(events, existingEventIds, ingressIndex);
|
|
305
|
+
const newestAssistantIndex = events.findLastIndex((event, eventIndex) => eventIndex > ingressIndex && event.role === "assistant");
|
|
306
|
+
const terminal = coordinates.findLast((coordinate) => coordinate.eventIndex === newestAssistantIndex);
|
|
307
|
+
return { coordinates, ...(terminal ? { terminal } : {}) };
|
|
244
308
|
}
|
|
245
309
|
function getSenseSessionPath(agentName, friendId, channel, sessionKey, agentRootOverride) {
|
|
246
310
|
return path.join(agentRootOverride ?? (0, identity_1.getAgentRoot)(agentName), "state", "sessions", friendId, channel, `${(0, config_1.sanitizeKey)(sessionKey)}.json`);
|
|
@@ -309,11 +373,13 @@ async function runSenseTurn(options) {
|
|
|
309
373
|
return runWithLease(sessPath, async (sessionTurnLease) => {
|
|
310
374
|
const baseSessionRevision = (0, session_transaction_1.readSessionTransaction)(sessPath, sessionTurnLease).revision;
|
|
311
375
|
const existing = (0, context_1.loadSession)(sessPath);
|
|
376
|
+
const precommittedIngressEvent = options.precommittedIngress
|
|
377
|
+
? existing?.events?.find((candidate) => candidate.id === options.precommittedIngress.eventId)
|
|
378
|
+
: undefined;
|
|
312
379
|
if (options.precommittedIngress) {
|
|
313
|
-
const event = existing?.events?.find((candidate) => candidate.id === options.precommittedIngress.eventId);
|
|
314
380
|
const latestUserEvent = existing?.events?.filter((candidate) => candidate.role === "user").at(-1);
|
|
315
|
-
if (!
|
|
316
|
-
|| !
|
|
381
|
+
if (!precommittedIngressEvent || precommittedIngressEvent !== latestUserEvent || precommittedIngressEvent.role !== "user" || precommittedIngressEvent.content !== userMessage
|
|
382
|
+
|| !precommittedIngressEvent.relations.references.includes(options.precommittedIngress.reference)) {
|
|
317
383
|
throw new Error("shared turn precommitted ingress is missing, mismatched, or no longer current");
|
|
318
384
|
}
|
|
319
385
|
}
|
|
@@ -323,7 +389,16 @@ async function runSenseTurn(options) {
|
|
|
323
389
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
324
390
|
? existing.messages
|
|
325
391
|
: [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(await (0, prompt_1.buildSystem)(channel, {}, undefined)) }];
|
|
326
|
-
|
|
392
|
+
if (precommittedIngressEvent) {
|
|
393
|
+
const projectedIngress = exactProjectedIngressMessage(existing, sessionMessages, precommittedIngressEvent.id);
|
|
394
|
+
if (!projectedIngress || projectedIngress.role !== "user" || projectedIngress.content !== userMessage)
|
|
395
|
+
throw new Error("shared turn precommitted ingress is absent from the provider projection");
|
|
396
|
+
(0, session_events_1.stampIngressRelations)(projectedIngress, {
|
|
397
|
+
replyToEventId: precommittedIngressEvent.relations.replyToEventId,
|
|
398
|
+
threadRootEventId: precommittedIngressEvent.relations.threadRootEventId,
|
|
399
|
+
references: precommittedIngressEvent.relations.references,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
327
402
|
// Pending dir
|
|
328
403
|
const pendingDir = (0, pending_1.getPendingDir)(agentName, friendId, channel, sessionKey);
|
|
329
404
|
// Accumulate outward text through the same callback boundary used by chat
|
|
@@ -352,9 +427,10 @@ async function runSenseTurn(options) {
|
|
|
352
427
|
const text = stripThinkBlocks(pendingResponseText);
|
|
353
428
|
pendingResponseText = "";
|
|
354
429
|
if (!text)
|
|
355
|
-
return;
|
|
430
|
+
return undefined;
|
|
356
431
|
const delivery = { kind, text };
|
|
357
432
|
const attempt = { kind, text, delivered: false };
|
|
433
|
+
const attemptIndex = deliveryAttempts.length;
|
|
358
434
|
deliveryAttempts.push(attempt);
|
|
359
435
|
try {
|
|
360
436
|
await options.deliverySink?.onDelivery(delivery);
|
|
@@ -376,6 +452,7 @@ async function runSenseTurn(options) {
|
|
|
376
452
|
throw error;
|
|
377
453
|
commitResponseText(text);
|
|
378
454
|
}
|
|
455
|
+
return attemptIndex;
|
|
379
456
|
};
|
|
380
457
|
/* v8 ignore start — callback stubs are exercised through the pipeline integration */
|
|
381
458
|
const callbacks = {
|
|
@@ -393,7 +470,7 @@ async function runSenseTurn(options) {
|
|
|
393
470
|
},
|
|
394
471
|
onError: () => { },
|
|
395
472
|
onClearText: () => { pendingResponseText = ""; },
|
|
396
|
-
flushNow: () => deliverPending("speak", { throwOnError: true }),
|
|
473
|
+
flushNow: async () => { await deliverPending("speak", { throwOnError: true }); },
|
|
397
474
|
};
|
|
398
475
|
/* v8 ignore stop */
|
|
399
476
|
// Run the pipeline
|
|
@@ -469,27 +546,28 @@ async function runSenseTurn(options) {
|
|
|
469
546
|
};
|
|
470
547
|
}
|
|
471
548
|
const persistedEvents = persistPromise ? await persistPromise : [];
|
|
472
|
-
const
|
|
473
|
-
|
|
549
|
+
const terminalEvents = persistedEvents.length > 0
|
|
550
|
+
? persistedEvents
|
|
551
|
+
: rawSessionEvents((0, session_transaction_1.readSessionTransaction)(sessPath, sessionTurnLease).value);
|
|
552
|
+
const eventView = currentTurnEventView(terminalEvents, existingEventIds, userMessage, options.precommittedIngress, options.ingressRelations);
|
|
553
|
+
let finalDeliveryKind = terminalDeliveryKind;
|
|
474
554
|
const acceptedTerminalOutcome = turnResult.turnOutcome === "settled" || turnResult.turnOutcome === "blocked";
|
|
475
555
|
const failoverText = turnResult.turnOutcome === "errored" ? turnResult.failoverMessage?.trim() : undefined;
|
|
476
556
|
const expectsOutwardResponse = acceptedTerminalOutcome || turnResult.turnOutcome === "command" || Boolean(failoverText);
|
|
477
|
-
|
|
478
|
-
let recoveredTerminalEventId;
|
|
557
|
+
let finalCausalCoordinate;
|
|
479
558
|
if (acceptedTerminalOutcome) {
|
|
480
|
-
const completionText = turnResult.completion?.answer
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
recoveredTerminalEventId = causalSessionEventIds(persistedEvents, existingEventIds, [{ kind: "text", text: stripThinkBlocks(plainTerminalText), delivered: true }], ingressEventId)[0] ?? undefined;
|
|
559
|
+
const completionText = stripThinkBlocks(turnResult.completion?.answer ?? "");
|
|
560
|
+
let authoritativeText = completionText;
|
|
561
|
+
if (!authoritativeText && eventView?.terminal) {
|
|
562
|
+
authoritativeText = eventView.terminal.text;
|
|
563
|
+
finalDeliveryKind = eventView.terminal.kind;
|
|
564
|
+
}
|
|
565
|
+
if (eventView?.terminal?.kind === finalDeliveryKind && eventView.terminal.text === authoritativeText)
|
|
566
|
+
finalCausalCoordinate = eventView.terminal;
|
|
567
|
+
const terminalAlreadyDelivered = finalCausalCoordinate
|
|
568
|
+
? alignedDeliveryCoordinates(eventView, deliveryAttempts).includes(finalCausalCoordinate)
|
|
569
|
+
: false;
|
|
570
|
+
pendingResponseText = authoritativeText && !terminalAlreadyDelivered ? authoritativeText : "";
|
|
493
571
|
}
|
|
494
572
|
else if (turnResult.turnOutcome === "command") {
|
|
495
573
|
// Slash-command text is emitted directly by the pipeline and has no assistant event.
|
|
@@ -500,28 +578,20 @@ async function runSenseTurn(options) {
|
|
|
500
578
|
else {
|
|
501
579
|
pendingResponseText = "";
|
|
502
580
|
}
|
|
503
|
-
await deliverPending(finalDeliveryKind, { throwOnError: false });
|
|
581
|
+
const finalDeliveryAttemptIndex = await deliverPending(finalDeliveryKind, { throwOnError: false });
|
|
504
582
|
const ponderDeferred = false;
|
|
505
583
|
// Build response
|
|
506
584
|
let finalResponse;
|
|
507
|
-
|
|
585
|
+
const finalDeliveryAttempt = finalDeliveryAttemptIndex === undefined ? undefined : deliveryAttempts[finalDeliveryAttemptIndex];
|
|
586
|
+
const responseDeliveryFailure = finalDeliveryAttempt && !finalDeliveryAttempt.delivered ? deliveryFailures.at(-1) : undefined;
|
|
587
|
+
const responseCausalSessionEventId = finalDeliveryAttempt && !finalDeliveryAttempt.delivered ? finalCausalCoordinate?.eventId : undefined;
|
|
508
588
|
if (committedResponseText.length === 0) {
|
|
509
589
|
if (!expectsOutwardResponse) {
|
|
510
590
|
finalResponse = "";
|
|
511
591
|
}
|
|
512
592
|
else {
|
|
513
|
-
// The terminal turn had no committed text — check its session transcript for the delivered answer.
|
|
514
|
-
const postTurnSession = (0, context_1.loadSession)(sessPath);
|
|
515
593
|
const emptyFallback = options.emptyResponseFallback?.();
|
|
516
|
-
|
|
517
|
-
const recovered = extractOutwardSenseDeliveryText(postTurnSession.messages.slice(preTurnMessageCount));
|
|
518
|
-
finalResponse = recovered ?? emptyFallback ?? (hadReasoningChunk ? "" : "(agent responded but response was empty)");
|
|
519
|
-
if (recovered)
|
|
520
|
-
responseCausalSessionEventId = causalSessionEventIds(persistedEvents, existingEventIds, [{ kind: finalDeliveryKind, text: stripThinkBlocks(recovered), delivered: true }], ingressEventId)[0] ?? undefined;
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
finalResponse = emptyFallback ?? (hadReasoningChunk ? "" : "(agent responded but response was empty)");
|
|
524
|
-
}
|
|
594
|
+
finalResponse = emptyFallback ?? (hadReasoningChunk ? "" : "(agent responded but response was empty)");
|
|
525
595
|
}
|
|
526
596
|
}
|
|
527
597
|
else {
|
|
@@ -561,10 +631,11 @@ async function runSenseTurn(options) {
|
|
|
561
631
|
ponderDeferred,
|
|
562
632
|
deliveries,
|
|
563
633
|
deliveryFailures,
|
|
634
|
+
...(responseDeliveryFailure ? { responseDeliveryFailure } : {}),
|
|
564
635
|
providerInvocationCount,
|
|
565
636
|
toolInvocationCount,
|
|
566
637
|
sessionPath: sessPath,
|
|
567
|
-
...(deliveries.length > 0 ? { causalSessionEventIds: causalSessionEventIds(
|
|
638
|
+
...(deliveries.length > 0 ? { causalSessionEventIds: causalSessionEventIds(eventView, deliveryAttempts, finalDeliveryAttemptIndex, finalCausalCoordinate) } : {}),
|
|
568
639
|
...(responseCausalSessionEventId ? { responseCausalSessionEventId } : {}),
|
|
569
640
|
};
|
|
570
641
|
});
|
package/dist/senses/telegram.js
CHANGED
|
@@ -95,6 +95,11 @@ function hasUserVisibleTurnResponse(response) {
|
|
|
95
95
|
const text = response.trim();
|
|
96
96
|
return text.length > 0 && text !== EMPTY_SHARED_TURN_DIAGNOSTIC;
|
|
97
97
|
}
|
|
98
|
+
function responseFallbackAfterDeliveries(result, successfulDeliveryCount) {
|
|
99
|
+
const failedTerminal = result.responseDeliveryFailure?.text ?? (result.responseCausalSessionEventId ? result.deliveryFailures.at(-1)?.text : undefined);
|
|
100
|
+
const response = failedTerminal ?? (successfulDeliveryCount === 0 && result.deliveryFailures.length === 0 ? result.response : "");
|
|
101
|
+
return hasUserVisibleTurnResponse(response) ? response : null;
|
|
102
|
+
}
|
|
98
103
|
function createFullVisibilityProgress() {
|
|
99
104
|
const progress = {};
|
|
100
105
|
return { progress, emptyResponseFallback: () => progress.fallback?.() };
|
|
@@ -1186,10 +1191,15 @@ function createTelegramSenseApp(options) {
|
|
|
1186
1191
|
emptyResponseFallback: fullVisibility.emptyResponseFallback,
|
|
1187
1192
|
deliverySink: { onDelivery: (delivery) => deliver(delivery.text, delivery.kind === "settle") },
|
|
1188
1193
|
});
|
|
1189
|
-
|
|
1190
|
-
|
|
1194
|
+
let responseFallbackArtifactId;
|
|
1195
|
+
const responseFallback = responseFallbackAfterDeliveries(result, effects.length);
|
|
1196
|
+
if (responseFallback) {
|
|
1197
|
+
await deliver(responseFallback, true);
|
|
1198
|
+
responseFallbackArtifactId = effects.at(-1)?.id;
|
|
1199
|
+
}
|
|
1191
1200
|
const causalEventIds = Object.fromEntries(effects.flatMap((artifact, index) => {
|
|
1192
|
-
const eventId = result.causalSessionEventIds?.[index]
|
|
1201
|
+
const eventId = result.causalSessionEventIds?.[index]
|
|
1202
|
+
?? (artifact.id === responseFallbackArtifactId ? result.responseCausalSessionEventId : undefined);
|
|
1193
1203
|
return eventId ? [[artifact.id, eventId]] : [];
|
|
1194
1204
|
}));
|
|
1195
1205
|
await recordAcceptedEffects(sessionPath, effects, undefined, causalEventIds);
|
|
@@ -1322,7 +1332,6 @@ function createTelegramSenseApp(options) {
|
|
|
1322
1332
|
meta: lifecycleMeta("senses.telegram_turn_start", { agentName: options.agentName, subject, ...acceptanceMeta, ...lifecycleCoordinates }, lifecycleStartedAt),
|
|
1323
1333
|
});
|
|
1324
1334
|
acceptanceAuditBarrier();
|
|
1325
|
-
let deliveryCount = 0;
|
|
1326
1335
|
const deliveredMessageIds = [];
|
|
1327
1336
|
const deliveredChunks = [];
|
|
1328
1337
|
let receiptStatus = "success";
|
|
@@ -1386,7 +1395,6 @@ function createTelegramSenseApp(options) {
|
|
|
1386
1395
|
}
|
|
1387
1396
|
else {
|
|
1388
1397
|
turnEffects.push(await deliverButlerEffect(delivery.text, `turn:${subject}:${message.updateId}:delivery:${deliveryOrdinal++}`, undefined, (messageId, chunk) => { deliveredMessageIds.push(messageId); deliveredChunks.push(chunk); }, exactDownloadCreditQuestion));
|
|
1389
|
-
deliveryCount += 1;
|
|
1390
1398
|
}
|
|
1391
1399
|
},
|
|
1392
1400
|
},
|
|
@@ -1418,12 +1426,14 @@ function createTelegramSenseApp(options) {
|
|
|
1418
1426
|
throw new Error("Canonical Sanctuary query did not produce exactly one matching grounded settle");
|
|
1419
1427
|
const canonical = (0, sanctuary_grounding_1.renderSanctuaryGroundedResponse)(grounding.toolName, grounding.facts);
|
|
1420
1428
|
turnEffects.push(await deliverButlerEffect(canonical, `turn:${subject}:${message.updateId}:delivery:${deliveryOrdinal++}`, undefined, (messageId, chunk) => { deliveredMessageIds.push(messageId); deliveredChunks.push(chunk); }));
|
|
1421
|
-
deliveryCount = 1;
|
|
1422
1429
|
}
|
|
1423
|
-
else
|
|
1424
|
-
const
|
|
1425
|
-
|
|
1426
|
-
|
|
1430
|
+
else {
|
|
1431
|
+
const responseFallback = responseFallbackAfterDeliveries(result, turnEffects.length);
|
|
1432
|
+
if (responseFallback) {
|
|
1433
|
+
const artifact = await deliverButlerEffect(responseFallback, `turn:${subject}:${message.updateId}:delivery:${deliveryOrdinal++}`, undefined, (messageId, chunk) => { deliveredMessageIds.push(messageId); deliveredChunks.push(chunk); }, exactDownloadCreditQuestion);
|
|
1434
|
+
turnEffects.push(artifact);
|
|
1435
|
+
responseFallbackArtifactId = artifact.id;
|
|
1436
|
+
}
|
|
1427
1437
|
}
|
|
1428
1438
|
if (result.sessionPath) {
|
|
1429
1439
|
const causalEventIds = Object.fromEntries((groundingIntentTool ? [] : turnEffects).flatMap((artifact, index) => {
|
|
@@ -1440,7 +1450,7 @@ function createTelegramSenseApp(options) {
|
|
|
1440
1450
|
component: "senses",
|
|
1441
1451
|
event: "senses.telegram_turn_end",
|
|
1442
1452
|
message: "Telegram authorized turn completed",
|
|
1443
|
-
meta: lifecycleMeta("senses.telegram_turn_end", { agentName: options.agentName, subject, deliveryCount:
|
|
1453
|
+
meta: lifecycleMeta("senses.telegram_turn_end", { agentName: options.agentName, subject, deliveryCount: turnEffects.length, ...acceptanceMeta, ...lifecycleCoordinates, ...(acceptanceMarker ? { outcome: "success", errorDigest: null } : {}) }, Math.max(Date.now(), lifecycleStartedAt + 1)),
|
|
1444
1454
|
});
|
|
1445
1455
|
}
|
|
1446
1456
|
catch (error) {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.803",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.803",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|