@ouro.bot/cli 0.1.0-alpha.657 → 0.1.0-alpha.658
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/README.md +13 -13
- package/changelog.json +9 -0
- package/dist/arc/evolution.js +1 -1
- package/dist/arc/flight-recorder.js +369 -0
- package/dist/arc/obligations.js +24 -2
- package/dist/heart/active-work.js +1 -1
- package/dist/heart/config-registry.js +5 -5
- package/dist/heart/daemon/agent-config-check.js +1 -1
- package/dist/heart/daemon/agent-service.js +18 -17
- package/dist/heart/daemon/cli-exec.js +27 -12
- package/dist/heart/daemon/cli-help.js +14 -0
- package/dist/heart/daemon/cli-parse.js +26 -0
- package/dist/heart/daemon/daemon-entry.js +1 -1
- package/dist/heart/daemon/daemon.js +3 -3
- package/dist/heart/daemon/hooks/bundle-meta.js +29 -9
- package/dist/heart/daemon/inner-status.js +4 -15
- package/dist/heart/habits/habit-parser.js +64 -1
- package/dist/heart/hatch/hatch-flow.js +17 -9
- package/dist/heart/hatch/specialist-tools.js +15 -11
- package/dist/heart/kept-notes.js +5 -73
- package/dist/heart/mailbox/readers/runtime-readers.js +21 -49
- package/dist/heart/mcp/mcp-server.js +8 -8
- package/dist/heart/session-events.js +1 -31
- package/dist/heart/start-of-turn-packet.js +8 -2
- package/dist/heart/tool-description.js +15 -3
- package/dist/heart/turn-context.js +27 -7
- package/dist/heart/work-card.js +386 -0
- package/dist/mailbox-ui/assets/{index-9-AxCxuB.js → index-Cbasiy6y.js} +1 -1
- package/dist/mailbox-ui/index.html +1 -1
- package/dist/mind/bundle-manifest.js +9 -3
- package/dist/mind/context.js +1 -2
- package/dist/mind/desk-section.js +53 -1
- package/dist/mind/diary.js +2 -3
- package/dist/mind/note-search.js +36 -106
- package/dist/mind/prompt.js +37 -102
- package/dist/mind/record-paths.js +312 -0
- package/dist/repertoire/bundle-templates.js +4 -5
- package/dist/repertoire/tools-bundle.js +1 -1
- package/dist/repertoire/tools-evolution.js +4 -4
- package/dist/repertoire/tools-notes.js +42 -62
- package/dist/repertoire/tools-record.js +16 -11
- package/dist/repertoire/tools-session.js +4 -4
- package/dist/repertoire/tools.js +1 -1
- package/dist/senses/habit-turn-message.js +19 -5
- package/dist/senses/inner-dialog-worker.js +58 -9
- package/dist/senses/inner-dialog.js +30 -11
- package/dist/senses/pipeline.js +135 -1
- package/dist/util/frontmatter.js +17 -1
- package/package.json +3 -3
- package/skills/configure-dev-tools.md +1 -1
- package/skills/travel-planning.md +1 -1
- package/dist/mind/journal-index.js +0 -162
|
@@ -11,8 +11,9 @@ function formatElapsed(ms) {
|
|
|
11
11
|
return `${hours} ${hours === 1 ? "hour" : "hours"}`;
|
|
12
12
|
}
|
|
13
13
|
function buildHabitTurnMessage(options) {
|
|
14
|
-
const { habitName, habitTitle, habitBody, lastRun, checkpoint, alsoDue, staleObligations, parseErrors, degradedComponents, now, } = options;
|
|
14
|
+
const { habitName, habitTitle, habitBody, lastRun, checkpoint, alsoDue, staleObligations, parseErrors, degradedComponents, arcResume, deskOrientation, surfacePolicy, now, } = options;
|
|
15
15
|
const hasBody = habitBody !== undefined && habitBody !== "";
|
|
16
|
+
const leadingSections = buildLeadingSections(arcResume, deskOrientation, surfacePolicy);
|
|
16
17
|
// First beat: lastRun is null
|
|
17
18
|
if (lastRun === null) {
|
|
18
19
|
// Cold start: no checkpoint, no body — bare awareness
|
|
@@ -23,7 +24,7 @@ function buildHabitTurnMessage(options) {
|
|
|
23
24
|
message: "habit turn message built (cold start)",
|
|
24
25
|
meta: { habitName, coldStart: true },
|
|
25
26
|
});
|
|
26
|
-
return "...time passing. anything stirring?";
|
|
27
|
+
return joinSections(leadingSections, ["...time passing. anything stirring?"]);
|
|
27
28
|
}
|
|
28
29
|
if (!hasBody) {
|
|
29
30
|
// First beat with no body: nudge
|
|
@@ -37,7 +38,7 @@ function buildHabitTurnMessage(options) {
|
|
|
37
38
|
message: "habit turn message built (first beat, no body)",
|
|
38
39
|
meta: { habitName, firstBeat: true, hasBody: false },
|
|
39
40
|
});
|
|
40
|
-
return sections
|
|
41
|
+
return joinSections(leadingSections, sections);
|
|
41
42
|
}
|
|
42
43
|
const sections = [
|
|
43
44
|
`your ${habitTitle} is alive. this is its first breath.`,
|
|
@@ -50,7 +51,7 @@ function buildHabitTurnMessage(options) {
|
|
|
50
51
|
message: "habit turn message built (first beat)",
|
|
51
52
|
meta: { habitName, firstBeat: true },
|
|
52
53
|
});
|
|
53
|
-
return sections
|
|
54
|
+
return joinSections(leadingSections, sections);
|
|
54
55
|
}
|
|
55
56
|
// Normal turn
|
|
56
57
|
const sections = [];
|
|
@@ -83,7 +84,20 @@ function buildHabitTurnMessage(options) {
|
|
|
83
84
|
staleObligationCount: staleObligations.length,
|
|
84
85
|
},
|
|
85
86
|
});
|
|
86
|
-
return sections
|
|
87
|
+
return joinSections(leadingSections, sections);
|
|
88
|
+
}
|
|
89
|
+
function buildLeadingSections(arcResume, deskOrientation, surfacePolicy) {
|
|
90
|
+
const sections = [];
|
|
91
|
+
if (arcResume?.trim())
|
|
92
|
+
sections.push(arcResume.trim());
|
|
93
|
+
if (deskOrientation?.trim())
|
|
94
|
+
sections.push(deskOrientation.trim());
|
|
95
|
+
if (surfacePolicy?.trim())
|
|
96
|
+
sections.push(surfacePolicy.trim());
|
|
97
|
+
return sections;
|
|
98
|
+
}
|
|
99
|
+
function joinSections(leadingSections, sections) {
|
|
100
|
+
return [...leadingSections, ...sections].filter((section) => section.trim().length > 0).join("\n\n");
|
|
87
101
|
}
|
|
88
102
|
function appendTrailingExtras(sections, alsoDue, staleObligations, parseErrors, degradedComponents) {
|
|
89
103
|
// 4. Also-due
|
|
@@ -42,6 +42,7 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
42
42
|
const identity_1 = require("../heart/identity");
|
|
43
43
|
const pending_1 = require("../mind/pending");
|
|
44
44
|
const habit_runtime_state_1 = require("../heart/habits/habit-runtime-state");
|
|
45
|
+
const flight_recorder_1 = require("../arc/flight-recorder");
|
|
45
46
|
/**
|
|
46
47
|
* Cap on consecutive `instinct` follow-on turns triggered by `hasPendingWork()`
|
|
47
48
|
* with no externally-queued work in between. Without this cap, a turn that
|
|
@@ -88,12 +89,52 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
88
89
|
const lastFireByHabit = new Map();
|
|
89
90
|
const recentHabitFires = [];
|
|
90
91
|
let heartbeatOkRestedAt = null;
|
|
91
|
-
function
|
|
92
|
+
function habitOutcomeForTurn(result, errors) {
|
|
93
|
+
if (errors.length > 0)
|
|
94
|
+
return { outcome: "error", producedRefs: [] };
|
|
95
|
+
const toolNames = new Set();
|
|
96
|
+
if (result && typeof result === "object" && Array.isArray(result.messages)) {
|
|
97
|
+
for (const message of result.messages) {
|
|
98
|
+
const toolCalls = message.tool_calls;
|
|
99
|
+
if (!Array.isArray(toolCalls))
|
|
100
|
+
continue;
|
|
101
|
+
for (const call of toolCalls) {
|
|
102
|
+
const functionName = call.function?.name;
|
|
103
|
+
if (typeof functionName === "string")
|
|
104
|
+
toolNames.add(functionName);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (toolNames.has("send_message") || toolNames.has("surface")) {
|
|
109
|
+
return { outcome: "surfaced", producedRefs: [{ kind: "surface", locator: "tool:send_message_or_surface" }] };
|
|
110
|
+
}
|
|
111
|
+
if (toolNames.has("diary_write") || toolNames.has("note")) {
|
|
112
|
+
return { outcome: "wrote_record", producedRefs: [{ kind: "desk_record", locator: "desk/_record" }] };
|
|
113
|
+
}
|
|
114
|
+
if ([...toolNames].some((name) => name.startsWith("mcp__desk__"))) {
|
|
115
|
+
return { outcome: "updated_desk", producedRefs: [{ kind: "desk_task", locator: "desk/" }] };
|
|
116
|
+
}
|
|
117
|
+
return { outcome: "no_change", producedRefs: [] };
|
|
118
|
+
}
|
|
119
|
+
function recordHabitCompletion(habitName, startedAt = new Date(nowSource()).toISOString(), endedAt = startedAt, trigger = "overdue", result, errors = []) {
|
|
92
120
|
try {
|
|
93
121
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
94
|
-
(0, habit_runtime_state_1.recordHabitRun)(agentRoot, habitName,
|
|
122
|
+
(0, habit_runtime_state_1.recordHabitRun)(agentRoot, habitName, endedAt, {
|
|
95
123
|
definitionPath: path.join(agentRoot, "habits", `${habitName}.md`),
|
|
96
124
|
});
|
|
125
|
+
const { outcome, producedRefs } = habitOutcomeForTurn(result, errors);
|
|
126
|
+
(0, flight_recorder_1.writeHabitRunReceipt)(agentRoot, {
|
|
127
|
+
schemaVersion: 1,
|
|
128
|
+
runId: (0, flight_recorder_1.createHabitRunId)(habitName, new Date(startedAt)),
|
|
129
|
+
habitName,
|
|
130
|
+
trigger,
|
|
131
|
+
startedAt,
|
|
132
|
+
endedAt,
|
|
133
|
+
outcome,
|
|
134
|
+
producedRefs,
|
|
135
|
+
surfaceAttempts: [],
|
|
136
|
+
errors,
|
|
137
|
+
});
|
|
97
138
|
}
|
|
98
139
|
catch {
|
|
99
140
|
// Habit file/state may be unavailable during the turn — skip gracefully
|
|
@@ -114,7 +155,8 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
114
155
|
return true;
|
|
115
156
|
}
|
|
116
157
|
function reuseHeartbeatOkRest(habitName) {
|
|
117
|
-
|
|
158
|
+
const nowIso = new Date(nowSource()).toISOString();
|
|
159
|
+
recordHabitCompletion(habitName, nowIso, nowIso, "overdue", { turnOutcome: "rested", restStatus: "HEARTBEAT_OK" });
|
|
118
160
|
(0, runtime_1.emitNervesEvent)({
|
|
119
161
|
level: "info",
|
|
120
162
|
component: "senses",
|
|
@@ -166,9 +208,9 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
166
208
|
});
|
|
167
209
|
}
|
|
168
210
|
}
|
|
169
|
-
async function run(reason, taskId, habitName, awaitName) {
|
|
211
|
+
async function run(reason, taskId, habitName, awaitName, trigger) {
|
|
170
212
|
if (running) {
|
|
171
|
-
queue.push({ reason, taskId, habitName, awaitName });
|
|
213
|
+
queue.push({ reason, taskId, habitName, awaitName, trigger });
|
|
172
214
|
return;
|
|
173
215
|
}
|
|
174
216
|
running = true;
|
|
@@ -177,10 +219,14 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
177
219
|
let nextTaskId = taskId;
|
|
178
220
|
let nextHabitName = habitName;
|
|
179
221
|
let nextAwaitName = awaitName;
|
|
222
|
+
let nextTrigger = trigger;
|
|
180
223
|
let consecutiveInstinctTurns = reason === "instinct" ? 1 : 0;
|
|
181
224
|
runLoop: do {
|
|
182
225
|
const currentReason = nextReason;
|
|
183
226
|
const currentHabitName = nextHabitName;
|
|
227
|
+
const currentTrigger = nextTrigger ?? "overdue";
|
|
228
|
+
const habitStartedAt = currentReason === "habit" && currentHabitName ? new Date(nowSource()).toISOString() : null;
|
|
229
|
+
const turnErrors = [];
|
|
184
230
|
if (!(currentReason === "habit" && currentHabitName === "heartbeat")) {
|
|
185
231
|
clearHeartbeatRestShield();
|
|
186
232
|
}
|
|
@@ -190,6 +236,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
190
236
|
}
|
|
191
237
|
catch (error) {
|
|
192
238
|
clearHeartbeatRestShield();
|
|
239
|
+
turnErrors.push(error instanceof Error ? error.message : String(error));
|
|
193
240
|
(0, runtime_1.emitNervesEvent)({
|
|
194
241
|
level: "error",
|
|
195
242
|
component: "senses",
|
|
@@ -205,8 +252,8 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
205
252
|
heartbeatOkRestedAt = isHeartbeatOkRestResult(turnResult) ? nowSource() : null;
|
|
206
253
|
}
|
|
207
254
|
// Record lastRun after a habit turn without dirtying the tracked habit file.
|
|
208
|
-
if (
|
|
209
|
-
recordHabitCompletion(
|
|
255
|
+
if (currentReason === "habit" && currentHabitName && habitStartedAt) {
|
|
256
|
+
recordHabitCompletion(currentHabitName, habitStartedAt, new Date(nowSource()).toISOString(), currentTrigger, turnResult, turnErrors);
|
|
210
257
|
}
|
|
211
258
|
// Drain queue first. Externally-queued work resets the instinct cap
|
|
212
259
|
// because a real outside trigger arrived between turns.
|
|
@@ -223,6 +270,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
223
270
|
nextTaskId = next.taskId;
|
|
224
271
|
nextHabitName = next.habitName;
|
|
225
272
|
nextAwaitName = next.awaitName;
|
|
273
|
+
nextTrigger = next.trigger;
|
|
226
274
|
consecutiveInstinctTurns = nextReason === "instinct" ? consecutiveInstinctTurns + 1 : 0;
|
|
227
275
|
continue runLoop;
|
|
228
276
|
}
|
|
@@ -251,6 +299,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
251
299
|
nextTaskId = undefined;
|
|
252
300
|
nextHabitName = undefined;
|
|
253
301
|
nextAwaitName = undefined;
|
|
302
|
+
nextTrigger = undefined;
|
|
254
303
|
continue;
|
|
255
304
|
}
|
|
256
305
|
break;
|
|
@@ -272,7 +321,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
272
321
|
return;
|
|
273
322
|
}
|
|
274
323
|
recordHabitFireForRecursion(habitName);
|
|
275
|
-
await run("habit", undefined, maybeMessage.habitName);
|
|
324
|
+
await run("habit", undefined, maybeMessage.habitName, undefined, maybeMessage.trigger ?? "overdue");
|
|
276
325
|
return;
|
|
277
326
|
}
|
|
278
327
|
if (maybeMessage.type === "await") {
|
|
@@ -290,7 +339,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
290
339
|
return;
|
|
291
340
|
}
|
|
292
341
|
recordHabitFireForRecursion("heartbeat");
|
|
293
|
-
await run("habit", undefined, "heartbeat");
|
|
342
|
+
await run("habit", undefined, "heartbeat", undefined, "overdue");
|
|
294
343
|
return;
|
|
295
344
|
}
|
|
296
345
|
if (maybeMessage.type === "poke") {
|
|
@@ -73,11 +73,12 @@ const habit_turn_message_1 = require("./habit-turn-message");
|
|
|
73
73
|
const await_turn_message_1 = require("./await-turn-message");
|
|
74
74
|
const await_parser_1 = require("../heart/awaiting/await-parser");
|
|
75
75
|
const await_runtime_state_1 = require("../heart/awaiting/await-runtime-state");
|
|
76
|
-
const journal_index_1 = require("../mind/journal-index");
|
|
77
76
|
const habit_parser_1 = require("../heart/habits/habit-parser");
|
|
78
77
|
const habit_runtime_state_1 = require("../heart/habits/habit-runtime-state");
|
|
79
78
|
const cadence_1 = require("../heart/daemon/cadence");
|
|
80
79
|
const daemon_health_1 = require("../heart/daemon/daemon-health");
|
|
80
|
+
const flight_recorder_1 = require("../arc/flight-recorder");
|
|
81
|
+
const desk_section_1 = require("../mind/desk-section");
|
|
81
82
|
const DEFAULT_INNER_DIALOG_INSTINCTS = [
|
|
82
83
|
{
|
|
83
84
|
id: "heartbeat_checkin",
|
|
@@ -627,6 +628,20 @@ function buildAlsoDueLine(agentRoot, currentHabitName, now) {
|
|
|
627
628
|
return "";
|
|
628
629
|
return `also due: ${alsoDue.join(", ")}`;
|
|
629
630
|
}
|
|
631
|
+
function buildHabitSurfacePolicy(origin, surface) {
|
|
632
|
+
const lines = ["## habit surface policy"];
|
|
633
|
+
lines.push("this habit runs privately, but it may message outward when it needs input, has a useful answer, is blocked, or should report status.");
|
|
634
|
+
if (surface.family)
|
|
635
|
+
lines.push("- family recipients are allowed by default.");
|
|
636
|
+
if (surface.originator && origin)
|
|
637
|
+
lines.push(`- the originator is allowed: ${origin.friendId} via ${origin.channel}/${origin.key}.`);
|
|
638
|
+
if (surface.originator && !origin)
|
|
639
|
+
lines.push("- originator messaging is enabled, but this habit has no origin metadata.");
|
|
640
|
+
if (surface.extra.length > 0)
|
|
641
|
+
lines.push(`- extra allowed recipients: ${surface.extra.join(", ")}.`);
|
|
642
|
+
lines.push("- use send_message for intentional contact; use surface only for a held return tied to an existing return obligation.");
|
|
643
|
+
return lines.join("\n");
|
|
644
|
+
}
|
|
630
645
|
async function runInnerDialogTurn(options) {
|
|
631
646
|
const now = options?.now ?? (() => new Date());
|
|
632
647
|
const reason = options?.reason ?? "instinct";
|
|
@@ -681,6 +696,8 @@ async function runInnerDialogTurn(options) {
|
|
|
681
696
|
let habitBody;
|
|
682
697
|
let habitTitle = habitName;
|
|
683
698
|
let habitLastRun = null;
|
|
699
|
+
let habitOrigin = null;
|
|
700
|
+
let habitSurface = { family: true, originator: true, extra: [] };
|
|
684
701
|
try {
|
|
685
702
|
const habitContent = fs.readFileSync(habitFilePath, "utf-8");
|
|
686
703
|
const parsed = (0, habit_runtime_state_1.applyHabitRuntimeState)(agentRoot, (0, habit_parser_1.parseHabitFile)(habitContent, habitFilePath));
|
|
@@ -688,6 +705,8 @@ async function runInnerDialogTurn(options) {
|
|
|
688
705
|
habitTitle = parsed.title || habitName;
|
|
689
706
|
habitLastRun = parsed.lastRun;
|
|
690
707
|
habitTools = parsed.tools;
|
|
708
|
+
habitOrigin = parsed.origin;
|
|
709
|
+
habitSurface = parsed.surface;
|
|
691
710
|
}
|
|
692
711
|
catch {
|
|
693
712
|
// Habit file missing or unreadable
|
|
@@ -707,6 +726,9 @@ async function runInnerDialogTurn(options) {
|
|
|
707
726
|
stalenessMs: nowMs - o.createdAt,
|
|
708
727
|
}));
|
|
709
728
|
const alsoDue = buildAlsoDueLine(agentRoot, habitName, now);
|
|
729
|
+
const arcResume = (0, flight_recorder_1.formatFlightRecorderResume)((0, flight_recorder_1.readFlightRecorderResume)(agentRoot));
|
|
730
|
+
const deskOrientation = (0, desk_section_1.deskRecordOrientationSection)(agentRoot, now());
|
|
731
|
+
const surfacePolicy = buildHabitSurfacePolicy(habitOrigin, habitSurface);
|
|
710
732
|
// Degraded state (best-effort: never crash)
|
|
711
733
|
let degradedComponents = [];
|
|
712
734
|
try {
|
|
@@ -728,17 +750,11 @@ async function runInnerDialogTurn(options) {
|
|
|
728
750
|
staleObligations,
|
|
729
751
|
parseErrors: options?.parseErrors ?? [],
|
|
730
752
|
degradedComponents,
|
|
753
|
+
arcResume,
|
|
754
|
+
deskOrientation,
|
|
755
|
+
surfacePolicy,
|
|
731
756
|
now,
|
|
732
757
|
});
|
|
733
|
-
// Piggyback journal embedding indexing (best-effort, fire-and-forget)
|
|
734
|
-
const journalDir = path.join(agentRoot, "journal");
|
|
735
|
-
/* v8 ignore start -- journal indexing piggyback: embedding provider may not be available; tested via journal-index unit tests @preserve */
|
|
736
|
-
void (0, journal_index_1.indexJournalFiles)(journalDir, path.join(journalDir, ".index.json"), {
|
|
737
|
-
embed: async () => [],
|
|
738
|
-
}).catch(() => {
|
|
739
|
-
// swallowed: indexing failure must never block habit turn
|
|
740
|
-
});
|
|
741
|
-
/* v8 ignore stop */
|
|
742
758
|
}
|
|
743
759
|
}
|
|
744
760
|
else if (reason === "await" && options?.awaitName) {
|
|
@@ -827,7 +843,10 @@ async function runInnerDialogTurn(options) {
|
|
|
827
843
|
};
|
|
828
844
|
}
|
|
829
845
|
// Fresh session: build system prompt
|
|
830
|
-
const systemPrompt = await (0, prompt_1.buildSystem)("inner", {
|
|
846
|
+
const systemPrompt = await (0, prompt_1.buildSystem)("inner", {
|
|
847
|
+
toolChoiceRequired: true,
|
|
848
|
+
flightRecorderResume: (0, flight_recorder_1.readFlightRecorderResume)((0, identity_1.getAgentRoot)()),
|
|
849
|
+
});
|
|
831
850
|
return {
|
|
832
851
|
messages: [{ role: "system", content: (0, prompt_1.flattenSystemPrompt)(systemPrompt) }],
|
|
833
852
|
sessionPath: sessionFilePath,
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -52,6 +52,8 @@ const socket_client_1 = require("../heart/daemon/socket-client");
|
|
|
52
52
|
const active_work_1 = require("../heart/active-work");
|
|
53
53
|
const delegation_1 = require("../heart/delegation");
|
|
54
54
|
const obligations_1 = require("../arc/obligations");
|
|
55
|
+
const packets_1 = require("../arc/packets");
|
|
56
|
+
const evolution_1 = require("../arc/evolution");
|
|
55
57
|
const provider_failover_1 = require("../heart/provider-failover");
|
|
56
58
|
const openai_codex_token_1 = require("../heart/providers/openai-codex-token");
|
|
57
59
|
const tempo_1 = require("../heart/tempo");
|
|
@@ -66,7 +68,16 @@ const episodes_1 = require("../arc/episodes");
|
|
|
66
68
|
const turn_context_1 = require("../heart/turn-context");
|
|
67
69
|
const provider_visibility_1 = require("../heart/provider-visibility");
|
|
68
70
|
const orientation_frame_1 = require("../heart/orientation-frame");
|
|
71
|
+
const flight_recorder_1 = require("../arc/flight-recorder");
|
|
69
72
|
const VOICE_PENDING_MAX_AGE_MS = 15 * 60 * 1_000;
|
|
73
|
+
const ACTIVE_FLIGHT_RECORDER_PACKET_STATUSES = new Set([
|
|
74
|
+
"drafting",
|
|
75
|
+
"processing",
|
|
76
|
+
"validating",
|
|
77
|
+
"collaborating",
|
|
78
|
+
"paused",
|
|
79
|
+
"blocked",
|
|
80
|
+
]);
|
|
70
81
|
function pendingExpirationReason(channel, message, now) {
|
|
71
82
|
/* v8 ignore start -- pending expiry edge permutations are covered by the stale voice queue tests; this helper keeps defensive non-voice fallbacks @preserve */
|
|
72
83
|
if (Number.isFinite(message.expiresAt) && Number(message.expiresAt) <= now)
|
|
@@ -198,6 +209,65 @@ function latestUserAuthoredText(messages, continuityIngressTexts) {
|
|
|
198
209
|
.filter(Boolean);
|
|
199
210
|
return userMessages[userMessages.length - 1];
|
|
200
211
|
}
|
|
212
|
+
function sessionRefForFlightRecorder(session) {
|
|
213
|
+
return `${session.friendId}/${session.channel}/${session.key}`;
|
|
214
|
+
}
|
|
215
|
+
function isTerminalWithoutContinuation(outcome, hasActiveContinuation) {
|
|
216
|
+
return !hasActiveContinuation && (outcome === "settled"
|
|
217
|
+
|| outcome === "observed"
|
|
218
|
+
|| outcome === "rested"
|
|
219
|
+
|| outcome === "superseded");
|
|
220
|
+
}
|
|
221
|
+
function readPostTurnFlightRecorderArcSnapshot(agentRoot) {
|
|
222
|
+
const latest = (0, flight_recorder_1.readFlightRecorderResume)(agentRoot);
|
|
223
|
+
return {
|
|
224
|
+
activeObligationIds: (0, obligations_1.readPendingObligations)(agentRoot).map((obligation) => obligation.id),
|
|
225
|
+
activeReturnObligationIds: (0, obligations_1.listActiveReturnObligationsForRoot)(agentRoot).map((obligation) => obligation.id),
|
|
226
|
+
activePacketIds: (0, packets_1.listPonderPackets)(agentRoot)
|
|
227
|
+
.filter((packet) => ACTIVE_FLIGHT_RECORDER_PACKET_STATUSES.has(packet.status))
|
|
228
|
+
.map((packet) => packet.id),
|
|
229
|
+
openEvolutionCaseIds: (0, evolution_1.listOpenEvolutionCases)(agentRoot).map((evolutionCase) => evolutionCase.id),
|
|
230
|
+
recentClaimIds: latest.recentClaimIds,
|
|
231
|
+
unverifiedClaimIds: latest.unverifiedClaimIds,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function recordPostTurnFlightRecorderCheckpoint(input) {
|
|
235
|
+
const hasActiveContinuation = input.mustResolveBeforeHandoff
|
|
236
|
+
|| input.activeObligationIds.length > 0
|
|
237
|
+
|| input.activeReturnObligationIds.length > 0
|
|
238
|
+
|| input.activePacketIds.length > 0
|
|
239
|
+
|| input.openEvolutionCaseIds.length > 0;
|
|
240
|
+
const blockedBecause = [
|
|
241
|
+
...(input.outcome === "blocked" ? ["agent reported a blocker in this turn"] : []),
|
|
242
|
+
...(input.outcome === "errored" ? ["turn errored before a safe continuation was reached"] : []),
|
|
243
|
+
...(input.outcome === "aborted" ? ["turn aborted before a safe continuation was reached"] : []),
|
|
244
|
+
...(isTerminalWithoutContinuation(input.outcome, hasActiveContinuation) ? [`turn outcome ${input.outcome}; wait for new input before acting`] : []),
|
|
245
|
+
];
|
|
246
|
+
const nextSafeAction = input.nextSafeAction
|
|
247
|
+
?? (blockedBecause.length > 0
|
|
248
|
+
? "inspect the latest session and wait for new input before acting"
|
|
249
|
+
: "continue the current held work and update Arc/Desk with the next checkpoint");
|
|
250
|
+
(0, flight_recorder_1.recordFlightRecorderEvent)(input.agentRoot, {
|
|
251
|
+
kind: "post_turn_persisted",
|
|
252
|
+
sessionRef: sessionRefForFlightRecorder(input.currentSession),
|
|
253
|
+
summary: `persisted ${input.currentSession.channel}/${input.currentSession.key} turn with outcome ${input.outcome}`,
|
|
254
|
+
currentAsk: input.currentAsk,
|
|
255
|
+
nextSafeAction,
|
|
256
|
+
blockedBecause,
|
|
257
|
+
stopBefore: blockedBecause.length > 0 ? ["acting on stale context"] : [],
|
|
258
|
+
activeObligationIds: input.activeObligationIds,
|
|
259
|
+
activeReturnObligationIds: input.activeReturnObligationIds,
|
|
260
|
+
activePacketIds: input.activePacketIds,
|
|
261
|
+
openEvolutionCaseIds: input.openEvolutionCaseIds,
|
|
262
|
+
recentClaimIds: input.recentClaimIds,
|
|
263
|
+
unverifiedClaimIds: input.unverifiedClaimIds,
|
|
264
|
+
meta: {
|
|
265
|
+
outcome: input.outcome,
|
|
266
|
+
mustResolveBeforeHandoff: input.mustResolveBeforeHandoff,
|
|
267
|
+
sessionPath: input.currentSession.sessionPath,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
201
271
|
function resolveCurrentFailoverBinding(agentName, lane) {
|
|
202
272
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
203
273
|
const { config: agentConfig } = (0, auth_flow_1.readAgentConfigForAgent)(agentName, path.dirname(agentRoot));
|
|
@@ -643,6 +713,7 @@ async function handleInboundTurn(input) {
|
|
|
643
713
|
all: activeWorkFrame.pendingObligations,
|
|
644
714
|
},
|
|
645
715
|
currentSessionTiming,
|
|
716
|
+
flightRecorderResume: ctx.flightRecorderResume,
|
|
646
717
|
});
|
|
647
718
|
/* v8 ignore next 3 -- syncFailure propagation tested in sync.test.ts @preserve */
|
|
648
719
|
if (syncFailure) {
|
|
@@ -708,7 +779,7 @@ async function handleInboundTurn(input) {
|
|
|
708
779
|
senseStatusLines: ctx.senseStatusLines,
|
|
709
780
|
bundleMeta: ctx.bundleMeta,
|
|
710
781
|
daemonHealth: ctx.daemonHealth,
|
|
711
|
-
|
|
782
|
+
flightRecorderResume: ctx.flightRecorderResume,
|
|
712
783
|
...(ctx.providerVisibility ? { providerVisibility: ctx.providerVisibility } : {}),
|
|
713
784
|
toolContext: {
|
|
714
785
|
/* v8 ignore next -- default no-op signin satisfies interface; real signin injected by sense adapter @preserve */
|
|
@@ -738,6 +809,36 @@ async function handleInboundTurn(input) {
|
|
|
738
809
|
result.error?.message ?? "unknown error", classification, currentProvider, currentBinding.model, agentName, inventory, {}, { currentLane });
|
|
739
810
|
input.failoverState.pending = failoverContext;
|
|
740
811
|
input.postTurn(sessionMessages, session.sessionPath, result.usage);
|
|
812
|
+
try {
|
|
813
|
+
const postTurnArc = readPostTurnFlightRecorderArcSnapshot((0, identity_1.getAgentRoot)());
|
|
814
|
+
recordPostTurnFlightRecorderCheckpoint({
|
|
815
|
+
agentRoot: (0, identity_1.getAgentRoot)(),
|
|
816
|
+
currentSession,
|
|
817
|
+
currentAsk: currentObligation
|
|
818
|
+
?? activeWorkFrame.primaryObligation?.content?.trim()
|
|
819
|
+
?? currentUserMessage
|
|
820
|
+
?? null,
|
|
821
|
+
nextSafeAction: failoverContext.userMessage,
|
|
822
|
+
outcome: "errored",
|
|
823
|
+
mustResolveBeforeHandoff,
|
|
824
|
+
activeObligationIds: postTurnArc.activeObligationIds,
|
|
825
|
+
activeReturnObligationIds: postTurnArc.activeReturnObligationIds,
|
|
826
|
+
activePacketIds: postTurnArc.activePacketIds,
|
|
827
|
+
openEvolutionCaseIds: postTurnArc.openEvolutionCaseIds,
|
|
828
|
+
recentClaimIds: postTurnArc.recentClaimIds,
|
|
829
|
+
unverifiedClaimIds: postTurnArc.unverifiedClaimIds,
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
catch (checkpointError) {
|
|
833
|
+
/* v8 ignore next -- best-effort recorder write must not hide provider-failover guidance @preserve */
|
|
834
|
+
(0, runtime_1.emitNervesEvent)({
|
|
835
|
+
level: "warn",
|
|
836
|
+
component: "senses",
|
|
837
|
+
event: "senses.flight_recorder_checkpoint_error",
|
|
838
|
+
message: "failed to record provider-failover flight recorder checkpoint",
|
|
839
|
+
meta: { error: checkpointError instanceof Error ? checkpointError.message : String(checkpointError) },
|
|
840
|
+
});
|
|
841
|
+
}
|
|
741
842
|
return {
|
|
742
843
|
resolvedContext,
|
|
743
844
|
gateResult,
|
|
@@ -781,6 +882,39 @@ async function handleInboundTurn(input) {
|
|
|
781
882
|
: undefined)
|
|
782
883
|
: (Object.keys(continuingState).length > 0 ? continuingState : undefined);
|
|
783
884
|
input.postTurn(sessionMessages, session.sessionPath, result.usage, undefined, nextState);
|
|
885
|
+
try {
|
|
886
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
887
|
+
const postTurnArc = readPostTurnFlightRecorderArcSnapshot(agentRoot);
|
|
888
|
+
recordPostTurnFlightRecorderCheckpoint({
|
|
889
|
+
agentRoot,
|
|
890
|
+
currentSession,
|
|
891
|
+
currentAsk: currentObligation
|
|
892
|
+
?? activeWorkFrame.primaryObligation?.content?.trim()
|
|
893
|
+
?? currentUserMessage
|
|
894
|
+
?? null,
|
|
895
|
+
nextSafeAction: activeWorkFrame.resumeHandle?.nextAction
|
|
896
|
+
?? activeWorkFrame.primaryObligation?.nextAction?.trim()
|
|
897
|
+
?? null,
|
|
898
|
+
outcome: result.outcome ?? "observed",
|
|
899
|
+
mustResolveBeforeHandoff,
|
|
900
|
+
activeObligationIds: postTurnArc.activeObligationIds,
|
|
901
|
+
activeReturnObligationIds: postTurnArc.activeReturnObligationIds,
|
|
902
|
+
activePacketIds: postTurnArc.activePacketIds,
|
|
903
|
+
openEvolutionCaseIds: postTurnArc.openEvolutionCaseIds,
|
|
904
|
+
recentClaimIds: postTurnArc.recentClaimIds,
|
|
905
|
+
unverifiedClaimIds: postTurnArc.unverifiedClaimIds,
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
catch (error) {
|
|
909
|
+
/* v8 ignore next -- defensive recorder failures are non-fatal to already-persisted user turns @preserve */
|
|
910
|
+
(0, runtime_1.emitNervesEvent)({
|
|
911
|
+
level: "warn",
|
|
912
|
+
component: "senses",
|
|
913
|
+
event: "senses.flight_recorder_checkpoint_error",
|
|
914
|
+
message: "failed to record post-turn flight recorder checkpoint",
|
|
915
|
+
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
916
|
+
});
|
|
917
|
+
}
|
|
784
918
|
// Step 7: Token accumulation
|
|
785
919
|
await input.accumulateFriendTokens(input.friendStore, resolvedContext.friend.id, result.usage);
|
|
786
920
|
(0, runtime_1.emitNervesEvent)({
|
package/dist/util/frontmatter.js
CHANGED
|
@@ -17,6 +17,10 @@ function parseScalar(raw) {
|
|
|
17
17
|
const value = raw.trim();
|
|
18
18
|
if (value === "null")
|
|
19
19
|
return null;
|
|
20
|
+
if (value === "true")
|
|
21
|
+
return true;
|
|
22
|
+
if (value === "false")
|
|
23
|
+
return false;
|
|
20
24
|
if (value === "[]")
|
|
21
25
|
return [];
|
|
22
26
|
if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
@@ -46,7 +50,19 @@ function parseFrontmatter(raw) {
|
|
|
46
50
|
items.push(parseScalar(lines[cursor].replace(/^\s*-\s+/, "")));
|
|
47
51
|
cursor += 1;
|
|
48
52
|
}
|
|
49
|
-
|
|
53
|
+
if (items.length > 0) {
|
|
54
|
+
frontmatter[key] = items;
|
|
55
|
+
idx = cursor - 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const nested = {};
|
|
59
|
+
cursor = idx + 1;
|
|
60
|
+
while (cursor < lines.length && /^\s+[A-Za-z0-9_:-]+:\s*/.test(lines[cursor])) {
|
|
61
|
+
const child = /^\s+([A-Za-z0-9_:-]+):\s*(.*)$/.exec(lines[cursor]);
|
|
62
|
+
nested[child[1]] = parseScalar(child[2]);
|
|
63
|
+
cursor += 1;
|
|
64
|
+
}
|
|
65
|
+
frontmatter[key] = Object.keys(nested).length > 0 ? nested : items;
|
|
50
66
|
idx = cursor - 1;
|
|
51
67
|
}
|
|
52
68
|
return frontmatter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.658",
|
|
4
4
|
"main": "dist/heart/daemon/ouro-entry.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cli": "dist/heart/daemon/ouro-bot-entry.js",
|
|
@@ -79,12 +79,12 @@
|
|
|
79
79
|
"@types/mailparser": "^3.4.6",
|
|
80
80
|
"@types/semver": "^7.7.1",
|
|
81
81
|
"@types/smtp-server": "^3.5.13",
|
|
82
|
-
"@vitest/coverage-v8": "^4.
|
|
82
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
83
83
|
"eslint": "^10.0.2",
|
|
84
84
|
"jsdom": "^29.0.2",
|
|
85
85
|
"typescript": "^5.7.0",
|
|
86
86
|
"typescript-eslint": "^8.56.1",
|
|
87
|
-
"vitest": "^4.
|
|
87
|
+
"vitest": "^4.1.8"
|
|
88
88
|
},
|
|
89
89
|
"overrides": {
|
|
90
90
|
"@microsoft/teams.apps": {
|
|
@@ -64,7 +64,7 @@ Once connected, these tools are available:
|
|
|
64
64
|
- **status** -- Get agent's current status and activity
|
|
65
65
|
- **catchup** -- Get recent activity summary
|
|
66
66
|
- **get_context** -- Get agent's current working context
|
|
67
|
-
- **
|
|
67
|
+
- **search_facts** -- Read-only Desk record fact search for specific topics; missing hits are not evidence that the agent has no belief or preference
|
|
68
68
|
- **get_task** -- Get details of the agent's current task
|
|
69
69
|
|
|
70
70
|
## Troubleshooting
|
|
@@ -92,7 +92,7 @@ an interactive sign-up flow.
|
|
|
92
92
|
|
|
93
93
|
### Post-Booking
|
|
94
94
|
- Save confirmation details (confirmation number, dates, hotel name, airline, booking reference)
|
|
95
|
-
- Save to diary
|
|
95
|
+
- Save durable travel facts to the Desk record diary or notes for future reference
|
|
96
96
|
- Set reminders for check-in windows
|
|
97
97
|
- Note cancellation deadlines
|
|
98
98
|
|