@ouro.bot/cli 0.1.0-alpha.674 → 0.1.0-alpha.675

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 (63) hide show
  1. package/README.md +11 -5
  2. package/changelog.json +13 -0
  3. package/dist/arc/evolution.js +13 -10
  4. package/dist/arc/obligations.js +1 -1
  5. package/dist/heart/active-work.js +3 -3
  6. package/dist/heart/agent-entry.js +8 -8
  7. package/dist/heart/commitments.js +1 -1
  8. package/dist/heart/core.js +45 -23
  9. package/dist/heart/daemon/agent-config-check.js +24 -0
  10. package/dist/heart/daemon/agent-discovery.js +28 -17
  11. package/dist/heart/daemon/agent-service.js +6 -6
  12. package/dist/heart/daemon/await-private-wake.js +59 -0
  13. package/dist/heart/daemon/cli-exec.js +180 -24
  14. package/dist/heart/daemon/cli-help.js +20 -2
  15. package/dist/heart/daemon/cli-parse.js +45 -2
  16. package/dist/heart/daemon/cli-render.js +1 -0
  17. package/dist/heart/daemon/daemon-entry.js +122 -25
  18. package/dist/heart/daemon/daemon.js +223 -20
  19. package/dist/heart/daemon/habit-private-wake.js +56 -0
  20. package/dist/heart/daemon/inner-status.js +3 -3
  21. package/dist/heart/daemon/message-router.js +39 -3
  22. package/dist/heart/daemon/pulse.js +43 -7
  23. package/dist/heart/daemon/socket-client.js +14 -2
  24. package/dist/heart/daemon/startup-tui.js +9 -1
  25. package/dist/heart/daemon/thoughts.js +39 -39
  26. package/dist/heart/habits/habit-scheduler.js +24 -4
  27. package/dist/heart/mailbox/mailbox-view.js +1 -1
  28. package/dist/heart/mailbox/readers/agent-machine.js +7 -7
  29. package/dist/heart/primitives.js +1 -1
  30. package/dist/heart/private-runtime/decision-renderer.js +156 -0
  31. package/dist/heart/private-runtime/index.js +18 -0
  32. package/dist/heart/private-runtime/ledger.js +218 -0
  33. package/dist/heart/private-runtime/policy.js +205 -0
  34. package/dist/heart/private-runtime/types.js +2 -0
  35. package/dist/heart/progress-story.js +1 -1
  36. package/dist/heart/session-transcript.js +1 -1
  37. package/dist/heart/tool-activity-callbacks.js +1 -1
  38. package/dist/heart/tool-description.js +2 -2
  39. package/dist/heart/tool-loop.js +2 -2
  40. package/dist/heart/turn-context.js +5 -5
  41. package/dist/mailbox-ui/assets/index-J8zDwHmE.js +1 -0
  42. package/dist/mailbox-ui/index.html +1 -1
  43. package/dist/mailroom/attention.js +1 -1
  44. package/dist/mind/context.js +2 -2
  45. package/dist/mind/pending.js +12 -5
  46. package/dist/mind/prompt.js +32 -25
  47. package/dist/repertoire/coding/feedback.js +25 -4
  48. package/dist/repertoire/tools-awaiting.js +2 -2
  49. package/dist/repertoire/tools-flow.js +4 -4
  50. package/dist/repertoire/tools-record.js +1 -1
  51. package/dist/repertoire/tools-session.js +33 -67
  52. package/dist/repertoire/tools-surface.js +3 -3
  53. package/dist/senses/cli.js +2 -2
  54. package/dist/senses/inner-dialog-worker.js +13 -519
  55. package/dist/senses/inner-dialog.js +10 -961
  56. package/dist/senses/mail.js +21 -5
  57. package/dist/senses/pipeline.js +1 -1
  58. package/dist/senses/private-runtime-worker.js +578 -0
  59. package/dist/senses/private-runtime.js +1145 -0
  60. package/dist/senses/shared-turn.js +1 -1
  61. package/dist/senses/trust-gate.js +2 -2
  62. package/package.json +1 -1
  63. package/dist/mailbox-ui/assets/index-CaTIFDmv.js +0 -1
@@ -42,20 +42,26 @@ const ouro_version_manager_1 = require("../versioning/ouro-version-manager");
42
42
  function getDaemonMessageRouterDir(homeDir) {
43
43
  return path.join((0, ouro_version_manager_1.getOuroCliHome)(homeDir), "daemon", "messages");
44
44
  }
45
- function messageId(nowIso) {
46
- return `msg-${nowIso.replace(/[^0-9]/g, "")}`;
45
+ function messageId(nowIso, sequence) {
46
+ const base = `msg-${nowIso.replace(/[^0-9]/g, "")}`;
47
+ return sequence === 1 ? base : `${base}-${sequence}`;
47
48
  }
48
49
  class FileMessageRouter {
49
50
  baseDir;
50
51
  now;
52
+ maxMessagesPerInbox;
53
+ messageIdSequences = new Map();
51
54
  constructor(options = {}) {
52
55
  this.baseDir = options.baseDir ?? getDaemonMessageRouterDir(options.homeDir);
53
56
  this.now = options.now ?? (() => new Date().toISOString());
57
+ this.maxMessagesPerInbox = Math.max(1, Math.floor(options.maxMessagesPerInbox ?? 1000));
54
58
  fs.mkdirSync(this.baseDir, { recursive: true });
55
59
  }
56
60
  async send(input) {
57
61
  const queuedAt = this.now();
58
- const id = messageId(queuedAt);
62
+ const sequence = (this.messageIdSequences.get(queuedAt) ?? 0) + 1;
63
+ this.messageIdSequences.set(queuedAt, sequence);
64
+ const id = messageId(queuedAt, sequence);
59
65
  const message = {
60
66
  id,
61
67
  from: input.from,
@@ -68,6 +74,7 @@ class FileMessageRouter {
68
74
  };
69
75
  const inboxPath = this.inboxPath(input.to);
70
76
  fs.appendFileSync(inboxPath, `${JSON.stringify(message)}\n`, "utf-8");
77
+ this.trimInbox(inboxPath);
71
78
  (0, runtime_1.emitNervesEvent)({
72
79
  component: "daemon",
73
80
  event: "daemon.message_queued",
@@ -107,5 +114,34 @@ class FileMessageRouter {
107
114
  inboxPath(agent) {
108
115
  return path.join(this.baseDir, `${agent}-inbox.jsonl`);
109
116
  }
117
+ trimInbox(inboxPath) {
118
+ const raw = fs.readFileSync(inboxPath, "utf-8");
119
+ const entries = raw
120
+ .split("\n")
121
+ .filter((line) => line.trim().length > 0)
122
+ .map((line) => {
123
+ try {
124
+ JSON.parse(line);
125
+ return { line, valid: true };
126
+ }
127
+ catch {
128
+ return { line, valid: false };
129
+ }
130
+ });
131
+ const validCount = entries.filter((entry) => entry.valid).length;
132
+ if (validCount <= this.maxMessagesPerInbox)
133
+ return;
134
+ let validSeen = 0;
135
+ const keepAfterValidIndex = validCount - this.maxMessagesPerInbox;
136
+ const kept = entries
137
+ .filter((entry) => {
138
+ if (!entry.valid)
139
+ return true;
140
+ validSeen += 1;
141
+ return validSeen > keepAfterValidIndex;
142
+ })
143
+ .map((entry) => entry.line);
144
+ fs.writeFileSync(inboxPath, `${kept.join("\n")}\n`, "utf-8");
145
+ }
110
146
  }
111
147
  exports.FileMessageRouter = FileMessageRouter;
@@ -41,6 +41,7 @@ exports.buildPulseState = buildPulseState;
41
41
  exports.findNovelBrokenAgents = findNovelBrokenAgents;
42
42
  exports.findRecoveredAgents = findRecoveredAgents;
43
43
  exports.buildRecoveryAlertId = buildRecoveryAlertId;
44
+ exports.buildPulsePrivateWakeRequest = buildPulsePrivateWakeRequest;
44
45
  exports.pickWakeRecipient = pickWakeRecipient;
45
46
  exports.writePulse = writePulse;
46
47
  exports.readPulse = readPulse;
@@ -88,7 +89,7 @@ function buildAlertId(agent, errorReason) {
88
89
  return `${agent}:${hashErrorReason(errorReason)}`;
89
90
  }
90
91
  /**
91
- * Read an agent's inner-dialog runtime state and format it as a short
92
+ * Read an agent's private-runtime state and format it as a short
92
93
  * activity hint string suitable for display in another agent's pulse
93
94
  * section. Returns null when the file is missing or malformed — both
94
95
  * cases are silent (the pulse just doesn't include activity for that
@@ -164,8 +165,8 @@ function buildPulseState(snapshots, bundlesRoot, daemonVersion, now, readActivit
164
165
  /**
165
166
  * A "novel broken transition" is an agent that is currently broken AND
166
167
  * either (a) wasn't broken in the previous pulse state, OR (b) was broken
167
- * but with a different alertId. Used to decide when to fire an inner.wake
168
- * for proactive notification.
168
+ * but with a different alertId. Used to decide when to queue a proactive
169
+ * pulse wake.
169
170
  *
170
171
  * Pure: takes prev and next states, returns the list of newly-broken
171
172
  * agents that warrant a wake.
@@ -218,8 +219,24 @@ function findRecoveredAgents(prev, next) {
218
219
  function buildRecoveryAlertId(agent, recoveredAt) {
219
220
  return `recovery:${agent}:${recoveredAt}`;
220
221
  }
222
+ function buildPulsePrivateWakeRequest(options) {
223
+ const prefix = options.triggerSource === "pulse-alert" ? "pulse alert" : "pulse recovery";
224
+ const detail = options.subject.errorReason ? `: ${options.subject.errorReason}` : "";
225
+ const originKind = options.triggerSource === "pulse-alert" ? "pulse-alert" : "pulse-recovery";
226
+ return {
227
+ agent: options.recipient,
228
+ reason: `${prefix} for ${options.subject.name}${detail}`,
229
+ triggerSource: options.triggerSource,
230
+ budgetClass: "scheduled",
231
+ idempotencyKey: `pulse:${options.recipient}:${options.alertId}`,
232
+ originRefs: [
233
+ { kind: originKind, id: options.alertId },
234
+ { kind: "agent", id: options.subject.name },
235
+ ],
236
+ };
237
+ }
221
238
  /**
222
- * Pick which agent should receive an inner.wake for a fleet alert. Heuristic:
239
+ * Pick which agent should receive a pulse wake for a fleet alert. Heuristic:
223
240
  * the most-recently-active running agent (by `lastSeenAt`), excluding the
224
241
  * broken agent itself and any agents that aren't currently running. Returns
225
242
  * null if there's no eligible recipient (e.g., the only other agent on the
@@ -394,7 +411,7 @@ function pruneDeliveredState(delivered, state) {
394
411
  /**
395
412
  * Single entry point the daemon's onSnapshotChange callback uses. Builds
396
413
  * the new pulse state, diffs against the previous, writes the file, and
397
- * fires inner.wake on novel broken transitions to the most-recently-active
414
+ * queues pulse wakes on novel broken transitions for the most-recently-active
398
415
  * sibling. Persistent at-most-once delivery via the delivered state file.
399
416
  *
400
417
  * Pure-ish: all I/O goes through dep callbacks so tests can pin every
@@ -409,6 +426,7 @@ function flushPulse(deps) {
409
426
  const readDelivered = deps.readDelivered ?? (() => readDeliveredState());
410
427
  const writeDelivered = deps.writeDelivered ?? ((d) => writeDeliveredState(d));
411
428
  const fireInnerWake = deps.fireInnerWake ?? null;
429
+ const firePrivateWake = deps.firePrivateWake ?? null;
412
430
  /* v8 ignore stop */
413
431
  const prev = readPrev();
414
432
  let delivered = readDelivered();
@@ -437,7 +455,16 @@ function flushPulse(deps) {
437
455
  if (broken.alertId === null)
438
456
  continue;
439
457
  const recipient = pickWakeRecipient(state, broken.name);
440
- if (recipient !== null && fireInnerWake !== null) {
458
+ if (recipient !== null && firePrivateWake !== null) {
459
+ firePrivateWake(buildPulsePrivateWakeRequest({
460
+ recipient,
461
+ subject: broken,
462
+ alertId: broken.alertId,
463
+ triggerSource: "pulse-alert",
464
+ }));
465
+ wakeFiredFor.push(recipient);
466
+ }
467
+ else if (recipient !== null && fireInnerWake !== null) {
441
468
  fireInnerWake(recipient);
442
469
  wakeFiredFor.push(recipient);
443
470
  }
@@ -453,7 +480,16 @@ function flushPulse(deps) {
453
480
  const recoveredAgent = undeliveredRecovered[i];
454
481
  const recoveryAlertId = buildRecoveryAlertId(recoveredAgent.name, state.generatedAt);
455
482
  const recipient = pickWakeRecipient(state, recoveredAgent.name);
456
- if (recipient !== null && fireInnerWake !== null) {
483
+ if (recipient !== null && firePrivateWake !== null) {
484
+ firePrivateWake(buildPulsePrivateWakeRequest({
485
+ recipient,
486
+ subject: recoveredAgent,
487
+ alertId: recoveryAlertId,
488
+ triggerSource: "pulse-recovery",
489
+ }));
490
+ wakeFiredFor.push(recipient);
491
+ }
492
+ else if (recipient !== null && fireInnerWake !== null) {
457
493
  fireInnerWake(recipient);
458
494
  wakeFiredFor.push(recipient);
459
495
  }
@@ -38,6 +38,7 @@ exports.__bypassVitestGuardForTests = __bypassVitestGuardForTests;
38
38
  exports.sendDaemonCommand = sendDaemonCommand;
39
39
  exports.checkDaemonSocketAlive = checkDaemonSocketAlive;
40
40
  exports.requestInnerWake = requestInnerWake;
41
+ exports.requestPrivateWake = requestPrivateWake;
41
42
  const fs = __importStar(require("fs"));
42
43
  const net = __importStar(require("net"));
43
44
  const runtime_1 = require("../../nerves/runtime");
@@ -326,7 +327,7 @@ async function requestInnerWake(agent, socketPath = exports.DEFAULT_DAEMON_SOCKE
326
327
  level: "warn",
327
328
  component: "daemon",
328
329
  event: "daemon.inner_wake_test_blocked",
329
- message: "blocked inner wake from leaking into real daemon under vitest",
330
+ message: "blocked legacy private-runtime wake alias from leaking into real daemon under vitest",
330
331
  meta: { agent, socketPath, isProductionSocket: isProductionDaemonSocket(socketPath) },
331
332
  });
332
333
  return null;
@@ -335,7 +336,7 @@ async function requestInnerWake(agent, socketPath = exports.DEFAULT_DAEMON_SOCKE
335
336
  (0, runtime_1.emitNervesEvent)({
336
337
  component: "daemon",
337
338
  event: "daemon.inner_wake_request",
338
- message: "requesting daemon-managed inner wake",
339
+ message: "requesting daemon-managed legacy private-runtime wake alias",
339
340
  meta: {
340
341
  agent,
341
342
  socketPath,
@@ -347,3 +348,14 @@ async function requestInnerWake(agent, socketPath = exports.DEFAULT_DAEMON_SOCKE
347
348
  }
348
349
  return sendDaemonCommand(socketPath, { kind: "inner.wake", agent });
349
350
  }
351
+ async function requestPrivateWake(agent, socketPath = exports.DEFAULT_DAEMON_SOCKET_PATH, options = {}) {
352
+ if (shouldSuppressSocketCall(socketPath))
353
+ return null;
354
+ if (!fs.existsSync(socketPath))
355
+ return null;
356
+ return sendDaemonCommand(socketPath, {
357
+ kind: "private.wake",
358
+ agent,
359
+ ...options,
360
+ });
361
+ }
@@ -42,7 +42,10 @@ function assessStability(payload, now) {
42
42
  const degraded = [];
43
43
  let allResolved = true;
44
44
  for (const worker of payload.workers) {
45
- if (worker.status === "crashed") {
45
+ if (isPassiveStoppedWorker(worker)) {
46
+ continue;
47
+ }
48
+ else if (worker.status === "crashed") {
46
49
  degraded.push({
47
50
  agent: worker.agent,
48
51
  errorReason: worker.errorReason ?? "unknown error",
@@ -281,6 +284,8 @@ function buildTimedOutStartupResult(payload, assessment, maxWaitMs) {
281
284
  degradedAgents.add(worker.agent);
282
285
  const seconds = (maxWaitMs / 1000).toFixed(0);
283
286
  for (const worker of payload.workers) {
287
+ if (isPassiveStoppedWorker(worker))
288
+ continue;
284
289
  if (stableAgents.has(worker.agent) || degradedAgents.has(worker.agent))
285
290
  continue;
286
291
  degraded.push({
@@ -293,6 +298,9 @@ function buildTimedOutStartupResult(payload, assessment, maxWaitMs) {
293
298
  }
294
299
  return { stable, degraded };
295
300
  }
301
+ function isPassiveStoppedWorker(worker) {
302
+ return worker.autoStart === false && worker.status === "stopped";
303
+ }
296
304
  function formatStartupWorkerLine(payload) {
297
305
  const base = `- ${payload.agent}/${payload.worker}: ${payload.status}`;
298
306
  if (payload.status === "crashed" && payload.errorReason) {
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // Formats inner dialog session turns for human consumption.
2
+ // Formats private-runtime session turns for human consumption.
3
3
  // Used by `ouro thoughts` CLI command to show what the agent has been thinking.
4
4
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
5
  if (k2 === undefined) k2 = k;
@@ -36,15 +36,15 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  })();
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
38
  exports.formatSurfacedValue = formatSurfacedValue;
39
- exports.deriveInnerDialogStatus = deriveInnerDialogStatus;
39
+ exports.derivePrivateRuntimeStatus = derivePrivateRuntimeStatus;
40
40
  exports.deriveInnerJob = deriveInnerJob;
41
- exports.formatInnerDialogStatus = formatInnerDialogStatus;
41
+ exports.formatPrivateRuntimeStatus = formatPrivateRuntimeStatus;
42
42
  exports.extractThoughtResponseFromMessages = extractThoughtResponseFromMessages;
43
- exports.parseInnerDialogSession = parseInnerDialogSession;
43
+ exports.parsePrivateRuntimeSession = parsePrivateRuntimeSession;
44
44
  exports.formatThoughtTurns = formatThoughtTurns;
45
- exports.getInnerDialogSessionPath = getInnerDialogSessionPath;
46
- exports.readInnerDialogStatus = readInnerDialogStatus;
47
- exports.readInnerDialogRawData = readInnerDialogRawData;
45
+ exports.getPrivateRuntimeSessionPath = getPrivateRuntimeSessionPath;
46
+ exports.readPrivateRuntimeStatus = readPrivateRuntimeStatus;
47
+ exports.readPrivateRuntimeRawData = readPrivateRuntimeRawData;
48
48
  exports.followThoughts = followThoughts;
49
49
  const fs = __importStar(require("fs"));
50
50
  const path = __importStar(require("path"));
@@ -167,11 +167,11 @@ function extractEnrichedFields(pendingMessages) {
167
167
  ...(delegated.obligationStatus === "pending" ? { obligationPending: true } : {}),
168
168
  };
169
169
  }
170
- function deriveInnerDialogStatus(pendingMessages, turns, runtimeState) {
170
+ function derivePrivateRuntimeStatus(pendingMessages, turns, runtimeState) {
171
171
  if (runtimeState?.status === "running") {
172
172
  if (pendingMessages.length > 0) {
173
173
  return {
174
- queue: "queued to inner/dialog",
174
+ queue: "queued to private runtime",
175
175
  wake: "queued behind active turn",
176
176
  processing: "pending",
177
177
  surfaced: "nothing yet",
@@ -187,8 +187,8 @@ function deriveInnerDialogStatus(pendingMessages, turns, runtimeState) {
187
187
  }
188
188
  if (pendingMessages.length > 0) {
189
189
  return {
190
- queue: "queued to inner/dialog",
191
- wake: "awaiting inner session",
190
+ queue: "queued to private runtime",
191
+ wake: "awaiting private-runtime turn",
192
192
  processing: "pending",
193
193
  surfaced: "nothing yet",
194
194
  ...extractEnrichedFields(pendingMessages),
@@ -224,8 +224,8 @@ function deriveInnerJob(pendingMessages, turns, runtimeState) {
224
224
  if (isRunning) {
225
225
  (0, runtime_1.emitNervesEvent)({
226
226
  component: "engine",
227
- event: "engine.inner_job_derive",
228
- message: "derived inner job state",
227
+ event: "engine.private_runtime_job_derive",
228
+ message: "derived private-runtime job state",
229
229
  meta: { status: "running", mode: pendingMode, hasOrigin: origin !== null, hasObligation: obligationStatus !== null },
230
230
  });
231
231
  return {
@@ -243,8 +243,8 @@ function deriveInnerJob(pendingMessages, turns, runtimeState) {
243
243
  if (pendingMessages.length > 0) {
244
244
  (0, runtime_1.emitNervesEvent)({
245
245
  component: "engine",
246
- event: "engine.inner_job_derive",
247
- message: "derived inner job state",
246
+ event: "engine.private_runtime_job_derive",
247
+ message: "derived private-runtime job state",
248
248
  meta: { status: "queued", mode: pendingMode, hasOrigin: origin !== null, hasObligation: obligationStatus !== null },
249
249
  });
250
250
  return {
@@ -269,8 +269,8 @@ function deriveInnerJob(pendingMessages, turns, runtimeState) {
269
269
  ]);
270
270
  (0, runtime_1.emitNervesEvent)({
271
271
  component: "engine",
272
- event: "engine.inner_job_derive",
273
- message: "derived inner job state",
272
+ event: "engine.private_runtime_job_derive",
273
+ message: "derived private-runtime job state",
274
274
  meta: { status: "surfaced", mode: "reflect", hasOrigin: false, hasObligation: false },
275
275
  });
276
276
  return {
@@ -288,8 +288,8 @@ function deriveInnerJob(pendingMessages, turns, runtimeState) {
288
288
  }
289
289
  (0, runtime_1.emitNervesEvent)({
290
290
  component: "engine",
291
- event: "engine.inner_job_derive",
292
- message: "derived inner job state",
291
+ event: "engine.private_runtime_job_derive",
292
+ message: "derived private-runtime job state",
293
293
  meta: { status: "idle", mode: "reflect", hasOrigin: false, hasObligation: false },
294
294
  });
295
295
  return {
@@ -304,7 +304,7 @@ function deriveInnerJob(pendingMessages, turns, runtimeState) {
304
304
  surfacedAt: null,
305
305
  };
306
306
  }
307
- function formatInnerDialogStatus(status) {
307
+ function formatPrivateRuntimeStatus(status) {
308
308
  const lines = [
309
309
  `queue: ${status.queue}`,
310
310
  `wake: ${status.wake}`,
@@ -418,11 +418,11 @@ function extractThoughtResponseFromMessages(messages) {
418
418
  ? contentToText(lastAssistant.content).trim()
419
419
  : extractSettleAnswer(messages) || extractToolActionSummary(messages);
420
420
  }
421
- function parseInnerDialogSession(sessionPath) {
421
+ function parsePrivateRuntimeSession(sessionPath) {
422
422
  (0, runtime_1.emitNervesEvent)({
423
423
  component: "daemon",
424
424
  event: "daemon.thoughts_parse",
425
- message: "parsing inner dialog session",
425
+ message: "parsing private-runtime session",
426
426
  meta: { sessionPath },
427
427
  });
428
428
  let raw;
@@ -494,11 +494,11 @@ function parseInnerDialogSession(sessionPath) {
494
494
  }
495
495
  function formatThoughtTurns(turns, lastN) {
496
496
  if (turns.length === 0)
497
- return "no inner dialog activity";
497
+ return "no private-runtime activity";
498
498
  const selected = lastN > 0 ? turns.slice(-lastN) : turns;
499
499
  /* v8 ignore next -- unreachable: turns.length > 0 checked above, slice always returns ≥1 @preserve */
500
500
  if (selected.length === 0)
501
- return "no inner dialog activity";
501
+ return "no private-runtime activity";
502
502
  const lines = [];
503
503
  for (const turn of selected) {
504
504
  const typeLabel = turn.type === "task" && turn.taskId
@@ -518,13 +518,13 @@ function formatThoughtTurns(turns, lastN) {
518
518
  }
519
519
  return lines.join("\n").trim();
520
520
  }
521
- function getInnerDialogSessionPath(agentRoot) {
521
+ function getPrivateRuntimeSessionPath(agentRoot) {
522
522
  return path.join(agentRoot, "state", "sessions", "self", "inner", "dialog.json");
523
523
  }
524
- function getInnerDialogRuntimeStatePath(sessionPath) {
524
+ function getPrivateRuntimeStatePath(sessionPath) {
525
525
  return path.join(path.dirname(sessionPath), "runtime.json");
526
526
  }
527
- function readInnerDialogRuntimeState(runtimePath) {
527
+ function readPrivateRuntimeState(runtimePath) {
528
528
  try {
529
529
  const raw = fs.readFileSync(runtimePath, "utf-8");
530
530
  const parsed = JSON.parse(raw);
@@ -543,17 +543,17 @@ function readInnerDialogRuntimeState(runtimePath) {
543
543
  return null;
544
544
  }
545
545
  }
546
- function readInnerDialogStatus(sessionPath, pendingDir, runtimePath = getInnerDialogRuntimeStatePath(sessionPath)) {
546
+ function readPrivateRuntimeStatus(sessionPath, pendingDir, runtimePath = getPrivateRuntimeStatePath(sessionPath)) {
547
547
  const pendingMessages = readPendingMessagesForStatus(pendingDir);
548
- const turns = parseInnerDialogSession(sessionPath);
549
- const runtimeState = readInnerDialogRuntimeState(runtimePath);
550
- return deriveInnerDialogStatus(pendingMessages, turns, runtimeState);
548
+ const turns = parsePrivateRuntimeSession(sessionPath);
549
+ const runtimeState = readPrivateRuntimeState(runtimePath);
550
+ return derivePrivateRuntimeStatus(pendingMessages, turns, runtimeState);
551
551
  }
552
- function readInnerDialogRawData(sessionPath, pendingDir) {
553
- const runtimePath = getInnerDialogRuntimeStatePath(sessionPath);
552
+ function readPrivateRuntimeRawData(sessionPath, pendingDir) {
553
+ const runtimePath = getPrivateRuntimeStatePath(sessionPath);
554
554
  const pendingMessages = readPendingMessagesForStatus(pendingDir);
555
- const turns = parseInnerDialogSession(sessionPath);
556
- const runtimeState = readInnerDialogRuntimeState(runtimePath);
555
+ const turns = parsePrivateRuntimeSession(sessionPath);
556
+ const runtimeState = readPrivateRuntimeState(runtimePath);
557
557
  return { pendingMessages, turns, runtimeState };
558
558
  }
559
559
  /**
@@ -561,15 +561,15 @@ function readInnerDialogRawData(sessionPath, pendingDir) {
561
561
  * Returns a cleanup function that stops the watcher.
562
562
  */
563
563
  function followThoughts(sessionPath, onNewTurns, pollIntervalMs = 1000) {
564
- let displayedCount = parseInnerDialogSession(sessionPath).length;
564
+ let displayedCount = parsePrivateRuntimeSession(sessionPath).length;
565
565
  (0, runtime_1.emitNervesEvent)({
566
566
  component: "daemon",
567
567
  event: "daemon.thoughts_follow_start",
568
- message: "started following inner dialog session",
568
+ message: "started following private-runtime session",
569
569
  meta: { sessionPath, initialTurns: displayedCount },
570
570
  });
571
571
  fs.watchFile(sessionPath, { interval: pollIntervalMs }, () => {
572
- const turns = parseInnerDialogSession(sessionPath);
572
+ const turns = parsePrivateRuntimeSession(sessionPath);
573
573
  if (turns.length > displayedCount) {
574
574
  const newTurns = turns.slice(displayedCount);
575
575
  onNewTurns(formatThoughtTurns(newTurns, 0));
@@ -584,7 +584,7 @@ function followThoughts(sessionPath, onNewTurns, pollIntervalMs = 1000) {
584
584
  (0, runtime_1.emitNervesEvent)({
585
585
  component: "daemon",
586
586
  event: "daemon.thoughts_follow_end",
587
- message: "stopped following inner dialog session",
587
+ message: "stopped following private-runtime session",
588
588
  meta: { sessionPath, totalTurns: displayedCount },
589
589
  });
590
590
  };
@@ -108,7 +108,9 @@ class HabitScheduler {
108
108
  message: "firing overdue habit (never run)",
109
109
  meta: { habitName: habit.name, agent: this.agent },
110
110
  });
111
- this.onHabitFire(habit.name, "overdue");
111
+ this.onHabitFire(habit.name, "overdue", {
112
+ occurrenceId: this.overdueOccurrenceId(habit),
113
+ });
112
114
  continue;
113
115
  }
114
116
  const lastRunMs = new Date(habit.lastRun).getTime();
@@ -120,7 +122,9 @@ class HabitScheduler {
120
122
  message: "firing overdue habit",
121
123
  meta: { habitName: habit.name, agent: this.agent, elapsedMs: elapsed },
122
124
  });
123
- this.onHabitFire(habit.name, "overdue");
125
+ this.onHabitFire(habit.name, "overdue", {
126
+ occurrenceId: this.overdueOccurrenceId(habit),
127
+ });
124
128
  }
125
129
  }
126
130
  }
@@ -184,7 +188,9 @@ class HabitScheduler {
184
188
  if (!job) {
185
189
  return { ok: false, message: `unknown habit job: ${jobId}` };
186
190
  }
187
- this.onHabitFire(job.taskId, trigger);
191
+ this.onHabitFire(job.taskId, trigger, {
192
+ occurrenceId: this.jobOccurrenceId(job, trigger),
193
+ });
188
194
  (0, runtime_1.emitNervesEvent)({
189
195
  component: "daemon",
190
196
  event: "daemon.habit_job_triggered",
@@ -312,7 +318,9 @@ class HabitScheduler {
312
318
  createTimerFallback(habitName, cadenceMs) {
313
319
  const schedule = () => {
314
320
  const timer = setTimeout(() => {
315
- this.onHabitFire(habitName, "overdue");
321
+ this.onHabitFire(habitName, "overdue", {
322
+ occurrenceId: this.timerOccurrenceId(habitName, cadenceMs),
323
+ });
316
324
  schedule();
317
325
  }, cadenceMs);
318
326
  this.timerFallbacks.set(habitName, timer);
@@ -387,5 +395,17 @@ class HabitScheduler {
387
395
  }
388
396
  return jobs;
389
397
  }
398
+ overdueOccurrenceId(habit) {
399
+ if (habit.lastRun === null)
400
+ return `overdue:first-run:${habit.cadence}`;
401
+ return `overdue:last-run:${habit.lastRun}:cadence:${habit.cadence}`;
402
+ }
403
+ jobOccurrenceId(job, trigger) {
404
+ return `job:${job.id}:${trigger}:last-run:${job.lastRun ?? "never"}`;
405
+ }
406
+ timerOccurrenceId(habitName, cadenceMs) {
407
+ const slot = Math.floor(this.deps.now() / cadenceMs);
408
+ return `timer:${habitName}:cadence-ms:${cadenceMs}:slot:${slot}`;
409
+ }
390
410
  }
391
411
  exports.HabitScheduler = HabitScheduler;
@@ -154,7 +154,7 @@ function buildRecentActivity(agent) {
154
154
  kind: "inner",
155
155
  at: agent.inner.latestActivityAt,
156
156
  label: agent.inner.surfacedSummary ?? agent.inner.status,
157
- detail: agent.inner.hasPending ? "pending inner work" : agent.inner.obligationStatus ?? "no linked obligation",
157
+ detail: agent.inner.hasPending ? "pending private-runtime work" : agent.inner.obligationStatus ?? "no linked obligation",
158
158
  });
159
159
  }
160
160
  return items
@@ -177,9 +177,9 @@ function readSessionSummary(agentName, agentRoot, now) {
177
177
  return { items };
178
178
  }
179
179
  function readInnerSummary(agentRoot) {
180
- const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
180
+ const sessionPath = (0, thoughts_1.getPrivateRuntimeSessionPath)(agentRoot);
181
181
  const pendingDir = path.join(agentRoot, "state", "pending", "self", "inner", "dialog");
182
- const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
182
+ const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readPrivateRuntimeRawData)(sessionPath, pendingDir);
183
183
  const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
184
184
  const surfacedSummary = job.surfacedResult ? (0, thoughts_1.formatSurfacedValue)(job.surfacedResult) : null;
185
185
  const latestPendingTimestamp = pendingMessages.length > 0
@@ -189,11 +189,11 @@ function readInnerSummary(agentRoot) {
189
189
  ?? runtimeState?.startedAt
190
190
  ?? runtimeState?.lastCompletedAt
191
191
  ?? null;
192
- // Read the return-obligation queue so the Inner tab can show what the
193
- // agent is actually holding right now. Before this, the "Inner work"
194
- // panel only consulted the pending-messages dir (inbox-style); it
195
- // reported "No pending inner work" even when dozens of held items were
196
- // sitting in arc/obligations/inner/ waiting to be reinjected next turn.
192
+ // Read the return-obligation queue so the Private Runtime tab can show what the
193
+ // agent is actually holding right now. Before this, the private-runtime work
194
+ // panel only consulted the pending-messages dir (inbox-style), so it
195
+ // looked empty even when dozens of held items were sitting in
196
+ // arc/obligations/inner/ waiting to be reinjected next turn.
197
197
  const returnObligationQueue = readReturnObligationQueueSummary(agentRoot);
198
198
  return {
199
199
  summary: {
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HARNESS_BOOTSTRAP_PHASES = void 0;
4
- exports.HARNESS_BOOTSTRAP_PHASES = ["bundle", "psyche", "inner-dialog"];
4
+ exports.HARNESS_BOOTSTRAP_PHASES = ["bundle", "psyche", "private-runtime"];