@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
@@ -60,9 +60,12 @@ const daemon_tombstone_1 = require("./daemon-tombstone");
60
60
  const agent_config_check_1 = require("./agent-config-check");
61
61
  const pulse_1 = require("./pulse");
62
62
  const socket_client_1 = require("./socket-client");
63
+ const await_private_wake_1 = require("./await-private-wake");
64
+ const habit_private_wake_1 = require("./habit-private-wake");
63
65
  const bundle_manifest_1 = require("../../mind/bundle-manifest");
64
66
  const mcp_canary_1 = require("./mcp-canary");
65
67
  const context_loss_sentinel_1 = require("../context-loss-sentinel");
68
+ const provider_credentials_1 = require("../provider-credentials");
66
69
  function parseSocketPath(argv) {
67
70
  const socketIndex = argv.indexOf("--socket");
68
71
  if (socketIndex >= 0) {
@@ -93,6 +96,10 @@ if (mode === "dev") {
93
96
  });
94
97
  }
95
98
  const managedAgents = (0, agent_discovery_1.listEnabledBundleAgents)();
99
+ const managedPrivateRuntimes = managedAgents.map((agent) => ({
100
+ agent,
101
+ config: (0, agent_discovery_1.readPrivateRuntimeConfig)(agent),
102
+ }));
96
103
  function sentinelHealthStatus(receipt) {
97
104
  if (receipt.verdict === "ready")
98
105
  return "ok";
@@ -118,26 +125,65 @@ async function refreshDaemonSentinel(agent, trigger, daemonHealthResults = []) {
118
125
  };
119
126
  }
120
127
  }
128
+ function emitAwaitPrivateWakeDispatchError(options) {
129
+ (0, runtime_1.emitNervesEvent)({
130
+ level: "error",
131
+ component: "daemon",
132
+ event: "daemon.await_private_wake_dispatch_error",
133
+ message: "failed to dispatch await private-runtime wake",
134
+ meta: {
135
+ agent: options.agent,
136
+ awaitName: options.awaitName,
137
+ triggerSource: options.triggerSource,
138
+ socketPath: options.socketPath,
139
+ error: options.error instanceof Error ? options.error.message : String(options.error),
140
+ },
141
+ });
142
+ }
143
+ function emitPulsePrivateWakeDispatchError(options) {
144
+ (0, runtime_1.emitNervesEvent)({
145
+ level: "error",
146
+ component: "daemon",
147
+ event: "daemon.pulse_private_wake_dispatch_error",
148
+ message: "failed to dispatch pulse private-runtime wake",
149
+ meta: {
150
+ agent: options.request.agent,
151
+ triggerSource: options.request.triggerSource,
152
+ idempotencyKey: options.request.idempotencyKey,
153
+ originRefs: options.request.originRefs,
154
+ socketPath: options.socketPath,
155
+ error: options.error instanceof Error ? options.error.message : String(options.error),
156
+ },
157
+ });
158
+ }
159
+ function emitHabitPrivateWakeDispatchError(options) {
160
+ (0, runtime_1.emitNervesEvent)({
161
+ level: "error",
162
+ component: "daemon",
163
+ event: "daemon.habit_private_wake_dispatch_error",
164
+ message: "failed to dispatch habit private-runtime wake",
165
+ meta: {
166
+ agent: options.agent,
167
+ habitName: options.habitName,
168
+ trigger: options.trigger,
169
+ triggerSource: options.triggerSource,
170
+ socketPath: options.socketPath,
171
+ error: options.error instanceof Error ? options.error.message : String(options.error),
172
+ },
173
+ });
174
+ }
121
175
  const processManager = new process_manager_1.DaemonProcessManager({
122
- agents: managedAgents.map((agent) => ({
176
+ agents: managedPrivateRuntimes.map(({ agent, config }) => ({
123
177
  name: agent,
124
178
  entry: "heart/agent-entry.js",
125
- channel: "inner-dialog",
126
- autoStart: (0, agent_discovery_1.isInnerDialogAutoStartEnabled)(agent),
179
+ channel: "private-runtime",
180
+ autoStart: config.autoStart,
127
181
  })),
128
182
  existsSync: fs.existsSync,
129
- /* v8 ignore next 4 -- wiring: delegates to checkAgentConfigWithProviderHealth which has full unit tests @preserve */
183
+ /* v8 ignore next 4 -- wiring: delegates to checkAgentConfig which has full unit tests @preserve */
130
184
  configCheck: async (agent) => {
131
- if (!(0, agent_discovery_1.isInnerDialogAutoStartEnabled)(agent)) {
132
- return {
133
- ok: true,
134
- skip: true,
135
- error: "inner-dialog auto-start is disabled in agent.json",
136
- fix: "Set innerDialog.autoStart to true and rerun `ouro up` to resume autonomous inner-dialog turns.",
137
- };
138
- }
139
185
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
140
- return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot);
186
+ return (0, agent_config_check_1.checkAgentConfig)(agent, bundlesRoot);
141
187
  },
142
188
  /* v8 ignore start -- pulse flush wiring: integration code; flushPulse itself has full unit tests @preserve */
143
189
  onSnapshotChange: () => {
@@ -146,12 +192,20 @@ const processManager = new process_manager_1.DaemonProcessManager({
146
192
  bundlesRoot: (0, identity_1.getAgentBundlesRoot)(),
147
193
  daemonVersion: (0, bundle_manifest_1.getPackageVersion)(),
148
194
  now: new Date(),
149
- // Default I/O wired into pulse.ts (writePulse, readPulse, etc.)
150
- // Wake recipient: send inner.wake over the daemon's own socket so
151
- // the recipient agent runs an inner-dialog turn that picks up the
152
- // pulse alert. Catch errors silently — pulse is best-effort.
153
- fireInnerWake: (agent) => {
154
- (0, socket_client_1.sendDaemonCommand)(socketPath, { kind: "inner.wake", agent }).catch(() => { });
195
+ // Default I/O wired into pulse.ts (writePulse, readPulse, etc.).
196
+ // Pulse alerts queue canonical private-runtime wakes so policy decides
197
+ // whether a model-backed notification turn may run.
198
+ firePrivateWake: (request) => {
199
+ (0, socket_client_1.sendDaemonCommand)(socketPath, {
200
+ kind: "private.wake",
201
+ ...request,
202
+ }).catch((error) => {
203
+ emitPulsePrivateWakeDispatchError({
204
+ request,
205
+ socketPath,
206
+ error,
207
+ });
208
+ });
155
209
  },
156
210
  });
157
211
  },
@@ -404,8 +458,11 @@ function writeStopCommandHealthState() {
404
458
  // Health writes are best-effort during shutdown.
405
459
  }
406
460
  }
461
+ async function preloadProviderCredentialPools() {
462
+ await Promise.all(managedAgents.map((agent) => (0, provider_credentials_1.refreshProviderCredentialPool)(agent, { preserveCachedOnFailure: true })));
463
+ }
407
464
  /* v8 ignore start -- habit wiring: lambdas delegate to processManager/fs; tested via HabitScheduler unit tests @preserve */
408
- void daemon.start().then(async () => {
465
+ void preloadProviderCredentialPools().then(() => daemon.start()).then(async () => {
409
466
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
410
467
  const ouroPath = (0, os_cron_deps_1.resolveOuroBinaryPath)();
411
468
  const osCronDeps = (0, os_cron_deps_1.createRealOsCronDeps)();
@@ -422,8 +479,24 @@ void daemon.start().then(async () => {
422
479
  agent,
423
480
  habitsDir,
424
481
  osCronManager,
425
- onHabitFire: (habitName, trigger) => {
426
- processManager.sendToAgent(agent, { type: "habit", habitName, trigger });
482
+ onHabitFire: (habitName, trigger, context) => {
483
+ const command = (0, habit_private_wake_1.buildHabitPrivateWakeCommand)({
484
+ agent,
485
+ habitName,
486
+ trigger,
487
+ sourceRef: { kind: "daemon-entry", id: "habit-scheduler" },
488
+ occurrenceId: context?.occurrenceId,
489
+ });
490
+ (0, socket_client_1.sendDaemonCommand)(socketPath, command).catch((error) => {
491
+ emitHabitPrivateWakeDispatchError({
492
+ agent,
493
+ habitName,
494
+ trigger,
495
+ triggerSource: command.triggerSource,
496
+ socketPath,
497
+ error,
498
+ });
499
+ });
427
500
  },
428
501
  deps: {
429
502
  readdir: (dir) => fs.readdirSync(dir),
@@ -482,7 +555,19 @@ void daemon.start().then(async () => {
482
555
  awaitsDir,
483
556
  osCronManager: awaitOsCronManager,
484
557
  onAwaitFire: (awaitName) => {
485
- processManager.sendToAgent(agent, { type: "await", awaitName });
558
+ (0, socket_client_1.sendDaemonCommand)(socketPath, (0, await_private_wake_1.buildAwaitPrivateWakeCommand)({
559
+ agent,
560
+ awaitName,
561
+ triggerSource: "await-scheduler",
562
+ })).catch((error) => {
563
+ emitAwaitPrivateWakeDispatchError({
564
+ agent,
565
+ awaitName,
566
+ triggerSource: "await-scheduler",
567
+ socketPath,
568
+ error,
569
+ });
570
+ });
486
571
  },
487
572
  onAwaitExpire: (awaitName) => {
488
573
  void (0, await_expiry_1.archiveAndAlertExpiredAwait)({
@@ -492,8 +577,20 @@ void daemon.start().then(async () => {
492
577
  deliveryDeps: {
493
578
  agentName: agent,
494
579
  queuePending: () => {
495
- // Best-effort: queue inner-dialog wake so the agent processes the alert path
496
- (0, socket_client_1.sendDaemonCommand)(socketPath, { kind: "inner.wake", agent }).catch(() => { });
580
+ // Best-effort: queue private-runtime wake so the agent processes the alert path.
581
+ (0, socket_client_1.sendDaemonCommand)(socketPath, (0, await_private_wake_1.buildAwaitPrivateWakeCommand)({
582
+ agent,
583
+ awaitName,
584
+ triggerSource: "await-expiry",
585
+ })).catch((error) => {
586
+ emitAwaitPrivateWakeDispatchError({
587
+ agent,
588
+ awaitName,
589
+ triggerSource: "await-expiry",
590
+ socketPath,
591
+ error,
592
+ });
593
+ });
497
594
  },
498
595
  },
499
596
  }).catch((err) => {
@@ -66,8 +66,13 @@ const mailbox_types_1 = require("../mailbox/mailbox-types");
66
66
  const mailbox_read_1 = require("../mailbox/mailbox-read");
67
67
  const mailbox_view_1 = require("../mailbox/mailbox-view");
68
68
  const provider_visibility_1 = require("../provider-visibility");
69
+ const private_runtime_1 = require("../private-runtime");
69
70
  const socket_client_1 = require("./socket-client");
70
71
  const flight_recorder_1 = require("../../arc/flight-recorder");
72
+ const await_private_wake_1 = require("./await-private-wake");
73
+ const habit_private_wake_1 = require("./habit-private-wake");
74
+ const habit_parser_1 = require("../habits/habit-parser");
75
+ const habit_runtime_state_1 = require("../habits/habit-runtime-state");
71
76
  const PIDFILE_PATH = path.join(os.homedir(), ".ouro-cli", "daemon.pids");
72
77
  /**
73
78
  * Defense-in-depth: detect if we're running under vitest. The pidfile lives
@@ -374,6 +379,7 @@ function buildWorkerRows(snapshots) {
374
379
  return snapshots.map((snapshot) => ({
375
380
  agent: snapshot.name,
376
381
  worker: snapshot.channel,
382
+ autoStart: snapshot.autoStart ?? true,
377
383
  status: snapshot.status,
378
384
  pid: snapshot.pid,
379
385
  restartCount: snapshot.restartCount,
@@ -395,11 +401,17 @@ function unhealthySenseRows(senses) {
395
401
  return true;
396
402
  });
397
403
  }
404
+ function isParkedWorker(row) {
405
+ return row.autoStart === false && row.status === "stopped";
406
+ }
407
+ function unhealthyWorkerRows(workers) {
408
+ return workers.filter((row) => !isParkedWorker(row) && row.status !== "running");
409
+ }
398
410
  function unhealthyHealthChecks(healthChecks) {
399
411
  return healthChecks.filter((row) => row.status !== "ok");
400
412
  }
401
413
  function overviewHealth(workers, senses, healthChecks = []) {
402
- if (!workers.every((worker) => worker.status === "running"))
414
+ if (unhealthyWorkerRows(workers).length > 0)
403
415
  return "warn";
404
416
  if (unhealthySenseRows(senses).length > 0)
405
417
  return "warn";
@@ -412,8 +424,7 @@ function formatStatusSummary(payload) {
412
424
  return "no managed agents";
413
425
  }
414
426
  const degraded = [
415
- ...payload.workers
416
- .filter((row) => row.status !== "running")
427
+ ...unhealthyWorkerRows(payload.workers)
417
428
  .map((row) => `worker:${row.agent}/${row.worker}:${row.status}`),
418
429
  ...unhealthySenseRows(payload.senses)
419
430
  .map((row) => `sense:${row.agent}/${row.sense}:${row.status}`),
@@ -456,6 +467,27 @@ function isValidSenseReviveCommand(command) {
456
467
  && typeof command.sense === "string"
457
468
  && typeof command.reason === "string";
458
469
  }
470
+ function isValidTaskPokeCommand(command) {
471
+ return typeof command.agent === "string"
472
+ && command.agent.trim().length > 0
473
+ && typeof command.taskId === "string"
474
+ && command.taskId.trim().length > 0;
475
+ }
476
+ function isValidHabitPokeCommand(command) {
477
+ return typeof command.agent === "string"
478
+ && command.agent.trim().length > 0
479
+ && typeof command.habitName === "string"
480
+ && command.habitName.trim().length > 0;
481
+ }
482
+ function taskIdFromTaskPokePrivateWakeCommand(command) {
483
+ if (command.kind !== "private.wake" || command.triggerSource !== "task-poke")
484
+ return null;
485
+ const taskRef = command.originRefs?.find((ref) => ref.kind === "task" && typeof ref.id === "string");
486
+ if (!taskRef || typeof taskRef.id !== "string")
487
+ return null;
488
+ const trimmed = taskRef.id.trim();
489
+ return trimmed.length > 0 ? trimmed : null;
490
+ }
459
491
  /**
460
492
  * Handle agent.senseTurn command: runs a full agent turn via the daemon process.
461
493
  * Dynamic import lazy-loads shared-turn. Hot-reload works because ouro dev
@@ -523,6 +555,7 @@ class OuroDaemon {
523
555
  socketIdentity = null;
524
556
  senseAutostartTimer = null;
525
557
  mailboxServerFactory;
558
+ privateRuntimePolicyDeps;
526
559
  onStopCommandComplete;
527
560
  constructor(options) {
528
561
  this.socketPath = options.socketPath;
@@ -534,6 +567,7 @@ class OuroDaemon {
534
567
  this.bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
535
568
  this.mode = options.mode ?? "production";
536
569
  this.mailboxServerFactory = options.mailboxServerFactory ?? this.createDefaultMailboxServer.bind(this);
570
+ this.privateRuntimePolicyDeps = options.privateRuntimePolicyDeps ?? {};
537
571
  this.onStopCommandComplete = options.onStopCommandComplete ?? null;
538
572
  }
539
573
  /* v8 ignore start -- default mailbox server wiring: production-only path, tests inject mailboxServerFactory stub instead. startMailboxHttpServer itself has full coverage in mailbox-http.test.ts @preserve */
@@ -1078,6 +1112,123 @@ class OuroDaemon {
1078
1112
  }
1079
1113
  /* v8 ignore stop */
1080
1114
  }
1115
+ hasManagedPrivateRuntime(agent) {
1116
+ return this.processManager.listAgentSnapshots().some((snapshot) => snapshot.name === agent && snapshot.channel === "private-runtime");
1117
+ }
1118
+ buildPrivateRuntimeWakeRequest(command) {
1119
+ if (command.kind === "inner.wake") {
1120
+ return {
1121
+ agent: command.agent,
1122
+ origin: "daemon.private.wake",
1123
+ reason: "legacy inner.wake compatibility alias",
1124
+ providerLane: "inner",
1125
+ triggerSource: "legacy",
1126
+ budgetClass: "interactive",
1127
+ originRefs: [{ kind: "daemon-command", id: "inner.wake" }],
1128
+ };
1129
+ }
1130
+ return {
1131
+ agent: command.agent,
1132
+ origin: "daemon.private.wake",
1133
+ reason: command.reason ?? "manual private-runtime wake",
1134
+ providerLane: "inner",
1135
+ triggerSource: command.triggerSource ?? "manual",
1136
+ budgetClass: command.budgetClass ?? "interactive",
1137
+ ...(command.idempotencyKey ? { idempotencyKey: command.idempotencyKey } : {}),
1138
+ originRefs: command.originRefs ?? [{ kind: "daemon-command", id: "private.wake" }],
1139
+ };
1140
+ }
1141
+ buildAwaitPrivateWakeCommand(command) {
1142
+ return (0, await_private_wake_1.buildAwaitPrivateWakeCommand)({
1143
+ agent: command.agent,
1144
+ awaitName: command.awaitName,
1145
+ triggerSource: "await-poke",
1146
+ });
1147
+ }
1148
+ buildTaskPokePrivateWakeCommand(command, receiptId) {
1149
+ return {
1150
+ kind: "private.wake",
1151
+ agent: command.agent,
1152
+ reason: `task poke ${command.taskId}`,
1153
+ triggerSource: "task-poke",
1154
+ budgetClass: "scheduled",
1155
+ idempotencyKey: `task-poke:${command.agent}:${command.taskId}:${receiptId}`,
1156
+ originRefs: [
1157
+ { kind: "task", id: command.taskId },
1158
+ { kind: "queue-receipt", id: receiptId },
1159
+ { kind: "daemon-command", id: "task.poke" },
1160
+ ],
1161
+ };
1162
+ }
1163
+ resolveHabitPokeOccurrenceId(command, trigger) {
1164
+ if (trigger === "poke" || trigger === "manual")
1165
+ return undefined;
1166
+ const agentRoot = path.join(this.bundlesRoot, `${command.agent}.ouro`);
1167
+ const habitPath = path.join(agentRoot, "habits", `${command.habitName}.md`);
1168
+ if (!fs.existsSync(habitPath))
1169
+ return { skipReason: "habit file not found" };
1170
+ const habit = (0, habit_runtime_state_1.applyHabitRuntimeState)(agentRoot, (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath));
1171
+ if (habit.cadence === null)
1172
+ return { skipReason: "habit has no cadence" };
1173
+ const cadence = habit.cadence;
1174
+ if (habit.lastRun === null)
1175
+ return `${trigger}:first-run:${cadence}`;
1176
+ return `${trigger}:last-run:${habit.lastRun}:cadence:${cadence}`;
1177
+ }
1178
+ buildPrivateRuntimeWorkerWakeMessage(command, decision) {
1179
+ const awaitName = (0, await_private_wake_1.awaitNameFromPrivateWakeCommand)(command);
1180
+ if (awaitName) {
1181
+ return {
1182
+ type: "await",
1183
+ awaitName,
1184
+ privateTurnDecision: decision,
1185
+ };
1186
+ }
1187
+ const taskId = taskIdFromTaskPokePrivateWakeCommand(command);
1188
+ if (taskId) {
1189
+ return {
1190
+ type: "poke",
1191
+ taskId,
1192
+ privateTurnDecision: decision,
1193
+ };
1194
+ }
1195
+ const habitMessage = (0, habit_private_wake_1.habitMessageFromPrivateWakeCommand)(command);
1196
+ if (habitMessage) {
1197
+ return {
1198
+ type: "habit",
1199
+ habitName: habitMessage.habitName,
1200
+ trigger: habitMessage.trigger,
1201
+ privateTurnDecision: decision,
1202
+ };
1203
+ }
1204
+ return { type: "message", privateTurnDecision: decision };
1205
+ }
1206
+ async handlePrivateRuntimeWake(command) {
1207
+ if (!this.hasManagedPrivateRuntime(command.agent)) {
1208
+ return {
1209
+ ok: false,
1210
+ error: `No managed agent '${command.agent}' is registered with daemon-managed private runtime.`,
1211
+ };
1212
+ }
1213
+ const decision = await (0, private_runtime_1.requestPrivateTurnDecision)(this.buildPrivateRuntimeWakeRequest(command), {
1214
+ bundlesRoot: this.bundlesRoot,
1215
+ ...this.privateRuntimePolicyDeps,
1216
+ });
1217
+ if (!decision.executable) {
1218
+ return {
1219
+ ok: true,
1220
+ message: `private-runtime wake denied for ${command.agent}: ${decision.deniedReason}`,
1221
+ data: { decision },
1222
+ };
1223
+ }
1224
+ await this.processManager.startAgent(command.agent);
1225
+ this.processManager.sendToAgent?.(command.agent, this.buildPrivateRuntimeWorkerWakeMessage(command, decision));
1226
+ return {
1227
+ ok: true,
1228
+ message: `woke private runtime for ${command.agent}`,
1229
+ data: { decision },
1230
+ };
1231
+ }
1081
1232
  async handleCommandInner(command) {
1082
1233
  switch (command.kind) {
1083
1234
  case "daemon.start":
@@ -1252,11 +1403,11 @@ class OuroDaemon {
1252
1403
  // (cli-exec.ts) intentionally sends only message.send for tool-use
1253
1404
  // events to avoid waking the agent on every tool call. The hook's
1254
1405
  // intent was completely defeated by this handler calling
1255
- // `sendToAgent({type: "message"})`, which woke the inner-dialog
1406
+ // `sendToAgent({type: "message"})`, which woke the private runtime
1256
1407
  // worker on EVERY message.send anyway. ~30 message.send/min × the
1257
1408
  // 3-turn instinct-loop cap = ~90 turns/min sustained for hours.
1258
1409
  //
1259
- // Callers that want immediate processing must send `inner.wake`
1410
+ // Callers that want immediate processing must send `private.wake`
1260
1411
  // explicitly after message.send. The CLI `ouro msg` does so
1261
1412
  // (lifecycle-boundary delivery should wake); the hook does so
1262
1413
  // only on session-start / stop, not per tool-use; the API does
@@ -1279,13 +1430,42 @@ class OuroDaemon {
1279
1430
  data: messages,
1280
1431
  };
1281
1432
  }
1282
- case "inner.wake":
1283
- await this.processManager.startAgent(command.agent);
1284
- this.processManager.sendToAgent?.(command.agent, { type: "message" });
1433
+ case "private.decisions": {
1434
+ const requestedLimit = Number.isInteger(command.limit) && command.limit && command.limit > 0 ? command.limit : 20;
1435
+ const limit = Math.min(requestedLimit, 1000);
1436
+ const ledgerPath = (0, private_runtime_1.privateTurnLedgerPath)(command.agent, { bundlesRoot: this.bundlesRoot });
1437
+ const ledgerExists = fs.existsSync(ledgerPath);
1438
+ let decisions;
1439
+ try {
1440
+ decisions = ledgerExists ? (0, private_runtime_1.readPrivateTurnLedger)(ledgerPath) : [];
1441
+ }
1442
+ catch {
1443
+ return {
1444
+ ok: false,
1445
+ error: `private-runtime decision ledger is malformed: ${ledgerPath}; repair or remove malformed JSONL rows before reading decisions`,
1446
+ };
1447
+ }
1448
+ const guidance = ledgerExists
1449
+ ? undefined
1450
+ : `No private-runtime decision ledger exists for ${command.agent}; run an explicit private-runtime trigger or check ${path.dirname(ledgerPath)}.`;
1451
+ const payload = (0, private_runtime_1.buildPrivateDecisionReadPayload)({
1452
+ agent: command.agent,
1453
+ ledgerPath,
1454
+ decisions,
1455
+ limit,
1456
+ ...(guidance ? { guidance } : {}),
1457
+ });
1285
1458
  return {
1286
1459
  ok: true,
1287
- message: `woke inner dialog for ${command.agent}`,
1460
+ summary: (0, private_runtime_1.privateDecisionCountSummary)(payload.decisions.length),
1461
+ ...(guidance ? { message: guidance } : {}),
1462
+ data: payload,
1288
1463
  };
1464
+ }
1465
+ case "inner.wake":
1466
+ return this.handlePrivateRuntimeWake(command);
1467
+ case "private.wake":
1468
+ return this.handlePrivateRuntimeWake(command);
1289
1469
  case "chat.connect":
1290
1470
  await this.processManager.startAgent(command.agent);
1291
1471
  return {
@@ -1293,6 +1473,12 @@ class OuroDaemon {
1293
1473
  message: `connected to ${command.agent}`,
1294
1474
  };
1295
1475
  case "task.poke": {
1476
+ if (!isValidTaskPokeCommand(command)) {
1477
+ return {
1478
+ ok: false,
1479
+ error: "Invalid task.poke payload: expected non-empty string fields 'agent' and 'taskId'.",
1480
+ };
1481
+ }
1296
1482
  const receipt = await this.router.send({
1297
1483
  from: "ouro-poke",
1298
1484
  to: command.agent,
@@ -1301,7 +1487,7 @@ class OuroDaemon {
1301
1487
  taskRef: command.taskId,
1302
1488
  });
1303
1489
  await this.scheduler.recordTaskRun?.(command.agent, command.taskId);
1304
- this.processManager.sendToAgent?.(command.agent, { type: "poke", taskId: command.taskId });
1490
+ await this.handlePrivateRuntimeWake(this.buildTaskPokePrivateWakeCommand(command, receipt.id));
1305
1491
  return {
1306
1492
  ok: true,
1307
1493
  message: `queued poke ${receipt.id}`,
@@ -1309,22 +1495,39 @@ class OuroDaemon {
1309
1495
  };
1310
1496
  }
1311
1497
  case "habit.poke": {
1498
+ if (!isValidHabitPokeCommand(command)) {
1499
+ return {
1500
+ ok: false,
1501
+ error: "Invalid habit.poke payload: expected non-empty string fields 'agent' and 'habitName'.",
1502
+ };
1503
+ }
1312
1504
  const trigger = command.trigger ?? "poke";
1313
1505
  if (!(0, flight_recorder_1.isHabitRunTrigger)(trigger)) {
1314
1506
  return { ok: false, error: `invalid habit trigger: ${String(trigger)}` };
1315
1507
  }
1316
- this.processManager.sendToAgent?.(command.agent, { type: "habit", habitName: command.habitName, trigger });
1317
- return {
1318
- ok: true,
1319
- message: `poked habit ${command.habitName} for ${command.agent}`,
1320
- };
1508
+ if (!this.hasManagedPrivateRuntime(command.agent)) {
1509
+ return {
1510
+ ok: false,
1511
+ error: `No managed agent '${command.agent}' is registered with daemon-managed private runtime.`,
1512
+ };
1513
+ }
1514
+ const occurrenceId = this.resolveHabitPokeOccurrenceId(command, trigger);
1515
+ if (typeof occurrenceId === "object") {
1516
+ return {
1517
+ ok: true,
1518
+ message: `skipped scheduled habit ${command.habitName} for ${command.agent}: ${occurrenceId.skipReason}`,
1519
+ };
1520
+ }
1521
+ return this.handlePrivateRuntimeWake((0, habit_private_wake_1.buildHabitPrivateWakeCommand)({
1522
+ agent: command.agent,
1523
+ habitName: command.habitName,
1524
+ trigger,
1525
+ sourceRef: { kind: "daemon-command", id: "habit.poke" },
1526
+ occurrenceId,
1527
+ }));
1321
1528
  }
1322
1529
  case "await.poke": {
1323
- this.processManager.sendToAgent?.(command.agent, { type: "await", awaitName: command.awaitName });
1324
- return {
1325
- ok: true,
1326
- message: `poked await ${command.awaitName} for ${command.agent}`,
1327
- };
1530
+ return this.handlePrivateRuntimeWake(this.buildAwaitPrivateWakeCommand(command));
1328
1531
  }
1329
1532
  case "mcp.list": {
1330
1533
  (0, identity_1.setAgentName)(command.agent ?? "default");
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildHabitPrivateWakeCommand = buildHabitPrivateWakeCommand;
4
+ exports.habitMessageFromPrivateWakeCommand = habitMessageFromPrivateWakeCommand;
5
+ const flight_recorder_1 = require("../../arc/flight-recorder");
6
+ const runtime_1 = require("../../nerves/runtime");
7
+ function buildHabitPrivateWakeCommand(options) {
8
+ const firedAt = (options.now ?? (() => new Date()))().toISOString();
9
+ const occurrenceId = options.occurrenceId ?? firedAt;
10
+ const triggerSource = `habit-${options.trigger}`;
11
+ (0, runtime_1.emitNervesEvent)({
12
+ component: "daemon",
13
+ event: "daemon.habit_private_wake_built",
14
+ message: "built habit private-runtime wake command",
15
+ meta: {
16
+ agent: options.agent,
17
+ habitName: options.habitName,
18
+ trigger: options.trigger,
19
+ triggerSource,
20
+ occurrenceId,
21
+ },
22
+ });
23
+ return {
24
+ kind: "private.wake",
25
+ agent: options.agent,
26
+ reason: `habit ${options.habitName} fired by ${options.trigger}`,
27
+ triggerSource,
28
+ budgetClass: "scheduled",
29
+ idempotencyKey: `habit:${options.agent}:${options.habitName}:${options.trigger}:${occurrenceId}`,
30
+ originRefs: [
31
+ { kind: "habit", id: options.habitName },
32
+ { kind: "habit-trigger", id: options.trigger },
33
+ { kind: "habit-occurrence", id: occurrenceId },
34
+ options.sourceRef,
35
+ ],
36
+ };
37
+ }
38
+ function habitMessageFromPrivateWakeCommand(command) {
39
+ if (command.kind !== "private.wake")
40
+ return null;
41
+ if (typeof command.triggerSource !== "string" || !command.triggerSource.startsWith("habit-"))
42
+ return null;
43
+ const habitRef = command.originRefs?.find((ref) => ref.kind === "habit" && typeof ref.id === "string");
44
+ const triggerRef = command.originRefs?.find((ref) => ref.kind === "habit-trigger" && typeof ref.id === "string");
45
+ if (!habitRef || typeof habitRef.id !== "string" || !triggerRef || typeof triggerRef.id !== "string")
46
+ return null;
47
+ const habitName = habitRef.id.trim();
48
+ const trigger = triggerRef.id.trim();
49
+ if (habitName.length === 0)
50
+ return null;
51
+ if (!(0, flight_recorder_1.isHabitRunTrigger)(trigger))
52
+ return null;
53
+ if (command.triggerSource !== `habit-${trigger}`)
54
+ return null;
55
+ return { habitName, trigger };
56
+ }
@@ -26,7 +26,7 @@ function formatCadence(cadenceMs) {
26
26
  function buildInnerStatusOutput(input) {
27
27
  const { agentName, runtimeState, recordSummary, heartbeat, attentionCount, now } = input;
28
28
  const lines = [];
29
- lines.push(`inner dialog status: ${agentName}`);
29
+ lines.push(`private runtime status: ${agentName}`);
30
30
  // Last turn
31
31
  if (runtimeState?.lastCompletedAt) {
32
32
  const lastMs = new Date(runtimeState.lastCompletedAt).getTime();
@@ -64,8 +64,8 @@ function buildInnerStatusOutput(input) {
64
64
  lines.push(` attention: ${attentionCount} held ${thoughtWord}`);
65
65
  (0, runtime_1.emitNervesEvent)({
66
66
  component: "daemon",
67
- event: "daemon.inner_status_read",
68
- message: "inner dialog status read",
67
+ event: "daemon.private_runtime_status_read",
68
+ message: "private runtime status read",
69
69
  meta: {
70
70
  agentName,
71
71
  status: runtimeState?.status ?? "unknown",