@ouro.bot/cli 0.1.0-alpha.7 → 0.1.0-alpha.70
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 +147 -205
- package/assets/ouroboros.png +0 -0
- package/changelog.json +387 -0
- package/dist/heart/active-work.js +178 -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 +68 -23
- package/dist/heart/core.js +282 -92
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +409 -0
- package/dist/heart/daemon/daemon-cli.js +1395 -248
- package/dist/heart/daemon/daemon-entry.js +55 -6
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +216 -10
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -82
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- 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 +14 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +307 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -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/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 +126 -21
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +74 -9
- package/dist/heart/providers/azure.js +86 -7
- package/dist/heart/providers/github-copilot.js +149 -0
- package/dist/heart/providers/minimax.js +4 -0
- package/dist/heart/providers/openai-codex.js +12 -3
- package/dist/heart/safe-workspace.js +228 -0
- 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 +100 -22
- package/dist/heart/target-resolution.js +123 -0
- 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 +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/pending.js +72 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +358 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- 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/guardrails.js +279 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +195 -0
- package/dist/repertoire/skills.js +3 -26
- 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 +642 -251
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +93 -52
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +893 -45
- package/dist/senses/cli-layout.js +87 -0
- package/dist/senses/cli.js +348 -144
- 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 +333 -84
- package/dist/senses/pipeline.js +278 -0
- package/dist/senses/teams.js +573 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/heart/daemon/subagent-installer.js +0 -125
- package/dist/inner-worker-entry.js +0 -4
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
|
@@ -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,16 +48,201 @@ 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");
|
|
50
|
-
const
|
|
51
|
+
const ouro_path_installer_1 = require("./ouro-path-installer");
|
|
52
|
+
const skill_management_installer_1 = require("./skill-management-installer");
|
|
51
53
|
const hatch_flow_1 = require("./hatch-flow");
|
|
52
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 runtime_mode_1 = require("./runtime-mode");
|
|
59
|
+
const daemon_runtime_sync_1 = require("./daemon-runtime-sync");
|
|
60
|
+
const agent_discovery_1 = require("./agent-discovery");
|
|
61
|
+
const update_hooks_1 = require("./update-hooks");
|
|
62
|
+
const bundle_meta_1 = require("./hooks/bundle-meta");
|
|
63
|
+
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
64
|
+
const tasks_1 = require("../../repertoire/tasks");
|
|
65
|
+
const thoughts_1 = require("./thoughts");
|
|
66
|
+
const ouro_bot_global_installer_1 = require("./ouro-bot-global-installer");
|
|
67
|
+
const launchd_1 = require("./launchd");
|
|
68
|
+
const socket_client_1 = require("./socket-client");
|
|
69
|
+
const session_activity_1 = require("../session-activity");
|
|
70
|
+
const auth_flow_1 = require("./auth-flow");
|
|
71
|
+
function stringField(value) {
|
|
72
|
+
return typeof value === "string" ? value : null;
|
|
73
|
+
}
|
|
74
|
+
function numberField(value) {
|
|
75
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
76
|
+
}
|
|
77
|
+
function booleanField(value) {
|
|
78
|
+
return typeof value === "boolean" ? value : null;
|
|
79
|
+
}
|
|
80
|
+
function parseStatusPayload(data) {
|
|
81
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
82
|
+
return null;
|
|
83
|
+
const raw = data;
|
|
84
|
+
const overview = raw.overview;
|
|
85
|
+
const senses = raw.senses;
|
|
86
|
+
const workers = raw.workers;
|
|
87
|
+
if (!overview || typeof overview !== "object" || Array.isArray(overview))
|
|
88
|
+
return null;
|
|
89
|
+
if (!Array.isArray(senses) || !Array.isArray(workers))
|
|
90
|
+
return null;
|
|
91
|
+
const parsedOverview = {
|
|
92
|
+
daemon: stringField(overview.daemon) ?? "unknown",
|
|
93
|
+
health: stringField(overview.health) ?? "unknown",
|
|
94
|
+
socketPath: stringField(overview.socketPath) ?? "unknown",
|
|
95
|
+
version: stringField(overview.version) ?? "unknown",
|
|
96
|
+
lastUpdated: stringField(overview.lastUpdated) ?? "unknown",
|
|
97
|
+
repoRoot: stringField(overview.repoRoot) ?? "unknown",
|
|
98
|
+
configFingerprint: stringField(overview.configFingerprint) ?? "unknown",
|
|
99
|
+
workerCount: numberField(overview.workerCount) ?? 0,
|
|
100
|
+
senseCount: numberField(overview.senseCount) ?? 0,
|
|
101
|
+
entryPath: stringField(overview.entryPath) ?? "unknown",
|
|
102
|
+
mode: stringField(overview.mode) ?? "unknown",
|
|
103
|
+
};
|
|
104
|
+
const parsedSenses = senses.map((entry) => {
|
|
105
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
106
|
+
return null;
|
|
107
|
+
const row = entry;
|
|
108
|
+
const agent = stringField(row.agent);
|
|
109
|
+
const sense = stringField(row.sense);
|
|
110
|
+
const status = stringField(row.status);
|
|
111
|
+
const detail = stringField(row.detail);
|
|
112
|
+
const enabled = booleanField(row.enabled);
|
|
113
|
+
if (!agent || !sense || !status || detail === null || enabled === null)
|
|
114
|
+
return null;
|
|
115
|
+
return {
|
|
116
|
+
agent,
|
|
117
|
+
sense,
|
|
118
|
+
label: stringField(row.label) ?? undefined,
|
|
119
|
+
enabled,
|
|
120
|
+
status,
|
|
121
|
+
detail,
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
const parsedWorkers = workers.map((entry) => {
|
|
125
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
126
|
+
return null;
|
|
127
|
+
const row = entry;
|
|
128
|
+
const agent = stringField(row.agent);
|
|
129
|
+
const worker = stringField(row.worker);
|
|
130
|
+
const status = stringField(row.status);
|
|
131
|
+
const restartCount = numberField(row.restartCount);
|
|
132
|
+
const hasPid = Object.prototype.hasOwnProperty.call(row, "pid");
|
|
133
|
+
const pid = row.pid === null ? null : numberField(row.pid);
|
|
134
|
+
const pidInvalid = !hasPid || (row.pid !== null && pid === null);
|
|
135
|
+
if (!agent || !worker || !status || restartCount === null || pidInvalid)
|
|
136
|
+
return null;
|
|
137
|
+
return {
|
|
138
|
+
agent,
|
|
139
|
+
worker,
|
|
140
|
+
status,
|
|
141
|
+
pid,
|
|
142
|
+
restartCount,
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
if (parsedSenses.some((row) => row === null) || parsedWorkers.some((row) => row === null))
|
|
146
|
+
return null;
|
|
147
|
+
return {
|
|
148
|
+
overview: parsedOverview,
|
|
149
|
+
senses: parsedSenses,
|
|
150
|
+
workers: parsedWorkers,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function humanizeSenseName(sense, label) {
|
|
154
|
+
if (label)
|
|
155
|
+
return label;
|
|
156
|
+
if (sense === "cli")
|
|
157
|
+
return "CLI";
|
|
158
|
+
if (sense === "bluebubbles")
|
|
159
|
+
return "BlueBubbles";
|
|
160
|
+
if (sense === "teams")
|
|
161
|
+
return "Teams";
|
|
162
|
+
return sense;
|
|
163
|
+
}
|
|
164
|
+
function formatTable(headers, rows) {
|
|
165
|
+
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index].length)));
|
|
166
|
+
const renderRow = (row) => `| ${row.map((cell, index) => cell.padEnd(widths[index])).join(" | ")} |`;
|
|
167
|
+
const divider = `|-${widths.map((width) => "-".repeat(width)).join("-|-")}-|`;
|
|
168
|
+
return [
|
|
169
|
+
renderRow(headers),
|
|
170
|
+
divider,
|
|
171
|
+
...rows.map(renderRow),
|
|
172
|
+
].join("\n");
|
|
173
|
+
}
|
|
174
|
+
function formatDaemonStatusOutput(response, fallback) {
|
|
175
|
+
const payload = parseStatusPayload(response.data);
|
|
176
|
+
if (!payload)
|
|
177
|
+
return fallback;
|
|
178
|
+
const overviewRows = [
|
|
179
|
+
["Daemon", payload.overview.daemon],
|
|
180
|
+
["Socket", payload.overview.socketPath],
|
|
181
|
+
["Version", payload.overview.version],
|
|
182
|
+
["Last Updated", payload.overview.lastUpdated],
|
|
183
|
+
["Entry Path", payload.overview.entryPath],
|
|
184
|
+
["Mode", payload.overview.mode],
|
|
185
|
+
["Workers", String(payload.overview.workerCount)],
|
|
186
|
+
["Senses", String(payload.overview.senseCount)],
|
|
187
|
+
["Health", payload.overview.health],
|
|
188
|
+
];
|
|
189
|
+
const senseRows = payload.senses.map((row) => [
|
|
190
|
+
row.agent,
|
|
191
|
+
humanizeSenseName(row.sense, row.label),
|
|
192
|
+
row.enabled ? "ON" : "OFF",
|
|
193
|
+
row.status,
|
|
194
|
+
row.detail,
|
|
195
|
+
]);
|
|
196
|
+
const workerRows = payload.workers.map((row) => [
|
|
197
|
+
row.agent,
|
|
198
|
+
row.worker,
|
|
199
|
+
row.status,
|
|
200
|
+
row.pid === null ? "n/a" : String(row.pid),
|
|
201
|
+
String(row.restartCount),
|
|
202
|
+
]);
|
|
203
|
+
return [
|
|
204
|
+
"Overview",
|
|
205
|
+
formatTable(["Item", "Value"], overviewRows),
|
|
206
|
+
"",
|
|
207
|
+
"Senses",
|
|
208
|
+
formatTable(["Agent", "Sense", "Enabled", "State", "Detail"], senseRows),
|
|
209
|
+
"",
|
|
210
|
+
"Workers",
|
|
211
|
+
formatTable(["Agent", "Worker", "State", "PID", "Restarts"], workerRows),
|
|
212
|
+
].join("\n");
|
|
213
|
+
}
|
|
53
214
|
async function ensureDaemonRunning(deps) {
|
|
54
215
|
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
55
216
|
if (alive) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
217
|
+
const localRuntime = (0, runtime_metadata_1.getRuntimeMetadata)();
|
|
218
|
+
let runningRuntimePromise = null;
|
|
219
|
+
const fetchRunningRuntimeMetadata = async () => {
|
|
220
|
+
runningRuntimePromise ??= (async () => {
|
|
221
|
+
const status = await deps.sendCommand(deps.socketPath, { kind: "daemon.status" });
|
|
222
|
+
const payload = parseStatusPayload(status.data);
|
|
223
|
+
return {
|
|
224
|
+
version: payload?.overview.version ?? "unknown",
|
|
225
|
+
lastUpdated: payload?.overview.lastUpdated ?? "unknown",
|
|
226
|
+
repoRoot: payload?.overview.repoRoot ?? "unknown",
|
|
227
|
+
configFingerprint: payload?.overview.configFingerprint ?? "unknown",
|
|
228
|
+
};
|
|
229
|
+
})();
|
|
230
|
+
return runningRuntimePromise;
|
|
59
231
|
};
|
|
232
|
+
return (0, daemon_runtime_sync_1.ensureCurrentDaemonRuntime)({
|
|
233
|
+
socketPath: deps.socketPath,
|
|
234
|
+
localVersion: localRuntime.version,
|
|
235
|
+
localLastUpdated: localRuntime.lastUpdated,
|
|
236
|
+
localRepoRoot: localRuntime.repoRoot,
|
|
237
|
+
localConfigFingerprint: localRuntime.configFingerprint,
|
|
238
|
+
fetchRunningVersion: async () => (await fetchRunningRuntimeMetadata()).version,
|
|
239
|
+
fetchRunningRuntimeMetadata,
|
|
240
|
+
stopDaemon: async () => {
|
|
241
|
+
await deps.sendCommand(deps.socketPath, { kind: "daemon.stop" });
|
|
242
|
+
},
|
|
243
|
+
cleanupStaleSocket: deps.cleanupStaleSocket,
|
|
244
|
+
startDaemonProcess: deps.startDaemonProcess,
|
|
245
|
+
});
|
|
60
246
|
}
|
|
61
247
|
deps.cleanupStaleSocket(deps.socketPath);
|
|
62
248
|
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
@@ -65,17 +251,90 @@ async function ensureDaemonRunning(deps) {
|
|
|
65
251
|
message: `daemon started (pid ${started.pid ?? "unknown"})`,
|
|
66
252
|
};
|
|
67
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Extract `--agent <name>` from an args array, returning the agent name and
|
|
256
|
+
* the remaining args with the flag pair removed.
|
|
257
|
+
*/
|
|
258
|
+
function extractAgentFlag(args) {
|
|
259
|
+
const idx = args.indexOf("--agent");
|
|
260
|
+
if (idx === -1 || idx + 1 >= args.length)
|
|
261
|
+
return { rest: args };
|
|
262
|
+
const agent = args[idx + 1];
|
|
263
|
+
const rest = [...args.slice(0, idx), ...args.slice(idx + 2)];
|
|
264
|
+
return { agent, rest };
|
|
265
|
+
}
|
|
68
266
|
function usage() {
|
|
69
267
|
return [
|
|
70
268
|
"Usage:",
|
|
71
269
|
" ouro [up]",
|
|
72
|
-
" ouro stop|status|logs|hatch",
|
|
270
|
+
" ouro stop|down|status|logs|hatch",
|
|
271
|
+
" ouro -v|--version",
|
|
272
|
+
" ouro auth --agent <name> [--provider <provider>]",
|
|
273
|
+
" ouro auth verify --agent <name> [--provider <provider>]",
|
|
274
|
+
" ouro auth switch --agent <name> --provider <provider>",
|
|
73
275
|
" ouro chat <agent>",
|
|
74
276
|
" ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
|
|
75
277
|
" ouro poke <agent> --task <task-id>",
|
|
76
278
|
" ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
|
|
279
|
+
" ouro task board [<status>] [--agent <name>]",
|
|
280
|
+
" ouro task create <title> [--type <type>] [--agent <name>]",
|
|
281
|
+
" ouro task update <id> <status> [--agent <name>]",
|
|
282
|
+
" ouro task show <id> [--agent <name>]",
|
|
283
|
+
" ouro task actionable|deps|sessions [--agent <name>]",
|
|
284
|
+
" ouro reminder create <title> --body <body> [--at <iso>] [--cadence <interval>] [--category <category>] [--agent <name>]",
|
|
285
|
+
" ouro friend list [--agent <name>]",
|
|
286
|
+
" ouro friend show <id> [--agent <name>]",
|
|
287
|
+
" ouro friend create --name <name> [--trust <level>] [--agent <name>]",
|
|
288
|
+
" ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
|
|
289
|
+
" ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
290
|
+
" ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
291
|
+
" ouro whoami [--agent <name>]",
|
|
292
|
+
" ouro session list [--agent <name>]",
|
|
293
|
+
" ouro mcp list",
|
|
294
|
+
" ouro mcp call <server> <tool> [--args '{...}']",
|
|
77
295
|
].join("\n");
|
|
78
296
|
}
|
|
297
|
+
function formatVersionOutput() {
|
|
298
|
+
return (0, runtime_metadata_1.getRuntimeMetadata)().version;
|
|
299
|
+
}
|
|
300
|
+
function buildStoppedStatusPayload(socketPath) {
|
|
301
|
+
const metadata = (0, runtime_metadata_1.getRuntimeMetadata)();
|
|
302
|
+
const repoRoot = (0, identity_1.getRepoRoot)();
|
|
303
|
+
return {
|
|
304
|
+
overview: {
|
|
305
|
+
daemon: "stopped",
|
|
306
|
+
health: "warn",
|
|
307
|
+
socketPath,
|
|
308
|
+
version: metadata.version,
|
|
309
|
+
lastUpdated: metadata.lastUpdated,
|
|
310
|
+
repoRoot: metadata.repoRoot,
|
|
311
|
+
configFingerprint: metadata.configFingerprint,
|
|
312
|
+
workerCount: 0,
|
|
313
|
+
senseCount: 0,
|
|
314
|
+
entryPath: path.join(repoRoot, "dist", "heart", "daemon", "daemon-entry.js"),
|
|
315
|
+
mode: (0, runtime_mode_1.detectRuntimeMode)(repoRoot),
|
|
316
|
+
},
|
|
317
|
+
senses: [],
|
|
318
|
+
workers: [],
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function daemonUnavailableStatusOutput(socketPath) {
|
|
322
|
+
return [
|
|
323
|
+
formatDaemonStatusOutput({
|
|
324
|
+
ok: true,
|
|
325
|
+
summary: "daemon not running",
|
|
326
|
+
data: buildStoppedStatusPayload(socketPath),
|
|
327
|
+
}, "daemon not running"),
|
|
328
|
+
"",
|
|
329
|
+
"daemon not running; run `ouro up`",
|
|
330
|
+
].join("\n");
|
|
331
|
+
}
|
|
332
|
+
function isDaemonUnavailableError(error) {
|
|
333
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
334
|
+
? String(error.code ?? "")
|
|
335
|
+
: "";
|
|
336
|
+
return code === "ENOENT" || code === "ECONNREFUSED";
|
|
337
|
+
}
|
|
79
338
|
function parseMessageCommand(args) {
|
|
80
339
|
let to;
|
|
81
340
|
let sessionId;
|
|
@@ -127,7 +386,7 @@ function parsePokeCommand(args) {
|
|
|
127
386
|
throw new Error(`Usage\n${usage()}`);
|
|
128
387
|
return { kind: "task.poke", agent, taskId };
|
|
129
388
|
}
|
|
130
|
-
function parseLinkCommand(args) {
|
|
389
|
+
function parseLinkCommand(args, kind = "friend.link") {
|
|
131
390
|
const agent = args[0];
|
|
132
391
|
if (!agent)
|
|
133
392
|
throw new Error(`Usage\n${usage()}`);
|
|
@@ -159,7 +418,7 @@ function parseLinkCommand(args) {
|
|
|
159
418
|
throw new Error(`Unknown identity provider '${providerRaw}'. Use aad|local|teams-conversation.`);
|
|
160
419
|
}
|
|
161
420
|
return {
|
|
162
|
-
kind
|
|
421
|
+
kind,
|
|
163
422
|
agent,
|
|
164
423
|
friendId,
|
|
165
424
|
provider: providerRaw,
|
|
@@ -167,8 +426,57 @@ function parseLinkCommand(args) {
|
|
|
167
426
|
};
|
|
168
427
|
}
|
|
169
428
|
function isAgentProvider(value) {
|
|
170
|
-
return value === "azure" || value === "anthropic" || value === "minimax" || value === "openai-codex";
|
|
429
|
+
return value === "azure" || value === "anthropic" || value === "minimax" || value === "openai-codex" || value === "github-copilot";
|
|
430
|
+
}
|
|
431
|
+
/* v8 ignore start -- hasStoredCredentials: per-provider branches tested via auth switch tests @preserve */
|
|
432
|
+
function hasStoredCredentials(provider, providerSecrets) {
|
|
433
|
+
if (provider === "anthropic")
|
|
434
|
+
return !!providerSecrets.setupToken;
|
|
435
|
+
if (provider === "openai-codex")
|
|
436
|
+
return !!providerSecrets.oauthAccessToken;
|
|
437
|
+
if (provider === "github-copilot")
|
|
438
|
+
return !!providerSecrets.githubToken;
|
|
439
|
+
if (provider === "minimax")
|
|
440
|
+
return !!providerSecrets.apiKey;
|
|
441
|
+
// azure
|
|
442
|
+
return !!providerSecrets.endpoint && !!providerSecrets.apiKey;
|
|
443
|
+
}
|
|
444
|
+
/* v8 ignore stop */
|
|
445
|
+
/* v8 ignore start -- verifyProviderCredentials: per-provider branches tested via auth verify tests @preserve */
|
|
446
|
+
function verifyProviderCredentials(provider, providers) {
|
|
447
|
+
const p = providers[provider];
|
|
448
|
+
if (!p)
|
|
449
|
+
return "not configured";
|
|
450
|
+
if (provider === "anthropic") {
|
|
451
|
+
const token = p.setupToken || "";
|
|
452
|
+
if (!token)
|
|
453
|
+
return "failed (no token)";
|
|
454
|
+
if (token.startsWith("sk-ant-"))
|
|
455
|
+
return "ok";
|
|
456
|
+
return "failed (invalid token format)";
|
|
457
|
+
}
|
|
458
|
+
if (provider === "openai-codex") {
|
|
459
|
+
const token = p.oauthAccessToken || "";
|
|
460
|
+
return token ? "ok" : "failed (no token)";
|
|
461
|
+
}
|
|
462
|
+
if (provider === "github-copilot") {
|
|
463
|
+
const token = p.githubToken || "";
|
|
464
|
+
return token ? "ok" : "failed (no token)";
|
|
465
|
+
}
|
|
466
|
+
if (provider === "minimax") {
|
|
467
|
+
const apiKey = p.apiKey || "";
|
|
468
|
+
return apiKey ? "ok" : "failed (no api key)";
|
|
469
|
+
}
|
|
470
|
+
// azure
|
|
471
|
+
const endpoint = p.endpoint || "";
|
|
472
|
+
const apiKey = p.apiKey || "";
|
|
473
|
+
if (!endpoint)
|
|
474
|
+
return "failed (no endpoint)";
|
|
475
|
+
if (!apiKey)
|
|
476
|
+
return "failed (no api key)";
|
|
477
|
+
return "ok";
|
|
171
478
|
}
|
|
479
|
+
/* v8 ignore stop */
|
|
172
480
|
function parseHatchCommand(args) {
|
|
173
481
|
let agentName;
|
|
174
482
|
let humanName;
|
|
@@ -224,7 +532,7 @@ function parseHatchCommand(args) {
|
|
|
224
532
|
}
|
|
225
533
|
}
|
|
226
534
|
if (providerRaw && !isAgentProvider(providerRaw)) {
|
|
227
|
-
throw new Error("Unknown provider. Use azure|anthropic|minimax|openai-codex.");
|
|
535
|
+
throw new Error("Unknown provider. Use azure|anthropic|minimax|openai-codex|github-copilot.");
|
|
228
536
|
}
|
|
229
537
|
const provider = providerRaw && isAgentProvider(providerRaw) ? providerRaw : undefined;
|
|
230
538
|
return {
|
|
@@ -236,13 +544,243 @@ function parseHatchCommand(args) {
|
|
|
236
544
|
migrationPath,
|
|
237
545
|
};
|
|
238
546
|
}
|
|
547
|
+
function parseTaskCommand(args) {
|
|
548
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
549
|
+
const [sub, ...rest] = cleaned;
|
|
550
|
+
if (!sub)
|
|
551
|
+
throw new Error(`Usage\n${usage()}`);
|
|
552
|
+
if (sub === "board") {
|
|
553
|
+
const status = rest[0];
|
|
554
|
+
return status
|
|
555
|
+
? { kind: "task.board", status, ...(agent ? { agent } : {}) }
|
|
556
|
+
: { kind: "task.board", ...(agent ? { agent } : {}) };
|
|
557
|
+
}
|
|
558
|
+
if (sub === "create") {
|
|
559
|
+
const title = rest[0];
|
|
560
|
+
if (!title)
|
|
561
|
+
throw new Error(`Usage\n${usage()}`);
|
|
562
|
+
let type;
|
|
563
|
+
for (let i = 1; i < rest.length; i++) {
|
|
564
|
+
if (rest[i] === "--type" && rest[i + 1]) {
|
|
565
|
+
type = rest[i + 1];
|
|
566
|
+
i += 1;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return type
|
|
570
|
+
? { kind: "task.create", title, type, ...(agent ? { agent } : {}) }
|
|
571
|
+
: { kind: "task.create", title, ...(agent ? { agent } : {}) };
|
|
572
|
+
}
|
|
573
|
+
if (sub === "update") {
|
|
574
|
+
const id = rest[0];
|
|
575
|
+
const status = rest[1];
|
|
576
|
+
if (!id || !status)
|
|
577
|
+
throw new Error(`Usage\n${usage()}`);
|
|
578
|
+
return { kind: "task.update", id, status, ...(agent ? { agent } : {}) };
|
|
579
|
+
}
|
|
580
|
+
if (sub === "show") {
|
|
581
|
+
const id = rest[0];
|
|
582
|
+
if (!id)
|
|
583
|
+
throw new Error(`Usage\n${usage()}`);
|
|
584
|
+
return { kind: "task.show", id, ...(agent ? { agent } : {}) };
|
|
585
|
+
}
|
|
586
|
+
if (sub === "actionable")
|
|
587
|
+
return { kind: "task.actionable", ...(agent ? { agent } : {}) };
|
|
588
|
+
if (sub === "deps")
|
|
589
|
+
return { kind: "task.deps", ...(agent ? { agent } : {}) };
|
|
590
|
+
if (sub === "sessions")
|
|
591
|
+
return { kind: "task.sessions", ...(agent ? { agent } : {}) };
|
|
592
|
+
throw new Error(`Usage\n${usage()}`);
|
|
593
|
+
}
|
|
594
|
+
function parseAuthCommand(args) {
|
|
595
|
+
const first = args[0];
|
|
596
|
+
if (first === "verify" || first === "switch") {
|
|
597
|
+
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
598
|
+
let provider;
|
|
599
|
+
/* v8 ignore start -- provider flag parsing: branches tested via CLI parsing tests @preserve */
|
|
600
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
601
|
+
if (rest[i] === "--provider") {
|
|
602
|
+
const value = rest[i + 1];
|
|
603
|
+
if (!isAgentProvider(value))
|
|
604
|
+
throw new Error(`Usage\n${usage()}`);
|
|
605
|
+
provider = value;
|
|
606
|
+
i += 1;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
/* v8 ignore stop */
|
|
611
|
+
/* v8 ignore next -- defensive: agent always provided in tests @preserve */
|
|
612
|
+
if (!agent)
|
|
613
|
+
throw new Error(`Usage\n${usage()}`);
|
|
614
|
+
if (first === "switch") {
|
|
615
|
+
if (!provider)
|
|
616
|
+
throw new Error(`auth switch requires --provider.\n${usage()}`);
|
|
617
|
+
return { kind: "auth.switch", agent, provider };
|
|
618
|
+
}
|
|
619
|
+
return provider ? { kind: "auth.verify", agent, provider } : { kind: "auth.verify", agent };
|
|
620
|
+
}
|
|
621
|
+
const { agent, rest } = extractAgentFlag(args);
|
|
622
|
+
let provider;
|
|
623
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
624
|
+
if (rest[i] === "--provider") {
|
|
625
|
+
const value = rest[i + 1];
|
|
626
|
+
if (!isAgentProvider(value))
|
|
627
|
+
throw new Error(`Usage\n${usage()}`);
|
|
628
|
+
provider = value;
|
|
629
|
+
i += 1;
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (!agent)
|
|
634
|
+
throw new Error(`Usage\n${usage()}`);
|
|
635
|
+
return provider ? { kind: "auth.run", agent, provider } : { kind: "auth.run", agent };
|
|
636
|
+
}
|
|
637
|
+
function parseReminderCommand(args) {
|
|
638
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
639
|
+
const [sub, ...rest] = cleaned;
|
|
640
|
+
if (!sub)
|
|
641
|
+
throw new Error(`Usage\n${usage()}`);
|
|
642
|
+
if (sub === "create") {
|
|
643
|
+
const title = rest[0];
|
|
644
|
+
if (!title)
|
|
645
|
+
throw new Error(`Usage\n${usage()}`);
|
|
646
|
+
let body;
|
|
647
|
+
let scheduledAt;
|
|
648
|
+
let cadence;
|
|
649
|
+
let category;
|
|
650
|
+
let requester;
|
|
651
|
+
for (let i = 1; i < rest.length; i++) {
|
|
652
|
+
if (rest[i] === "--body" && rest[i + 1]) {
|
|
653
|
+
body = rest[i + 1];
|
|
654
|
+
i += 1;
|
|
655
|
+
}
|
|
656
|
+
else if (rest[i] === "--at" && rest[i + 1]) {
|
|
657
|
+
scheduledAt = rest[i + 1];
|
|
658
|
+
i += 1;
|
|
659
|
+
}
|
|
660
|
+
else if (rest[i] === "--cadence" && rest[i + 1]) {
|
|
661
|
+
cadence = rest[i + 1];
|
|
662
|
+
i += 1;
|
|
663
|
+
}
|
|
664
|
+
else if (rest[i] === "--category" && rest[i + 1]) {
|
|
665
|
+
category = rest[i + 1];
|
|
666
|
+
i += 1;
|
|
667
|
+
}
|
|
668
|
+
else if (rest[i] === "--requester" && rest[i + 1]) {
|
|
669
|
+
requester = rest[i + 1];
|
|
670
|
+
i += 1;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (!body)
|
|
674
|
+
throw new Error(`Usage\n${usage()}`);
|
|
675
|
+
if (!scheduledAt && !cadence)
|
|
676
|
+
throw new Error(`Usage\n${usage()}`);
|
|
677
|
+
return {
|
|
678
|
+
kind: "reminder.create",
|
|
679
|
+
title,
|
|
680
|
+
body,
|
|
681
|
+
...(scheduledAt ? { scheduledAt } : {}),
|
|
682
|
+
...(cadence ? { cadence } : {}),
|
|
683
|
+
...(category ? { category } : {}),
|
|
684
|
+
...(requester ? { requester } : {}),
|
|
685
|
+
...(agent ? { agent } : {}),
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
throw new Error(`Usage\n${usage()}`);
|
|
689
|
+
}
|
|
690
|
+
function parseSessionCommand(args) {
|
|
691
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
692
|
+
const [sub] = cleaned;
|
|
693
|
+
if (!sub)
|
|
694
|
+
throw new Error(`Usage\n${usage()}`);
|
|
695
|
+
if (sub === "list")
|
|
696
|
+
return { kind: "session.list", ...(agent ? { agent } : {}) };
|
|
697
|
+
throw new Error(`Usage\n${usage()}`);
|
|
698
|
+
}
|
|
699
|
+
function parseThoughtsCommand(args) {
|
|
700
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
701
|
+
let last;
|
|
702
|
+
let json = false;
|
|
703
|
+
let follow = false;
|
|
704
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
705
|
+
if (cleaned[i] === "--last" && i + 1 < cleaned.length) {
|
|
706
|
+
last = Number.parseInt(cleaned[i + 1], 10);
|
|
707
|
+
i++;
|
|
708
|
+
}
|
|
709
|
+
if (cleaned[i] === "--json")
|
|
710
|
+
json = true;
|
|
711
|
+
if (cleaned[i] === "--follow" || cleaned[i] === "-f")
|
|
712
|
+
follow = true;
|
|
713
|
+
}
|
|
714
|
+
return { kind: "thoughts", ...(agent ? { agent } : {}), ...(last ? { last } : {}), ...(json ? { json } : {}), ...(follow ? { follow } : {}) };
|
|
715
|
+
}
|
|
716
|
+
function parseFriendCommand(args) {
|
|
717
|
+
const { agent, rest: cleaned } = extractAgentFlag(args);
|
|
718
|
+
const [sub, ...rest] = cleaned;
|
|
719
|
+
if (!sub)
|
|
720
|
+
throw new Error(`Usage\n${usage()}`);
|
|
721
|
+
if (sub === "list")
|
|
722
|
+
return { kind: "friend.list", ...(agent ? { agent } : {}) };
|
|
723
|
+
if (sub === "show") {
|
|
724
|
+
const friendId = rest[0];
|
|
725
|
+
if (!friendId)
|
|
726
|
+
throw new Error(`Usage\n${usage()}`);
|
|
727
|
+
return { kind: "friend.show", friendId, ...(agent ? { agent } : {}) };
|
|
728
|
+
}
|
|
729
|
+
if (sub === "create") {
|
|
730
|
+
let name;
|
|
731
|
+
let trustLevel;
|
|
732
|
+
for (let i = 0; i < rest.length; i++) {
|
|
733
|
+
if (rest[i] === "--name" && rest[i + 1]) {
|
|
734
|
+
name = rest[i + 1];
|
|
735
|
+
i += 1;
|
|
736
|
+
}
|
|
737
|
+
else if (rest[i] === "--trust" && rest[i + 1]) {
|
|
738
|
+
trustLevel = rest[i + 1];
|
|
739
|
+
i += 1;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (!name)
|
|
743
|
+
throw new Error(`Usage\n${usage()}`);
|
|
744
|
+
return {
|
|
745
|
+
kind: "friend.create",
|
|
746
|
+
name,
|
|
747
|
+
...(trustLevel ? { trustLevel } : {}),
|
|
748
|
+
...(agent ? { agent } : {}),
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
if (sub === "link")
|
|
752
|
+
return parseLinkCommand(rest, "friend.link");
|
|
753
|
+
if (sub === "unlink")
|
|
754
|
+
return parseLinkCommand(rest, "friend.unlink");
|
|
755
|
+
throw new Error(`Usage\n${usage()}`);
|
|
756
|
+
}
|
|
757
|
+
function parseMcpCommand(args) {
|
|
758
|
+
const [sub, ...rest] = args;
|
|
759
|
+
if (!sub)
|
|
760
|
+
throw new Error(`Usage\n${usage()}`);
|
|
761
|
+
if (sub === "list")
|
|
762
|
+
return { kind: "mcp.list" };
|
|
763
|
+
if (sub === "call") {
|
|
764
|
+
const server = rest[0];
|
|
765
|
+
const tool = rest[1];
|
|
766
|
+
if (!server || !tool)
|
|
767
|
+
throw new Error(`Usage\n${usage()}`);
|
|
768
|
+
const argsIdx = rest.indexOf("--args");
|
|
769
|
+
const mcpArgs = argsIdx !== -1 && rest[argsIdx + 1] ? rest[argsIdx + 1] : undefined;
|
|
770
|
+
return { kind: "mcp.call", server, tool, ...(mcpArgs ? { args: mcpArgs } : {}) };
|
|
771
|
+
}
|
|
772
|
+
throw new Error(`Usage\n${usage()}`);
|
|
773
|
+
}
|
|
239
774
|
function parseOuroCommand(args) {
|
|
240
775
|
const [head, second] = args;
|
|
241
776
|
if (!head)
|
|
242
777
|
return { kind: "daemon.up" };
|
|
778
|
+
if (head === "--agent" && second) {
|
|
779
|
+
return parseOuroCommand(args.slice(2));
|
|
780
|
+
}
|
|
243
781
|
if (head === "up")
|
|
244
782
|
return { kind: "daemon.up" };
|
|
245
|
-
if (head === "stop")
|
|
783
|
+
if (head === "stop" || head === "down")
|
|
246
784
|
return { kind: "daemon.stop" };
|
|
247
785
|
if (head === "status")
|
|
248
786
|
return { kind: "daemon.status" };
|
|
@@ -250,6 +788,34 @@ function parseOuroCommand(args) {
|
|
|
250
788
|
return { kind: "daemon.logs" };
|
|
251
789
|
if (head === "hatch")
|
|
252
790
|
return parseHatchCommand(args.slice(1));
|
|
791
|
+
if (head === "auth")
|
|
792
|
+
return parseAuthCommand(args.slice(1));
|
|
793
|
+
if (head === "task")
|
|
794
|
+
return parseTaskCommand(args.slice(1));
|
|
795
|
+
if (head === "reminder")
|
|
796
|
+
return parseReminderCommand(args.slice(1));
|
|
797
|
+
if (head === "friend")
|
|
798
|
+
return parseFriendCommand(args.slice(1));
|
|
799
|
+
if (head === "mcp")
|
|
800
|
+
return parseMcpCommand(args.slice(1));
|
|
801
|
+
if (head === "whoami") {
|
|
802
|
+
const { agent } = extractAgentFlag(args.slice(1));
|
|
803
|
+
return { kind: "whoami", ...(agent ? { agent } : {}) };
|
|
804
|
+
}
|
|
805
|
+
if (head === "session")
|
|
806
|
+
return parseSessionCommand(args.slice(1));
|
|
807
|
+
if (head === "changelog") {
|
|
808
|
+
const sliced = args.slice(1);
|
|
809
|
+
const { agent, rest: remaining } = extractAgentFlag(sliced);
|
|
810
|
+
let from;
|
|
811
|
+
const fromIdx = remaining.indexOf("--from");
|
|
812
|
+
if (fromIdx !== -1 && remaining[fromIdx + 1]) {
|
|
813
|
+
from = remaining[fromIdx + 1];
|
|
814
|
+
}
|
|
815
|
+
return { kind: "changelog", ...(from ? { from } : {}), ...(agent ? { agent } : {}) };
|
|
816
|
+
}
|
|
817
|
+
if (head === "thoughts")
|
|
818
|
+
return parseThoughtsCommand(args.slice(1));
|
|
253
819
|
if (head === "chat") {
|
|
254
820
|
if (!second)
|
|
255
821
|
throw new Error(`Usage\n${usage()}`);
|
|
@@ -263,38 +829,6 @@ function parseOuroCommand(args) {
|
|
|
263
829
|
return parseLinkCommand(args.slice(1));
|
|
264
830
|
throw new Error(`Unknown command '${args.join(" ")}'.\n${usage()}`);
|
|
265
831
|
}
|
|
266
|
-
function defaultSendCommand(socketPath, command) {
|
|
267
|
-
return new Promise((resolve, reject) => {
|
|
268
|
-
const client = net.createConnection(socketPath);
|
|
269
|
-
let raw = "";
|
|
270
|
-
client.on("connect", () => {
|
|
271
|
-
client.write(JSON.stringify(command));
|
|
272
|
-
client.end();
|
|
273
|
-
});
|
|
274
|
-
client.on("data", (chunk) => {
|
|
275
|
-
raw += chunk.toString("utf-8");
|
|
276
|
-
});
|
|
277
|
-
client.on("error", reject);
|
|
278
|
-
client.on("end", () => {
|
|
279
|
-
const trimmed = raw.trim();
|
|
280
|
-
if (trimmed.length === 0 && command.kind === "daemon.stop") {
|
|
281
|
-
resolve({ ok: true, message: "daemon stopped" });
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
if (trimmed.length === 0) {
|
|
285
|
-
reject(new Error("Daemon returned empty response."));
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
try {
|
|
289
|
-
const parsed = JSON.parse(trimmed);
|
|
290
|
-
resolve(parsed);
|
|
291
|
-
}
|
|
292
|
-
catch (error) {
|
|
293
|
-
reject(error);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
832
|
function defaultStartDaemonProcess(socketPath) {
|
|
299
833
|
const entry = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
|
|
300
834
|
const child = (0, child_process_1.spawn)("node", [entry, "--socket", socketPath], {
|
|
@@ -308,46 +842,6 @@ function defaultWriteStdout(text) {
|
|
|
308
842
|
// eslint-disable-next-line no-console -- terminal UX: CLI command output
|
|
309
843
|
console.log(text);
|
|
310
844
|
}
|
|
311
|
-
function defaultCheckSocketAlive(socketPath) {
|
|
312
|
-
return new Promise((resolve) => {
|
|
313
|
-
const client = net.createConnection(socketPath);
|
|
314
|
-
let raw = "";
|
|
315
|
-
let done = false;
|
|
316
|
-
const finalize = (alive) => {
|
|
317
|
-
if (done)
|
|
318
|
-
return;
|
|
319
|
-
done = true;
|
|
320
|
-
resolve(alive);
|
|
321
|
-
};
|
|
322
|
-
if ("setTimeout" in client && typeof client.setTimeout === "function") {
|
|
323
|
-
client.setTimeout(800, () => {
|
|
324
|
-
client.destroy();
|
|
325
|
-
finalize(false);
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
client.on("connect", () => {
|
|
329
|
-
client.write(JSON.stringify({ kind: "daemon.status" }));
|
|
330
|
-
client.end();
|
|
331
|
-
});
|
|
332
|
-
client.on("data", (chunk) => {
|
|
333
|
-
raw += chunk.toString("utf-8");
|
|
334
|
-
});
|
|
335
|
-
client.on("error", () => finalize(false));
|
|
336
|
-
client.on("end", () => {
|
|
337
|
-
if (raw.trim().length === 0) {
|
|
338
|
-
finalize(false);
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
try {
|
|
342
|
-
JSON.parse(raw);
|
|
343
|
-
finalize(true);
|
|
344
|
-
}
|
|
345
|
-
catch {
|
|
346
|
-
finalize(false);
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
845
|
function defaultCleanupStaleSocket(socketPath) {
|
|
352
846
|
if (fs.existsSync(socketPath)) {
|
|
353
847
|
fs.unlinkSync(socketPath);
|
|
@@ -382,9 +876,38 @@ function defaultFallbackPendingMessage(command) {
|
|
|
382
876
|
});
|
|
383
877
|
return pendingPath;
|
|
384
878
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
879
|
+
function defaultEnsureDaemonBootPersistence(socketPath) {
|
|
880
|
+
if (process.platform !== "darwin") {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const homeDir = os.homedir();
|
|
884
|
+
const launchdDeps = {
|
|
885
|
+
exec: (cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); },
|
|
886
|
+
writeFile: (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
|
|
887
|
+
removeFile: (filePath) => fs.rmSync(filePath, { force: true }),
|
|
888
|
+
existsFile: (filePath) => fs.existsSync(filePath),
|
|
889
|
+
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
890
|
+
homeDir,
|
|
891
|
+
userUid: process.getuid?.() ?? 0,
|
|
892
|
+
};
|
|
893
|
+
const entryPath = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
|
|
894
|
+
/* v8 ignore next -- covered via mock in daemon-cli-defaults.test.ts; v8 on CI attributes the real fs.existsSync branch to the non-mock load @preserve */
|
|
895
|
+
if (!fs.existsSync(entryPath)) {
|
|
896
|
+
(0, runtime_1.emitNervesEvent)({
|
|
897
|
+
level: "warn",
|
|
898
|
+
component: "daemon",
|
|
899
|
+
event: "daemon.entry_path_missing",
|
|
900
|
+
message: "entryPath does not exist on disk — plist may point to a stale location. Run 'ouro daemon install' from the correct location.",
|
|
901
|
+
meta: { entryPath },
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
const logDir = (0, identity_1.getAgentDaemonLogsDir)();
|
|
905
|
+
(0, launchd_1.installLaunchAgent)(launchdDeps, {
|
|
906
|
+
nodePath: process.execPath,
|
|
907
|
+
entryPath,
|
|
908
|
+
socketPath,
|
|
909
|
+
logDir,
|
|
910
|
+
envPath: process.env.PATH,
|
|
388
911
|
});
|
|
389
912
|
}
|
|
390
913
|
async function defaultPromptInput(question) {
|
|
@@ -402,144 +925,316 @@ async function defaultPromptInput(question) {
|
|
|
402
925
|
}
|
|
403
926
|
}
|
|
404
927
|
function defaultListDiscoveredAgents() {
|
|
405
|
-
|
|
928
|
+
return (0, agent_discovery_1.listEnabledBundleAgents)({
|
|
929
|
+
bundlesRoot: (0, identity_1.getAgentBundlesRoot)(),
|
|
930
|
+
readdirSync: fs.readdirSync,
|
|
931
|
+
readFileSync: fs.readFileSync,
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
function discoverExistingCredentials(secretsRoot) {
|
|
935
|
+
const found = [];
|
|
406
936
|
let entries;
|
|
407
937
|
try {
|
|
408
|
-
entries = fs.readdirSync(
|
|
938
|
+
entries = fs.readdirSync(secretsRoot, { withFileTypes: true });
|
|
409
939
|
}
|
|
410
940
|
catch {
|
|
411
|
-
return
|
|
941
|
+
return found;
|
|
412
942
|
}
|
|
413
|
-
const discovered = [];
|
|
414
943
|
for (const entry of entries) {
|
|
415
|
-
if (!entry.isDirectory()
|
|
944
|
+
if (!entry.isDirectory())
|
|
416
945
|
continue;
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
let enabled = true;
|
|
946
|
+
const secretsPath = path.join(secretsRoot, entry.name, "secrets.json");
|
|
947
|
+
let raw;
|
|
420
948
|
try {
|
|
421
|
-
|
|
422
|
-
const parsed = JSON.parse(raw);
|
|
423
|
-
if (typeof parsed.enabled === "boolean") {
|
|
424
|
-
enabled = parsed.enabled;
|
|
425
|
-
}
|
|
949
|
+
raw = fs.readFileSync(secretsPath, "utf-8");
|
|
426
950
|
}
|
|
427
951
|
catch {
|
|
428
952
|
continue;
|
|
429
953
|
}
|
|
430
|
-
|
|
431
|
-
|
|
954
|
+
let parsed;
|
|
955
|
+
try {
|
|
956
|
+
parsed = JSON.parse(raw);
|
|
957
|
+
}
|
|
958
|
+
catch {
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (!parsed.providers)
|
|
962
|
+
continue;
|
|
963
|
+
for (const [provName, provConfig] of Object.entries(parsed.providers)) {
|
|
964
|
+
if (provName === "anthropic" && provConfig.setupToken) {
|
|
965
|
+
found.push({ agentName: entry.name, provider: "anthropic", credentials: { setupToken: provConfig.setupToken }, providerConfig: { ...provConfig } });
|
|
966
|
+
}
|
|
967
|
+
else if (provName === "openai-codex" && provConfig.oauthAccessToken) {
|
|
968
|
+
found.push({ agentName: entry.name, provider: "openai-codex", credentials: { oauthAccessToken: provConfig.oauthAccessToken }, providerConfig: { ...provConfig } });
|
|
969
|
+
}
|
|
970
|
+
else if (provName === "minimax" && provConfig.apiKey) {
|
|
971
|
+
found.push({ agentName: entry.name, provider: "minimax", credentials: { apiKey: provConfig.apiKey }, providerConfig: { ...provConfig } });
|
|
972
|
+
}
|
|
973
|
+
else if (provName === "azure" && provConfig.apiKey && provConfig.endpoint && provConfig.deployment) {
|
|
974
|
+
found.push({ agentName: entry.name, provider: "azure", credentials: { apiKey: provConfig.apiKey, endpoint: provConfig.endpoint, deployment: provConfig.deployment }, providerConfig: { ...provConfig } });
|
|
975
|
+
}
|
|
432
976
|
}
|
|
433
977
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const alreadyLinked = current.externalIds.some((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
|
|
443
|
-
if (alreadyLinked) {
|
|
444
|
-
return `identity already linked: ${command.provider}:${command.externalId}`;
|
|
445
|
-
}
|
|
446
|
-
const now = new Date().toISOString();
|
|
447
|
-
await friendStore.put(command.friendId, {
|
|
448
|
-
...current,
|
|
449
|
-
externalIds: [
|
|
450
|
-
...current.externalIds,
|
|
451
|
-
{
|
|
452
|
-
provider: command.provider,
|
|
453
|
-
externalId: command.externalId,
|
|
454
|
-
linkedAt: now,
|
|
455
|
-
},
|
|
456
|
-
],
|
|
457
|
-
updatedAt: now,
|
|
978
|
+
// Deduplicate by provider+credential value (keep first seen)
|
|
979
|
+
const seen = new Set();
|
|
980
|
+
return found.filter((cred) => {
|
|
981
|
+
const key = `${cred.provider}:${JSON.stringify(cred.credentials)}`;
|
|
982
|
+
if (seen.has(key))
|
|
983
|
+
return false;
|
|
984
|
+
seen.add(key);
|
|
985
|
+
return true;
|
|
458
986
|
});
|
|
459
|
-
return `linked ${command.provider}:${command.externalId} to ${command.friendId}`;
|
|
460
987
|
}
|
|
461
|
-
/* v8 ignore
|
|
988
|
+
/* v8 ignore start -- integration: interactive terminal specialist session @preserve */
|
|
462
989
|
async function defaultRunAdoptionSpecialist() {
|
|
463
|
-
const
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
|
|
990
|
+
const { runCliSession } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
991
|
+
const { patchRuntimeConfig } = await Promise.resolve().then(() => __importStar(require("../config")));
|
|
992
|
+
const { setAgentName, setAgentConfigOverride } = await Promise.resolve().then(() => __importStar(require("../identity")));
|
|
993
|
+
const readlinePromises = await Promise.resolve().then(() => __importStar(require("readline/promises")));
|
|
994
|
+
const crypto = await Promise.resolve().then(() => __importStar(require("crypto")));
|
|
995
|
+
// Phase 1: cold CLI — collect provider/credentials with a simple readline
|
|
996
|
+
const coldRl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
997
|
+
const coldPrompt = async (q) => {
|
|
998
|
+
const answer = await coldRl.question(q);
|
|
467
999
|
return answer.trim();
|
|
468
1000
|
};
|
|
1001
|
+
let providerRaw;
|
|
1002
|
+
let credentials = {};
|
|
1003
|
+
let providerConfig = {};
|
|
1004
|
+
const tempDir = path.join(os.tmpdir(), `ouro-hatch-${crypto.randomUUID()}`);
|
|
469
1005
|
try {
|
|
470
|
-
const
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
1006
|
+
const secretsRoot = path.join(os.homedir(), ".agentsecrets");
|
|
1007
|
+
const discovered = discoverExistingCredentials(secretsRoot);
|
|
1008
|
+
const existingBundleCount = (0, specialist_orchestrator_1.listExistingBundles)((0, identity_1.getAgentBundlesRoot)()).length;
|
|
1009
|
+
const hatchVerb = existingBundleCount > 0 ? "let's hatch a new agent." : "let's hatch your first agent.";
|
|
1010
|
+
// Default models per provider (used when entering new credentials)
|
|
1011
|
+
const defaultModels = {
|
|
1012
|
+
anthropic: "claude-opus-4-6",
|
|
1013
|
+
minimax: "MiniMax-Text-01",
|
|
1014
|
+
"openai-codex": "gpt-5.4",
|
|
1015
|
+
"github-copilot": "claude-sonnet-4.6",
|
|
1016
|
+
azure: "",
|
|
1017
|
+
};
|
|
1018
|
+
if (discovered.length > 0) {
|
|
1019
|
+
process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
|
|
1020
|
+
process.stdout.write("i found existing API credentials:\n\n");
|
|
1021
|
+
const unique = [...new Map(discovered.map((d) => [`${d.provider}`, d])).values()];
|
|
1022
|
+
for (let i = 0; i < unique.length; i++) {
|
|
1023
|
+
const model = unique[i].providerConfig.model || unique[i].providerConfig.deployment || "";
|
|
1024
|
+
const modelLabel = model ? `, ${model}` : "";
|
|
1025
|
+
process.stdout.write(` ${i + 1}. ${unique[i].provider}${modelLabel} (from ${unique[i].agentName})\n`);
|
|
1026
|
+
}
|
|
1027
|
+
process.stdout.write("\n");
|
|
1028
|
+
const choice = await coldPrompt("use one of these? enter number, or 'new' for a different key: ");
|
|
1029
|
+
const idx = parseInt(choice, 10) - 1;
|
|
1030
|
+
if (idx >= 0 && idx < unique.length) {
|
|
1031
|
+
providerRaw = unique[idx].provider;
|
|
1032
|
+
credentials = unique[idx].credentials;
|
|
1033
|
+
providerConfig = unique[idx].providerConfig;
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex/github-copilot): ");
|
|
1037
|
+
if (!isAgentProvider(pRaw)) {
|
|
1038
|
+
process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
|
|
1039
|
+
coldRl.close();
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
providerRaw = pRaw;
|
|
1043
|
+
providerConfig = { model: defaultModels[providerRaw] };
|
|
1044
|
+
if (providerRaw === "anthropic")
|
|
1045
|
+
credentials.setupToken = await coldPrompt("API key: ");
|
|
1046
|
+
if (providerRaw === "openai-codex")
|
|
1047
|
+
credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
|
|
1048
|
+
if (providerRaw === "minimax")
|
|
1049
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
1050
|
+
if (providerRaw === "azure") {
|
|
1051
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
1052
|
+
credentials.endpoint = await coldPrompt("endpoint: ");
|
|
1053
|
+
credentials.deployment = await coldPrompt("deployment: ");
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
475
1056
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
1057
|
+
else {
|
|
1058
|
+
process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
|
|
1059
|
+
process.stdout.write("i need an API key to power our conversation.\n\n");
|
|
1060
|
+
const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex/github-copilot): ");
|
|
1061
|
+
if (!isAgentProvider(pRaw)) {
|
|
1062
|
+
process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
|
|
1063
|
+
coldRl.close();
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
providerRaw = pRaw;
|
|
1067
|
+
providerConfig = { model: defaultModels[providerRaw] };
|
|
1068
|
+
if (providerRaw === "anthropic")
|
|
1069
|
+
credentials.setupToken = await coldPrompt("API key: ");
|
|
1070
|
+
if (providerRaw === "openai-codex")
|
|
1071
|
+
credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
|
|
1072
|
+
if (providerRaw === "minimax")
|
|
1073
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
1074
|
+
if (providerRaw === "azure") {
|
|
1075
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
1076
|
+
credentials.endpoint = await coldPrompt("endpoint: ");
|
|
1077
|
+
credentials.deployment = await coldPrompt("deployment: ");
|
|
1078
|
+
}
|
|
487
1079
|
}
|
|
488
|
-
|
|
489
|
-
|
|
1080
|
+
coldRl.close();
|
|
1081
|
+
process.stdout.write("\n");
|
|
1082
|
+
// Phase 2: configure runtime for adoption specialist
|
|
490
1083
|
const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
|
|
491
1084
|
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
1085
|
+
const secretsRoot2 = path.join(os.homedir(), ".agentsecrets");
|
|
1086
|
+
// Suppress non-critical log noise during adoption (no secrets.json, etc.)
|
|
1087
|
+
const { setRuntimeLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
|
|
1088
|
+
const { createLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves")));
|
|
1089
|
+
setRuntimeLogger(createLogger({ level: "error" }));
|
|
1090
|
+
// Configure runtime: set agent identity + config override so runAgent
|
|
1091
|
+
// doesn't try to read from ~/AgentBundles/AdoptionSpecialist.ouro/
|
|
1092
|
+
setAgentName("AdoptionSpecialist");
|
|
1093
|
+
// Build specialist system prompt
|
|
1094
|
+
const soulText = (0, specialist_orchestrator_1.loadSoulText)(bundleSourceDir);
|
|
1095
|
+
const identitiesDir = path.join(bundleSourceDir, "psyche", "identities");
|
|
1096
|
+
const identity = (0, specialist_orchestrator_1.pickRandomIdentity)(identitiesDir);
|
|
1097
|
+
// Load identity-specific spinner phrases (falls back to DEFAULT_AGENT_PHRASES)
|
|
1098
|
+
const { loadIdentityPhrases } = await Promise.resolve().then(() => __importStar(require("./specialist-orchestrator")));
|
|
1099
|
+
const phrases = loadIdentityPhrases(bundleSourceDir, identity.fileName);
|
|
1100
|
+
setAgentConfigOverride({
|
|
1101
|
+
version: 1,
|
|
1102
|
+
enabled: true,
|
|
497
1103
|
provider: providerRaw,
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
},
|
|
504
|
-
callbacks: {
|
|
505
|
-
onModelStart: () => { },
|
|
506
|
-
onModelStreamStart: () => { },
|
|
507
|
-
onTextChunk: (text) => process.stdout.write(text),
|
|
508
|
-
onReasoningChunk: () => { },
|
|
509
|
-
onToolStart: () => { },
|
|
510
|
-
onToolEnd: () => { },
|
|
511
|
-
onError: (err) => process.stderr.write(`error: ${err.message}\n`),
|
|
1104
|
+
phrases,
|
|
1105
|
+
});
|
|
1106
|
+
patchRuntimeConfig({
|
|
1107
|
+
providers: {
|
|
1108
|
+
[providerRaw]: { ...providerConfig, ...credentials },
|
|
512
1109
|
},
|
|
513
1110
|
});
|
|
1111
|
+
const existingBundles = (0, specialist_orchestrator_1.listExistingBundles)(bundlesRoot);
|
|
1112
|
+
const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles, {
|
|
1113
|
+
tempDir,
|
|
1114
|
+
provider: providerRaw,
|
|
1115
|
+
});
|
|
1116
|
+
// Build specialist tools
|
|
1117
|
+
const specialistTools = (0, specialist_tools_1.getSpecialistTools)();
|
|
1118
|
+
const specialistExecTool = (0, specialist_tools_1.createSpecialistExecTool)({
|
|
1119
|
+
tempDir,
|
|
1120
|
+
credentials,
|
|
1121
|
+
provider: providerRaw,
|
|
1122
|
+
bundlesRoot,
|
|
1123
|
+
secretsRoot: secretsRoot2,
|
|
1124
|
+
animationWriter: (text) => process.stdout.write(text),
|
|
1125
|
+
});
|
|
1126
|
+
// Run the adoption specialist session via runCliSession
|
|
1127
|
+
const result = await runCliSession({
|
|
1128
|
+
agentName: "AdoptionSpecialist",
|
|
1129
|
+
tools: specialistTools,
|
|
1130
|
+
execTool: specialistExecTool,
|
|
1131
|
+
exitOnToolCall: "complete_adoption",
|
|
1132
|
+
autoFirstTurn: true,
|
|
1133
|
+
banner: false,
|
|
1134
|
+
disableCommands: true,
|
|
1135
|
+
skipSystemPromptRefresh: true,
|
|
1136
|
+
messages: [
|
|
1137
|
+
{ role: "system", content: systemPrompt },
|
|
1138
|
+
{ role: "user", content: "hi" },
|
|
1139
|
+
],
|
|
1140
|
+
});
|
|
1141
|
+
if (result.exitReason === "tool_exit" && result.toolResult) {
|
|
1142
|
+
const parsed = typeof result.toolResult === "string" ? JSON.parse(result.toolResult) : result.toolResult;
|
|
1143
|
+
if (parsed.success && parsed.agentName) {
|
|
1144
|
+
return parsed.agentName;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return null;
|
|
514
1148
|
}
|
|
515
|
-
catch {
|
|
516
|
-
|
|
1149
|
+
catch (err) {
|
|
1150
|
+
process.stderr.write(`\nouro adoption error: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
1151
|
+
coldRl.close();
|
|
517
1152
|
return null;
|
|
518
1153
|
}
|
|
1154
|
+
finally {
|
|
1155
|
+
// Clear specialist config/identity so the hatched agent gets its own
|
|
1156
|
+
setAgentConfigOverride(null);
|
|
1157
|
+
const { resetProviderRuntime } = await Promise.resolve().then(() => __importStar(require("../core")));
|
|
1158
|
+
resetProviderRuntime();
|
|
1159
|
+
const { resetConfigCache } = await Promise.resolve().then(() => __importStar(require("../config")));
|
|
1160
|
+
resetConfigCache();
|
|
1161
|
+
// Restore default logging
|
|
1162
|
+
const { setRuntimeLogger: restoreLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
|
|
1163
|
+
restoreLogger(null);
|
|
1164
|
+
// Clean up temp dir if it still exists
|
|
1165
|
+
try {
|
|
1166
|
+
if (fs.existsSync(tempDir)) {
|
|
1167
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
catch {
|
|
1171
|
+
// Best effort cleanup
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
519
1174
|
}
|
|
520
|
-
|
|
1175
|
+
/* v8 ignore stop */
|
|
1176
|
+
function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SOCKET_PATH) {
|
|
521
1177
|
return {
|
|
522
1178
|
socketPath,
|
|
523
|
-
sendCommand:
|
|
1179
|
+
sendCommand: socket_client_1.sendDaemonCommand,
|
|
524
1180
|
startDaemonProcess: defaultStartDaemonProcess,
|
|
525
1181
|
writeStdout: defaultWriteStdout,
|
|
526
|
-
checkSocketAlive:
|
|
1182
|
+
checkSocketAlive: socket_client_1.checkDaemonSocketAlive,
|
|
527
1183
|
cleanupStaleSocket: defaultCleanupStaleSocket,
|
|
528
1184
|
fallbackPendingMessage: defaultFallbackPendingMessage,
|
|
529
|
-
installSubagents: defaultInstallSubagents,
|
|
530
|
-
linkFriendIdentity: defaultLinkFriendIdentity,
|
|
531
1185
|
listDiscoveredAgents: defaultListDiscoveredAgents,
|
|
532
1186
|
runHatchFlow: hatch_flow_1.runHatchFlow,
|
|
533
1187
|
promptInput: defaultPromptInput,
|
|
534
1188
|
runAdoptionSpecialist: defaultRunAdoptionSpecialist,
|
|
1189
|
+
runAuthFlow: auth_flow_1.runRuntimeAuthFlow,
|
|
535
1190
|
registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
|
|
1191
|
+
installOuroCommand: ouro_path_installer_1.installOuroCommand,
|
|
1192
|
+
syncGlobalOuroBotWrapper: ouro_bot_global_installer_1.syncGlobalOuroBotWrapper,
|
|
1193
|
+
ensureSkillManagement: skill_management_installer_1.ensureSkillManagement,
|
|
1194
|
+
ensureDaemonBootPersistence: defaultEnsureDaemonBootPersistence,
|
|
536
1195
|
/* v8 ignore next 3 -- integration: launches interactive CLI session @preserve */
|
|
537
1196
|
startChat: async (agentName) => {
|
|
538
1197
|
const { main } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
539
1198
|
await main(agentName);
|
|
540
1199
|
},
|
|
1200
|
+
scanSessions: async () => {
|
|
1201
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
1202
|
+
const agentRoot = (0, identity_1.getAgentRoot)(agentName);
|
|
1203
|
+
return (0, session_activity_1.listSessionActivity)({
|
|
1204
|
+
sessionsDir: path.join(agentRoot, "state", "sessions"),
|
|
1205
|
+
friendsDir: path.join(agentRoot, "friends"),
|
|
1206
|
+
agentName,
|
|
1207
|
+
}).map((entry) => ({
|
|
1208
|
+
friendId: entry.friendId,
|
|
1209
|
+
friendName: entry.friendName,
|
|
1210
|
+
channel: entry.channel,
|
|
1211
|
+
lastActivity: entry.lastActivityAt,
|
|
1212
|
+
}));
|
|
1213
|
+
},
|
|
541
1214
|
};
|
|
542
1215
|
}
|
|
1216
|
+
function formatMcpResponse(command, response) {
|
|
1217
|
+
if (command.kind === "mcp.list") {
|
|
1218
|
+
const allTools = response.data;
|
|
1219
|
+
if (!allTools || allTools.length === 0) {
|
|
1220
|
+
return response.message ?? "no tools available from connected MCP servers";
|
|
1221
|
+
}
|
|
1222
|
+
const lines = [];
|
|
1223
|
+
for (const entry of allTools) {
|
|
1224
|
+
lines.push(`[${entry.server}]`);
|
|
1225
|
+
for (const tool of entry.tools) {
|
|
1226
|
+
lines.push(` ${tool.name}: ${tool.description}`);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return lines.join("\n");
|
|
1230
|
+
}
|
|
1231
|
+
// mcp.call
|
|
1232
|
+
const result = response.data;
|
|
1233
|
+
if (!result) {
|
|
1234
|
+
return response.message ?? "no result";
|
|
1235
|
+
}
|
|
1236
|
+
return result.content.map((c) => c.text).join("\n");
|
|
1237
|
+
}
|
|
543
1238
|
function toDaemonCommand(command) {
|
|
544
1239
|
return command;
|
|
545
1240
|
}
|
|
@@ -547,28 +1242,17 @@ async function resolveHatchInput(command, deps) {
|
|
|
547
1242
|
const prompt = deps.promptInput;
|
|
548
1243
|
const agentName = command.agentName ?? (prompt ? await prompt("Hatchling name: ") : "");
|
|
549
1244
|
const humanName = command.humanName ?? (prompt ? await prompt("Your name: ") : os.userInfo().username);
|
|
550
|
-
const providerRaw = command.provider ?? (prompt ? await prompt("Provider (azure|anthropic|minimax|openai-codex): ") : "");
|
|
1245
|
+
const providerRaw = command.provider ?? (prompt ? await prompt("Provider (azure|anthropic|minimax|openai-codex|github-copilot): ") : "");
|
|
551
1246
|
if (!agentName || !humanName || !isAgentProvider(providerRaw)) {
|
|
552
1247
|
throw new Error(`Usage\n${usage()}`);
|
|
553
1248
|
}
|
|
554
|
-
const credentials =
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
if (providerRaw === "minimax" && !credentials.apiKey && prompt) {
|
|
562
|
-
credentials.apiKey = await prompt("MiniMax API key: ");
|
|
563
|
-
}
|
|
564
|
-
if (providerRaw === "azure") {
|
|
565
|
-
if (!credentials.apiKey && prompt)
|
|
566
|
-
credentials.apiKey = await prompt("Azure API key: ");
|
|
567
|
-
if (!credentials.endpoint && prompt)
|
|
568
|
-
credentials.endpoint = await prompt("Azure endpoint: ");
|
|
569
|
-
if (!credentials.deployment && prompt)
|
|
570
|
-
credentials.deployment = await prompt("Azure deployment: ");
|
|
571
|
-
}
|
|
1249
|
+
const credentials = await (0, auth_flow_1.resolveHatchCredentials)({
|
|
1250
|
+
agentName,
|
|
1251
|
+
provider: providerRaw,
|
|
1252
|
+
credentials: command.credentials,
|
|
1253
|
+
promptInput: prompt,
|
|
1254
|
+
runAuthFlow: deps.runAuthFlow,
|
|
1255
|
+
});
|
|
572
1256
|
return {
|
|
573
1257
|
agentName,
|
|
574
1258
|
humanName,
|
|
@@ -594,12 +1278,240 @@ async function registerOuroBundleTypeNonBlocking(deps) {
|
|
|
594
1278
|
});
|
|
595
1279
|
}
|
|
596
1280
|
}
|
|
1281
|
+
async function performSystemSetup(deps) {
|
|
1282
|
+
// Install ouro command to PATH (non-blocking)
|
|
1283
|
+
if (deps.installOuroCommand) {
|
|
1284
|
+
try {
|
|
1285
|
+
deps.installOuroCommand();
|
|
1286
|
+
}
|
|
1287
|
+
catch (error) {
|
|
1288
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1289
|
+
level: "warn",
|
|
1290
|
+
component: "daemon",
|
|
1291
|
+
event: "daemon.system_setup_ouro_cmd_error",
|
|
1292
|
+
message: "failed to install ouro command to PATH",
|
|
1293
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (deps.syncGlobalOuroBotWrapper) {
|
|
1298
|
+
try {
|
|
1299
|
+
await Promise.resolve(deps.syncGlobalOuroBotWrapper());
|
|
1300
|
+
}
|
|
1301
|
+
catch (error) {
|
|
1302
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1303
|
+
level: "warn",
|
|
1304
|
+
component: "daemon",
|
|
1305
|
+
event: "daemon.system_setup_ouro_bot_wrapper_error",
|
|
1306
|
+
message: "failed to sync global ouro.bot wrapper",
|
|
1307
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
// Ensure skill-management skill is available
|
|
1312
|
+
if (deps.ensureSkillManagement) {
|
|
1313
|
+
try {
|
|
1314
|
+
await deps.ensureSkillManagement();
|
|
1315
|
+
/* v8 ignore start -- defensive: ensureSkillManagement handles its own errors internally @preserve */
|
|
1316
|
+
}
|
|
1317
|
+
catch (error) {
|
|
1318
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1319
|
+
level: "warn",
|
|
1320
|
+
component: "daemon",
|
|
1321
|
+
event: "daemon.system_setup_skill_management_error",
|
|
1322
|
+
message: "failed to ensure skill-management skill",
|
|
1323
|
+
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
/* v8 ignore stop */
|
|
1327
|
+
}
|
|
1328
|
+
// Register .ouro bundle type (UTI on macOS)
|
|
1329
|
+
await registerOuroBundleTypeNonBlocking(deps);
|
|
1330
|
+
}
|
|
1331
|
+
function executeTaskCommand(command, taskMod) {
|
|
1332
|
+
if (command.kind === "task.board") {
|
|
1333
|
+
if (command.status) {
|
|
1334
|
+
const lines = taskMod.boardStatus(command.status);
|
|
1335
|
+
return lines.length > 0 ? lines.join("\n") : "no tasks in that status";
|
|
1336
|
+
}
|
|
1337
|
+
const board = taskMod.getBoard();
|
|
1338
|
+
return board.full || board.compact || "no tasks found";
|
|
1339
|
+
}
|
|
1340
|
+
if (command.kind === "task.create") {
|
|
1341
|
+
try {
|
|
1342
|
+
const created = taskMod.createTask({
|
|
1343
|
+
title: command.title,
|
|
1344
|
+
type: command.type ?? "one-shot",
|
|
1345
|
+
category: "general",
|
|
1346
|
+
body: "",
|
|
1347
|
+
});
|
|
1348
|
+
return `created: ${created}`;
|
|
1349
|
+
}
|
|
1350
|
+
catch (error) {
|
|
1351
|
+
return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
if (command.kind === "task.update") {
|
|
1355
|
+
const result = taskMod.updateStatus(command.id, command.status);
|
|
1356
|
+
if (!result.ok) {
|
|
1357
|
+
return `error: ${result.reason ?? "status update failed"}`;
|
|
1358
|
+
}
|
|
1359
|
+
const archivedSuffix = result.archived && result.archived.length > 0
|
|
1360
|
+
? ` | archived: ${result.archived.join(", ")}`
|
|
1361
|
+
: "";
|
|
1362
|
+
return `updated: ${command.id} -> ${result.to}${archivedSuffix}`;
|
|
1363
|
+
}
|
|
1364
|
+
if (command.kind === "task.show") {
|
|
1365
|
+
const task = taskMod.getTask(command.id);
|
|
1366
|
+
if (!task)
|
|
1367
|
+
return `task not found: ${command.id}`;
|
|
1368
|
+
return [
|
|
1369
|
+
`title: ${task.title}`,
|
|
1370
|
+
`type: ${task.type}`,
|
|
1371
|
+
`status: ${task.status}`,
|
|
1372
|
+
`category: ${task.category}`,
|
|
1373
|
+
`created: ${task.created}`,
|
|
1374
|
+
`updated: ${task.updated}`,
|
|
1375
|
+
`path: ${task.path}`,
|
|
1376
|
+
task.body ? `\n${task.body}` : "",
|
|
1377
|
+
].filter(Boolean).join("\n");
|
|
1378
|
+
}
|
|
1379
|
+
if (command.kind === "task.actionable") {
|
|
1380
|
+
const lines = taskMod.boardAction();
|
|
1381
|
+
return lines.length > 0 ? lines.join("\n") : "no action required";
|
|
1382
|
+
}
|
|
1383
|
+
if (command.kind === "task.deps") {
|
|
1384
|
+
const lines = taskMod.boardDeps();
|
|
1385
|
+
return lines.length > 0 ? lines.join("\n") : "no unresolved dependencies";
|
|
1386
|
+
}
|
|
1387
|
+
// command.kind === "task.sessions"
|
|
1388
|
+
const lines = taskMod.boardSessions();
|
|
1389
|
+
return lines.length > 0 ? lines.join("\n") : "no active sessions";
|
|
1390
|
+
}
|
|
1391
|
+
const TRUST_RANK = { family: 4, friend: 3, acquaintance: 2, stranger: 1 };
|
|
1392
|
+
/* v8 ignore start -- defensive: ?? fallbacks are unreachable when inputs are valid TrustLevel values @preserve */
|
|
1393
|
+
function higherTrust(a, b) {
|
|
1394
|
+
const rankA = TRUST_RANK[a ?? "stranger"] ?? 1;
|
|
1395
|
+
const rankB = TRUST_RANK[b ?? "stranger"] ?? 1;
|
|
1396
|
+
return rankA >= rankB ? (a ?? "stranger") : (b ?? "stranger");
|
|
1397
|
+
}
|
|
1398
|
+
/* v8 ignore stop */
|
|
1399
|
+
async function executeFriendCommand(command, store) {
|
|
1400
|
+
if (command.kind === "friend.list") {
|
|
1401
|
+
const listAll = store.listAll;
|
|
1402
|
+
if (!listAll)
|
|
1403
|
+
return "friend store does not support listing";
|
|
1404
|
+
const friends = await listAll.call(store);
|
|
1405
|
+
if (friends.length === 0)
|
|
1406
|
+
return "no friends found";
|
|
1407
|
+
const lines = friends.map((f) => {
|
|
1408
|
+
const trust = f.trustLevel ?? "unknown";
|
|
1409
|
+
return `${f.id} ${f.name} ${trust}`;
|
|
1410
|
+
});
|
|
1411
|
+
return lines.join("\n");
|
|
1412
|
+
}
|
|
1413
|
+
if (command.kind === "friend.show") {
|
|
1414
|
+
const record = await store.get(command.friendId);
|
|
1415
|
+
if (!record)
|
|
1416
|
+
return `friend not found: ${command.friendId}`;
|
|
1417
|
+
return JSON.stringify(record, null, 2);
|
|
1418
|
+
}
|
|
1419
|
+
if (command.kind === "friend.create") {
|
|
1420
|
+
const now = new Date().toISOString();
|
|
1421
|
+
const id = (0, crypto_1.randomUUID)();
|
|
1422
|
+
const trustLevel = (command.trustLevel ?? "acquaintance");
|
|
1423
|
+
await store.put(id, {
|
|
1424
|
+
id,
|
|
1425
|
+
name: command.name,
|
|
1426
|
+
trustLevel,
|
|
1427
|
+
externalIds: [],
|
|
1428
|
+
tenantMemberships: [],
|
|
1429
|
+
toolPreferences: {},
|
|
1430
|
+
notes: {},
|
|
1431
|
+
totalTokens: 0,
|
|
1432
|
+
createdAt: now,
|
|
1433
|
+
updatedAt: now,
|
|
1434
|
+
schemaVersion: 1,
|
|
1435
|
+
});
|
|
1436
|
+
return `created: ${id} (${command.name}, ${trustLevel})`;
|
|
1437
|
+
}
|
|
1438
|
+
if (command.kind === "friend.link") {
|
|
1439
|
+
const current = await store.get(command.friendId);
|
|
1440
|
+
if (!current)
|
|
1441
|
+
return `friend not found: ${command.friendId}`;
|
|
1442
|
+
const alreadyLinked = current.externalIds.some((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
|
|
1443
|
+
if (alreadyLinked)
|
|
1444
|
+
return `identity already linked: ${command.provider}:${command.externalId}`;
|
|
1445
|
+
const now = new Date().toISOString();
|
|
1446
|
+
const newExternalIds = [
|
|
1447
|
+
...current.externalIds,
|
|
1448
|
+
{ provider: command.provider, externalId: command.externalId, linkedAt: now },
|
|
1449
|
+
];
|
|
1450
|
+
// Orphan cleanup: check if another friend has this externalId
|
|
1451
|
+
const orphan = await store.findByExternalId(command.provider, command.externalId);
|
|
1452
|
+
let mergeMessage = "";
|
|
1453
|
+
let mergedNotes = { ...current.notes };
|
|
1454
|
+
let mergedTrust = current.trustLevel;
|
|
1455
|
+
let orphanExternalIds = [];
|
|
1456
|
+
if (orphan && orphan.id !== command.friendId) {
|
|
1457
|
+
// Merge orphan's notes (target's notes take priority)
|
|
1458
|
+
mergedNotes = { ...orphan.notes, ...current.notes };
|
|
1459
|
+
// Keep higher trust level
|
|
1460
|
+
mergedTrust = higherTrust(current.trustLevel, orphan.trustLevel);
|
|
1461
|
+
// Collect orphan's other externalIds (excluding the one being linked)
|
|
1462
|
+
orphanExternalIds = orphan.externalIds.filter((ext) => !(ext.provider === command.provider && ext.externalId === command.externalId));
|
|
1463
|
+
await store.delete(orphan.id);
|
|
1464
|
+
mergeMessage = ` (merged orphan ${orphan.id})`;
|
|
1465
|
+
}
|
|
1466
|
+
await store.put(command.friendId, {
|
|
1467
|
+
...current,
|
|
1468
|
+
externalIds: [...newExternalIds, ...orphanExternalIds],
|
|
1469
|
+
notes: mergedNotes,
|
|
1470
|
+
trustLevel: mergedTrust,
|
|
1471
|
+
updatedAt: now,
|
|
1472
|
+
});
|
|
1473
|
+
return `linked ${command.provider}:${command.externalId} to ${command.friendId}${mergeMessage}`;
|
|
1474
|
+
}
|
|
1475
|
+
// command.kind === "friend.unlink"
|
|
1476
|
+
const current = await store.get(command.friendId);
|
|
1477
|
+
if (!current)
|
|
1478
|
+
return `friend not found: ${command.friendId}`;
|
|
1479
|
+
const idx = current.externalIds.findIndex((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
|
|
1480
|
+
if (idx === -1)
|
|
1481
|
+
return `identity not linked: ${command.provider}:${command.externalId}`;
|
|
1482
|
+
const now = new Date().toISOString();
|
|
1483
|
+
const filtered = current.externalIds.filter((_, i) => i !== idx);
|
|
1484
|
+
await store.put(command.friendId, { ...current, externalIds: filtered, updatedAt: now });
|
|
1485
|
+
return `unlinked ${command.provider}:${command.externalId} from ${command.friendId}`;
|
|
1486
|
+
}
|
|
1487
|
+
function executeReminderCommand(command, taskMod) {
|
|
1488
|
+
try {
|
|
1489
|
+
const created = taskMod.createTask({
|
|
1490
|
+
title: command.title,
|
|
1491
|
+
type: command.cadence ? "habit" : "one-shot",
|
|
1492
|
+
category: command.category ?? "reminder",
|
|
1493
|
+
body: command.body,
|
|
1494
|
+
scheduledAt: command.scheduledAt,
|
|
1495
|
+
cadence: command.cadence,
|
|
1496
|
+
requester: command.requester,
|
|
1497
|
+
});
|
|
1498
|
+
return `created: ${created}`;
|
|
1499
|
+
}
|
|
1500
|
+
catch (error) {
|
|
1501
|
+
return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
597
1504
|
async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
598
1505
|
if (args.includes("--help") || args.includes("-h")) {
|
|
599
1506
|
const text = usage();
|
|
600
1507
|
deps.writeStdout(text);
|
|
601
1508
|
return text;
|
|
602
1509
|
}
|
|
1510
|
+
if (args.length === 1 && (args[0] === "-v" || args[0] === "--version")) {
|
|
1511
|
+
const text = formatVersionOutput();
|
|
1512
|
+
deps.writeStdout(text);
|
|
1513
|
+
return text;
|
|
1514
|
+
}
|
|
603
1515
|
let command;
|
|
604
1516
|
try {
|
|
605
1517
|
command = parseOuroCommand(args);
|
|
@@ -618,23 +1530,12 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
618
1530
|
if (args.length === 0) {
|
|
619
1531
|
const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
|
|
620
1532
|
if (discovered.length === 0 && deps.runAdoptionSpecialist) {
|
|
1533
|
+
// System setup first — ouro command, subagents, UTI — before the interactive specialist
|
|
1534
|
+
await performSystemSetup(deps);
|
|
621
1535
|
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
622
1536
|
if (!hatchlingName) {
|
|
623
1537
|
return "";
|
|
624
1538
|
}
|
|
625
|
-
try {
|
|
626
|
-
await deps.installSubagents();
|
|
627
|
-
}
|
|
628
|
-
catch (error) {
|
|
629
|
-
(0, runtime_1.emitNervesEvent)({
|
|
630
|
-
level: "warn",
|
|
631
|
-
component: "daemon",
|
|
632
|
-
event: "daemon.subagent_install_error",
|
|
633
|
-
message: "subagent auto-install failed",
|
|
634
|
-
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
638
1539
|
await ensureDaemonRunning(deps);
|
|
639
1540
|
if (deps.startChat) {
|
|
640
1541
|
await deps.startChat(hatchlingName);
|
|
@@ -681,19 +1582,34 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
681
1582
|
meta: { kind: command.kind },
|
|
682
1583
|
});
|
|
683
1584
|
if (command.kind === "daemon.up") {
|
|
684
|
-
|
|
685
|
-
|
|
1585
|
+
await performSystemSetup(deps);
|
|
1586
|
+
if (deps.ensureDaemonBootPersistence) {
|
|
1587
|
+
try {
|
|
1588
|
+
await Promise.resolve(deps.ensureDaemonBootPersistence(deps.socketPath));
|
|
1589
|
+
}
|
|
1590
|
+
catch (error) {
|
|
1591
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1592
|
+
level: "warn",
|
|
1593
|
+
component: "daemon",
|
|
1594
|
+
event: "daemon.system_setup_launchd_error",
|
|
1595
|
+
message: "failed to persist daemon boot startup",
|
|
1596
|
+
meta: { error: error instanceof Error ? error.message : String(error), socketPath: deps.socketPath },
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
686
1599
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1600
|
+
// Run update hooks before starting daemon so user sees the output
|
|
1601
|
+
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
1602
|
+
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
1603
|
+
const currentVersion = (0, bundle_manifest_1.getPackageVersion)();
|
|
1604
|
+
const updateSummary = await (0, update_hooks_1.applyPendingUpdates)(bundlesRoot, currentVersion);
|
|
1605
|
+
if (updateSummary.updated.length > 0) {
|
|
1606
|
+
const agents = updateSummary.updated.map((e) => e.agent);
|
|
1607
|
+
const from = updateSummary.updated[0].from;
|
|
1608
|
+
const to = updateSummary.updated[0].to;
|
|
1609
|
+
const fromStr = from ? ` (was ${from})` : "";
|
|
1610
|
+
const count = agents.length;
|
|
1611
|
+
deps.writeStdout(`updated ${count} agent${count === 1 ? "" : "s"} to runtime ${to}${fromStr}`);
|
|
695
1612
|
}
|
|
696
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
697
1613
|
const daemonResult = await ensureDaemonRunning(deps);
|
|
698
1614
|
deps.writeStdout(daemonResult.message);
|
|
699
1615
|
return daemonResult.message;
|
|
@@ -702,33 +1618,263 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
702
1618
|
deps.tailLogs();
|
|
703
1619
|
return "";
|
|
704
1620
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const
|
|
1621
|
+
// ── mcp subcommands (routed through daemon socket) ──
|
|
1622
|
+
if (command.kind === "mcp.list" || command.kind === "mcp.call") {
|
|
1623
|
+
const daemonCommand = toDaemonCommand(command);
|
|
1624
|
+
let response;
|
|
1625
|
+
try {
|
|
1626
|
+
response = await deps.sendCommand(deps.socketPath, daemonCommand);
|
|
1627
|
+
}
|
|
1628
|
+
catch {
|
|
1629
|
+
const message = "daemon unavailable — start with `ouro up` first";
|
|
1630
|
+
deps.writeStdout(message);
|
|
1631
|
+
return message;
|
|
1632
|
+
}
|
|
1633
|
+
if (!response.ok) {
|
|
1634
|
+
const message = response.error ?? "unknown error";
|
|
1635
|
+
deps.writeStdout(message);
|
|
1636
|
+
return message;
|
|
1637
|
+
}
|
|
1638
|
+
const message = formatMcpResponse(command, response);
|
|
1639
|
+
deps.writeStdout(message);
|
|
1640
|
+
return message;
|
|
1641
|
+
}
|
|
1642
|
+
// ── task subcommands (local, no daemon socket needed) ──
|
|
1643
|
+
if (command.kind === "task.board" || command.kind === "task.create" || command.kind === "task.update" ||
|
|
1644
|
+
command.kind === "task.show" || command.kind === "task.actionable" || command.kind === "task.deps" ||
|
|
1645
|
+
command.kind === "task.sessions") {
|
|
1646
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1647
|
+
const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
|
|
1648
|
+
/* v8 ignore stop */
|
|
1649
|
+
const message = executeTaskCommand(command, taskMod);
|
|
1650
|
+
deps.writeStdout(message);
|
|
1651
|
+
return message;
|
|
1652
|
+
}
|
|
1653
|
+
// ── reminder subcommands (local, no daemon socket needed) ──
|
|
1654
|
+
if (command.kind === "reminder.create") {
|
|
1655
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1656
|
+
const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
|
|
1657
|
+
/* v8 ignore stop */
|
|
1658
|
+
const message = executeReminderCommand(command, taskMod);
|
|
708
1659
|
deps.writeStdout(message);
|
|
709
1660
|
return message;
|
|
710
1661
|
}
|
|
1662
|
+
// ── friend subcommands (local, no daemon socket needed) ──
|
|
1663
|
+
if (command.kind === "friend.list" || command.kind === "friend.show" || command.kind === "friend.create" ||
|
|
1664
|
+
command.kind === "friend.link" || command.kind === "friend.unlink") {
|
|
1665
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1666
|
+
let store = deps.friendStore;
|
|
1667
|
+
if (!store) {
|
|
1668
|
+
// Derive agent-scoped friends dir from --agent flag or link/unlink's agent field
|
|
1669
|
+
const agentName = ("agent" in command && command.agent) ? command.agent : undefined;
|
|
1670
|
+
const friendsDir = agentName
|
|
1671
|
+
? path.join((0, identity_1.getAgentBundlesRoot)(), `${agentName}.ouro`, "friends")
|
|
1672
|
+
: path.join((0, identity_1.getAgentBundlesRoot)(), "friends");
|
|
1673
|
+
store = new store_file_1.FileFriendStore(friendsDir);
|
|
1674
|
+
}
|
|
1675
|
+
/* v8 ignore stop */
|
|
1676
|
+
const message = await executeFriendCommand(command, store);
|
|
1677
|
+
deps.writeStdout(message);
|
|
1678
|
+
return message;
|
|
1679
|
+
}
|
|
1680
|
+
// ── auth (local, no daemon socket needed) ──
|
|
1681
|
+
if (command.kind === "auth.run") {
|
|
1682
|
+
const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent).config.provider;
|
|
1683
|
+
/* v8 ignore next -- tests always inject runAuthFlow; default is for production @preserve */
|
|
1684
|
+
const authRunner = deps.runAuthFlow ?? auth_flow_1.runRuntimeAuthFlow;
|
|
1685
|
+
const result = await authRunner({
|
|
1686
|
+
agentName: command.agent,
|
|
1687
|
+
provider,
|
|
1688
|
+
promptInput: deps.promptInput,
|
|
1689
|
+
});
|
|
1690
|
+
// Behavior: ouro auth stores credentials only — does NOT switch provider.
|
|
1691
|
+
// Use `ouro auth switch` to change the active provider.
|
|
1692
|
+
deps.writeStdout(result.message);
|
|
1693
|
+
return result.message;
|
|
1694
|
+
}
|
|
1695
|
+
// ── auth verify (local, no daemon socket needed) ──
|
|
1696
|
+
/* v8 ignore start -- auth verify/switch: tested in daemon-cli.test.ts but v8 traces differ in CI @preserve */
|
|
1697
|
+
if (command.kind === "auth.verify") {
|
|
1698
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent);
|
|
1699
|
+
const providers = secrets.providers;
|
|
1700
|
+
if (command.provider) {
|
|
1701
|
+
const status = verifyProviderCredentials(command.provider, providers);
|
|
1702
|
+
const message = `${command.provider}: ${status}`;
|
|
1703
|
+
deps.writeStdout(message);
|
|
1704
|
+
return message;
|
|
1705
|
+
}
|
|
1706
|
+
const lines = [];
|
|
1707
|
+
for (const p of Object.keys(providers)) {
|
|
1708
|
+
const status = verifyProviderCredentials(p, providers);
|
|
1709
|
+
lines.push(`${p}: ${status}`);
|
|
1710
|
+
}
|
|
1711
|
+
const message = lines.join("\n");
|
|
1712
|
+
deps.writeStdout(message);
|
|
1713
|
+
return message;
|
|
1714
|
+
}
|
|
1715
|
+
// ── auth switch (local, no daemon socket needed) ──
|
|
1716
|
+
if (command.kind === "auth.switch") {
|
|
1717
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent);
|
|
1718
|
+
const providerSecrets = secrets.providers[command.provider];
|
|
1719
|
+
if (!providerSecrets || !hasStoredCredentials(command.provider, providerSecrets)) {
|
|
1720
|
+
const message = `no credentials stored for ${command.provider}. Run \`ouro auth --agent ${command.agent} --provider ${command.provider}\` first.`;
|
|
1721
|
+
deps.writeStdout(message);
|
|
1722
|
+
return message;
|
|
1723
|
+
}
|
|
1724
|
+
(0, auth_flow_1.writeAgentProviderSelection)(command.agent, command.provider);
|
|
1725
|
+
const message = `switched ${command.agent} to ${command.provider}`;
|
|
1726
|
+
deps.writeStdout(message);
|
|
1727
|
+
return message;
|
|
1728
|
+
}
|
|
1729
|
+
/* v8 ignore stop */
|
|
1730
|
+
// ── whoami (local, no daemon socket needed) ──
|
|
1731
|
+
if (command.kind === "whoami") {
|
|
1732
|
+
if (command.agent) {
|
|
1733
|
+
const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
|
|
1734
|
+
const message = [
|
|
1735
|
+
`agent: ${command.agent}`,
|
|
1736
|
+
`home: ${agentRoot}`,
|
|
1737
|
+
`bones: ${(0, runtime_metadata_1.getRuntimeMetadata)().version}`,
|
|
1738
|
+
].join("\n");
|
|
1739
|
+
deps.writeStdout(message);
|
|
1740
|
+
return message;
|
|
1741
|
+
}
|
|
1742
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1743
|
+
try {
|
|
1744
|
+
const info = deps.whoamiInfo
|
|
1745
|
+
? deps.whoamiInfo()
|
|
1746
|
+
: {
|
|
1747
|
+
agentName: (0, identity_1.getAgentName)(),
|
|
1748
|
+
homePath: path.join((0, identity_1.getAgentBundlesRoot)(), `${(0, identity_1.getAgentName)()}.ouro`),
|
|
1749
|
+
bonesVersion: (0, runtime_metadata_1.getRuntimeMetadata)().version,
|
|
1750
|
+
};
|
|
1751
|
+
const message = [
|
|
1752
|
+
`agent: ${info.agentName}`,
|
|
1753
|
+
`home: ${info.homePath}`,
|
|
1754
|
+
`bones: ${info.bonesVersion}`,
|
|
1755
|
+
].join("\n");
|
|
1756
|
+
deps.writeStdout(message);
|
|
1757
|
+
return message;
|
|
1758
|
+
}
|
|
1759
|
+
catch {
|
|
1760
|
+
const message = "error: no agent context — use --agent <name> to specify";
|
|
1761
|
+
deps.writeStdout(message);
|
|
1762
|
+
return message;
|
|
1763
|
+
}
|
|
1764
|
+
/* v8 ignore stop */
|
|
1765
|
+
}
|
|
1766
|
+
// ── changelog (local, no daemon socket needed) ──
|
|
1767
|
+
if (command.kind === "changelog") {
|
|
1768
|
+
try {
|
|
1769
|
+
const changelogPath = deps.getChangelogPath
|
|
1770
|
+
? deps.getChangelogPath()
|
|
1771
|
+
: (0, bundle_manifest_1.getChangelogPath)();
|
|
1772
|
+
const raw = fs.readFileSync(changelogPath, "utf-8");
|
|
1773
|
+
const entries = JSON.parse(raw);
|
|
1774
|
+
let filtered = entries;
|
|
1775
|
+
if (command.from) {
|
|
1776
|
+
const fromVersion = command.from;
|
|
1777
|
+
filtered = entries.filter((e) => e.version > fromVersion);
|
|
1778
|
+
}
|
|
1779
|
+
if (filtered.length === 0) {
|
|
1780
|
+
const message = "no changelog entries found.";
|
|
1781
|
+
deps.writeStdout(message);
|
|
1782
|
+
return message;
|
|
1783
|
+
}
|
|
1784
|
+
const lines = [];
|
|
1785
|
+
for (const entry of filtered) {
|
|
1786
|
+
lines.push(`## ${entry.version}${entry.date ? ` (${entry.date})` : ""}`);
|
|
1787
|
+
if (entry.changes) {
|
|
1788
|
+
for (const change of entry.changes) {
|
|
1789
|
+
lines.push(`- ${change}`);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
lines.push("");
|
|
1793
|
+
}
|
|
1794
|
+
const message = lines.join("\n").trim();
|
|
1795
|
+
deps.writeStdout(message);
|
|
1796
|
+
return message;
|
|
1797
|
+
}
|
|
1798
|
+
catch {
|
|
1799
|
+
const message = "no changelog entries found.";
|
|
1800
|
+
deps.writeStdout(message);
|
|
1801
|
+
return message;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
// ── thoughts (local, no daemon socket needed) ──
|
|
1805
|
+
if (command.kind === "thoughts") {
|
|
1806
|
+
try {
|
|
1807
|
+
const agentName = command.agent ?? (0, identity_1.getAgentName)();
|
|
1808
|
+
const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${agentName}.ouro`);
|
|
1809
|
+
const sessionFilePath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
|
|
1810
|
+
if (command.json) {
|
|
1811
|
+
try {
|
|
1812
|
+
const raw = fs.readFileSync(sessionFilePath, "utf-8");
|
|
1813
|
+
deps.writeStdout(raw);
|
|
1814
|
+
return raw;
|
|
1815
|
+
}
|
|
1816
|
+
catch {
|
|
1817
|
+
const message = "no inner dialog session found";
|
|
1818
|
+
deps.writeStdout(message);
|
|
1819
|
+
return message;
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
const turns = (0, thoughts_1.parseInnerDialogSession)(sessionFilePath);
|
|
1823
|
+
const message = (0, thoughts_1.formatThoughtTurns)(turns, command.last ?? 10);
|
|
1824
|
+
deps.writeStdout(message);
|
|
1825
|
+
if (command.follow) {
|
|
1826
|
+
deps.writeStdout("\n\n--- following (ctrl+c to stop) ---\n");
|
|
1827
|
+
/* v8 ignore start -- callback tested via followThoughts unit tests @preserve */
|
|
1828
|
+
const stop = (0, thoughts_1.followThoughts)(sessionFilePath, (formatted) => {
|
|
1829
|
+
deps.writeStdout("\n" + formatted);
|
|
1830
|
+
});
|
|
1831
|
+
/* v8 ignore stop */
|
|
1832
|
+
// Block until process exit; cleanup watcher on SIGINT/SIGTERM
|
|
1833
|
+
return new Promise((resolve) => {
|
|
1834
|
+
const cleanup = () => { stop(); resolve(message); };
|
|
1835
|
+
process.once("SIGINT", cleanup);
|
|
1836
|
+
process.once("SIGTERM", cleanup);
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
return message;
|
|
1840
|
+
}
|
|
1841
|
+
catch {
|
|
1842
|
+
const message = "error: no agent context — use --agent <name> to specify";
|
|
1843
|
+
deps.writeStdout(message);
|
|
1844
|
+
return message;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
// ── session list (local, no daemon socket needed) ──
|
|
1848
|
+
if (command.kind === "session.list") {
|
|
1849
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1850
|
+
const scanner = deps.scanSessions ?? (async () => []);
|
|
1851
|
+
/* v8 ignore stop */
|
|
1852
|
+
const sessions = await scanner();
|
|
1853
|
+
if (sessions.length === 0) {
|
|
1854
|
+
const message = "no active sessions";
|
|
1855
|
+
deps.writeStdout(message);
|
|
1856
|
+
return message;
|
|
1857
|
+
}
|
|
1858
|
+
const lines = sessions.map((s) => `${s.friendId} ${s.friendName} ${s.channel} ${s.lastActivity}`);
|
|
1859
|
+
const message = lines.join("\n");
|
|
1860
|
+
deps.writeStdout(message);
|
|
1861
|
+
return message;
|
|
1862
|
+
}
|
|
1863
|
+
if (command.kind === "chat.connect" && deps.startChat) {
|
|
1864
|
+
await ensureDaemonRunning(deps);
|
|
1865
|
+
await deps.startChat(command.agent);
|
|
1866
|
+
return "";
|
|
1867
|
+
}
|
|
711
1868
|
if (command.kind === "hatch.start") {
|
|
712
1869
|
// Route through adoption specialist when no explicit hatch args were provided
|
|
713
1870
|
const hasExplicitHatchArgs = !!(command.agentName || command.humanName || command.provider || command.credentials);
|
|
714
1871
|
if (deps.runAdoptionSpecialist && !hasExplicitHatchArgs) {
|
|
1872
|
+
// System setup first — ouro command, subagents, UTI — before the interactive specialist
|
|
1873
|
+
await performSystemSetup(deps);
|
|
715
1874
|
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
716
1875
|
if (!hatchlingName) {
|
|
717
1876
|
return "";
|
|
718
1877
|
}
|
|
719
|
-
try {
|
|
720
|
-
await deps.installSubagents();
|
|
721
|
-
}
|
|
722
|
-
catch (error) {
|
|
723
|
-
(0, runtime_1.emitNervesEvent)({
|
|
724
|
-
level: "warn",
|
|
725
|
-
component: "daemon",
|
|
726
|
-
event: "daemon.subagent_install_error",
|
|
727
|
-
message: "subagent auto-install failed",
|
|
728
|
-
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
732
1878
|
await ensureDaemonRunning(deps);
|
|
733
1879
|
if (deps.startChat) {
|
|
734
1880
|
await deps.startChat(hatchlingName);
|
|
@@ -744,19 +1890,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
744
1890
|
}
|
|
745
1891
|
const hatchInput = await resolveHatchInput(command, deps);
|
|
746
1892
|
const result = await hatchRunner(hatchInput);
|
|
747
|
-
|
|
748
|
-
await deps.installSubagents();
|
|
749
|
-
}
|
|
750
|
-
catch (error) {
|
|
751
|
-
(0, runtime_1.emitNervesEvent)({
|
|
752
|
-
level: "warn",
|
|
753
|
-
component: "daemon",
|
|
754
|
-
event: "daemon.subagent_install_error",
|
|
755
|
-
message: "subagent auto-install failed",
|
|
756
|
-
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
1893
|
+
await performSystemSetup(deps);
|
|
760
1894
|
const daemonResult = await ensureDaemonRunning(deps);
|
|
761
1895
|
if (deps.startChat) {
|
|
762
1896
|
await deps.startChat(hatchInput.agentName);
|
|
@@ -778,9 +1912,22 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
778
1912
|
deps.writeStdout(message);
|
|
779
1913
|
return message;
|
|
780
1914
|
}
|
|
1915
|
+
if (command.kind === "daemon.status" && isDaemonUnavailableError(error)) {
|
|
1916
|
+
const message = daemonUnavailableStatusOutput(deps.socketPath);
|
|
1917
|
+
deps.writeStdout(message);
|
|
1918
|
+
return message;
|
|
1919
|
+
}
|
|
1920
|
+
if (command.kind === "daemon.stop" && isDaemonUnavailableError(error)) {
|
|
1921
|
+
const message = "daemon not running";
|
|
1922
|
+
deps.writeStdout(message);
|
|
1923
|
+
return message;
|
|
1924
|
+
}
|
|
781
1925
|
throw error;
|
|
782
1926
|
}
|
|
783
|
-
const
|
|
1927
|
+
const fallbackMessage = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
|
|
1928
|
+
const message = command.kind === "daemon.status"
|
|
1929
|
+
? formatDaemonStatusOutput(response, fallbackMessage)
|
|
1930
|
+
: fallbackMessage;
|
|
784
1931
|
deps.writeStdout(message);
|
|
785
1932
|
return message;
|
|
786
1933
|
}
|