@ouro.bot/cli 0.1.0-alpha.5 → 0.1.0-alpha.50
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/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +117 -188
- package/assets/ouroboros.png +0 -0
- package/changelog.json +242 -0
- package/dist/heart/active-work.js +157 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/config.js +81 -8
- package/dist/heart/core.js +145 -50
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/daemon-cli.js +1099 -164
- package/dist/heart/daemon/daemon-entry.js +14 -5
- package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
- package/dist/heart/daemon/daemon.js +184 -9
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +3 -20
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +151 -0
- package/dist/heart/daemon/message-router.js +15 -6
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +178 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +1 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-metadata.js +118 -0
- package/dist/heart/daemon/sense-manager.js +290 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +53 -84
- package/dist/heart/daemon/specialist-prompt.js +64 -5
- package/dist/heart/daemon/specialist-tools.js +213 -58
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/subagent-installer.js +48 -7
- package/dist/heart/daemon/thoughts.js +379 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +82 -4
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +16 -2
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/session-activity.js +169 -0
- package/dist/heart/session-recall.js +116 -0
- package/dist/heart/streaming.js +96 -21
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +27 -11
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +43 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/types.js +9 -1
- package/dist/mind/memory.js +10 -3
- package/dist/mind/pending.js +72 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +266 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +62 -4
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +462 -245
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +57 -35
- package/dist/senses/bluebubbles-client.js +484 -0
- package/dist/senses/bluebubbles-entry.js +13 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +338 -0
- package/dist/senses/bluebubbles-model.js +261 -0
- package/dist/senses/bluebubbles-mutation-log.js +116 -0
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +1142 -0
- package/dist/senses/cli.js +340 -138
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +148 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +330 -84
- package/dist/senses/pipeline.js +256 -0
- package/dist/senses/teams.js +541 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +46 -33
- package/subagents/work-doer.md +28 -24
- package/subagents/work-merger.md +24 -30
- package/subagents/work-planner.md +44 -27
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/inner-worker-entry.js +0 -4
|
@@ -35,11 +35,12 @@ 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");
|
|
42
|
+
const crypto_1 = require("crypto");
|
|
41
43
|
const fs = __importStar(require("fs"));
|
|
42
|
-
const net = __importStar(require("net"));
|
|
43
44
|
const os = __importStar(require("os"));
|
|
44
45
|
const path = __importStar(require("path"));
|
|
45
46
|
const identity_1 = require("../identity");
|
|
@@ -47,15 +48,179 @@ 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
|
+
const daemon_runtime_sync_1 = require("./daemon-runtime-sync");
|
|
59
|
+
const agent_discovery_1 = require("./agent-discovery");
|
|
60
|
+
const update_hooks_1 = require("./update-hooks");
|
|
61
|
+
const bundle_meta_1 = require("./hooks/bundle-meta");
|
|
62
|
+
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
63
|
+
const tasks_1 = require("../../repertoire/tasks");
|
|
64
|
+
const thoughts_1 = require("./thoughts");
|
|
65
|
+
const ouro_bot_global_installer_1 = require("./ouro-bot-global-installer");
|
|
66
|
+
const launchd_1 = require("./launchd");
|
|
67
|
+
const socket_client_1 = require("./socket-client");
|
|
68
|
+
const session_activity_1 = require("../session-activity");
|
|
69
|
+
function stringField(value) {
|
|
70
|
+
return typeof value === "string" ? value : null;
|
|
71
|
+
}
|
|
72
|
+
function numberField(value) {
|
|
73
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
74
|
+
}
|
|
75
|
+
function booleanField(value) {
|
|
76
|
+
return typeof value === "boolean" ? value : null;
|
|
77
|
+
}
|
|
78
|
+
function parseStatusPayload(data) {
|
|
79
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
80
|
+
return null;
|
|
81
|
+
const raw = data;
|
|
82
|
+
const overview = raw.overview;
|
|
83
|
+
const senses = raw.senses;
|
|
84
|
+
const workers = raw.workers;
|
|
85
|
+
if (!overview || typeof overview !== "object" || Array.isArray(overview))
|
|
86
|
+
return null;
|
|
87
|
+
if (!Array.isArray(senses) || !Array.isArray(workers))
|
|
88
|
+
return null;
|
|
89
|
+
const parsedOverview = {
|
|
90
|
+
daemon: stringField(overview.daemon) ?? "unknown",
|
|
91
|
+
health: stringField(overview.health) ?? "unknown",
|
|
92
|
+
socketPath: stringField(overview.socketPath) ?? "unknown",
|
|
93
|
+
version: stringField(overview.version) ?? "unknown",
|
|
94
|
+
lastUpdated: stringField(overview.lastUpdated) ?? "unknown",
|
|
95
|
+
workerCount: numberField(overview.workerCount) ?? 0,
|
|
96
|
+
senseCount: numberField(overview.senseCount) ?? 0,
|
|
97
|
+
};
|
|
98
|
+
const parsedSenses = senses.map((entry) => {
|
|
99
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
100
|
+
return null;
|
|
101
|
+
const row = entry;
|
|
102
|
+
const agent = stringField(row.agent);
|
|
103
|
+
const sense = stringField(row.sense);
|
|
104
|
+
const status = stringField(row.status);
|
|
105
|
+
const detail = stringField(row.detail);
|
|
106
|
+
const enabled = booleanField(row.enabled);
|
|
107
|
+
if (!agent || !sense || !status || detail === null || enabled === null)
|
|
108
|
+
return null;
|
|
109
|
+
return {
|
|
110
|
+
agent,
|
|
111
|
+
sense,
|
|
112
|
+
label: stringField(row.label) ?? undefined,
|
|
113
|
+
enabled,
|
|
114
|
+
status,
|
|
115
|
+
detail,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
const parsedWorkers = workers.map((entry) => {
|
|
119
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
120
|
+
return null;
|
|
121
|
+
const row = entry;
|
|
122
|
+
const agent = stringField(row.agent);
|
|
123
|
+
const worker = stringField(row.worker);
|
|
124
|
+
const status = stringField(row.status);
|
|
125
|
+
const restartCount = numberField(row.restartCount);
|
|
126
|
+
const hasPid = Object.prototype.hasOwnProperty.call(row, "pid");
|
|
127
|
+
const pid = row.pid === null ? null : numberField(row.pid);
|
|
128
|
+
const pidInvalid = !hasPid || (row.pid !== null && pid === null);
|
|
129
|
+
if (!agent || !worker || !status || restartCount === null || pidInvalid)
|
|
130
|
+
return null;
|
|
131
|
+
return {
|
|
132
|
+
agent,
|
|
133
|
+
worker,
|
|
134
|
+
status,
|
|
135
|
+
pid,
|
|
136
|
+
restartCount,
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
if (parsedSenses.some((row) => row === null) || parsedWorkers.some((row) => row === null))
|
|
140
|
+
return null;
|
|
141
|
+
return {
|
|
142
|
+
overview: parsedOverview,
|
|
143
|
+
senses: parsedSenses,
|
|
144
|
+
workers: parsedWorkers,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function humanizeSenseName(sense, label) {
|
|
148
|
+
if (label)
|
|
149
|
+
return label;
|
|
150
|
+
if (sense === "cli")
|
|
151
|
+
return "CLI";
|
|
152
|
+
if (sense === "bluebubbles")
|
|
153
|
+
return "BlueBubbles";
|
|
154
|
+
if (sense === "teams")
|
|
155
|
+
return "Teams";
|
|
156
|
+
return sense;
|
|
157
|
+
}
|
|
158
|
+
function formatTable(headers, rows) {
|
|
159
|
+
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index].length)));
|
|
160
|
+
const renderRow = (row) => `| ${row.map((cell, index) => cell.padEnd(widths[index])).join(" | ")} |`;
|
|
161
|
+
const divider = `|-${widths.map((width) => "-".repeat(width)).join("-|-")}-|`;
|
|
162
|
+
return [
|
|
163
|
+
renderRow(headers),
|
|
164
|
+
divider,
|
|
165
|
+
...rows.map(renderRow),
|
|
166
|
+
].join("\n");
|
|
167
|
+
}
|
|
168
|
+
function formatDaemonStatusOutput(response, fallback) {
|
|
169
|
+
const payload = parseStatusPayload(response.data);
|
|
170
|
+
if (!payload)
|
|
171
|
+
return fallback;
|
|
172
|
+
const overviewRows = [
|
|
173
|
+
["Daemon", payload.overview.daemon],
|
|
174
|
+
["Socket", payload.overview.socketPath],
|
|
175
|
+
["Version", payload.overview.version],
|
|
176
|
+
["Last Updated", payload.overview.lastUpdated],
|
|
177
|
+
["Workers", String(payload.overview.workerCount)],
|
|
178
|
+
["Senses", String(payload.overview.senseCount)],
|
|
179
|
+
["Health", payload.overview.health],
|
|
180
|
+
];
|
|
181
|
+
const senseRows = payload.senses.map((row) => [
|
|
182
|
+
row.agent,
|
|
183
|
+
humanizeSenseName(row.sense, row.label),
|
|
184
|
+
row.enabled ? "ON" : "OFF",
|
|
185
|
+
row.status,
|
|
186
|
+
row.detail,
|
|
187
|
+
]);
|
|
188
|
+
const workerRows = payload.workers.map((row) => [
|
|
189
|
+
row.agent,
|
|
190
|
+
row.worker,
|
|
191
|
+
row.status,
|
|
192
|
+
row.pid === null ? "n/a" : String(row.pid),
|
|
193
|
+
String(row.restartCount),
|
|
194
|
+
]);
|
|
195
|
+
return [
|
|
196
|
+
"Overview",
|
|
197
|
+
formatTable(["Item", "Value"], overviewRows),
|
|
198
|
+
"",
|
|
199
|
+
"Senses",
|
|
200
|
+
formatTable(["Agent", "Sense", "Enabled", "State", "Detail"], senseRows),
|
|
201
|
+
"",
|
|
202
|
+
"Workers",
|
|
203
|
+
formatTable(["Agent", "Worker", "State", "PID", "Restarts"], workerRows),
|
|
204
|
+
].join("\n");
|
|
205
|
+
}
|
|
52
206
|
async function ensureDaemonRunning(deps) {
|
|
53
207
|
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
54
208
|
if (alive) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
209
|
+
const localRuntime = (0, runtime_metadata_1.getRuntimeMetadata)();
|
|
210
|
+
return (0, daemon_runtime_sync_1.ensureCurrentDaemonRuntime)({
|
|
211
|
+
socketPath: deps.socketPath,
|
|
212
|
+
localVersion: localRuntime.version,
|
|
213
|
+
fetchRunningVersion: async () => {
|
|
214
|
+
const status = await deps.sendCommand(deps.socketPath, { kind: "daemon.status" });
|
|
215
|
+
const payload = parseStatusPayload(status.data);
|
|
216
|
+
return payload?.overview.version ?? "unknown";
|
|
217
|
+
},
|
|
218
|
+
stopDaemon: async () => {
|
|
219
|
+
await deps.sendCommand(deps.socketPath, { kind: "daemon.stop" });
|
|
220
|
+
},
|
|
221
|
+
cleanupStaleSocket: deps.cleanupStaleSocket,
|
|
222
|
+
startDaemonProcess: deps.startDaemonProcess,
|
|
223
|
+
});
|
|
59
224
|
}
|
|
60
225
|
deps.cleanupStaleSocket(deps.socketPath);
|
|
61
226
|
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
@@ -64,17 +229,80 @@ async function ensureDaemonRunning(deps) {
|
|
|
64
229
|
message: `daemon started (pid ${started.pid ?? "unknown"})`,
|
|
65
230
|
};
|
|
66
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Extract `--agent <name>` from an args array, returning the agent name and
|
|
234
|
+
* the remaining args with the flag pair removed.
|
|
235
|
+
*/
|
|
236
|
+
function extractAgentFlag(args) {
|
|
237
|
+
const idx = args.indexOf("--agent");
|
|
238
|
+
if (idx === -1 || idx + 1 >= args.length)
|
|
239
|
+
return { rest: args };
|
|
240
|
+
const agent = args[idx + 1];
|
|
241
|
+
const rest = [...args.slice(0, idx), ...args.slice(idx + 2)];
|
|
242
|
+
return { agent, rest };
|
|
243
|
+
}
|
|
67
244
|
function usage() {
|
|
68
245
|
return [
|
|
69
246
|
"Usage:",
|
|
70
247
|
" ouro [up]",
|
|
71
|
-
" ouro stop|status|logs|hatch",
|
|
248
|
+
" ouro stop|down|status|logs|hatch",
|
|
249
|
+
" ouro -v|--version",
|
|
72
250
|
" ouro chat <agent>",
|
|
73
251
|
" ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
|
|
74
252
|
" ouro poke <agent> --task <task-id>",
|
|
75
253
|
" ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
|
|
254
|
+
" ouro task board [<status>] [--agent <name>]",
|
|
255
|
+
" ouro task create <title> [--type <type>] [--agent <name>]",
|
|
256
|
+
" ouro task update <id> <status> [--agent <name>]",
|
|
257
|
+
" ouro task show <id> [--agent <name>]",
|
|
258
|
+
" ouro task actionable|deps|sessions [--agent <name>]",
|
|
259
|
+
" ouro reminder create <title> --body <body> [--at <iso>] [--cadence <interval>] [--category <category>] [--agent <name>]",
|
|
260
|
+
" ouro friend list [--agent <name>]",
|
|
261
|
+
" ouro friend show <id> [--agent <name>]",
|
|
262
|
+
" ouro friend create --name <name> [--trust <level>] [--agent <name>]",
|
|
263
|
+
" ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
|
|
264
|
+
" ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
265
|
+
" ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
266
|
+
" ouro whoami [--agent <name>]",
|
|
267
|
+
" ouro session list [--agent <name>]",
|
|
76
268
|
].join("\n");
|
|
77
269
|
}
|
|
270
|
+
function formatVersionOutput() {
|
|
271
|
+
return (0, runtime_metadata_1.getRuntimeMetadata)().version;
|
|
272
|
+
}
|
|
273
|
+
function buildStoppedStatusPayload(socketPath) {
|
|
274
|
+
const metadata = (0, runtime_metadata_1.getRuntimeMetadata)();
|
|
275
|
+
return {
|
|
276
|
+
overview: {
|
|
277
|
+
daemon: "stopped",
|
|
278
|
+
health: "warn",
|
|
279
|
+
socketPath,
|
|
280
|
+
version: metadata.version,
|
|
281
|
+
lastUpdated: metadata.lastUpdated,
|
|
282
|
+
workerCount: 0,
|
|
283
|
+
senseCount: 0,
|
|
284
|
+
},
|
|
285
|
+
senses: [],
|
|
286
|
+
workers: [],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function daemonUnavailableStatusOutput(socketPath) {
|
|
290
|
+
return [
|
|
291
|
+
formatDaemonStatusOutput({
|
|
292
|
+
ok: true,
|
|
293
|
+
summary: "daemon not running",
|
|
294
|
+
data: buildStoppedStatusPayload(socketPath),
|
|
295
|
+
}, "daemon not running"),
|
|
296
|
+
"",
|
|
297
|
+
"daemon not running; run `ouro up`",
|
|
298
|
+
].join("\n");
|
|
299
|
+
}
|
|
300
|
+
function isDaemonUnavailableError(error) {
|
|
301
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
302
|
+
? String(error.code ?? "")
|
|
303
|
+
: "";
|
|
304
|
+
return code === "ENOENT" || code === "ECONNREFUSED";
|
|
305
|
+
}
|
|
78
306
|
function parseMessageCommand(args) {
|
|
79
307
|
let to;
|
|
80
308
|
let sessionId;
|
|
@@ -126,7 +354,7 @@ function parsePokeCommand(args) {
|
|
|
126
354
|
throw new Error(`Usage\n${usage()}`);
|
|
127
355
|
return { kind: "task.poke", agent, taskId };
|
|
128
356
|
}
|
|
129
|
-
function parseLinkCommand(args) {
|
|
357
|
+
function parseLinkCommand(args, kind = "friend.link") {
|
|
130
358
|
const agent = args[0];
|
|
131
359
|
if (!agent)
|
|
132
360
|
throw new Error(`Usage\n${usage()}`);
|
|
@@ -158,7 +386,7 @@ function parseLinkCommand(args) {
|
|
|
158
386
|
throw new Error(`Unknown identity provider '${providerRaw}'. Use aad|local|teams-conversation.`);
|
|
159
387
|
}
|
|
160
388
|
return {
|
|
161
|
-
kind
|
|
389
|
+
kind,
|
|
162
390
|
agent,
|
|
163
391
|
friendId,
|
|
164
392
|
provider: providerRaw,
|
|
@@ -235,13 +463,180 @@ function parseHatchCommand(args) {
|
|
|
235
463
|
migrationPath,
|
|
236
464
|
};
|
|
237
465
|
}
|
|
466
|
+
function parseTaskCommand(args) {
|
|
467
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
468
|
+
const [sub, ...rest] = cleaned;
|
|
469
|
+
if (!sub)
|
|
470
|
+
throw new Error(`Usage\n${usage()}`);
|
|
471
|
+
if (sub === "board") {
|
|
472
|
+
const status = rest[0];
|
|
473
|
+
return status
|
|
474
|
+
? { kind: "task.board", status, ...(agent ? { agent } : {}) }
|
|
475
|
+
: { kind: "task.board", ...(agent ? { agent } : {}) };
|
|
476
|
+
}
|
|
477
|
+
if (sub === "create") {
|
|
478
|
+
const title = rest[0];
|
|
479
|
+
if (!title)
|
|
480
|
+
throw new Error(`Usage\n${usage()}`);
|
|
481
|
+
let type;
|
|
482
|
+
for (let i = 1; i < rest.length; i++) {
|
|
483
|
+
if (rest[i] === "--type" && rest[i + 1]) {
|
|
484
|
+
type = rest[i + 1];
|
|
485
|
+
i += 1;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return type
|
|
489
|
+
? { kind: "task.create", title, type, ...(agent ? { agent } : {}) }
|
|
490
|
+
: { kind: "task.create", title, ...(agent ? { agent } : {}) };
|
|
491
|
+
}
|
|
492
|
+
if (sub === "update") {
|
|
493
|
+
const id = rest[0];
|
|
494
|
+
const status = rest[1];
|
|
495
|
+
if (!id || !status)
|
|
496
|
+
throw new Error(`Usage\n${usage()}`);
|
|
497
|
+
return { kind: "task.update", id, status, ...(agent ? { agent } : {}) };
|
|
498
|
+
}
|
|
499
|
+
if (sub === "show") {
|
|
500
|
+
const id = rest[0];
|
|
501
|
+
if (!id)
|
|
502
|
+
throw new Error(`Usage\n${usage()}`);
|
|
503
|
+
return { kind: "task.show", id, ...(agent ? { agent } : {}) };
|
|
504
|
+
}
|
|
505
|
+
if (sub === "actionable")
|
|
506
|
+
return { kind: "task.actionable", ...(agent ? { agent } : {}) };
|
|
507
|
+
if (sub === "deps")
|
|
508
|
+
return { kind: "task.deps", ...(agent ? { agent } : {}) };
|
|
509
|
+
if (sub === "sessions")
|
|
510
|
+
return { kind: "task.sessions", ...(agent ? { agent } : {}) };
|
|
511
|
+
throw new Error(`Usage\n${usage()}`);
|
|
512
|
+
}
|
|
513
|
+
function parseReminderCommand(args) {
|
|
514
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
515
|
+
const [sub, ...rest] = cleaned;
|
|
516
|
+
if (!sub)
|
|
517
|
+
throw new Error(`Usage\n${usage()}`);
|
|
518
|
+
if (sub === "create") {
|
|
519
|
+
const title = rest[0];
|
|
520
|
+
if (!title)
|
|
521
|
+
throw new Error(`Usage\n${usage()}`);
|
|
522
|
+
let body;
|
|
523
|
+
let scheduledAt;
|
|
524
|
+
let cadence;
|
|
525
|
+
let category;
|
|
526
|
+
let requester;
|
|
527
|
+
for (let i = 1; i < rest.length; i++) {
|
|
528
|
+
if (rest[i] === "--body" && rest[i + 1]) {
|
|
529
|
+
body = rest[i + 1];
|
|
530
|
+
i += 1;
|
|
531
|
+
}
|
|
532
|
+
else if (rest[i] === "--at" && rest[i + 1]) {
|
|
533
|
+
scheduledAt = rest[i + 1];
|
|
534
|
+
i += 1;
|
|
535
|
+
}
|
|
536
|
+
else if (rest[i] === "--cadence" && rest[i + 1]) {
|
|
537
|
+
cadence = rest[i + 1];
|
|
538
|
+
i += 1;
|
|
539
|
+
}
|
|
540
|
+
else if (rest[i] === "--category" && rest[i + 1]) {
|
|
541
|
+
category = rest[i + 1];
|
|
542
|
+
i += 1;
|
|
543
|
+
}
|
|
544
|
+
else if (rest[i] === "--requester" && rest[i + 1]) {
|
|
545
|
+
requester = rest[i + 1];
|
|
546
|
+
i += 1;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (!body)
|
|
550
|
+
throw new Error(`Usage\n${usage()}`);
|
|
551
|
+
if (!scheduledAt && !cadence)
|
|
552
|
+
throw new Error(`Usage\n${usage()}`);
|
|
553
|
+
return {
|
|
554
|
+
kind: "reminder.create",
|
|
555
|
+
title,
|
|
556
|
+
body,
|
|
557
|
+
...(scheduledAt ? { scheduledAt } : {}),
|
|
558
|
+
...(cadence ? { cadence } : {}),
|
|
559
|
+
...(category ? { category } : {}),
|
|
560
|
+
...(requester ? { requester } : {}),
|
|
561
|
+
...(agent ? { agent } : {}),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
throw new Error(`Usage\n${usage()}`);
|
|
565
|
+
}
|
|
566
|
+
function parseSessionCommand(args) {
|
|
567
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
568
|
+
const [sub] = cleaned;
|
|
569
|
+
if (!sub)
|
|
570
|
+
throw new Error(`Usage\n${usage()}`);
|
|
571
|
+
if (sub === "list")
|
|
572
|
+
return { kind: "session.list", ...(agent ? { agent } : {}) };
|
|
573
|
+
throw new Error(`Usage\n${usage()}`);
|
|
574
|
+
}
|
|
575
|
+
function parseThoughtsCommand(args) {
|
|
576
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
577
|
+
let last;
|
|
578
|
+
let json = false;
|
|
579
|
+
let follow = false;
|
|
580
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
581
|
+
if (cleaned[i] === "--last" && i + 1 < cleaned.length) {
|
|
582
|
+
last = Number.parseInt(cleaned[i + 1], 10);
|
|
583
|
+
i++;
|
|
584
|
+
}
|
|
585
|
+
if (cleaned[i] === "--json")
|
|
586
|
+
json = true;
|
|
587
|
+
if (cleaned[i] === "--follow" || cleaned[i] === "-f")
|
|
588
|
+
follow = true;
|
|
589
|
+
}
|
|
590
|
+
return { kind: "thoughts", ...(agent ? { agent } : {}), ...(last ? { last } : {}), ...(json ? { json } : {}), ...(follow ? { follow } : {}) };
|
|
591
|
+
}
|
|
592
|
+
function parseFriendCommand(args) {
|
|
593
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
594
|
+
const [sub, ...rest] = cleaned;
|
|
595
|
+
if (!sub)
|
|
596
|
+
throw new Error(`Usage\n${usage()}`);
|
|
597
|
+
if (sub === "list")
|
|
598
|
+
return { kind: "friend.list", ...(agent ? { agent } : {}) };
|
|
599
|
+
if (sub === "show") {
|
|
600
|
+
const friendId = rest[0];
|
|
601
|
+
if (!friendId)
|
|
602
|
+
throw new Error(`Usage\n${usage()}`);
|
|
603
|
+
return { kind: "friend.show", friendId, ...(agent ? { agent } : {}) };
|
|
604
|
+
}
|
|
605
|
+
if (sub === "create") {
|
|
606
|
+
let name;
|
|
607
|
+
let trustLevel;
|
|
608
|
+
for (let i = 0; i < rest.length; i++) {
|
|
609
|
+
if (rest[i] === "--name" && rest[i + 1]) {
|
|
610
|
+
name = rest[i + 1];
|
|
611
|
+
i += 1;
|
|
612
|
+
}
|
|
613
|
+
else if (rest[i] === "--trust" && rest[i + 1]) {
|
|
614
|
+
trustLevel = rest[i + 1];
|
|
615
|
+
i += 1;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (!name)
|
|
619
|
+
throw new Error(`Usage\n${usage()}`);
|
|
620
|
+
return {
|
|
621
|
+
kind: "friend.create",
|
|
622
|
+
name,
|
|
623
|
+
...(trustLevel ? { trustLevel } : {}),
|
|
624
|
+
...(agent ? { agent } : {}),
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
if (sub === "link")
|
|
628
|
+
return parseLinkCommand(rest, "friend.link");
|
|
629
|
+
if (sub === "unlink")
|
|
630
|
+
return parseLinkCommand(rest, "friend.unlink");
|
|
631
|
+
throw new Error(`Usage\n${usage()}`);
|
|
632
|
+
}
|
|
238
633
|
function parseOuroCommand(args) {
|
|
239
634
|
const [head, second] = args;
|
|
240
635
|
if (!head)
|
|
241
636
|
return { kind: "daemon.up" };
|
|
242
637
|
if (head === "up")
|
|
243
638
|
return { kind: "daemon.up" };
|
|
244
|
-
if (head === "stop")
|
|
639
|
+
if (head === "stop" || head === "down")
|
|
245
640
|
return { kind: "daemon.stop" };
|
|
246
641
|
if (head === "status")
|
|
247
642
|
return { kind: "daemon.status" };
|
|
@@ -249,6 +644,20 @@ function parseOuroCommand(args) {
|
|
|
249
644
|
return { kind: "daemon.logs" };
|
|
250
645
|
if (head === "hatch")
|
|
251
646
|
return parseHatchCommand(args.slice(1));
|
|
647
|
+
if (head === "task")
|
|
648
|
+
return parseTaskCommand(args.slice(1));
|
|
649
|
+
if (head === "reminder")
|
|
650
|
+
return parseReminderCommand(args.slice(1));
|
|
651
|
+
if (head === "friend")
|
|
652
|
+
return parseFriendCommand(args.slice(1));
|
|
653
|
+
if (head === "whoami") {
|
|
654
|
+
const { agent } = extractAgentFlag(args.slice(1));
|
|
655
|
+
return { kind: "whoami", ...(agent ? { agent } : {}) };
|
|
656
|
+
}
|
|
657
|
+
if (head === "session")
|
|
658
|
+
return parseSessionCommand(args.slice(1));
|
|
659
|
+
if (head === "thoughts")
|
|
660
|
+
return parseThoughtsCommand(args.slice(1));
|
|
252
661
|
if (head === "chat") {
|
|
253
662
|
if (!second)
|
|
254
663
|
throw new Error(`Usage\n${usage()}`);
|
|
@@ -262,38 +671,6 @@ function parseOuroCommand(args) {
|
|
|
262
671
|
return parseLinkCommand(args.slice(1));
|
|
263
672
|
throw new Error(`Unknown command '${args.join(" ")}'.\n${usage()}`);
|
|
264
673
|
}
|
|
265
|
-
function defaultSendCommand(socketPath, command) {
|
|
266
|
-
return new Promise((resolve, reject) => {
|
|
267
|
-
const client = net.createConnection(socketPath);
|
|
268
|
-
let raw = "";
|
|
269
|
-
client.on("connect", () => {
|
|
270
|
-
client.write(JSON.stringify(command));
|
|
271
|
-
client.end();
|
|
272
|
-
});
|
|
273
|
-
client.on("data", (chunk) => {
|
|
274
|
-
raw += chunk.toString("utf-8");
|
|
275
|
-
});
|
|
276
|
-
client.on("error", reject);
|
|
277
|
-
client.on("end", () => {
|
|
278
|
-
const trimmed = raw.trim();
|
|
279
|
-
if (trimmed.length === 0 && command.kind === "daemon.stop") {
|
|
280
|
-
resolve({ ok: true, message: "daemon stopped" });
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
if (trimmed.length === 0) {
|
|
284
|
-
reject(new Error("Daemon returned empty response."));
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
try {
|
|
288
|
-
const parsed = JSON.parse(trimmed);
|
|
289
|
-
resolve(parsed);
|
|
290
|
-
}
|
|
291
|
-
catch (error) {
|
|
292
|
-
reject(error);
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
674
|
function defaultStartDaemonProcess(socketPath) {
|
|
298
675
|
const entry = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
|
|
299
676
|
const child = (0, child_process_1.spawn)("node", [entry, "--socket", socketPath], {
|
|
@@ -307,46 +684,6 @@ function defaultWriteStdout(text) {
|
|
|
307
684
|
// eslint-disable-next-line no-console -- terminal UX: CLI command output
|
|
308
685
|
console.log(text);
|
|
309
686
|
}
|
|
310
|
-
function defaultCheckSocketAlive(socketPath) {
|
|
311
|
-
return new Promise((resolve) => {
|
|
312
|
-
const client = net.createConnection(socketPath);
|
|
313
|
-
let raw = "";
|
|
314
|
-
let done = false;
|
|
315
|
-
const finalize = (alive) => {
|
|
316
|
-
if (done)
|
|
317
|
-
return;
|
|
318
|
-
done = true;
|
|
319
|
-
resolve(alive);
|
|
320
|
-
};
|
|
321
|
-
if ("setTimeout" in client && typeof client.setTimeout === "function") {
|
|
322
|
-
client.setTimeout(800, () => {
|
|
323
|
-
client.destroy();
|
|
324
|
-
finalize(false);
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
client.on("connect", () => {
|
|
328
|
-
client.write(JSON.stringify({ kind: "daemon.status" }));
|
|
329
|
-
client.end();
|
|
330
|
-
});
|
|
331
|
-
client.on("data", (chunk) => {
|
|
332
|
-
raw += chunk.toString("utf-8");
|
|
333
|
-
});
|
|
334
|
-
client.on("error", () => finalize(false));
|
|
335
|
-
client.on("end", () => {
|
|
336
|
-
if (raw.trim().length === 0) {
|
|
337
|
-
finalize(false);
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
try {
|
|
341
|
-
JSON.parse(raw);
|
|
342
|
-
finalize(true);
|
|
343
|
-
}
|
|
344
|
-
catch {
|
|
345
|
-
finalize(false);
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
687
|
function defaultCleanupStaleSocket(socketPath) {
|
|
351
688
|
if (fs.existsSync(socketPath)) {
|
|
352
689
|
fs.unlinkSync(socketPath);
|
|
@@ -381,6 +718,25 @@ function defaultFallbackPendingMessage(command) {
|
|
|
381
718
|
});
|
|
382
719
|
return pendingPath;
|
|
383
720
|
}
|
|
721
|
+
function defaultEnsureDaemonBootPersistence(socketPath) {
|
|
722
|
+
if (process.platform !== "darwin") {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const homeDir = os.homedir();
|
|
726
|
+
const launchdDeps = {
|
|
727
|
+
writeFile: (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
|
|
728
|
+
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
729
|
+
homeDir,
|
|
730
|
+
};
|
|
731
|
+
const entryPath = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
|
|
732
|
+
const logDir = path.join(homeDir, ".agentstate", "daemon", "logs");
|
|
733
|
+
(0, launchd_1.writeLaunchAgentPlist)(launchdDeps, {
|
|
734
|
+
nodePath: process.execPath,
|
|
735
|
+
entryPath,
|
|
736
|
+
socketPath,
|
|
737
|
+
logDir,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
384
740
|
async function defaultInstallSubagents() {
|
|
385
741
|
return (0, subagent_installer_1.installSubagentsForAvailableCli)({
|
|
386
742
|
repoRoot: (0, identity_1.getRepoRoot)(),
|
|
@@ -401,82 +757,290 @@ async function defaultPromptInput(question) {
|
|
|
401
757
|
}
|
|
402
758
|
}
|
|
403
759
|
function defaultListDiscoveredAgents() {
|
|
404
|
-
|
|
760
|
+
return (0, agent_discovery_1.listEnabledBundleAgents)({
|
|
761
|
+
bundlesRoot: (0, identity_1.getAgentBundlesRoot)(),
|
|
762
|
+
readdirSync: fs.readdirSync,
|
|
763
|
+
readFileSync: fs.readFileSync,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
function discoverExistingCredentials(secretsRoot) {
|
|
767
|
+
const found = [];
|
|
405
768
|
let entries;
|
|
406
769
|
try {
|
|
407
|
-
entries = fs.readdirSync(
|
|
770
|
+
entries = fs.readdirSync(secretsRoot, { withFileTypes: true });
|
|
408
771
|
}
|
|
409
772
|
catch {
|
|
410
|
-
return
|
|
773
|
+
return found;
|
|
411
774
|
}
|
|
412
|
-
const discovered = [];
|
|
413
775
|
for (const entry of entries) {
|
|
414
|
-
if (!entry.isDirectory()
|
|
776
|
+
if (!entry.isDirectory())
|
|
415
777
|
continue;
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
let enabled = true;
|
|
778
|
+
const secretsPath = path.join(secretsRoot, entry.name, "secrets.json");
|
|
779
|
+
let raw;
|
|
419
780
|
try {
|
|
420
|
-
|
|
421
|
-
const parsed = JSON.parse(raw);
|
|
422
|
-
if (typeof parsed.enabled === "boolean") {
|
|
423
|
-
enabled = parsed.enabled;
|
|
424
|
-
}
|
|
781
|
+
raw = fs.readFileSync(secretsPath, "utf-8");
|
|
425
782
|
}
|
|
426
783
|
catch {
|
|
427
784
|
continue;
|
|
428
785
|
}
|
|
429
|
-
|
|
430
|
-
|
|
786
|
+
let parsed;
|
|
787
|
+
try {
|
|
788
|
+
parsed = JSON.parse(raw);
|
|
789
|
+
}
|
|
790
|
+
catch {
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (!parsed.providers)
|
|
794
|
+
continue;
|
|
795
|
+
for (const [provName, provConfig] of Object.entries(parsed.providers)) {
|
|
796
|
+
if (provName === "anthropic" && provConfig.setupToken) {
|
|
797
|
+
found.push({ agentName: entry.name, provider: "anthropic", credentials: { setupToken: provConfig.setupToken }, providerConfig: { ...provConfig } });
|
|
798
|
+
}
|
|
799
|
+
else if (provName === "openai-codex" && provConfig.oauthAccessToken) {
|
|
800
|
+
found.push({ agentName: entry.name, provider: "openai-codex", credentials: { oauthAccessToken: provConfig.oauthAccessToken }, providerConfig: { ...provConfig } });
|
|
801
|
+
}
|
|
802
|
+
else if (provName === "minimax" && provConfig.apiKey) {
|
|
803
|
+
found.push({ agentName: entry.name, provider: "minimax", credentials: { apiKey: provConfig.apiKey }, providerConfig: { ...provConfig } });
|
|
804
|
+
}
|
|
805
|
+
else if (provName === "azure" && provConfig.apiKey && provConfig.endpoint && provConfig.deployment) {
|
|
806
|
+
found.push({ agentName: entry.name, provider: "azure", credentials: { apiKey: provConfig.apiKey, endpoint: provConfig.endpoint, deployment: provConfig.deployment }, providerConfig: { ...provConfig } });
|
|
807
|
+
}
|
|
431
808
|
}
|
|
432
809
|
}
|
|
433
|
-
|
|
810
|
+
// Deduplicate by provider+credential value (keep first seen)
|
|
811
|
+
const seen = new Set();
|
|
812
|
+
return found.filter((cred) => {
|
|
813
|
+
const key = `${cred.provider}:${JSON.stringify(cred.credentials)}`;
|
|
814
|
+
if (seen.has(key))
|
|
815
|
+
return false;
|
|
816
|
+
seen.add(key);
|
|
817
|
+
return true;
|
|
818
|
+
});
|
|
434
819
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
820
|
+
/* v8 ignore start -- integration: interactive terminal specialist session @preserve */
|
|
821
|
+
async function defaultRunAdoptionSpecialist() {
|
|
822
|
+
const { runCliSession } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
823
|
+
const { patchRuntimeConfig } = await Promise.resolve().then(() => __importStar(require("../config")));
|
|
824
|
+
const { setAgentName, setAgentConfigOverride } = await Promise.resolve().then(() => __importStar(require("../identity")));
|
|
825
|
+
const readlinePromises = await Promise.resolve().then(() => __importStar(require("readline/promises")));
|
|
826
|
+
const crypto = await Promise.resolve().then(() => __importStar(require("crypto")));
|
|
827
|
+
// Phase 1: cold CLI — collect provider/credentials with a simple readline
|
|
828
|
+
const coldRl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
829
|
+
const coldPrompt = async (q) => {
|
|
830
|
+
const answer = await coldRl.question(q);
|
|
831
|
+
return answer.trim();
|
|
832
|
+
};
|
|
833
|
+
let providerRaw;
|
|
834
|
+
let credentials = {};
|
|
835
|
+
let providerConfig = {};
|
|
836
|
+
const tempDir = path.join(os.tmpdir(), `ouro-hatch-${crypto.randomUUID()}`);
|
|
837
|
+
try {
|
|
838
|
+
const secretsRoot = path.join(os.homedir(), ".agentsecrets");
|
|
839
|
+
const discovered = discoverExistingCredentials(secretsRoot);
|
|
840
|
+
const existingBundleCount = (0, specialist_orchestrator_1.listExistingBundles)((0, identity_1.getAgentBundlesRoot)()).length;
|
|
841
|
+
const hatchVerb = existingBundleCount > 0 ? "let's hatch a new agent." : "let's hatch your first agent.";
|
|
842
|
+
// Default models per provider (used when entering new credentials)
|
|
843
|
+
const defaultModels = {
|
|
844
|
+
anthropic: "claude-opus-4-6",
|
|
845
|
+
minimax: "MiniMax-Text-01",
|
|
846
|
+
"openai-codex": "gpt-5.4",
|
|
847
|
+
azure: "",
|
|
848
|
+
};
|
|
849
|
+
if (discovered.length > 0) {
|
|
850
|
+
process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
|
|
851
|
+
process.stdout.write("i found existing API credentials:\n\n");
|
|
852
|
+
const unique = [...new Map(discovered.map((d) => [`${d.provider}`, d])).values()];
|
|
853
|
+
for (let i = 0; i < unique.length; i++) {
|
|
854
|
+
const model = unique[i].providerConfig.model || unique[i].providerConfig.deployment || "";
|
|
855
|
+
const modelLabel = model ? `, ${model}` : "";
|
|
856
|
+
process.stdout.write(` ${i + 1}. ${unique[i].provider}${modelLabel} (from ${unique[i].agentName})\n`);
|
|
857
|
+
}
|
|
858
|
+
process.stdout.write("\n");
|
|
859
|
+
const choice = await coldPrompt("use one of these? enter number, or 'new' for a different key: ");
|
|
860
|
+
const idx = parseInt(choice, 10) - 1;
|
|
861
|
+
if (idx >= 0 && idx < unique.length) {
|
|
862
|
+
providerRaw = unique[idx].provider;
|
|
863
|
+
credentials = unique[idx].credentials;
|
|
864
|
+
providerConfig = unique[idx].providerConfig;
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
|
|
868
|
+
if (!isAgentProvider(pRaw)) {
|
|
869
|
+
process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
|
|
870
|
+
coldRl.close();
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
providerRaw = pRaw;
|
|
874
|
+
providerConfig = { model: defaultModels[providerRaw] };
|
|
875
|
+
if (providerRaw === "anthropic")
|
|
876
|
+
credentials.setupToken = await coldPrompt("API key: ");
|
|
877
|
+
if (providerRaw === "openai-codex")
|
|
878
|
+
credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
|
|
879
|
+
if (providerRaw === "minimax")
|
|
880
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
881
|
+
if (providerRaw === "azure") {
|
|
882
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
883
|
+
credentials.endpoint = await coldPrompt("endpoint: ");
|
|
884
|
+
credentials.deployment = await coldPrompt("deployment: ");
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
|
|
890
|
+
process.stdout.write("i need an API key to power our conversation.\n\n");
|
|
891
|
+
const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
|
|
892
|
+
if (!isAgentProvider(pRaw)) {
|
|
893
|
+
process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
|
|
894
|
+
coldRl.close();
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
providerRaw = pRaw;
|
|
898
|
+
providerConfig = { model: defaultModels[providerRaw] };
|
|
899
|
+
if (providerRaw === "anthropic")
|
|
900
|
+
credentials.setupToken = await coldPrompt("API key: ");
|
|
901
|
+
if (providerRaw === "openai-codex")
|
|
902
|
+
credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
|
|
903
|
+
if (providerRaw === "minimax")
|
|
904
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
905
|
+
if (providerRaw === "azure") {
|
|
906
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
907
|
+
credentials.endpoint = await coldPrompt("endpoint: ");
|
|
908
|
+
credentials.deployment = await coldPrompt("deployment: ");
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
coldRl.close();
|
|
912
|
+
process.stdout.write("\n");
|
|
913
|
+
// Phase 2: configure runtime for adoption specialist
|
|
914
|
+
const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
|
|
915
|
+
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
916
|
+
const secretsRoot2 = path.join(os.homedir(), ".agentsecrets");
|
|
917
|
+
// Suppress non-critical log noise during adoption (no secrets.json, etc.)
|
|
918
|
+
const { setRuntimeLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
|
|
919
|
+
const { createLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves")));
|
|
920
|
+
setRuntimeLogger(createLogger({ level: "error" }));
|
|
921
|
+
// Configure runtime: set agent identity + config override so runAgent
|
|
922
|
+
// doesn't try to read from ~/AgentBundles/AdoptionSpecialist.ouro/
|
|
923
|
+
setAgentName("AdoptionSpecialist");
|
|
924
|
+
// Build specialist system prompt
|
|
925
|
+
const soulText = (0, specialist_orchestrator_1.loadSoulText)(bundleSourceDir);
|
|
926
|
+
const identitiesDir = path.join(bundleSourceDir, "psyche", "identities");
|
|
927
|
+
const identity = (0, specialist_orchestrator_1.pickRandomIdentity)(identitiesDir);
|
|
928
|
+
// Load identity-specific spinner phrases (falls back to DEFAULT_AGENT_PHRASES)
|
|
929
|
+
const { loadIdentityPhrases } = await Promise.resolve().then(() => __importStar(require("./specialist-orchestrator")));
|
|
930
|
+
const phrases = loadIdentityPhrases(bundleSourceDir, identity.fileName);
|
|
931
|
+
setAgentConfigOverride({
|
|
932
|
+
version: 1,
|
|
933
|
+
enabled: true,
|
|
934
|
+
provider: providerRaw,
|
|
935
|
+
phrases,
|
|
936
|
+
});
|
|
937
|
+
patchRuntimeConfig({
|
|
938
|
+
providers: {
|
|
939
|
+
[providerRaw]: { ...providerConfig, ...credentials },
|
|
940
|
+
},
|
|
941
|
+
});
|
|
942
|
+
const existingBundles = (0, specialist_orchestrator_1.listExistingBundles)(bundlesRoot);
|
|
943
|
+
const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles, {
|
|
944
|
+
tempDir,
|
|
945
|
+
provider: providerRaw,
|
|
946
|
+
});
|
|
947
|
+
// Build specialist tools
|
|
948
|
+
const specialistTools = (0, specialist_tools_1.getSpecialistTools)();
|
|
949
|
+
const specialistExecTool = (0, specialist_tools_1.createSpecialistExecTool)({
|
|
950
|
+
tempDir,
|
|
951
|
+
credentials,
|
|
952
|
+
provider: providerRaw,
|
|
953
|
+
bundlesRoot,
|
|
954
|
+
secretsRoot: secretsRoot2,
|
|
955
|
+
animationWriter: (text) => process.stdout.write(text),
|
|
956
|
+
});
|
|
957
|
+
// Run the adoption specialist session via runCliSession
|
|
958
|
+
const result = await runCliSession({
|
|
959
|
+
agentName: "AdoptionSpecialist",
|
|
960
|
+
tools: specialistTools,
|
|
961
|
+
execTool: specialistExecTool,
|
|
962
|
+
exitOnToolCall: "complete_adoption",
|
|
963
|
+
autoFirstTurn: true,
|
|
964
|
+
banner: false,
|
|
965
|
+
disableCommands: true,
|
|
966
|
+
skipSystemPromptRefresh: true,
|
|
967
|
+
messages: [
|
|
968
|
+
{ role: "system", content: systemPrompt },
|
|
969
|
+
{ role: "user", content: "hi" },
|
|
970
|
+
],
|
|
971
|
+
});
|
|
972
|
+
if (result.exitReason === "tool_exit" && result.toolResult) {
|
|
973
|
+
const parsed = typeof result.toolResult === "string" ? JSON.parse(result.toolResult) : result.toolResult;
|
|
974
|
+
if (parsed.success && parsed.agentName) {
|
|
975
|
+
return parsed.agentName;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return null;
|
|
440
979
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
980
|
+
catch (err) {
|
|
981
|
+
process.stderr.write(`\nouro adoption error: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
982
|
+
coldRl.close();
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
finally {
|
|
986
|
+
// Clear specialist config/identity so the hatched agent gets its own
|
|
987
|
+
setAgentConfigOverride(null);
|
|
988
|
+
const { resetProviderRuntime } = await Promise.resolve().then(() => __importStar(require("../core")));
|
|
989
|
+
resetProviderRuntime();
|
|
990
|
+
const { resetConfigCache } = await Promise.resolve().then(() => __importStar(require("../config")));
|
|
991
|
+
resetConfigCache();
|
|
992
|
+
// Restore default logging
|
|
993
|
+
const { setRuntimeLogger: restoreLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
|
|
994
|
+
restoreLogger(null);
|
|
995
|
+
// Clean up temp dir if it still exists
|
|
996
|
+
try {
|
|
997
|
+
if (fs.existsSync(tempDir)) {
|
|
998
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
catch {
|
|
1002
|
+
// Best effort cleanup
|
|
1003
|
+
}
|
|
444
1004
|
}
|
|
445
|
-
const now = new Date().toISOString();
|
|
446
|
-
await friendStore.put(command.friendId, {
|
|
447
|
-
...current,
|
|
448
|
-
externalIds: [
|
|
449
|
-
...current.externalIds,
|
|
450
|
-
{
|
|
451
|
-
provider: command.provider,
|
|
452
|
-
externalId: command.externalId,
|
|
453
|
-
linkedAt: now,
|
|
454
|
-
},
|
|
455
|
-
],
|
|
456
|
-
updatedAt: now,
|
|
457
|
-
});
|
|
458
|
-
return `linked ${command.provider}:${command.externalId} to ${command.friendId}`;
|
|
459
1005
|
}
|
|
460
|
-
|
|
1006
|
+
/* v8 ignore stop */
|
|
1007
|
+
function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SOCKET_PATH) {
|
|
461
1008
|
return {
|
|
462
1009
|
socketPath,
|
|
463
|
-
sendCommand:
|
|
1010
|
+
sendCommand: socket_client_1.sendDaemonCommand,
|
|
464
1011
|
startDaemonProcess: defaultStartDaemonProcess,
|
|
465
1012
|
writeStdout: defaultWriteStdout,
|
|
466
|
-
checkSocketAlive:
|
|
1013
|
+
checkSocketAlive: socket_client_1.checkDaemonSocketAlive,
|
|
467
1014
|
cleanupStaleSocket: defaultCleanupStaleSocket,
|
|
468
1015
|
fallbackPendingMessage: defaultFallbackPendingMessage,
|
|
469
1016
|
installSubagents: defaultInstallSubagents,
|
|
470
|
-
linkFriendIdentity: defaultLinkFriendIdentity,
|
|
471
1017
|
listDiscoveredAgents: defaultListDiscoveredAgents,
|
|
472
1018
|
runHatchFlow: hatch_flow_1.runHatchFlow,
|
|
473
1019
|
promptInput: defaultPromptInput,
|
|
1020
|
+
runAdoptionSpecialist: defaultRunAdoptionSpecialist,
|
|
474
1021
|
registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
|
|
1022
|
+
installOuroCommand: ouro_path_installer_1.installOuroCommand,
|
|
1023
|
+
syncGlobalOuroBotWrapper: ouro_bot_global_installer_1.syncGlobalOuroBotWrapper,
|
|
1024
|
+
ensureDaemonBootPersistence: defaultEnsureDaemonBootPersistence,
|
|
475
1025
|
/* v8 ignore next 3 -- integration: launches interactive CLI session @preserve */
|
|
476
1026
|
startChat: async (agentName) => {
|
|
477
1027
|
const { main } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
478
1028
|
await main(agentName);
|
|
479
1029
|
},
|
|
1030
|
+
scanSessions: async () => {
|
|
1031
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
1032
|
+
const agentRoot = (0, identity_1.getAgentRoot)(agentName);
|
|
1033
|
+
return (0, session_activity_1.listSessionActivity)({
|
|
1034
|
+
sessionsDir: path.join(agentRoot, "state", "sessions"),
|
|
1035
|
+
friendsDir: path.join(agentRoot, "friends"),
|
|
1036
|
+
agentName,
|
|
1037
|
+
}).map((entry) => ({
|
|
1038
|
+
friendId: entry.friendId,
|
|
1039
|
+
friendName: entry.friendName,
|
|
1040
|
+
channel: entry.channel,
|
|
1041
|
+
lastActivity: entry.lastActivityAt,
|
|
1042
|
+
}));
|
|
1043
|
+
},
|
|
480
1044
|
};
|
|
481
1045
|
}
|
|
482
1046
|
function toDaemonCommand(command) {
|
|
@@ -533,12 +1097,236 @@ async function registerOuroBundleTypeNonBlocking(deps) {
|
|
|
533
1097
|
});
|
|
534
1098
|
}
|
|
535
1099
|
}
|
|
1100
|
+
async function performSystemSetup(deps) {
|
|
1101
|
+
// Install ouro command to PATH (non-blocking)
|
|
1102
|
+
if (deps.installOuroCommand) {
|
|
1103
|
+
try {
|
|
1104
|
+
deps.installOuroCommand();
|
|
1105
|
+
}
|
|
1106
|
+
catch (error) {
|
|
1107
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1108
|
+
level: "warn",
|
|
1109
|
+
component: "daemon",
|
|
1110
|
+
event: "daemon.system_setup_ouro_cmd_error",
|
|
1111
|
+
message: "failed to install ouro command to PATH",
|
|
1112
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (deps.syncGlobalOuroBotWrapper) {
|
|
1117
|
+
try {
|
|
1118
|
+
await Promise.resolve(deps.syncGlobalOuroBotWrapper());
|
|
1119
|
+
}
|
|
1120
|
+
catch (error) {
|
|
1121
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1122
|
+
level: "warn",
|
|
1123
|
+
component: "daemon",
|
|
1124
|
+
event: "daemon.system_setup_ouro_bot_wrapper_error",
|
|
1125
|
+
message: "failed to sync global ouro.bot wrapper",
|
|
1126
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
// Install subagents (claude/codex skills)
|
|
1131
|
+
try {
|
|
1132
|
+
await deps.installSubagents();
|
|
1133
|
+
}
|
|
1134
|
+
catch (error) {
|
|
1135
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1136
|
+
level: "warn",
|
|
1137
|
+
component: "daemon",
|
|
1138
|
+
event: "daemon.subagent_install_error",
|
|
1139
|
+
message: "subagent auto-install failed",
|
|
1140
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
// Register .ouro bundle type (UTI on macOS)
|
|
1144
|
+
await registerOuroBundleTypeNonBlocking(deps);
|
|
1145
|
+
}
|
|
1146
|
+
function executeTaskCommand(command, taskMod) {
|
|
1147
|
+
if (command.kind === "task.board") {
|
|
1148
|
+
if (command.status) {
|
|
1149
|
+
const lines = taskMod.boardStatus(command.status);
|
|
1150
|
+
return lines.length > 0 ? lines.join("\n") : "no tasks in that status";
|
|
1151
|
+
}
|
|
1152
|
+
const board = taskMod.getBoard();
|
|
1153
|
+
return board.full || board.compact || "no tasks found";
|
|
1154
|
+
}
|
|
1155
|
+
if (command.kind === "task.create") {
|
|
1156
|
+
try {
|
|
1157
|
+
const created = taskMod.createTask({
|
|
1158
|
+
title: command.title,
|
|
1159
|
+
type: command.type ?? "one-shot",
|
|
1160
|
+
category: "general",
|
|
1161
|
+
body: "",
|
|
1162
|
+
});
|
|
1163
|
+
return `created: ${created}`;
|
|
1164
|
+
}
|
|
1165
|
+
catch (error) {
|
|
1166
|
+
return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (command.kind === "task.update") {
|
|
1170
|
+
const result = taskMod.updateStatus(command.id, command.status);
|
|
1171
|
+
if (!result.ok) {
|
|
1172
|
+
return `error: ${result.reason ?? "status update failed"}`;
|
|
1173
|
+
}
|
|
1174
|
+
const archivedSuffix = result.archived && result.archived.length > 0
|
|
1175
|
+
? ` | archived: ${result.archived.join(", ")}`
|
|
1176
|
+
: "";
|
|
1177
|
+
return `updated: ${command.id} -> ${result.to}${archivedSuffix}`;
|
|
1178
|
+
}
|
|
1179
|
+
if (command.kind === "task.show") {
|
|
1180
|
+
const task = taskMod.getTask(command.id);
|
|
1181
|
+
if (!task)
|
|
1182
|
+
return `task not found: ${command.id}`;
|
|
1183
|
+
return [
|
|
1184
|
+
`title: ${task.title}`,
|
|
1185
|
+
`type: ${task.type}`,
|
|
1186
|
+
`status: ${task.status}`,
|
|
1187
|
+
`category: ${task.category}`,
|
|
1188
|
+
`created: ${task.created}`,
|
|
1189
|
+
`updated: ${task.updated}`,
|
|
1190
|
+
`path: ${task.path}`,
|
|
1191
|
+
task.body ? `\n${task.body}` : "",
|
|
1192
|
+
].filter(Boolean).join("\n");
|
|
1193
|
+
}
|
|
1194
|
+
if (command.kind === "task.actionable") {
|
|
1195
|
+
const lines = taskMod.boardAction();
|
|
1196
|
+
return lines.length > 0 ? lines.join("\n") : "no action required";
|
|
1197
|
+
}
|
|
1198
|
+
if (command.kind === "task.deps") {
|
|
1199
|
+
const lines = taskMod.boardDeps();
|
|
1200
|
+
return lines.length > 0 ? lines.join("\n") : "no unresolved dependencies";
|
|
1201
|
+
}
|
|
1202
|
+
// command.kind === "task.sessions"
|
|
1203
|
+
const lines = taskMod.boardSessions();
|
|
1204
|
+
return lines.length > 0 ? lines.join("\n") : "no active sessions";
|
|
1205
|
+
}
|
|
1206
|
+
const TRUST_RANK = { family: 4, friend: 3, acquaintance: 2, stranger: 1 };
|
|
1207
|
+
/* v8 ignore start -- defensive: ?? fallbacks are unreachable when inputs are valid TrustLevel values @preserve */
|
|
1208
|
+
function higherTrust(a, b) {
|
|
1209
|
+
const rankA = TRUST_RANK[a ?? "stranger"] ?? 1;
|
|
1210
|
+
const rankB = TRUST_RANK[b ?? "stranger"] ?? 1;
|
|
1211
|
+
return rankA >= rankB ? (a ?? "stranger") : (b ?? "stranger");
|
|
1212
|
+
}
|
|
1213
|
+
/* v8 ignore stop */
|
|
1214
|
+
async function executeFriendCommand(command, store) {
|
|
1215
|
+
if (command.kind === "friend.list") {
|
|
1216
|
+
const listAll = store.listAll;
|
|
1217
|
+
if (!listAll)
|
|
1218
|
+
return "friend store does not support listing";
|
|
1219
|
+
const friends = await listAll.call(store);
|
|
1220
|
+
if (friends.length === 0)
|
|
1221
|
+
return "no friends found";
|
|
1222
|
+
const lines = friends.map((f) => {
|
|
1223
|
+
const trust = f.trustLevel ?? "unknown";
|
|
1224
|
+
return `${f.id} ${f.name} ${trust}`;
|
|
1225
|
+
});
|
|
1226
|
+
return lines.join("\n");
|
|
1227
|
+
}
|
|
1228
|
+
if (command.kind === "friend.show") {
|
|
1229
|
+
const record = await store.get(command.friendId);
|
|
1230
|
+
if (!record)
|
|
1231
|
+
return `friend not found: ${command.friendId}`;
|
|
1232
|
+
return JSON.stringify(record, null, 2);
|
|
1233
|
+
}
|
|
1234
|
+
if (command.kind === "friend.create") {
|
|
1235
|
+
const now = new Date().toISOString();
|
|
1236
|
+
const id = (0, crypto_1.randomUUID)();
|
|
1237
|
+
const trustLevel = (command.trustLevel ?? "acquaintance");
|
|
1238
|
+
await store.put(id, {
|
|
1239
|
+
id,
|
|
1240
|
+
name: command.name,
|
|
1241
|
+
trustLevel,
|
|
1242
|
+
externalIds: [],
|
|
1243
|
+
tenantMemberships: [],
|
|
1244
|
+
toolPreferences: {},
|
|
1245
|
+
notes: {},
|
|
1246
|
+
totalTokens: 0,
|
|
1247
|
+
createdAt: now,
|
|
1248
|
+
updatedAt: now,
|
|
1249
|
+
schemaVersion: 1,
|
|
1250
|
+
});
|
|
1251
|
+
return `created: ${id} (${command.name}, ${trustLevel})`;
|
|
1252
|
+
}
|
|
1253
|
+
if (command.kind === "friend.link") {
|
|
1254
|
+
const current = await store.get(command.friendId);
|
|
1255
|
+
if (!current)
|
|
1256
|
+
return `friend not found: ${command.friendId}`;
|
|
1257
|
+
const alreadyLinked = current.externalIds.some((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
|
|
1258
|
+
if (alreadyLinked)
|
|
1259
|
+
return `identity already linked: ${command.provider}:${command.externalId}`;
|
|
1260
|
+
const now = new Date().toISOString();
|
|
1261
|
+
const newExternalIds = [
|
|
1262
|
+
...current.externalIds,
|
|
1263
|
+
{ provider: command.provider, externalId: command.externalId, linkedAt: now },
|
|
1264
|
+
];
|
|
1265
|
+
// Orphan cleanup: check if another friend has this externalId
|
|
1266
|
+
const orphan = await store.findByExternalId(command.provider, command.externalId);
|
|
1267
|
+
let mergeMessage = "";
|
|
1268
|
+
let mergedNotes = { ...current.notes };
|
|
1269
|
+
let mergedTrust = current.trustLevel;
|
|
1270
|
+
let orphanExternalIds = [];
|
|
1271
|
+
if (orphan && orphan.id !== command.friendId) {
|
|
1272
|
+
// Merge orphan's notes (target's notes take priority)
|
|
1273
|
+
mergedNotes = { ...orphan.notes, ...current.notes };
|
|
1274
|
+
// Keep higher trust level
|
|
1275
|
+
mergedTrust = higherTrust(current.trustLevel, orphan.trustLevel);
|
|
1276
|
+
// Collect orphan's other externalIds (excluding the one being linked)
|
|
1277
|
+
orphanExternalIds = orphan.externalIds.filter((ext) => !(ext.provider === command.provider && ext.externalId === command.externalId));
|
|
1278
|
+
await store.delete(orphan.id);
|
|
1279
|
+
mergeMessage = ` (merged orphan ${orphan.id})`;
|
|
1280
|
+
}
|
|
1281
|
+
await store.put(command.friendId, {
|
|
1282
|
+
...current,
|
|
1283
|
+
externalIds: [...newExternalIds, ...orphanExternalIds],
|
|
1284
|
+
notes: mergedNotes,
|
|
1285
|
+
trustLevel: mergedTrust,
|
|
1286
|
+
updatedAt: now,
|
|
1287
|
+
});
|
|
1288
|
+
return `linked ${command.provider}:${command.externalId} to ${command.friendId}${mergeMessage}`;
|
|
1289
|
+
}
|
|
1290
|
+
// command.kind === "friend.unlink"
|
|
1291
|
+
const current = await store.get(command.friendId);
|
|
1292
|
+
if (!current)
|
|
1293
|
+
return `friend not found: ${command.friendId}`;
|
|
1294
|
+
const idx = current.externalIds.findIndex((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
|
|
1295
|
+
if (idx === -1)
|
|
1296
|
+
return `identity not linked: ${command.provider}:${command.externalId}`;
|
|
1297
|
+
const now = new Date().toISOString();
|
|
1298
|
+
const filtered = current.externalIds.filter((_, i) => i !== idx);
|
|
1299
|
+
await store.put(command.friendId, { ...current, externalIds: filtered, updatedAt: now });
|
|
1300
|
+
return `unlinked ${command.provider}:${command.externalId} from ${command.friendId}`;
|
|
1301
|
+
}
|
|
1302
|
+
function executeReminderCommand(command, taskMod) {
|
|
1303
|
+
try {
|
|
1304
|
+
const created = taskMod.createTask({
|
|
1305
|
+
title: command.title,
|
|
1306
|
+
type: command.cadence ? "habit" : "one-shot",
|
|
1307
|
+
category: command.category ?? "reminder",
|
|
1308
|
+
body: command.body,
|
|
1309
|
+
scheduledAt: command.scheduledAt,
|
|
1310
|
+
cadence: command.cadence,
|
|
1311
|
+
requester: command.requester,
|
|
1312
|
+
});
|
|
1313
|
+
return `created: ${created}`;
|
|
1314
|
+
}
|
|
1315
|
+
catch (error) {
|
|
1316
|
+
return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
536
1319
|
async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
537
1320
|
if (args.includes("--help") || args.includes("-h")) {
|
|
538
1321
|
const text = usage();
|
|
539
1322
|
deps.writeStdout(text);
|
|
540
1323
|
return text;
|
|
541
1324
|
}
|
|
1325
|
+
if (args.length === 1 && (args[0] === "-v" || args[0] === "--version")) {
|
|
1326
|
+
const text = formatVersionOutput();
|
|
1327
|
+
deps.writeStdout(text);
|
|
1328
|
+
return text;
|
|
1329
|
+
}
|
|
542
1330
|
let command;
|
|
543
1331
|
try {
|
|
544
1332
|
command = parseOuroCommand(args);
|
|
@@ -557,23 +1345,12 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
557
1345
|
if (args.length === 0) {
|
|
558
1346
|
const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
|
|
559
1347
|
if (discovered.length === 0 && deps.runAdoptionSpecialist) {
|
|
1348
|
+
// System setup first — ouro command, subagents, UTI — before the interactive specialist
|
|
1349
|
+
await performSystemSetup(deps);
|
|
560
1350
|
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
561
1351
|
if (!hatchlingName) {
|
|
562
1352
|
return "";
|
|
563
1353
|
}
|
|
564
|
-
try {
|
|
565
|
-
await deps.installSubagents();
|
|
566
|
-
}
|
|
567
|
-
catch (error) {
|
|
568
|
-
(0, runtime_1.emitNervesEvent)({
|
|
569
|
-
level: "warn",
|
|
570
|
-
component: "daemon",
|
|
571
|
-
event: "daemon.subagent_install_error",
|
|
572
|
-
message: "subagent auto-install failed",
|
|
573
|
-
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
577
1354
|
await ensureDaemonRunning(deps);
|
|
578
1355
|
if (deps.startChat) {
|
|
579
1356
|
await deps.startChat(hatchlingName);
|
|
@@ -620,19 +1397,34 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
620
1397
|
meta: { kind: command.kind },
|
|
621
1398
|
});
|
|
622
1399
|
if (command.kind === "daemon.up") {
|
|
623
|
-
|
|
624
|
-
|
|
1400
|
+
await performSystemSetup(deps);
|
|
1401
|
+
if (deps.ensureDaemonBootPersistence) {
|
|
1402
|
+
try {
|
|
1403
|
+
await Promise.resolve(deps.ensureDaemonBootPersistence(deps.socketPath));
|
|
1404
|
+
}
|
|
1405
|
+
catch (error) {
|
|
1406
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1407
|
+
level: "warn",
|
|
1408
|
+
component: "daemon",
|
|
1409
|
+
event: "daemon.system_setup_launchd_error",
|
|
1410
|
+
message: "failed to persist daemon boot startup",
|
|
1411
|
+
meta: { error: error instanceof Error ? error.message : String(error), socketPath: deps.socketPath },
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
625
1414
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
1415
|
+
// Run update hooks before starting daemon so user sees the output
|
|
1416
|
+
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
1417
|
+
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
1418
|
+
const currentVersion = (0, bundle_manifest_1.getPackageVersion)();
|
|
1419
|
+
const updateSummary = await (0, update_hooks_1.applyPendingUpdates)(bundlesRoot, currentVersion);
|
|
1420
|
+
if (updateSummary.updated.length > 0) {
|
|
1421
|
+
const agents = updateSummary.updated.map((e) => e.agent);
|
|
1422
|
+
const from = updateSummary.updated[0].from;
|
|
1423
|
+
const to = updateSummary.updated[0].to;
|
|
1424
|
+
const fromStr = from ? ` (was ${from})` : "";
|
|
1425
|
+
const count = agents.length;
|
|
1426
|
+
deps.writeStdout(`updated ${count} agent${count === 1 ? "" : "s"} to runtime ${to}${fromStr}`);
|
|
634
1427
|
}
|
|
635
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
636
1428
|
const daemonResult = await ensureDaemonRunning(deps);
|
|
637
1429
|
deps.writeStdout(daemonResult.message);
|
|
638
1430
|
return daemonResult.message;
|
|
@@ -641,13 +1433,155 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
641
1433
|
deps.tailLogs();
|
|
642
1434
|
return "";
|
|
643
1435
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
1436
|
+
// ── task subcommands (local, no daemon socket needed) ──
|
|
1437
|
+
if (command.kind === "task.board" || command.kind === "task.create" || command.kind === "task.update" ||
|
|
1438
|
+
command.kind === "task.show" || command.kind === "task.actionable" || command.kind === "task.deps" ||
|
|
1439
|
+
command.kind === "task.sessions") {
|
|
1440
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1441
|
+
const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
|
|
1442
|
+
/* v8 ignore stop */
|
|
1443
|
+
const message = executeTaskCommand(command, taskMod);
|
|
1444
|
+
deps.writeStdout(message);
|
|
1445
|
+
return message;
|
|
1446
|
+
}
|
|
1447
|
+
// ── reminder subcommands (local, no daemon socket needed) ──
|
|
1448
|
+
if (command.kind === "reminder.create") {
|
|
1449
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1450
|
+
const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
|
|
1451
|
+
/* v8 ignore stop */
|
|
1452
|
+
const message = executeReminderCommand(command, taskMod);
|
|
1453
|
+
deps.writeStdout(message);
|
|
1454
|
+
return message;
|
|
1455
|
+
}
|
|
1456
|
+
// ── friend subcommands (local, no daemon socket needed) ──
|
|
1457
|
+
if (command.kind === "friend.list" || command.kind === "friend.show" || command.kind === "friend.create" ||
|
|
1458
|
+
command.kind === "friend.link" || command.kind === "friend.unlink") {
|
|
1459
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1460
|
+
let store = deps.friendStore;
|
|
1461
|
+
if (!store) {
|
|
1462
|
+
// Derive agent-scoped friends dir from --agent flag or link/unlink's agent field
|
|
1463
|
+
const agentName = ("agent" in command && command.agent) ? command.agent : undefined;
|
|
1464
|
+
const friendsDir = agentName
|
|
1465
|
+
? path.join((0, identity_1.getAgentBundlesRoot)(), `${agentName}.ouro`, "friends")
|
|
1466
|
+
: path.join((0, identity_1.getAgentBundlesRoot)(), "friends");
|
|
1467
|
+
store = new store_file_1.FileFriendStore(friendsDir);
|
|
1468
|
+
}
|
|
1469
|
+
/* v8 ignore stop */
|
|
1470
|
+
const message = await executeFriendCommand(command, store);
|
|
1471
|
+
deps.writeStdout(message);
|
|
1472
|
+
return message;
|
|
1473
|
+
}
|
|
1474
|
+
// ── whoami (local, no daemon socket needed) ──
|
|
1475
|
+
if (command.kind === "whoami") {
|
|
1476
|
+
if (command.agent) {
|
|
1477
|
+
const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
|
|
1478
|
+
const message = [
|
|
1479
|
+
`agent: ${command.agent}`,
|
|
1480
|
+
`home: ${agentRoot}`,
|
|
1481
|
+
`bones: ${(0, runtime_metadata_1.getRuntimeMetadata)().version}`,
|
|
1482
|
+
].join("\n");
|
|
1483
|
+
deps.writeStdout(message);
|
|
1484
|
+
return message;
|
|
1485
|
+
}
|
|
1486
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1487
|
+
try {
|
|
1488
|
+
const info = deps.whoamiInfo
|
|
1489
|
+
? deps.whoamiInfo()
|
|
1490
|
+
: {
|
|
1491
|
+
agentName: (0, identity_1.getAgentName)(),
|
|
1492
|
+
homePath: path.join((0, identity_1.getAgentBundlesRoot)(), `${(0, identity_1.getAgentName)()}.ouro`),
|
|
1493
|
+
bonesVersion: (0, runtime_metadata_1.getRuntimeMetadata)().version,
|
|
1494
|
+
};
|
|
1495
|
+
const message = [
|
|
1496
|
+
`agent: ${info.agentName}`,
|
|
1497
|
+
`home: ${info.homePath}`,
|
|
1498
|
+
`bones: ${info.bonesVersion}`,
|
|
1499
|
+
].join("\n");
|
|
1500
|
+
deps.writeStdout(message);
|
|
1501
|
+
return message;
|
|
1502
|
+
}
|
|
1503
|
+
catch {
|
|
1504
|
+
const message = "error: no agent context — use --agent <name> to specify";
|
|
1505
|
+
deps.writeStdout(message);
|
|
1506
|
+
return message;
|
|
1507
|
+
}
|
|
1508
|
+
/* v8 ignore stop */
|
|
1509
|
+
}
|
|
1510
|
+
// ── thoughts (local, no daemon socket needed) ──
|
|
1511
|
+
if (command.kind === "thoughts") {
|
|
1512
|
+
try {
|
|
1513
|
+
const agentName = command.agent ?? (0, identity_1.getAgentName)();
|
|
1514
|
+
const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${agentName}.ouro`);
|
|
1515
|
+
const sessionFilePath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
|
|
1516
|
+
if (command.json) {
|
|
1517
|
+
try {
|
|
1518
|
+
const raw = fs.readFileSync(sessionFilePath, "utf-8");
|
|
1519
|
+
deps.writeStdout(raw);
|
|
1520
|
+
return raw;
|
|
1521
|
+
}
|
|
1522
|
+
catch {
|
|
1523
|
+
const message = "no inner dialog session found";
|
|
1524
|
+
deps.writeStdout(message);
|
|
1525
|
+
return message;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
const turns = (0, thoughts_1.parseInnerDialogSession)(sessionFilePath);
|
|
1529
|
+
const message = (0, thoughts_1.formatThoughtTurns)(turns, command.last ?? 10);
|
|
1530
|
+
deps.writeStdout(message);
|
|
1531
|
+
if (command.follow) {
|
|
1532
|
+
deps.writeStdout("\n\n--- following (ctrl+c to stop) ---\n");
|
|
1533
|
+
/* v8 ignore start -- callback tested via followThoughts unit tests @preserve */
|
|
1534
|
+
const stop = (0, thoughts_1.followThoughts)(sessionFilePath, (formatted) => {
|
|
1535
|
+
deps.writeStdout("\n" + formatted);
|
|
1536
|
+
});
|
|
1537
|
+
/* v8 ignore stop */
|
|
1538
|
+
// Block until process exit; cleanup watcher on SIGINT/SIGTERM
|
|
1539
|
+
return new Promise((resolve) => {
|
|
1540
|
+
const cleanup = () => { stop(); resolve(message); };
|
|
1541
|
+
process.once("SIGINT", cleanup);
|
|
1542
|
+
process.once("SIGTERM", cleanup);
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
return message;
|
|
1546
|
+
}
|
|
1547
|
+
catch {
|
|
1548
|
+
const message = "error: no agent context — use --agent <name> to specify";
|
|
1549
|
+
deps.writeStdout(message);
|
|
1550
|
+
return message;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
// ── session list (local, no daemon socket needed) ──
|
|
1554
|
+
if (command.kind === "session.list") {
|
|
1555
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1556
|
+
const scanner = deps.scanSessions ?? (async () => []);
|
|
1557
|
+
/* v8 ignore stop */
|
|
1558
|
+
const sessions = await scanner();
|
|
1559
|
+
if (sessions.length === 0) {
|
|
1560
|
+
const message = "no active sessions";
|
|
1561
|
+
deps.writeStdout(message);
|
|
1562
|
+
return message;
|
|
1563
|
+
}
|
|
1564
|
+
const lines = sessions.map((s) => `${s.friendId} ${s.friendName} ${s.channel} ${s.lastActivity}`);
|
|
1565
|
+
const message = lines.join("\n");
|
|
647
1566
|
deps.writeStdout(message);
|
|
648
1567
|
return message;
|
|
649
1568
|
}
|
|
650
1569
|
if (command.kind === "hatch.start") {
|
|
1570
|
+
// Route through adoption specialist when no explicit hatch args were provided
|
|
1571
|
+
const hasExplicitHatchArgs = !!(command.agentName || command.humanName || command.provider || command.credentials);
|
|
1572
|
+
if (deps.runAdoptionSpecialist && !hasExplicitHatchArgs) {
|
|
1573
|
+
// System setup first — ouro command, subagents, UTI — before the interactive specialist
|
|
1574
|
+
await performSystemSetup(deps);
|
|
1575
|
+
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
1576
|
+
if (!hatchlingName) {
|
|
1577
|
+
return "";
|
|
1578
|
+
}
|
|
1579
|
+
await ensureDaemonRunning(deps);
|
|
1580
|
+
if (deps.startChat) {
|
|
1581
|
+
await deps.startChat(hatchlingName);
|
|
1582
|
+
}
|
|
1583
|
+
return "";
|
|
1584
|
+
}
|
|
651
1585
|
const hatchRunner = deps.runHatchFlow;
|
|
652
1586
|
if (!hatchRunner) {
|
|
653
1587
|
const response = await deps.sendCommand(deps.socketPath, { kind: "hatch.start" });
|
|
@@ -657,19 +1591,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
657
1591
|
}
|
|
658
1592
|
const hatchInput = await resolveHatchInput(command, deps);
|
|
659
1593
|
const result = await hatchRunner(hatchInput);
|
|
660
|
-
|
|
661
|
-
await deps.installSubagents();
|
|
662
|
-
}
|
|
663
|
-
catch (error) {
|
|
664
|
-
(0, runtime_1.emitNervesEvent)({
|
|
665
|
-
level: "warn",
|
|
666
|
-
component: "daemon",
|
|
667
|
-
event: "daemon.subagent_install_error",
|
|
668
|
-
message: "subagent auto-install failed",
|
|
669
|
-
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
1594
|
+
await performSystemSetup(deps);
|
|
673
1595
|
const daemonResult = await ensureDaemonRunning(deps);
|
|
674
1596
|
if (deps.startChat) {
|
|
675
1597
|
await deps.startChat(hatchInput.agentName);
|
|
@@ -691,9 +1613,22 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
691
1613
|
deps.writeStdout(message);
|
|
692
1614
|
return message;
|
|
693
1615
|
}
|
|
1616
|
+
if (command.kind === "daemon.status" && isDaemonUnavailableError(error)) {
|
|
1617
|
+
const message = daemonUnavailableStatusOutput(deps.socketPath);
|
|
1618
|
+
deps.writeStdout(message);
|
|
1619
|
+
return message;
|
|
1620
|
+
}
|
|
1621
|
+
if (command.kind === "daemon.stop" && isDaemonUnavailableError(error)) {
|
|
1622
|
+
const message = "daemon not running";
|
|
1623
|
+
deps.writeStdout(message);
|
|
1624
|
+
return message;
|
|
1625
|
+
}
|
|
694
1626
|
throw error;
|
|
695
1627
|
}
|
|
696
|
-
const
|
|
1628
|
+
const fallbackMessage = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
|
|
1629
|
+
const message = command.kind === "daemon.status"
|
|
1630
|
+
? formatDaemonStatusOutput(response, fallbackMessage)
|
|
1631
|
+
: fallbackMessage;
|
|
697
1632
|
deps.writeStdout(message);
|
|
698
1633
|
return message;
|
|
699
1634
|
}
|