@ouro.bot/cli 0.1.0-alpha.666 → 0.1.0-alpha.668
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 +13 -0
- package/dist/arc/flight-recorder.js +179 -6
- package/dist/arc/obligations.js +35 -1
- package/dist/heart/daemon/cli-exec.js +90 -1
- package/dist/heart/daemon/cli-help.js +12 -1
- package/dist/heart/daemon/cli-parse.js +102 -0
- package/dist/heart/habits/habit-parser.js +8 -0
- package/dist/heart/habits/habit-runtime-state.js +17 -3
- package/dist/heart/habits/habit-session-summary.js +318 -0
- package/dist/heart/habits/habit-session.js +62 -7
- package/dist/heart/mailbox/mailbox-http-hooks.js +27 -1
- package/dist/heart/mailbox/mailbox-http-routes.js +82 -1
- package/dist/heart/mailbox/mailbox-read.js +3 -1
- package/dist/heart/mailbox/readers/agent-machine.js +12 -2
- package/dist/heart/mailbox/readers/runtime-readers.js +31 -0
- package/dist/heart/work-card.js +6 -2
- package/dist/repertoire/tools-session.js +126 -0
- package/dist/senses/habit-turn-message.js +41 -3
- package/dist/senses/inner-dialog-worker.js +113 -1
- package/dist/senses/inner-dialog.js +24 -12
- package/dist/senses/pipeline.js +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
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.668",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Harden flight-recorder reconciliation so terminal fulfilled-only repairs are not continuable work, unverifiable active obligation ids are preserved and degraded, parseable-invalid obligation records are excluded from verified recovery state, synthesized recovery actions get fresh provenance, reads remain side-effect-free, and legacy obligation records stay visible in Mailbox, Workbench, and Work Card summaries."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.667",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Add stateful habit session summaries, summary tooling, mailbox APIs, nerves review, and habit history visibility.",
|
|
14
|
+
"Harden habit summary receipts as the canonical summary snapshot, validate agent-scoped Mailbox routes, await post-turn persistence, and preserve session-summary recovery through malformed locators and projected session edge cases."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"version": "0.1.0-alpha.666",
|
|
6
19
|
"changes": [
|
|
@@ -50,6 +50,7 @@ const path = __importStar(require("path"));
|
|
|
50
50
|
const crypto_1 = require("crypto");
|
|
51
51
|
const session_events_1 = require("../heart/session-events");
|
|
52
52
|
const runtime_1 = require("../nerves/runtime");
|
|
53
|
+
const obligations_1 = require("./obligations");
|
|
53
54
|
function isHabitRunTrigger(value) {
|
|
54
55
|
return value === "cron"
|
|
55
56
|
|| value === "launchd"
|
|
@@ -201,7 +202,7 @@ function normalizeResumeInvariants(resume) {
|
|
|
201
202
|
];
|
|
202
203
|
const missingChanged = resume.missing.join("\n") !== missing.join("\n");
|
|
203
204
|
const issues = [
|
|
204
|
-
...(resume.canContinue && !
|
|
205
|
+
...(resume.canContinue && !hasCompleteState ? ["canContinue true while hasCompleteState false"] : []),
|
|
205
206
|
...(resume.canContinue && !hasCurrentAsk ? ["canContinue true without currentAsk"] : []),
|
|
206
207
|
...(resume.canContinue && !hasNextSafeAction ? ["canContinue true without nextSafeAction"] : []),
|
|
207
208
|
...(resume.canContinue && resume.blockedBecause.length > 0 ? ["canContinue true while blocked"] : []),
|
|
@@ -226,7 +227,117 @@ function normalizeResumeInvariants(resume) {
|
|
|
226
227
|
: resume.recorderHealth,
|
|
227
228
|
};
|
|
228
229
|
}
|
|
229
|
-
|
|
230
|
+
const FLIGHT_RECORDER_RECONCILE_SOURCE_ID = "reconcile:active-obligations";
|
|
231
|
+
function remainingArcWorkDescriptions(resume) {
|
|
232
|
+
return [
|
|
233
|
+
...resume.activeReturnObligationIds.map((id) => `return obligation ${id}`),
|
|
234
|
+
...resume.activePacketIds.map((id) => `packet ${id}`),
|
|
235
|
+
...resume.openEvolutionCaseIds.map((id) => `evolution case ${id}`),
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
function nextSafeActionAfterObligationReconcile(resume, activeObligations, staleActiveObligationIds, unverifiableActiveObligationIds) {
|
|
239
|
+
if (unverifiableActiveObligationIds.length > 0) {
|
|
240
|
+
return (0, session_events_1.capStructuredRecordString)(`inspect unverifiable active obligations before acting: ${unverifiableActiveObligationIds.join(", ")}`);
|
|
241
|
+
}
|
|
242
|
+
const firstActive = activeObligations[0];
|
|
243
|
+
if (firstActive) {
|
|
244
|
+
const detail = firstActive.nextAction?.trim() || firstActive.content;
|
|
245
|
+
return (0, session_events_1.capStructuredRecordString)(`continue open obligation ${firstActive.id}: ${detail}`);
|
|
246
|
+
}
|
|
247
|
+
const remainingWork = remainingArcWorkDescriptions(resume);
|
|
248
|
+
if (remainingWork.length > 0) {
|
|
249
|
+
return (0, session_events_1.capStructuredRecordString)(`continue remaining Arc work: ${remainingWork.slice(0, 5).join(", ")}`);
|
|
250
|
+
}
|
|
251
|
+
return (0, session_events_1.capStructuredRecordString)(`wait for new input; reconciled completed or missing obligations: ${staleActiveObligationIds.join(", ")}`);
|
|
252
|
+
}
|
|
253
|
+
function reconcileActiveObligations(agentRoot, resume) {
|
|
254
|
+
const obligations = (0, obligations_1.readVerifiedObligations)(agentRoot);
|
|
255
|
+
const activeObligations = (0, obligations_1.readVerifiedPendingObligations)(agentRoot);
|
|
256
|
+
const canonicalIdSet = new Set(obligations.map((obligation) => obligation.id));
|
|
257
|
+
const openIdSet = new Set(activeObligations.map((obligation) => obligation.id));
|
|
258
|
+
const resumeIdSet = new Set(resume.activeObligationIds);
|
|
259
|
+
const staleActiveObligationIds = resume.activeObligationIds.filter((id) => canonicalIdSet.has(id) && !openIdSet.has(id));
|
|
260
|
+
const unverifiableActiveObligationIds = resume.activeObligationIds.filter((id) => !canonicalIdSet.has(id));
|
|
261
|
+
const missingActiveObligationIds = activeObligations
|
|
262
|
+
.map((obligation) => obligation.id)
|
|
263
|
+
.filter((id) => !resumeIdSet.has(id));
|
|
264
|
+
if (staleActiveObligationIds.length === 0
|
|
265
|
+
&& missingActiveObligationIds.length === 0
|
|
266
|
+
&& unverifiableActiveObligationIds.length === 0) {
|
|
267
|
+
return { resume, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds };
|
|
268
|
+
}
|
|
269
|
+
const activeObligationIds = uniqueStrings([
|
|
270
|
+
...resume.activeObligationIds.filter((id) => !staleActiveObligationIds.includes(id)),
|
|
271
|
+
...missingActiveObligationIds,
|
|
272
|
+
]);
|
|
273
|
+
const hasActiveArcContinuation = activeObligationIds.length > 0
|
|
274
|
+
|| resume.activeReturnObligationIds.length > 0
|
|
275
|
+
|| resume.activePacketIds.length > 0
|
|
276
|
+
|| resume.openEvolutionCaseIds.length > 0;
|
|
277
|
+
const isTerminalWait = staleActiveObligationIds.length > 0 && !hasActiveArcContinuation;
|
|
278
|
+
const mustSynthesizeAction = staleActiveObligationIds.length > 0
|
|
279
|
+
|| unverifiableActiveObligationIds.length > 0
|
|
280
|
+
|| !nonEmpty(resume.nextSafeAction.value);
|
|
281
|
+
const nextSafeActionValue = mustSynthesizeAction
|
|
282
|
+
? nextSafeActionAfterObligationReconcile(resume, activeObligations, staleActiveObligationIds, unverifiableActiveObligationIds)
|
|
283
|
+
: resume.nextSafeAction.value;
|
|
284
|
+
const recorderHealth = unverifiableActiveObligationIds.length > 0
|
|
285
|
+
? {
|
|
286
|
+
status: resume.recorderHealth.status === "unavailable" ? "unavailable" : "degraded",
|
|
287
|
+
issues: uniqueStrings([
|
|
288
|
+
...resume.recorderHealth.issues,
|
|
289
|
+
`active obligation ids could not be verified in arc/obligations: ${unverifiableActiveObligationIds.join(", ")}`,
|
|
290
|
+
]),
|
|
291
|
+
}
|
|
292
|
+
: resume.recorderHealth;
|
|
293
|
+
const canContinue = nonEmpty(resume.currentAsk.value)
|
|
294
|
+
&& nonEmpty(nextSafeActionValue)
|
|
295
|
+
&& resume.blockedBecause.length === 0
|
|
296
|
+
&& recorderHealth.status === "ok"
|
|
297
|
+
&& hasActiveArcContinuation
|
|
298
|
+
&& !isTerminalWait;
|
|
299
|
+
return {
|
|
300
|
+
resume: normalizeResumeInvariants({
|
|
301
|
+
...resume,
|
|
302
|
+
canContinue,
|
|
303
|
+
recorderHealth,
|
|
304
|
+
activeObligationIds,
|
|
305
|
+
nextSafeAction: {
|
|
306
|
+
...resume.nextSafeAction,
|
|
307
|
+
value: nextSafeActionValue,
|
|
308
|
+
sourceEventIds: mustSynthesizeAction
|
|
309
|
+
? [FLIGHT_RECORDER_RECONCILE_SOURCE_ID]
|
|
310
|
+
: resume.nextSafeAction.sourceEventIds,
|
|
311
|
+
stopBefore: unverifiableActiveObligationIds.length > 0
|
|
312
|
+
? uniqueStrings([...resume.nextSafeAction.stopBefore, "acting on unverifiable obligation state"])
|
|
313
|
+
: resume.nextSafeAction.stopBefore,
|
|
314
|
+
},
|
|
315
|
+
}),
|
|
316
|
+
staleActiveObligationIds,
|
|
317
|
+
missingActiveObligationIds,
|
|
318
|
+
unverifiableActiveObligationIds,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function normalizeResumeForAgentRoot(agentRoot, resume) {
|
|
322
|
+
return reconcileActiveObligations(agentRoot, normalizeResumeInvariants(resume));
|
|
323
|
+
}
|
|
324
|
+
function emitFlightRecorderReconciled(agentRoot, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds) {
|
|
325
|
+
if (staleActiveObligationIds.length === 0
|
|
326
|
+
&& missingActiveObligationIds.length === 0
|
|
327
|
+
&& unverifiableActiveObligationIds.length === 0)
|
|
328
|
+
return;
|
|
329
|
+
(0, runtime_1.emitNervesEvent)({
|
|
330
|
+
component: "mind",
|
|
331
|
+
event: "mind.flight_recorder_resume_reconciled",
|
|
332
|
+
message: "flight recorder resume reconciled with canonical Arc state",
|
|
333
|
+
meta: {
|
|
334
|
+
agentRoot,
|
|
335
|
+
staleActiveObligationIds,
|
|
336
|
+
missingActiveObligationIds,
|
|
337
|
+
unverifiableActiveObligationIds,
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
}
|
|
230
341
|
function latestFromEvent(event, previous) {
|
|
231
342
|
const currentAskValue = event.currentAsk !== undefined ? event.currentAsk : previous.currentAsk.value;
|
|
232
343
|
const nextSafeActionValue = event.nextSafeAction !== undefined ? event.nextSafeAction : previous.nextSafeAction.value;
|
|
@@ -279,7 +390,12 @@ function readFlightRecorderResume(agentRoot) {
|
|
|
279
390
|
if (!isFlightRecorderResume(parsed)) {
|
|
280
391
|
throw new Error("latest.json has invalid flight-recorder resume shape");
|
|
281
392
|
}
|
|
282
|
-
const resume =
|
|
393
|
+
const { resume, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds, } = normalizeResumeForAgentRoot(agentRoot, parsed);
|
|
394
|
+
if (staleActiveObligationIds.length > 0
|
|
395
|
+
|| missingActiveObligationIds.length > 0
|
|
396
|
+
|| unverifiableActiveObligationIds.length > 0) {
|
|
397
|
+
emitFlightRecorderReconciled(agentRoot, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds);
|
|
398
|
+
}
|
|
283
399
|
(0, runtime_1.emitNervesEvent)({
|
|
284
400
|
component: "mind",
|
|
285
401
|
event: "mind.flight_recorder_resume_read",
|
|
@@ -302,8 +418,9 @@ function readFlightRecorderResume(agentRoot) {
|
|
|
302
418
|
}
|
|
303
419
|
}
|
|
304
420
|
function writeFlightRecorderResume(agentRoot, resume) {
|
|
305
|
-
const safeResume =
|
|
421
|
+
const { resume: safeResume, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds, } = normalizeResumeForAgentRoot(agentRoot, resume);
|
|
306
422
|
atomicWriteJson(flightRecorderLatestPath(agentRoot), safeResume);
|
|
423
|
+
emitFlightRecorderReconciled(agentRoot, staleActiveObligationIds, missingActiveObligationIds, unverifiableActiveObligationIds);
|
|
307
424
|
(0, runtime_1.emitNervesEvent)({
|
|
308
425
|
component: "mind",
|
|
309
426
|
event: "mind.flight_recorder_resume_written",
|
|
@@ -397,6 +514,52 @@ function isHabitToolPolicy(value) {
|
|
|
397
514
|
&& isStringArray(value.deniedTools)
|
|
398
515
|
&& typeof value.outwardMessagingAllowed === "boolean";
|
|
399
516
|
}
|
|
517
|
+
function defaultHabitRunSummarySnapshot(receipt) {
|
|
518
|
+
if (receipt.errors.length > 0) {
|
|
519
|
+
return {
|
|
520
|
+
summary: `Habit ${receipt.habitName} finished with errors: ${receipt.errors.join("; ")}`,
|
|
521
|
+
decisions: [],
|
|
522
|
+
nextLikelyStep: null,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
const surface = receipt.surfaceAttempts.find((attempt) => attempt.result !== "blocked" && attempt.result !== "failed" && attempt.result !== "unavailable");
|
|
526
|
+
if (surface) {
|
|
527
|
+
return {
|
|
528
|
+
summary: `Habit ${receipt.habitName} surfaced via ${surface.recipient}/${surface.channel}.`,
|
|
529
|
+
decisions: [],
|
|
530
|
+
nextLikelyStep: null,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
const produced = receipt.producedRefs.find((ref) => ref.kind !== "none");
|
|
534
|
+
if (produced) {
|
|
535
|
+
return {
|
|
536
|
+
summary: `Habit ${receipt.habitName} produced ${produced.kind}: ${produced.locator}.`,
|
|
537
|
+
decisions: [],
|
|
538
|
+
nextLikelyStep: null,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
summary: `Habit ${receipt.habitName} finished with ${receipt.outcome}.`,
|
|
543
|
+
decisions: [],
|
|
544
|
+
nextLikelyStep: null,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
function normalizeHabitRunSummarySnapshot(value, fallback) {
|
|
548
|
+
const snapshot = isPlainRecord(value) ? value : {};
|
|
549
|
+
const summary = typeof snapshot.summary === "string" && snapshot.summary.trim().length > 0
|
|
550
|
+
? snapshot.summary
|
|
551
|
+
: fallback.summary;
|
|
552
|
+
const nextLikelyStep = snapshot.nextLikelyStep === null
|
|
553
|
+
? null
|
|
554
|
+
: typeof snapshot.nextLikelyStep === "string" && snapshot.nextLikelyStep.trim().length > 0
|
|
555
|
+
? snapshot.nextLikelyStep
|
|
556
|
+
: fallback.nextLikelyStep;
|
|
557
|
+
return {
|
|
558
|
+
summary: (0, session_events_1.capStructuredRecordString)(summary),
|
|
559
|
+
decisions: cappedArray(isStringArray(snapshot.decisions) ? snapshot.decisions : fallback.decisions),
|
|
560
|
+
nextLikelyStep: nextLikelyStep === null ? null : (0, session_events_1.capStructuredRecordString)(nextLikelyStep),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
400
563
|
function isHabitRunReceipt(value) {
|
|
401
564
|
if (!isPlainRecord(value))
|
|
402
565
|
return false;
|
|
@@ -423,9 +586,11 @@ function isHabitRunReceipt(value) {
|
|
|
423
586
|
&& typeof value.pendingLocator === "string"
|
|
424
587
|
&& typeof value.runtimeStateLocator === "string"
|
|
425
588
|
&& typeof value.receiptLocator === "string"
|
|
589
|
+
&& (value.operationId === undefined || value.operationId === null || typeof value.operationId === "string")
|
|
426
590
|
&& (value.nextRunAt === null || typeof value.nextRunAt === "string")
|
|
427
591
|
&& isHabitPermissionEnvelope(value.permissionEnvelope)
|
|
428
592
|
&& isHabitToolPolicy(value.toolPolicy)
|
|
593
|
+
&& (value.summarySnapshot === undefined || isPlainRecord(value.summarySnapshot))
|
|
429
594
|
&& isProducedRefArray(value.producedRefs)
|
|
430
595
|
&& isHabitSurfaceAttemptArray(value.surfaceAttempts)
|
|
431
596
|
&& isStringArray(value.errors);
|
|
@@ -479,6 +644,7 @@ function normalizeLegacyHabitRunReceipt(receipt) {
|
|
|
479
644
|
pendingLocator: `state/habit-sessions/${receipt.runId}/pending`,
|
|
480
645
|
runtimeStateLocator: `state/habits/${receipt.habitName}.json`,
|
|
481
646
|
receiptLocator: `arc/flight-recorder/habit-receipts/${receipt.runId}.json`,
|
|
647
|
+
operationId: null,
|
|
482
648
|
nextRunAt: null,
|
|
483
649
|
permissionEnvelope: {
|
|
484
650
|
schemaVersion: 1,
|
|
@@ -493,12 +659,14 @@ function normalizeLegacyHabitRunReceipt(receipt) {
|
|
|
493
659
|
deniedTools: sawSurface ? [] : ["send_message", "surface"],
|
|
494
660
|
outwardMessagingAllowed: sawSurface,
|
|
495
661
|
},
|
|
662
|
+
summarySnapshot: defaultHabitRunSummarySnapshot(receipt),
|
|
496
663
|
producedRefs: receipt.producedRefs,
|
|
497
664
|
surfaceAttempts: receipt.surfaceAttempts,
|
|
498
665
|
errors: receipt.errors,
|
|
499
666
|
};
|
|
500
667
|
}
|
|
501
668
|
function capHabitRunReceipt(receipt) {
|
|
669
|
+
const fallbackSnapshot = defaultHabitRunSummarySnapshot(receipt);
|
|
502
670
|
return {
|
|
503
671
|
...receipt,
|
|
504
672
|
habitName: (0, session_events_1.capStructuredRecordString)(receipt.habitName),
|
|
@@ -507,6 +675,7 @@ function capHabitRunReceipt(receipt) {
|
|
|
507
675
|
pendingLocator: (0, session_events_1.capStructuredRecordString)(receipt.pendingLocator),
|
|
508
676
|
runtimeStateLocator: (0, session_events_1.capStructuredRecordString)(receipt.runtimeStateLocator),
|
|
509
677
|
receiptLocator: (0, session_events_1.capStructuredRecordString)(receipt.receiptLocator),
|
|
678
|
+
operationId: receipt.operationId ? (0, session_events_1.capStructuredRecordString)(receipt.operationId) : null,
|
|
510
679
|
permissionEnvelope: {
|
|
511
680
|
...receipt.permissionEnvelope,
|
|
512
681
|
returnRoutes: receipt.permissionEnvelope.returnRoutes.map((route) => ({
|
|
@@ -526,6 +695,7 @@ function capHabitRunReceipt(receipt) {
|
|
|
526
695
|
deniedTools: cappedArray(receipt.toolPolicy.deniedTools),
|
|
527
696
|
outwardMessagingAllowed: receipt.toolPolicy.outwardMessagingAllowed,
|
|
528
697
|
},
|
|
698
|
+
summarySnapshot: normalizeHabitRunSummarySnapshot(receipt.summarySnapshot, fallbackSnapshot),
|
|
529
699
|
producedRefs: receipt.producedRefs.map((ref) => ({ ...ref, locator: (0, session_events_1.capStructuredRecordString)(ref.locator) })),
|
|
530
700
|
surfaceAttempts: receipt.surfaceAttempts.map((attempt) => ({
|
|
531
701
|
...attempt,
|
|
@@ -562,7 +732,7 @@ function readHabitRunReceipt(agentRoot, runId) {
|
|
|
562
732
|
message: "flight recorder habit receipt read",
|
|
563
733
|
meta: { agentRoot, runId },
|
|
564
734
|
});
|
|
565
|
-
return receipt;
|
|
735
|
+
return capHabitRunReceipt(receipt);
|
|
566
736
|
}
|
|
567
737
|
catch (error) {
|
|
568
738
|
warnMalformedHabitReceipt(agentRoot, runId, error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error));
|
|
@@ -593,7 +763,10 @@ function writeHabitRunReceipt(agentRoot, receipt) {
|
|
|
593
763
|
recordedAt: safeReceipt.endedAt,
|
|
594
764
|
summary: `habit ${safeReceipt.habitName} finished with ${safeReceipt.outcome}`,
|
|
595
765
|
producedRefs: safeReceipt.producedRefs,
|
|
596
|
-
meta: {
|
|
766
|
+
meta: {
|
|
767
|
+
receiptPath: path.join("arc", "flight-recorder", "habit-receipts", `${safeReceipt.runId}.json`),
|
|
768
|
+
operationId: safeReceipt.operationId ?? null,
|
|
769
|
+
},
|
|
597
770
|
});
|
|
598
771
|
(0, runtime_1.emitNervesEvent)({
|
|
599
772
|
component: "mind",
|
package/dist/arc/obligations.js
CHANGED
|
@@ -38,6 +38,8 @@ exports.isOpenObligation = isOpenObligation;
|
|
|
38
38
|
exports.createObligation = createObligation;
|
|
39
39
|
exports.readObligations = readObligations;
|
|
40
40
|
exports.readPendingObligations = readPendingObligations;
|
|
41
|
+
exports.readVerifiedObligations = readVerifiedObligations;
|
|
42
|
+
exports.readVerifiedPendingObligations = readVerifiedPendingObligations;
|
|
41
43
|
exports.advanceObligation = advanceObligation;
|
|
42
44
|
exports.fulfillObligation = fulfillObligation;
|
|
43
45
|
exports.findPendingObligationForOrigin = findPendingObligationForOrigin;
|
|
@@ -64,6 +66,31 @@ function isOpenObligationStatus(status) {
|
|
|
64
66
|
function isOpenObligation(obligation) {
|
|
65
67
|
return isOpenObligationStatus(obligation.status);
|
|
66
68
|
}
|
|
69
|
+
function isReadableObligation(value) {
|
|
70
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
71
|
+
return false;
|
|
72
|
+
const obligation = value;
|
|
73
|
+
return typeof obligation.id === "string"
|
|
74
|
+
&& typeof obligation.content === "string";
|
|
75
|
+
}
|
|
76
|
+
function isVerifiedObligationStatus(value) {
|
|
77
|
+
return value === "pending"
|
|
78
|
+
|| value === "investigating"
|
|
79
|
+
|| value === "waiting_for_merge"
|
|
80
|
+
|| value === "updating_runtime"
|
|
81
|
+
|| value === "fulfilled";
|
|
82
|
+
}
|
|
83
|
+
function isVerifiedObligation(value) {
|
|
84
|
+
if (!isReadableObligation(value))
|
|
85
|
+
return false;
|
|
86
|
+
const obligation = value;
|
|
87
|
+
return isVerifiedObligationStatus(obligation.status)
|
|
88
|
+
&& typeof obligation.createdAt === "string"
|
|
89
|
+
&& !!obligation.origin
|
|
90
|
+
&& typeof obligation.origin.friendId === "string"
|
|
91
|
+
&& typeof obligation.origin.channel === "string"
|
|
92
|
+
&& typeof obligation.origin.key === "string";
|
|
93
|
+
}
|
|
67
94
|
function createObligation(agentRoot, input) {
|
|
68
95
|
const now = new Date().toISOString();
|
|
69
96
|
const id = (0, json_store_1.generateTimestampId)();
|
|
@@ -92,11 +119,18 @@ function createObligation(agentRoot, input) {
|
|
|
92
119
|
}
|
|
93
120
|
function readObligations(agentRoot) {
|
|
94
121
|
const all = (0, json_store_1.readJsonDir)(obligationsDir(agentRoot));
|
|
95
|
-
return all.filter(
|
|
122
|
+
return all.filter(isReadableObligation);
|
|
96
123
|
}
|
|
97
124
|
function readPendingObligations(agentRoot) {
|
|
98
125
|
return readObligations(agentRoot).filter(isOpenObligation);
|
|
99
126
|
}
|
|
127
|
+
function readVerifiedObligations(agentRoot) {
|
|
128
|
+
const all = (0, json_store_1.readJsonDir)(obligationsDir(agentRoot));
|
|
129
|
+
return all.filter(isVerifiedObligation);
|
|
130
|
+
}
|
|
131
|
+
function readVerifiedPendingObligations(agentRoot) {
|
|
132
|
+
return readVerifiedObligations(agentRoot).filter(isOpenObligation);
|
|
133
|
+
}
|
|
100
134
|
function advanceObligation(agentRoot, obligationId, update) {
|
|
101
135
|
const dir = obligationsDir(agentRoot);
|
|
102
136
|
const obligation = (0, json_store_1.readJsonFile)(dir, obligationId);
|
|
@@ -96,6 +96,7 @@ const cli_help_1 = require("./cli-help");
|
|
|
96
96
|
const plugin_cli_1 = require("./plugin-cli");
|
|
97
97
|
const cli_desk_1 = require("./cli-desk");
|
|
98
98
|
const migrate_to_desk_1 = require("./migrate-to-desk");
|
|
99
|
+
const core_2 = require("../../nerves/review/core");
|
|
99
100
|
const cli_render_1 = require("./cli-render");
|
|
100
101
|
const cli_defaults_1 = require("./cli-defaults");
|
|
101
102
|
const agent_config_check_1 = require("./agent-config-check");
|
|
@@ -150,6 +151,58 @@ function returnCliFailure(deps, message, exitCode = 1) {
|
|
|
150
151
|
deps.writeStdout(message);
|
|
151
152
|
return message;
|
|
152
153
|
}
|
|
154
|
+
function renderHabitSummaryCli(summary) {
|
|
155
|
+
return [
|
|
156
|
+
`${summary.runId} habit=${summary.habitName} outcome=${summary.status} completedAt=${summary.completedAt}`,
|
|
157
|
+
summary.operationId ? `operation=${summary.operationId}` : null,
|
|
158
|
+
`summary=${summary.summary}`,
|
|
159
|
+
summary.nextLikelyStep ? `next=${summary.nextLikelyStep}` : null,
|
|
160
|
+
summary.decisions.length > 0 ? `decisions=${summary.decisions.join("; ")}` : null,
|
|
161
|
+
`pending=${summary.pending.count}${summary.pending.files.length > 0 ? ` (${summary.pending.files.join(", ")})` : ""}`,
|
|
162
|
+
`messages=${summary.messagesSent.length}`,
|
|
163
|
+
`tools=${summary.toolsUsed.length > 0 ? summary.toolsUsed.join(",") : "none"}`,
|
|
164
|
+
summary.producedRefs.length > 0 ? `refs=${summary.producedRefs.map((ref) => `${ref.kind}:${ref.locator}`).join(",")}` : null,
|
|
165
|
+
summary.errors.length > 0 ? `errors=${summary.errors.join("; ")}` : null,
|
|
166
|
+
summary.warnings.length > 0 ? `warnings=${summary.warnings.join("; ")}` : null,
|
|
167
|
+
`receipt=${summary.sources.receipt}`,
|
|
168
|
+
`session=${summary.sources.session}`,
|
|
169
|
+
`runtime=${summary.sources.runtimeState}`,
|
|
170
|
+
].filter((line) => Boolean(line)).join("\n");
|
|
171
|
+
}
|
|
172
|
+
function executeNervesReviewCommand(command, deps) {
|
|
173
|
+
let sinceMs;
|
|
174
|
+
if (command.since) {
|
|
175
|
+
const parsed = (0, core_2.parseDuration)(command.since);
|
|
176
|
+
if (parsed === null) {
|
|
177
|
+
const message = `nerves-review: --since '${command.since}' is not a valid duration (e.g. 5m, 2h, 1d)`;
|
|
178
|
+
deps.setExitCode?.(2);
|
|
179
|
+
deps.writeStdout(message);
|
|
180
|
+
return message;
|
|
181
|
+
}
|
|
182
|
+
sinceMs = parsed;
|
|
183
|
+
}
|
|
184
|
+
const logsDir = (0, identity_1.getAgentDaemonLogsDir)(command.agent);
|
|
185
|
+
const filePath = path.join(logsDir, `${command.process}.ndjson`);
|
|
186
|
+
const entries = (0, core_2.reviewNerveEvents)(filePath, {
|
|
187
|
+
componentSubstring: command.component,
|
|
188
|
+
eventSubstring: command.event,
|
|
189
|
+
level: command.level,
|
|
190
|
+
sinceMs,
|
|
191
|
+
limit: command.limit,
|
|
192
|
+
nowMs: Date.now(),
|
|
193
|
+
});
|
|
194
|
+
const message = entries.length === 0
|
|
195
|
+
? `(no matching nerves events in ${filePath})`
|
|
196
|
+
: entries.map((entry) => command.json ? entry.raw : (0, core_2.formatNerveEntry)(entry)).join("\n");
|
|
197
|
+
deps.writeStdout(message);
|
|
198
|
+
(0, runtime_1.emitNervesEvent)({
|
|
199
|
+
component: "daemon",
|
|
200
|
+
event: "daemon.nerves_review_cli_read",
|
|
201
|
+
message: "nerves review CLI read local log events",
|
|
202
|
+
meta: { agent: command.agent, process: command.process, count: entries.length, json: command.json },
|
|
203
|
+
});
|
|
204
|
+
return message;
|
|
205
|
+
}
|
|
153
206
|
function summarizeDaemonStartupFailure(result) {
|
|
154
207
|
if (result.startupFailureReason && result.startupFailureReason.trim().length > 0) {
|
|
155
208
|
return result.startupFailureReason;
|
|
@@ -366,6 +419,8 @@ function agentResolutionFailureMode(command) {
|
|
|
366
419
|
case "habit.create":
|
|
367
420
|
case "habit.runs":
|
|
368
421
|
case "habit.inspect":
|
|
422
|
+
case "habit.summary":
|
|
423
|
+
case "nerves-review":
|
|
369
424
|
case "thoughts":
|
|
370
425
|
case "attention.list":
|
|
371
426
|
case "attention.show":
|
|
@@ -5684,6 +5739,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
5684
5739
|
if (command.kind === "hook") {
|
|
5685
5740
|
await refreshHookSentinel(command, deps);
|
|
5686
5741
|
}
|
|
5742
|
+
if (command.kind === "nerves-review") {
|
|
5743
|
+
return executeNervesReviewCommand(command, deps);
|
|
5744
|
+
}
|
|
5687
5745
|
if (args.length === 0) {
|
|
5688
5746
|
const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : (0, cli_defaults_1.defaultListDiscoveredAgents)());
|
|
5689
5747
|
/* v8 ignore start -- the interactive home shell is exercised extensively in daemon-cli tests; V8 miscounts this orchestrator because it chains through recursive command handoffs and early chat health exits @preserve */
|
|
@@ -6843,10 +6901,11 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
6843
6901
|
return result.summary;
|
|
6844
6902
|
}
|
|
6845
6903
|
// ── habit subcommands (local, no daemon socket needed) ──
|
|
6846
|
-
if (command.kind === "habit.list" || command.kind === "habit.create" || command.kind === "habit.runs" || command.kind === "habit.inspect") {
|
|
6904
|
+
if (command.kind === "habit.list" || command.kind === "habit.create" || command.kind === "habit.runs" || command.kind === "habit.inspect" || command.kind === "habit.summary") {
|
|
6847
6905
|
const { parseHabitFile, renderHabitFile } = await Promise.resolve().then(() => __importStar(require("../habits/habit-parser")));
|
|
6848
6906
|
const { applyHabitRuntimeState } = await Promise.resolve().then(() => __importStar(require("../habits/habit-runtime-state")));
|
|
6849
6907
|
const { listHabitRunReceipts, readHabitRunReceipt } = await Promise.resolve().then(() => __importStar(require("../../arc/flight-recorder")));
|
|
6908
|
+
const { readHabitSessionSummary } = await Promise.resolve().then(() => __importStar(require("../habits/habit-session-summary")));
|
|
6850
6909
|
/* v8 ignore start -- production default: uses real bundle root @preserve */
|
|
6851
6910
|
const bundleRoot = deps.agentBundleRoot ?? path.join(deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
|
|
6852
6911
|
/* v8 ignore stop */
|
|
@@ -6923,6 +6982,36 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
6923
6982
|
});
|
|
6924
6983
|
return message;
|
|
6925
6984
|
}
|
|
6985
|
+
if (command.kind === "habit.summary") {
|
|
6986
|
+
const summary = readHabitSessionSummary(bundleRoot, {
|
|
6987
|
+
...(command.runId ? { runId: command.runId } : {}),
|
|
6988
|
+
...(command.habitName ? { habitName: command.habitName } : {}),
|
|
6989
|
+
...(command.operationId ? { operationId: command.operationId } : {}),
|
|
6990
|
+
...(command.which ? { which: command.which } : {}),
|
|
6991
|
+
});
|
|
6992
|
+
if (!summary) {
|
|
6993
|
+
const message = "error: habit summary not found";
|
|
6994
|
+
deps.writeStdout(message);
|
|
6995
|
+
deps.setExitCode?.(1);
|
|
6996
|
+
(0, runtime_1.emitNervesEvent)({
|
|
6997
|
+
level: "warn",
|
|
6998
|
+
component: "daemon",
|
|
6999
|
+
event: "daemon.habit_summary_cli_read_missing",
|
|
7000
|
+
message: "habit run summary not found from CLI",
|
|
7001
|
+
meta: { agent: command.agent, runId: command.runId, habitName: command.habitName, operationId: command.operationId, which: command.which },
|
|
7002
|
+
});
|
|
7003
|
+
return message;
|
|
7004
|
+
}
|
|
7005
|
+
const message = command.json ? `${JSON.stringify(summary, null, 2)}\n` : renderHabitSummaryCli(summary);
|
|
7006
|
+
deps.writeStdout(message);
|
|
7007
|
+
(0, runtime_1.emitNervesEvent)({
|
|
7008
|
+
component: "daemon",
|
|
7009
|
+
event: "daemon.habit_summary_cli_read",
|
|
7010
|
+
message: "habit run summary read from CLI",
|
|
7011
|
+
meta: { agent: command.agent, runId: summary.runId, habitName: summary.habitName, json: command.json },
|
|
7012
|
+
});
|
|
7013
|
+
return message;
|
|
7014
|
+
}
|
|
6926
7015
|
// habit.create
|
|
6927
7016
|
const filePath = path.join(habitsDir, `${command.name}.md`);
|
|
6928
7017
|
if (fs.existsSync(filePath)) {
|
|
@@ -135,7 +135,7 @@ exports.COMMAND_REGISTRY = {
|
|
|
135
135
|
description: "Manage agent habits",
|
|
136
136
|
usage: "ouro habit <subcommand> [--agent <name>]",
|
|
137
137
|
example: "ouro habit list",
|
|
138
|
-
subcommands: ["list", "create", "poke"],
|
|
138
|
+
subcommands: ["list", "create", "runs", "inspect", "summary", "poke"],
|
|
139
139
|
},
|
|
140
140
|
desk: {
|
|
141
141
|
category: "Tasks",
|
|
@@ -158,6 +158,12 @@ exports.COMMAND_REGISTRY = {
|
|
|
158
158
|
example: "ouro work sentinel --agent slugger --format json",
|
|
159
159
|
subcommands: ["card", "gauntlet", "sentinel"],
|
|
160
160
|
},
|
|
161
|
+
"nerves-review": {
|
|
162
|
+
category: "Internal",
|
|
163
|
+
description: "Read-only review of recent nerves events from an agent log stream",
|
|
164
|
+
usage: "ouro nerves-review [--agent <name>] [--process <name>] [--component <substr>] [--event <substr>] [--level <level>] [--since <duration>] [--limit <n>] [--json]",
|
|
165
|
+
example: "ouro nerves-review --agent slugger --component daemon --event habit --since 30m --json",
|
|
166
|
+
},
|
|
161
167
|
"work card": {
|
|
162
168
|
category: "Tasks",
|
|
163
169
|
description: "Show the agent's durable Work Card compiled from arc records.",
|
|
@@ -395,6 +401,11 @@ const SUBCOMMAND_HELP = {
|
|
|
395
401
|
usage: "ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
|
|
396
402
|
example: "ouro account ensure --agent <agent> --owner-email you@example.com --source hey",
|
|
397
403
|
},
|
|
404
|
+
"habit summary": {
|
|
405
|
+
description: "Read a habit run summary from receipts and session artifacts without contacting the daemon",
|
|
406
|
+
usage: "ouro habit summary [--agent <name>] (--run-id <id>|--habit <name>|--operation-id <id>) [--which latest|previous|latest-success|latest-failure] [--json]",
|
|
407
|
+
example: "ouro habit summary --agent slugger --operation-id habit:standup --which latest --json",
|
|
408
|
+
},
|
|
398
409
|
"mail import-mbox": {
|
|
399
410
|
description: "Import a HEY or other MBOX export into an existing delegated Mailroom source grant",
|
|
400
411
|
usage: "ouro mail import-mbox (--file <path>|--discover) [--owner-email <email>] [--source <label>] [--agent <name>] [--foreground]",
|
|
@@ -110,6 +110,7 @@ function usage() {
|
|
|
110
110
|
" ouro habit create [--agent <name>] <name> [--cadence <interval>]",
|
|
111
111
|
" ouro habit runs [--agent <name>] [--limit <n>]",
|
|
112
112
|
" ouro habit inspect [--agent <name>] <runId>",
|
|
113
|
+
" ouro habit summary [--agent <name>] (--run-id <id>|--habit <name>|--operation-id <id>) [--which latest|previous|latest-success|latest-failure] [--json]",
|
|
113
114
|
" ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
|
|
114
115
|
" ouro bluebubbles replay [--agent <name>] --message-guid <guid> [--event-type new-message|updated-message] [--json]",
|
|
115
116
|
" ouro friend list [--agent <name>]",
|
|
@@ -118,6 +119,7 @@ function usage() {
|
|
|
118
119
|
" ouro friend update <id> --trust <level> [--agent <name>]",
|
|
119
120
|
" ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
|
|
120
121
|
" ouro work card|gauntlet|sentinel [refresh] [--agent <name>] [--format text|json|--json]",
|
|
122
|
+
" ouro nerves-review [--agent <name>] [--process <name>] [--component <substr>] [--event <substr>] [--level <level>] [--since <duration>] [--limit <n>] [--json]",
|
|
121
123
|
" ouro inner [--agent <name>]",
|
|
122
124
|
" ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
123
125
|
" ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
@@ -261,6 +263,53 @@ function parseHabitCommand(args) {
|
|
|
261
263
|
throw new Error(`Usage\n${usage()}`);
|
|
262
264
|
return { kind: "habit.inspect", ...(agent ? { agent } : {}), runId: positional[0] };
|
|
263
265
|
}
|
|
266
|
+
if (sub === "summary") {
|
|
267
|
+
let runId;
|
|
268
|
+
let habitName;
|
|
269
|
+
let operationId;
|
|
270
|
+
let which;
|
|
271
|
+
let json = false;
|
|
272
|
+
const options = rest.slice(1);
|
|
273
|
+
for (let i = 0; i < options.length; i += 1) {
|
|
274
|
+
const option = options[i];
|
|
275
|
+
if (option === "--json") {
|
|
276
|
+
json = true;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if ((option === "--run-id" || option === "--habit" || option === "--operation-id" || option === "--which") && options[i + 1]) {
|
|
280
|
+
const value = options[++i];
|
|
281
|
+
if (option === "--run-id")
|
|
282
|
+
runId = value;
|
|
283
|
+
if (option === "--habit")
|
|
284
|
+
habitName = value;
|
|
285
|
+
if (option === "--operation-id")
|
|
286
|
+
operationId = value;
|
|
287
|
+
if (option === "--which") {
|
|
288
|
+
if (!["latest", "previous", "latest-success", "latest-failure"].includes(value)) {
|
|
289
|
+
throw new Error("--which must be latest, previous, latest-success, or latest-failure");
|
|
290
|
+
}
|
|
291
|
+
which = value;
|
|
292
|
+
}
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
throw new Error(`Usage\n${usage()}`);
|
|
296
|
+
}
|
|
297
|
+
if (runId !== undefined && (habitName !== undefined || operationId !== undefined || which !== undefined)) {
|
|
298
|
+
throw new Error("--run-id cannot be combined with --habit, --operation-id, or --which");
|
|
299
|
+
}
|
|
300
|
+
if (runId === undefined && habitName === undefined && operationId === undefined) {
|
|
301
|
+
throw new Error("provide --run-id, --habit, or --operation-id");
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
kind: "habit.summary",
|
|
305
|
+
...(agent ? { agent } : {}),
|
|
306
|
+
...(runId ? { runId } : {}),
|
|
307
|
+
...(habitName ? { habitName } : {}),
|
|
308
|
+
...(operationId ? { operationId } : {}),
|
|
309
|
+
...(which ? { which } : {}),
|
|
310
|
+
json,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
264
313
|
throw new Error(`Usage\n${usage()}`);
|
|
265
314
|
}
|
|
266
315
|
function parseLinkCommand(args, kind = "friend.link") {
|
|
@@ -1537,6 +1586,57 @@ function parseBlueBubblesCommand(args) {
|
|
|
1537
1586
|
...(json ? { json: true } : {}),
|
|
1538
1587
|
};
|
|
1539
1588
|
}
|
|
1589
|
+
function parseNervesReviewCommand(args) {
|
|
1590
|
+
const { agent, rest } = extractAgentFlag(args);
|
|
1591
|
+
let processName = "daemon";
|
|
1592
|
+
let component;
|
|
1593
|
+
let event;
|
|
1594
|
+
let level;
|
|
1595
|
+
let since;
|
|
1596
|
+
let limit;
|
|
1597
|
+
let json = false;
|
|
1598
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
1599
|
+
const token = rest[i];
|
|
1600
|
+
const next = rest[i + 1];
|
|
1601
|
+
if (token === "--json") {
|
|
1602
|
+
json = true;
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
if ((token === "--process" || token === "--component" || token === "--event" || token === "--level" || token === "--since" || token === "--limit") && next) {
|
|
1606
|
+
if (token === "--process")
|
|
1607
|
+
processName = next;
|
|
1608
|
+
if (token === "--component")
|
|
1609
|
+
component = next;
|
|
1610
|
+
if (token === "--event")
|
|
1611
|
+
event = next;
|
|
1612
|
+
if (token === "--level")
|
|
1613
|
+
level = next;
|
|
1614
|
+
if (token === "--since")
|
|
1615
|
+
since = next;
|
|
1616
|
+
if (token === "--limit") {
|
|
1617
|
+
const parsed = Number.parseInt(next, 10);
|
|
1618
|
+
if (!Number.isInteger(parsed) || String(parsed) !== next || parsed < 1 || parsed > 1000) {
|
|
1619
|
+
throw new Error("--limit must be an integer between 1 and 1000");
|
|
1620
|
+
}
|
|
1621
|
+
limit = parsed;
|
|
1622
|
+
}
|
|
1623
|
+
i += 1;
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
throw new Error(`Usage\n${usage()}`);
|
|
1627
|
+
}
|
|
1628
|
+
return {
|
|
1629
|
+
kind: "nerves-review",
|
|
1630
|
+
...(agent ? { agent } : {}),
|
|
1631
|
+
process: processName,
|
|
1632
|
+
...(component ? { component } : {}),
|
|
1633
|
+
...(event ? { event } : {}),
|
|
1634
|
+
...(level ? { level } : {}),
|
|
1635
|
+
...(since ? { since } : {}),
|
|
1636
|
+
...(limit ? { limit } : {}),
|
|
1637
|
+
json,
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1540
1640
|
// ── Main dispatch ──
|
|
1541
1641
|
function parseOuroCommand(args) {
|
|
1542
1642
|
const [head, second] = args;
|
|
@@ -1704,6 +1804,8 @@ function parseOuroCommand(args) {
|
|
|
1704
1804
|
return parseMessageCommand(args.slice(1));
|
|
1705
1805
|
if (head === "poke")
|
|
1706
1806
|
return parsePokeCommand(args.slice(1));
|
|
1807
|
+
if (head === "nerves-review")
|
|
1808
|
+
return parseNervesReviewCommand(args.slice(1));
|
|
1707
1809
|
if (head === "link")
|
|
1708
1810
|
return parseLinkCommand(args.slice(1));
|
|
1709
1811
|
if (head === "mcp-serve")
|