@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
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.createMailboxHttpRequestHandler = createMailboxHttpRequestHandler;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
+
const mailbox_http_hooks_1 = require("./mailbox-http-hooks");
|
|
39
40
|
const mailbox_http_response_1 = require("./mailbox-http-response");
|
|
40
41
|
const mailbox_http_static_1 = require("./mailbox-http-static");
|
|
41
42
|
function decodePathSegment(value) {
|
|
@@ -46,6 +47,16 @@ function decodePathSegment(value) {
|
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
function rawRequestTargetsUnsafeAgent(urlValue = "/") {
|
|
51
|
+
const rawTarget = urlValue.split(/[?#]/, 1)[0];
|
|
52
|
+
const rawPath = rawTarget.replace(/\/+$/, "") || "/";
|
|
53
|
+
const pathname = (0, mailbox_http_static_1.normalizeLegacyMailboxApiPath)(rawPath);
|
|
54
|
+
const rawAgentMatch = /^\/api\/agents\/([^/]+)(?:\/|$)/.exec(pathname);
|
|
55
|
+
if (!rawAgentMatch)
|
|
56
|
+
return false;
|
|
57
|
+
const agent = decodePathSegment(rawAgentMatch[1]);
|
|
58
|
+
return !agent || !(0, mailbox_http_hooks_1.isSafeMailboxAgentName)(agent);
|
|
59
|
+
}
|
|
49
60
|
function parseHabitRunLimit(urlValue) {
|
|
50
61
|
const rawLimit = new URL(urlValue, "http://127.0.0.1").searchParams.get("limit");
|
|
51
62
|
if (rawLimit === null)
|
|
@@ -55,9 +66,47 @@ function parseHabitRunLimit(urlValue) {
|
|
|
55
66
|
const limit = Number.parseInt(rawLimit, 10);
|
|
56
67
|
return limit >= 1 && limit <= 100 ? limit : null;
|
|
57
68
|
}
|
|
69
|
+
const VALID_HABIT_SUMMARY_WHICH = new Set(["latest", "previous", "latest-success", "latest-failure"]);
|
|
70
|
+
function firstQueryValue(params, names) {
|
|
71
|
+
for (const name of names) {
|
|
72
|
+
const value = params.get(name);
|
|
73
|
+
if (value !== null && value.trim().length > 0)
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
function parseHabitSummarySelector(urlValue) {
|
|
79
|
+
const params = new URL(urlValue, "http://127.0.0.1").searchParams;
|
|
80
|
+
const runId = firstQueryValue(params, ["runId", "run-id"]);
|
|
81
|
+
const habitName = firstQueryValue(params, ["habitName", "habit"]);
|
|
82
|
+
const operationId = firstQueryValue(params, ["operationId", "operation-id"]);
|
|
83
|
+
const which = firstQueryValue(params, ["which"]);
|
|
84
|
+
if (runId !== undefined && (habitName !== undefined || operationId !== undefined || which !== undefined)) {
|
|
85
|
+
return { ok: false, error: "--run-id cannot be combined with --habit, --operation-id, or --which" };
|
|
86
|
+
}
|
|
87
|
+
if (runId === undefined && habitName === undefined && operationId === undefined) {
|
|
88
|
+
return { ok: false, error: "provide runId, habitName, or operationId" };
|
|
89
|
+
}
|
|
90
|
+
if (which !== undefined && !VALID_HABIT_SUMMARY_WHICH.has(which)) {
|
|
91
|
+
return { ok: false, error: "which must be latest, previous, latest-success, or latest-failure" };
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
ok: true,
|
|
95
|
+
selector: {
|
|
96
|
+
...(runId ? { runId } : {}),
|
|
97
|
+
...(habitName ? { habitName } : {}),
|
|
98
|
+
...(operationId ? { operationId } : {}),
|
|
99
|
+
...(which ? { which } : {}),
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
58
103
|
function createMailboxHttpRequestHandler(options) {
|
|
59
104
|
const staticFiles = options.staticFiles ?? { resolveSpaDistDir: mailbox_http_static_1.resolveSpaDistDir, serveStaticFile: mailbox_http_static_1.serveStaticFile };
|
|
60
105
|
return (request, response) => {
|
|
106
|
+
if (rawRequestTargetsUnsafeAgent(request.url)) {
|
|
107
|
+
(0, mailbox_http_response_1.writeJson)(response, 400, { ok: false, error: "agent name must be a safe bundle name" });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
61
110
|
let pathname = (0, mailbox_http_static_1.normalizeMailboxRequestPath)(request.url);
|
|
62
111
|
const origin = `http://${options.host}:${options.getPort()}`;
|
|
63
112
|
if (pathname.startsWith("/assets/")) {
|
|
@@ -99,8 +148,14 @@ function createMailboxHttpRequestHandler(options) {
|
|
|
99
148
|
}
|
|
100
149
|
const agentMatch = /^\/api\/agents\/([^/]+)(?:\/(.+))?$/.exec(pathname);
|
|
101
150
|
if (agentMatch) {
|
|
151
|
+
const agent = decodePathSegment(agentMatch[1]);
|
|
152
|
+
/* v8 ignore next -- rawRequestTargetsUnsafeAgent rejects unsafe/malformed agent segments before normalization @preserve */
|
|
153
|
+
if (!agent || !(0, mailbox_http_hooks_1.isSafeMailboxAgentName)(agent)) {
|
|
154
|
+
(0, mailbox_http_response_1.writeJson)(response, 400, { ok: false, error: "agent name must be a safe bundle name" });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
102
157
|
void handleAgentRoute(request, response, {
|
|
103
|
-
agent
|
|
158
|
+
agent,
|
|
104
159
|
surface: agentMatch[2] ?? null,
|
|
105
160
|
options,
|
|
106
161
|
}).catch((error) => {
|
|
@@ -229,6 +284,32 @@ async function handleAgentRoute(request, response, context) {
|
|
|
229
284
|
(0, mailbox_http_response_1.writeJson)(response, 200, view);
|
|
230
285
|
return;
|
|
231
286
|
}
|
|
287
|
+
if (surface === "habit-run-summaries") {
|
|
288
|
+
const limit = parseHabitRunLimit(request.url);
|
|
289
|
+
if (limit === null) {
|
|
290
|
+
(0, mailbox_http_response_1.writeJson)(response, 400, { ok: false, error: "limit must be an integer between 1 and 100" });
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const view = limit === undefined
|
|
294
|
+
? options.hooks.readAgentHabitRunSummaries(agent)
|
|
295
|
+
: options.hooks.readAgentHabitRunSummaries(agent, { limit });
|
|
296
|
+
(0, mailbox_http_response_1.writeJson)(response, 200, view);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (surface === "habit-run-summary") {
|
|
300
|
+
const parsed = parseHabitSummarySelector(request.url);
|
|
301
|
+
if (!parsed.ok) {
|
|
302
|
+
(0, mailbox_http_response_1.writeJson)(response, 400, { ok: false, error: parsed.error });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const summary = options.hooks.readAgentHabitRunSummary(agent, parsed.selector);
|
|
306
|
+
if (!summary) {
|
|
307
|
+
(0, mailbox_http_response_1.writeJson)(response, 404, { ok: false, error: "habit summary not found" });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
(0, mailbox_http_response_1.writeJson)(response, 200, summary);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
232
313
|
if (surface.startsWith("habit-runs/")) {
|
|
233
314
|
const rawRunId = surface.slice("habit-runs/".length);
|
|
234
315
|
const runId = decodePathSegment(rawRunId);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.readSelfFixView = exports.readMailboxContinuity = exports.readOrientationView = exports.readObligationDetailView = exports.readNoteDecisionView = exports.readSentinelView = exports.readContextLossGauntletView = exports.readChangesView = exports.readNeedsMeView = exports.readNotesView = exports.readLogView = exports.readHabitView = exports.readHabitRunView = exports.readHabitRunReceiptView = exports.readFriendView = exports.readDeskPrefs = exports.readDaemonHealthDeep = exports.readCodingDeep = exports.readBridgeInventory = exports.readAttentionView = exports.readMailView = exports.readMailMessageView = exports.readSessionTranscript = exports.readSessionInventory = exports.readMailboxMachineState = exports.readMailboxAgentState = exports.readObligationSummary = void 0;
|
|
3
|
+
exports.readSelfFixView = exports.readMailboxContinuity = exports.readOrientationView = exports.readObligationDetailView = exports.readNoteDecisionView = exports.readSentinelView = exports.readContextLossGauntletView = exports.readChangesView = exports.readNeedsMeView = exports.readNotesView = exports.readLogView = exports.readHabitView = exports.readHabitRunView = exports.readHabitRunReceiptView = exports.readHabitSessionSummaryView = exports.readHabitSessionSummaryListView = exports.readFriendView = exports.readDeskPrefs = exports.readDaemonHealthDeep = exports.readCodingDeep = exports.readBridgeInventory = exports.readAttentionView = exports.readMailView = exports.readMailMessageView = exports.readSessionTranscript = exports.readSessionInventory = exports.readMailboxMachineState = exports.readMailboxAgentState = exports.readObligationSummary = void 0;
|
|
4
4
|
var agent_machine_1 = require("./readers/agent-machine");
|
|
5
5
|
Object.defineProperty(exports, "readObligationSummary", { enumerable: true, get: function () { return agent_machine_1.readObligationSummary; } });
|
|
6
6
|
Object.defineProperty(exports, "readMailboxAgentState", { enumerable: true, get: function () { return agent_machine_1.readMailboxAgentState; } });
|
|
@@ -18,6 +18,8 @@ Object.defineProperty(exports, "readCodingDeep", { enumerable: true, get: functi
|
|
|
18
18
|
Object.defineProperty(exports, "readDaemonHealthDeep", { enumerable: true, get: function () { return runtime_readers_1.readDaemonHealthDeep; } });
|
|
19
19
|
Object.defineProperty(exports, "readDeskPrefs", { enumerable: true, get: function () { return runtime_readers_1.readDeskPrefs; } });
|
|
20
20
|
Object.defineProperty(exports, "readFriendView", { enumerable: true, get: function () { return runtime_readers_1.readFriendView; } });
|
|
21
|
+
Object.defineProperty(exports, "readHabitSessionSummaryListView", { enumerable: true, get: function () { return runtime_readers_1.readHabitSessionSummaryListView; } });
|
|
22
|
+
Object.defineProperty(exports, "readHabitSessionSummaryView", { enumerable: true, get: function () { return runtime_readers_1.readHabitSessionSummaryView; } });
|
|
21
23
|
Object.defineProperty(exports, "readHabitRunReceiptView", { enumerable: true, get: function () { return runtime_readers_1.readHabitRunReceiptView; } });
|
|
22
24
|
Object.defineProperty(exports, "readHabitRunView", { enumerable: true, get: function () { return runtime_readers_1.readHabitRunView; } });
|
|
23
25
|
Object.defineProperty(exports, "readHabitView", { enumerable: true, get: function () { return runtime_readers_1.readHabitView; } });
|
|
@@ -127,14 +127,24 @@ function normalizeObligationCurrentSurface(currentSurface, updatedAt, liveCoding
|
|
|
127
127
|
&& (Date.now() - updatedAtMs) <= STALE_CODING_SURFACE_WINDOW_MS;
|
|
128
128
|
return recentlyTouched ? currentSurface : null;
|
|
129
129
|
}
|
|
130
|
+
function legacyObligationTimestamp(obligation) {
|
|
131
|
+
if (typeof obligation.updatedAt === "string")
|
|
132
|
+
return obligation.updatedAt;
|
|
133
|
+
if (typeof obligation.createdAt === "string")
|
|
134
|
+
return obligation.createdAt;
|
|
135
|
+
return "";
|
|
136
|
+
}
|
|
137
|
+
function legacyObligationStatus(obligation) {
|
|
138
|
+
return typeof obligation.status === "string" ? obligation.status : "pending";
|
|
139
|
+
}
|
|
130
140
|
function readObligationSummary(agentRoot) {
|
|
131
141
|
const liveCodingSurfaceLabels = buildLiveCodingSurfaceLabels(agentRoot);
|
|
132
142
|
const items = (0, obligations_1.readPendingObligations)(agentRoot)
|
|
133
143
|
.map((obligation) => {
|
|
134
|
-
const updatedAt = obligation
|
|
144
|
+
const updatedAt = legacyObligationTimestamp(obligation);
|
|
135
145
|
return {
|
|
136
146
|
id: obligation.id,
|
|
137
|
-
status: obligation
|
|
147
|
+
status: legacyObligationStatus(obligation),
|
|
138
148
|
content: obligation.content,
|
|
139
149
|
updatedAt,
|
|
140
150
|
nextAction: obligation.nextAction ?? null,
|
|
@@ -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();
|
package/dist/heart/work-card.js
CHANGED
|
@@ -134,14 +134,18 @@ function scanArcSourceIssues(agentRoot) {
|
|
|
134
134
|
}
|
|
135
135
|
function obligationItem(obligation) {
|
|
136
136
|
const freshness = obligation.meaning?.stalenessClass === "at-risk" ? "stale_risky" : "current";
|
|
137
|
+
const status = typeof obligation.status === "string" ? obligation.status : "pending";
|
|
138
|
+
const updatedAt = typeof obligation.updatedAt === "string"
|
|
139
|
+
? obligation.updatedAt
|
|
140
|
+
: typeof obligation.createdAt === "string" ? obligation.createdAt : undefined;
|
|
137
141
|
return {
|
|
138
142
|
id: obligation.id,
|
|
139
143
|
title: obligation.content,
|
|
140
|
-
status
|
|
144
|
+
status,
|
|
141
145
|
source: source("obligation", obligationLocator(obligation.id), freshness),
|
|
142
146
|
...(obligation.latestNote ? { summary: obligation.latestNote } : {}),
|
|
143
147
|
...(obligation.nextAction ? { nextAction: obligation.nextAction } : {}),
|
|
144
|
-
updatedAt
|
|
148
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
145
149
|
};
|
|
146
150
|
}
|
|
147
151
|
function returnObligationItem(obligation) {
|
|
@@ -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,
|