@ouro.bot/cli 0.1.0-alpha.611 → 0.1.0-alpha.612

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 (34) hide show
  1. package/changelog.json +10 -0
  2. package/dist/arc/cares.js +7 -3
  3. package/dist/arc/episodes.js +4 -3
  4. package/dist/arc/intentions.js +2 -1
  5. package/dist/arc/obligations.js +18 -6
  6. package/dist/arc/packets.js +9 -8
  7. package/dist/heart/awaiting/await-runtime-state.js +4 -1
  8. package/dist/heart/bridges/store.js +14 -2
  9. package/dist/heart/daemon/daemon.js +47 -0
  10. package/dist/heart/daemon/process-manager.js +13 -0
  11. package/dist/heart/daemon/sense-manager.js +12 -0
  12. package/dist/heart/mailbox/readers/runtime-readers.js +107 -2
  13. package/dist/heart/mailbox/readers/sessions.js +2 -2
  14. package/dist/heart/session-events.js +73 -66
  15. package/dist/heart/session-transcript.js +6 -116
  16. package/dist/mailbox-ui/assets/{index-CtUWEo-S.js → index-9-AxCxuB.js} +3 -3
  17. package/dist/mailbox-ui/assets/index-CWzt267f.css +1 -0
  18. package/dist/mailbox-ui/index.html +2 -2
  19. package/dist/mind/diary.js +6 -1
  20. package/dist/mind/friends/store-file.js +9 -1
  21. package/dist/mind/journal-index.js +2 -1
  22. package/dist/mind/pending.js +2 -1
  23. package/dist/mind/prompt.js +6 -5
  24. package/dist/repertoire/coding/context-pack.js +3 -2
  25. package/dist/repertoire/coding/manager.js +6 -2
  26. package/dist/repertoire/tools-awaiting.js +11 -6
  27. package/dist/repertoire/tools-base.js +2 -0
  28. package/dist/repertoire/tools-bridge.js +0 -1
  29. package/dist/repertoire/tools-record.js +463 -0
  30. package/dist/repertoire/tools-runtime.js +87 -0
  31. package/dist/repertoire/tools-session.js +9 -37
  32. package/dist/senses/bluebubbles/index.js +1 -1
  33. package/package.json +1 -1
  34. package/dist/mailbox-ui/assets/index-BPr5vNuM.css +0 -1
package/changelog.json CHANGED
@@ -1,6 +1,16 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.612",
6
+ "changes": [
7
+ "Drop the session archive and route durable history lookup through notes.",
8
+ "`query_session` keeps `mode=search` parseable during the deprecation cycle, but now returns a structured deprecation stub pointing agents to `search_notes` or `consult_notes`; the search mode is targeted for removal in alpha.616.",
9
+ "Session `.archive.ndjson` files are no longer written or read. Existing files under `~/AgentBundles/<agent>.ouro/state/sessions/**/*.archive.ndjson` are operationally inert and safe to delete with `rm`; the harness deliberately does not provide a built-in delete command now that archive pressure is gone and pruning is unnecessary, though an optional `ouro session archive prune` follow-up may land later if user demand emerges.",
10
+ "Canonical notes now have self-trust-only `note` and `consult_notes` tools backed by a rebuildable `notes/.index.json` version 1 semantic index derived from markdown notes.",
11
+ "Wire `daemon.sense_revive` through the live sense manager so `revive_sense({ sense: \"bluebubbles\" })` can recover BlueBubbles in the actual daemon topology."
12
+ ]
13
+ },
4
14
  {
5
15
  "version": "0.1.0-alpha.611",
6
16
  "changes": [
package/dist/arc/cares.js CHANGED
@@ -39,6 +39,7 @@ exports.readActiveCares = readActiveCares;
39
39
  exports.updateCare = updateCare;
40
40
  exports.resolveCare = resolveCare;
41
41
  const path = __importStar(require("path"));
42
+ const session_events_1 = require("../heart/session-events");
42
43
  const runtime_1 = require("../nerves/runtime");
43
44
  const json_store_1 = require("./json-store");
44
45
  function caresDir(agentRoot) {
@@ -49,8 +50,8 @@ function createCare(agentRoot, input) {
49
50
  const id = (0, json_store_1.generateTimestampId)("care");
50
51
  const care = {
51
52
  id,
52
- label: input.label,
53
- why: input.why,
53
+ label: (0, session_events_1.capStructuredRecordString)(input.label),
54
+ why: (0, session_events_1.capStructuredRecordString)(input.why),
54
55
  kind: input.kind,
55
56
  status: input.status,
56
57
  salience: input.salience,
@@ -59,7 +60,7 @@ function createCare(agentRoot, input) {
59
60
  relatedAgentIds: input.relatedAgentIds,
60
61
  relatedObligationIds: input.relatedObligationIds,
61
62
  relatedEpisodeIds: input.relatedEpisodeIds,
62
- currentRisk: input.currentRisk,
63
+ currentRisk: input.currentRisk === null ? null : (0, session_events_1.capStructuredRecordString)(input.currentRisk),
63
64
  nextCheckAt: input.nextCheckAt,
64
65
  createdAt: now,
65
66
  updatedAt: now,
@@ -107,6 +108,9 @@ function updateCare(agentRoot, id, updates) {
107
108
  const updated = {
108
109
  ...care,
109
110
  ...updates,
111
+ ...(typeof updates.label === "string" ? { label: (0, session_events_1.capStructuredRecordString)(updates.label) } : {}),
112
+ ...(typeof updates.why === "string" ? { why: (0, session_events_1.capStructuredRecordString)(updates.why) } : {}),
113
+ ...(typeof updates.currentRisk === "string" ? { currentRisk: (0, session_events_1.capStructuredRecordString)(updates.currentRisk) } : {}),
110
114
  id: care.id, // protect ID from overwrite
111
115
  createdAt: care.createdAt, // protect createdAt
112
116
  updatedAt: now,
@@ -37,6 +37,7 @@ exports.emitEpisode = emitEpisode;
37
37
  exports.readRecentEpisodes = readRecentEpisodes;
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
+ const session_events_1 = require("../heart/session-events");
40
41
  const runtime_1 = require("../nerves/runtime");
41
42
  function episodesDir(agentRoot) {
42
43
  return path.join(agentRoot, "arc", "episodes");
@@ -53,11 +54,11 @@ function emitEpisode(agentRoot, input) {
53
54
  id,
54
55
  timestamp: now,
55
56
  kind: input.kind,
56
- summary: input.summary,
57
- whyItMattered: input.whyItMattered,
57
+ summary: (0, session_events_1.capStructuredRecordString)(input.summary),
58
+ whyItMattered: (0, session_events_1.capStructuredRecordString)(input.whyItMattered),
58
59
  relatedEntities: input.relatedEntities,
59
60
  salience: input.salience,
60
- ...(input.meta ? { meta: input.meta } : {}),
61
+ ...(input.meta ? { meta: (0, session_events_1.capStructuredRecordStringLeaves)(input.meta) } : {}),
61
62
  };
62
63
  const dir = episodesDir(agentRoot);
63
64
  fs.mkdirSync(dir, { recursive: true });
@@ -38,6 +38,7 @@ exports.readOpenIntentions = readOpenIntentions;
38
38
  exports.resolveIntention = resolveIntention;
39
39
  exports.dismissIntention = dismissIntention;
40
40
  const path = __importStar(require("path"));
41
+ const session_events_1 = require("../heart/session-events");
41
42
  const runtime_1 = require("../nerves/runtime");
42
43
  const json_store_1 = require("./json-store");
43
44
  function intentionsDir(agentRoot) {
@@ -58,7 +59,7 @@ function captureIntention(agentRoot, input) {
58
59
  const id = (0, json_store_1.generateTimestampId)("int");
59
60
  const intention = {
60
61
  id,
61
- content: input.content,
62
+ content: (0, session_events_1.capStructuredRecordString)(input.content),
62
63
  status: "open",
63
64
  createdAt: now,
64
65
  updatedAt: now,
@@ -50,6 +50,7 @@ exports.advanceReturnObligation = advanceReturnObligation;
50
50
  exports.listActiveReturnObligations = listActiveReturnObligations;
51
51
  const path = __importStar(require("path"));
52
52
  const identity_1 = require("../heart/identity");
53
+ const session_events_1 = require("../heart/session-events");
53
54
  const runtime_1 = require("../nerves/runtime");
54
55
  const json_store_1 = require("./json-store");
55
56
  function obligationsDir(agentRoot) {
@@ -68,7 +69,7 @@ function createObligation(agentRoot, input) {
68
69
  id,
69
70
  origin: input.origin,
70
71
  ...(input.bridgeId ? { bridgeId: input.bridgeId } : {}),
71
- content: input.content,
72
+ content: (0, session_events_1.capStructuredRecordString)(input.content),
72
73
  status: "pending",
73
74
  createdAt: now,
74
75
  updatedAt: now,
@@ -110,13 +111,13 @@ function advanceObligation(agentRoot, obligationId, update) {
110
111
  obligation.currentSurface = update.currentSurface;
111
112
  }
112
113
  if (typeof update.currentArtifact === "string") {
113
- obligation.currentArtifact = update.currentArtifact;
114
+ obligation.currentArtifact = (0, session_events_1.capStructuredRecordString)(update.currentArtifact);
114
115
  }
115
116
  if (typeof update.nextAction === "string") {
116
- obligation.nextAction = update.nextAction;
117
+ obligation.nextAction = (0, session_events_1.capStructuredRecordString)(update.nextAction);
117
118
  }
118
119
  if (typeof update.latestNote === "string") {
119
- obligation.latestNote = update.latestNote;
120
+ obligation.latestNote = (0, session_events_1.capStructuredRecordString)(update.latestNote);
120
121
  }
121
122
  obligation.updatedAt = new Date().toISOString();
122
123
  (0, json_store_1.writeJsonFile)(dir, obligationId, obligation);
@@ -165,7 +166,14 @@ function enrichObligation(agentRoot, id, meaning) {
165
166
  throw new Error(`Obligation not found: ${id}`);
166
167
  }
167
168
  const obligation = existing;
168
- obligation.meaning = meaning;
169
+ obligation.meaning = {
170
+ ...meaning,
171
+ ...(typeof meaning.careReason === "string" ? { careReason: (0, session_events_1.capStructuredRecordString)(meaning.careReason) } : {}),
172
+ ...(typeof meaning.resumeHint === "string" ? { resumeHint: (0, session_events_1.capStructuredRecordString)(meaning.resumeHint) } : {}),
173
+ ...(meaning.waitingOn
174
+ ? { waitingOn: { ...meaning.waitingOn, detail: (0, session_events_1.capStructuredRecordString)(meaning.waitingOn.detail) } }
175
+ : {}),
176
+ };
169
177
  obligation.updatedAt = new Date().toISOString();
170
178
  (0, json_store_1.writeJsonFile)(dir, id, obligation);
171
179
  (0, runtime_1.emitNervesEvent)({
@@ -188,7 +196,11 @@ function getReturnObligationsDir(agentName) {
188
196
  }
189
197
  function createReturnObligation(agentName, obligation) {
190
198
  const dir = getReturnObligationsDir(agentName);
191
- (0, json_store_1.writeJsonFile)(dir, obligation.id, obligation);
199
+ const cappedObligation = {
200
+ ...obligation,
201
+ delegatedContent: (0, session_events_1.capStructuredRecordString)(obligation.delegatedContent),
202
+ };
203
+ (0, json_store_1.writeJsonFile)(dir, obligation.id, cappedObligation);
192
204
  const filePath = path.join(dir, `${obligation.id}.json`);
193
205
  (0, runtime_1.emitNervesEvent)({
194
206
  event: "mind.obligation_created",
@@ -41,6 +41,7 @@ exports.revisePonderPacket = revisePonderPacket;
41
41
  exports.advancePonderPacket = advancePonderPacket;
42
42
  exports.findHarnessFrictionPacket = findHarnessFrictionPacket;
43
43
  const path = __importStar(require("path"));
44
+ const session_events_1 = require("../heart/session-events");
44
45
  const runtime_1 = require("../nerves/runtime");
45
46
  const task_lifecycle_1 = require("./task-lifecycle");
46
47
  const json_store_1 = require("./json-store");
@@ -92,14 +93,14 @@ function createPonderPacket(agentRoot, input) {
92
93
  kind: input.kind,
93
94
  sop: packetSop(input.kind),
94
95
  status: "drafting",
95
- objective: input.objective,
96
- summary: input.summary,
97
- successCriteria: input.successCriteria,
96
+ objective: (0, session_events_1.capStructuredRecordString)(input.objective),
97
+ summary: (0, session_events_1.capStructuredRecordString)(input.summary),
98
+ successCriteria: (0, session_events_1.capStructuredRecordStringArray)(input.successCriteria),
98
99
  ...(input.origin ? { origin: input.origin } : {}),
99
100
  ...(input.relatedObligationId ? { relatedObligationId: input.relatedObligationId } : {}),
100
101
  ...(input.relatedReturnObligationId ? { relatedReturnObligationId: input.relatedReturnObligationId } : {}),
101
102
  ...(input.followsPacketId ? { followsPacketId: input.followsPacketId } : {}),
102
- payload: input.payload,
103
+ payload: (0, session_events_1.capStructuredRecordStringLeaves)(input.payload),
103
104
  createdAt: now,
104
105
  updatedAt: now,
105
106
  };
@@ -124,10 +125,10 @@ function revisePonderPacket(agentRoot, packetId, input) {
124
125
  ...existing,
125
126
  kind: input.kind,
126
127
  sop: packetSop(input.kind),
127
- objective: input.objective,
128
- summary: input.summary,
129
- successCriteria: input.successCriteria,
130
- payload: input.payload,
128
+ objective: (0, session_events_1.capStructuredRecordString)(input.objective),
129
+ summary: (0, session_events_1.capStructuredRecordString)(input.summary),
130
+ successCriteria: (0, session_events_1.capStructuredRecordStringArray)(input.successCriteria),
131
+ payload: (0, session_events_1.capStructuredRecordStringLeaves)(input.payload),
131
132
  updatedAt: Date.now(),
132
133
  };
133
134
  (0, json_store_1.writeJsonFile)(packetsDir(agentRoot), packetId, revised);
@@ -39,6 +39,7 @@ exports.writeAwaitRuntimeState = writeAwaitRuntimeState;
39
39
  exports.recordAwaitCheck = recordAwaitCheck;
40
40
  const path = __importStar(require("path"));
41
41
  const json_store_1 = require("../../arc/json-store");
42
+ const session_events_1 = require("../session-events");
42
43
  const runtime_1 = require("../../nerves/runtime");
43
44
  function awaitRuntimeStateDir(agentRoot) {
44
45
  return path.join(agentRoot, "state", "awaits");
@@ -67,7 +68,9 @@ function writeAwaitRuntimeState(agentRoot, name, partial) {
67
68
  const existing = readAwaitRuntimeState(agentRoot, name);
68
69
  const merged = {
69
70
  last_checked: partial.last_checked ?? existing?.last_checked ?? null,
70
- last_observation: partial.last_observation ?? existing?.last_observation ?? null,
71
+ last_observation: partial.last_observation != null
72
+ ? (0, session_events_1.capStructuredRecordString)(partial.last_observation)
73
+ : existing?.last_observation ?? null,
71
74
  checked_count: partial.checked_count ?? existing?.checked_count ?? 0,
72
75
  };
73
76
  const record = {
@@ -38,6 +38,7 @@ exports.createBridgeStore = createBridgeStore;
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
40
  const identity_1 = require("../identity");
41
+ const session_events_1 = require("../session-events");
41
42
  const runtime_1 = require("../../nerves/runtime");
42
43
  function sessionIdentityMatches(session, candidate) {
43
44
  return (session.friendId === candidate.friendId
@@ -58,7 +59,18 @@ function createBridgeStore(options = {}) {
58
59
  return {
59
60
  save(bridge) {
60
61
  ensureRoot();
61
- fs.writeFileSync(bridgeFilePath(rootDir, bridge.id), JSON.stringify(bridge, null, 2), "utf-8");
62
+ const cappedBridge = {
63
+ ...bridge,
64
+ objective: (0, session_events_1.capStructuredRecordString)(bridge.objective),
65
+ summary: (0, session_events_1.capStructuredRecordString)(bridge.summary),
66
+ attachedSessions: bridge.attachedSessions.map((session) => ({
67
+ ...session,
68
+ snapshot: typeof session.snapshot === "string"
69
+ ? (0, session_events_1.capStructuredRecordString)(session.snapshot)
70
+ : session.snapshot,
71
+ })),
72
+ };
73
+ fs.writeFileSync(bridgeFilePath(rootDir, bridge.id), JSON.stringify(cappedBridge, null, 2), "utf-8");
62
74
  (0, runtime_1.emitNervesEvent)({
63
75
  component: "engine",
64
76
  event: "engine.bridge_store_save",
@@ -68,7 +80,7 @@ function createBridgeStore(options = {}) {
68
80
  rootDir,
69
81
  },
70
82
  });
71
- return bridge;
83
+ return cappedBridge;
72
84
  },
73
85
  get(id) {
74
86
  const filePath = bridgeFilePath(rootDir, id);
@@ -449,6 +449,11 @@ function parseIncomingCommand(raw) {
449
449
  }
450
450
  return parsed;
451
451
  }
452
+ function isValidSenseReviveCommand(command) {
453
+ return typeof command.agent === "string"
454
+ && typeof command.sense === "string"
455
+ && typeof command.reason === "string";
456
+ }
452
457
  /**
453
458
  * Handle agent.senseTurn command: runs a full agent turn via the daemon process.
454
459
  * Dynamic import lazy-loads shared-turn. Hot-reload works because ouro dev
@@ -1114,6 +1119,48 @@ class OuroDaemon {
1114
1119
  message: "log streaming available via ouro logs",
1115
1120
  data: { logDir: "~/AgentBundles/<agent>.ouro/state/daemon/logs" },
1116
1121
  };
1122
+ case "daemon.sense_revive": {
1123
+ if (!isValidSenseReviveCommand(command)) {
1124
+ return {
1125
+ ok: false,
1126
+ error: "Invalid daemon.sense_revive payload: expected string fields 'agent', 'sense', and 'reason'.",
1127
+ };
1128
+ }
1129
+ const revivedSenseRow = await this.senseManager?.reviveSense?.(command.agent, command.sense);
1130
+ if (revivedSenseRow) {
1131
+ return {
1132
+ ok: true,
1133
+ message: `revived ${command.agent}/${command.sense}`,
1134
+ data: revivedSenseRow,
1135
+ };
1136
+ }
1137
+ const managedSenseSnapshots = this.processManager.listAgentSnapshots()
1138
+ .filter((snapshot) => snapshot.name.startsWith(`${command.agent}:`));
1139
+ if (managedSenseSnapshots.length === 0) {
1140
+ return {
1141
+ ok: false,
1142
+ error: `No managed agent '${command.agent}' is registered with daemon-managed senses.`,
1143
+ };
1144
+ }
1145
+ const exactTargetName = `${command.agent}:${command.sense}`;
1146
+ const target = managedSenseSnapshots.find((snapshot) => snapshot.name === exactTargetName)
1147
+ ?? managedSenseSnapshots.find((snapshot) => snapshot.channel === command.sense);
1148
+ if (!target) {
1149
+ return {
1150
+ ok: false,
1151
+ error: `No managed sense '${command.sense}' is registered for agent '${command.agent}'.`,
1152
+ };
1153
+ }
1154
+ this.processManager.resetAgentFailureState(target.name);
1155
+ await this.processManager.startAgent(target.name);
1156
+ const freshSnapshot = this.processManager.listAgentSnapshots()
1157
+ .find((snapshot) => snapshot.name === target.name) ?? target;
1158
+ return {
1159
+ ok: true,
1160
+ message: `revived ${command.agent}/${command.sense}`,
1161
+ data: freshSnapshot,
1162
+ };
1163
+ }
1117
1164
  case "agent.start":
1118
1165
  await this.processManager.startAgent(command.agent);
1119
1166
  return { ok: true, message: `started ${command.agent}` };
@@ -502,6 +502,19 @@ class DaemonProcessManager {
502
502
  await this.stopAgent(state.config.name);
503
503
  }
504
504
  }
505
+ resetAgentFailureState(agent) {
506
+ const state = this.requireAgent(agent);
507
+ this.clearRestartTimer(state);
508
+ this.clearCooldownTimer(state);
509
+ state.cooldownRetryCount = 0;
510
+ state.crashTimestamps = [];
511
+ state.fastCrashCount = 0;
512
+ state.respawnLoopTripped = false;
513
+ state.orchestratedRestartTimestamps = [];
514
+ state.snapshot.errorReason = null;
515
+ state.snapshot.fixHint = null;
516
+ this.notifySnapshotChange(state.snapshot);
517
+ }
505
518
  sendToAgent(agent, message) {
506
519
  const state = this.requireAgent(agent);
507
520
  if (!state.process)
@@ -667,6 +667,18 @@ class DaemonSenseManager {
667
667
  }
668
668
  await this.processManager.startAgent?.(managedName);
669
669
  }
670
+ async reviveSense(agent, sense) {
671
+ const parsed = parseSenseSnapshotName(`${agent}:${sense}`);
672
+ if (!parsed)
673
+ return null;
674
+ const context = this.contexts.get(parsed.agent);
675
+ if (!context || !context.senses[parsed.sense].enabled)
676
+ return null;
677
+ const managedName = `${parsed.agent}:${parsed.sense}`;
678
+ this.processManager.resetAgentFailureState?.(managedName);
679
+ await this.processManager.startAgent?.(managedName);
680
+ return this.listSenseRows().find((row) => row.agent === parsed.agent && row.sense === parsed.sense) ?? null;
681
+ }
670
682
  listSenseRows() {
671
683
  const runtime = new Map();
672
684
  for (const snapshot of this.processManager.listAgentSnapshots()) {
@@ -53,6 +53,7 @@ const daemon_health_1 = require("../../daemon/daemon-health");
53
53
  const shared_1 = require("./shared");
54
54
  const agent_machine_1 = require("./agent-machine");
55
55
  const sessions_1 = require("./sessions");
56
+ const NOTES_VIEW_LIMIT = 20;
56
57
  /* v8 ignore start — defensive parsing of on-disk JSON, fallback branches are safety nets */
57
58
  function readCodingDeep(agentRoot) {
58
59
  const stateFilePath = path.join(agentRoot, "state", "coding", "sessions.json");
@@ -376,13 +377,117 @@ function readNotesView(agentRoot) {
376
377
  // no journal index
377
378
  }
378
379
  journalEntries.sort((a, b) => b.mtime - a.mtime);
380
+ const canonicalNotes = readCanonicalNotes(agentRoot);
379
381
  return {
380
382
  diaryEntryCount: diaryEntries.length,
381
- recentDiaryEntries: diaryEntries.slice(0, 20),
383
+ recentDiaryEntries: diaryEntries.slice(0, NOTES_VIEW_LIMIT),
382
384
  journalEntryCount: journalEntries.length,
383
- recentJournalEntries: journalEntries.slice(0, 20),
385
+ recentJournalEntries: journalEntries.slice(0, NOTES_VIEW_LIMIT),
386
+ canonicalNoteCount: canonicalNotes.length,
387
+ recentCanonicalNotes: canonicalNotes.slice(0, NOTES_VIEW_LIMIT),
384
388
  };
385
389
  }
390
+ function readCanonicalNotes(agentRoot) {
391
+ const notesRoot = path.join(agentRoot, "notes");
392
+ const notes = [];
393
+ for (const filename of (0, shared_1.safeReaddir)(notesRoot)) {
394
+ if (!filename.endsWith(".md"))
395
+ continue;
396
+ const filePath = path.join(notesRoot, filename);
397
+ if ((0, shared_1.safeIsDirectory)(filePath))
398
+ continue;
399
+ try {
400
+ const raw = fs.readFileSync(filePath, "utf-8");
401
+ const { frontmatter, body } = parseMarkdownFrontmatter(raw);
402
+ const writtenAt = safeCanonicalNoteTimestamp(frontmatter.created_at, filePath);
403
+ notes.push({
404
+ filename,
405
+ title: firstMarkdownHeading(body) ?? titleFromFilename(filename),
406
+ tags: Array.isArray(frontmatter.tags) ? frontmatter.tags : [],
407
+ preview: body.slice(0, 200),
408
+ writtenAt,
409
+ });
410
+ }
411
+ catch {
412
+ // skip unreadable notes
413
+ }
414
+ }
415
+ notes.sort((a, b) => b.writtenAt.localeCompare(a.writtenAt) || b.filename.localeCompare(a.filename));
416
+ return notes;
417
+ }
418
+ function safeCanonicalNoteTimestamp(value, filePath) {
419
+ const fallback = (0, shared_1.safeFileMtime)(filePath);
420
+ if (typeof value !== "string")
421
+ return fallback;
422
+ const trimmed = value.trim();
423
+ if (!trimmed)
424
+ return fallback;
425
+ return Number.isNaN(new Date(trimmed).getTime()) ? fallback : trimmed;
426
+ }
427
+ function parseMarkdownFrontmatter(raw) {
428
+ const lines = raw.split(/\r?\n/);
429
+ if (lines[0] !== "---")
430
+ return { frontmatter: {}, body: raw };
431
+ const endIndex = lines.findIndex((line, index) => index > 0 && line === "---");
432
+ if (endIndex === -1)
433
+ return { frontmatter: {}, body: raw };
434
+ return {
435
+ frontmatter: parseMinimalFrontmatter(lines.slice(1, endIndex)),
436
+ body: lines.slice(endIndex + 1).join("\n").replace(/^\n/, ""),
437
+ };
438
+ }
439
+ function parseMinimalFrontmatter(lines) {
440
+ const frontmatter = {};
441
+ for (let index = 0; index < lines.length; index += 1) {
442
+ const line = lines[index];
443
+ const match = line?.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
444
+ if (!match)
445
+ continue;
446
+ const key = match[1];
447
+ const rawValue = match[2];
448
+ if (key === "tags") {
449
+ frontmatter.tags = parseFrontmatterTags(rawValue, lines, index + 1);
450
+ continue;
451
+ }
452
+ frontmatter[key] = rawValue.trim();
453
+ }
454
+ return frontmatter;
455
+ }
456
+ function parseFrontmatterTags(rawValue, lines, startIndex) {
457
+ const trimmed = rawValue.trim();
458
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
459
+ try {
460
+ const parsed = JSON.parse(trimmed);
461
+ return parsed.filter((item) => typeof item === "string");
462
+ }
463
+ catch {
464
+ return trimmed.slice(1, -1).split(",").map((tag) => tag.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
465
+ }
466
+ }
467
+ if (trimmed.length > 0)
468
+ return [trimmed];
469
+ const tags = [];
470
+ for (let index = startIndex; index < lines.length; index += 1) {
471
+ const itemMatch = lines[index]?.match(/^\s*-\s*(.+)$/);
472
+ if (!itemMatch)
473
+ break;
474
+ const tag = itemMatch[1].trim().replace(/^['"]|['"]$/g, "");
475
+ if (tag)
476
+ tags.push(tag);
477
+ }
478
+ return tags;
479
+ }
480
+ function firstMarkdownHeading(body) {
481
+ const heading = body
482
+ .split(/\r?\n/)
483
+ .map((line) => line.match(/^#\s+(.+)$/)?.[1]?.trim())
484
+ .find((title) => title && title.length > 0);
485
+ return heading ?? null;
486
+ }
487
+ function titleFromFilename(filename) {
488
+ const stem = filename.replace(/\.md$/i, "").replace(/^\d{4}-\d{2}-\d{2}-/, "");
489
+ return stem.replace(/[-_]+/g, " ").trim() || filename;
490
+ }
386
491
  function readFriendView(agentName, options = {}) {
387
492
  const bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
388
493
  const agentRoot = path.join(bundlesRoot, `${agentName}.ouro`);
@@ -213,8 +213,7 @@ function readSessionTranscript(agentName, friendId, channel, key, options = {})
213
213
  const envelope = (0, shared_1.readSessionEnvelope)(sessionPath);
214
214
  if (!envelope)
215
215
  return null;
216
- // Use full event history (envelope + archive) for complete transcript
217
- const rawMessages = (0, session_events_1.loadFullEventHistory)(sessionPath);
216
+ const rawMessages = envelope.events;
218
217
  const friendsDir = path.join(agentRoot, "friends");
219
218
  const friendName = (0, shared_1.resolveFriendName)(friendsDir, friendId);
220
219
  const messages = rawMessages;
@@ -225,6 +224,7 @@ function readSessionTranscript(agentName, friendId, channel, key, options = {})
225
224
  key,
226
225
  sessionPath,
227
226
  messageCount: messages.length,
227
+ truncatedHistory: envelope.projection.trimmed,
228
228
  lastUsage: parseSessionUsage(envelope.lastUsage),
229
229
  continuity: parseSessionContinuity(envelope.state),
230
230
  messages,