@ouro.bot/cli 0.1.0-alpha.665 → 0.1.0-alpha.666
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 +6 -0
- package/dist/arc/flight-recorder.js +267 -4
- package/dist/heart/core.js +167 -4
- package/dist/heart/cross-chat-delivery.js +3 -2
- package/dist/heart/daemon/cli-exec.js +50 -1
- package/dist/heart/daemon/cli-help.js +1 -1
- package/dist/heart/daemon/cli-parse.js +36 -2
- package/dist/heart/daemon/daemon-entry.js +24 -5
- package/dist/heart/daemon/daemon.js +10 -1
- package/dist/heart/habits/habit-scheduler.js +24 -5
- package/dist/heart/habits/habit-session.js +563 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +2 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +40 -0
- package/dist/heart/mailbox/mailbox-read.js +3 -1
- package/dist/heart/mailbox/readers/runtime-readers.js +56 -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 +14 -0
- package/dist/repertoire/tools-surface.js +11 -0
- package/dist/repertoire/tools.js +7 -0
- package/dist/senses/inner-dialog-worker.js +153 -69
- package/dist/senses/inner-dialog.js +5 -3
- package/dist/senses/pipeline.js +0 -9
- 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",
|
|
@@ -127,6 +127,19 @@ function renderCrossChatDeliveryStatus(target, result) {
|
|
|
127
127
|
outcomeText: `${lead}\n${result.detail}`,
|
|
128
128
|
}));
|
|
129
129
|
}
|
|
130
|
+
function normalizeHabitSendStatus(status) {
|
|
131
|
+
return status === "queued_for_later" ? "queued" : status;
|
|
132
|
+
}
|
|
133
|
+
function recordHabitSendAttempt(ctx, args, result) {
|
|
134
|
+
ctx?.habitSession?.recordSurfaceAttempt?.({
|
|
135
|
+
recipient: args.friendId,
|
|
136
|
+
channel: args.channel,
|
|
137
|
+
reason: result.status === "blocked" ? "blocked" : result.status === "failed" ? "other" : "status",
|
|
138
|
+
result: normalizeHabitSendStatus(result.status),
|
|
139
|
+
rawStatus: result.rawStatus ?? result.status,
|
|
140
|
+
...(result.status === "blocked" || result.status === "failed" ? { error: result.detail } : {}),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
130
143
|
async function deliverVoiceChannelMessage(request, agentName, initialAudio) {
|
|
131
144
|
const result = await (0, outbound_1.placeTrustedFriendVoiceOutboundCall)({
|
|
132
145
|
agentName,
|
|
@@ -724,6 +737,7 @@ exports.sessionToolDefinitions = [
|
|
|
724
737
|
voice: async (request) => deliverVoiceChannelMessage(request, agentName, voiceInitialAudio),
|
|
725
738
|
},
|
|
726
739
|
});
|
|
740
|
+
recordHabitSendAttempt(ctx, { friendId, channel, key }, deliveryResult);
|
|
727
741
|
return renderCrossChatDeliveryStatus(`${friendId} on ${channel}/${key}`, deliveryResult);
|
|
728
742
|
},
|
|
729
743
|
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
|
}
|
|
@@ -36,13 +36,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.HEARTBEAT_OK_REST_SUPPRESSION_MS = exports.HABIT_RECURSION_BURST_THRESHOLD = exports.HABIT_RECURSION_BURST_WINDOW_MS = exports.HABIT_RECURSION_MIN_INTERVAL_MS = exports.MAX_CONSECUTIVE_INSTINCT_TURNS = void 0;
|
|
37
37
|
exports.createInnerDialogWorker = createInnerDialogWorker;
|
|
38
38
|
exports.startInnerDialogWorker = startInnerDialogWorker;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
39
40
|
const path = __importStar(require("path"));
|
|
40
41
|
const inner_dialog_1 = require("./inner-dialog");
|
|
41
42
|
const runtime_1 = require("../nerves/runtime");
|
|
42
43
|
const identity_1 = require("../heart/identity");
|
|
43
44
|
const pending_1 = require("../mind/pending");
|
|
44
|
-
const
|
|
45
|
+
const habit_parser_1 = require("../heart/habits/habit-parser");
|
|
46
|
+
const habit_session_1 = require("../heart/habits/habit-session");
|
|
45
47
|
const flight_recorder_1 = require("../arc/flight-recorder");
|
|
48
|
+
const store_file_1 = require("../mind/friends/store-file");
|
|
49
|
+
const tools_base_1 = require("../repertoire/tools-base");
|
|
50
|
+
const tools_surface_1 = require("../repertoire/tools-surface");
|
|
51
|
+
const tools_1 = require("../repertoire/tools");
|
|
46
52
|
/**
|
|
47
53
|
* Cap on consecutive `instinct` follow-on turns triggered by `hasPendingWork()`
|
|
48
54
|
* with no externally-queued work in between. Without this cap, a turn that
|
|
@@ -83,62 +89,87 @@ function isHeartbeatOkRestResult(result) {
|
|
|
83
89
|
const maybeResult = result;
|
|
84
90
|
return maybeResult.turnOutcome === "rested" && maybeResult.restStatus === "HEARTBEAT_OK";
|
|
85
91
|
}
|
|
86
|
-
function
|
|
92
|
+
function fallbackHabitFile(habitName) {
|
|
93
|
+
return {
|
|
94
|
+
name: habitName,
|
|
95
|
+
title: habitName,
|
|
96
|
+
cadence: null,
|
|
97
|
+
status: "active",
|
|
98
|
+
lastRun: null,
|
|
99
|
+
created: null,
|
|
100
|
+
tools: [],
|
|
101
|
+
origin: null,
|
|
102
|
+
surface: { family: false, originator: false, extra: [] },
|
|
103
|
+
body: "",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function readHabitForRun(agentRoot, habitName, errors) {
|
|
107
|
+
const habitPath = path.join(agentRoot, "habits", `${habitName}.md`);
|
|
108
|
+
try {
|
|
109
|
+
return (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
113
|
+
errors.push(`habit file could not be read: ${reason}`);
|
|
114
|
+
(0, runtime_1.emitNervesEvent)({
|
|
115
|
+
level: "warn",
|
|
116
|
+
component: "senses",
|
|
117
|
+
event: "senses.habit_file_read_error",
|
|
118
|
+
message: "habit file could not be read for habit session",
|
|
119
|
+
meta: { habitName, habitPath, reason },
|
|
120
|
+
});
|
|
121
|
+
return fallbackHabitFile(habitName);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function prepareHabitRun(habitName, trigger, startedAt) {
|
|
125
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
126
|
+
const errors = [];
|
|
127
|
+
const habit = readHabitForRun(agentRoot, habitName, errors);
|
|
128
|
+
const runId = (0, flight_recorder_1.createHabitRunId)(habitName, new Date(startedAt));
|
|
129
|
+
const paths = (0, habit_session_1.createHabitSessionPaths)(agentRoot, runId, habit.name);
|
|
130
|
+
const friendStore = new store_file_1.FileFriendStore(path.join(agentRoot, "friends"));
|
|
131
|
+
const permissionEnvelope = await (0, habit_session_1.normalizeHabitPermissionEnvelope)(habit, { agentRoot, friendStore });
|
|
132
|
+
const toolPolicy = (0, habit_session_1.filterHabitToolsForEnvelope)([...tools_base_1.baseToolDefinitions, tools_surface_1.surfaceToolDefinition], habit.tools ?? null, permissionEnvelope, riskProfileForHabitPolicy);
|
|
133
|
+
return {
|
|
134
|
+
agentRoot,
|
|
135
|
+
habit,
|
|
136
|
+
runId,
|
|
137
|
+
trigger,
|
|
138
|
+
startedAt,
|
|
139
|
+
paths,
|
|
140
|
+
permissionEnvelope,
|
|
141
|
+
toolPolicy,
|
|
142
|
+
friendStore,
|
|
143
|
+
results: [],
|
|
144
|
+
errors,
|
|
145
|
+
producedRefs: [],
|
|
146
|
+
surfaceAttempts: [],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function riskProfileForHabitPolicy(definition, name) {
|
|
150
|
+
const probeArgs = name === "shell" ? { command: "touch /tmp/habit-policy-probe" } : {};
|
|
151
|
+
return (0, tools_1.riskProfileForTool)(definition, name, probeArgs);
|
|
152
|
+
}
|
|
153
|
+
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()) {
|
|
87
154
|
let running = false;
|
|
88
155
|
const queue = [];
|
|
89
156
|
const lastFireByHabit = new Map();
|
|
90
157
|
const recentHabitFires = [];
|
|
91
158
|
let heartbeatOkRestedAt = null;
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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 = []) {
|
|
120
|
-
try {
|
|
121
|
-
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
122
|
-
(0, habit_runtime_state_1.recordHabitRun)(agentRoot, habitName, endedAt, {
|
|
123
|
-
definitionPath: path.join(agentRoot, "habits", `${habitName}.md`),
|
|
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
|
-
});
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
// Habit file/state may be unavailable during the turn — skip gracefully
|
|
141
|
-
}
|
|
159
|
+
function recordHabitCompletion(habitRun, endedAt = habitRun.startedAt) {
|
|
160
|
+
(0, habit_session_1.completeHabitRun)({
|
|
161
|
+
agentRoot: habitRun.agentRoot,
|
|
162
|
+
habit: habitRun.habit,
|
|
163
|
+
runId: habitRun.runId,
|
|
164
|
+
trigger: habitRun.trigger,
|
|
165
|
+
startedAt: habitRun.startedAt,
|
|
166
|
+
endedAt,
|
|
167
|
+
permissionEnvelope: habitRun.permissionEnvelope,
|
|
168
|
+
toolPolicy: habitRun.toolPolicy,
|
|
169
|
+
producedRefs: habitRun.producedRefs,
|
|
170
|
+
surfaceAttempts: habitRun.surfaceAttempts,
|
|
171
|
+
errors: habitRun.errors,
|
|
172
|
+
});
|
|
142
173
|
}
|
|
143
174
|
function clearHeartbeatRestShield() {
|
|
144
175
|
heartbeatOkRestedAt = null;
|
|
@@ -154,9 +185,11 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
154
185
|
}
|
|
155
186
|
return true;
|
|
156
187
|
}
|
|
157
|
-
function reuseHeartbeatOkRest(habitName) {
|
|
188
|
+
async function reuseHeartbeatOkRest(habitName) {
|
|
158
189
|
const nowIso = new Date(nowSource()).toISOString();
|
|
159
|
-
|
|
190
|
+
const habitRun = await prepareHabitRun(habitName, "overdue", nowIso);
|
|
191
|
+
habitRun.results.push({ turnOutcome: "rested", restStatus: "HEARTBEAT_OK" });
|
|
192
|
+
recordHabitCompletion(habitRun, nowIso);
|
|
160
193
|
(0, runtime_1.emitNervesEvent)({
|
|
161
194
|
level: "info",
|
|
162
195
|
component: "senses",
|
|
@@ -220,19 +253,54 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
220
253
|
let nextHabitName = habitName;
|
|
221
254
|
let nextAwaitName = awaitName;
|
|
222
255
|
let nextTrigger = trigger;
|
|
256
|
+
let nextHabitRun = null;
|
|
223
257
|
let consecutiveInstinctTurns = reason === "instinct" ? 1 : 0;
|
|
224
258
|
runLoop: do {
|
|
225
259
|
const currentReason = nextReason;
|
|
226
260
|
const currentHabitName = nextHabitName;
|
|
227
261
|
const currentTrigger = nextTrigger ?? "overdue";
|
|
228
|
-
const
|
|
262
|
+
const currentHabitRun = currentReason === "habit" && currentHabitName
|
|
263
|
+
? nextHabitRun && nextHabitRun.habit.name === currentHabitName
|
|
264
|
+
? nextHabitRun
|
|
265
|
+
: await prepareHabitRun(currentHabitName, currentTrigger, new Date(nowSource()).toISOString())
|
|
266
|
+
: null;
|
|
267
|
+
nextHabitRun = null;
|
|
268
|
+
let currentHabitRunFinalized = false;
|
|
269
|
+
const finalizeCurrentHabitRun = () => {
|
|
270
|
+
if (!currentHabitRun || currentHabitRunFinalized)
|
|
271
|
+
return;
|
|
272
|
+
recordHabitCompletion(currentHabitRun, new Date(nowSource()).toISOString());
|
|
273
|
+
currentHabitRunFinalized = true;
|
|
274
|
+
};
|
|
229
275
|
const turnErrors = [];
|
|
230
276
|
if (!(currentReason === "habit" && currentHabitName === "heartbeat")) {
|
|
231
277
|
clearHeartbeatRestShield();
|
|
232
278
|
}
|
|
233
279
|
let turnResult;
|
|
234
280
|
try {
|
|
235
|
-
|
|
281
|
+
const turnOptions = {
|
|
282
|
+
reason: nextReason,
|
|
283
|
+
taskId: nextTaskId,
|
|
284
|
+
habitName: nextHabitName,
|
|
285
|
+
awaitName: nextAwaitName,
|
|
286
|
+
...(currentHabitRun
|
|
287
|
+
? {
|
|
288
|
+
trigger: currentHabitRun.trigger,
|
|
289
|
+
habitSession: {
|
|
290
|
+
runId: currentHabitRun.runId,
|
|
291
|
+
sessionPath: currentHabitRun.paths.sessionPath,
|
|
292
|
+
pendingDir: currentHabitRun.paths.pendingDir,
|
|
293
|
+
permissionEnvelope: currentHabitRun.permissionEnvelope,
|
|
294
|
+
toolPolicy: currentHabitRun.toolPolicy,
|
|
295
|
+
friendStore: currentHabitRun.friendStore,
|
|
296
|
+
recordProducedRef: (ref) => { currentHabitRun.producedRefs.push(ref); },
|
|
297
|
+
recordSurfaceAttempt: (attempt) => { currentHabitRun.surfaceAttempts.push(attempt); },
|
|
298
|
+
recordError: (error) => { currentHabitRun.errors.push(error); },
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
: {}),
|
|
302
|
+
};
|
|
303
|
+
turnResult = await runTurn(turnOptions);
|
|
236
304
|
}
|
|
237
305
|
catch (error) {
|
|
238
306
|
clearHeartbeatRestShield();
|
|
@@ -251,21 +319,23 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
251
319
|
if (currentReason === "habit" && currentHabitName === "heartbeat") {
|
|
252
320
|
heartbeatOkRestedAt = isHeartbeatOkRestResult(turnResult) ? nowSource() : null;
|
|
253
321
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
322
|
+
if (currentHabitRun) {
|
|
323
|
+
currentHabitRun.results.push(turnResult);
|
|
324
|
+
currentHabitRun.errors.push(...turnErrors);
|
|
257
325
|
}
|
|
258
326
|
// Drain queue first. Externally-queued work resets the instinct cap
|
|
259
327
|
// because a real outside trigger arrived between turns.
|
|
260
328
|
while (queue.length > 0) {
|
|
261
329
|
const next = queue.shift();
|
|
262
330
|
if (next.reason === "habit" && next.habitName === "heartbeat" && shouldReuseHeartbeatOkRest(next.habitName)) {
|
|
263
|
-
|
|
331
|
+
finalizeCurrentHabitRun();
|
|
332
|
+
await reuseHeartbeatOkRest(next.habitName);
|
|
264
333
|
continue;
|
|
265
334
|
}
|
|
266
335
|
if (!(next.reason === "habit" && next.habitName === "heartbeat")) {
|
|
267
336
|
clearHeartbeatRestShield();
|
|
268
337
|
}
|
|
338
|
+
finalizeCurrentHabitRun();
|
|
269
339
|
nextReason = next.reason;
|
|
270
340
|
nextTaskId = next.taskId;
|
|
271
341
|
nextHabitName = next.habitName;
|
|
@@ -278,7 +348,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
278
348
|
// tool that writes to the inner-dialog pending dir during a turn
|
|
279
349
|
// would cause hasPendingWork() to be true here, producing a
|
|
280
350
|
// self-sustaining "instinct" loop with no external input. Cap it.
|
|
281
|
-
if (hasPendingWork()) {
|
|
351
|
+
if (hasPendingWork(currentHabitRun?.paths.pendingDir)) {
|
|
282
352
|
clearHeartbeatRestShield();
|
|
283
353
|
if (consecutiveInstinctTurns >= exports.MAX_CONSECUTIVE_INSTINCT_TURNS) {
|
|
284
354
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -292,16 +362,30 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
292
362
|
lastReason: nextReason,
|
|
293
363
|
},
|
|
294
364
|
});
|
|
365
|
+
finalizeCurrentHabitRun();
|
|
295
366
|
break;
|
|
296
367
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
368
|
+
if (currentReason === "habit" && currentHabitName && currentHabitRun) {
|
|
369
|
+
consecutiveInstinctTurns += 1;
|
|
370
|
+
nextReason = "habit";
|
|
371
|
+
nextTaskId = undefined;
|
|
372
|
+
nextHabitName = currentHabitName;
|
|
373
|
+
nextAwaitName = undefined;
|
|
374
|
+
nextTrigger = currentTrigger;
|
|
375
|
+
nextHabitRun = currentHabitRun;
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
finalizeCurrentHabitRun();
|
|
379
|
+
consecutiveInstinctTurns += 1;
|
|
380
|
+
nextReason = "instinct";
|
|
381
|
+
nextTaskId = undefined;
|
|
382
|
+
nextHabitName = undefined;
|
|
383
|
+
nextAwaitName = undefined;
|
|
384
|
+
nextTrigger = undefined;
|
|
385
|
+
}
|
|
303
386
|
continue;
|
|
304
387
|
}
|
|
388
|
+
finalizeCurrentHabitRun();
|
|
305
389
|
break;
|
|
306
390
|
} while (true);
|
|
307
391
|
}
|
|
@@ -317,7 +401,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
317
401
|
/* v8 ignore next -- defensive fallback: live habit dispatch always sets habitName @preserve */
|
|
318
402
|
const habitName = maybeMessage.habitName ?? "(unnamed)";
|
|
319
403
|
if (shouldReuseHeartbeatOkRest(habitName)) {
|
|
320
|
-
reuseHeartbeatOkRest(habitName);
|
|
404
|
+
await reuseHeartbeatOkRest(habitName);
|
|
321
405
|
return;
|
|
322
406
|
}
|
|
323
407
|
recordHabitFireForRecursion(habitName);
|
|
@@ -335,7 +419,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
335
419
|
if (maybeMessage.type === "heartbeat") {
|
|
336
420
|
// Backward compatibility: heartbeat -> habit/heartbeat
|
|
337
421
|
if (shouldReuseHeartbeatOkRest("heartbeat")) {
|
|
338
|
-
reuseHeartbeatOkRest("heartbeat");
|
|
422
|
+
await reuseHeartbeatOkRest("heartbeat");
|
|
339
423
|
return;
|
|
340
424
|
}
|
|
341
425
|
recordHabitFireForRecursion("heartbeat");
|
|
@@ -645,7 +645,7 @@ function buildHabitSurfacePolicy(origin, surface) {
|
|
|
645
645
|
async function runInnerDialogTurn(options) {
|
|
646
646
|
const now = options?.now ?? (() => new Date());
|
|
647
647
|
const reason = options?.reason ?? "instinct";
|
|
648
|
-
const sessionFilePath = innerDialogSessionPath();
|
|
648
|
+
const sessionFilePath = options?.habitSession?.sessionPath ?? innerDialogSessionPath();
|
|
649
649
|
const agentName = (0, identity_1.getAgentName)();
|
|
650
650
|
writeInnerDialogRuntimeState(sessionFilePath, {
|
|
651
651
|
status: "running",
|
|
@@ -661,7 +661,7 @@ async function runInnerDialogTurn(options) {
|
|
|
661
661
|
resting: false,
|
|
662
662
|
lastHeartbeatAt: now().toISOString(),
|
|
663
663
|
};
|
|
664
|
-
const pendingDir = (0, pending_1.getInnerDialogPendingDir)(agentName);
|
|
664
|
+
const pendingDir = options?.habitSession?.pendingDir ?? (0, pending_1.getInnerDialogPendingDir)(agentName);
|
|
665
665
|
const shouldUseHeldReturnWake = !options?.taskId && reason !== "habit" && reason !== "await"
|
|
666
666
|
? (0, obligations_1.listActiveReturnObligations)(agentName).length > 0
|
|
667
667
|
: false;
|
|
@@ -669,7 +669,7 @@ async function runInnerDialogTurn(options) {
|
|
|
669
669
|
let userContent;
|
|
670
670
|
let habitTools;
|
|
671
671
|
let habitParsedSuccessfully = false;
|
|
672
|
-
if (existingMessages.length === 0) {
|
|
672
|
+
if (existingMessages.length === 0 && !(reason === "habit" && options?.habitName)) {
|
|
673
673
|
// Fresh session: bootstrap message with non-canonical cleanup nudge
|
|
674
674
|
const aspirations = readAspirations((0, identity_1.getAgentRoot)());
|
|
675
675
|
const nonCanonical = (0, bundle_manifest_1.findNonCanonicalBundlePaths)((0, identity_1.getAgentRoot)());
|
|
@@ -917,7 +917,9 @@ async function runInnerDialogTurn(options) {
|
|
|
917
917
|
toolContext: {
|
|
918
918
|
signin: async () => undefined,
|
|
919
919
|
delegatedOrigins: attentionQueue,
|
|
920
|
+
...(options?.habitSession ? { habitSession: options.habitSession } : {}),
|
|
920
921
|
},
|
|
922
|
+
...(options?.habitSession ? { habitSession: options.habitSession } : {}),
|
|
921
923
|
},
|
|
922
924
|
});
|
|
923
925
|
// Post-turn routeDelegatedCompletion removed: delivery is now inline via surface tool.
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -48,7 +48,6 @@ const continuity_1 = require("./continuity");
|
|
|
48
48
|
const manager_1 = require("../heart/bridges/manager");
|
|
49
49
|
const identity_1 = require("../heart/identity");
|
|
50
50
|
const auth_flow_1 = require("../heart/auth/auth-flow");
|
|
51
|
-
const socket_client_1 = require("../heart/daemon/socket-client");
|
|
52
51
|
const active_work_1 = require("../heart/active-work");
|
|
53
52
|
const delegation_1 = require("../heart/delegation");
|
|
54
53
|
const obligations_1 = require("../arc/obligations");
|
|
@@ -949,14 +948,6 @@ async function handleInboundTurn(input) {
|
|
|
949
948
|
friendId: resolvedContext.friend.id,
|
|
950
949
|
},
|
|
951
950
|
});
|
|
952
|
-
// DRY cross-session awareness: notify inner dialog that activity happened on another channel
|
|
953
|
-
// Inner dialog's next checkpoint will include this session's state
|
|
954
|
-
if (input.channel !== "inner") {
|
|
955
|
-
try {
|
|
956
|
-
(0, socket_client_1.requestInnerWake)((0, identity_1.getAgentName)(), existingToolContext?.daemonSocketPath).catch(/* v8 ignore next */ () => { });
|
|
957
|
-
}
|
|
958
|
-
catch { /* getAgentName may fail in test environments */ }
|
|
959
|
-
}
|
|
960
951
|
return {
|
|
961
952
|
resolvedContext,
|
|
962
953
|
gateResult,
|
|
@@ -4,7 +4,7 @@ exports.handleSurface = handleSurface;
|
|
|
4
4
|
const attention_queue_1 = require("./attention-queue");
|
|
5
5
|
const runtime_1 = require("../nerves/runtime");
|
|
6
6
|
async function handleSurface(input) {
|
|
7
|
-
const { content, delegationId, friendId, deliveryHint, queue, routeToFriend, advanceObligation, completePonderPacket, fulfillHeartObligation, } = input;
|
|
7
|
+
const { content, delegationId, friendId, deliveryHint, queue, routeToFriend, advanceObligation, completePonderPacket, fulfillHeartObligation, onRouteResult, } = input;
|
|
8
8
|
// Resolve target friend
|
|
9
9
|
let targetFriendId;
|
|
10
10
|
let queueItem;
|
|
@@ -68,6 +68,7 @@ async function handleSurface(input) {
|
|
|
68
68
|
...(result.detail ? { detail: result.detail } : {}),
|
|
69
69
|
},
|
|
70
70
|
});
|
|
71
|
+
onRouteResult?.({ targetFriendId, queueItem, result });
|
|
71
72
|
// On successful routing with delegationId:
|
|
72
73
|
// 1. Advance obligation to "returned" (disk FIRST — crash safety)
|
|
73
74
|
// 2. Dequeue from process-local queue (AFTER obligation advance)
|