@ouro.bot/cli 0.1.0-alpha.346 → 0.1.0-alpha.347

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/changelog.json CHANGED
@@ -1,6 +1,12 @@
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.347",
6
+ "changes": [
7
+ "Post-turn session persist functions now return the events array directly, eliminating a redundant `loadSession` file read after every CLI TUI turn. `postTurnPersist` returns `SessionEvent[]` and `deferPostTurnPersist` returns `Promise<SessionEvent[]>`, so the CLI sense uses the returned data instead of re-reading the file it just wrote."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.346",
6
12
  "changes": [
@@ -676,11 +676,11 @@ function selectProjectedEventIds(currentMessages, currentEventIds, trimmedMessag
676
676
  }
677
677
  function buildCanonicalSessionEnvelope(options) {
678
678
  const existing = options.existing;
679
- // Capture ingress timestamps before sanitization strips extra properties
680
- const currentIngressTimes = options.currentMessages.map(getIngressTime);
681
- const previousMessages = sanitizeProviderMessages(options.previousMessages);
682
- const currentMessages = sanitizeProviderMessages(options.currentMessages);
683
- const trimmedMessages = sanitizeProviderMessages(options.trimmedMessages);
679
+ // Callers pass pre-sanitized messages + pre-captured ingress times.
680
+ const currentIngressTimes = options.currentIngressTimes ?? options.currentMessages.map(getIngressTime);
681
+ const previousMessages = options.previousMessages;
682
+ const currentMessages = options.currentMessages;
683
+ const trimmedMessages = options.trimmedMessages;
684
684
  const previousProjectionIds = existing?.projection.eventIds.length
685
685
  ? [...existing.projection.eventIds]
686
686
  : existing?.events.map((event) => event.id) ?? [];
@@ -39,6 +39,9 @@ exports.saveSession = saveSession;
39
39
  exports.appendSyntheticAssistantMessage = appendSyntheticAssistantMessage;
40
40
  exports.loadSession = loadSession;
41
41
  exports.postTurn = postTurn;
42
+ exports.postTurnTrim = postTurnTrim;
43
+ exports.postTurnPersist = postTurnPersist;
44
+ exports.deferPostTurnPersist = deferPostTurnPersist;
42
45
  exports.deleteSession = deleteSession;
43
46
  const config_1 = require("../heart/config");
44
47
  const session_events_1 = require("../heart/session-events");
@@ -193,12 +196,14 @@ function writeSessionEnvelope(filePath, envelope) {
193
196
  function saveSession(filePath, messages, lastUsage, state) {
194
197
  const existing = (0, session_events_1.loadSessionEnvelopeFile)(filePath);
195
198
  const previousMessages = existing ? (0, session_events_1.projectProviderMessages)(existing) : [];
199
+ const currentIngressTimes = messages.map(session_events_1.getIngressTime);
196
200
  const sanitized = (0, session_events_1.sanitizeProviderMessages)(messages);
197
201
  const envelope = (0, session_events_1.buildCanonicalSessionEnvelope)({
198
202
  existing,
199
203
  previousMessages,
200
204
  currentMessages: sanitized,
201
205
  trimmedMessages: sanitized,
206
+ currentIngressTimes,
202
207
  recordedAt: new Date().toISOString(),
203
208
  lastUsage: lastUsage ?? null,
204
209
  state,
@@ -247,7 +252,19 @@ function loadSession(filePath) {
247
252
  return null;
248
253
  }
249
254
  }
255
+ /**
256
+ * Synchronous post-turn: sanitize, trim (mutates messages in place), and persist to disk.
257
+ * For non-blocking persist, use postTurnTrim() + deferPostTurnPersist() instead.
258
+ */
250
259
  function postTurn(messages, sessPath, usage, hooks, state) {
260
+ const prepared = postTurnTrim(messages, usage, hooks);
261
+ postTurnPersist(sessPath, prepared, usage, state);
262
+ }
263
+ /**
264
+ * Synchronous phase: run hooks, sanitize, trim, and mutate the messages array in place.
265
+ * Returns the data needed by postTurnPersist / deferPostTurnPersist.
266
+ */
267
+ function postTurnTrim(messages, usage, hooks) {
251
268
  const preTrimMessages = [...messages];
252
269
  if (hooks?.beforeTrim) {
253
270
  try {
@@ -266,26 +283,59 @@ function postTurn(messages, sessPath, usage, hooks, state) {
266
283
  }
267
284
  }
268
285
  const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
286
+ const currentIngressTimes = messages.map(session_events_1.getIngressTime);
269
287
  const currentMessages = (0, session_events_1.sanitizeProviderMessages)(messages);
270
- const trimmed = trimMessages(currentMessages, maxTokens, contextMargin, usage?.input_tokens);
271
- messages.splice(0, messages.length, ...trimmed);
288
+ const trimmedMessages = trimMessages(currentMessages, maxTokens, contextMargin, usage?.input_tokens);
289
+ messages.splice(0, messages.length, ...trimmedMessages);
290
+ return { currentMessages, trimmedMessages, currentIngressTimes, maxTokens, contextMargin };
291
+ }
292
+ /**
293
+ * Synchronous persist: load existing envelope, build canonical envelope, write to disk.
294
+ */
295
+ function postTurnPersist(sessPath, prepared, usage, state) {
272
296
  const existing = (0, session_events_1.loadSessionEnvelopeFile)(sessPath);
273
297
  const previousMessages = existing ? (0, session_events_1.projectProviderMessages)(existing) : [];
274
298
  const envelope = (0, session_events_1.buildCanonicalSessionEnvelope)({
275
299
  existing,
276
300
  previousMessages,
277
- currentMessages,
278
- trimmedMessages: trimmed,
301
+ currentMessages: prepared.currentMessages,
302
+ trimmedMessages: prepared.trimmedMessages,
303
+ currentIngressTimes: prepared.currentIngressTimes,
279
304
  recordedAt: new Date().toISOString(),
280
305
  lastUsage: usage ?? null,
281
306
  state,
282
307
  projectionBasis: {
283
- maxTokens,
284
- contextMargin,
308
+ maxTokens: prepared.maxTokens,
309
+ contextMargin: prepared.contextMargin,
285
310
  inputTokens: usage?.input_tokens ?? null,
286
311
  },
287
312
  });
288
313
  writeSessionEnvelope(sessPath, envelope);
314
+ return envelope.events;
315
+ }
316
+ /**
317
+ * Deferred persist: same as postTurnPersist but runs on the next event loop tick.
318
+ * Returns a promise that resolves when the persist completes.
319
+ */
320
+ function deferPostTurnPersist(sessPath, prepared, usage, state) {
321
+ return new Promise((resolve) => {
322
+ setImmediate(() => {
323
+ try {
324
+ const events = postTurnPersist(sessPath, prepared, usage, state);
325
+ resolve(events);
326
+ }
327
+ catch (err) {
328
+ (0, runtime_1.emitNervesEvent)({
329
+ level: "warn",
330
+ component: "mind",
331
+ event: "mind.deferred_persist_error",
332
+ message: "deferred session persist failed",
333
+ meta: { error: err instanceof Error ? err.message : String(err) },
334
+ });
335
+ resolve([]);
336
+ }
337
+ });
338
+ });
289
339
  }
290
340
  function deleteSession(filePath) {
291
341
  try {
@@ -1009,8 +1009,10 @@ async function main(agentName, options) {
1009
1009
  lastActivityAt: sessionState?.lastFriendActivityAt,
1010
1010
  _testInputSource: options?._testInputSource,
1011
1011
  onAsyncAssistantMessage: async (messages, _assistantMessage) => {
1012
- (0, context_1.postTurn)(messages, sessPath, undefined, undefined, sessionState);
1013
- sessionEvents = (0, context_1.loadSession)(sessPath)?.events ?? sessionEvents;
1012
+ const prepared = (0, context_1.postTurnTrim)(messages);
1013
+ const events = (0, context_1.postTurnPersist)(sessPath, prepared, undefined, sessionState);
1014
+ /* v8 ignore next -- defensive: postTurnPersist always returns events in practice @preserve */
1015
+ sessionEvents = events.length > 0 ? events : sessionEvents;
1014
1016
  },
1015
1017
  runTurn: async (messages, userInput, callbacks, signal, toolContext, userContent) => {
1016
1018
  // Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
@@ -1022,9 +1024,10 @@ async function main(agentName, options) {
1022
1024
  /* v8 ignore start -- failover-aware callback wrapper: tested via pipeline integration @preserve */
1023
1025
  const failoverAwareCallbacks = {
1024
1026
  ...callbacks,
1025
- // Save session after each tool result for crash recovery
1027
+ // Save session after each tool result for crash recovery (deferred to avoid blocking)
1026
1028
  onToolResult: (turnMessages) => {
1027
- (0, context_1.postTurn)(turnMessages, sessPath, undefined, undefined, sessionState);
1029
+ const prepared = (0, context_1.postTurnTrim)(turnMessages);
1030
+ (0, context_1.deferPostTurnPersist)(sessPath, prepared, undefined, sessionState);
1028
1031
  },
1029
1032
  onError: (error, severity) => {
1030
1033
  if (severity === "terminal" && failoverState) {
@@ -1069,9 +1072,14 @@ async function main(agentName, options) {
1069
1072
  },
1070
1073
  }),
1071
1074
  postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
1072
- (0, context_1.postTurn)(turnMessages, sessionPathArg, usage, hooks, state);
1075
+ // Trim synchronously (mutates turnMessages for next turn),
1076
+ // then defer envelope build + disk I/O to avoid blocking the TUI.
1077
+ const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
1073
1078
  sessionState = state;
1074
- sessionEvents = (0, context_1.loadSession)(sessionPathArg)?.events ?? sessionEvents;
1079
+ (0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state).then((events) => {
1080
+ /* v8 ignore next -- defensive: deferPostTurnPersist always resolves events in practice @preserve */
1081
+ sessionEvents = events.length > 0 ? events : sessionEvents;
1082
+ });
1075
1083
  },
1076
1084
  accumulateFriendTokens: tokens_1.accumulateFriendTokens,
1077
1085
  signal,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.346",
3
+ "version": "0.1.0-alpha.347",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",