@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.
Files changed (52) hide show
  1. package/README.md +13 -13
  2. package/changelog.json +9 -0
  3. package/dist/arc/evolution.js +1 -1
  4. package/dist/arc/flight-recorder.js +369 -0
  5. package/dist/arc/obligations.js +24 -2
  6. package/dist/heart/active-work.js +1 -1
  7. package/dist/heart/config-registry.js +5 -5
  8. package/dist/heart/daemon/agent-config-check.js +1 -1
  9. package/dist/heart/daemon/agent-service.js +18 -17
  10. package/dist/heart/daemon/cli-exec.js +27 -12
  11. package/dist/heart/daemon/cli-help.js +14 -0
  12. package/dist/heart/daemon/cli-parse.js +26 -0
  13. package/dist/heart/daemon/daemon-entry.js +1 -1
  14. package/dist/heart/daemon/daemon.js +3 -3
  15. package/dist/heart/daemon/hooks/bundle-meta.js +29 -9
  16. package/dist/heart/daemon/inner-status.js +4 -15
  17. package/dist/heart/habits/habit-parser.js +64 -1
  18. package/dist/heart/hatch/hatch-flow.js +17 -9
  19. package/dist/heart/hatch/specialist-tools.js +15 -11
  20. package/dist/heart/kept-notes.js +5 -73
  21. package/dist/heart/mailbox/readers/runtime-readers.js +21 -49
  22. package/dist/heart/mcp/mcp-server.js +8 -8
  23. package/dist/heart/session-events.js +1 -31
  24. package/dist/heart/start-of-turn-packet.js +8 -2
  25. package/dist/heart/tool-description.js +15 -3
  26. package/dist/heart/turn-context.js +27 -7
  27. package/dist/heart/work-card.js +386 -0
  28. package/dist/mailbox-ui/assets/{index-9-AxCxuB.js → index-Cbasiy6y.js} +1 -1
  29. package/dist/mailbox-ui/index.html +1 -1
  30. package/dist/mind/bundle-manifest.js +9 -3
  31. package/dist/mind/context.js +1 -2
  32. package/dist/mind/desk-section.js +53 -1
  33. package/dist/mind/diary.js +2 -3
  34. package/dist/mind/note-search.js +36 -106
  35. package/dist/mind/prompt.js +37 -102
  36. package/dist/mind/record-paths.js +312 -0
  37. package/dist/repertoire/bundle-templates.js +4 -5
  38. package/dist/repertoire/tools-bundle.js +1 -1
  39. package/dist/repertoire/tools-evolution.js +4 -4
  40. package/dist/repertoire/tools-notes.js +42 -62
  41. package/dist/repertoire/tools-record.js +16 -11
  42. package/dist/repertoire/tools-session.js +4 -4
  43. package/dist/repertoire/tools.js +1 -1
  44. package/dist/senses/habit-turn-message.js +19 -5
  45. package/dist/senses/inner-dialog-worker.js +58 -9
  46. package/dist/senses/inner-dialog.js +30 -11
  47. package/dist/senses/pipeline.js +135 -1
  48. package/dist/util/frontmatter.js +17 -1
  49. package/package.json +3 -3
  50. package/skills/configure-dev-tools.md +1 -1
  51. package/skills/travel-planning.md +1 -1
  52. 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.handleAgentSearchNotes = handleAgentSearchNotes;
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 search_notes tool does. */
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, diary_1.resolveDiaryRoot)(path.join((0, identity_1.getAgentRoot)(agent), "diary"));
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 search_notes tool uses (substring fallback no embedding provider in shim)
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 notes found for: ${question}`;
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 noteSummary = null;
397
+ let recordSummary = null;
397
398
  if (query) {
398
399
  const hits = await (0, diary_1.searchDiaryEntries)(query, facts);
399
- noteSummary = hits.length > 0
400
+ recordSummary = hits.length > 0
400
401
  ? hits.slice(0, 10).map((f) => f.text).join("\n")
401
- : `No relevant notes for: ${query}`;
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
- noteSummary = recent.map((f) => f.text).join("\n");
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
- noteSummary,
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 handleAgentSearchNotes(params) {
423
- emit("daemon.agent_service_start", "handling agent.searchNotes", { agent: params.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.searchNotes missing query", { agent: params.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 search_notes tool
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.searchNotes", { agent: params.agent, matchCount: hits.length });
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 search_notes tool
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 journal files
7299
- const journalDir = path.join(agentRoot, "journal");
7300
- let journalFiles = [];
7312
+ // Read canonical Desk record summary
7313
+ const recordPaths = resolveDeskRecordPaths(agentRoot);
7314
+ const recordSummary = { diaryFactCount: 0, noteCount: 0 };
7301
7315
  try {
7302
- const journalEntries = fs.readdirSync(journalDir, { withFileTypes: true });
7303
- journalFiles = journalEntries
7304
- .filter((e) => e.isFile() && !e.name.startsWith("."))
7305
- .map((e) => {
7306
- const stat = fs.statSync(path.join(journalDir, e.name));
7307
- return { name: e.name, mtimeMs: stat.mtimeMs };
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 — will show (empty) */ }
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
- journalFiles,
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.searchNotes":
1209
- return (0, agent_service_1.handleAgentSearchNotes)(command);
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 the old psyche note store to diary/
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
- // Migrate diary from the old pre-diary bundle layout.
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 arc/, diary/, journal/.
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 arc/, diary/, journal/ from ignore (they should be tracked)
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-2 migration if needed
173
+ // Run schema migrations if needed.
169
174
  const currentSchema = existing?.bundleSchemaVersion ?? 1;
170
- if (currentSchema < 2) {
171
- migrateToSchema2(ctx.agentRoot);
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 < 2 ? 2 : 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, journalFiles, heartbeat, attentionCount, now } = input;
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
- // Journal
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
- journalCount: journalFiles.length,
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.push(`${key}: ${formatFrontmatterValue(frontmatter[key])}`);
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. Journal anything important.");
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 writeDiaryScaffold(bundleRoot) {
119
- const diaryRoot = path.join(bundleRoot, "diary");
120
- fs.mkdirSync(path.join(diaryRoot, "daily"), { recursive: true });
121
- fs.mkdirSync(path.join(diaryRoot, "archive"), { recursive: true });
122
- fs.writeFileSync(path.join(diaryRoot, "facts.jsonl"), "", "utf-8");
123
- fs.writeFileSync(path.join(diaryRoot, "entities.json"), "{}\n", "utf-8");
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, "diary"), "Persistent diary -- things I have learned and chosen to keep.");
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
- writeDiaryScaffold(bundleRoot);
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, "notes"), "Persistent notes store.");
111
- writeReadme(path.join(bundleRoot, "notes", "daily"), "Daily note entries.");
112
- writeReadme(path.join(bundleRoot, "notes", "archive"), "Archived notes.");
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
- // Notes scaffold files
122
- const notesRoot = path.join(bundleRoot, "notes");
123
- const factsPath = path.join(notesRoot, "facts.jsonl");
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");