@ouro.bot/cli 0.1.0-alpha.674 → 0.1.0-alpha.676
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 +28 -17
- 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 +122 -25
- package/dist/heart/daemon/daemon.js +223 -20
- package/dist/heart/daemon/habit-private-wake.js +56 -0
- package/dist/heart/daemon/inner-status.js +3 -3
- package/dist/heart/daemon/message-router.js +39 -3
- 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 -67
- 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,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:
|
|
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
|
-
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.
|
|
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
|
-
//
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
496
|
-
(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
|
+
});
|
|
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 (
|
|
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
|
+
}
|
|
@@ -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(`
|
|
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.
|
|
68
|
-
message: "
|
|
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",
|