@ouro.bot/cli 0.1.0-alpha.666 → 0.1.0-alpha.667
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.json +7 -0
- package/dist/arc/flight-recorder.js +58 -2
- 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/runtime-readers.js +31 -0
- 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
|
@@ -43,6 +43,8 @@ exports.readLogView = readLogView;
|
|
|
43
43
|
exports.readHabitView = readHabitView;
|
|
44
44
|
exports.readHabitRunView = readHabitRunView;
|
|
45
45
|
exports.readHabitRunReceiptView = readHabitRunReceiptView;
|
|
46
|
+
exports.readHabitSessionSummaryListView = readHabitSessionSummaryListView;
|
|
47
|
+
exports.readHabitSessionSummaryView = readHabitSessionSummaryView;
|
|
46
48
|
exports.readNeedsMeView = readNeedsMeView;
|
|
47
49
|
exports.readDeskPrefs = readDeskPrefs;
|
|
48
50
|
const fs = __importStar(require("fs"));
|
|
@@ -50,6 +52,7 @@ const path = __importStar(require("path"));
|
|
|
50
52
|
const runtime_1 = require("../../../nerves/runtime");
|
|
51
53
|
const habit_parser_1 = require("../../habits/habit-parser");
|
|
52
54
|
const habit_runtime_state_1 = require("../../habits/habit-runtime-state");
|
|
55
|
+
const habit_session_summary_1 = require("../../habits/habit-session-summary");
|
|
53
56
|
const identity_1 = require("../../identity");
|
|
54
57
|
const daemon_health_1 = require("../../daemon/daemon-health");
|
|
55
58
|
const flight_recorder_1 = require("../../../arc/flight-recorder");
|
|
@@ -656,6 +659,34 @@ function readHabitRunReceiptView(agentRoot, runId) {
|
|
|
656
659
|
});
|
|
657
660
|
return receipt ? { receipt } : null;
|
|
658
661
|
}
|
|
662
|
+
function readHabitSessionSummaryListView(agentRoot, options = {}) {
|
|
663
|
+
const limit = normalizeHabitRunLimit(options.limit);
|
|
664
|
+
const receipts = (0, flight_recorder_1.listHabitRunReceipts)(agentRoot);
|
|
665
|
+
const items = receipts.slice(0, limit)
|
|
666
|
+
.map((receipt) => (0, habit_session_summary_1.readHabitSessionSummary)(agentRoot, { runId: receipt.runId }))
|
|
667
|
+
.filter((summary) => summary !== null);
|
|
668
|
+
(0, runtime_1.emitNervesEvent)({
|
|
669
|
+
component: "heart",
|
|
670
|
+
event: "heart.mailbox_habit_run_summaries_read",
|
|
671
|
+
message: "reading mailbox habit run summaries",
|
|
672
|
+
meta: { agentRoot, totalCount: receipts.length, limit, itemCount: items.length },
|
|
673
|
+
});
|
|
674
|
+
return {
|
|
675
|
+
totalCount: receipts.length,
|
|
676
|
+
limit,
|
|
677
|
+
items,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function readHabitSessionSummaryView(agentRoot, selector) {
|
|
681
|
+
const summary = (0, habit_session_summary_1.readHabitSessionSummary)(agentRoot, selector);
|
|
682
|
+
(0, runtime_1.emitNervesEvent)({
|
|
683
|
+
component: "heart",
|
|
684
|
+
event: "heart.mailbox_habit_run_summary_read",
|
|
685
|
+
message: "reading mailbox habit run summary",
|
|
686
|
+
meta: { agentRoot, runId: summary?.runId ?? null, habitName: summary?.habitName ?? selector.habitName ?? null, found: summary !== null },
|
|
687
|
+
});
|
|
688
|
+
return summary;
|
|
689
|
+
}
|
|
659
690
|
function readNeedsMeView(agentName, options = {}) {
|
|
660
691
|
const bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
661
692
|
const now = options.now?.() ?? new Date();
|
|
@@ -51,11 +51,13 @@ const coding_1 = require("./coding");
|
|
|
51
51
|
const pending_1 = require("../mind/pending");
|
|
52
52
|
const obligations_1 = require("../arc/obligations");
|
|
53
53
|
const progress_story_1 = require("../heart/progress-story");
|
|
54
|
+
const habit_session_summary_1 = require("../heart/habits/habit-session-summary");
|
|
54
55
|
const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
|
|
55
56
|
const mail_import_discovery_1 = require("../heart/mail-import-discovery");
|
|
56
57
|
const outbound_1 = require("../senses/voice/outbound");
|
|
57
58
|
const NO_SESSION_FOUND_MESSAGE = "no session found for that friend/channel/key combination.";
|
|
58
59
|
const EMPTY_SESSION_MESSAGE = "session exists but has no non-system messages.";
|
|
60
|
+
const VALID_HABIT_SUMMARY_WHICH = new Set(["latest", "previous", "latest-success", "latest-failure"]);
|
|
59
61
|
async function summarizeSessionTailSafely(options) {
|
|
60
62
|
try {
|
|
61
63
|
return await (0, session_transcript_1.summarizeSessionTail)(options);
|
|
@@ -99,6 +101,79 @@ function normalizeProgressOutcome(text) {
|
|
|
99
101
|
}
|
|
100
102
|
return trimmed;
|
|
101
103
|
}
|
|
104
|
+
function optionalArg(args, key) {
|
|
105
|
+
const value = args[key];
|
|
106
|
+
if (value === undefined || value === null)
|
|
107
|
+
return undefined;
|
|
108
|
+
if (typeof value !== "string")
|
|
109
|
+
return null;
|
|
110
|
+
const trimmed = value.trim();
|
|
111
|
+
return trimmed ? trimmed : undefined;
|
|
112
|
+
}
|
|
113
|
+
function validateSessionSummarySelector(args) {
|
|
114
|
+
const runId = optionalArg(args, "runId");
|
|
115
|
+
const habitName = optionalArg(args, "habitName");
|
|
116
|
+
const operationId = optionalArg(args, "operationId");
|
|
117
|
+
const which = optionalArg(args, "which");
|
|
118
|
+
if (runId === null || habitName === null || operationId === null || which === null) {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
code: which === null ? "invalid_which" : "selector_required",
|
|
122
|
+
message: which === null
|
|
123
|
+
? "which must be latest, previous, latest-success, or latest-failure"
|
|
124
|
+
: "selector fields must be strings",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (runId !== undefined) {
|
|
128
|
+
if (habitName !== undefined || operationId !== undefined || which !== undefined) {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
code: "run_id_exclusive",
|
|
132
|
+
message: "runId cannot be combined with habitName, operationId, or which",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return { ok: true, selector: { runId } };
|
|
136
|
+
}
|
|
137
|
+
if (habitName === undefined && operationId === undefined) {
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
code: "selector_required",
|
|
141
|
+
message: "provide runId, habitName, or operationId",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (which !== undefined && !VALID_HABIT_SUMMARY_WHICH.has(which)) {
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
code: "invalid_which",
|
|
148
|
+
message: "which must be latest, previous, latest-success, or latest-failure",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
selector: {
|
|
154
|
+
...(habitName !== undefined ? { habitName } : {}),
|
|
155
|
+
...(operationId !== undefined ? { operationId } : {}),
|
|
156
|
+
...(which !== undefined ? { which } : {}),
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function renderSessionSummaryText(summary) {
|
|
161
|
+
const lines = [
|
|
162
|
+
`habit ${summary.habitName} run ${summary.runId} finished with ${summary.status}.`,
|
|
163
|
+
summary.operationId ? `operation: ${summary.operationId}` : null,
|
|
164
|
+
summary.summary,
|
|
165
|
+
summary.nextLikelyStep ? `next: ${summary.nextLikelyStep}` : null,
|
|
166
|
+
summary.decisions.length > 0 ? `decisions: ${summary.decisions.join("; ")}` : null,
|
|
167
|
+
summary.pending.count > 0 ? `pending: ${summary.pending.count} file(s) (${summary.pending.files.join(", ")})` : "pending: none",
|
|
168
|
+
summary.messagesSent.length > 0 ? `messages: ${summary.messagesSent.length}` : "messages: none",
|
|
169
|
+
summary.toolsUsed.length > 0 ? `tools: ${summary.toolsUsed.join(", ")}` : "tools: none",
|
|
170
|
+
summary.errors.length > 0 ? `errors: ${summary.errors.join("; ")}` : null,
|
|
171
|
+
summary.warnings.length > 0 ? `warnings: ${summary.warnings.join("; ")}` : null,
|
|
172
|
+
`receipt: ${summary.sources.receipt}`,
|
|
173
|
+
`session: ${summary.sources.session}`,
|
|
174
|
+
];
|
|
175
|
+
return lines.filter((line) => Boolean(line)).join("\n");
|
|
176
|
+
}
|
|
102
177
|
function writePendingEnvelope(queueDir, message) {
|
|
103
178
|
fs.mkdirSync(queueDir, { recursive: true });
|
|
104
179
|
const fileName = `${message.timestamp}-${Math.random().toString(36).slice(2, 10)}.json`;
|
|
@@ -369,6 +444,57 @@ exports.sessionToolDefinitions = [
|
|
|
369
444
|
return `this is my current top-level live world-state.\nanswer whole-self status questions from this before drilling into individual sessions.\n\n${(0, active_work_1.formatActiveWorkFrame)(frame)}`;
|
|
370
445
|
},
|
|
371
446
|
},
|
|
447
|
+
{
|
|
448
|
+
tool: {
|
|
449
|
+
type: "function",
|
|
450
|
+
function: {
|
|
451
|
+
name: "session_summary",
|
|
452
|
+
description: "read-only orientation for habit runs. returns a structured live summary from habit receipts, session files, pending dirs, and runtime cursors without writing state.",
|
|
453
|
+
parameters: {
|
|
454
|
+
type: "object",
|
|
455
|
+
properties: {
|
|
456
|
+
runId: { type: "string", description: "exact habit run id; cannot be combined with habitName, operationId, or which" },
|
|
457
|
+
habitName: { type: "string", description: "habit name to select from" },
|
|
458
|
+
operationId: { type: "string", description: "operation id for stateful habit run groups, such as habit:heartbeat" },
|
|
459
|
+
which: {
|
|
460
|
+
type: "string",
|
|
461
|
+
enum: ["latest", "previous", "latest-success", "latest-failure"],
|
|
462
|
+
description: "which matching run to read; defaults to latest",
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
handler: (args) => {
|
|
469
|
+
const validation = validateSessionSummarySelector(args);
|
|
470
|
+
if (!validation.ok) {
|
|
471
|
+
return JSON.stringify({
|
|
472
|
+
kind: "invalid_selector",
|
|
473
|
+
code: validation.code,
|
|
474
|
+
message: validation.message,
|
|
475
|
+
}, null, 2);
|
|
476
|
+
}
|
|
477
|
+
const summary = (0, habit_session_summary_1.readHabitSessionSummary)((0, identity_1.getAgentRoot)(), validation.selector);
|
|
478
|
+
if (!summary) {
|
|
479
|
+
return JSON.stringify({
|
|
480
|
+
kind: "not_found",
|
|
481
|
+
message: "no habit run matched selector",
|
|
482
|
+
selector: validation.selector,
|
|
483
|
+
}, null, 2);
|
|
484
|
+
}
|
|
485
|
+
return JSON.stringify({
|
|
486
|
+
kind: "habit_session_summary",
|
|
487
|
+
text: renderSessionSummaryText(summary),
|
|
488
|
+
summary,
|
|
489
|
+
}, null, 2);
|
|
490
|
+
},
|
|
491
|
+
riskProfile: {
|
|
492
|
+
mutates: "none",
|
|
493
|
+
risk: "low",
|
|
494
|
+
reason: "reads habit run summaries from local receipts and session artifacts",
|
|
495
|
+
},
|
|
496
|
+
summaryKeys: ["runId", "habitName", "operationId", "which"],
|
|
497
|
+
},
|
|
372
498
|
{
|
|
373
499
|
tool: {
|
|
374
500
|
type: "function",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildHabitTurnMessage = buildHabitTurnMessage;
|
|
4
4
|
const runtime_1 = require("../nerves/runtime");
|
|
5
|
+
const PRIOR_SESSION_SUMMARY_LIMIT = 1600;
|
|
6
|
+
const PRIOR_SESSION_TRUNCATION_SUFFIX = "\n[truncated]";
|
|
5
7
|
function formatElapsed(ms) {
|
|
6
8
|
const minutes = Math.floor(ms / 60000);
|
|
7
9
|
if (minutes < 60) {
|
|
@@ -11,9 +13,9 @@ function formatElapsed(ms) {
|
|
|
11
13
|
return `${hours} ${hours === 1 ? "hour" : "hours"}`;
|
|
12
14
|
}
|
|
13
15
|
function buildHabitTurnMessage(options) {
|
|
14
|
-
const { habitName, habitTitle, habitBody, lastRun, checkpoint, alsoDue, staleObligations, parseErrors, degradedComponents, arcResume, deskOrientation, surfacePolicy, now, } = options;
|
|
16
|
+
const { habitName, habitTitle, habitBody, lastRun, checkpoint, alsoDue, staleObligations, parseErrors, degradedComponents, arcResume, deskOrientation, surfacePolicy, priorSessionSummary, now, } = options;
|
|
15
17
|
const hasBody = habitBody !== undefined && habitBody !== "";
|
|
16
|
-
const leadingSections = buildLeadingSections(arcResume, deskOrientation, surfacePolicy);
|
|
18
|
+
const leadingSections = buildLeadingSections(arcResume, deskOrientation, surfacePolicy, priorSessionSummary);
|
|
17
19
|
// First beat: lastRun is null
|
|
18
20
|
if (lastRun === null) {
|
|
19
21
|
// Cold start: no checkpoint, no body — bare awareness
|
|
@@ -86,7 +88,7 @@ function buildHabitTurnMessage(options) {
|
|
|
86
88
|
});
|
|
87
89
|
return joinSections(leadingSections, sections);
|
|
88
90
|
}
|
|
89
|
-
function buildLeadingSections(arcResume, deskOrientation, surfacePolicy) {
|
|
91
|
+
function buildLeadingSections(arcResume, deskOrientation, surfacePolicy, priorSessionSummary) {
|
|
90
92
|
const sections = [];
|
|
91
93
|
if (arcResume?.trim())
|
|
92
94
|
sections.push(arcResume.trim());
|
|
@@ -94,11 +96,47 @@ function buildLeadingSections(arcResume, deskOrientation, surfacePolicy) {
|
|
|
94
96
|
sections.push(deskOrientation.trim());
|
|
95
97
|
if (surfacePolicy?.trim())
|
|
96
98
|
sections.push(surfacePolicy.trim());
|
|
99
|
+
const priorSummary = buildPriorSessionSummarySection(priorSessionSummary);
|
|
100
|
+
if (priorSummary)
|
|
101
|
+
sections.push(priorSummary);
|
|
97
102
|
return sections;
|
|
98
103
|
}
|
|
99
104
|
function joinSections(leadingSections, sections) {
|
|
100
105
|
return [...leadingSections, ...sections].filter((section) => section.trim().length > 0).join("\n\n");
|
|
101
106
|
}
|
|
107
|
+
function truncatePriorSummary(summary) {
|
|
108
|
+
if (summary.length <= PRIOR_SESSION_SUMMARY_LIMIT)
|
|
109
|
+
return summary;
|
|
110
|
+
return `${summary.slice(0, PRIOR_SESSION_SUMMARY_LIMIT - PRIOR_SESSION_TRUNCATION_SUFFIX.length)}${PRIOR_SESSION_TRUNCATION_SUFFIX}`;
|
|
111
|
+
}
|
|
112
|
+
function buildPriorSessionSummarySection(priorSessionSummary) {
|
|
113
|
+
if (!priorSessionSummary || priorSessionSummary.mode !== "stateful")
|
|
114
|
+
return null;
|
|
115
|
+
const lines = ["## Prior habit session summary"];
|
|
116
|
+
const summary = priorSessionSummary.summary?.trim();
|
|
117
|
+
if (summary) {
|
|
118
|
+
lines.push(truncatePriorSummary(summary));
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
lines.push("No prior stateful habit summary found for this operation.");
|
|
122
|
+
}
|
|
123
|
+
const sourceLines = ["receipt", "session", "pending", "runtimeState"]
|
|
124
|
+
.flatMap((key) => {
|
|
125
|
+
const value = cleanSourceLocator(priorSessionSummary.sources[key]);
|
|
126
|
+
return value ? [`${key}: ${value}`] : [];
|
|
127
|
+
});
|
|
128
|
+
if (sourceLines.length > 0)
|
|
129
|
+
lines.push(...sourceLines);
|
|
130
|
+
if (priorSessionSummary.warnings.length > 0)
|
|
131
|
+
lines.push(`warnings: ${priorSessionSummary.warnings.join("; ")}`);
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
function cleanSourceLocator(value) {
|
|
135
|
+
if (typeof value !== "string")
|
|
136
|
+
return null;
|
|
137
|
+
const trimmed = value.trim();
|
|
138
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
139
|
+
}
|
|
102
140
|
function appendTrailingExtras(sections, alsoDue, staleObligations, parseErrors, degradedComponents) {
|
|
103
141
|
// 4. Also-due
|
|
104
142
|
if (alsoDue) {
|
|
@@ -43,8 +43,10 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
43
43
|
const identity_1 = require("../heart/identity");
|
|
44
44
|
const pending_1 = require("../mind/pending");
|
|
45
45
|
const habit_parser_1 = require("../heart/habits/habit-parser");
|
|
46
|
+
const habit_runtime_state_1 = require("../heart/habits/habit-runtime-state");
|
|
46
47
|
const habit_session_1 = require("../heart/habits/habit-session");
|
|
47
48
|
const flight_recorder_1 = require("../arc/flight-recorder");
|
|
49
|
+
const habit_session_summary_1 = require("../heart/habits/habit-session-summary");
|
|
48
50
|
const store_file_1 = require("../mind/friends/store-file");
|
|
49
51
|
const tools_base_1 = require("../repertoire/tools-base");
|
|
50
52
|
const tools_surface_1 = require("../repertoire/tools-surface");
|
|
@@ -89,6 +91,79 @@ function isHeartbeatOkRestResult(result) {
|
|
|
89
91
|
const maybeResult = result;
|
|
90
92
|
return maybeResult.turnOutcome === "rested" && maybeResult.restStatus === "HEARTBEAT_OK";
|
|
91
93
|
}
|
|
94
|
+
function isRecord(value) {
|
|
95
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
96
|
+
}
|
|
97
|
+
function contentToText(content) {
|
|
98
|
+
if (typeof content === "string")
|
|
99
|
+
return content.trim();
|
|
100
|
+
if (!Array.isArray(content))
|
|
101
|
+
return "";
|
|
102
|
+
return content
|
|
103
|
+
.map((part) => isRecord(part) && typeof part.text === "string" ? part.text : "")
|
|
104
|
+
.filter((text) => text.trim().length > 0)
|
|
105
|
+
.join("\n")
|
|
106
|
+
.trim();
|
|
107
|
+
}
|
|
108
|
+
function resultMessages(result) {
|
|
109
|
+
if (Array.isArray(result))
|
|
110
|
+
return result.flatMap((entry) => resultMessages(entry));
|
|
111
|
+
return isRecord(result) && Array.isArray(result.messages) ? result.messages : [];
|
|
112
|
+
}
|
|
113
|
+
function latestAssistantText(results) {
|
|
114
|
+
for (let resultIndex = results.length - 1; resultIndex >= 0; resultIndex--) {
|
|
115
|
+
const messages = resultMessages(results[resultIndex]);
|
|
116
|
+
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex--) {
|
|
117
|
+
const message = messages[messageIndex];
|
|
118
|
+
if (!isRecord(message) || message.role !== "assistant")
|
|
119
|
+
continue;
|
|
120
|
+
const text = contentToText(message.content);
|
|
121
|
+
if (text.length > 0)
|
|
122
|
+
return text.replace(/^checkpoint\s*:\s*/i, "").trim() || text;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
function deriveHabitSummarySnapshot(habitRun) {
|
|
128
|
+
const assistant = latestAssistantText(habitRun.results);
|
|
129
|
+
if (assistant)
|
|
130
|
+
return { summary: assistant, decisions: [], nextLikelyStep: null };
|
|
131
|
+
if (habitRun.errors.length > 0) {
|
|
132
|
+
return {
|
|
133
|
+
summary: `Habit ${habitRun.habit.name} finished with errors: ${habitRun.errors.join("; ")}`,
|
|
134
|
+
decisions: [],
|
|
135
|
+
nextLikelyStep: null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const surfaced = habitRun.surfaceAttempts.find((attempt) => attempt.result !== "blocked" && attempt.result !== "failed" && attempt.result !== "unavailable");
|
|
139
|
+
if (surfaced) {
|
|
140
|
+
return {
|
|
141
|
+
summary: `Habit ${habitRun.habit.name} surfaced via ${surfaced.recipient}/${surfaced.channel}.`,
|
|
142
|
+
decisions: [],
|
|
143
|
+
nextLikelyStep: null,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const produced = habitRun.producedRefs.find((ref) => ref.kind !== "none");
|
|
147
|
+
if (produced) {
|
|
148
|
+
return {
|
|
149
|
+
summary: `Habit ${habitRun.habit.name} produced ${produced.kind}: ${produced.locator}.`,
|
|
150
|
+
decisions: [],
|
|
151
|
+
nextLikelyStep: null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (habitRun.results.some(isHeartbeatOkRestResult)) {
|
|
155
|
+
return {
|
|
156
|
+
summary: `Habit ${habitRun.habit.name} rested with HEARTBEAT_OK.`,
|
|
157
|
+
decisions: [],
|
|
158
|
+
nextLikelyStep: null,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
summary: `Habit ${habitRun.habit.name} completed without additional surfaced output.`,
|
|
163
|
+
decisions: [],
|
|
164
|
+
nextLikelyStep: null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
92
167
|
function fallbackHabitFile(habitName) {
|
|
93
168
|
return {
|
|
94
169
|
name: habitName,
|
|
@@ -100,6 +175,7 @@ function fallbackHabitFile(habitName) {
|
|
|
100
175
|
tools: [],
|
|
101
176
|
origin: null,
|
|
102
177
|
surface: { family: false, originator: false, extra: [] },
|
|
178
|
+
continuity: { mode: "fresh" },
|
|
103
179
|
body: "",
|
|
104
180
|
};
|
|
105
181
|
}
|
|
@@ -124,8 +200,10 @@ function readHabitForRun(agentRoot, habitName, errors) {
|
|
|
124
200
|
async function prepareHabitRun(habitName, trigger, startedAt) {
|
|
125
201
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
126
202
|
const errors = [];
|
|
127
|
-
const habit = readHabitForRun(agentRoot, habitName, errors);
|
|
203
|
+
const habit = (0, habit_runtime_state_1.applyHabitRuntimeState)(agentRoot, readHabitForRun(agentRoot, habitName, errors));
|
|
128
204
|
const runId = (0, flight_recorder_1.createHabitRunId)(habitName, new Date(startedAt));
|
|
205
|
+
const operationId = habit.continuity.mode === "stateful" ? `habit:${habit.name}` : null;
|
|
206
|
+
const priorSessionSummary = readPriorSessionSummary(agentRoot, operationId);
|
|
129
207
|
const paths = (0, habit_session_1.createHabitSessionPaths)(agentRoot, runId, habit.name);
|
|
130
208
|
const friendStore = new store_file_1.FileFriendStore(path.join(agentRoot, "friends"));
|
|
131
209
|
const permissionEnvelope = await (0, habit_session_1.normalizeHabitPermissionEnvelope)(habit, { agentRoot, friendStore });
|
|
@@ -134,8 +212,10 @@ async function prepareHabitRun(habitName, trigger, startedAt) {
|
|
|
134
212
|
agentRoot,
|
|
135
213
|
habit,
|
|
136
214
|
runId,
|
|
215
|
+
operationId,
|
|
137
216
|
trigger,
|
|
138
217
|
startedAt,
|
|
218
|
+
priorSessionSummary,
|
|
139
219
|
paths,
|
|
140
220
|
permissionEnvelope,
|
|
141
221
|
toolPolicy,
|
|
@@ -150,6 +230,29 @@ function riskProfileForHabitPolicy(definition, name) {
|
|
|
150
230
|
const probeArgs = name === "shell" ? { command: "touch /tmp/habit-policy-probe" } : {};
|
|
151
231
|
return (0, tools_1.riskProfileForTool)(definition, name, probeArgs);
|
|
152
232
|
}
|
|
233
|
+
function readPriorSessionSummary(agentRoot, operationId) {
|
|
234
|
+
if (operationId === null)
|
|
235
|
+
return undefined;
|
|
236
|
+
try {
|
|
237
|
+
const summary = (0, habit_session_summary_1.readHabitSessionSummary)(agentRoot, { operationId, which: "latest" });
|
|
238
|
+
if (!summary)
|
|
239
|
+
return { mode: "stateful", summary: null, sources: {}, warnings: [] };
|
|
240
|
+
return {
|
|
241
|
+
mode: "stateful",
|
|
242
|
+
summary: summary.summary,
|
|
243
|
+
sources: summary.sources,
|
|
244
|
+
warnings: summary.warnings,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
return {
|
|
249
|
+
mode: "stateful",
|
|
250
|
+
summary: null,
|
|
251
|
+
sources: {},
|
|
252
|
+
warnings: [`prior summary read failed: ${String(error)}`],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
153
256
|
function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runInnerDialogTurn)(options), hasPendingWork = (pendingDir) => (0, pending_1.hasPendingMessages)(pendingDir ?? (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)())), nowSource = () => Date.now()) {
|
|
154
257
|
let running = false;
|
|
155
258
|
const queue = [];
|
|
@@ -164,11 +267,13 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
164
267
|
trigger: habitRun.trigger,
|
|
165
268
|
startedAt: habitRun.startedAt,
|
|
166
269
|
endedAt,
|
|
270
|
+
operationId: habitRun.operationId,
|
|
167
271
|
permissionEnvelope: habitRun.permissionEnvelope,
|
|
168
272
|
toolPolicy: habitRun.toolPolicy,
|
|
169
273
|
producedRefs: habitRun.producedRefs,
|
|
170
274
|
surfaceAttempts: habitRun.surfaceAttempts,
|
|
171
275
|
errors: habitRun.errors,
|
|
276
|
+
summarySnapshot: deriveHabitSummarySnapshot(habitRun),
|
|
172
277
|
});
|
|
173
278
|
}
|
|
174
279
|
function clearHeartbeatRestShield() {
|
|
@@ -286,6 +391,13 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
286
391
|
...(currentHabitRun
|
|
287
392
|
? {
|
|
288
393
|
trigger: currentHabitRun.trigger,
|
|
394
|
+
preparedHabit: {
|
|
395
|
+
runId: currentHabitRun.runId,
|
|
396
|
+
trigger: currentHabitRun.trigger,
|
|
397
|
+
operationId: currentHabitRun.operationId,
|
|
398
|
+
habit: currentHabitRun.habit,
|
|
399
|
+
priorSessionSummary: currentHabitRun.priorSessionSummary,
|
|
400
|
+
},
|
|
289
401
|
habitSession: {
|
|
290
402
|
runId: currentHabitRun.runId,
|
|
291
403
|
sessionPath: currentHabitRun.paths.sessionPath,
|
|
@@ -692,24 +692,35 @@ async function runInnerDialogTurn(options) {
|
|
|
692
692
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
693
693
|
const habitName = options.habitName;
|
|
694
694
|
const habitFilePath = path.join(agentRoot, "habits", `${habitName}.md`);
|
|
695
|
+
const preparedHabit = options.preparedHabit?.habit.name === habitName ? options.preparedHabit.habit : null;
|
|
695
696
|
// Read and parse the habit file
|
|
696
697
|
let habitBody;
|
|
697
698
|
let habitTitle = habitName;
|
|
698
699
|
let habitLastRun = null;
|
|
699
700
|
let habitOrigin = null;
|
|
700
701
|
let habitSurface = { family: true, originator: true, extra: [] };
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
habitOrigin = parsed.origin;
|
|
709
|
-
habitSurface = parsed.surface;
|
|
702
|
+
if (preparedHabit) {
|
|
703
|
+
habitBody = preparedHabit.body || undefined;
|
|
704
|
+
habitTitle = preparedHabit.title || habitName;
|
|
705
|
+
habitLastRun = preparedHabit.lastRun;
|
|
706
|
+
habitTools = preparedHabit.tools;
|
|
707
|
+
habitOrigin = preparedHabit.origin;
|
|
708
|
+
habitSurface = preparedHabit.surface;
|
|
710
709
|
}
|
|
711
|
-
|
|
712
|
-
|
|
710
|
+
else {
|
|
711
|
+
try {
|
|
712
|
+
const habitContent = fs.readFileSync(habitFilePath, "utf-8");
|
|
713
|
+
const parsed = (0, habit_runtime_state_1.applyHabitRuntimeState)(agentRoot, (0, habit_parser_1.parseHabitFile)(habitContent, habitFilePath));
|
|
714
|
+
habitBody = parsed.body || undefined;
|
|
715
|
+
habitTitle = parsed.title || habitName;
|
|
716
|
+
habitLastRun = parsed.lastRun;
|
|
717
|
+
habitTools = parsed.tools;
|
|
718
|
+
habitOrigin = parsed.origin;
|
|
719
|
+
habitSurface = parsed.surface;
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
// Habit file missing or unreadable
|
|
723
|
+
}
|
|
713
724
|
}
|
|
714
725
|
// If the habit file couldn't be read at all (no body, no title parsed), error message
|
|
715
726
|
if (habitBody === undefined && habitTitle === habitName) {
|
|
@@ -753,6 +764,7 @@ async function runInnerDialogTurn(options) {
|
|
|
753
764
|
arcResume,
|
|
754
765
|
deskOrientation,
|
|
755
766
|
surfacePolicy,
|
|
767
|
+
priorSessionSummary: options.preparedHabit?.habit.name === habitName ? options.preparedHabit.priorSessionSummary : undefined,
|
|
756
768
|
now,
|
|
757
769
|
});
|
|
758
770
|
}
|
|
@@ -875,7 +887,7 @@ async function runInnerDialogTurn(options) {
|
|
|
875
887
|
runAgent: core_1.runAgent,
|
|
876
888
|
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
877
889
|
const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
|
|
878
|
-
(0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
|
|
890
|
+
return (0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
|
|
879
891
|
},
|
|
880
892
|
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
881
893
|
signal: options?.signal,
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -827,7 +827,7 @@ async function handleInboundTurn(input) {
|
|
|
827
827
|
/* v8 ignore next -- defensive: error always set when errored @preserve */
|
|
828
828
|
result.error?.message ?? "unknown error", classification, currentProvider, currentBinding.model, agentName, inventory, {}, { currentLane });
|
|
829
829
|
input.failoverState.pending = failoverContext;
|
|
830
|
-
input.postTurn(sessionMessages, session.sessionPath, result.usage);
|
|
830
|
+
await input.postTurn(sessionMessages, session.sessionPath, result.usage);
|
|
831
831
|
try {
|
|
832
832
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
833
833
|
const postTurnArc = readPostTurnFlightRecorderArcSnapshot(agentRoot);
|
|
@@ -902,7 +902,7 @@ async function handleInboundTurn(input) {
|
|
|
902
902
|
? { lastFriendActivityAt }
|
|
903
903
|
: undefined)
|
|
904
904
|
: (Object.keys(continuingState).length > 0 ? continuingState : undefined);
|
|
905
|
-
input.postTurn(sessionMessages, session.sessionPath, result.usage, undefined, nextState);
|
|
905
|
+
await input.postTurn(sessionMessages, session.sessionPath, result.usage, undefined, nextState);
|
|
906
906
|
try {
|
|
907
907
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
908
908
|
const postTurnArc = readPostTurnFlightRecorderArcSnapshot(agentRoot);
|