@ouro.bot/cli 0.1.0-alpha.314 → 0.1.0-alpha.315

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.
@@ -1,65 +1,17 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.recallSession = recallSession;
37
4
  exports.searchSessionTranscript = searchSessionTranscript;
38
- const fs = __importStar(require("fs"));
39
5
  const runtime_1 = require("../nerves/runtime");
40
- function normalizeContent(content) {
41
- if (typeof content === "string")
42
- return content;
43
- if (!Array.isArray(content))
44
- return "";
45
- return content
46
- .map((part) => (part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part
47
- ? String(part.text ?? "")
48
- : ""))
49
- .filter((text) => text.length > 0)
50
- .join("");
51
- }
52
- function normalizeSessionMessages(messages) {
53
- if (!Array.isArray(messages))
54
- return [];
55
- return messages
56
- .map((message) => {
57
- const record = message && typeof message === "object" ? message : {};
58
- return {
59
- role: typeof record.role === "string" ? record.role : "",
60
- content: normalizeContent(record.content),
61
- };
62
- })
6
+ const session_events_1 = require("./session-events");
7
+ function normalizeSessionMessages(events) {
8
+ return events
9
+ .map((event) => ({
10
+ id: event.id,
11
+ role: event.role,
12
+ content: (0, session_events_1.extractEventText)(event),
13
+ timestamp: (0, session_events_1.formatSessionEventTimestamp)(event),
14
+ }))
63
15
  .filter((message) => message.role !== "system" && message.content.length > 0);
64
16
  }
65
17
  function buildSummaryInstruction(friendId, channel, trustLevel) {
@@ -114,6 +66,10 @@ function buildSearchExcerpts(messages, query, maxMatches) {
114
66
  const start = Math.max(0, i - 1);
115
67
  const end = Math.min(messages.length, i + 2);
116
68
  const excerpt = messages
69
+ .slice(start, end)
70
+ .map((message) => `[${message.timestamp} | ${message.role} | ${message.id}] ${clip(message.content, 200)}`)
71
+ .join("\n");
72
+ const signature = messages
117
73
  .slice(start, end)
118
74
  .map((message) => `[${message.role}] ${clip(message.content, 200)}`)
119
75
  .join("\n");
@@ -121,15 +77,15 @@ function buildSearchExcerpts(messages, query, maxMatches) {
121
77
  .slice(start, end)
122
78
  .filter((message) => message.content.toLowerCase().includes(normalizedQuery))
123
79
  .length;
124
- candidates.push({ excerpt, score, index: i });
80
+ candidates.push({ excerpt, signature, score, index: i });
125
81
  }
126
82
  const seen = new Set();
127
83
  return candidates
128
84
  .sort((a, b) => b.score - a.score || a.index - b.index)
129
85
  .filter((candidate) => {
130
- if (seen.has(candidate.excerpt))
86
+ if (seen.has(candidate.signature))
131
87
  return false;
132
- seen.add(candidate.excerpt);
88
+ seen.add(candidate.signature);
133
89
  return true;
134
90
  })
135
91
  .slice(0, maxMatches)
@@ -147,20 +103,15 @@ async function recallSession(options) {
147
103
  messageCount: options.messageCount,
148
104
  },
149
105
  });
150
- let raw;
151
- try {
152
- raw = fs.readFileSync(options.sessionPath, "utf-8");
153
- }
154
- catch {
106
+ const envelope = (0, session_events_1.loadSessionEnvelopeFile)(options.sessionPath);
107
+ if (!envelope)
155
108
  return { kind: "missing" };
156
- }
157
- const parsed = JSON.parse(raw);
158
- const tailMessages = normalizeSessionMessages(parsed.messages).slice(-options.messageCount);
109
+ const tailMessages = normalizeSessionMessages(envelope.events).slice(-options.messageCount);
159
110
  if (tailMessages.length === 0) {
160
111
  return { kind: "empty" };
161
112
  }
162
113
  const transcript = tailMessages
163
- .map((message) => `[${message.role}] ${message.content}`)
114
+ .map((message) => `[${message.timestamp} | ${message.role} | ${message.id}] ${message.content}`)
164
115
  .join("\n");
165
116
  const summary = options.summarize
166
117
  ? await options.summarize(transcript, buildSummaryInstruction(options.friendId, options.channel, options.trustLevel ?? "family"))
@@ -186,15 +137,10 @@ async function searchSessionTranscript(options) {
186
137
  maxMatches: options.maxMatches ?? 5,
187
138
  },
188
139
  });
189
- let raw;
190
- try {
191
- raw = fs.readFileSync(options.sessionPath, "utf-8");
192
- }
193
- catch {
140
+ const envelope = (0, session_events_1.loadSessionEnvelopeFile)(options.sessionPath);
141
+ if (!envelope)
194
142
  return { kind: "missing" };
195
- }
196
- const parsed = JSON.parse(raw);
197
- const messages = normalizeSessionMessages(parsed.messages);
143
+ const messages = normalizeSessionMessages(envelope.events);
198
144
  if (messages.length === 0) {
199
145
  return { kind: "empty" };
200
146
  }
@@ -192,6 +192,7 @@ function buildStartOfTurnPacket(view, opts) {
192
192
  cares: buildCaresSection(view.activeCares),
193
193
  presence: buildPresenceSection(view.peerPresence),
194
194
  resumeHint: buildResumeHint(view, opts?.canonicalObligations ? effectiveObligations : undefined),
195
+ currentSessionTiming: opts?.currentSessionTiming,
195
196
  tempo,
196
197
  tokenBudget,
197
198
  assembledAt: new Date().toISOString(),
@@ -230,6 +231,7 @@ function renderStartOfTurnPacket(packet) {
230
231
  { label: "bundleState", content: (0, bundle_state_1.renderBundleStateHint)(packet.bundleState ?? []), priority: 7 },
231
232
  { label: "syncFailure", content: packet.syncFailure ?? "", priority: 7 },
232
233
  { label: "resume", content: packet.resumeHint, priority: 6 },
234
+ { label: "sessionTiming", content: packet.currentSessionTiming ?? "", priority: 5 },
233
235
  { label: "obligations", content: packet.obligations, priority: 5 },
234
236
  { label: "cares", content: packet.cares, priority: 4 },
235
237
  { label: "plot", content: packet.plotLine, priority: 3 },
@@ -33,20 +33,23 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.validateSessionMessages = exports.repairSessionMessages = exports.migrateToolNames = void 0;
36
37
  exports.trimMessages = trimMessages;
37
- exports.validateSessionMessages = validateSessionMessages;
38
- exports.repairSessionMessages = repairSessionMessages;
39
- exports.migrateToolNames = migrateToolNames;
40
38
  exports.saveSession = saveSession;
41
39
  exports.appendSyntheticAssistantMessage = appendSyntheticAssistantMessage;
42
40
  exports.loadSession = loadSession;
43
41
  exports.postTurn = postTurn;
44
42
  exports.deleteSession = deleteSession;
45
43
  const config_1 = require("../heart/config");
44
+ const session_events_1 = require("../heart/session-events");
46
45
  const runtime_1 = require("../nerves/runtime");
47
46
  const fs = __importStar(require("fs"));
48
47
  const path = __importStar(require("path"));
49
48
  const token_estimate_1 = require("./token-estimate");
49
+ var session_events_2 = require("../heart/session-events");
50
+ Object.defineProperty(exports, "migrateToolNames", { enumerable: true, get: function () { return session_events_2.migrateToolNames; } });
51
+ Object.defineProperty(exports, "repairSessionMessages", { enumerable: true, get: function () { return session_events_2.repairSessionMessages; } });
52
+ Object.defineProperty(exports, "validateSessionMessages", { enumerable: true, get: function () { return session_events_2.validateSessionMessages; } });
50
53
  function buildTrimmableBlocks(messages) {
51
54
  const blocks = [];
52
55
  let i = 0;
@@ -175,163 +178,47 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
175
178
  * user → assistant (with optional tool calls/results) → user → assistant...
176
179
  * Never assistant → assistant without a user in between.
177
180
  */
178
- function validateSessionMessages(messages) {
179
- const violations = [];
180
- let prevNonToolRole = null;
181
- let prevAssistantHadToolCalls = false;
182
- let sawToolResultSincePrevAssistant = false;
183
- for (let i = 0; i < messages.length; i++) {
184
- const msg = messages[i];
185
- if (msg.role === "system")
186
- continue;
187
- if (msg.role === "tool") {
188
- sawToolResultSincePrevAssistant = true;
189
- continue;
190
- }
191
- if (msg.role === "assistant" && prevNonToolRole === "assistant") {
192
- // assistant → tool(s) → assistant is valid (tool call flow)
193
- if (!(prevAssistantHadToolCalls && sawToolResultSincePrevAssistant)) {
194
- violations.push(`back-to-back assistant at index ${i}`);
195
- }
196
- }
197
- prevAssistantHadToolCalls = msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;
198
- sawToolResultSincePrevAssistant = false;
199
- prevNonToolRole = msg.role;
200
- }
201
- return violations;
202
- }
203
- /**
204
- * Repairs session invariant violations by merging consecutive assistant messages.
205
- */
206
- function repairSessionMessages(messages) {
207
- const violations = validateSessionMessages(messages);
208
- if (violations.length === 0)
209
- return messages;
210
- const result = [];
211
- for (const msg of messages) {
212
- if (msg.role === "assistant" && result.length > 0) {
213
- const prev = result[result.length - 1];
214
- if (prev.role === "assistant" && !("tool_calls" in prev)) {
215
- const prevContent = typeof prev.content === "string" ? prev.content : "";
216
- const curContent = typeof msg.content === "string" ? msg.content : "";
217
- prev.content = `${prevContent}\n\n${curContent}`;
218
- continue;
219
- }
220
- }
221
- result.push(msg);
222
- }
223
- (0, runtime_1.emitNervesEvent)({
224
- level: "info",
225
- event: "mind.session_invariant_repair",
226
- component: "mind",
227
- message: "repaired session invariant violations",
228
- meta: { violations },
229
- });
230
- return result;
231
- }
232
- function stripOrphanedToolResults(messages) {
233
- const validCallIds = new Set();
234
- for (const msg of messages) {
235
- if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls))
236
- continue;
237
- for (const toolCall of msg.tool_calls)
238
- validCallIds.add(toolCall.id);
239
- }
240
- let removed = 0;
241
- const repaired = messages.filter((msg) => {
242
- if (msg.role !== "tool")
243
- return true;
244
- const keep = validCallIds.has(msg.tool_call_id);
245
- if (!keep)
246
- removed++;
247
- return keep;
248
- });
249
- if (removed > 0) {
250
- (0, runtime_1.emitNervesEvent)({
251
- level: "info",
252
- event: "mind.session_orphan_tool_result_repair",
253
- component: "mind",
254
- message: "removed orphaned tool results from session history",
255
- meta: { removed },
256
- });
257
- }
258
- return repaired;
259
- }
260
- // Tool renames that have shipped. Old names in session history confuse the
261
- // model into calling tools that no longer exist. Applied on session load so
262
- // the transcript uses the current vocabulary.
263
- const TOOL_NAME_MIGRATIONS = {
264
- final_answer: "settle",
265
- no_response: "observe",
266
- go_inward: "ponder",
267
- descend: "ponder",
268
- memory_save: "diary_write",
269
- memory_search: "recall",
270
- };
271
- function migrateToolNames(messages) {
272
- let migrated = 0;
273
- const result = messages.map((msg) => {
274
- if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0)
275
- return msg;
276
- let changed = false;
277
- const updatedCalls = msg.tool_calls.map((tc) => {
278
- if (tc.type !== "function")
279
- return tc;
280
- const newName = TOOL_NAME_MIGRATIONS[tc.function.name];
281
- if (!newName)
282
- return tc;
283
- changed = true;
284
- migrated++;
285
- return { ...tc, function: { ...tc.function, name: newName } };
286
- });
287
- return changed ? { ...msg, tool_calls: updatedCalls } : msg;
288
- });
289
- if (migrated > 0) {
290
- (0, runtime_1.emitNervesEvent)({
291
- level: "info",
292
- event: "mind.session_tool_name_migration",
293
- component: "mind",
294
- message: "migrated deprecated tool names in session history",
295
- meta: { migrated },
296
- });
297
- }
298
- return result;
181
+ function denormalizeContinuityState(state) {
182
+ if (!state.mustResolveBeforeHandoff && typeof state.lastFriendActivityAt !== "string")
183
+ return undefined;
184
+ return {
185
+ ...(state.mustResolveBeforeHandoff ? { mustResolveBeforeHandoff: true } : {}),
186
+ ...(typeof state.lastFriendActivityAt === "string" ? { lastFriendActivityAt: state.lastFriendActivityAt } : {}),
187
+ };
299
188
  }
300
- function saveSession(filePath, messages, lastUsage, state) {
301
- const violations = validateSessionMessages(messages);
302
- if (violations.length > 0) {
303
- (0, runtime_1.emitNervesEvent)({
304
- level: "info",
305
- event: "mind.session_invariant_violation",
306
- component: "mind",
307
- message: "session invariant violated on save",
308
- meta: { path: filePath, violations },
309
- });
310
- messages = repairSessionMessages(messages);
311
- }
312
- messages = stripOrphanedToolResults(messages);
189
+ function writeSessionEnvelope(filePath, envelope) {
313
190
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
314
- const envelope = { version: 1, messages };
315
- if (lastUsage)
316
- envelope.lastUsage = lastUsage;
317
- if (state?.mustResolveBeforeHandoff === true || typeof state?.lastFriendActivityAt === "string") {
318
- envelope.state = {
319
- ...(state?.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
320
- ...(typeof state?.lastFriendActivityAt === "string" ? { lastFriendActivityAt: state.lastFriendActivityAt } : {}),
321
- };
322
- }
323
191
  fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
324
192
  }
193
+ function saveSession(filePath, messages, lastUsage, state) {
194
+ const existing = (0, session_events_1.loadSessionEnvelopeFile)(filePath);
195
+ const previousMessages = existing ? (0, session_events_1.projectProviderMessages)(existing) : [];
196
+ const sanitized = (0, session_events_1.sanitizeProviderMessages)(messages);
197
+ const envelope = (0, session_events_1.buildCanonicalSessionEnvelope)({
198
+ existing,
199
+ previousMessages,
200
+ currentMessages: sanitized,
201
+ trimmedMessages: sanitized,
202
+ recordedAt: new Date().toISOString(),
203
+ lastUsage: lastUsage ?? null,
204
+ state,
205
+ projectionBasis: {
206
+ maxTokens: null,
207
+ contextMargin: null,
208
+ inputTokens: lastUsage?.input_tokens ?? null,
209
+ },
210
+ });
211
+ writeSessionEnvelope(filePath, envelope);
212
+ }
325
213
  function appendSyntheticAssistantMessage(filePath, content) {
326
214
  try {
327
215
  if (!fs.existsSync(filePath))
328
216
  return false;
329
- const raw = fs.readFileSync(filePath, "utf-8");
330
- const data = JSON.parse(raw);
331
- if (data.version !== 1 || !Array.isArray(data.messages))
217
+ const envelope = (0, session_events_1.loadSessionEnvelopeFile)(filePath);
218
+ if (!envelope)
332
219
  return false;
333
- data.messages.push({ role: "assistant", content });
334
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
220
+ const updated = (0, session_events_1.appendSyntheticAssistantEvent)(envelope, content, new Date().toISOString());
221
+ writeSessionEnvelope(filePath, updated);
335
222
  (0, runtime_1.emitNervesEvent)({
336
223
  component: "mind",
337
224
  event: "mind.session_synthetic_message_appended",
@@ -346,44 +233,25 @@ function appendSyntheticAssistantMessage(filePath, content) {
346
233
  }
347
234
  function loadSession(filePath) {
348
235
  try {
349
- const raw = fs.readFileSync(filePath, "utf-8");
350
- const data = JSON.parse(raw);
351
- if (data.version !== 1)
236
+ const envelope = (0, session_events_1.loadSessionEnvelopeFile)(filePath);
237
+ if (!envelope)
352
238
  return null;
353
- let messages = data.messages;
354
- const violations = validateSessionMessages(messages);
355
- if (violations.length > 0) {
356
- (0, runtime_1.emitNervesEvent)({
357
- level: "info",
358
- event: "mind.session_invariant_violation",
359
- component: "mind",
360
- message: "session invariant violated on load",
361
- meta: { path: filePath, violations },
362
- });
363
- messages = repairSessionMessages(messages);
364
- }
365
- messages = stripOrphanedToolResults(messages);
366
- messages = migrateToolNames(messages);
367
- const rawState = data?.state && typeof data.state === "object" && data.state !== null
368
- ? data.state
369
- : undefined;
370
- const state = rawState && (rawState.mustResolveBeforeHandoff === true
371
- || typeof rawState.lastFriendActivityAt === "string")
372
- ? {
373
- ...(rawState.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
374
- ...(typeof rawState.lastFriendActivityAt === "string" ? { lastFriendActivityAt: rawState.lastFriendActivityAt } : {}),
375
- }
376
- : undefined;
377
- return { messages, lastUsage: data.lastUsage, state };
239
+ return {
240
+ messages: (0, session_events_1.projectProviderMessages)(envelope),
241
+ events: envelope.events,
242
+ lastUsage: envelope.lastUsage ?? undefined,
243
+ state: denormalizeContinuityState(envelope.state),
244
+ };
378
245
  }
379
246
  catch {
380
247
  return null;
381
248
  }
382
249
  }
383
250
  function postTurn(messages, sessPath, usage, hooks, state) {
251
+ const preTrimMessages = [...messages];
384
252
  if (hooks?.beforeTrim) {
385
253
  try {
386
- hooks.beforeTrim([...messages]);
254
+ hooks.beforeTrim(preTrimMessages);
387
255
  }
388
256
  catch (error) {
389
257
  (0, runtime_1.emitNervesEvent)({
@@ -398,9 +266,26 @@ function postTurn(messages, sessPath, usage, hooks, state) {
398
266
  }
399
267
  }
400
268
  const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
401
- const trimmed = trimMessages(messages, maxTokens, contextMargin, usage?.input_tokens);
269
+ const currentMessages = (0, session_events_1.sanitizeProviderMessages)(messages);
270
+ const trimmed = trimMessages(currentMessages, maxTokens, contextMargin, usage?.input_tokens);
402
271
  messages.splice(0, messages.length, ...trimmed);
403
- saveSession(sessPath, messages, usage, state);
272
+ const existing = (0, session_events_1.loadSessionEnvelopeFile)(sessPath);
273
+ const previousMessages = existing ? (0, session_events_1.projectProviderMessages)(existing) : [];
274
+ const envelope = (0, session_events_1.buildCanonicalSessionEnvelope)({
275
+ existing,
276
+ previousMessages,
277
+ currentMessages,
278
+ trimmedMessages: trimmed,
279
+ recordedAt: new Date().toISOString(),
280
+ lastUsage: usage ?? null,
281
+ state,
282
+ projectionBasis: {
283
+ maxTokens,
284
+ contextMargin,
285
+ inputTokens: usage?.input_tokens ?? null,
286
+ },
287
+ });
288
+ writeSessionEnvelope(sessPath, envelope);
404
289
  }
405
290
  function deleteSession(filePath) {
406
291
  try {
@@ -127,8 +127,20 @@ function buildSessionSummary(options) {
127
127
  return "";
128
128
  const lines = ["## active sessions"];
129
129
  for (const entry of entries) {
130
- const ago = formatTimeAgo(now - entry.lastActivityMs);
131
- lines.push(`- ${entry.friendName}/${entry.channel}/${entry.key} (last: ${ago})`);
130
+ const parts = [];
131
+ if (entry.lastInboundAt) {
132
+ parts.push(`in ${formatTimeAgo(now - Date.parse(entry.lastInboundAt))}`);
133
+ }
134
+ else {
135
+ parts.push(`last ${formatTimeAgo(now - entry.lastActivityMs)}`);
136
+ }
137
+ if (entry.lastOutboundAt) {
138
+ parts.push(`out ${formatTimeAgo(now - Date.parse(entry.lastOutboundAt))}`);
139
+ }
140
+ if (entry.unansweredInboundCount > 0) {
141
+ parts.push(`${entry.unansweredInboundCount} waiting`);
142
+ }
143
+ lines.push(`- ${entry.friendName}/${entry.channel}/${entry.key} (${parts.join(" · ")})`);
132
144
  }
133
145
  return lines.join("\n");
134
146
  }
@@ -91,6 +91,10 @@ const DISPATCH_EXEMPT_PATTERNS = [
91
91
  "heart/attachments/originals",
92
92
  "heart/attachments/sources/index",
93
93
  "heart/attachments/sources/cli-local-file",
94
+ // Browser-safe Outlook contract helpers: shared types/formatting helpers
95
+ // consumed by server readers and the UI. Outlook read/render modules own
96
+ // the observability for these projections.
97
+ "heart/outlook/outlook-types",
94
98
  ];
95
99
  function isDispatchExempt(filePath) {
96
100
  return DISPATCH_EXEMPT_PATTERNS.some((pattern) => filePath.includes(pattern));