@ouro.bot/cli 0.1.0-alpha.665 → 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 +13 -0
- package/dist/arc/flight-recorder.js +324 -5
- package/dist/heart/core.js +167 -4
- package/dist/heart/cross-chat-delivery.js +3 -2
- package/dist/heart/daemon/cli-exec.js +139 -1
- package/dist/heart/daemon/cli-help.js +13 -2
- package/dist/heart/daemon/cli-parse.js +138 -2
- package/dist/heart/daemon/daemon-entry.js +24 -5
- package/dist/heart/daemon/daemon.js +10 -1
- package/dist/heart/habits/habit-parser.js +8 -0
- package/dist/heart/habits/habit-runtime-state.js +17 -3
- package/dist/heart/habits/habit-scheduler.js +24 -5
- package/dist/heart/habits/habit-session-summary.js +318 -0
- package/dist/heart/habits/habit-session.js +618 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +29 -1
- package/dist/heart/mailbox/mailbox-http-routes.js +122 -1
- package/dist/heart/mailbox/mailbox-read.js +5 -1
- package/dist/heart/mailbox/readers/runtime-readers.js +87 -0
- package/dist/mailbox-ui/assets/index-CaTIFDmv.js +1 -0
- package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
- package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
- package/dist/mailbox-ui/index.html +3 -2
- package/dist/repertoire/tools-notes.js +50 -0
- package/dist/repertoire/tools-record.js +13 -0
- package/dist/repertoire/tools-session.js +140 -0
- package/dist/repertoire/tools-surface.js +11 -0
- package/dist/repertoire/tools.js +7 -0
- package/dist/senses/habit-turn-message.js +41 -3
- package/dist/senses/inner-dialog-worker.js +264 -68
- package/dist/senses/inner-dialog.js +29 -15
- package/dist/senses/pipeline.js +2 -11
- package/dist/senses/surface-tool.js +2 -1
- package/package.json +1 -1
- package/dist/mailbox-ui/assets/index-BZ60na8O.js +0 -61
- package/dist/mailbox-ui/assets/index-DG6Xf5uL.css +0 -1
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
<meta name="color-scheme" content="dark" />
|
|
7
7
|
<title>Ouro Mailbox</title>
|
|
8
8
|
<meta name="description" content="The daemon-hosted shared orientation surface for agents alive on this machine." />
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-CaTIFDmv.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-CcN1XpQ9.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Du_9G9WO.css">
|
|
11
12
|
</head>
|
|
12
13
|
<body>
|
|
13
14
|
<div id="app"></div>
|
|
@@ -1,13 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.notesToolDefinitions = void 0;
|
|
4
37
|
const child_process_1 = require("child_process");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
5
39
|
const skills_1 = require("./skills");
|
|
6
40
|
const config_1 = require("../heart/config");
|
|
7
41
|
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
const identity_1 = require("../heart/identity");
|
|
8
43
|
const diary_1 = require("../mind/diary");
|
|
44
|
+
const record_paths_1 = require("../mind/record-paths");
|
|
9
45
|
const provenance_trust_1 = require("../mind/provenance-trust");
|
|
10
46
|
const CLAUDE_READ_ONLY_TOOLS = "Read,Grep,Glob,LS";
|
|
47
|
+
function bundleRelativeLocator(filePath, ctx) {
|
|
48
|
+
const agentRoot = ctx?.agentRoot ?? (0, identity_1.getAgentRoot)();
|
|
49
|
+
const relativePath = path.relative(agentRoot, filePath);
|
|
50
|
+
if (relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
51
|
+
return relativePath.split(path.sep).join("/");
|
|
52
|
+
}
|
|
53
|
+
return filePath.split(path.sep).join("/");
|
|
54
|
+
}
|
|
11
55
|
exports.notesToolDefinitions = [
|
|
12
56
|
{
|
|
13
57
|
tool: {
|
|
@@ -252,6 +296,12 @@ exports.notesToolDefinitions = [
|
|
|
252
296
|
about: typeof a.about === "string" ? a.about : undefined,
|
|
253
297
|
provenance,
|
|
254
298
|
});
|
|
299
|
+
if (result.added > 0) {
|
|
300
|
+
ctx?.habitSession?.recordProducedRef?.({
|
|
301
|
+
kind: "desk_record",
|
|
302
|
+
locator: bundleRelativeLocator(path.join((0, record_paths_1.resolveRecordDiaryRoot)(), "facts.jsonl"), ctx),
|
|
303
|
+
});
|
|
304
|
+
}
|
|
255
305
|
return `saved diary entry (added=${result.added}, skipped=${result.skipped})`;
|
|
256
306
|
},
|
|
257
307
|
summaryKeys: ["entry", "about"],
|
|
@@ -42,6 +42,7 @@ const note_search_1 = require("../mind/note-search");
|
|
|
42
42
|
const record_paths_1 = require("../mind/record-paths");
|
|
43
43
|
const types_1 = require("../mind/friends/types");
|
|
44
44
|
const runtime_1 = require("../nerves/runtime");
|
|
45
|
+
const identity_1 = require("../heart/identity");
|
|
45
46
|
const NOTES_INDEX_VERSION = 1;
|
|
46
47
|
const NOTE_SLUG_MAX_CHARS = 40;
|
|
47
48
|
const DEFAULT_LIMIT = 5;
|
|
@@ -61,6 +62,14 @@ function hasRecordReadTrust(ctx) {
|
|
|
61
62
|
const friend = ctx?.context?.friend;
|
|
62
63
|
return Boolean(friend) && (0, types_1.isTrustedLevel)(friend?.trustLevel);
|
|
63
64
|
}
|
|
65
|
+
function bundleRelativeLocator(filePath, ctx) {
|
|
66
|
+
const agentRoot = ctx?.agentRoot ?? (0, identity_1.getAgentRoot)();
|
|
67
|
+
const relativePath = path.relative(agentRoot, filePath);
|
|
68
|
+
if (relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
69
|
+
return relativePath.split(path.sep).join("/");
|
|
70
|
+
}
|
|
71
|
+
return filePath.split(path.sep).join("/");
|
|
72
|
+
}
|
|
64
73
|
function normalizeTags(value) {
|
|
65
74
|
if (value === undefined || value === null)
|
|
66
75
|
return undefined;
|
|
@@ -371,6 +380,10 @@ exports.recordToolDefinitions = [
|
|
|
371
380
|
const savedPath = ensureUniquePath(notesDir, date, slugForContent(content));
|
|
372
381
|
fs.writeFileSync(savedPath, renderNote(createdAt, cappedContent, tags), "utf8");
|
|
373
382
|
await updateIndexForSavedNote(notesDir, indexPath, savedPath, provider);
|
|
383
|
+
ctx?.habitSession?.recordProducedRef?.({
|
|
384
|
+
kind: "desk_record",
|
|
385
|
+
locator: bundleRelativeLocator(savedPath, ctx),
|
|
386
|
+
});
|
|
374
387
|
(0, runtime_1.emitNervesEvent)({
|
|
375
388
|
component: "repertoire",
|
|
376
389
|
event: "repertoire.record_note_saved",
|
|
@@ -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`;
|
|
@@ -127,6 +202,19 @@ function renderCrossChatDeliveryStatus(target, result) {
|
|
|
127
202
|
outcomeText: `${lead}\n${result.detail}`,
|
|
128
203
|
}));
|
|
129
204
|
}
|
|
205
|
+
function normalizeHabitSendStatus(status) {
|
|
206
|
+
return status === "queued_for_later" ? "queued" : status;
|
|
207
|
+
}
|
|
208
|
+
function recordHabitSendAttempt(ctx, args, result) {
|
|
209
|
+
ctx?.habitSession?.recordSurfaceAttempt?.({
|
|
210
|
+
recipient: args.friendId,
|
|
211
|
+
channel: args.channel,
|
|
212
|
+
reason: result.status === "blocked" ? "blocked" : result.status === "failed" ? "other" : "status",
|
|
213
|
+
result: normalizeHabitSendStatus(result.status),
|
|
214
|
+
rawStatus: result.rawStatus ?? result.status,
|
|
215
|
+
...(result.status === "blocked" || result.status === "failed" ? { error: result.detail } : {}),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
130
218
|
async function deliverVoiceChannelMessage(request, agentName, initialAudio) {
|
|
131
219
|
const result = await (0, outbound_1.placeTrustedFriendVoiceOutboundCall)({
|
|
132
220
|
agentName,
|
|
@@ -356,6 +444,57 @@ exports.sessionToolDefinitions = [
|
|
|
356
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)}`;
|
|
357
445
|
},
|
|
358
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
|
+
},
|
|
359
498
|
{
|
|
360
499
|
tool: {
|
|
361
500
|
type: "function",
|
|
@@ -724,6 +863,7 @@ exports.sessionToolDefinitions = [
|
|
|
724
863
|
voice: async (request) => deliverVoiceChannelMessage(request, agentName, voiceInitialAudio),
|
|
725
864
|
},
|
|
726
865
|
});
|
|
866
|
+
recordHabitSendAttempt(ctx, { friendId, channel, key }, deliveryResult);
|
|
727
867
|
return renderCrossChatDeliveryStatus(`${friendId} on ${channel}/${key}`, deliveryResult);
|
|
728
868
|
},
|
|
729
869
|
riskProfile: sendMessageRiskProfile,
|
|
@@ -332,6 +332,17 @@ exports.surfaceToolDefinition = {
|
|
|
332
332
|
}
|
|
333
333
|
/* v8 ignore stop */
|
|
334
334
|
},
|
|
335
|
+
onRouteResult: ({ targetFriendId, queueItem, result }) => {
|
|
336
|
+
ctx?.habitSession?.recordSurfaceAttempt?.({
|
|
337
|
+
recipient: targetFriendId,
|
|
338
|
+
channel: queueItem?.channel ?? args.channel ?? "surface",
|
|
339
|
+
reason: result.status === "failed" ? "blocked" : "answer",
|
|
340
|
+
result: result.status,
|
|
341
|
+
rawStatus: result.status,
|
|
342
|
+
...(queueItem ? { routeKind: "originator" } : {}),
|
|
343
|
+
...(result.status === "failed" ? { error: result.detail ?? "surface route failed" } : {}),
|
|
344
|
+
});
|
|
345
|
+
},
|
|
335
346
|
});
|
|
336
347
|
},
|
|
337
348
|
summaryKeys: ["content", "delegationId", "friendId", "channel"],
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.surfaceToolDef = exports.speakTool = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
|
|
4
4
|
exports.resetMcpDefinitions = resetMcpDefinitions;
|
|
5
5
|
exports.getToolsForChannel = getToolsForChannel;
|
|
6
|
+
exports.shellRiskProfile = shellRiskProfile;
|
|
7
|
+
exports.riskProfileForTool = riskProfileForTool;
|
|
8
|
+
exports.riskProfileForToolName = riskProfileForToolName;
|
|
6
9
|
exports.execTool = execTool;
|
|
7
10
|
exports.summarizeArgs = summarizeArgs;
|
|
8
11
|
exports.buildToolResultSummary = buildToolResultSummary;
|
|
@@ -202,6 +205,10 @@ function riskProfileForTool(def, name, args) {
|
|
|
202
205
|
return def.riskProfile(args);
|
|
203
206
|
return def.riskProfile ?? { mutates: "none", risk: "low" };
|
|
204
207
|
}
|
|
208
|
+
function riskProfileForToolName(name, args) {
|
|
209
|
+
const def = findDefinition(name);
|
|
210
|
+
return def ? riskProfileForTool(def, name, args) : null;
|
|
211
|
+
}
|
|
205
212
|
function orientationHoldMessage(name, profile, reason) {
|
|
206
213
|
return `orientation hold: ${reason} Available: orientation_get plus read-only inspection tools like trip_status, query_session, read_config, read_file, grep, search_facts, consult_diary, and consult_notes. Resolve the referent/correction, then retry ${name} if the action is still correct. Blocked ${mutationKindsFor(profile).join(", ")}. ${profile.reason}.`;
|
|
207
214
|
}
|
|
@@ -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) {
|