@ouro.bot/cli 0.1.0-alpha.657 → 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 +9 -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 +5 -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 +27 -12
- package/dist/heart/daemon/cli-help.js +14 -0
- package/dist/heart/daemon/cli-parse.js +26 -0
- 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/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/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/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 +27 -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 +37 -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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Agent service layer — handles MCP-facing daemon commands.
|
|
4
4
|
* Each handler receives { agent, friendId, ...params } and returns DaemonResponse.
|
|
5
5
|
*
|
|
6
|
-
* DRY: uses the same shared functions the agent's own tools use (diary, session transcript).
|
|
6
|
+
* DRY: uses the same shared functions the agent's own tools use (Desk record diary, session transcript).
|
|
7
7
|
* This file is a thin adapter — no reimplemented search, parsing, or state reading.
|
|
8
8
|
*/
|
|
9
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
@@ -45,7 +45,7 @@ exports.handleAgentAsk = handleAgentAsk;
|
|
|
45
45
|
exports.handleAgentCatchup = handleAgentCatchup;
|
|
46
46
|
exports.handleAgentDelegate = handleAgentDelegate;
|
|
47
47
|
exports.handleAgentGetContext = handleAgentGetContext;
|
|
48
|
-
exports.
|
|
48
|
+
exports.handleAgentSearchFacts = handleAgentSearchFacts;
|
|
49
49
|
exports.handleAgentGetTask = handleAgentGetTask;
|
|
50
50
|
exports.handleAgentCheckScope = handleAgentCheckScope;
|
|
51
51
|
exports.handleAgentRequestDecision = handleAgentRequestDecision;
|
|
@@ -57,9 +57,10 @@ const fs = __importStar(require("fs"));
|
|
|
57
57
|
const path = __importStar(require("path"));
|
|
58
58
|
const identity_1 = require("../identity");
|
|
59
59
|
const diary_1 = require("../../mind/diary");
|
|
60
|
+
const record_paths_1 = require("../../mind/record-paths");
|
|
60
61
|
const runtime_1 = require("../../nerves/runtime");
|
|
61
62
|
const socket_client_1 = require("./socket-client");
|
|
62
|
-
/** Format diary hits the same way the
|
|
63
|
+
/** Format diary hits the same way the search_facts tool does. */
|
|
63
64
|
function formatDiaryHits(hits) {
|
|
64
65
|
return hits.map((f) => `[diary] ${f.text} (source=${f.source}, createdAt=${f.createdAt})`);
|
|
65
66
|
}
|
|
@@ -72,7 +73,7 @@ function readAgentFile(agent, ...segments) {
|
|
|
72
73
|
}
|
|
73
74
|
/** Resolve the diary root for a specific agent. */
|
|
74
75
|
function agentDiaryRoot(agent) {
|
|
75
|
-
return (0,
|
|
76
|
+
return (0, record_paths_1.resolveRecordDiaryRoot)((0, identity_1.getAgentRoot)(agent));
|
|
76
77
|
}
|
|
77
78
|
/** Read inner dialog runtime status. */
|
|
78
79
|
function readInnerDialogStatus(agent) {
|
|
@@ -339,12 +340,12 @@ async function handleAgentAsk(params) {
|
|
|
339
340
|
emit("daemon.agent_service_error", "agent.ask missing question", { agent: params.agent });
|
|
340
341
|
return { ok: false, error: "Missing required parameter: question" };
|
|
341
342
|
}
|
|
342
|
-
// Use the same searchDiaryEntries the
|
|
343
|
+
// Use the same searchDiaryEntries the search_facts tool uses (substring fallback; no embedding provider in shim).
|
|
343
344
|
const diaryRoot = agentDiaryRoot(params.agent);
|
|
344
345
|
const hits = await (0, diary_1.searchDiaryEntries)(question, (0, diary_1.readDiaryEntries)(diaryRoot));
|
|
345
346
|
const context = hits.length > 0
|
|
346
347
|
? hits.slice(0, 10).map((f) => f.text).join("\n")
|
|
347
|
-
: `No relevant
|
|
348
|
+
: `No relevant facts found for: ${question}`;
|
|
348
349
|
emit("daemon.agent_service_end", "completed agent.ask", { agent: params.agent });
|
|
349
350
|
return { ok: true, message: context };
|
|
350
351
|
}
|
|
@@ -393,17 +394,17 @@ async function handleAgentGetContext(params) {
|
|
|
393
394
|
const innerStatus = readInnerDialogStatus(params.agent);
|
|
394
395
|
const sessions = enumerateSessions(params.agent);
|
|
395
396
|
const taskFiles = listTaskFiles(params.agent);
|
|
396
|
-
let
|
|
397
|
+
let recordSummary = null;
|
|
397
398
|
if (query) {
|
|
398
399
|
const hits = await (0, diary_1.searchDiaryEntries)(query, facts);
|
|
399
|
-
|
|
400
|
+
recordSummary = hits.length > 0
|
|
400
401
|
? hits.slice(0, 10).map((f) => f.text).join("\n")
|
|
401
|
-
: `No relevant
|
|
402
|
+
: `No relevant facts for: ${query}`;
|
|
402
403
|
}
|
|
403
404
|
else {
|
|
404
405
|
const recent = facts.slice(-10);
|
|
405
406
|
if (recent.length > 0)
|
|
406
|
-
|
|
407
|
+
recordSummary = recent.map((f) => f.text).join("\n");
|
|
407
408
|
}
|
|
408
409
|
emit("daemon.agent_service_end", "completed agent.getContext", { agent: params.agent });
|
|
409
410
|
return {
|
|
@@ -412,25 +413,25 @@ async function handleAgentGetContext(params) {
|
|
|
412
413
|
agent: params.agent,
|
|
413
414
|
hasDiaryEntries: facts.length > 0,
|
|
414
415
|
factCount: facts.length,
|
|
415
|
-
|
|
416
|
+
recordSummary,
|
|
416
417
|
taskCount: taskFiles.length,
|
|
417
418
|
sessionCount: sessions.length,
|
|
418
419
|
innerStatus: innerStatus?.status ?? null,
|
|
419
420
|
},
|
|
420
421
|
};
|
|
421
422
|
}
|
|
422
|
-
async function
|
|
423
|
-
emit("daemon.agent_service_start", "handling agent.
|
|
423
|
+
async function handleAgentSearchFacts(params) {
|
|
424
|
+
emit("daemon.agent_service_start", "handling agent.searchFacts", { agent: params.agent });
|
|
424
425
|
const query = params.query;
|
|
425
426
|
if (!query) {
|
|
426
|
-
emit("daemon.agent_service_error", "agent.
|
|
427
|
+
emit("daemon.agent_service_error", "agent.searchFacts missing query", { agent: params.agent });
|
|
427
428
|
return { ok: false, error: "Missing required parameter: query" };
|
|
428
429
|
}
|
|
429
|
-
// Same searchDiaryEntries as the
|
|
430
|
+
// Same searchDiaryEntries as the search_facts tool.
|
|
430
431
|
const diaryRoot = agentDiaryRoot(params.agent);
|
|
431
432
|
const hits = await (0, diary_1.searchDiaryEntries)(query, (0, diary_1.readDiaryEntries)(diaryRoot));
|
|
432
433
|
const formatted = formatDiaryHits(hits.slice(0, 20));
|
|
433
|
-
emit("daemon.agent_service_end", "completed agent.
|
|
434
|
+
emit("daemon.agent_service_end", "completed agent.searchFacts", { agent: params.agent, matchCount: hits.length });
|
|
434
435
|
return {
|
|
435
436
|
ok: true,
|
|
436
437
|
message: hits.length > 0 ? `Found ${hits.length} matches` : "No matches found",
|
|
@@ -481,7 +482,7 @@ async function handleAgentCheckGuidance(params) {
|
|
|
481
482
|
emit("daemon.agent_service_error", "agent.checkGuidance missing topic", { agent: params.agent });
|
|
482
483
|
return { ok: false, error: "Missing required parameter: topic" };
|
|
483
484
|
}
|
|
484
|
-
// Same searchDiaryEntries as the
|
|
485
|
+
// Same searchDiaryEntries as the search_facts tool.
|
|
485
486
|
const diaryRoot = agentDiaryRoot(params.agent);
|
|
486
487
|
const hits = await (0, diary_1.searchDiaryEntries)(topic, (0, diary_1.readDiaryEntries)(diaryRoot));
|
|
487
488
|
const guidance = hits.length > 0
|
|
@@ -365,6 +365,7 @@ function agentResolutionFailureMode(command) {
|
|
|
365
365
|
case "attention.list":
|
|
366
366
|
case "attention.show":
|
|
367
367
|
case "attention.history":
|
|
368
|
+
case "work.card":
|
|
368
369
|
case "inner.status":
|
|
369
370
|
case "session.list":
|
|
370
371
|
return "return-message";
|
|
@@ -7277,6 +7278,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
7277
7278
|
}
|
|
7278
7279
|
}
|
|
7279
7280
|
/* v8 ignore stop */
|
|
7281
|
+
// ── work card (local, no daemon socket needed) ──
|
|
7282
|
+
if (command.kind === "work.card") {
|
|
7283
|
+
const { buildWorkCard, formatWorkCardText } = await Promise.resolve().then(() => __importStar(require("../work-card")));
|
|
7284
|
+
if (!command.agent)
|
|
7285
|
+
throw new Error("work card requires --agent <name>");
|
|
7286
|
+
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
7287
|
+
const agentRoot = deps.agentBundleRoot ?? path.join(bundlesRoot, `${command.agent}.ouro`);
|
|
7288
|
+
const card = buildWorkCard(command.agent, agentRoot);
|
|
7289
|
+
const message = command.format === "json" ? JSON.stringify(card, null, 2) : formatWorkCardText(card);
|
|
7290
|
+
deps.writeStdout(message);
|
|
7291
|
+
return message;
|
|
7292
|
+
}
|
|
7280
7293
|
// ── inner dialog status (local, no daemon socket needed) ──
|
|
7281
7294
|
/* v8 ignore start -- inner status handler: requires real agent state on disk @preserve */
|
|
7282
7295
|
if (command.kind === "inner.status") {
|
|
@@ -7286,6 +7299,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
7286
7299
|
const { parseCadenceToMs: parseCadenceMs, DEFAULT_CADENCE_MS } = await Promise.resolve().then(() => __importStar(require("./cadence")));
|
|
7287
7300
|
const { parseFrontmatter } = await Promise.resolve().then(() => __importStar(require("../../util/frontmatter")));
|
|
7288
7301
|
const { listActiveReturnObligations } = await Promise.resolve().then(() => __importStar(require("../../arc/obligations")));
|
|
7302
|
+
const { resolveDeskRecordPaths } = await Promise.resolve().then(() => __importStar(require("../../mind/record-paths")));
|
|
7289
7303
|
// Read runtime state
|
|
7290
7304
|
const innerSessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
|
|
7291
7305
|
const runtimeJsonPath = path.join(path.dirname(innerSessionPath), "runtime.json");
|
|
@@ -7295,19 +7309,20 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
7295
7309
|
runtimeState = JSON.parse(raw);
|
|
7296
7310
|
}
|
|
7297
7311
|
catch { /* missing or corrupt — will show "unknown" */ }
|
|
7298
|
-
// Read
|
|
7299
|
-
const
|
|
7300
|
-
|
|
7312
|
+
// Read canonical Desk record summary
|
|
7313
|
+
const recordPaths = resolveDeskRecordPaths(agentRoot);
|
|
7314
|
+
const recordSummary = { diaryFactCount: 0, noteCount: 0 };
|
|
7301
7315
|
try {
|
|
7302
|
-
const
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7316
|
+
const rawFacts = fs.readFileSync(recordPaths.factsPath, "utf-8");
|
|
7317
|
+
recordSummary.diaryFactCount = rawFacts.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
|
|
7318
|
+
}
|
|
7319
|
+
catch { /* missing facts file — count stays zero */ }
|
|
7320
|
+
try {
|
|
7321
|
+
recordSummary.noteCount = fs.readdirSync(recordPaths.notesRoot, { withFileTypes: true })
|
|
7322
|
+
.filter((e) => e.isFile() && !e.name.startsWith(".") && e.name.endsWith(".md"))
|
|
7323
|
+
.length;
|
|
7309
7324
|
}
|
|
7310
|
-
catch { /* missing dir —
|
|
7325
|
+
catch { /* missing notes dir — count stays zero */ }
|
|
7311
7326
|
// Read heartbeat cadence
|
|
7312
7327
|
let heartbeat = null;
|
|
7313
7328
|
try {
|
|
@@ -7342,7 +7357,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
7342
7357
|
const message = buildInnerStatusOutput({
|
|
7343
7358
|
agentName: command.agent,
|
|
7344
7359
|
runtimeState,
|
|
7345
|
-
|
|
7360
|
+
recordSummary,
|
|
7346
7361
|
heartbeat,
|
|
7347
7362
|
attentionCount: activeObligations.length,
|
|
7348
7363
|
now: Date.now(),
|
|
@@ -151,6 +151,20 @@ exports.COMMAND_REGISTRY = {
|
|
|
151
151
|
example: "ouro task list",
|
|
152
152
|
subcommands: ["list", "new", "done", "archive", "show"],
|
|
153
153
|
},
|
|
154
|
+
work: {
|
|
155
|
+
category: "Tasks",
|
|
156
|
+
description: "Show the agent's durable Work Card compiled from arc records.",
|
|
157
|
+
usage: "ouro work card [--agent <name>] [--format text|json|--json]",
|
|
158
|
+
example: "ouro work card --agent slugger --format json",
|
|
159
|
+
subcommands: ["card"],
|
|
160
|
+
},
|
|
161
|
+
"work card": {
|
|
162
|
+
category: "Tasks",
|
|
163
|
+
description: "Show the agent's durable Work Card compiled from arc records.",
|
|
164
|
+
usage: "ouro work card [--agent <name>] [--format text|json|--json]",
|
|
165
|
+
example: "ouro work card --agent slugger --format json",
|
|
166
|
+
hidden: true,
|
|
167
|
+
},
|
|
154
168
|
"migrate-to-desk": {
|
|
155
169
|
category: "Tasks",
|
|
156
170
|
description: "Migrate a legacy `tasks/` tree into the new `desk/` shape (copy semantics — source untouched).",
|
|
@@ -114,6 +114,7 @@ function usage() {
|
|
|
114
114
|
" ouro friend create --name <name> [--trust <level>] [--agent <name>]",
|
|
115
115
|
" ouro friend update <id> --trust <level> [--agent <name>]",
|
|
116
116
|
" ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
|
|
117
|
+
" ouro work card [--agent <name>] [--format text|json|--json]",
|
|
117
118
|
" ouro inner [--agent <name>]",
|
|
118
119
|
" ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
119
120
|
" ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
@@ -1000,6 +1001,29 @@ function parseAttentionCommand(args) {
|
|
|
1000
1001
|
}
|
|
1001
1002
|
return { kind: "attention.list", ...(agent ? { agent } : {}) };
|
|
1002
1003
|
}
|
|
1004
|
+
function parseWorkCommand(args) {
|
|
1005
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
1006
|
+
const sub = cleaned[0];
|
|
1007
|
+
if (sub !== "card")
|
|
1008
|
+
throw new Error("Usage: ouro work card [--agent <name>] [--format text|json|--json]");
|
|
1009
|
+
let format = "text";
|
|
1010
|
+
for (let i = 1; i < cleaned.length; i += 1) {
|
|
1011
|
+
if (cleaned[i] === "--json") {
|
|
1012
|
+
format = "json";
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (cleaned[i] === "--format" && cleaned[i + 1]) {
|
|
1016
|
+
const value = cleaned[++i];
|
|
1017
|
+
if (value !== "text" && value !== "json") {
|
|
1018
|
+
throw new Error("--format must be text or json");
|
|
1019
|
+
}
|
|
1020
|
+
format = value;
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
throw new Error("Usage: ouro work card [--agent <name>] [--format text|json|--json]");
|
|
1024
|
+
}
|
|
1025
|
+
return { kind: "work.card", ...(agent ? { agent } : {}), ...(format !== "text" ? { format } : {}) };
|
|
1026
|
+
}
|
|
1003
1027
|
function parseThoughtsCommand(args) {
|
|
1004
1028
|
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
1005
1029
|
let last;
|
|
@@ -1583,6 +1607,8 @@ function parseOuroCommand(args) {
|
|
|
1583
1607
|
return parseThoughtsCommand(args.slice(1));
|
|
1584
1608
|
if (head === "attention")
|
|
1585
1609
|
return parseAttentionCommand(args.slice(1));
|
|
1610
|
+
if (head === "work")
|
|
1611
|
+
return parseWorkCommand(args.slice(1));
|
|
1586
1612
|
if (head === "inner") {
|
|
1587
1613
|
const { agent } = extractAgentFlag(args.slice(1));
|
|
1588
1614
|
return { kind: "inner.status", ...(agent ? { agent } : {}) };
|
|
@@ -366,7 +366,7 @@ void daemon.start().then(() => {
|
|
|
366
366
|
habitsDir,
|
|
367
367
|
osCronManager,
|
|
368
368
|
onHabitFire: (habitName) => {
|
|
369
|
-
processManager.sendToAgent(agent, { type: "habit", habitName });
|
|
369
|
+
processManager.sendToAgent(agent, { type: "habit", habitName, trigger: "overdue" });
|
|
370
370
|
},
|
|
371
371
|
deps: {
|
|
372
372
|
readdir: (dir) => fs.readdirSync(dir),
|
|
@@ -1205,8 +1205,8 @@ class OuroDaemon {
|
|
|
1205
1205
|
return (0, agent_service_1.handleAgentDelegate)(command);
|
|
1206
1206
|
case "agent.getContext":
|
|
1207
1207
|
return (0, agent_service_1.handleAgentGetContext)(command);
|
|
1208
|
-
case "agent.
|
|
1209
|
-
return (0, agent_service_1.
|
|
1208
|
+
case "agent.searchFacts":
|
|
1209
|
+
return (0, agent_service_1.handleAgentSearchFacts)(command);
|
|
1210
1210
|
case "agent.getTask":
|
|
1211
1211
|
return (0, agent_service_1.handleAgentGetTask)(command);
|
|
1212
1212
|
case "agent.checkScope":
|
|
@@ -1298,7 +1298,7 @@ class OuroDaemon {
|
|
|
1298
1298
|
};
|
|
1299
1299
|
}
|
|
1300
1300
|
case "habit.poke": {
|
|
1301
|
-
this.processManager.sendToAgent?.(command.agent, { type: "habit", habitName: command.habitName });
|
|
1301
|
+
this.processManager.sendToAgent?.(command.agent, { type: "habit", habitName: command.habitName, trigger: "poke" });
|
|
1302
1302
|
return {
|
|
1303
1303
|
ok: true,
|
|
1304
1304
|
message: `poked habit ${command.habitName} for ${command.agent}`,
|
|
@@ -37,10 +37,11 @@ exports.bundleMetaHook = bundleMetaHook;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const runtime_1 = require("../../../nerves/runtime");
|
|
40
|
+
const record_paths_1 = require("../../../mind/record-paths");
|
|
40
41
|
/**
|
|
41
42
|
* Migrate bundle from schema 1 to schema 2:
|
|
42
43
|
* - Move state/{episodes,obligations,cares,intentions}/* to arc/{name}/*
|
|
43
|
-
* - Move
|
|
44
|
+
* - Move legacy record stores into desk/_record/
|
|
44
45
|
* Idempotent: skips missing sources; on collision, newer mtime wins.
|
|
45
46
|
*/
|
|
46
47
|
function migrateToSchema2(agentRoot) {
|
|
@@ -56,7 +57,7 @@ function migrateToSchema2(agentRoot) {
|
|
|
56
57
|
const dest = path.join(agentRoot, "arc", name);
|
|
57
58
|
migrateDirectory(src, dest);
|
|
58
59
|
}
|
|
59
|
-
//
|
|
60
|
+
// Stage legacy diary files for schema 3 to canonicalize into Desk record.
|
|
60
61
|
const legacyDiarySrc = path.join(agentRoot, "psyche", "mem" + "ory");
|
|
61
62
|
const diaryDest = path.join(agentRoot, "diary");
|
|
62
63
|
migrateDirectory(legacyDiarySrc, diaryDest);
|
|
@@ -70,7 +71,7 @@ function migrateToSchema2(agentRoot) {
|
|
|
70
71
|
});
|
|
71
72
|
}
|
|
72
73
|
/**
|
|
73
|
-
* Ensure bundle .gitignore has state/ ignored and does NOT ignore
|
|
74
|
+
* Ensure bundle .gitignore has state/ ignored and does NOT ignore tracked record roots.
|
|
74
75
|
*/
|
|
75
76
|
function updateBundleGitignore(agentRoot) {
|
|
76
77
|
const gitignorePath = path.join(agentRoot, ".gitignore");
|
|
@@ -83,8 +84,8 @@ function updateBundleGitignore(agentRoot) {
|
|
|
83
84
|
catch {
|
|
84
85
|
// If we can't read, start fresh
|
|
85
86
|
}
|
|
86
|
-
// Remove
|
|
87
|
-
const toRemove = new Set(["arc/", "diary/", "journal/"]);
|
|
87
|
+
// Remove tracked bundle roots from ignore.
|
|
88
|
+
const toRemove = new Set(["arc/", "desk/", "diary/", "journal/"]);
|
|
88
89
|
lines = lines.filter((line) => !toRemove.has(line.trim()));
|
|
89
90
|
// Ensure state/ is in the ignore list
|
|
90
91
|
const hasState = lines.some((line) => line.trim() === "state/");
|
|
@@ -100,6 +101,10 @@ function updateBundleGitignore(agentRoot) {
|
|
|
100
101
|
// Non-blocking: if we can't write .gitignore, migration still succeeds
|
|
101
102
|
}
|
|
102
103
|
}
|
|
104
|
+
function migrateToSchema3(agentRoot) {
|
|
105
|
+
(0, record_paths_1.migrateLegacyRecordStores)(agentRoot);
|
|
106
|
+
updateBundleGitignore(agentRoot);
|
|
107
|
+
}
|
|
103
108
|
/**
|
|
104
109
|
* Recursively copy files from src to dest.
|
|
105
110
|
* Creates destination directories as needed. Skips if source doesn't exist.
|
|
@@ -165,14 +170,29 @@ async function bundleMetaHook(ctx) {
|
|
|
165
170
|
// Malformed JSON -- treat as missing, will overwrite with fresh
|
|
166
171
|
existing = undefined;
|
|
167
172
|
}
|
|
168
|
-
// Run schema
|
|
173
|
+
// Run schema migrations if needed.
|
|
169
174
|
const currentSchema = existing?.bundleSchemaVersion ?? 1;
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
try {
|
|
176
|
+
if (currentSchema < 2) {
|
|
177
|
+
migrateToSchema2(ctx.agentRoot);
|
|
178
|
+
}
|
|
179
|
+
if (currentSchema < 3) {
|
|
180
|
+
migrateToSchema3(ctx.agentRoot);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
const errorMessage = err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err);
|
|
185
|
+
(0, runtime_1.emitNervesEvent)({
|
|
186
|
+
component: "daemon",
|
|
187
|
+
event: "daemon.bundle_meta_hook_error",
|
|
188
|
+
message: "bundle-meta hook migration failed",
|
|
189
|
+
meta: { agentRoot: ctx.agentRoot, error: errorMessage },
|
|
190
|
+
});
|
|
191
|
+
return { ok: false, error: errorMessage };
|
|
172
192
|
}
|
|
173
193
|
const updated = {
|
|
174
194
|
runtimeVersion: ctx.currentVersion,
|
|
175
|
-
bundleSchemaVersion: currentSchema <
|
|
195
|
+
bundleSchemaVersion: currentSchema < 3 ? 3 : currentSchema,
|
|
176
196
|
lastUpdated: new Date().toISOString(),
|
|
177
197
|
};
|
|
178
198
|
// Save old runtimeVersion as previousRuntimeVersion (if there was one)
|
|
@@ -24,7 +24,7 @@ function formatCadence(cadenceMs) {
|
|
|
24
24
|
return `${minutes}m`;
|
|
25
25
|
}
|
|
26
26
|
function buildInnerStatusOutput(input) {
|
|
27
|
-
const { agentName, runtimeState,
|
|
27
|
+
const { agentName, runtimeState, recordSummary, heartbeat, attentionCount, now } = input;
|
|
28
28
|
const lines = [];
|
|
29
29
|
lines.push(`inner dialog status: ${agentName}`);
|
|
30
30
|
// Last turn
|
|
@@ -58,19 +58,7 @@ function buildInnerStatusOutput(input) {
|
|
|
58
58
|
else {
|
|
59
59
|
lines.push(" heartbeat: unknown");
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
if (journalFiles.length === 0) {
|
|
63
|
-
lines.push(" journal: (empty)");
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
lines.push(" journal:");
|
|
67
|
-
const sorted = [...journalFiles].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
68
|
-
for (const file of sorted) {
|
|
69
|
-
const elapsed = now - file.mtimeMs;
|
|
70
|
-
const relativeTime = formatRelativeTime(elapsed);
|
|
71
|
-
lines.push(` - ${file.name} (${relativeTime})`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
61
|
+
lines.push(` Desk record: ${recordSummary.diaryFactCount} diary facts, ${recordSummary.noteCount} notes`);
|
|
74
62
|
// Attention
|
|
75
63
|
const thoughtWord = attentionCount === 1 ? "thought" : "thoughts";
|
|
76
64
|
lines.push(` attention: ${attentionCount} held ${thoughtWord}`);
|
|
@@ -81,7 +69,8 @@ function buildInnerStatusOutput(input) {
|
|
|
81
69
|
meta: {
|
|
82
70
|
agentName,
|
|
83
71
|
status: runtimeState?.status ?? "unknown",
|
|
84
|
-
|
|
72
|
+
diaryFactCount: recordSummary.diaryFactCount,
|
|
73
|
+
noteCount: recordSummary.noteCount,
|
|
85
74
|
attentionCount,
|
|
86
75
|
},
|
|
87
76
|
});
|
|
@@ -57,6 +57,53 @@ function parseToolsField(raw) {
|
|
|
57
57
|
}
|
|
58
58
|
return undefined;
|
|
59
59
|
}
|
|
60
|
+
function objectRecord(raw) {
|
|
61
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
62
|
+
}
|
|
63
|
+
function stringField(record, key) {
|
|
64
|
+
const value = record[key];
|
|
65
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
66
|
+
}
|
|
67
|
+
function booleanField(record, key, fallback) {
|
|
68
|
+
const value = record[key];
|
|
69
|
+
if (typeof value === "boolean")
|
|
70
|
+
return value;
|
|
71
|
+
if (typeof value === "string") {
|
|
72
|
+
if (value === "true")
|
|
73
|
+
return true;
|
|
74
|
+
if (value === "false")
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return fallback;
|
|
78
|
+
}
|
|
79
|
+
function parseStringArray(raw) {
|
|
80
|
+
if (typeof raw === "string" && raw.startsWith("[") && raw.endsWith("]")) {
|
|
81
|
+
const inner = raw.slice(1, -1);
|
|
82
|
+
if (!inner.trim())
|
|
83
|
+
return [];
|
|
84
|
+
return inner.split(",").map((item) => item.trim()).filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
function parseOrigin(raw) {
|
|
89
|
+
const record = objectRecord(raw);
|
|
90
|
+
if (!record)
|
|
91
|
+
return null;
|
|
92
|
+
const friendId = stringField(record, "friendId");
|
|
93
|
+
const channel = stringField(record, "channel");
|
|
94
|
+
const key = stringField(record, "key");
|
|
95
|
+
if (!friendId || !channel || !key)
|
|
96
|
+
return null;
|
|
97
|
+
return { friendId, channel, key };
|
|
98
|
+
}
|
|
99
|
+
function parseSurface(raw) {
|
|
100
|
+
const record = objectRecord(raw);
|
|
101
|
+
return {
|
|
102
|
+
family: record ? booleanField(record, "family", true) : true,
|
|
103
|
+
originator: record ? booleanField(record, "originator", true) : true,
|
|
104
|
+
extra: record ? parseStringArray(record.extra) : [],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
60
107
|
function extractFrontmatterAndBody(content) {
|
|
61
108
|
const lines = content.split(/\r?\n/);
|
|
62
109
|
if (lines[0]?.trim() !== "---") {
|
|
@@ -88,6 +135,8 @@ function parseHabitFile(content, filePath) {
|
|
|
88
135
|
lastRun: null,
|
|
89
136
|
created: null,
|
|
90
137
|
tools: undefined,
|
|
138
|
+
origin: null,
|
|
139
|
+
surface: { family: true, originator: true, extra: [] },
|
|
91
140
|
body: content.trim(),
|
|
92
141
|
};
|
|
93
142
|
}
|
|
@@ -103,6 +152,8 @@ function parseHabitFile(content, filePath) {
|
|
|
103
152
|
const rawCreated = frontmatter.created;
|
|
104
153
|
const created = typeof rawCreated === "string" && rawCreated.length > 0 ? rawCreated : null;
|
|
105
154
|
const tools = parseToolsField(frontmatter.tools);
|
|
155
|
+
const origin = parseOrigin(frontmatter.origin);
|
|
156
|
+
const surface = parseSurface(frontmatter.surface);
|
|
106
157
|
return {
|
|
107
158
|
name: stem,
|
|
108
159
|
title,
|
|
@@ -111,6 +162,8 @@ function parseHabitFile(content, filePath) {
|
|
|
111
162
|
lastRun,
|
|
112
163
|
created,
|
|
113
164
|
tools,
|
|
165
|
+
origin,
|
|
166
|
+
surface,
|
|
114
167
|
body,
|
|
115
168
|
};
|
|
116
169
|
}
|
|
@@ -121,6 +174,16 @@ function formatFrontmatterValue(value) {
|
|
|
121
174
|
return `[${value.join(", ")}]`;
|
|
122
175
|
return String(value);
|
|
123
176
|
}
|
|
177
|
+
function renderFrontmatterLine(lines, key, value) {
|
|
178
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
179
|
+
lines.push(`${key}:`);
|
|
180
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
181
|
+
lines.push(` ${childKey}: ${formatFrontmatterValue(childValue)}`);
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
lines.push(`${key}: ${formatFrontmatterValue(value)}`);
|
|
186
|
+
}
|
|
124
187
|
function renderHabitFile(frontmatter, body) {
|
|
125
188
|
(0, runtime_1.emitNervesEvent)({
|
|
126
189
|
event: "daemon.habit_render",
|
|
@@ -130,7 +193,7 @@ function renderHabitFile(frontmatter, body) {
|
|
|
130
193
|
});
|
|
131
194
|
const lines = ["---"];
|
|
132
195
|
for (const key of Object.keys(frontmatter)) {
|
|
133
|
-
lines
|
|
196
|
+
renderFrontmatterLine(lines, key, frontmatter[key]);
|
|
134
197
|
}
|
|
135
198
|
lines.push("---");
|
|
136
199
|
lines.push("");
|
|
@@ -44,6 +44,8 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
44
44
|
const auth_flow_1 = require("../auth/auth-flow");
|
|
45
45
|
const provider_models_1 = require("../provider-models");
|
|
46
46
|
const habit_parser_1 = require("../habits/habit-parser");
|
|
47
|
+
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
48
|
+
const record_paths_1 = require("../../mind/record-paths");
|
|
47
49
|
const hatch_specialist_1 = require("./hatch-specialist");
|
|
48
50
|
function requiredCredentialKeys(provider) {
|
|
49
51
|
return identity_1.PROVIDER_CREDENTIALS[provider].required;
|
|
@@ -83,7 +85,7 @@ function writeHeartbeatHabit(bundleRoot, now) {
|
|
|
83
85
|
cadence: "30m",
|
|
84
86
|
status: "active",
|
|
85
87
|
created: now.toISOString(),
|
|
86
|
-
}, "Run a lightweight heartbeat cycle. Review task board and inbox.\nCheck on pending obligations.
|
|
88
|
+
}, "Run a lightweight heartbeat cycle. Review task board and inbox.\nCheck on pending obligations. Write important durable outputs to Arc or Desk record.");
|
|
87
89
|
fs.writeFileSync(filePath, content, "utf-8");
|
|
88
90
|
}
|
|
89
91
|
function writeFriendImprint(bundleRoot, humanName, now) {
|
|
@@ -115,12 +117,15 @@ function writeFriendImprint(bundleRoot, humanName, now) {
|
|
|
115
117
|
};
|
|
116
118
|
fs.writeFileSync(path.join(friendsDir, `${id}.json`), `${JSON.stringify(record, null, 2)}\n`, "utf-8");
|
|
117
119
|
}
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
fs.mkdirSync(
|
|
121
|
-
fs.mkdirSync(
|
|
122
|
-
fs.writeFileSync(
|
|
123
|
-
fs.writeFileSync(
|
|
120
|
+
function writeRecordScaffold(bundleRoot) {
|
|
121
|
+
const recordPaths = (0, record_paths_1.resolveDeskRecordPaths)(bundleRoot);
|
|
122
|
+
fs.mkdirSync(recordPaths.diaryDailyDir, { recursive: true });
|
|
123
|
+
fs.mkdirSync(recordPaths.notesRoot, { recursive: true });
|
|
124
|
+
fs.writeFileSync(recordPaths.factsPath, "", "utf-8");
|
|
125
|
+
fs.writeFileSync(recordPaths.entitiesPath, "{}\n", "utf-8");
|
|
126
|
+
fs.mkdirSync(path.join(bundleRoot, "arc", "flight-recorder", "events"), { recursive: true });
|
|
127
|
+
fs.mkdirSync(path.join(bundleRoot, "arc", "flight-recorder", "habit-receipts"), { recursive: true });
|
|
128
|
+
fs.mkdirSync(path.join(bundleRoot, "arc", "claims"), { recursive: true });
|
|
124
129
|
}
|
|
125
130
|
function writeHatchlingAgentConfig(bundleRoot, input) {
|
|
126
131
|
const template = (0, identity_1.buildDefaultAgentTemplate)(input.agentName);
|
|
@@ -156,7 +161,9 @@ async function runHatchFlow(input, deps = {}) {
|
|
|
156
161
|
fs.mkdirSync(bundleRoot, { recursive: true });
|
|
157
162
|
writeReadme(bundleRoot, "Root of this agent bundle.");
|
|
158
163
|
writeReadme(path.join(bundleRoot, "psyche"), "Identity and behavior files.");
|
|
159
|
-
writeReadme(path.join(bundleRoot, "
|
|
164
|
+
writeReadme(path.join(bundleRoot, "arc"), "Live continuity, claims, obligations, and resume state.");
|
|
165
|
+
writeReadme(path.join(bundleRoot, "desk"), "Durable work and maintained record.");
|
|
166
|
+
writeReadme(path.join(bundleRoot, "desk", "_record"), "Desk record: diary facts and maintained reference notes.");
|
|
160
167
|
writeReadme(path.join(bundleRoot, "friends"), "Known friend records.");
|
|
161
168
|
writeReadme(path.join(bundleRoot, "tasks"), "Task files.");
|
|
162
169
|
writeReadme(path.join(bundleRoot, "tasks", "one-shots"), "One-shot tasks.");
|
|
@@ -166,8 +173,9 @@ async function runHatchFlow(input, deps = {}) {
|
|
|
166
173
|
writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
|
|
167
174
|
writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
|
|
168
175
|
writeHatchlingAgentConfig(bundleRoot, input);
|
|
176
|
+
fs.writeFileSync(path.join(bundleRoot, "bundle-meta.json"), `${JSON.stringify((0, bundle_manifest_1.createBundleMeta)(), null, 2)}\n`, "utf-8");
|
|
169
177
|
const credentialPath = await storeHatchlingProviderCredentials(input.agentName, input.provider, input.credentials);
|
|
170
|
-
|
|
178
|
+
writeRecordScaffold(bundleRoot);
|
|
171
179
|
writeFriendImprint(bundleRoot, input.humanName, now);
|
|
172
180
|
writeHeartbeatHabit(bundleRoot, now);
|
|
173
181
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -41,6 +41,7 @@ const tools_base_1 = require("../../repertoire/tools-base");
|
|
|
41
41
|
const hatch_flow_1 = require("./hatch-flow");
|
|
42
42
|
const hatch_animation_1 = require("./hatch-animation");
|
|
43
43
|
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
44
|
+
const record_paths_1 = require("../../mind/record-paths");
|
|
44
45
|
const identity_1 = require("../identity");
|
|
45
46
|
const runtime_1 = require("../../nerves/runtime");
|
|
46
47
|
const vault_setup_1 = require("../../repertoire/vault-setup");
|
|
@@ -107,9 +108,10 @@ function writeReadme(dir, purpose) {
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
function scaffoldBundle(bundleRoot) {
|
|
110
|
-
writeReadme(path.join(bundleRoot, "
|
|
111
|
-
writeReadme(path.join(bundleRoot, "
|
|
112
|
-
writeReadme(path.join(bundleRoot, "
|
|
111
|
+
writeReadme(path.join(bundleRoot, "arc"), "Live continuity, claims, obligations, and resume state.");
|
|
112
|
+
writeReadme(path.join(bundleRoot, "arc", "flight-recorder"), "Flight recorder resume and event receipts.");
|
|
113
|
+
writeReadme(path.join(bundleRoot, "desk"), "Durable work and maintained record.");
|
|
114
|
+
writeReadme(path.join(bundleRoot, "desk", "_record"), "Desk record: diary facts and maintained reference notes.");
|
|
113
115
|
writeReadme(path.join(bundleRoot, "friends"), "Known friend records.");
|
|
114
116
|
writeReadme(path.join(bundleRoot, "tasks"), "Task files.");
|
|
115
117
|
writeReadme(path.join(bundleRoot, "tasks", "one-shots"), "One-shot tasks.");
|
|
@@ -118,16 +120,18 @@ function scaffoldBundle(bundleRoot) {
|
|
|
118
120
|
writeReadme(path.join(bundleRoot, "skills"), "Local skill files.");
|
|
119
121
|
writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
|
|
120
122
|
writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const entitiesPath = path.join(notesRoot, "entities.json");
|
|
123
|
+
const recordPaths = (0, record_paths_1.resolveDeskRecordPaths)(bundleRoot);
|
|
124
|
+
fs.mkdirSync(recordPaths.diaryDailyDir, { recursive: true });
|
|
125
|
+
fs.mkdirSync(recordPaths.notesRoot, { recursive: true });
|
|
125
126
|
/* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
|
|
126
|
-
if (!fs.existsSync(factsPath))
|
|
127
|
-
fs.writeFileSync(factsPath, "", "utf-8");
|
|
127
|
+
if (!fs.existsSync(recordPaths.factsPath))
|
|
128
|
+
fs.writeFileSync(recordPaths.factsPath, "", "utf-8");
|
|
128
129
|
/* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
|
|
129
|
-
if (!fs.existsSync(entitiesPath))
|
|
130
|
-
fs.writeFileSync(entitiesPath, "{}\n", "utf-8");
|
|
130
|
+
if (!fs.existsSync(recordPaths.entitiesPath))
|
|
131
|
+
fs.writeFileSync(recordPaths.entitiesPath, "{}\n", "utf-8");
|
|
132
|
+
fs.mkdirSync(path.join(bundleRoot, "arc", "flight-recorder", "events"), { recursive: true });
|
|
133
|
+
fs.mkdirSync(path.join(bundleRoot, "arc", "flight-recorder", "habit-receipts"), { recursive: true });
|
|
134
|
+
fs.mkdirSync(path.join(bundleRoot, "arc", "claims"), { recursive: true });
|
|
131
135
|
// bundle-meta.json
|
|
132
136
|
const meta = (0, bundle_manifest_1.createBundleMeta)();
|
|
133
137
|
fs.writeFileSync(path.join(bundleRoot, "bundle-meta.json"), JSON.stringify(meta, null, 2) + "\n", "utf-8");
|