@ouro.bot/cli 0.1.0-alpha.2 → 0.1.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/assets/ouroboros.png +0 -0
  5. package/dist/heart/config.js +66 -4
  6. package/dist/heart/core.js +75 -2
  7. package/dist/heart/daemon/daemon-cli.js +507 -29
  8. package/dist/heart/daemon/daemon-entry.js +13 -5
  9. package/dist/heart/daemon/daemon.js +42 -9
  10. package/dist/heart/daemon/hatch-animation.js +35 -0
  11. package/dist/heart/daemon/hatch-flow.js +2 -11
  12. package/dist/heart/daemon/hatch-specialist.js +6 -1
  13. package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
  14. package/dist/heart/daemon/ouro-path-installer.js +177 -0
  15. package/dist/heart/daemon/ouro-uti.js +11 -2
  16. package/dist/heart/daemon/process-manager.js +1 -1
  17. package/dist/heart/daemon/runtime-logging.js +9 -5
  18. package/dist/heart/daemon/runtime-metadata.js +118 -0
  19. package/dist/heart/daemon/sense-manager.js +266 -0
  20. package/dist/heart/daemon/specialist-orchestrator.js +129 -0
  21. package/dist/heart/daemon/specialist-prompt.js +98 -0
  22. package/dist/heart/daemon/specialist-tools.js +237 -0
  23. package/dist/heart/daemon/subagent-installer.js +10 -1
  24. package/dist/heart/identity.js +77 -1
  25. package/dist/heart/providers/anthropic.js +19 -2
  26. package/dist/heart/sense-truth.js +61 -0
  27. package/dist/heart/streaming.js +99 -21
  28. package/dist/mind/bundle-manifest.js +58 -0
  29. package/dist/mind/friends/channel.js +8 -0
  30. package/dist/mind/friends/types.js +1 -1
  31. package/dist/mind/prompt.js +77 -3
  32. package/dist/nerves/cli-logging.js +15 -2
  33. package/dist/repertoire/ado-client.js +4 -2
  34. package/dist/repertoire/coding/feedback.js +134 -0
  35. package/dist/repertoire/coding/index.js +4 -1
  36. package/dist/repertoire/coding/manager.js +61 -2
  37. package/dist/repertoire/coding/spawner.js +3 -3
  38. package/dist/repertoire/coding/tools.js +41 -2
  39. package/dist/repertoire/data/ado-endpoints.json +188 -0
  40. package/dist/repertoire/tools-base.js +69 -5
  41. package/dist/repertoire/tools-teams.js +57 -4
  42. package/dist/repertoire/tools.js +44 -11
  43. package/dist/senses/bluebubbles-client.js +433 -0
  44. package/dist/senses/bluebubbles-entry.js +11 -0
  45. package/dist/senses/bluebubbles-media.js +244 -0
  46. package/dist/senses/bluebubbles-model.js +253 -0
  47. package/dist/senses/bluebubbles-mutation-log.js +76 -0
  48. package/dist/senses/bluebubbles.js +421 -0
  49. package/dist/senses/cli.js +293 -133
  50. package/dist/senses/debug-activity.js +107 -0
  51. package/dist/senses/teams.js +173 -54
  52. package/package.json +11 -4
  53. package/subagents/work-doer.md +26 -24
  54. package/subagents/work-merger.md +24 -30
  55. package/subagents/work-planner.md +34 -25
  56. package/dist/inner-worker-entry.js +0 -4
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ensureDaemonRunning = ensureDaemonRunning;
37
37
  exports.parseOuroCommand = parseOuroCommand;
38
+ exports.discoverExistingCredentials = discoverExistingCredentials;
38
39
  exports.createDefaultOuroCliDeps = createDefaultOuroCliDeps;
39
40
  exports.runOuroCli = runOuroCli;
40
41
  const child_process_1 = require("child_process");
@@ -47,8 +48,150 @@ const runtime_1 = require("../../nerves/runtime");
47
48
  const store_file_1 = require("../../mind/friends/store-file");
48
49
  const types_1 = require("../../mind/friends/types");
49
50
  const ouro_uti_1 = require("./ouro-uti");
51
+ const ouro_path_installer_1 = require("./ouro-path-installer");
50
52
  const subagent_installer_1 = require("./subagent-installer");
51
53
  const hatch_flow_1 = require("./hatch-flow");
54
+ const specialist_orchestrator_1 = require("./specialist-orchestrator");
55
+ const specialist_prompt_1 = require("./specialist-prompt");
56
+ const specialist_tools_1 = require("./specialist-tools");
57
+ const runtime_metadata_1 = require("./runtime-metadata");
58
+ function stringField(value) {
59
+ return typeof value === "string" ? value : null;
60
+ }
61
+ function numberField(value) {
62
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
63
+ }
64
+ function booleanField(value) {
65
+ return typeof value === "boolean" ? value : null;
66
+ }
67
+ function parseStatusPayload(data) {
68
+ if (!data || typeof data !== "object" || Array.isArray(data))
69
+ return null;
70
+ const raw = data;
71
+ const overview = raw.overview;
72
+ const senses = raw.senses;
73
+ const workers = raw.workers;
74
+ if (!overview || typeof overview !== "object" || Array.isArray(overview))
75
+ return null;
76
+ if (!Array.isArray(senses) || !Array.isArray(workers))
77
+ return null;
78
+ const parsedOverview = {
79
+ daemon: stringField(overview.daemon) ?? "unknown",
80
+ health: stringField(overview.health) ?? "unknown",
81
+ socketPath: stringField(overview.socketPath) ?? "unknown",
82
+ version: stringField(overview.version) ?? "unknown",
83
+ lastUpdated: stringField(overview.lastUpdated) ?? "unknown",
84
+ workerCount: numberField(overview.workerCount) ?? 0,
85
+ senseCount: numberField(overview.senseCount) ?? 0,
86
+ };
87
+ const parsedSenses = senses.map((entry) => {
88
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
89
+ return null;
90
+ const row = entry;
91
+ const agent = stringField(row.agent);
92
+ const sense = stringField(row.sense);
93
+ const status = stringField(row.status);
94
+ const detail = stringField(row.detail);
95
+ const enabled = booleanField(row.enabled);
96
+ if (!agent || !sense || !status || detail === null || enabled === null)
97
+ return null;
98
+ return {
99
+ agent,
100
+ sense,
101
+ label: stringField(row.label) ?? undefined,
102
+ enabled,
103
+ status,
104
+ detail,
105
+ };
106
+ });
107
+ const parsedWorkers = workers.map((entry) => {
108
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
109
+ return null;
110
+ const row = entry;
111
+ const agent = stringField(row.agent);
112
+ const worker = stringField(row.worker);
113
+ const status = stringField(row.status);
114
+ const restartCount = numberField(row.restartCount);
115
+ const hasPid = Object.prototype.hasOwnProperty.call(row, "pid");
116
+ const pid = row.pid === null ? null : numberField(row.pid);
117
+ const pidInvalid = !hasPid || (row.pid !== null && pid === null);
118
+ if (!agent || !worker || !status || restartCount === null || pidInvalid)
119
+ return null;
120
+ return {
121
+ agent,
122
+ worker,
123
+ status,
124
+ pid,
125
+ restartCount,
126
+ };
127
+ });
128
+ if (parsedSenses.some((row) => row === null) || parsedWorkers.some((row) => row === null))
129
+ return null;
130
+ return {
131
+ overview: parsedOverview,
132
+ senses: parsedSenses,
133
+ workers: parsedWorkers,
134
+ };
135
+ }
136
+ function humanizeSenseName(sense, label) {
137
+ if (label)
138
+ return label;
139
+ if (sense === "cli")
140
+ return "CLI";
141
+ if (sense === "bluebubbles")
142
+ return "BlueBubbles";
143
+ if (sense === "teams")
144
+ return "Teams";
145
+ return sense;
146
+ }
147
+ function formatTable(headers, rows) {
148
+ const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index].length)));
149
+ const renderRow = (row) => `| ${row.map((cell, index) => cell.padEnd(widths[index])).join(" | ")} |`;
150
+ const divider = `|-${widths.map((width) => "-".repeat(width)).join("-|-")}-|`;
151
+ return [
152
+ renderRow(headers),
153
+ divider,
154
+ ...rows.map(renderRow),
155
+ ].join("\n");
156
+ }
157
+ function formatDaemonStatusOutput(response, fallback) {
158
+ const payload = parseStatusPayload(response.data);
159
+ if (!payload)
160
+ return fallback;
161
+ const overviewRows = [
162
+ ["Daemon", payload.overview.daemon],
163
+ ["Socket", payload.overview.socketPath],
164
+ ["Version", payload.overview.version],
165
+ ["Last Updated", payload.overview.lastUpdated],
166
+ ["Workers", String(payload.overview.workerCount)],
167
+ ["Senses", String(payload.overview.senseCount)],
168
+ ["Health", payload.overview.health],
169
+ ];
170
+ const senseRows = payload.senses.map((row) => [
171
+ row.agent,
172
+ humanizeSenseName(row.sense, row.label),
173
+ row.enabled ? "ON" : "OFF",
174
+ row.status,
175
+ row.detail,
176
+ ]);
177
+ const workerRows = payload.workers.map((row) => [
178
+ row.agent,
179
+ row.worker,
180
+ row.status,
181
+ row.pid === null ? "n/a" : String(row.pid),
182
+ String(row.restartCount),
183
+ ]);
184
+ return [
185
+ "Overview",
186
+ formatTable(["Item", "Value"], overviewRows),
187
+ "",
188
+ "Senses",
189
+ formatTable(["Agent", "Sense", "Enabled", "State", "Detail"], senseRows),
190
+ "",
191
+ "Workers",
192
+ formatTable(["Agent", "Worker", "State", "PID", "Restarts"], workerRows),
193
+ ].join("\n");
194
+ }
52
195
  async function ensureDaemonRunning(deps) {
53
196
  const alive = await deps.checkSocketAlive(deps.socketPath);
54
197
  if (alive) {
@@ -69,12 +212,49 @@ function usage() {
69
212
  "Usage:",
70
213
  " ouro [up]",
71
214
  " ouro stop|status|logs|hatch",
215
+ " ouro -v|--version",
72
216
  " ouro chat <agent>",
73
217
  " ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
74
218
  " ouro poke <agent> --task <task-id>",
75
219
  " ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
76
220
  ].join("\n");
77
221
  }
222
+ function formatVersionOutput() {
223
+ return (0, runtime_metadata_1.getRuntimeMetadata)().version;
224
+ }
225
+ function buildStoppedStatusPayload(socketPath) {
226
+ const metadata = (0, runtime_metadata_1.getRuntimeMetadata)();
227
+ return {
228
+ overview: {
229
+ daemon: "stopped",
230
+ health: "warn",
231
+ socketPath,
232
+ version: metadata.version,
233
+ lastUpdated: metadata.lastUpdated,
234
+ workerCount: 0,
235
+ senseCount: 0,
236
+ },
237
+ senses: [],
238
+ workers: [],
239
+ };
240
+ }
241
+ function daemonUnavailableStatusOutput(socketPath) {
242
+ return [
243
+ formatDaemonStatusOutput({
244
+ ok: true,
245
+ summary: "daemon not running",
246
+ data: buildStoppedStatusPayload(socketPath),
247
+ }, "daemon not running"),
248
+ "",
249
+ "daemon not running; run `ouro up`",
250
+ ].join("\n");
251
+ }
252
+ function isDaemonUnavailableError(error) {
253
+ const code = typeof error === "object" && error !== null && "code" in error
254
+ ? String(error.code ?? "")
255
+ : "";
256
+ return code === "ENOENT" || code === "ECONNREFUSED";
257
+ }
78
258
  function parseMessageCommand(args) {
79
259
  let to;
80
260
  let sessionId;
@@ -433,7 +613,8 @@ function defaultListDiscoveredAgents() {
433
613
  return discovered.sort((left, right) => left.localeCompare(right));
434
614
  }
435
615
  async function defaultLinkFriendIdentity(command) {
436
- const friendStore = new store_file_1.FileFriendStore(path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`, "friends"));
616
+ const fp = path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`, "friends");
617
+ const friendStore = new store_file_1.FileFriendStore(fp);
437
618
  const current = await friendStore.get(command.friendId);
438
619
  if (!current) {
439
620
  return `friend not found: ${command.friendId}`;
@@ -457,6 +638,247 @@ async function defaultLinkFriendIdentity(command) {
457
638
  });
458
639
  return `linked ${command.provider}:${command.externalId} to ${command.friendId}`;
459
640
  }
641
+ function discoverExistingCredentials(secretsRoot) {
642
+ const found = [];
643
+ let entries;
644
+ try {
645
+ entries = fs.readdirSync(secretsRoot, { withFileTypes: true });
646
+ }
647
+ catch {
648
+ return found;
649
+ }
650
+ for (const entry of entries) {
651
+ if (!entry.isDirectory())
652
+ continue;
653
+ const secretsPath = path.join(secretsRoot, entry.name, "secrets.json");
654
+ let raw;
655
+ try {
656
+ raw = fs.readFileSync(secretsPath, "utf-8");
657
+ }
658
+ catch {
659
+ continue;
660
+ }
661
+ let parsed;
662
+ try {
663
+ parsed = JSON.parse(raw);
664
+ }
665
+ catch {
666
+ continue;
667
+ }
668
+ if (!parsed.providers)
669
+ continue;
670
+ for (const [provName, provConfig] of Object.entries(parsed.providers)) {
671
+ if (provName === "anthropic" && provConfig.setupToken) {
672
+ found.push({ agentName: entry.name, provider: "anthropic", credentials: { setupToken: provConfig.setupToken }, providerConfig: { ...provConfig } });
673
+ }
674
+ else if (provName === "openai-codex" && provConfig.oauthAccessToken) {
675
+ found.push({ agentName: entry.name, provider: "openai-codex", credentials: { oauthAccessToken: provConfig.oauthAccessToken }, providerConfig: { ...provConfig } });
676
+ }
677
+ else if (provName === "minimax" && provConfig.apiKey) {
678
+ found.push({ agentName: entry.name, provider: "minimax", credentials: { apiKey: provConfig.apiKey }, providerConfig: { ...provConfig } });
679
+ }
680
+ else if (provName === "azure" && provConfig.apiKey && provConfig.endpoint && provConfig.deployment) {
681
+ found.push({ agentName: entry.name, provider: "azure", credentials: { apiKey: provConfig.apiKey, endpoint: provConfig.endpoint, deployment: provConfig.deployment }, providerConfig: { ...provConfig } });
682
+ }
683
+ }
684
+ }
685
+ // Deduplicate by provider+credential value (keep first seen)
686
+ const seen = new Set();
687
+ return found.filter((cred) => {
688
+ const key = `${cred.provider}:${JSON.stringify(cred.credentials)}`;
689
+ if (seen.has(key))
690
+ return false;
691
+ seen.add(key);
692
+ return true;
693
+ });
694
+ }
695
+ /* v8 ignore start -- integration: interactive terminal specialist session @preserve */
696
+ async function defaultRunAdoptionSpecialist() {
697
+ const { runCliSession } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
698
+ const { patchRuntimeConfig } = await Promise.resolve().then(() => __importStar(require("../config")));
699
+ const { setAgentName, setAgentConfigOverride } = await Promise.resolve().then(() => __importStar(require("../identity")));
700
+ const readlinePromises = await Promise.resolve().then(() => __importStar(require("readline/promises")));
701
+ const crypto = await Promise.resolve().then(() => __importStar(require("crypto")));
702
+ // Phase 1: cold CLI — collect provider/credentials with a simple readline
703
+ const coldRl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
704
+ const coldPrompt = async (q) => {
705
+ const answer = await coldRl.question(q);
706
+ return answer.trim();
707
+ };
708
+ let providerRaw;
709
+ let credentials = {};
710
+ let providerConfig = {};
711
+ const tempDir = path.join(os.tmpdir(), `ouro-hatch-${crypto.randomUUID()}`);
712
+ try {
713
+ const secretsRoot = path.join(os.homedir(), ".agentsecrets");
714
+ const discovered = discoverExistingCredentials(secretsRoot);
715
+ const existingBundleCount = (0, specialist_orchestrator_1.listExistingBundles)((0, identity_1.getAgentBundlesRoot)()).length;
716
+ const hatchVerb = existingBundleCount > 0 ? "let's hatch a new agent." : "let's hatch your first agent.";
717
+ // Default models per provider (used when entering new credentials)
718
+ const defaultModels = {
719
+ anthropic: "claude-opus-4-6",
720
+ minimax: "MiniMax-Text-01",
721
+ "openai-codex": "gpt-5.4",
722
+ azure: "",
723
+ };
724
+ if (discovered.length > 0) {
725
+ process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
726
+ process.stdout.write("i found existing API credentials:\n\n");
727
+ const unique = [...new Map(discovered.map((d) => [`${d.provider}`, d])).values()];
728
+ for (let i = 0; i < unique.length; i++) {
729
+ const model = unique[i].providerConfig.model || unique[i].providerConfig.deployment || "";
730
+ const modelLabel = model ? `, ${model}` : "";
731
+ process.stdout.write(` ${i + 1}. ${unique[i].provider}${modelLabel} (from ${unique[i].agentName})\n`);
732
+ }
733
+ process.stdout.write("\n");
734
+ const choice = await coldPrompt("use one of these? enter number, or 'new' for a different key: ");
735
+ const idx = parseInt(choice, 10) - 1;
736
+ if (idx >= 0 && idx < unique.length) {
737
+ providerRaw = unique[idx].provider;
738
+ credentials = unique[idx].credentials;
739
+ providerConfig = unique[idx].providerConfig;
740
+ }
741
+ else {
742
+ const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
743
+ if (!isAgentProvider(pRaw)) {
744
+ process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
745
+ coldRl.close();
746
+ return null;
747
+ }
748
+ providerRaw = pRaw;
749
+ providerConfig = { model: defaultModels[providerRaw] };
750
+ if (providerRaw === "anthropic")
751
+ credentials.setupToken = await coldPrompt("API key: ");
752
+ if (providerRaw === "openai-codex")
753
+ credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
754
+ if (providerRaw === "minimax")
755
+ credentials.apiKey = await coldPrompt("API key: ");
756
+ if (providerRaw === "azure") {
757
+ credentials.apiKey = await coldPrompt("API key: ");
758
+ credentials.endpoint = await coldPrompt("endpoint: ");
759
+ credentials.deployment = await coldPrompt("deployment: ");
760
+ }
761
+ }
762
+ }
763
+ else {
764
+ process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
765
+ process.stdout.write("i need an API key to power our conversation.\n\n");
766
+ const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
767
+ if (!isAgentProvider(pRaw)) {
768
+ process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
769
+ coldRl.close();
770
+ return null;
771
+ }
772
+ providerRaw = pRaw;
773
+ providerConfig = { model: defaultModels[providerRaw] };
774
+ if (providerRaw === "anthropic")
775
+ credentials.setupToken = await coldPrompt("API key: ");
776
+ if (providerRaw === "openai-codex")
777
+ credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
778
+ if (providerRaw === "minimax")
779
+ credentials.apiKey = await coldPrompt("API key: ");
780
+ if (providerRaw === "azure") {
781
+ credentials.apiKey = await coldPrompt("API key: ");
782
+ credentials.endpoint = await coldPrompt("endpoint: ");
783
+ credentials.deployment = await coldPrompt("deployment: ");
784
+ }
785
+ }
786
+ coldRl.close();
787
+ process.stdout.write("\n");
788
+ // Phase 2: configure runtime for adoption specialist
789
+ const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
790
+ const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
791
+ const secretsRoot2 = path.join(os.homedir(), ".agentsecrets");
792
+ // Suppress non-critical log noise during adoption (no secrets.json, etc.)
793
+ const { setRuntimeLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
794
+ const { createLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves")));
795
+ setRuntimeLogger(createLogger({ level: "error" }));
796
+ // Configure runtime: set agent identity + config override so runAgent
797
+ // doesn't try to read from ~/AgentBundles/AdoptionSpecialist.ouro/
798
+ setAgentName("AdoptionSpecialist");
799
+ // Build specialist system prompt
800
+ const soulText = (0, specialist_orchestrator_1.loadSoulText)(bundleSourceDir);
801
+ const identitiesDir = path.join(bundleSourceDir, "psyche", "identities");
802
+ const identity = (0, specialist_orchestrator_1.pickRandomIdentity)(identitiesDir);
803
+ // Load identity-specific spinner phrases (falls back to DEFAULT_AGENT_PHRASES)
804
+ const { loadIdentityPhrases } = await Promise.resolve().then(() => __importStar(require("./specialist-orchestrator")));
805
+ const phrases = loadIdentityPhrases(bundleSourceDir, identity.fileName);
806
+ setAgentConfigOverride({
807
+ version: 1,
808
+ enabled: true,
809
+ provider: providerRaw,
810
+ phrases,
811
+ });
812
+ patchRuntimeConfig({
813
+ providers: {
814
+ [providerRaw]: { ...providerConfig, ...credentials },
815
+ },
816
+ });
817
+ const existingBundles = (0, specialist_orchestrator_1.listExistingBundles)(bundlesRoot);
818
+ const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles, {
819
+ tempDir,
820
+ provider: providerRaw,
821
+ });
822
+ // Build specialist tools
823
+ const specialistTools = (0, specialist_tools_1.getSpecialistTools)();
824
+ const specialistExecTool = (0, specialist_tools_1.createSpecialistExecTool)({
825
+ tempDir,
826
+ credentials,
827
+ provider: providerRaw,
828
+ bundlesRoot,
829
+ secretsRoot: secretsRoot2,
830
+ animationWriter: (text) => process.stdout.write(text),
831
+ });
832
+ // Run the adoption specialist session via runCliSession
833
+ const result = await runCliSession({
834
+ agentName: "AdoptionSpecialist",
835
+ tools: specialistTools,
836
+ execTool: specialistExecTool,
837
+ exitOnToolCall: "complete_adoption",
838
+ autoFirstTurn: true,
839
+ banner: false,
840
+ disableCommands: true,
841
+ skipSystemPromptRefresh: true,
842
+ messages: [
843
+ { role: "system", content: systemPrompt },
844
+ { role: "user", content: "hi" },
845
+ ],
846
+ });
847
+ if (result.exitReason === "tool_exit" && result.toolResult) {
848
+ const parsed = typeof result.toolResult === "string" ? JSON.parse(result.toolResult) : result.toolResult;
849
+ if (parsed.success && parsed.agentName) {
850
+ return parsed.agentName;
851
+ }
852
+ }
853
+ return null;
854
+ }
855
+ catch (err) {
856
+ process.stderr.write(`\nouro adoption error: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
857
+ coldRl.close();
858
+ return null;
859
+ }
860
+ finally {
861
+ // Clear specialist config/identity so the hatched agent gets its own
862
+ setAgentConfigOverride(null);
863
+ const { resetProviderRuntime } = await Promise.resolve().then(() => __importStar(require("../core")));
864
+ resetProviderRuntime();
865
+ const { resetConfigCache } = await Promise.resolve().then(() => __importStar(require("../config")));
866
+ resetConfigCache();
867
+ // Restore default logging
868
+ const { setRuntimeLogger: restoreLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
869
+ restoreLogger(null);
870
+ // Clean up temp dir if it still exists
871
+ try {
872
+ if (fs.existsSync(tempDir)) {
873
+ fs.rmSync(tempDir, { recursive: true, force: true });
874
+ }
875
+ }
876
+ catch {
877
+ // Best effort cleanup
878
+ }
879
+ }
880
+ }
881
+ /* v8 ignore stop */
460
882
  function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
461
883
  return {
462
884
  socketPath,
@@ -471,7 +893,9 @@ function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
471
893
  listDiscoveredAgents: defaultListDiscoveredAgents,
472
894
  runHatchFlow: hatch_flow_1.runHatchFlow,
473
895
  promptInput: defaultPromptInput,
896
+ runAdoptionSpecialist: defaultRunAdoptionSpecialist,
474
897
  registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
898
+ installOuroCommand: ouro_path_installer_1.installOuroCommand,
475
899
  /* v8 ignore next 3 -- integration: launches interactive CLI session @preserve */
476
900
  startChat: async (agentName) => {
477
901
  const { main } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
@@ -533,12 +957,49 @@ async function registerOuroBundleTypeNonBlocking(deps) {
533
957
  });
534
958
  }
535
959
  }
960
+ async function performSystemSetup(deps) {
961
+ // Install ouro command to PATH (non-blocking)
962
+ if (deps.installOuroCommand) {
963
+ try {
964
+ deps.installOuroCommand();
965
+ }
966
+ catch (error) {
967
+ (0, runtime_1.emitNervesEvent)({
968
+ level: "warn",
969
+ component: "daemon",
970
+ event: "daemon.system_setup_ouro_cmd_error",
971
+ message: "failed to install ouro command to PATH",
972
+ meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
973
+ });
974
+ }
975
+ }
976
+ // Install subagents (claude/codex skills)
977
+ try {
978
+ await deps.installSubagents();
979
+ }
980
+ catch (error) {
981
+ (0, runtime_1.emitNervesEvent)({
982
+ level: "warn",
983
+ component: "daemon",
984
+ event: "daemon.subagent_install_error",
985
+ message: "subagent auto-install failed",
986
+ meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
987
+ });
988
+ }
989
+ // Register .ouro bundle type (UTI on macOS)
990
+ await registerOuroBundleTypeNonBlocking(deps);
991
+ }
536
992
  async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
537
993
  if (args.includes("--help") || args.includes("-h")) {
538
994
  const text = usage();
539
995
  deps.writeStdout(text);
540
996
  return text;
541
997
  }
998
+ if (args.length === 1 && (args[0] === "-v" || args[0] === "--version")) {
999
+ const text = formatVersionOutput();
1000
+ deps.writeStdout(text);
1001
+ return text;
1002
+ }
542
1003
  let command;
543
1004
  try {
544
1005
  command = parseOuroCommand(args);
@@ -556,7 +1017,20 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
556
1017
  }
557
1018
  if (args.length === 0) {
558
1019
  const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
559
- if (discovered.length === 0) {
1020
+ if (discovered.length === 0 && deps.runAdoptionSpecialist) {
1021
+ // System setup first — ouro command, subagents, UTI — before the interactive specialist
1022
+ await performSystemSetup(deps);
1023
+ const hatchlingName = await deps.runAdoptionSpecialist();
1024
+ if (!hatchlingName) {
1025
+ return "";
1026
+ }
1027
+ await ensureDaemonRunning(deps);
1028
+ if (deps.startChat) {
1029
+ await deps.startChat(hatchlingName);
1030
+ }
1031
+ return "";
1032
+ }
1033
+ else if (discovered.length === 0) {
560
1034
  command = { kind: "hatch.start" };
561
1035
  }
562
1036
  else if (discovered.length === 1) {
@@ -596,19 +1070,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
596
1070
  meta: { kind: command.kind },
597
1071
  });
598
1072
  if (command.kind === "daemon.up") {
599
- try {
600
- await deps.installSubagents();
601
- }
602
- catch (error) {
603
- (0, runtime_1.emitNervesEvent)({
604
- level: "warn",
605
- component: "daemon",
606
- event: "daemon.subagent_install_error",
607
- message: "subagent auto-install failed",
608
- meta: { error: error instanceof Error ? error.message : String(error) },
609
- });
610
- }
611
- await registerOuroBundleTypeNonBlocking(deps);
1073
+ await performSystemSetup(deps);
612
1074
  const daemonResult = await ensureDaemonRunning(deps);
613
1075
  deps.writeStdout(daemonResult.message);
614
1076
  return daemonResult.message;
@@ -624,6 +1086,21 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
624
1086
  return message;
625
1087
  }
626
1088
  if (command.kind === "hatch.start") {
1089
+ // Route through adoption specialist when no explicit hatch args were provided
1090
+ const hasExplicitHatchArgs = !!(command.agentName || command.humanName || command.provider || command.credentials);
1091
+ if (deps.runAdoptionSpecialist && !hasExplicitHatchArgs) {
1092
+ // System setup first — ouro command, subagents, UTI — before the interactive specialist
1093
+ await performSystemSetup(deps);
1094
+ const hatchlingName = await deps.runAdoptionSpecialist();
1095
+ if (!hatchlingName) {
1096
+ return "";
1097
+ }
1098
+ await ensureDaemonRunning(deps);
1099
+ if (deps.startChat) {
1100
+ await deps.startChat(hatchlingName);
1101
+ }
1102
+ return "";
1103
+ }
627
1104
  const hatchRunner = deps.runHatchFlow;
628
1105
  if (!hatchRunner) {
629
1106
  const response = await deps.sendCommand(deps.socketPath, { kind: "hatch.start" });
@@ -633,19 +1110,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
633
1110
  }
634
1111
  const hatchInput = await resolveHatchInput(command, deps);
635
1112
  const result = await hatchRunner(hatchInput);
636
- try {
637
- await deps.installSubagents();
638
- }
639
- catch (error) {
640
- (0, runtime_1.emitNervesEvent)({
641
- level: "warn",
642
- component: "daemon",
643
- event: "daemon.subagent_install_error",
644
- message: "subagent auto-install failed",
645
- meta: { error: error instanceof Error ? error.message : String(error) },
646
- });
647
- }
648
- await registerOuroBundleTypeNonBlocking(deps);
1113
+ await performSystemSetup(deps);
649
1114
  const daemonResult = await ensureDaemonRunning(deps);
650
1115
  if (deps.startChat) {
651
1116
  await deps.startChat(hatchInput.agentName);
@@ -667,9 +1132,22 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
667
1132
  deps.writeStdout(message);
668
1133
  return message;
669
1134
  }
1135
+ if (command.kind === "daemon.status" && isDaemonUnavailableError(error)) {
1136
+ const message = daemonUnavailableStatusOutput(deps.socketPath);
1137
+ deps.writeStdout(message);
1138
+ return message;
1139
+ }
1140
+ if (command.kind === "daemon.stop" && isDaemonUnavailableError(error)) {
1141
+ const message = "daemon not running";
1142
+ deps.writeStdout(message);
1143
+ return message;
1144
+ }
670
1145
  throw error;
671
1146
  }
672
- const message = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
1147
+ const fallbackMessage = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
1148
+ const message = command.kind === "daemon.status"
1149
+ ? formatDaemonStatusOutput(response, fallbackMessage)
1150
+ : fallbackMessage;
673
1151
  deps.writeStdout(message);
674
1152
  return message;
675
1153
  }
@@ -8,6 +8,7 @@ const message_router_1 = require("./message-router");
8
8
  const health_monitor_1 = require("./health-monitor");
9
9
  const task_scheduler_1 = require("./task-scheduler");
10
10
  const runtime_logging_1 = require("./runtime-logging");
11
+ const sense_manager_1 = require("./sense-manager");
11
12
  function parseSocketPath(argv) {
12
13
  const socketIndex = argv.indexOf("--socket");
13
14
  if (socketIndex >= 0) {
@@ -25,16 +26,22 @@ const socketPath = parseSocketPath(process.argv);
25
26
  message: "starting daemon entrypoint",
26
27
  meta: { socketPath },
27
28
  });
29
+ const managedAgents = ["ouroboros", "slugger"];
28
30
  const processManager = new process_manager_1.DaemonProcessManager({
29
- agents: [
30
- { name: "ouroboros", entry: "heart/agent-entry.js", channel: "cli", autoStart: true },
31
- { name: "slugger", entry: "heart/agent-entry.js", channel: "cli", autoStart: true },
32
- ],
31
+ agents: managedAgents.map((agent) => ({
32
+ name: agent,
33
+ entry: "heart/agent-entry.js",
34
+ channel: "inner-dialog",
35
+ autoStart: true,
36
+ })),
33
37
  });
34
38
  const scheduler = new task_scheduler_1.TaskDrivenScheduler({
35
- agents: ["ouroboros", "slugger"],
39
+ agents: [...managedAgents],
36
40
  });
37
41
  const router = new message_router_1.FileMessageRouter();
42
+ const senseManager = new sense_manager_1.DaemonSenseManager({
43
+ agents: [...managedAgents],
44
+ });
38
45
  const healthMonitor = new health_monitor_1.HealthMonitor({
39
46
  processManager,
40
47
  scheduler,
@@ -51,6 +58,7 @@ const healthMonitor = new health_monitor_1.HealthMonitor({
51
58
  const daemon = new daemon_1.OuroDaemon({
52
59
  socketPath,
53
60
  processManager,
61
+ senseManager,
54
62
  scheduler,
55
63
  healthMonitor,
56
64
  router,