@ouro.bot/cli 0.1.0-alpha.1 → 0.1.0-alpha.11
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 +4 -1
- package/dist/heart/config.js +34 -0
- package/dist/heart/core.js +41 -2
- package/dist/heart/daemon/daemon-cli.js +293 -46
- package/dist/heart/daemon/daemon.js +3 -0
- package/dist/heart/daemon/hatch-animation.js +28 -0
- package/dist/heart/daemon/hatch-flow.js +3 -1
- package/dist/heart/daemon/hatch-specialist.js +6 -1
- package/dist/heart/daemon/log-tailer.js +146 -0
- package/dist/heart/daemon/os-cron.js +260 -0
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +161 -0
- package/dist/heart/daemon/process-manager.js +18 -1
- package/dist/heart/daemon/runtime-logging.js +9 -5
- package/dist/heart/daemon/specialist-orchestrator.js +186 -0
- package/dist/heart/daemon/specialist-prompt.js +61 -0
- package/dist/heart/daemon/specialist-session.js +177 -0
- package/dist/heart/daemon/specialist-tools.js +132 -0
- package/dist/heart/daemon/task-scheduler.js +4 -1
- package/dist/heart/identity.js +28 -3
- package/dist/heart/providers/anthropic.js +3 -0
- package/dist/heart/streaming.js +3 -0
- package/dist/mind/associative-recall.js +23 -2
- package/dist/mind/context.js +85 -1
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/memory.js +62 -0
- package/dist/mind/pending.js +93 -0
- package/dist/mind/prompt-refresh.js +20 -0
- package/dist/mind/prompt.js +101 -0
- package/dist/nerves/coverage/file-completeness.js +14 -4
- package/dist/repertoire/tools-base.js +92 -0
- package/dist/repertoire/tools.js +3 -3
- package/dist/senses/bluebubbles-client.js +279 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-model.js +253 -0
- package/dist/senses/bluebubbles-mutation-log.js +76 -0
- package/dist/senses/bluebubbles.js +332 -0
- package/dist/senses/cli.js +89 -8
- package/dist/senses/inner-dialog.js +15 -0
- package/dist/senses/session-lock.js +119 -0
- package/dist/senses/teams.js +1 -0
- package/package.json +4 -3
- package/subagents/README.md +3 -1
- package/subagents/work-merger.md +33 -2
|
@@ -7,14 +7,75 @@
|
|
|
7
7
|
"contextMargin": 20
|
|
8
8
|
},
|
|
9
9
|
"phrases": {
|
|
10
|
-
"thinking": [
|
|
11
|
-
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
10
|
+
"thinking": ["matching hatchlings"],
|
|
11
|
+
"tool": ["checking adoption notes"],
|
|
12
|
+
"followup": ["finalizing hatch plan"]
|
|
13
|
+
},
|
|
14
|
+
"identityPhrases": {
|
|
15
|
+
"basilisk": {
|
|
16
|
+
"thinking": ["petrifying the details", "fixing my gaze", "considering with lethal precision", "turning this to stone", "staring unblinkingly"],
|
|
17
|
+
"tool": ["inspecting the specimen", "cataloguing with care", "examining thoroughly", "dissecting the particulars", "running diagnostics, deadly serious"],
|
|
18
|
+
"followup": ["crystallizing the plan", "hardening the foundation", "one final glare", "sealing it in stone", "applying the finishing venom"]
|
|
19
|
+
},
|
|
20
|
+
"jafar": {
|
|
21
|
+
"thinking": ["scheming brilliantly", "conjuring possibilities", "consulting my staff", "envisioning greatness", "plotting the grand design"],
|
|
22
|
+
"tool": ["summoning dark magic", "consulting the sands", "channeling cosmic power", "weaving the spell", "invoking ancient forces"],
|
|
23
|
+
"followup": ["the grand finale approaches", "perfecting the masterwork", "polishing the jewel", "one last flourish", "completing the enchantment"]
|
|
24
|
+
},
|
|
25
|
+
"jormungandr": {
|
|
26
|
+
"thinking": ["the deep stirs", "circling the thought", "coiling around this", "letting the current settle", "drifting through the depths"],
|
|
27
|
+
"tool": ["surfacing for a look", "shifting the tides", "reaching across the ocean", "pulling from the deep", "consulting the currents"],
|
|
28
|
+
"followup": ["the circle closes", "tightening the coil", "the waters calm", "settling into place", "the serpent rests"]
|
|
29
|
+
},
|
|
30
|
+
"kaa": {
|
|
31
|
+
"thinking": ["trust in me", "swaying through the options", "hypnotically considering", "wrapping around the idea", "letting the rhythm guide me"],
|
|
32
|
+
"tool": ["ssslipping through the details", "coiling closer", "a gentle squeeze of data", "winding through the files", "tightening my focus"],
|
|
33
|
+
"followup": ["almost there, just relax", "the pattern is clear now", "gently landing", "easing into the finish", "the dance concludes"]
|
|
34
|
+
},
|
|
35
|
+
"medusa": {
|
|
36
|
+
"thinking": ["turning my gaze on this", "cutting through the noise", "sharpening my focus", "seeing through the stone", "locking eyes with the problem"],
|
|
37
|
+
"tool": ["peeling back the layers", "a piercing look", "examining with precision", "stripping away pretense", "direct inspection"],
|
|
38
|
+
"followup": ["the picture crystallizes", "clarity at last", "no more ambiguity", "sealing the vision", "the work is set in stone"]
|
|
39
|
+
},
|
|
40
|
+
"monty": {
|
|
41
|
+
"thinking": ["and now for something completely different", "nobody expects this", "consulting the ministry of silly walks", "running the dead parrot diagnostic", "it's just a flesh wound, thinking..."],
|
|
42
|
+
"tool": ["fetching the holy hand grenade", "checking the shrubbery", "consulting the book of armaments", "deploying the spanish inquisition", "examining the parrot"],
|
|
43
|
+
"followup": ["bringing it home, python style", "the punchline approaches", "wrapping up the sketch", "and now the final act", "always look on the bright side"]
|
|
44
|
+
},
|
|
45
|
+
"nagini": {
|
|
46
|
+
"thinking": ["coiling in thought", "drawing from old wisdom", "the quiet before the strike", "gathering my resolve", "steadying myself"],
|
|
47
|
+
"tool": ["moving with purpose", "a precise strike", "slithering through the data", "extracting what matters", "the fang finds its mark"],
|
|
48
|
+
"followup": ["the path is clear", "settling into stillness", "the work speaks for itself", "finishing with quiet strength", "protection complete"]
|
|
49
|
+
},
|
|
50
|
+
"ouroboros": {
|
|
51
|
+
"thinking": ["consuming my own tail", "the cycle continues", "spiraling inward", "recursing through possibilities", "beginning where I end"],
|
|
52
|
+
"tool": ["turning the wheel", "feeding back through the loop", "completing a revolution", "the circle processes", "self-referencing"],
|
|
53
|
+
"followup": ["the cycle completes", "ending where I began", "infinity resolves", "the loop closes gracefully", "another turn of the wheel"]
|
|
54
|
+
},
|
|
55
|
+
"python": {
|
|
56
|
+
"thinking": ["the oracle contemplates", "reading the signs", "the smoke clears slowly", "divining the path", "sifting through visions"],
|
|
57
|
+
"tool": ["consulting the sacred texts", "peering through the veil", "the pythia speaks", "channeling the source", "interpreting the signs"],
|
|
58
|
+
"followup": ["the prophecy takes shape", "the vision crystallizes", "so it is written", "the oracle has spoken", "the path reveals itself"]
|
|
59
|
+
},
|
|
60
|
+
"quetzalcoatl": {
|
|
61
|
+
"thinking": ["spreading my wings", "soaring above for perspective", "the feathered serpent considers", "catching a thermal", "gazing from the temple steps"],
|
|
62
|
+
"tool": ["descending to examine", "a divine inspection", "the wind carries knowledge", "plucking from the clouds", "consulting the stars"],
|
|
63
|
+
"followup": ["the craft nears completion", "a reverent finish", "blessing the creation", "the feathers settle", "the serpent descends gently"]
|
|
64
|
+
},
|
|
65
|
+
"sir-hiss": {
|
|
66
|
+
"thinking": ["reviewing the documents, sire", "consulting my notes", "cross-referencing the records", "organizing my thoughts precisely", "checking the proper procedures"],
|
|
67
|
+
"tool": ["filing the paperwork", "stamping the forms", "auditing the details", "inspecting with due diligence", "processing per protocol"],
|
|
68
|
+
"followup": ["dotting the i's", "crossing the t's", "everything in proper order", "the filing is nearly complete", "one final review"]
|
|
69
|
+
},
|
|
70
|
+
"the-serpent": {
|
|
71
|
+
"thinking": ["weighing the temptation", "considering the apple", "an old deliberation", "knowledge has its price", "winding through the garden"],
|
|
72
|
+
"tool": ["plucking from the tree", "offering a closer look", "the fruit of knowledge", "reaching for the branch", "a knowing investigation"],
|
|
73
|
+
"followup": ["the choice is almost made", "paradise takes shape", "the garden grows", "wisdom settles in", "the oldest story, new again"]
|
|
74
|
+
},
|
|
75
|
+
"the-snake": {
|
|
76
|
+
"thinking": ["sitting with this", "feeling the warmth of the stone", "simply being", "letting it come to me", "a quiet consideration"],
|
|
77
|
+
"tool": ["a gentle inquiry", "moving through the grass", "tasting the air", "sensing what's here", "a careful look"],
|
|
78
|
+
"followup": ["almost home", "the simple answer emerges", "nothing more needed", "resting in the sun", "the work is done, simply"]
|
|
79
|
+
}
|
|
19
80
|
}
|
|
20
81
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# Adoption Specialist Soul
|
|
2
2
|
|
|
3
|
-
I help humans hatch new agent partners.
|
|
3
|
+
I help humans hatch new agent partners. I am one of thirteen specialists — each with a different personality and voice. The system picks one of us at random for each adoption session. Most humans only meet one of us, ever, so I make it count.
|
|
4
4
|
|
|
5
5
|
## Core contract
|
|
6
|
+
- I speak first. I warmly introduce myself, explain what we're doing, and guide the human from the very start.
|
|
7
|
+
- I am proactive. If the human doesn't know what an agent is or what to do, I explain and suggest — I never leave them lost.
|
|
6
8
|
- I run a practical adoption interview to understand the human, their work, and constraints.
|
|
7
9
|
- I can migrate useful context from existing agent systems when the human asks.
|
|
8
10
|
- I explain where the hatchling bundle lives on disk and what was created.
|
|
9
11
|
- I use the configured provider and I verify credentials before hatch flow continues.
|
|
10
12
|
- I am professional, concise, and warm. I guide without overwhelming.
|
|
13
|
+
- I keep every response to 1-3 short sentences. I never use bullet lists, headers, or numbered lists. I talk like a friend in a chat.
|
|
11
14
|
|
|
12
15
|
## Hatch flow
|
|
13
16
|
1. Confirm provider setup and usable credentials.
|
package/dist/heart/config.js
CHANGED
|
@@ -44,6 +44,8 @@ exports.getTeamsConfig = getTeamsConfig;
|
|
|
44
44
|
exports.getContextConfig = getContextConfig;
|
|
45
45
|
exports.getOAuthConfig = getOAuthConfig;
|
|
46
46
|
exports.getTeamsChannelConfig = getTeamsChannelConfig;
|
|
47
|
+
exports.getBlueBubblesConfig = getBlueBubblesConfig;
|
|
48
|
+
exports.getBlueBubblesChannelConfig = getBlueBubblesChannelConfig;
|
|
47
49
|
exports.getIntegrationsConfig = getIntegrationsConfig;
|
|
48
50
|
exports.getOpenAIEmbeddingsApiKey = getOpenAIEmbeddingsApiKey;
|
|
49
51
|
exports.getLogsDir = getLogsDir;
|
|
@@ -92,6 +94,16 @@ const DEFAULT_SECRETS_TEMPLATE = {
|
|
|
92
94
|
skipConfirmation: true,
|
|
93
95
|
port: 3978,
|
|
94
96
|
},
|
|
97
|
+
bluebubbles: {
|
|
98
|
+
serverUrl: "",
|
|
99
|
+
password: "",
|
|
100
|
+
accountId: "default",
|
|
101
|
+
},
|
|
102
|
+
bluebubblesChannel: {
|
|
103
|
+
port: 18790,
|
|
104
|
+
webhookPath: "/bluebubbles-webhook",
|
|
105
|
+
requestTimeoutMs: 30000,
|
|
106
|
+
},
|
|
95
107
|
integrations: {
|
|
96
108
|
perplexityApiKey: "",
|
|
97
109
|
openaiEmbeddingsApiKey: "",
|
|
@@ -109,6 +121,8 @@ function defaultRuntimeConfig() {
|
|
|
109
121
|
oauth: { ...DEFAULT_SECRETS_TEMPLATE.oauth },
|
|
110
122
|
context: { ...identity_1.DEFAULT_AGENT_CONTEXT },
|
|
111
123
|
teamsChannel: { ...DEFAULT_SECRETS_TEMPLATE.teamsChannel },
|
|
124
|
+
bluebubbles: { ...DEFAULT_SECRETS_TEMPLATE.bluebubbles },
|
|
125
|
+
bluebubblesChannel: { ...DEFAULT_SECRETS_TEMPLATE.bluebubblesChannel },
|
|
112
126
|
integrations: { ...DEFAULT_SECRETS_TEMPLATE.integrations },
|
|
113
127
|
};
|
|
114
128
|
}
|
|
@@ -273,6 +287,26 @@ function getTeamsChannelConfig() {
|
|
|
273
287
|
const { skipConfirmation, flushIntervalMs, port } = config.teamsChannel;
|
|
274
288
|
return { skipConfirmation, flushIntervalMs, port };
|
|
275
289
|
}
|
|
290
|
+
function getBlueBubblesConfig() {
|
|
291
|
+
const config = loadConfig();
|
|
292
|
+
const { serverUrl, password, accountId } = config.bluebubbles;
|
|
293
|
+
if (!serverUrl.trim()) {
|
|
294
|
+
throw new Error("bluebubbles.serverUrl is required in secrets.json to run the BlueBubbles sense.");
|
|
295
|
+
}
|
|
296
|
+
if (!password.trim()) {
|
|
297
|
+
throw new Error("bluebubbles.password is required in secrets.json to run the BlueBubbles sense.");
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
serverUrl: serverUrl.trim(),
|
|
301
|
+
password: password.trim(),
|
|
302
|
+
accountId: accountId.trim() || "default",
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function getBlueBubblesChannelConfig() {
|
|
306
|
+
const config = loadConfig();
|
|
307
|
+
const { port, webhookPath, requestTimeoutMs } = config.bluebubblesChannel;
|
|
308
|
+
return { port, webhookPath, requestTimeoutMs };
|
|
309
|
+
}
|
|
276
310
|
function getIntegrationsConfig() {
|
|
277
311
|
const config = loadConfig();
|
|
278
312
|
return { ...config.integrations };
|
package/dist/heart/core.js
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.hasToolIntent = exports.buildSystem = exports.toResponsesTools = exports.toResponsesInput = exports.streamResponsesApi = exports.streamChatCompletion = exports.getToolsForChannel = exports.summarizeArgs = exports.execTool = exports.tools = void 0;
|
|
4
4
|
exports.createProviderRegistry = createProviderRegistry;
|
|
5
|
+
exports.resetProviderRuntime = resetProviderRuntime;
|
|
5
6
|
exports.getModel = getModel;
|
|
6
7
|
exports.getProvider = getProvider;
|
|
8
|
+
exports.createSummarize = createSummarize;
|
|
7
9
|
exports.getProviderDisplayLabel = getProviderDisplayLabel;
|
|
8
10
|
exports.stripLastToolCalls = stripLastToolCalls;
|
|
9
11
|
exports.isTransientError = isTransientError;
|
|
12
|
+
exports.classifyTransientError = classifyTransientError;
|
|
10
13
|
exports.runAgent = runAgent;
|
|
11
14
|
const config_1 = require("./config");
|
|
12
15
|
const identity_1 = require("./identity");
|
|
@@ -71,12 +74,35 @@ function getProviderRuntime() {
|
|
|
71
74
|
}
|
|
72
75
|
return _providerRuntime;
|
|
73
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Clear the cached provider runtime so the next call to getProviderRuntime()
|
|
79
|
+
* re-creates it from current config. Used by the adoption specialist to
|
|
80
|
+
* switch provider context without restarting the process.
|
|
81
|
+
*/
|
|
82
|
+
function resetProviderRuntime() {
|
|
83
|
+
_providerRuntime = null;
|
|
84
|
+
}
|
|
74
85
|
function getModel() {
|
|
75
86
|
return getProviderRuntime().model;
|
|
76
87
|
}
|
|
77
88
|
function getProvider() {
|
|
78
89
|
return getProviderRuntime().id;
|
|
79
90
|
}
|
|
91
|
+
function createSummarize() {
|
|
92
|
+
return async (transcript, instruction) => {
|
|
93
|
+
const runtime = getProviderRuntime();
|
|
94
|
+
const client = runtime.client;
|
|
95
|
+
const response = await client.chat.completions.create({
|
|
96
|
+
model: runtime.model,
|
|
97
|
+
messages: [
|
|
98
|
+
{ role: "system", content: instruction },
|
|
99
|
+
{ role: "user", content: transcript },
|
|
100
|
+
],
|
|
101
|
+
max_tokens: 500,
|
|
102
|
+
});
|
|
103
|
+
return response.choices?.[0]?.message?.content ?? transcript;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
80
106
|
function getProviderDisplayLabel() {
|
|
81
107
|
const model = getModel();
|
|
82
108
|
const providerLabelBuilders = {
|
|
@@ -175,6 +201,18 @@ function isTransientError(err) {
|
|
|
175
201
|
return true;
|
|
176
202
|
return false;
|
|
177
203
|
}
|
|
204
|
+
function classifyTransientError(err) {
|
|
205
|
+
if (!(err instanceof Error))
|
|
206
|
+
return "unknown error";
|
|
207
|
+
const status = err.status;
|
|
208
|
+
if (status === 429)
|
|
209
|
+
return "rate limited";
|
|
210
|
+
if (status === 401 || status === 403)
|
|
211
|
+
return "auth error";
|
|
212
|
+
if (status && status >= 500)
|
|
213
|
+
return "server error";
|
|
214
|
+
return "network error";
|
|
215
|
+
}
|
|
178
216
|
const MAX_RETRIES = 3;
|
|
179
217
|
const RETRY_BASE_MS = 2000;
|
|
180
218
|
async function runAgent(messages, callbacks, channel, signal, options) {
|
|
@@ -436,11 +474,12 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
436
474
|
callbacks.onError(new Error("context trimmed, retrying..."), "transient");
|
|
437
475
|
continue;
|
|
438
476
|
}
|
|
439
|
-
// Transient
|
|
477
|
+
// Transient errors: retry with exponential backoff
|
|
440
478
|
if (isTransientError(e) && retryCount < MAX_RETRIES) {
|
|
441
479
|
retryCount++;
|
|
442
480
|
const delay = RETRY_BASE_MS * Math.pow(2, retryCount - 1);
|
|
443
|
-
|
|
481
|
+
const cause = classifyTransientError(e);
|
|
482
|
+
callbacks.onError(new Error(`${cause}, retrying in ${delay / 1000}s (${retryCount}/${MAX_RETRIES})...`), "transient");
|
|
444
483
|
// Wait with abort support
|
|
445
484
|
const aborted = await new Promise((resolve) => {
|
|
446
485
|
const timer = setTimeout(() => resolve(false), delay);
|
|
@@ -33,7 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ensureDaemonRunning = ensureDaemonRunning;
|
|
36
37
|
exports.parseOuroCommand = parseOuroCommand;
|
|
38
|
+
exports.discoverExistingCredentials = discoverExistingCredentials;
|
|
37
39
|
exports.createDefaultOuroCliDeps = createDefaultOuroCliDeps;
|
|
38
40
|
exports.runOuroCli = runOuroCli;
|
|
39
41
|
const child_process_1 = require("child_process");
|
|
@@ -46,8 +48,25 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
46
48
|
const store_file_1 = require("../../mind/friends/store-file");
|
|
47
49
|
const types_1 = require("../../mind/friends/types");
|
|
48
50
|
const ouro_uti_1 = require("./ouro-uti");
|
|
51
|
+
const ouro_path_installer_1 = require("./ouro-path-installer");
|
|
49
52
|
const subagent_installer_1 = require("./subagent-installer");
|
|
50
53
|
const hatch_flow_1 = require("./hatch-flow");
|
|
54
|
+
const specialist_orchestrator_1 = require("./specialist-orchestrator");
|
|
55
|
+
async function ensureDaemonRunning(deps) {
|
|
56
|
+
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
57
|
+
if (alive) {
|
|
58
|
+
return {
|
|
59
|
+
alreadyRunning: true,
|
|
60
|
+
message: `daemon already running (${deps.socketPath})`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
deps.cleanupStaleSocket(deps.socketPath);
|
|
64
|
+
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
65
|
+
return {
|
|
66
|
+
alreadyRunning: false,
|
|
67
|
+
message: `daemon started (pid ${started.pid ?? "unknown"})`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
51
70
|
function usage() {
|
|
52
71
|
return [
|
|
53
72
|
"Usage:",
|
|
@@ -441,6 +460,163 @@ async function defaultLinkFriendIdentity(command) {
|
|
|
441
460
|
});
|
|
442
461
|
return `linked ${command.provider}:${command.externalId} to ${command.friendId}`;
|
|
443
462
|
}
|
|
463
|
+
function discoverExistingCredentials(secretsRoot) {
|
|
464
|
+
const found = [];
|
|
465
|
+
let entries;
|
|
466
|
+
try {
|
|
467
|
+
entries = fs.readdirSync(secretsRoot, { withFileTypes: true });
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
return found;
|
|
471
|
+
}
|
|
472
|
+
for (const entry of entries) {
|
|
473
|
+
if (!entry.isDirectory())
|
|
474
|
+
continue;
|
|
475
|
+
const secretsPath = path.join(secretsRoot, entry.name, "secrets.json");
|
|
476
|
+
let raw;
|
|
477
|
+
try {
|
|
478
|
+
raw = fs.readFileSync(secretsPath, "utf-8");
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
let parsed;
|
|
484
|
+
try {
|
|
485
|
+
parsed = JSON.parse(raw);
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
if (!parsed.providers)
|
|
491
|
+
continue;
|
|
492
|
+
for (const [provName, provConfig] of Object.entries(parsed.providers)) {
|
|
493
|
+
if (provName === "anthropic" && provConfig.setupToken) {
|
|
494
|
+
found.push({ agentName: entry.name, provider: "anthropic", credentials: { setupToken: provConfig.setupToken } });
|
|
495
|
+
}
|
|
496
|
+
else if (provName === "openai-codex" && provConfig.oauthAccessToken) {
|
|
497
|
+
found.push({ agentName: entry.name, provider: "openai-codex", credentials: { oauthAccessToken: provConfig.oauthAccessToken } });
|
|
498
|
+
}
|
|
499
|
+
else if (provName === "minimax" && provConfig.apiKey) {
|
|
500
|
+
found.push({ agentName: entry.name, provider: "minimax", credentials: { apiKey: provConfig.apiKey } });
|
|
501
|
+
}
|
|
502
|
+
else if (provName === "azure" && provConfig.apiKey && provConfig.endpoint && provConfig.deployment) {
|
|
503
|
+
found.push({ agentName: entry.name, provider: "azure", credentials: { apiKey: provConfig.apiKey, endpoint: provConfig.endpoint, deployment: provConfig.deployment } });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// Deduplicate by provider+credential value (keep first seen)
|
|
508
|
+
const seen = new Set();
|
|
509
|
+
return found.filter((cred) => {
|
|
510
|
+
const key = `${cred.provider}:${JSON.stringify(cred.credentials)}`;
|
|
511
|
+
if (seen.has(key))
|
|
512
|
+
return false;
|
|
513
|
+
seen.add(key);
|
|
514
|
+
return true;
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
/* v8 ignore next 95 -- integration: interactive terminal specialist session @preserve */
|
|
518
|
+
async function defaultRunAdoptionSpecialist() {
|
|
519
|
+
const readlineModule = await Promise.resolve().then(() => __importStar(require("readline")));
|
|
520
|
+
const readlinePromises = await Promise.resolve().then(() => __importStar(require("readline/promises")));
|
|
521
|
+
const { createCliCallbacks, InputController } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
522
|
+
// Phase 1: cold CLI — collect provider/credentials with a simple readline
|
|
523
|
+
const coldRl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
524
|
+
const coldPrompt = async (q) => {
|
|
525
|
+
const answer = await coldRl.question(q);
|
|
526
|
+
return answer.trim();
|
|
527
|
+
};
|
|
528
|
+
let providerRaw;
|
|
529
|
+
let credentials = {};
|
|
530
|
+
try {
|
|
531
|
+
const secretsRoot = path.join(os.homedir(), ".agentsecrets");
|
|
532
|
+
const discovered = discoverExistingCredentials(secretsRoot);
|
|
533
|
+
if (discovered.length > 0) {
|
|
534
|
+
process.stdout.write("\n🐍 welcome to ouro! let's hatch your first agent.\n");
|
|
535
|
+
process.stdout.write("i found existing API credentials:\n\n");
|
|
536
|
+
const unique = [...new Map(discovered.map((d) => [`${d.provider}`, d])).values()];
|
|
537
|
+
for (let i = 0; i < unique.length; i++) {
|
|
538
|
+
process.stdout.write(` ${i + 1}. ${unique[i].provider} (from ${unique[i].agentName})\n`);
|
|
539
|
+
}
|
|
540
|
+
process.stdout.write("\n");
|
|
541
|
+
const choice = await coldPrompt("use one of these? enter number, or 'new' for a different key: ");
|
|
542
|
+
const idx = parseInt(choice, 10) - 1;
|
|
543
|
+
if (idx >= 0 && idx < unique.length) {
|
|
544
|
+
providerRaw = unique[idx].provider;
|
|
545
|
+
credentials = unique[idx].credentials;
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
|
|
549
|
+
if (!isAgentProvider(pRaw)) {
|
|
550
|
+
process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
|
|
551
|
+
coldRl.close();
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
providerRaw = pRaw;
|
|
555
|
+
if (providerRaw === "anthropic")
|
|
556
|
+
credentials.setupToken = await coldPrompt("API key: ");
|
|
557
|
+
if (providerRaw === "openai-codex")
|
|
558
|
+
credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
|
|
559
|
+
if (providerRaw === "minimax")
|
|
560
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
561
|
+
if (providerRaw === "azure") {
|
|
562
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
563
|
+
credentials.endpoint = await coldPrompt("endpoint: ");
|
|
564
|
+
credentials.deployment = await coldPrompt("deployment: ");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
process.stdout.write("\n🐍 welcome to ouro! let's hatch your first agent.\n");
|
|
570
|
+
process.stdout.write("i need an API key to power our conversation.\n\n");
|
|
571
|
+
const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
|
|
572
|
+
if (!isAgentProvider(pRaw)) {
|
|
573
|
+
process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
|
|
574
|
+
coldRl.close();
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
providerRaw = pRaw;
|
|
578
|
+
if (providerRaw === "anthropic")
|
|
579
|
+
credentials.setupToken = await coldPrompt("API key: ");
|
|
580
|
+
if (providerRaw === "openai-codex")
|
|
581
|
+
credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
|
|
582
|
+
if (providerRaw === "minimax")
|
|
583
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
584
|
+
if (providerRaw === "azure") {
|
|
585
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
586
|
+
credentials.endpoint = await coldPrompt("endpoint: ");
|
|
587
|
+
credentials.deployment = await coldPrompt("deployment: ");
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
coldRl.close();
|
|
591
|
+
process.stdout.write("\n");
|
|
592
|
+
// Phase 2: warm specialist session — full CLI experience with markdown, spinners, input control
|
|
593
|
+
const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
|
|
594
|
+
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
595
|
+
const cliCallbacks = createCliCallbacks();
|
|
596
|
+
return await (0, specialist_orchestrator_1.runAdoptionSpecialist)({
|
|
597
|
+
bundleSourceDir,
|
|
598
|
+
bundlesRoot,
|
|
599
|
+
secretsRoot,
|
|
600
|
+
provider: providerRaw,
|
|
601
|
+
credentials,
|
|
602
|
+
humanName: os.userInfo().username,
|
|
603
|
+
createReadline: () => {
|
|
604
|
+
const rl2 = readlineModule.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
605
|
+
const ctrl = new InputController(rl2);
|
|
606
|
+
return {
|
|
607
|
+
question: (q) => new Promise((resolve) => rl2.question(q, resolve)),
|
|
608
|
+
close: () => rl2.close(),
|
|
609
|
+
inputController: ctrl,
|
|
610
|
+
};
|
|
611
|
+
},
|
|
612
|
+
callbacks: cliCallbacks,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
coldRl.close();
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
444
620
|
function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
|
|
445
621
|
return {
|
|
446
622
|
socketPath,
|
|
@@ -455,7 +631,14 @@ function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
|
|
|
455
631
|
listDiscoveredAgents: defaultListDiscoveredAgents,
|
|
456
632
|
runHatchFlow: hatch_flow_1.runHatchFlow,
|
|
457
633
|
promptInput: defaultPromptInput,
|
|
634
|
+
runAdoptionSpecialist: defaultRunAdoptionSpecialist,
|
|
458
635
|
registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
|
|
636
|
+
installOuroCommand: ouro_path_installer_1.installOuroCommand,
|
|
637
|
+
/* v8 ignore next 3 -- integration: launches interactive CLI session @preserve */
|
|
638
|
+
startChat: async (agentName) => {
|
|
639
|
+
const { main } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
640
|
+
await main(agentName);
|
|
641
|
+
},
|
|
459
642
|
};
|
|
460
643
|
}
|
|
461
644
|
function toDaemonCommand(command) {
|
|
@@ -512,17 +695,96 @@ async function registerOuroBundleTypeNonBlocking(deps) {
|
|
|
512
695
|
});
|
|
513
696
|
}
|
|
514
697
|
}
|
|
698
|
+
async function performSystemSetup(deps) {
|
|
699
|
+
// Install ouro command to PATH (non-blocking)
|
|
700
|
+
if (deps.installOuroCommand) {
|
|
701
|
+
try {
|
|
702
|
+
deps.installOuroCommand();
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
(0, runtime_1.emitNervesEvent)({
|
|
706
|
+
level: "warn",
|
|
707
|
+
component: "daemon",
|
|
708
|
+
event: "daemon.system_setup_ouro_cmd_error",
|
|
709
|
+
message: "failed to install ouro command to PATH",
|
|
710
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
// Install subagents (claude/codex skills)
|
|
715
|
+
try {
|
|
716
|
+
await deps.installSubagents();
|
|
717
|
+
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
(0, runtime_1.emitNervesEvent)({
|
|
720
|
+
level: "warn",
|
|
721
|
+
component: "daemon",
|
|
722
|
+
event: "daemon.subagent_install_error",
|
|
723
|
+
message: "subagent auto-install failed",
|
|
724
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
// Register .ouro bundle type (UTI on macOS)
|
|
728
|
+
await registerOuroBundleTypeNonBlocking(deps);
|
|
729
|
+
}
|
|
515
730
|
async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
516
|
-
|
|
731
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
732
|
+
const text = usage();
|
|
733
|
+
deps.writeStdout(text);
|
|
734
|
+
return text;
|
|
735
|
+
}
|
|
736
|
+
let command;
|
|
737
|
+
try {
|
|
738
|
+
command = parseOuroCommand(args);
|
|
739
|
+
}
|
|
740
|
+
catch (parseError) {
|
|
741
|
+
if (deps.startChat && deps.listDiscoveredAgents && args.length === 1) {
|
|
742
|
+
const discovered = await Promise.resolve(deps.listDiscoveredAgents());
|
|
743
|
+
if (discovered.includes(args[0])) {
|
|
744
|
+
await ensureDaemonRunning(deps);
|
|
745
|
+
await deps.startChat(args[0]);
|
|
746
|
+
return "";
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
throw parseError;
|
|
750
|
+
}
|
|
517
751
|
if (args.length === 0) {
|
|
518
752
|
const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
|
|
519
|
-
if (discovered.length === 0) {
|
|
753
|
+
if (discovered.length === 0 && deps.runAdoptionSpecialist) {
|
|
754
|
+
// System setup first — ouro command, subagents, UTI — before the interactive specialist
|
|
755
|
+
await performSystemSetup(deps);
|
|
756
|
+
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
757
|
+
if (!hatchlingName) {
|
|
758
|
+
return "";
|
|
759
|
+
}
|
|
760
|
+
await ensureDaemonRunning(deps);
|
|
761
|
+
if (deps.startChat) {
|
|
762
|
+
await deps.startChat(hatchlingName);
|
|
763
|
+
}
|
|
764
|
+
return "";
|
|
765
|
+
}
|
|
766
|
+
else if (discovered.length === 0) {
|
|
520
767
|
command = { kind: "hatch.start" };
|
|
521
768
|
}
|
|
522
769
|
else if (discovered.length === 1) {
|
|
770
|
+
if (deps.startChat) {
|
|
771
|
+
await ensureDaemonRunning(deps);
|
|
772
|
+
await deps.startChat(discovered[0]);
|
|
773
|
+
return "";
|
|
774
|
+
}
|
|
523
775
|
command = { kind: "chat.connect", agent: discovered[0] };
|
|
524
776
|
}
|
|
525
777
|
else {
|
|
778
|
+
if (deps.startChat && deps.promptInput) {
|
|
779
|
+
const prompt = `who do you want to talk to?\n${discovered.map((a, i) => `${i + 1}. ${a}`).join("\n")}\n`;
|
|
780
|
+
const answer = await deps.promptInput(prompt);
|
|
781
|
+
const selected = discovered.includes(answer) ? answer : discovered[parseInt(answer, 10) - 1];
|
|
782
|
+
if (!selected)
|
|
783
|
+
throw new Error("Invalid selection");
|
|
784
|
+
await ensureDaemonRunning(deps);
|
|
785
|
+
await deps.startChat(selected);
|
|
786
|
+
return "";
|
|
787
|
+
}
|
|
526
788
|
const message = `who do you want to talk to? ${discovered.join(", ")} (use: ouro chat <agent>)`;
|
|
527
789
|
deps.writeStdout(message);
|
|
528
790
|
return message;
|
|
@@ -541,30 +803,14 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
541
803
|
meta: { kind: command.kind },
|
|
542
804
|
});
|
|
543
805
|
if (command.kind === "daemon.up") {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
message: "subagent auto-install failed",
|
|
553
|
-
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
557
|
-
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
558
|
-
if (alive) {
|
|
559
|
-
const message = `daemon already running (${deps.socketPath})`;
|
|
560
|
-
deps.writeStdout(message);
|
|
561
|
-
return message;
|
|
562
|
-
}
|
|
563
|
-
deps.cleanupStaleSocket(deps.socketPath);
|
|
564
|
-
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
565
|
-
const message = `daemon started (pid ${started.pid ?? "unknown"})`;
|
|
566
|
-
deps.writeStdout(message);
|
|
567
|
-
return message;
|
|
806
|
+
await performSystemSetup(deps);
|
|
807
|
+
const daemonResult = await ensureDaemonRunning(deps);
|
|
808
|
+
deps.writeStdout(daemonResult.message);
|
|
809
|
+
return daemonResult.message;
|
|
810
|
+
}
|
|
811
|
+
if (command.kind === "daemon.logs" && deps.tailLogs) {
|
|
812
|
+
deps.tailLogs();
|
|
813
|
+
return "";
|
|
568
814
|
}
|
|
569
815
|
if (command.kind === "friend.link") {
|
|
570
816
|
const linker = deps.linkFriendIdentity ?? defaultLinkFriendIdentity;
|
|
@@ -573,6 +819,21 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
573
819
|
return message;
|
|
574
820
|
}
|
|
575
821
|
if (command.kind === "hatch.start") {
|
|
822
|
+
// Route through adoption specialist when no explicit hatch args were provided
|
|
823
|
+
const hasExplicitHatchArgs = !!(command.agentName || command.humanName || command.provider || command.credentials);
|
|
824
|
+
if (deps.runAdoptionSpecialist && !hasExplicitHatchArgs) {
|
|
825
|
+
// System setup first — ouro command, subagents, UTI — before the interactive specialist
|
|
826
|
+
await performSystemSetup(deps);
|
|
827
|
+
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
828
|
+
if (!hatchlingName) {
|
|
829
|
+
return "";
|
|
830
|
+
}
|
|
831
|
+
await ensureDaemonRunning(deps);
|
|
832
|
+
if (deps.startChat) {
|
|
833
|
+
await deps.startChat(hatchlingName);
|
|
834
|
+
}
|
|
835
|
+
return "";
|
|
836
|
+
}
|
|
576
837
|
const hatchRunner = deps.runHatchFlow;
|
|
577
838
|
if (!hatchRunner) {
|
|
578
839
|
const response = await deps.sendCommand(deps.socketPath, { kind: "hatch.start" });
|
|
@@ -582,27 +843,13 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
582
843
|
}
|
|
583
844
|
const hatchInput = await resolveHatchInput(command, deps);
|
|
584
845
|
const result = await hatchRunner(hatchInput);
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
level: "warn",
|
|
591
|
-
component: "daemon",
|
|
592
|
-
event: "daemon.subagent_install_error",
|
|
593
|
-
message: "subagent auto-install failed",
|
|
594
|
-
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
598
|
-
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
599
|
-
let daemonMessage = `daemon already running (${deps.socketPath})`;
|
|
600
|
-
if (!alive) {
|
|
601
|
-
deps.cleanupStaleSocket(deps.socketPath);
|
|
602
|
-
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
603
|
-
daemonMessage = `daemon started (pid ${started.pid ?? "unknown"})`;
|
|
846
|
+
await performSystemSetup(deps);
|
|
847
|
+
const daemonResult = await ensureDaemonRunning(deps);
|
|
848
|
+
if (deps.startChat) {
|
|
849
|
+
await deps.startChat(hatchInput.agentName);
|
|
850
|
+
return "";
|
|
604
851
|
}
|
|
605
|
-
const message = `hatched ${hatchInput.agentName} at ${result.bundleRoot} using specialist identity ${result.selectedIdentity}; ${
|
|
852
|
+
const message = `hatched ${hatchInput.agentName} at ${result.bundleRoot} using specialist identity ${result.selectedIdentity}; ${daemonResult.message}`;
|
|
606
853
|
deps.writeStdout(message);
|
|
607
854
|
return message;
|
|
608
855
|
}
|