@ouro.bot/cli 0.1.0-alpha.732 → 0.1.0-alpha.734
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 +1 -1
- package/changelog.json +34 -0
- package/deploy/unraid/Dockerfile +27 -0
- package/deploy/unraid/README.txt +1368 -0
- package/deploy/unraid/audit-container-spec.sh +4 -0
- package/deploy/unraid/container-runtime.json +1 -0
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +37 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +200 -0
- package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
- package/deploy/unraid/sanctuary-deployment-target.mjs +705 -0
- package/deploy/unraid/sanctuary-safe-rename.py +24 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +1777 -0
- package/deploy/unraid/sanctuary-unit16-run.sh +502 -0
- package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
- package/deploy/unraid/sanctuary.ouro/agent.json +24 -0
- package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
- package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
- package/deploy/unraid/sanctuary.ouro/provider-readiness.json +18 -0
- package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +5 -0
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +21 -0
- package/deploy/unraid/sanctuary.xml +25 -0
- package/dist/heart/approval-files.js +113 -0
- package/dist/heart/approval-store.js +168 -9
- package/dist/heart/auth/auth-flow.js +17 -1
- package/dist/heart/autonomy-budget.js +44 -1
- package/dist/heart/awaiting/await-scheduler.js +6 -2
- package/dist/heart/config.js +8 -0
- package/dist/heart/core.js +117 -132
- package/dist/heart/daemon/cli-exec.js +74 -2
- package/dist/heart/daemon/cli-help.js +6 -1
- package/dist/heart/daemon/cli-parse.js +6 -4
- package/dist/heart/daemon/container-credential-bootstrap.js +185 -0
- package/dist/heart/daemon/container-healthcheck.js +94 -0
- package/dist/heart/daemon/container-runtime.js +84 -0
- package/dist/heart/daemon/container-spec-auditor-main.js +156 -0
- package/dist/heart/daemon/container-spec-auditor.js +156 -0
- package/dist/heart/daemon/daemon-bootstrap-startup.js +41 -0
- package/dist/heart/daemon/daemon-entry.js +116 -28
- package/dist/heart/daemon/daemon-health.js +10 -0
- package/dist/heart/daemon/daemon.js +92 -5
- package/dist/heart/daemon/doctor.js +56 -7
- package/dist/heart/daemon/runtime-logging.js +6 -0
- package/dist/heart/daemon/runtime-mode.js +13 -2
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2365 -0
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +1624 -0
- package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +985 -0
- package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
- package/dist/heart/daemon/sanctuary-scheduler-origin.js +126 -0
- package/dist/heart/daemon/sense-manager.js +25 -6
- package/dist/heart/daemon/supercronic-supervisor.js +267 -0
- package/dist/heart/habits/habit-scheduler.js +7 -3
- package/dist/heart/identity.js +13 -3
- package/dist/heart/provider-credentials.js +9 -1
- package/dist/heart/provider-models.js +8 -0
- package/dist/heart/provider-ping.js +5 -2
- package/dist/heart/providers/openai-compatible.js +177 -0
- package/dist/heart/runtime-credentials.js +217 -8
- package/dist/heart/sense-truth.js +2 -0
- package/dist/heart/tool-approval.js +1 -0
- package/dist/heart/turn-context.js +8 -0
- package/dist/mailroom/blob-store.js +97 -19
- package/dist/mailroom/cache-sync-cli.js +5 -1
- package/dist/mailroom/core.js +23 -8
- package/dist/mailroom/hosted-cache-sync.js +179 -44
- package/dist/mailroom/search-cache.js +109 -0
- package/dist/mind/prompt.js +9 -0
- package/dist/nerves/coverage/source-scanner.js +4 -4
- package/dist/nerves/index.js +75 -9
- package/dist/nerves/runtime.js +12 -0
- package/dist/repertoire/tool-arguments.js +39 -0
- package/dist/repertoire/tools-unraid.js +353 -0
- package/dist/repertoire/tools.js +19 -1
- package/dist/repertoire/unraid-client.js +155 -0
- package/dist/repertoire/unraid-restart.js +130 -0
- package/dist/senses/private-runtime-worker.js +3 -2
- package/dist/senses/sanctuary-grounding.js +118 -0
- package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
- package/dist/senses/sanctuary-health-acceptance-probe.js +772 -0
- package/dist/senses/sanctuary-health-runner.js +146 -0
- package/dist/senses/sanctuary-health.js +447 -0
- package/dist/senses/sanctuary-interactive-control.js +306 -0
- package/dist/senses/sanctuary-runtime.js +206 -0
- package/dist/senses/shared-turn.js +11 -3
- package/dist/senses/telegram-approval-runtime.js +526 -0
- package/dist/senses/telegram-audit-ledger.js +220 -0
- package/dist/senses/telegram-client.js +1471 -0
- package/dist/senses/telegram-entry.js +80 -0
- package/dist/senses/telegram.js +1232 -0
- package/npm-shrinkwrap.json +6558 -0
- package/package.json +4 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.failFastContainerCredentialBootstrapStartup = failFastContainerCredentialBootstrapStartup;
|
|
4
|
+
exports.startDaemonAfterContainerCredentialBootstrap = startDaemonAfterContainerCredentialBootstrap;
|
|
5
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
6
|
+
const daemon_tombstone_1 = require("./daemon-tombstone");
|
|
7
|
+
const REDACTED_BOOTSTRAP_STARTUP_ERROR = "container credential bootstrap rejected; recoverable claim retained for reconciliation";
|
|
8
|
+
function failFastContainerCredentialBootstrapStartup(input) {
|
|
9
|
+
const error = new Error(REDACTED_BOOTSTRAP_STARTUP_ERROR);
|
|
10
|
+
try {
|
|
11
|
+
(0, daemon_tombstone_1.writeDaemonTombstone)("startupFailure", error);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// Exit remains mandatory even if best-effort tombstone reporting fails.
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
(0, runtime_1.emitNervesEvent)({
|
|
18
|
+
level: "error",
|
|
19
|
+
component: "daemon",
|
|
20
|
+
event: "daemon.entry_error",
|
|
21
|
+
message: "daemon entrypoint failed before server startup",
|
|
22
|
+
meta: { error: REDACTED_BOOTSTRAP_STARTUP_ERROR },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Exit remains mandatory even if best-effort event reporting fails.
|
|
27
|
+
}
|
|
28
|
+
input.exit(1);
|
|
29
|
+
}
|
|
30
|
+
async function startDaemonAfterContainerCredentialBootstrap(input) {
|
|
31
|
+
try {
|
|
32
|
+
await input.loadBootstrap();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
input.markStartupFailure();
|
|
36
|
+
failFastContainerCredentialBootstrapStartup({ exit: input.exit });
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
await input.startDaemon();
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
@@ -57,6 +57,8 @@ const await_scheduler_1 = require("../awaiting/await-scheduler");
|
|
|
57
57
|
const await_expiry_1 = require("../awaiting/await-expiry");
|
|
58
58
|
const os_cron_deps_1 = require("./os-cron-deps");
|
|
59
59
|
const os_cron_1 = require("./os-cron");
|
|
60
|
+
const container_runtime_1 = require("./container-runtime");
|
|
61
|
+
const supercronic_supervisor_1 = require("./supercronic-supervisor");
|
|
60
62
|
const daemon_tombstone_1 = require("./daemon-tombstone");
|
|
61
63
|
const agent_config_check_1 = require("./agent-config-check");
|
|
62
64
|
const pulse_1 = require("./pulse");
|
|
@@ -70,7 +72,14 @@ const context_loss_sentinel_1 = require("../context-loss-sentinel");
|
|
|
70
72
|
const provider_credentials_1 = require("../provider-credentials");
|
|
71
73
|
const runtime_credentials_1 = require("../runtime-credentials");
|
|
72
74
|
const machine_identity_1 = require("../machine-identity");
|
|
75
|
+
const container_credential_bootstrap_1 = require("./container-credential-bootstrap");
|
|
76
|
+
const daemon_bootstrap_startup_1 = require("./daemon-bootstrap-startup");
|
|
73
77
|
const auth_flow_1 = require("../auth/auth-flow");
|
|
78
|
+
const sanctuary_health_runner_1 = require("../../senses/sanctuary-health-runner");
|
|
79
|
+
const sanctuary_acceptance_marker_1 = require("./sanctuary-acceptance-marker");
|
|
80
|
+
const sanctuary_scheduler_liveness_1 = require("./sanctuary-scheduler-liveness");
|
|
81
|
+
const sanctuary_scheduler_origin_1 = require("./sanctuary-scheduler-origin");
|
|
82
|
+
const telegram_1 = require("../../senses/telegram");
|
|
74
83
|
function parseSocketPath(argv) {
|
|
75
84
|
const socketIndex = argv.indexOf("--socket");
|
|
76
85
|
if (socketIndex >= 0) {
|
|
@@ -244,6 +253,17 @@ const taskScheduler = new task_scheduler_1.TaskDrivenScheduler({
|
|
|
244
253
|
});
|
|
245
254
|
const habitSchedulers = [];
|
|
246
255
|
const awaitSchedulers = [];
|
|
256
|
+
const containerRuntimePolicy = (0, container_runtime_1.readContainerRuntimePolicy)();
|
|
257
|
+
const supercronicSupervisor = containerRuntimePolicy?.scheduler === "supercronic"
|
|
258
|
+
? new supercronic_supervisor_1.SupercronicSupervisor({
|
|
259
|
+
binaryPath: "/usr/local/bin/supercronic",
|
|
260
|
+
crontabPath: path.join((0, identity_1.getAgentBundlesRoot)(), "..", ".ouro-cli", "scheduler", "sanctuary.crontab"),
|
|
261
|
+
onFatal: (error) => {
|
|
262
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.supercronic_state", message: "Supercronic restart budget exhausted", meta: { error: error.message } });
|
|
263
|
+
process.exit(1);
|
|
264
|
+
},
|
|
265
|
+
})
|
|
266
|
+
: null;
|
|
247
267
|
function habitCronLabelOwner(agent) {
|
|
248
268
|
const agentPrefix = `bot.ouro.${agent}.`;
|
|
249
269
|
const awaitPrefix = `${agentPrefix}await.`;
|
|
@@ -334,21 +354,25 @@ const healthMonitor = new health_monitor_1.HealthMonitor({
|
|
|
334
354
|
catch { /* recovery is best-effort */ }
|
|
335
355
|
},
|
|
336
356
|
});
|
|
337
|
-
let
|
|
357
|
+
let entryRuntimeStopPromise = null;
|
|
338
358
|
let stopCommandExitScheduled = false;
|
|
339
359
|
function stopEntryRuntime() {
|
|
340
|
-
if (
|
|
341
|
-
return;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
s
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
s
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
360
|
+
if (entryRuntimeStopPromise)
|
|
361
|
+
return entryRuntimeStopPromise;
|
|
362
|
+
entryRuntimeStopPromise = (async () => {
|
|
363
|
+
stopHealthHeartbeat();
|
|
364
|
+
for (const s of habitSchedulers) {
|
|
365
|
+
s.stopWatching();
|
|
366
|
+
s.stop();
|
|
367
|
+
}
|
|
368
|
+
for (const s of awaitSchedulers) {
|
|
369
|
+
s.stopWatching();
|
|
370
|
+
s.stop();
|
|
371
|
+
}
|
|
372
|
+
await supercronicSupervisor?.stop();
|
|
373
|
+
healthMonitor.stopPeriodicChecks();
|
|
374
|
+
})();
|
|
375
|
+
return entryRuntimeStopPromise;
|
|
352
376
|
}
|
|
353
377
|
function scheduleCleanProcessExitAfterStopCommand() {
|
|
354
378
|
if (stopCommandExitScheduled)
|
|
@@ -367,9 +391,56 @@ const daemon = new daemon_1.OuroDaemon({
|
|
|
367
391
|
scheduler,
|
|
368
392
|
healthMonitor,
|
|
369
393
|
router,
|
|
394
|
+
schedulerFireVerifier: (command) => {
|
|
395
|
+
if (!supercronicSupervisor)
|
|
396
|
+
throw new Error("Supercronic scheduler is unavailable");
|
|
397
|
+
const marker = (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)("sanctuary");
|
|
398
|
+
return (0, sanctuary_scheduler_origin_1.verifySanctuarySchedulerFireCommand)(command, {
|
|
399
|
+
childPid: supercronicSupervisor.authenticatedSnapshot("habit:sanctuary").childPid,
|
|
400
|
+
identityKey: (0, telegram_1.readOrCreateTelegramIdentityKey)(path.join((0, identity_1.getAgentBundlesRoot)(), "sanctuary.ouro")),
|
|
401
|
+
scenarioHandleDigest: marker?.label === "unit-16f-cron-fingerprint" ? marker.scenarioHandleDigest : null, now: () => new Date(),
|
|
402
|
+
readFile: (target) => fs.readFileSync(target, "utf8"), readLink: (target) => fs.readlinkSync(target),
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
schedulerFireConsumer: (origin) => (0, sanctuary_scheduler_liveness_1.consumeSanctuarySchedulerFire)(path.join((0, identity_1.getAgentBundlesRoot)(), "sanctuary.ouro"), origin),
|
|
406
|
+
nativeHabitRunner: async ({ agent, habitName, trigger, occurrenceId, runnerId, schedulerOrigin }) => {
|
|
407
|
+
if (agent !== "sanctuary" || habitName !== "sanctuary-health")
|
|
408
|
+
return null;
|
|
409
|
+
const marker = (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)(agent);
|
|
410
|
+
const schedulerScenario = marker?.label === "unit-16f-cron-fingerprint" ? marker : null;
|
|
411
|
+
const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${agent}.ouro`);
|
|
412
|
+
const before = schedulerScenario ? (0, sanctuary_scheduler_liveness_1.readSanctuaryHealthCursor)(agentRoot) : null;
|
|
413
|
+
let providerInvocationCount = 0;
|
|
414
|
+
let privateTurnCount = 0;
|
|
415
|
+
const result = await (0, sanctuary_health_runner_1.runSanctuaryHealthHabit)(agent, schedulerScenario ? {
|
|
416
|
+
acceptanceMetrics: {
|
|
417
|
+
onPrivateTurnStart: () => { privateTurnCount += 1; },
|
|
418
|
+
onProviderInvocation: () => { providerInvocationCount += 1; },
|
|
419
|
+
},
|
|
420
|
+
} : {});
|
|
421
|
+
if (schedulerScenario) {
|
|
422
|
+
if (!supercronicSupervisor || !before || !occurrenceId || !schedulerOrigin)
|
|
423
|
+
throw new Error("Sanctuary scheduler liveness supervisor provenance is unavailable");
|
|
424
|
+
(0, sanctuary_scheduler_liveness_1.recordSanctuarySchedulerLivenessReceipt)({
|
|
425
|
+
agentRoot,
|
|
426
|
+
trigger,
|
|
427
|
+
occurrenceId,
|
|
428
|
+
runnerId,
|
|
429
|
+
scenario: schedulerScenario,
|
|
430
|
+
supervisor: supercronicSupervisor.authenticatedSnapshot("habit:sanctuary"),
|
|
431
|
+
before,
|
|
432
|
+
providerInvocationCount,
|
|
433
|
+
privateTurnCount,
|
|
434
|
+
schedulerOrigin,
|
|
435
|
+
identityKey: (0, telegram_1.readOrCreateTelegramIdentityKey)(agentRoot),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return result;
|
|
439
|
+
},
|
|
440
|
+
nativeHabitMatch: (agent, habitName) => agent === "sanctuary" && habitName === "sanctuary-health",
|
|
370
441
|
mode,
|
|
371
|
-
onStopCommandComplete: () => {
|
|
372
|
-
stopEntryRuntime();
|
|
442
|
+
onStopCommandComplete: async () => {
|
|
443
|
+
await stopEntryRuntime();
|
|
373
444
|
scheduleCleanProcessExitAfterStopCommand();
|
|
374
445
|
},
|
|
375
446
|
});
|
|
@@ -495,6 +566,7 @@ function emitAwaitSetupError(agent, error) {
|
|
|
495
566
|
const healthWriter = new daemon_health_1.DaemonHealthWriter((0, daemon_health_1.getDefaultHealthPath)());
|
|
496
567
|
const healthSink = (0, daemon_health_1.createHealthNervesSink)(healthWriter, buildDaemonHealthState);
|
|
497
568
|
(0, index_1.registerGlobalLogSink)(healthSink);
|
|
569
|
+
const stopHealthHeartbeat = (0, daemon_health_1.startDaemonHealthHeartbeat)(healthWriter, buildDaemonHealthState);
|
|
498
570
|
/* v8 ignore stop */
|
|
499
571
|
function writeStopCommandHealthState() {
|
|
500
572
|
try {
|
|
@@ -611,10 +683,20 @@ function scheduleStartupSentinelAfterProviderPreload(agent, preload) {
|
|
|
611
683
|
/* v8 ignore stop */
|
|
612
684
|
}
|
|
613
685
|
/* v8 ignore start -- habit wiring: lambdas delegate to processManager/fs; tested via HabitScheduler unit tests @preserve */
|
|
614
|
-
void
|
|
686
|
+
void (0, daemon_bootstrap_startup_1.startDaemonAfterContainerCredentialBootstrap)({
|
|
687
|
+
loadBootstrap: () => (0, container_credential_bootstrap_1.loadContainerCredentialBootstrap)(managedAgents),
|
|
688
|
+
startDaemon: () => daemon.start(),
|
|
689
|
+
markStartupFailure: () => { _tombstoneWritten = true; },
|
|
690
|
+
exit: (code) => process.exit(code),
|
|
691
|
+
}).then(async (started) => {
|
|
692
|
+
if (!started)
|
|
693
|
+
return;
|
|
694
|
+
supercronicSupervisor?.start();
|
|
615
695
|
const providerPreload = startProviderCredentialPoolPreload();
|
|
616
696
|
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
617
|
-
const ouroPath =
|
|
697
|
+
const ouroPath = supercronicSupervisor
|
|
698
|
+
? "/usr/local/bin/node /opt/ouro/dist/heart/daemon/ouro-entry.js"
|
|
699
|
+
: (0, os_cron_deps_1.resolveOuroBinaryPath)();
|
|
618
700
|
const osCronDeps = (0, os_cron_deps_1.createRealOsCronDeps)();
|
|
619
701
|
for (const agent of managedAgents) {
|
|
620
702
|
scheduleStartupSentinelAfterProviderPreload(agent, providerPreload);
|
|
@@ -624,13 +706,15 @@ void daemon.start().then(async () => {
|
|
|
624
706
|
try {
|
|
625
707
|
// Migrate old tasks/habits/ to habits/ at bundle root
|
|
626
708
|
(0, habit_migration_1.migrateHabitsFromTaskSystem)(bundleRoot);
|
|
627
|
-
const osCronManager =
|
|
709
|
+
const osCronManager = supercronicSupervisor
|
|
710
|
+
? supercronicSupervisor.namespace(`habit:${agent}`)
|
|
711
|
+
: new os_cron_1.LaunchdCronManager(osCronDeps, { ownsLabel: habitCronLabelOwner(agent) });
|
|
628
712
|
const scheduler = new habit_scheduler_1.HabitScheduler({
|
|
629
713
|
agent,
|
|
630
714
|
habitsDir,
|
|
631
715
|
osCronManager,
|
|
632
716
|
onHabitFire: (habitName, trigger, context) => {
|
|
633
|
-
if ((0, habit_policy_1.isRsvpHabitName)(habitName)) {
|
|
717
|
+
if ((0, habit_policy_1.isRsvpHabitName)(habitName) || (agent === "sanctuary" && habitName === "sanctuary-health")) {
|
|
634
718
|
(0, socket_client_1.sendDaemonCommand)(socketPath, {
|
|
635
719
|
kind: "habit.poke",
|
|
636
720
|
agent,
|
|
@@ -676,7 +760,9 @@ void daemon.start().then(async () => {
|
|
|
676
760
|
ouroPath,
|
|
677
761
|
watch: (dir, cb) => fs.watch(dir, cb),
|
|
678
762
|
},
|
|
679
|
-
execForVerify: verifyOsCron,
|
|
763
|
+
execForVerify: supercronicSupervisor ? () => supercronicSupervisor.verificationOutput() : verifyOsCron,
|
|
764
|
+
verifyJobs: supercronicSupervisor ? (jobs) => supercronicSupervisor.verifyNamespace(`habit:${agent}`, jobs) : undefined,
|
|
765
|
+
platform: supercronicSupervisor ? "linux" : undefined,
|
|
680
766
|
});
|
|
681
767
|
try {
|
|
682
768
|
scheduler.start();
|
|
@@ -719,7 +805,9 @@ void daemon.start().then(async () => {
|
|
|
719
805
|
const awaitsDir = path.join(bundleRoot, "awaiting");
|
|
720
806
|
const awaitDegradedComponent = `awaits:${agent}`;
|
|
721
807
|
try {
|
|
722
|
-
const awaitOsCronManager =
|
|
808
|
+
const awaitOsCronManager = supercronicSupervisor
|
|
809
|
+
? supercronicSupervisor.namespace(`await:${agent}`)
|
|
810
|
+
: new os_cron_1.LaunchdCronManager(osCronDeps, { ownsLabel: awaitCronLabelOwner(agent) });
|
|
723
811
|
const awaitScheduler = new await_scheduler_1.AwaitScheduler({
|
|
724
812
|
agent,
|
|
725
813
|
awaitsDir,
|
|
@@ -782,7 +870,9 @@ void daemon.start().then(async () => {
|
|
|
782
870
|
ouroPath,
|
|
783
871
|
watch: (dir, cb) => fs.watch(dir, cb),
|
|
784
872
|
},
|
|
785
|
-
execForVerify: verifyOsCron,
|
|
873
|
+
execForVerify: supercronicSupervisor ? () => supercronicSupervisor.verificationOutput() : verifyOsCron,
|
|
874
|
+
verifyJobs: supercronicSupervisor ? (jobs) => supercronicSupervisor.verifyNamespace(`await:${agent}`, jobs) : undefined,
|
|
875
|
+
platform: supercronicSupervisor ? "linux" : undefined,
|
|
786
876
|
});
|
|
787
877
|
try {
|
|
788
878
|
awaitScheduler.start();
|
|
@@ -853,10 +943,9 @@ process.on("SIGINT", () => {
|
|
|
853
943
|
// tombstone is strictly better than silence.
|
|
854
944
|
_tombstoneWritten = true;
|
|
855
945
|
(0, daemon_tombstone_1.writeDaemonTombstone)("sigint", new Error("daemon received SIGINT"));
|
|
856
|
-
|
|
857
|
-
const forcedExit = setTimeout(() => process.exit(1), 5_000);
|
|
946
|
+
const forcedExit = setTimeout(() => process.exit(1), 12_000);
|
|
858
947
|
forcedExit.unref();
|
|
859
|
-
void daemon.stop().then(() => {
|
|
948
|
+
void stopEntryRuntime().then(() => daemon.stop()).then(() => {
|
|
860
949
|
clearTimeout(forcedExit);
|
|
861
950
|
process.exit(0);
|
|
862
951
|
}, () => {
|
|
@@ -867,10 +956,9 @@ process.on("SIGINT", () => {
|
|
|
867
956
|
process.on("SIGTERM", () => {
|
|
868
957
|
_tombstoneWritten = true;
|
|
869
958
|
(0, daemon_tombstone_1.writeDaemonTombstone)("sigterm", new Error("daemon received SIGTERM"));
|
|
870
|
-
|
|
871
|
-
const forcedExit = setTimeout(() => process.exit(1), 5_000);
|
|
959
|
+
const forcedExit = setTimeout(() => process.exit(1), 12_000);
|
|
872
960
|
forcedExit.unref();
|
|
873
|
-
void daemon.stop().then(() => {
|
|
961
|
+
void stopEntryRuntime().then(() => daemon.stop()).then(() => {
|
|
874
962
|
clearTimeout(forcedExit);
|
|
875
963
|
process.exit(0);
|
|
876
964
|
}, () => {
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.HEALTH_TRACKED_EVENTS = exports.DaemonHealthWriter = void 0;
|
|
37
37
|
exports.isRollupStatus = isRollupStatus;
|
|
38
38
|
exports.isDaemonStatus = isDaemonStatus;
|
|
39
|
+
exports.startDaemonHealthHeartbeat = startDaemonHealthHeartbeat;
|
|
39
40
|
exports.getDefaultHealthPath = getDefaultHealthPath;
|
|
40
41
|
exports.createHealthNervesSink = createHealthNervesSink;
|
|
41
42
|
exports.readHealth = readHealth;
|
|
@@ -100,6 +101,15 @@ class DaemonHealthWriter {
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
exports.DaemonHealthWriter = DaemonHealthWriter;
|
|
104
|
+
function startDaemonHealthHeartbeat(writer, getState, intervalMs = 30_000) {
|
|
105
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0)
|
|
106
|
+
throw new Error("health heartbeat interval must be positive");
|
|
107
|
+
const write = () => writer.writeHealth(getState());
|
|
108
|
+
write();
|
|
109
|
+
const timer = setInterval(write, intervalMs);
|
|
110
|
+
timer.unref?.();
|
|
111
|
+
return () => clearInterval(timer);
|
|
112
|
+
}
|
|
103
113
|
function getDefaultHealthPath() {
|
|
104
114
|
return path.join(os.homedir(), ".ouro-cli", "daemon-health.json");
|
|
105
115
|
}
|
|
@@ -49,6 +49,7 @@ const fs = __importStar(require("fs"));
|
|
|
49
49
|
const net = __importStar(require("net"));
|
|
50
50
|
const os = __importStar(require("os"));
|
|
51
51
|
const path = __importStar(require("path"));
|
|
52
|
+
const node_crypto_1 = require("node:crypto");
|
|
52
53
|
const identity_1 = require("../identity");
|
|
53
54
|
const agent_discovery_1 = require("./agent-discovery");
|
|
54
55
|
const runtime_1 = require("../../nerves/runtime");
|
|
@@ -78,6 +79,8 @@ const habit_parser_1 = require("../habits/habit-parser");
|
|
|
78
79
|
const habit_runtime_state_1 = require("../habits/habit-runtime-state");
|
|
79
80
|
const router_1 = require("../external-events/router");
|
|
80
81
|
const habit_policy_1 = require("../../rsvp/habit-policy");
|
|
82
|
+
const container_runtime_1 = require("./container-runtime");
|
|
83
|
+
const habit_session_1 = require("../habits/habit-session");
|
|
81
84
|
const PIDFILE_PATH = path.join(os.homedir(), ".ouro-cli", "daemon.pids");
|
|
82
85
|
/**
|
|
83
86
|
* Defense-in-depth: detect if we're running under vitest. The pidfile lives
|
|
@@ -127,6 +130,7 @@ function parseOrphanPidsFromPs(psOutput, selfPid) {
|
|
|
127
130
|
&& !line.includes("mail-entry.js")
|
|
128
131
|
&& !line.includes("teams-entry.js")
|
|
129
132
|
&& !line.includes("a2a-entry.js")
|
|
133
|
+
&& !line.includes("telegram-entry.js")
|
|
130
134
|
&& !line.includes("voice-entry.js"))
|
|
131
135
|
continue;
|
|
132
136
|
// Parse `<pid> <ppid> <command...>`. ps pads these with leading spaces.
|
|
@@ -616,6 +620,12 @@ class OuroDaemon {
|
|
|
616
620
|
onStopCommandComplete;
|
|
617
621
|
externalEventRoot;
|
|
618
622
|
rsvpHabitRunner;
|
|
623
|
+
nativeHabitRunner;
|
|
624
|
+
nativeHabitMatch;
|
|
625
|
+
nativeHabitClaims = new Set();
|
|
626
|
+
authenticatedSchedulerPokes = new WeakMap();
|
|
627
|
+
schedulerFireVerifier;
|
|
628
|
+
schedulerFireConsumer;
|
|
619
629
|
orphanStartupDrain;
|
|
620
630
|
constructor(options) {
|
|
621
631
|
this.socketPath = options.socketPath;
|
|
@@ -631,6 +641,10 @@ class OuroDaemon {
|
|
|
631
641
|
this.onStopCommandComplete = options.onStopCommandComplete ?? null;
|
|
632
642
|
this.externalEventRoot = options.externalEventRoot ?? null;
|
|
633
643
|
this.rsvpHabitRunner = options.rsvpHabitRunner;
|
|
644
|
+
this.nativeHabitRunner = options.nativeHabitRunner;
|
|
645
|
+
this.nativeHabitMatch = options.nativeHabitMatch;
|
|
646
|
+
this.schedulerFireVerifier = options.schedulerFireVerifier;
|
|
647
|
+
this.schedulerFireConsumer = options.schedulerFireConsumer;
|
|
634
648
|
this.orphanStartupDrain = options.orphanStartupDrain ?? drainOrphanProcessesBeforeStartup;
|
|
635
649
|
}
|
|
636
650
|
/* 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 */
|
|
@@ -752,19 +766,21 @@ class OuroDaemon {
|
|
|
752
766
|
}
|
|
753
767
|
}
|
|
754
768
|
async startInner() {
|
|
769
|
+
const containerPolicy = (0, container_runtime_1.readContainerRuntimePolicy)();
|
|
755
770
|
// Register update hooks and apply pending updates before starting agents
|
|
756
771
|
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
757
772
|
(0, update_hooks_1.registerUpdateHook)(agent_config_v2_1.agentConfigV2Hook);
|
|
758
773
|
const currentVersion = (0, bundle_manifest_1.getPackageVersion)();
|
|
759
|
-
|
|
774
|
+
if (containerPolicy?.updates !== "disabled")
|
|
775
|
+
await (0, update_hooks_1.applyPendingUpdates)(this.bundlesRoot, currentVersion);
|
|
760
776
|
// Start periodic update checker (polls npm registry every 30 minutes)
|
|
761
777
|
// Skip in dev mode — dev builds should not auto-update from npm
|
|
762
|
-
if (this.mode === "dev") {
|
|
778
|
+
if (this.mode === "dev" || containerPolicy?.updates === "disabled") {
|
|
763
779
|
(0, runtime_1.emitNervesEvent)({
|
|
764
780
|
component: "daemon",
|
|
765
781
|
event: "daemon.update_checker_skip",
|
|
766
782
|
message: "skipping update checker in dev mode",
|
|
767
|
-
meta: { reason: "dev mode" },
|
|
783
|
+
meta: { reason: this.mode === "dev" ? "dev mode" : "container policy" },
|
|
768
784
|
});
|
|
769
785
|
}
|
|
770
786
|
else {
|
|
@@ -1538,7 +1554,7 @@ class OuroDaemon {
|
|
|
1538
1554
|
await this.stop();
|
|
1539
1555
|
}
|
|
1540
1556
|
finally {
|
|
1541
|
-
this.onStopCommandComplete?.();
|
|
1557
|
+
await this.onStopCommandComplete?.();
|
|
1542
1558
|
}
|
|
1543
1559
|
return { ok: true, message: "daemon stopped" };
|
|
1544
1560
|
case "daemon.restart": {
|
|
@@ -1559,7 +1575,7 @@ class OuroDaemon {
|
|
|
1559
1575
|
await this.stop();
|
|
1560
1576
|
}
|
|
1561
1577
|
finally {
|
|
1562
|
-
this.onStopCommandComplete?.();
|
|
1578
|
+
await this.onStopCommandComplete?.();
|
|
1563
1579
|
}
|
|
1564
1580
|
return {
|
|
1565
1581
|
ok: true,
|
|
@@ -1872,6 +1888,26 @@ class OuroDaemon {
|
|
|
1872
1888
|
data: receipt,
|
|
1873
1889
|
};
|
|
1874
1890
|
}
|
|
1891
|
+
case "habit.scheduler-fire": {
|
|
1892
|
+
if (!this.schedulerFireVerifier || !this.schedulerFireConsumer)
|
|
1893
|
+
return { ok: false, error: "authenticated scheduler fire is unavailable" };
|
|
1894
|
+
let origin;
|
|
1895
|
+
try {
|
|
1896
|
+
origin = this.schedulerFireVerifier(command);
|
|
1897
|
+
this.schedulerFireConsumer(origin);
|
|
1898
|
+
}
|
|
1899
|
+
catch (error) {
|
|
1900
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
1901
|
+
}
|
|
1902
|
+
const poke = { kind: "habit.poke", agent: command.agent, habitName: command.habitName, trigger: "cron", occurrenceId: origin.occurrenceId };
|
|
1903
|
+
this.authenticatedSchedulerPokes.set(poke, origin);
|
|
1904
|
+
try {
|
|
1905
|
+
return await this.handleCommand(poke);
|
|
1906
|
+
}
|
|
1907
|
+
finally {
|
|
1908
|
+
this.authenticatedSchedulerPokes.delete(poke);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1875
1911
|
case "habit.poke": {
|
|
1876
1912
|
if (!isValidHabitPokeCommand(command)) {
|
|
1877
1913
|
return {
|
|
@@ -1883,6 +1919,10 @@ class OuroDaemon {
|
|
|
1883
1919
|
if (!(0, flight_recorder_1.isHabitRunTrigger)(trigger)) {
|
|
1884
1920
|
return { ok: false, error: `invalid habit trigger: ${String(trigger)}` };
|
|
1885
1921
|
}
|
|
1922
|
+
const schedulerOrigin = this.authenticatedSchedulerPokes.get(command);
|
|
1923
|
+
if (command.agent === "sanctuary" && command.habitName === "sanctuary-health" && trigger === "cron" && !schedulerOrigin) {
|
|
1924
|
+
return { ok: false, error: "Sanctuary cron habit requires authenticated Supercronic provenance" };
|
|
1925
|
+
}
|
|
1886
1926
|
if (!(0, habit_policy_1.isRsvpHabitName)(command.habitName) && !this.hasManagedPrivateRuntime(command.agent)) {
|
|
1887
1927
|
return {
|
|
1888
1928
|
ok: false,
|
|
@@ -1915,6 +1955,53 @@ class OuroDaemon {
|
|
|
1915
1955
|
trigger,
|
|
1916
1956
|
resolution,
|
|
1917
1957
|
});
|
|
1958
|
+
if (this.nativeHabitRunner && this.nativeHabitMatch?.(command.agent, command.habitName)) {
|
|
1959
|
+
const occurrenceId = resolution.occurrenceId ?? `${trigger}:${command.agent}:${command.habitName}`;
|
|
1960
|
+
const claimKey = `${command.agent}:${command.habitName}`;
|
|
1961
|
+
if (this.nativeHabitClaims.has(claimKey)) {
|
|
1962
|
+
return { ok: true, message: `skipped overlapping native habit occurrence ${occurrenceId}` };
|
|
1963
|
+
}
|
|
1964
|
+
this.nativeHabitClaims.add(claimKey);
|
|
1965
|
+
const startedAt = new Date().toISOString();
|
|
1966
|
+
const runnerId = (0, node_crypto_1.randomUUID)();
|
|
1967
|
+
let nativeResult;
|
|
1968
|
+
try {
|
|
1969
|
+
nativeResult = await this.nativeHabitRunner({
|
|
1970
|
+
agent: command.agent,
|
|
1971
|
+
habitName: command.habitName,
|
|
1972
|
+
trigger,
|
|
1973
|
+
occurrenceId,
|
|
1974
|
+
runnerId,
|
|
1975
|
+
...(schedulerOrigin ? { schedulerOrigin } : {}),
|
|
1976
|
+
}) ?? { ok: false, error: "native habit runner declined a matched habit" };
|
|
1977
|
+
}
|
|
1978
|
+
catch (error) {
|
|
1979
|
+
nativeResult = { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
1980
|
+
}
|
|
1981
|
+
const endedAt = new Date().toISOString();
|
|
1982
|
+
let completion;
|
|
1983
|
+
try {
|
|
1984
|
+
completion = (0, habit_session_1.completeHabitRun)({
|
|
1985
|
+
agentRoot: path.join(this.bundlesRoot, `${command.agent}.ouro`),
|
|
1986
|
+
habit: resolution.habit,
|
|
1987
|
+
runId: runnerId,
|
|
1988
|
+
trigger,
|
|
1989
|
+
startedAt,
|
|
1990
|
+
endedAt,
|
|
1991
|
+
operationId: occurrenceId,
|
|
1992
|
+
permissionEnvelope: { schemaVersion: 1, canMessageOutward: true, returnRoutes: [], deniedTools: [], warnings: [] },
|
|
1993
|
+
toolPolicy: { requestedTools: null, grantedTools: [], deniedTools: [], outwardMessagingAllowed: true },
|
|
1994
|
+
...(nativeResult.ok ? {} : { errors: [nativeResult.error ?? nativeResult.message ?? "native habit failed"] }),
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
finally {
|
|
1998
|
+
this.nativeHabitClaims.delete(claimKey);
|
|
1999
|
+
}
|
|
2000
|
+
if (!completion.receiptWritten || !completion.runtimeStateRecorded) {
|
|
2001
|
+
return { ok: false, error: "native habit completed but its durable receipt or cursor could not be recorded", data: nativeResult };
|
|
2002
|
+
}
|
|
2003
|
+
return nativeResult;
|
|
2004
|
+
}
|
|
1918
2005
|
if ((0, habit_policy_1.isRsvpHabitName)(command.habitName)) {
|
|
1919
2006
|
return this.runNativeRsvpHabit(command, trigger, resolution.occurrenceId);
|
|
1920
2007
|
}
|
|
@@ -35,6 +35,7 @@ const nerves_1 = require("../../nerves");
|
|
|
35
35
|
const config_1 = require("../../rsvp/config");
|
|
36
36
|
const cutover_1 = require("../../rsvp/cutover");
|
|
37
37
|
const diagnostics_1 = require("../../rsvp/diagnostics");
|
|
38
|
+
const blob_store_1 = require("../../mailroom/blob-store");
|
|
38
39
|
const hosted_cache_sync_1 = require("../../mailroom/hosted-cache-sync");
|
|
39
40
|
const search_cache_1 = require("../../mailroom/search-cache");
|
|
40
41
|
const prunable_bundle_1 = require("./prunable-bundle");
|
|
@@ -1137,27 +1138,73 @@ async function hostedMailCacheAuthorityCheck(deps, agentDir, store) {
|
|
|
1137
1138
|
const documentsById = new Map();
|
|
1138
1139
|
let malformedOrNoncanonical = 0;
|
|
1139
1140
|
for (const fileName of fileNames) {
|
|
1140
|
-
const
|
|
1141
|
+
const filePath = `${cacheDir}/${fileName}`;
|
|
1142
|
+
let regularFile = false;
|
|
1143
|
+
try {
|
|
1144
|
+
const fileStat = deps.lstatSync?.(filePath);
|
|
1145
|
+
regularFile = fileStat?.isFile?.() === true && !fileStat.isSymbolicLink();
|
|
1146
|
+
}
|
|
1147
|
+
catch {
|
|
1148
|
+
// A disappearing or unreadable entry is not usable cache evidence.
|
|
1149
|
+
}
|
|
1150
|
+
if (!regularFile) {
|
|
1151
|
+
malformedOrNoncanonical += 1;
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
const document = parseCacheDocument(deps, filePath);
|
|
1141
1155
|
if (!document || document.agentId !== agentName || fileName !== `${document.messageId}.json` || !recordsById.has(document.messageId)) {
|
|
1142
1156
|
malformedOrNoncanonical += 1;
|
|
1143
1157
|
continue;
|
|
1144
1158
|
}
|
|
1145
1159
|
documentsById.set(document.messageId, document);
|
|
1146
1160
|
}
|
|
1161
|
+
const receiptsById = new Map();
|
|
1162
|
+
let invalidReceipts = 0;
|
|
1163
|
+
try {
|
|
1164
|
+
for (const inspected of (0, search_cache_1.inspectMailSearchSkipReceipts)(agentName, { cacheDirForAgent: () => cacheDir })) {
|
|
1165
|
+
const receipt = inspected.receipt;
|
|
1166
|
+
const record = receipt ? recordsById.get(receipt.messageId) : undefined;
|
|
1167
|
+
const currentFingerprint = record
|
|
1168
|
+
? (0, blob_store_1.canonicalMailIndexRecordSnapshot)([record]).messageIndexFingerprint
|
|
1169
|
+
: null;
|
|
1170
|
+
if (!receipt ||
|
|
1171
|
+
!inspected.canonical ||
|
|
1172
|
+
!record ||
|
|
1173
|
+
documentsById.has(receipt.messageId) ||
|
|
1174
|
+
receipt.recordFingerprint !== currentFingerprint ||
|
|
1175
|
+
store.privateKeyIds.has(receipt.missingKeyId)) {
|
|
1176
|
+
invalidReceipts += 1;
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
receiptsById.set(receipt.messageId, receipt.missingKeyId);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
catch (error) {
|
|
1183
|
+
return {
|
|
1184
|
+
...base,
|
|
1185
|
+
status: "warn",
|
|
1186
|
+
detail: `hosted authority is sound but local skip receipts are unreadable: ${String(error)}; run \`ouro mail sync-cache --agent ${agentName}\``,
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1147
1189
|
let missing = 0;
|
|
1190
|
+
let receiptedMissing = 0;
|
|
1148
1191
|
let stale = 0;
|
|
1149
1192
|
let invalidKeyProvenance = 0;
|
|
1150
1193
|
for (const record of observation.records) {
|
|
1151
1194
|
const document = documentsById.get(record.id);
|
|
1152
1195
|
if (!document) {
|
|
1153
|
-
|
|
1196
|
+
if (receiptsById.has(record.id))
|
|
1197
|
+
receiptedMissing += 1;
|
|
1198
|
+
else
|
|
1199
|
+
missing += 1;
|
|
1154
1200
|
continue;
|
|
1155
1201
|
}
|
|
1156
1202
|
if (!(0, hosted_cache_sync_1.mailSearchCacheDocumentMatchesRecord)(document, record, true)) {
|
|
1157
1203
|
stale += 1;
|
|
1158
1204
|
continue;
|
|
1159
1205
|
}
|
|
1160
|
-
if (
|
|
1206
|
+
if (document.bodyForm === "encrypted" &&
|
|
1207
|
+
(!document.decryptionKeyId || !store.privateKeyIds.has(document.decryptionKeyId))) {
|
|
1161
1208
|
invalidKeyProvenance += 1;
|
|
1162
1209
|
}
|
|
1163
1210
|
}
|
|
@@ -1175,21 +1222,23 @@ async function hostedMailCacheAuthorityCheck(deps, agentDir, store) {
|
|
|
1175
1222
|
&& coverage.skippedMessageCount === observation.snapshot.visibleMessageCount - expectedCachedCount;
|
|
1176
1223
|
if (coverageMatches
|
|
1177
1224
|
&& coverage.skippedMessageCount > 0
|
|
1178
|
-
&&
|
|
1225
|
+
&& receiptedMissing === coverage.skippedMessageCount
|
|
1226
|
+
&& missing === 0
|
|
1179
1227
|
&& stale === 0
|
|
1180
1228
|
&& malformedOrNoncanonical === 0
|
|
1229
|
+
&& invalidReceipts === 0
|
|
1181
1230
|
&& invalidKeyProvenance === 0) {
|
|
1182
1231
|
return {
|
|
1183
1232
|
...base,
|
|
1184
1233
|
status: "warn",
|
|
1185
|
-
detail: `hosted authority is sound; the last completed sync skipped ${
|
|
1234
|
+
detail: `hosted authority is sound; the last completed sync skipped ${receiptedMissing} hosted message(s) with current per-message missing-key receipts, so local search remains incomplete; retry only if conditions have changed since that sync`,
|
|
1186
1235
|
};
|
|
1187
1236
|
}
|
|
1188
|
-
if (malformedOrNoncanonical > 0 || missing > 0 || stale > 0 || invalidKeyProvenance > 0 || !coverageMatches) {
|
|
1237
|
+
if (malformedOrNoncanonical > 0 || missing > 0 || stale > 0 || invalidReceipts > 0 || invalidKeyProvenance > 0 || !coverageMatches) {
|
|
1189
1238
|
return {
|
|
1190
1239
|
...base,
|
|
1191
1240
|
status: "warn",
|
|
1192
|
-
detail: `hosted authority is sound but cache diverges: ${missing + stale} missing/stale canonical document(s), ${malformedOrNoncanonical} malformed/orphan/noncanonical/duplicate file(s), ${invalidKeyProvenance} invalid key provenance document(s), all-scope coverage ${coverageMatches ? "matches" : "missing/malformed/stale"}; run \`ouro mail sync-cache --agent ${agentName}\``,
|
|
1241
|
+
detail: `hosted authority is sound but cache diverges: ${missing + stale} unreceipted-missing/stale canonical document(s), ${malformedOrNoncanonical} malformed/orphan/noncanonical/duplicate cache file(s), ${invalidReceipts} malformed/orphan/stale/recoverable skip receipt(s), ${invalidKeyProvenance} invalid key provenance document(s), all-scope coverage ${coverageMatches ? "matches" : "missing/malformed/stale"}; run \`ouro mail sync-cache --agent ${agentName}\``,
|
|
1193
1242
|
};
|
|
1194
1243
|
}
|
|
1195
1244
|
const count = observation.records.length;
|
|
@@ -52,6 +52,12 @@ function defaultLoggingForProcess(processName) {
|
|
|
52
52
|
sinks: ["ndjson"],
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
+
if (processName === "telegram") {
|
|
56
|
+
return {
|
|
57
|
+
level: "info",
|
|
58
|
+
sinks: ["terminal", "ndjson"],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
55
61
|
if (processName === "bluebubbles" || processName === "mail" || processName === "voice" || processName === "a2a") {
|
|
56
62
|
return {
|
|
57
63
|
level: "warn",
|
|
@@ -50,11 +50,22 @@ function detectRuntimeMode(rootPath, deps = {}) {
|
|
|
50
50
|
});
|
|
51
51
|
return "production";
|
|
52
52
|
}
|
|
53
|
+
const isWorktree = rootPath.includes(".claude/worktrees/");
|
|
54
|
+
const isGitRepo = checkExists(path.join(rootPath, ".git"));
|
|
55
|
+
if (!isWorktree && !isGitRepo && checkExists(path.join(rootPath, "container-runtime.json"))) {
|
|
56
|
+
(0, runtime_1.emitNervesEvent)({
|
|
57
|
+
component: "daemon",
|
|
58
|
+
event: "daemon.runtime_mode_detected",
|
|
59
|
+
message: "detected runtime mode",
|
|
60
|
+
meta: { rootPath, mode: "production", reason: "container-runtime" },
|
|
61
|
+
});
|
|
62
|
+
return "production";
|
|
63
|
+
}
|
|
53
64
|
// 2-4. Everything else is dev: worktrees, git repos, unknown paths
|
|
54
65
|
// (conservative default: assume dev unless proven production)
|
|
55
|
-
const reason =
|
|
66
|
+
const reason = isWorktree
|
|
56
67
|
? "worktree"
|
|
57
|
-
:
|
|
68
|
+
: isGitRepo
|
|
58
69
|
? "git-repo"
|
|
59
70
|
: "unknown";
|
|
60
71
|
(0, runtime_1.emitNervesEvent)({
|