@ouro.bot/cli 0.1.0-alpha.655 → 0.1.0-alpha.658
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/changelog.json +21 -0
- package/dist/arc/evolution.js +1 -1
- package/dist/arc/flight-recorder.js +369 -0
- package/dist/arc/obligations.js +24 -2
- package/dist/heart/active-work.js +1 -1
- package/dist/heart/config-registry.js +14 -5
- package/dist/heart/daemon/agent-config-check.js +1 -1
- package/dist/heart/daemon/agent-service.js +18 -17
- package/dist/heart/daemon/cli-exec.js +134 -15
- package/dist/heart/daemon/cli-help.js +21 -2
- package/dist/heart/daemon/cli-parse.js +31 -3
- package/dist/heart/daemon/daemon-entry.js +1 -1
- package/dist/heart/daemon/daemon.js +3 -3
- package/dist/heart/daemon/hooks/bundle-meta.js +29 -9
- package/dist/heart/daemon/inner-status.js +4 -15
- package/dist/heart/daemon/sense-manager.js +16 -1
- package/dist/heart/habits/habit-parser.js +64 -1
- package/dist/heart/hatch/hatch-flow.js +17 -9
- package/dist/heart/hatch/specialist-tools.js +15 -11
- package/dist/heart/identity.js +4 -1
- package/dist/heart/kept-notes.js +5 -73
- package/dist/heart/mailbox/readers/runtime-readers.js +21 -49
- package/dist/heart/mcp/mcp-server.js +8 -8
- package/dist/heart/sense-truth.js +2 -0
- package/dist/heart/session-events.js +1 -31
- package/dist/heart/start-of-turn-packet.js +8 -2
- package/dist/heart/tool-description.js +15 -3
- package/dist/heart/turn-context.js +34 -7
- package/dist/heart/work-card.js +386 -0
- package/dist/mailbox-ui/assets/{index-9-AxCxuB.js → index-Cbasiy6y.js} +1 -1
- package/dist/mailbox-ui/index.html +1 -1
- package/dist/mind/bundle-manifest.js +9 -3
- package/dist/mind/context.js +1 -2
- package/dist/mind/desk-section.js +53 -1
- package/dist/mind/diary.js +2 -3
- package/dist/mind/note-search.js +36 -106
- package/dist/mind/prompt.js +45 -102
- package/dist/mind/record-paths.js +312 -0
- package/dist/repertoire/bundle-templates.js +4 -5
- package/dist/repertoire/tools-bundle.js +1 -1
- package/dist/repertoire/tools-evolution.js +4 -4
- package/dist/repertoire/tools-notes.js +42 -62
- package/dist/repertoire/tools-record.js +16 -11
- package/dist/repertoire/tools-session.js +4 -4
- package/dist/repertoire/tools.js +1 -1
- package/dist/senses/habit-turn-message.js +19 -5
- package/dist/senses/inner-dialog-worker.js +58 -9
- package/dist/senses/inner-dialog.js +30 -11
- package/dist/senses/pipeline.js +135 -1
- package/dist/util/frontmatter.js +17 -1
- package/package.json +3 -3
- package/skills/configure-dev-tools.md +1 -1
- package/skills/travel-planning.md +1 -1
- package/dist/mind/journal-index.js +0 -162
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - Build artifacts (rare in bundles, but possible).
|
|
15
15
|
*
|
|
16
16
|
* It DOES NOT handle PII. The bundle is inherently full of PII — `friends/`,
|
|
17
|
-
* `
|
|
17
|
+
* `desk/_record/`, `psyche/`, `arc/`, `facts/`, `family/`, `travel/`
|
|
18
18
|
* etc. That's the point of the bundle; blocking those via .gitignore would
|
|
19
19
|
* defeat the purpose.
|
|
20
20
|
*
|
|
@@ -54,19 +54,18 @@ node_modules/
|
|
|
54
54
|
dist/
|
|
55
55
|
`;
|
|
56
56
|
/**
|
|
57
|
-
* PII-sensitive
|
|
57
|
+
* PII-sensitive bundle directories. Enumerated here so `bundle_first_push_review`
|
|
58
58
|
* can categorize and count. Adding a new PII bucket to the bundle means adding
|
|
59
59
|
* it here so the first-push warning includes it.
|
|
60
60
|
*/
|
|
61
61
|
exports.PII_BUNDLE_DIRECTORIES = [
|
|
62
62
|
"friends",
|
|
63
|
-
"diary",
|
|
64
|
-
"
|
|
63
|
+
"desk/_record/diary",
|
|
64
|
+
"desk/_record/notes",
|
|
65
65
|
"psyche",
|
|
66
66
|
"arc",
|
|
67
67
|
"facts",
|
|
68
68
|
"family",
|
|
69
69
|
"travel",
|
|
70
|
-
"notes",
|
|
71
70
|
"sessions",
|
|
72
71
|
];
|
|
@@ -944,7 +944,7 @@ exports.bundleToolDefinitions = [
|
|
|
944
944
|
type: "function",
|
|
945
945
|
function: {
|
|
946
946
|
name: "bundle_first_push_review",
|
|
947
|
-
description: "Review my bundle for PII exposure before the first push to a new remote. Enumerates PII-bearing directories (friends,
|
|
947
|
+
description: "Review my bundle for PII exposure before the first push to a new remote. Enumerates PII-bearing directories (friends, Desk record, Arc, etc.) with per-directory counts, probes the remote URL for GitHub public/private visibility, and returns a first-person warning text I must show the human plus a confirmationToken I must pass to bundle_push on first push. Required before the first push to any new remote.",
|
|
948
948
|
parameters: { type: "object", properties: {} },
|
|
949
949
|
},
|
|
950
950
|
},
|
|
@@ -8,7 +8,7 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
8
8
|
const EVOLUTION_ACTIONS = new Set([
|
|
9
9
|
"create_case",
|
|
10
10
|
"add_evidence",
|
|
11
|
-
"
|
|
11
|
+
"write_record",
|
|
12
12
|
"write_desk",
|
|
13
13
|
"write_diary",
|
|
14
14
|
"spawn_coding",
|
|
@@ -46,7 +46,7 @@ const EVIDENCE_KINDS = new Set([
|
|
|
46
46
|
"release",
|
|
47
47
|
"installed_runtime",
|
|
48
48
|
"diary_entry",
|
|
49
|
-
"
|
|
49
|
+
"desk_record_note",
|
|
50
50
|
"skill_file",
|
|
51
51
|
"sense_artifact",
|
|
52
52
|
"hosted_audit",
|
|
@@ -54,7 +54,7 @@ const EVIDENCE_KINDS = new Set([
|
|
|
54
54
|
"external_doc",
|
|
55
55
|
]);
|
|
56
56
|
const REDACTIONS = new Set(["none", "summary", "private_ref", "secret_ref"]);
|
|
57
|
-
const DECISIONS = new Set(["ignore", "defer", "
|
|
57
|
+
const DECISIONS = new Set(["ignore", "defer", "record", "ask", "delegate", "act", "abandon"]);
|
|
58
58
|
const VERIFICATION_STATUSES = new Set(["not-verified", "partial", "passed", "failed"]);
|
|
59
59
|
const RATIFICATION_DESTINATIONS = new Set([
|
|
60
60
|
"code",
|
|
@@ -63,7 +63,7 @@ const RATIFICATION_DESTINATIONS = new Set([
|
|
|
63
63
|
"desk_lesson",
|
|
64
64
|
"desk_task",
|
|
65
65
|
"diary",
|
|
66
|
-
"
|
|
66
|
+
"desk_record",
|
|
67
67
|
"habit",
|
|
68
68
|
"policy",
|
|
69
69
|
"agent_config",
|
|
@@ -1,45 +1,9 @@
|
|
|
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
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.notesToolDefinitions = void 0;
|
|
37
|
-
const fs = __importStar(require("fs"));
|
|
38
|
-
const path = __importStar(require("path"));
|
|
39
4
|
const child_process_1 = require("child_process");
|
|
40
5
|
const skills_1 = require("./skills");
|
|
41
6
|
const config_1 = require("../heart/config");
|
|
42
|
-
const identity_1 = require("../heart/identity");
|
|
43
7
|
const runtime_1 = require("../nerves/runtime");
|
|
44
8
|
const diary_1 = require("../mind/diary");
|
|
45
9
|
const provenance_trust_1 = require("../mind/provenance-trust");
|
|
@@ -170,8 +134,8 @@ exports.notesToolDefinitions = [
|
|
|
170
134
|
tool: {
|
|
171
135
|
type: "function",
|
|
172
136
|
function: {
|
|
173
|
-
name: "
|
|
174
|
-
description: "Search my
|
|
137
|
+
name: "search_facts",
|
|
138
|
+
description: "Search my Desk record diary facts matching a query. Uses semantic similarity -- phrasing matters. Try different angles if the first query doesn't find what you're looking for. Search written facts before asking the human something the record may already answer.",
|
|
175
139
|
parameters: {
|
|
176
140
|
type: "object",
|
|
177
141
|
properties: { query: { type: "string" } },
|
|
@@ -185,7 +149,6 @@ exports.notesToolDefinitions = [
|
|
|
185
149
|
if (!query)
|
|
186
150
|
return "query is required";
|
|
187
151
|
const resultLines = [];
|
|
188
|
-
// Search diary entries
|
|
189
152
|
const hits = await (0, diary_1.searchDiaryEntries)(query, (0, diary_1.readDiaryEntries)());
|
|
190
153
|
for (const fact of hits) {
|
|
191
154
|
let meta = `source=${fact.source}, createdAt=${fact.createdAt}`;
|
|
@@ -200,35 +163,52 @@ exports.notesToolDefinitions = [
|
|
|
200
163
|
const tag = (0, provenance_trust_1.classifyProvenanceTrust)(fact.provenance) === "external" ? "diary/external" : "diary";
|
|
201
164
|
resultLines.push(`[${tag}] ${fact.text} (${meta})`);
|
|
202
165
|
}
|
|
203
|
-
// Search journal index
|
|
204
|
-
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
205
|
-
const journalIndexPath = path.join(agentRoot, "journal", ".index.json");
|
|
206
|
-
try {
|
|
207
|
-
const raw = fs.readFileSync(journalIndexPath, "utf8");
|
|
208
|
-
const journalEntries = JSON.parse(raw);
|
|
209
|
-
if (Array.isArray(journalEntries) && journalEntries.length > 0) {
|
|
210
|
-
// Substring match on preview and filename
|
|
211
|
-
const lowerQuery = query.toLowerCase();
|
|
212
|
-
for (const entry of journalEntries) {
|
|
213
|
-
/* v8 ignore next 4 -- both sides tested (filename-only match in search_notes-journal.test.ts); v8 misreports || short-circuit @preserve */
|
|
214
|
-
if (entry.preview.toLowerCase().includes(lowerQuery) ||
|
|
215
|
-
entry.filename.toLowerCase().includes(lowerQuery)) {
|
|
216
|
-
resultLines.push(`[journal] ${entry.filename}: ${entry.preview}`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
// No journal index or malformed — skip journal search
|
|
223
|
-
}
|
|
224
166
|
return resultLines.join("\n");
|
|
225
167
|
}
|
|
226
168
|
catch (e) {
|
|
227
|
-
return `error: ${e instanceof Error ? e.message : String(e)}`;
|
|
169
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
228
170
|
}
|
|
229
171
|
},
|
|
230
172
|
summaryKeys: ["query"],
|
|
231
173
|
},
|
|
174
|
+
{
|
|
175
|
+
tool: {
|
|
176
|
+
type: "function",
|
|
177
|
+
function: {
|
|
178
|
+
name: "consult_diary",
|
|
179
|
+
description: "Inspect my Desk record diary facts. With a query, searches semantically. Without a query, returns the most recent facts. Use this for direct record inspection before asking someone to restate something already written down.",
|
|
180
|
+
parameters: {
|
|
181
|
+
type: "object",
|
|
182
|
+
properties: {
|
|
183
|
+
query: { type: "string" },
|
|
184
|
+
limit: { type: "string" },
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
handler: async (a) => {
|
|
190
|
+
try {
|
|
191
|
+
const limitRaw = a.limit ? Number.parseInt(String(a.limit), 10) : 10;
|
|
192
|
+
const limit = Number.isFinite(limitRaw) ? Math.min(50, Math.max(1, limitRaw)) : 10;
|
|
193
|
+
const query = typeof a.query === "string" ? a.query.trim() : "";
|
|
194
|
+
const facts = (0, diary_1.readDiaryEntries)();
|
|
195
|
+
const matches = query ? await (0, diary_1.searchDiaryEntries)(query, facts) : [...facts].sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
196
|
+
const items = matches.slice(0, limit).map((fact) => ({
|
|
197
|
+
id: fact.id,
|
|
198
|
+
text: fact.text,
|
|
199
|
+
source: fact.source,
|
|
200
|
+
createdAt: fact.createdAt,
|
|
201
|
+
...(fact.about ? { about: fact.about } : {}),
|
|
202
|
+
...(fact.provenance ? { provenance: fact.provenance } : {}),
|
|
203
|
+
}));
|
|
204
|
+
return JSON.stringify({ items });
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
summaryKeys: ["query", "limit"],
|
|
211
|
+
},
|
|
232
212
|
{
|
|
233
213
|
tool: {
|
|
234
214
|
type: "function",
|
|
@@ -275,7 +255,7 @@ exports.notesToolDefinitions = [
|
|
|
275
255
|
return `saved diary entry (added=${result.added}, skipped=${result.skipped})`;
|
|
276
256
|
},
|
|
277
257
|
summaryKeys: ["entry", "about"],
|
|
278
|
-
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "writes diary
|
|
258
|
+
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "writes Desk record diary fact" },
|
|
279
259
|
},
|
|
280
260
|
{
|
|
281
261
|
tool: {
|
|
@@ -37,9 +37,10 @@ exports.recordToolDefinitions = void 0;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const session_events_1 = require("../heart/session-events");
|
|
40
|
-
const identity_1 = require("../heart/identity");
|
|
41
40
|
const embedding_provider_1 = require("../mind/embedding-provider");
|
|
42
41
|
const note_search_1 = require("../mind/note-search");
|
|
42
|
+
const record_paths_1 = require("../mind/record-paths");
|
|
43
|
+
const types_1 = require("../mind/friends/types");
|
|
43
44
|
const runtime_1 = require("../nerves/runtime");
|
|
44
45
|
const NOTES_INDEX_VERSION = 1;
|
|
45
46
|
const NOTE_SLUG_MAX_CHARS = 40;
|
|
@@ -54,6 +55,12 @@ function hasSelfTrust(ctx) {
|
|
|
54
55
|
const friend = ctx?.context?.friend;
|
|
55
56
|
return !friend || friend.id === "self";
|
|
56
57
|
}
|
|
58
|
+
function hasRecordReadTrust(ctx) {
|
|
59
|
+
if (hasSelfTrust(ctx))
|
|
60
|
+
return true;
|
|
61
|
+
const friend = ctx?.context?.friend;
|
|
62
|
+
return Boolean(friend) && (0, types_1.isTrustedLevel)(friend?.trustLevel);
|
|
63
|
+
}
|
|
57
64
|
function normalizeTags(value) {
|
|
58
65
|
if (value === undefined || value === null)
|
|
59
66
|
return undefined;
|
|
@@ -224,8 +231,6 @@ function indexFreshForRecords(index, records) {
|
|
|
224
231
|
const recordsByFilename = new Map(records.map((record) => [record.filename, record]));
|
|
225
232
|
const seenFilenames = new Set();
|
|
226
233
|
for (const entry of index.entries) {
|
|
227
|
-
if (seenFilenames.has(entry.filename))
|
|
228
|
-
return false;
|
|
229
234
|
const record = recordsByFilename.get(entry.filename);
|
|
230
235
|
if (!record || !entryMatchesRecord(entry, record))
|
|
231
236
|
return false;
|
|
@@ -331,7 +336,7 @@ exports.recordToolDefinitions = [
|
|
|
331
336
|
type: "function",
|
|
332
337
|
function: {
|
|
333
338
|
name: "note",
|
|
334
|
-
description: "Write a durable self note as canonical markdown in my notes
|
|
339
|
+
description: "Write a durable self note as canonical markdown in my Desk record notes. Only available to my self/inner context, not external callers.",
|
|
335
340
|
parameters: {
|
|
336
341
|
type: "object",
|
|
337
342
|
properties: {
|
|
@@ -359,9 +364,9 @@ exports.recordToolDefinitions = [
|
|
|
359
364
|
const createdAt = new Date().toISOString();
|
|
360
365
|
const date = createdAt.slice(0, 10);
|
|
361
366
|
const tags = normalizeTags(rawArgs.tags);
|
|
362
|
-
const notesDir = path.join((0, identity_1.getAgentRoot)(), "notes");
|
|
363
|
-
const indexPath = path.join(notesDir, ".index.json");
|
|
364
367
|
try {
|
|
368
|
+
const notesDir = (0, record_paths_1.resolveRecordNotesRoot)();
|
|
369
|
+
const indexPath = path.join(notesDir, ".index.json");
|
|
365
370
|
fs.mkdirSync(notesDir, { recursive: true });
|
|
366
371
|
const savedPath = ensureUniquePath(notesDir, date, slugForContent(content));
|
|
367
372
|
fs.writeFileSync(savedPath, renderNote(createdAt, cappedContent, tags), "utf8");
|
|
@@ -386,14 +391,14 @@ exports.recordToolDefinitions = [
|
|
|
386
391
|
}
|
|
387
392
|
},
|
|
388
393
|
summaryKeys: ["content", "tags"],
|
|
389
|
-
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "writes canonical note
|
|
394
|
+
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "writes canonical Desk record note" },
|
|
390
395
|
},
|
|
391
396
|
{
|
|
392
397
|
tool: {
|
|
393
398
|
type: "function",
|
|
394
399
|
function: {
|
|
395
400
|
name: "consult_notes",
|
|
396
|
-
description: "Search my canonical markdown notes semantically using the notes-native index.
|
|
401
|
+
description: "Search my canonical markdown Desk record notes semantically using the notes-native index. Read-only orientation lookup for trusted callers.",
|
|
397
402
|
parameters: {
|
|
398
403
|
type: "object",
|
|
399
404
|
properties: {
|
|
@@ -407,13 +412,13 @@ exports.recordToolDefinitions = [
|
|
|
407
412
|
},
|
|
408
413
|
},
|
|
409
414
|
handler: async (args, ctx) => {
|
|
410
|
-
if (!
|
|
411
|
-
return "error: consult_notes requires
|
|
415
|
+
if (!hasRecordReadTrust(ctx))
|
|
416
|
+
return "error: consult_notes requires trusted record-read access.";
|
|
412
417
|
const rawArgs = args;
|
|
413
418
|
const query = typeof rawArgs.query === "string" ? rawArgs.query.trim() : "";
|
|
414
419
|
if (!query)
|
|
415
420
|
return JSON.stringify({ items: [] });
|
|
416
|
-
const notesDir =
|
|
421
|
+
const notesDir = (0, record_paths_1.resolveRecordNotesRoot)();
|
|
417
422
|
const indexPath = path.join(notesDir, ".index.json");
|
|
418
423
|
const records = listCanonicalNotes(notesDir);
|
|
419
424
|
if (records.length === 0)
|
|
@@ -361,7 +361,7 @@ exports.sessionToolDefinitions = [
|
|
|
361
361
|
type: "function",
|
|
362
362
|
function: {
|
|
363
363
|
name: "query_session",
|
|
364
|
-
description: "inspect another session. use transcript for recent context or status for self/inner progress. deprecated search invocations should use
|
|
364
|
+
description: "inspect another session. use transcript for recent context or status for self/inner progress. deprecated search invocations should use search_facts, consult_diary, or consult_notes instead.",
|
|
365
365
|
parameters: {
|
|
366
366
|
type: "object",
|
|
367
367
|
properties: {
|
|
@@ -372,9 +372,9 @@ exports.sessionToolDefinitions = [
|
|
|
372
372
|
mode: {
|
|
373
373
|
type: "string",
|
|
374
374
|
enum: ["transcript", "status", "search"],
|
|
375
|
-
description: "transcript (default), lightweight status for self/inner checks, or deprecated search; use
|
|
375
|
+
description: "transcript (default), lightweight status for self/inner checks, or deprecated search; use search_facts, consult_diary, or consult_notes instead",
|
|
376
376
|
},
|
|
377
|
-
query: { type: "string", description: "deprecated when mode=search; use
|
|
377
|
+
query: { type: "string", description: "deprecated when mode=search; use search_facts, consult_diary, or consult_notes instead" },
|
|
378
378
|
},
|
|
379
379
|
required: ["friendId", "channel"],
|
|
380
380
|
},
|
|
@@ -405,7 +405,7 @@ exports.sessionToolDefinitions = [
|
|
|
405
405
|
if (mode === "search") {
|
|
406
406
|
return JSON.stringify({
|
|
407
407
|
kind: "deprecated",
|
|
408
|
-
message: "query_session mode=search is no longer available; use
|
|
408
|
+
message: "query_session mode=search is no longer available; use search_facts, consult_diary, or consult_notes instead.",
|
|
409
409
|
removalCycle: "alpha.616",
|
|
410
410
|
});
|
|
411
411
|
}
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -203,7 +203,7 @@ function riskProfileForTool(def, name, args) {
|
|
|
203
203
|
return def.riskProfile ?? { mutates: "none", risk: "low" };
|
|
204
204
|
}
|
|
205
205
|
function orientationHoldMessage(name, profile, reason) {
|
|
206
|
-
return `orientation hold: ${reason} Available: orientation_get plus read-only inspection tools like trip_status, query_session, read_config, read_file, grep, and
|
|
206
|
+
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
207
|
}
|
|
208
208
|
function mutationKindsFor(profile) {
|
|
209
209
|
const mutates = profile.mutates;
|
|
@@ -11,8 +11,9 @@ function formatElapsed(ms) {
|
|
|
11
11
|
return `${hours} ${hours === 1 ? "hour" : "hours"}`;
|
|
12
12
|
}
|
|
13
13
|
function buildHabitTurnMessage(options) {
|
|
14
|
-
const { habitName, habitTitle, habitBody, lastRun, checkpoint, alsoDue, staleObligations, parseErrors, degradedComponents, now, } = options;
|
|
14
|
+
const { habitName, habitTitle, habitBody, lastRun, checkpoint, alsoDue, staleObligations, parseErrors, degradedComponents, arcResume, deskOrientation, surfacePolicy, now, } = options;
|
|
15
15
|
const hasBody = habitBody !== undefined && habitBody !== "";
|
|
16
|
+
const leadingSections = buildLeadingSections(arcResume, deskOrientation, surfacePolicy);
|
|
16
17
|
// First beat: lastRun is null
|
|
17
18
|
if (lastRun === null) {
|
|
18
19
|
// Cold start: no checkpoint, no body — bare awareness
|
|
@@ -23,7 +24,7 @@ function buildHabitTurnMessage(options) {
|
|
|
23
24
|
message: "habit turn message built (cold start)",
|
|
24
25
|
meta: { habitName, coldStart: true },
|
|
25
26
|
});
|
|
26
|
-
return "...time passing. anything stirring?";
|
|
27
|
+
return joinSections(leadingSections, ["...time passing. anything stirring?"]);
|
|
27
28
|
}
|
|
28
29
|
if (!hasBody) {
|
|
29
30
|
// First beat with no body: nudge
|
|
@@ -37,7 +38,7 @@ function buildHabitTurnMessage(options) {
|
|
|
37
38
|
message: "habit turn message built (first beat, no body)",
|
|
38
39
|
meta: { habitName, firstBeat: true, hasBody: false },
|
|
39
40
|
});
|
|
40
|
-
return sections
|
|
41
|
+
return joinSections(leadingSections, sections);
|
|
41
42
|
}
|
|
42
43
|
const sections = [
|
|
43
44
|
`your ${habitTitle} is alive. this is its first breath.`,
|
|
@@ -50,7 +51,7 @@ function buildHabitTurnMessage(options) {
|
|
|
50
51
|
message: "habit turn message built (first beat)",
|
|
51
52
|
meta: { habitName, firstBeat: true },
|
|
52
53
|
});
|
|
53
|
-
return sections
|
|
54
|
+
return joinSections(leadingSections, sections);
|
|
54
55
|
}
|
|
55
56
|
// Normal turn
|
|
56
57
|
const sections = [];
|
|
@@ -83,7 +84,20 @@ function buildHabitTurnMessage(options) {
|
|
|
83
84
|
staleObligationCount: staleObligations.length,
|
|
84
85
|
},
|
|
85
86
|
});
|
|
86
|
-
return sections
|
|
87
|
+
return joinSections(leadingSections, sections);
|
|
88
|
+
}
|
|
89
|
+
function buildLeadingSections(arcResume, deskOrientation, surfacePolicy) {
|
|
90
|
+
const sections = [];
|
|
91
|
+
if (arcResume?.trim())
|
|
92
|
+
sections.push(arcResume.trim());
|
|
93
|
+
if (deskOrientation?.trim())
|
|
94
|
+
sections.push(deskOrientation.trim());
|
|
95
|
+
if (surfacePolicy?.trim())
|
|
96
|
+
sections.push(surfacePolicy.trim());
|
|
97
|
+
return sections;
|
|
98
|
+
}
|
|
99
|
+
function joinSections(leadingSections, sections) {
|
|
100
|
+
return [...leadingSections, ...sections].filter((section) => section.trim().length > 0).join("\n\n");
|
|
87
101
|
}
|
|
88
102
|
function appendTrailingExtras(sections, alsoDue, staleObligations, parseErrors, degradedComponents) {
|
|
89
103
|
// 4. Also-due
|
|
@@ -42,6 +42,7 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
42
42
|
const identity_1 = require("../heart/identity");
|
|
43
43
|
const pending_1 = require("../mind/pending");
|
|
44
44
|
const habit_runtime_state_1 = require("../heart/habits/habit-runtime-state");
|
|
45
|
+
const flight_recorder_1 = require("../arc/flight-recorder");
|
|
45
46
|
/**
|
|
46
47
|
* Cap on consecutive `instinct` follow-on turns triggered by `hasPendingWork()`
|
|
47
48
|
* with no externally-queued work in between. Without this cap, a turn that
|
|
@@ -88,12 +89,52 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
88
89
|
const lastFireByHabit = new Map();
|
|
89
90
|
const recentHabitFires = [];
|
|
90
91
|
let heartbeatOkRestedAt = null;
|
|
91
|
-
function
|
|
92
|
+
function habitOutcomeForTurn(result, errors) {
|
|
93
|
+
if (errors.length > 0)
|
|
94
|
+
return { outcome: "error", producedRefs: [] };
|
|
95
|
+
const toolNames = new Set();
|
|
96
|
+
if (result && typeof result === "object" && Array.isArray(result.messages)) {
|
|
97
|
+
for (const message of result.messages) {
|
|
98
|
+
const toolCalls = message.tool_calls;
|
|
99
|
+
if (!Array.isArray(toolCalls))
|
|
100
|
+
continue;
|
|
101
|
+
for (const call of toolCalls) {
|
|
102
|
+
const functionName = call.function?.name;
|
|
103
|
+
if (typeof functionName === "string")
|
|
104
|
+
toolNames.add(functionName);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (toolNames.has("send_message") || toolNames.has("surface")) {
|
|
109
|
+
return { outcome: "surfaced", producedRefs: [{ kind: "surface", locator: "tool:send_message_or_surface" }] };
|
|
110
|
+
}
|
|
111
|
+
if (toolNames.has("diary_write") || toolNames.has("note")) {
|
|
112
|
+
return { outcome: "wrote_record", producedRefs: [{ kind: "desk_record", locator: "desk/_record" }] };
|
|
113
|
+
}
|
|
114
|
+
if ([...toolNames].some((name) => name.startsWith("mcp__desk__"))) {
|
|
115
|
+
return { outcome: "updated_desk", producedRefs: [{ kind: "desk_task", locator: "desk/" }] };
|
|
116
|
+
}
|
|
117
|
+
return { outcome: "no_change", producedRefs: [] };
|
|
118
|
+
}
|
|
119
|
+
function recordHabitCompletion(habitName, startedAt = new Date(nowSource()).toISOString(), endedAt = startedAt, trigger = "overdue", result, errors = []) {
|
|
92
120
|
try {
|
|
93
121
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
94
|
-
(0, habit_runtime_state_1.recordHabitRun)(agentRoot, habitName,
|
|
122
|
+
(0, habit_runtime_state_1.recordHabitRun)(agentRoot, habitName, endedAt, {
|
|
95
123
|
definitionPath: path.join(agentRoot, "habits", `${habitName}.md`),
|
|
96
124
|
});
|
|
125
|
+
const { outcome, producedRefs } = habitOutcomeForTurn(result, errors);
|
|
126
|
+
(0, flight_recorder_1.writeHabitRunReceipt)(agentRoot, {
|
|
127
|
+
schemaVersion: 1,
|
|
128
|
+
runId: (0, flight_recorder_1.createHabitRunId)(habitName, new Date(startedAt)),
|
|
129
|
+
habitName,
|
|
130
|
+
trigger,
|
|
131
|
+
startedAt,
|
|
132
|
+
endedAt,
|
|
133
|
+
outcome,
|
|
134
|
+
producedRefs,
|
|
135
|
+
surfaceAttempts: [],
|
|
136
|
+
errors,
|
|
137
|
+
});
|
|
97
138
|
}
|
|
98
139
|
catch {
|
|
99
140
|
// Habit file/state may be unavailable during the turn — skip gracefully
|
|
@@ -114,7 +155,8 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
114
155
|
return true;
|
|
115
156
|
}
|
|
116
157
|
function reuseHeartbeatOkRest(habitName) {
|
|
117
|
-
|
|
158
|
+
const nowIso = new Date(nowSource()).toISOString();
|
|
159
|
+
recordHabitCompletion(habitName, nowIso, nowIso, "overdue", { turnOutcome: "rested", restStatus: "HEARTBEAT_OK" });
|
|
118
160
|
(0, runtime_1.emitNervesEvent)({
|
|
119
161
|
level: "info",
|
|
120
162
|
component: "senses",
|
|
@@ -166,9 +208,9 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
166
208
|
});
|
|
167
209
|
}
|
|
168
210
|
}
|
|
169
|
-
async function run(reason, taskId, habitName, awaitName) {
|
|
211
|
+
async function run(reason, taskId, habitName, awaitName, trigger) {
|
|
170
212
|
if (running) {
|
|
171
|
-
queue.push({ reason, taskId, habitName, awaitName });
|
|
213
|
+
queue.push({ reason, taskId, habitName, awaitName, trigger });
|
|
172
214
|
return;
|
|
173
215
|
}
|
|
174
216
|
running = true;
|
|
@@ -177,10 +219,14 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
177
219
|
let nextTaskId = taskId;
|
|
178
220
|
let nextHabitName = habitName;
|
|
179
221
|
let nextAwaitName = awaitName;
|
|
222
|
+
let nextTrigger = trigger;
|
|
180
223
|
let consecutiveInstinctTurns = reason === "instinct" ? 1 : 0;
|
|
181
224
|
runLoop: do {
|
|
182
225
|
const currentReason = nextReason;
|
|
183
226
|
const currentHabitName = nextHabitName;
|
|
227
|
+
const currentTrigger = nextTrigger ?? "overdue";
|
|
228
|
+
const habitStartedAt = currentReason === "habit" && currentHabitName ? new Date(nowSource()).toISOString() : null;
|
|
229
|
+
const turnErrors = [];
|
|
184
230
|
if (!(currentReason === "habit" && currentHabitName === "heartbeat")) {
|
|
185
231
|
clearHeartbeatRestShield();
|
|
186
232
|
}
|
|
@@ -190,6 +236,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
190
236
|
}
|
|
191
237
|
catch (error) {
|
|
192
238
|
clearHeartbeatRestShield();
|
|
239
|
+
turnErrors.push(error instanceof Error ? error.message : String(error));
|
|
193
240
|
(0, runtime_1.emitNervesEvent)({
|
|
194
241
|
level: "error",
|
|
195
242
|
component: "senses",
|
|
@@ -205,8 +252,8 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
205
252
|
heartbeatOkRestedAt = isHeartbeatOkRestResult(turnResult) ? nowSource() : null;
|
|
206
253
|
}
|
|
207
254
|
// Record lastRun after a habit turn without dirtying the tracked habit file.
|
|
208
|
-
if (
|
|
209
|
-
recordHabitCompletion(
|
|
255
|
+
if (currentReason === "habit" && currentHabitName && habitStartedAt) {
|
|
256
|
+
recordHabitCompletion(currentHabitName, habitStartedAt, new Date(nowSource()).toISOString(), currentTrigger, turnResult, turnErrors);
|
|
210
257
|
}
|
|
211
258
|
// Drain queue first. Externally-queued work resets the instinct cap
|
|
212
259
|
// because a real outside trigger arrived between turns.
|
|
@@ -223,6 +270,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
223
270
|
nextTaskId = next.taskId;
|
|
224
271
|
nextHabitName = next.habitName;
|
|
225
272
|
nextAwaitName = next.awaitName;
|
|
273
|
+
nextTrigger = next.trigger;
|
|
226
274
|
consecutiveInstinctTurns = nextReason === "instinct" ? consecutiveInstinctTurns + 1 : 0;
|
|
227
275
|
continue runLoop;
|
|
228
276
|
}
|
|
@@ -251,6 +299,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
251
299
|
nextTaskId = undefined;
|
|
252
300
|
nextHabitName = undefined;
|
|
253
301
|
nextAwaitName = undefined;
|
|
302
|
+
nextTrigger = undefined;
|
|
254
303
|
continue;
|
|
255
304
|
}
|
|
256
305
|
break;
|
|
@@ -272,7 +321,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
272
321
|
return;
|
|
273
322
|
}
|
|
274
323
|
recordHabitFireForRecursion(habitName);
|
|
275
|
-
await run("habit", undefined, maybeMessage.habitName);
|
|
324
|
+
await run("habit", undefined, maybeMessage.habitName, undefined, maybeMessage.trigger ?? "overdue");
|
|
276
325
|
return;
|
|
277
326
|
}
|
|
278
327
|
if (maybeMessage.type === "await") {
|
|
@@ -290,7 +339,7 @@ function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runIn
|
|
|
290
339
|
return;
|
|
291
340
|
}
|
|
292
341
|
recordHabitFireForRecursion("heartbeat");
|
|
293
|
-
await run("habit", undefined, "heartbeat");
|
|
342
|
+
await run("habit", undefined, "heartbeat", undefined, "overdue");
|
|
294
343
|
return;
|
|
295
344
|
}
|
|
296
345
|
if (maybeMessage.type === "poke") {
|