@ouro.bot/cli 0.1.0-alpha.673 → 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.
- package/README.md +11 -5
- package/changelog.json +19 -0
- package/dist/arc/evolution.js +13 -10
- package/dist/arc/obligations.js +1 -1
- package/dist/heart/active-work.js +3 -3
- package/dist/heart/agent-entry.js +8 -8
- package/dist/heart/commitments.js +1 -1
- package/dist/heart/core.js +45 -23
- package/dist/heart/daemon/agent-config-check.js +24 -0
- package/dist/heart/daemon/agent-discovery.js +50 -0
- package/dist/heart/daemon/agent-service.js +6 -6
- package/dist/heart/daemon/await-private-wake.js +59 -0
- package/dist/heart/daemon/cli-exec.js +180 -24
- package/dist/heart/daemon/cli-help.js +20 -2
- package/dist/heart/daemon/cli-parse.js +45 -2
- package/dist/heart/daemon/cli-render.js +1 -0
- package/dist/heart/daemon/daemon-entry.js +126 -19
- package/dist/heart/daemon/daemon.js +223 -20
- package/dist/heart/daemon/habit-private-wake.js +56 -0
- package/dist/heart/daemon/health-monitor.js +6 -2
- package/dist/heart/daemon/inner-status.js +3 -3
- package/dist/heart/daemon/message-router.js +39 -3
- package/dist/heart/daemon/process-manager.js +1 -0
- package/dist/heart/daemon/pulse.js +43 -7
- package/dist/heart/daemon/socket-client.js +14 -2
- package/dist/heart/daemon/startup-tui.js +9 -1
- package/dist/heart/daemon/thoughts.js +39 -39
- package/dist/heart/habits/habit-scheduler.js +24 -4
- package/dist/heart/mailbox/mailbox-view.js +1 -1
- package/dist/heart/mailbox/readers/agent-machine.js +7 -7
- package/dist/heart/primitives.js +1 -1
- package/dist/heart/private-runtime/decision-renderer.js +156 -0
- package/dist/heart/private-runtime/index.js +18 -0
- package/dist/heart/private-runtime/ledger.js +218 -0
- package/dist/heart/private-runtime/policy.js +205 -0
- package/dist/heart/private-runtime/types.js +2 -0
- package/dist/heart/progress-story.js +1 -1
- package/dist/heart/session-transcript.js +1 -1
- package/dist/heart/tool-activity-callbacks.js +1 -1
- package/dist/heart/tool-description.js +2 -2
- package/dist/heart/tool-loop.js +2 -2
- package/dist/heart/turn-context.js +5 -5
- package/dist/mailbox-ui/assets/index-J8zDwHmE.js +1 -0
- package/dist/mailbox-ui/index.html +1 -1
- package/dist/mailroom/attention.js +1 -1
- package/dist/mind/context.js +2 -2
- package/dist/mind/pending.js +12 -5
- package/dist/mind/prompt.js +32 -25
- package/dist/repertoire/coding/feedback.js +25 -4
- package/dist/repertoire/tools-awaiting.js +2 -2
- package/dist/repertoire/tools-flow.js +4 -4
- package/dist/repertoire/tools-record.js +1 -1
- package/dist/repertoire/tools-session.js +33 -58
- package/dist/repertoire/tools-surface.js +3 -3
- package/dist/senses/cli.js +2 -2
- package/dist/senses/inner-dialog-worker.js +13 -519
- package/dist/senses/inner-dialog.js +10 -961
- package/dist/senses/mail.js +21 -5
- package/dist/senses/pipeline.js +1 -1
- package/dist/senses/private-runtime-worker.js +578 -0
- package/dist/senses/private-runtime.js +1145 -0
- package/dist/senses/shared-turn.js +1 -1
- package/dist/senses/trust-gate.js +2 -2
- package/package.json +1 -1
- 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,18 +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:
|
|
176
|
+
agents: managedPrivateRuntimes.map(({ agent, config }) => ({
|
|
123
177
|
name: agent,
|
|
124
178
|
entry: "heart/agent-entry.js",
|
|
125
|
-
channel: "
|
|
126
|
-
autoStart:
|
|
179
|
+
channel: "private-runtime",
|
|
180
|
+
autoStart: config.autoStart,
|
|
127
181
|
})),
|
|
128
182
|
existsSync: fs.existsSync,
|
|
129
|
-
/* v8 ignore next 4 -- wiring: delegates to
|
|
183
|
+
/* v8 ignore next 4 -- wiring: delegates to checkAgentConfig which has full unit tests @preserve */
|
|
130
184
|
configCheck: async (agent) => {
|
|
131
185
|
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
132
|
-
return (0, agent_config_check_1.
|
|
186
|
+
return (0, agent_config_check_1.checkAgentConfig)(agent, bundlesRoot);
|
|
133
187
|
},
|
|
134
188
|
/* v8 ignore start -- pulse flush wiring: integration code; flushPulse itself has full unit tests @preserve */
|
|
135
189
|
onSnapshotChange: () => {
|
|
@@ -138,12 +192,20 @@ const processManager = new process_manager_1.DaemonProcessManager({
|
|
|
138
192
|
bundlesRoot: (0, identity_1.getAgentBundlesRoot)(),
|
|
139
193
|
daemonVersion: (0, bundle_manifest_1.getPackageVersion)(),
|
|
140
194
|
now: new Date(),
|
|
141
|
-
// Default I/O wired into pulse.ts (writePulse, readPulse, etc.)
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
+
});
|
|
147
209
|
},
|
|
148
210
|
});
|
|
149
211
|
},
|
|
@@ -266,7 +328,7 @@ const degradedComponents = [];
|
|
|
266
328
|
function buildDaemonHealthState() {
|
|
267
329
|
const snapshots = processManager.listAgentSnapshots();
|
|
268
330
|
const agentDegradedComponents = snapshots
|
|
269
|
-
.filter((snapshot) => snapshot.status !== "running")
|
|
331
|
+
.filter((snapshot) => snapshot.status !== "running" && snapshot.autoStart !== false)
|
|
270
332
|
.map((snapshot) => {
|
|
271
333
|
const reasonParts = [
|
|
272
334
|
snapshot.errorReason ?? `${snapshot.channel} is ${snapshot.status}`,
|
|
@@ -305,7 +367,9 @@ function buildDaemonHealthState() {
|
|
|
305
367
|
const rollupStatus = (0, daemon_rollup_1.computeDaemonRollup)({
|
|
306
368
|
enabledAgents: snapshots.map((snapshot) => ({
|
|
307
369
|
name: snapshot.name,
|
|
308
|
-
|
|
370
|
+
// A parked non-autostart worker is intentionally not serving; it should
|
|
371
|
+
// remain visible in worker status but should not degrade daemon health.
|
|
372
|
+
status: snapshot.autoStart === false ? "running" : snapshot.status,
|
|
309
373
|
})),
|
|
310
374
|
bootstrapDegraded: degradedComponents,
|
|
311
375
|
safeMode: false,
|
|
@@ -394,8 +458,11 @@ function writeStopCommandHealthState() {
|
|
|
394
458
|
// Health writes are best-effort during shutdown.
|
|
395
459
|
}
|
|
396
460
|
}
|
|
461
|
+
async function preloadProviderCredentialPools() {
|
|
462
|
+
await Promise.all(managedAgents.map((agent) => (0, provider_credentials_1.refreshProviderCredentialPool)(agent, { preserveCachedOnFailure: true })));
|
|
463
|
+
}
|
|
397
464
|
/* v8 ignore start -- habit wiring: lambdas delegate to processManager/fs; tested via HabitScheduler unit tests @preserve */
|
|
398
|
-
void daemon.start().then(async () => {
|
|
465
|
+
void preloadProviderCredentialPools().then(() => daemon.start()).then(async () => {
|
|
399
466
|
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
400
467
|
const ouroPath = (0, os_cron_deps_1.resolveOuroBinaryPath)();
|
|
401
468
|
const osCronDeps = (0, os_cron_deps_1.createRealOsCronDeps)();
|
|
@@ -412,8 +479,24 @@ void daemon.start().then(async () => {
|
|
|
412
479
|
agent,
|
|
413
480
|
habitsDir,
|
|
414
481
|
osCronManager,
|
|
415
|
-
onHabitFire: (habitName, trigger) => {
|
|
416
|
-
|
|
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
|
+
});
|
|
417
500
|
},
|
|
418
501
|
deps: {
|
|
419
502
|
readdir: (dir) => fs.readdirSync(dir),
|
|
@@ -472,7 +555,19 @@ void daemon.start().then(async () => {
|
|
|
472
555
|
awaitsDir,
|
|
473
556
|
osCronManager: awaitOsCronManager,
|
|
474
557
|
onAwaitFire: (awaitName) => {
|
|
475
|
-
|
|
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
|
+
});
|
|
476
571
|
},
|
|
477
572
|
onAwaitExpire: (awaitName) => {
|
|
478
573
|
void (0, await_expiry_1.archiveAndAlertExpiredAwait)({
|
|
@@ -482,8 +577,20 @@ void daemon.start().then(async () => {
|
|
|
482
577
|
deliveryDeps: {
|
|
483
578
|
agentName: agent,
|
|
484
579
|
queuePending: () => {
|
|
485
|
-
// Best-effort: queue
|
|
486
|
-
(0, socket_client_1.sendDaemonCommand)(socketPath,
|
|
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
|
+
});
|
|
487
594
|
},
|
|
488
595
|
},
|
|
489
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 (
|
|
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
|
|
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 `
|
|
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 "
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
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.
|
|
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
|
+
}
|
|
@@ -69,7 +69,8 @@ class HealthMonitor {
|
|
|
69
69
|
async runChecks() {
|
|
70
70
|
const results = [];
|
|
71
71
|
const snapshots = this.processManager.listAgentSnapshots();
|
|
72
|
-
const unhealthy = snapshots.filter((snapshot) => snapshot.status !== "running");
|
|
72
|
+
const unhealthy = snapshots.filter((snapshot) => snapshot.status !== "running" && snapshot.autoStart !== false);
|
|
73
|
+
const parked = snapshots.filter((snapshot) => snapshot.status !== "running" && snapshot.autoStart === false);
|
|
73
74
|
if (unhealthy.length > 0) {
|
|
74
75
|
const unhealthySummary = unhealthy.map((item) => {
|
|
75
76
|
const detail = [
|
|
@@ -105,7 +106,10 @@ class HealthMonitor {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
else {
|
|
108
|
-
|
|
109
|
+
const message = parked.length > 0
|
|
110
|
+
? `all auto-start managed agents running; parked agents: ${parked.map((item) => item.name).join(", ")}`
|
|
111
|
+
: "all managed agents running";
|
|
112
|
+
results.push({ name: "agent-processes", status: "ok", message });
|
|
109
113
|
}
|
|
110
114
|
const jobs = this.scheduler.listJobs();
|
|
111
115
|
const neverRan = jobs.filter((job) => !job.lastRun);
|