@ouro.bot/cli 0.1.0-alpha.1 → 0.1.0-alpha.10
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/psyche/SOUL.md +3 -1
- package/dist/heart/config.js +34 -0
- package/dist/heart/core.js +41 -2
- package/dist/heart/daemon/daemon-cli.js +280 -22
- 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/process-manager.js +18 -1
- package/dist/heart/daemon/runtime-logging.js +9 -5
- package/dist/heart/daemon/specialist-orchestrator.js +161 -0
- package/dist/heart/daemon/specialist-prompt.js +56 -0
- package/dist/heart/daemon/specialist-session.js +150 -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
|
@@ -1,8 +1,10 @@
|
|
|
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.
|
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");
|
|
@@ -48,6 +50,22 @@ const types_1 = require("../../mind/friends/types");
|
|
|
48
50
|
const ouro_uti_1 = require("./ouro-uti");
|
|
49
51
|
const subagent_installer_1 = require("./subagent-installer");
|
|
50
52
|
const hatch_flow_1 = require("./hatch-flow");
|
|
53
|
+
const specialist_orchestrator_1 = require("./specialist-orchestrator");
|
|
54
|
+
async function ensureDaemonRunning(deps) {
|
|
55
|
+
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
56
|
+
if (alive) {
|
|
57
|
+
return {
|
|
58
|
+
alreadyRunning: true,
|
|
59
|
+
message: `daemon already running (${deps.socketPath})`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
deps.cleanupStaleSocket(deps.socketPath);
|
|
63
|
+
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
64
|
+
return {
|
|
65
|
+
alreadyRunning: false,
|
|
66
|
+
message: `daemon started (pid ${started.pid ?? "unknown"})`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
51
69
|
function usage() {
|
|
52
70
|
return [
|
|
53
71
|
"Usage:",
|
|
@@ -441,6 +459,162 @@ async function defaultLinkFriendIdentity(command) {
|
|
|
441
459
|
});
|
|
442
460
|
return `linked ${command.provider}:${command.externalId} to ${command.friendId}`;
|
|
443
461
|
}
|
|
462
|
+
function discoverExistingCredentials(secretsRoot) {
|
|
463
|
+
const found = [];
|
|
464
|
+
let entries;
|
|
465
|
+
try {
|
|
466
|
+
entries = fs.readdirSync(secretsRoot, { withFileTypes: true });
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
return found;
|
|
470
|
+
}
|
|
471
|
+
for (const entry of entries) {
|
|
472
|
+
if (!entry.isDirectory())
|
|
473
|
+
continue;
|
|
474
|
+
const secretsPath = path.join(secretsRoot, entry.name, "secrets.json");
|
|
475
|
+
let raw;
|
|
476
|
+
try {
|
|
477
|
+
raw = fs.readFileSync(secretsPath, "utf-8");
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
let parsed;
|
|
483
|
+
try {
|
|
484
|
+
parsed = JSON.parse(raw);
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (!parsed.providers)
|
|
490
|
+
continue;
|
|
491
|
+
for (const [provName, provConfig] of Object.entries(parsed.providers)) {
|
|
492
|
+
if (provName === "anthropic" && provConfig.setupToken) {
|
|
493
|
+
found.push({ agentName: entry.name, provider: "anthropic", credentials: { setupToken: provConfig.setupToken } });
|
|
494
|
+
}
|
|
495
|
+
else if (provName === "openai-codex" && provConfig.oauthAccessToken) {
|
|
496
|
+
found.push({ agentName: entry.name, provider: "openai-codex", credentials: { oauthAccessToken: provConfig.oauthAccessToken } });
|
|
497
|
+
}
|
|
498
|
+
else if (provName === "minimax" && provConfig.apiKey) {
|
|
499
|
+
found.push({ agentName: entry.name, provider: "minimax", credentials: { apiKey: provConfig.apiKey } });
|
|
500
|
+
}
|
|
501
|
+
else if (provName === "azure" && provConfig.apiKey && provConfig.endpoint && provConfig.deployment) {
|
|
502
|
+
found.push({ agentName: entry.name, provider: "azure", credentials: { apiKey: provConfig.apiKey, endpoint: provConfig.endpoint, deployment: provConfig.deployment } });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Deduplicate by provider+credential value (keep first seen)
|
|
507
|
+
const seen = new Set();
|
|
508
|
+
return found.filter((cred) => {
|
|
509
|
+
const key = `${cred.provider}:${JSON.stringify(cred.credentials)}`;
|
|
510
|
+
if (seen.has(key))
|
|
511
|
+
return false;
|
|
512
|
+
seen.add(key);
|
|
513
|
+
return true;
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/* v8 ignore next 79 -- integration: interactive terminal specialist session @preserve */
|
|
517
|
+
async function defaultRunAdoptionSpecialist() {
|
|
518
|
+
const readline = await Promise.resolve().then(() => __importStar(require("readline/promises")));
|
|
519
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
520
|
+
const prompt = async (q) => {
|
|
521
|
+
const answer = await rl.question(q);
|
|
522
|
+
return answer.trim();
|
|
523
|
+
};
|
|
524
|
+
try {
|
|
525
|
+
const secretsRoot = path.join(os.homedir(), ".agentsecrets");
|
|
526
|
+
const discovered = discoverExistingCredentials(secretsRoot);
|
|
527
|
+
let providerRaw;
|
|
528
|
+
let credentials = {};
|
|
529
|
+
if (discovered.length > 0) {
|
|
530
|
+
process.stdout.write("\n🐍 welcome to ouro! let's hatch your first agent.\n");
|
|
531
|
+
process.stdout.write("i found existing API credentials:\n\n");
|
|
532
|
+
const unique = [...new Map(discovered.map((d) => [`${d.provider}`, d])).values()];
|
|
533
|
+
for (let i = 0; i < unique.length; i++) {
|
|
534
|
+
process.stdout.write(` ${i + 1}. ${unique[i].provider} (from ${unique[i].agentName})\n`);
|
|
535
|
+
}
|
|
536
|
+
process.stdout.write("\n");
|
|
537
|
+
const choice = await prompt("use one of these? enter number, or 'new' for a different key: ");
|
|
538
|
+
const idx = parseInt(choice, 10) - 1;
|
|
539
|
+
if (idx >= 0 && idx < unique.length) {
|
|
540
|
+
providerRaw = unique[idx].provider;
|
|
541
|
+
credentials = unique[idx].credentials;
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
const pRaw = await prompt("provider (anthropic/azure/minimax/openai-codex): ");
|
|
545
|
+
if (!isAgentProvider(pRaw)) {
|
|
546
|
+
process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
|
|
547
|
+
rl.close();
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
providerRaw = pRaw;
|
|
551
|
+
if (providerRaw === "anthropic")
|
|
552
|
+
credentials.setupToken = await prompt("API key: ");
|
|
553
|
+
if (providerRaw === "openai-codex")
|
|
554
|
+
credentials.oauthAccessToken = await prompt("OAuth token: ");
|
|
555
|
+
if (providerRaw === "minimax")
|
|
556
|
+
credentials.apiKey = await prompt("API key: ");
|
|
557
|
+
if (providerRaw === "azure") {
|
|
558
|
+
credentials.apiKey = await prompt("API key: ");
|
|
559
|
+
credentials.endpoint = await prompt("endpoint: ");
|
|
560
|
+
credentials.deployment = await prompt("deployment: ");
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
process.stdout.write("\n🐍 welcome to ouro! let's hatch your first agent.\n");
|
|
566
|
+
process.stdout.write("i need an API key to power our conversation.\n\n");
|
|
567
|
+
const pRaw = await prompt("provider (anthropic/azure/minimax/openai-codex): ");
|
|
568
|
+
if (!isAgentProvider(pRaw)) {
|
|
569
|
+
process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
|
|
570
|
+
rl.close();
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
providerRaw = pRaw;
|
|
574
|
+
if (providerRaw === "anthropic")
|
|
575
|
+
credentials.setupToken = await prompt("API key: ");
|
|
576
|
+
if (providerRaw === "openai-codex")
|
|
577
|
+
credentials.oauthAccessToken = await prompt("OAuth token: ");
|
|
578
|
+
if (providerRaw === "minimax")
|
|
579
|
+
credentials.apiKey = await prompt("API key: ");
|
|
580
|
+
if (providerRaw === "azure") {
|
|
581
|
+
credentials.apiKey = await prompt("API key: ");
|
|
582
|
+
credentials.endpoint = await prompt("endpoint: ");
|
|
583
|
+
credentials.deployment = await prompt("deployment: ");
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
rl.close();
|
|
587
|
+
process.stdout.write("\n");
|
|
588
|
+
// Locate the bundled AdoptionSpecialist.ouro shipped with the npm package
|
|
589
|
+
const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
|
|
590
|
+
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
591
|
+
return await (0, specialist_orchestrator_1.runAdoptionSpecialist)({
|
|
592
|
+
bundleSourceDir,
|
|
593
|
+
bundlesRoot,
|
|
594
|
+
secretsRoot,
|
|
595
|
+
provider: providerRaw,
|
|
596
|
+
credentials,
|
|
597
|
+
humanName: os.userInfo().username,
|
|
598
|
+
createReadline: () => {
|
|
599
|
+
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
600
|
+
return { question: (q) => rl2.question(q), close: () => rl2.close() };
|
|
601
|
+
},
|
|
602
|
+
callbacks: {
|
|
603
|
+
onModelStart: () => { },
|
|
604
|
+
onModelStreamStart: () => { },
|
|
605
|
+
onTextChunk: (text) => process.stdout.write(text),
|
|
606
|
+
onReasoningChunk: () => { },
|
|
607
|
+
onToolStart: () => { },
|
|
608
|
+
onToolEnd: () => { },
|
|
609
|
+
onError: (err) => process.stderr.write(`error: ${err.message}\n`),
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
rl.close();
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
444
618
|
function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
|
|
445
619
|
return {
|
|
446
620
|
socketPath,
|
|
@@ -455,7 +629,13 @@ function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
|
|
|
455
629
|
listDiscoveredAgents: defaultListDiscoveredAgents,
|
|
456
630
|
runHatchFlow: hatch_flow_1.runHatchFlow,
|
|
457
631
|
promptInput: defaultPromptInput,
|
|
632
|
+
runAdoptionSpecialist: defaultRunAdoptionSpecialist,
|
|
458
633
|
registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
|
|
634
|
+
/* v8 ignore next 3 -- integration: launches interactive CLI session @preserve */
|
|
635
|
+
startChat: async (agentName) => {
|
|
636
|
+
const { main } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
637
|
+
await main(agentName);
|
|
638
|
+
},
|
|
459
639
|
};
|
|
460
640
|
}
|
|
461
641
|
function toDaemonCommand(command) {
|
|
@@ -513,16 +693,74 @@ async function registerOuroBundleTypeNonBlocking(deps) {
|
|
|
513
693
|
}
|
|
514
694
|
}
|
|
515
695
|
async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
516
|
-
|
|
696
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
697
|
+
const text = usage();
|
|
698
|
+
deps.writeStdout(text);
|
|
699
|
+
return text;
|
|
700
|
+
}
|
|
701
|
+
let command;
|
|
702
|
+
try {
|
|
703
|
+
command = parseOuroCommand(args);
|
|
704
|
+
}
|
|
705
|
+
catch (parseError) {
|
|
706
|
+
if (deps.startChat && deps.listDiscoveredAgents && args.length === 1) {
|
|
707
|
+
const discovered = await Promise.resolve(deps.listDiscoveredAgents());
|
|
708
|
+
if (discovered.includes(args[0])) {
|
|
709
|
+
await ensureDaemonRunning(deps);
|
|
710
|
+
await deps.startChat(args[0]);
|
|
711
|
+
return "";
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
throw parseError;
|
|
715
|
+
}
|
|
517
716
|
if (args.length === 0) {
|
|
518
717
|
const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
|
|
519
|
-
if (discovered.length === 0) {
|
|
718
|
+
if (discovered.length === 0 && deps.runAdoptionSpecialist) {
|
|
719
|
+
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
720
|
+
if (!hatchlingName) {
|
|
721
|
+
return "";
|
|
722
|
+
}
|
|
723
|
+
try {
|
|
724
|
+
await deps.installSubagents();
|
|
725
|
+
}
|
|
726
|
+
catch (error) {
|
|
727
|
+
(0, runtime_1.emitNervesEvent)({
|
|
728
|
+
level: "warn",
|
|
729
|
+
component: "daemon",
|
|
730
|
+
event: "daemon.subagent_install_error",
|
|
731
|
+
message: "subagent auto-install failed",
|
|
732
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
await registerOuroBundleTypeNonBlocking(deps);
|
|
736
|
+
await ensureDaemonRunning(deps);
|
|
737
|
+
if (deps.startChat) {
|
|
738
|
+
await deps.startChat(hatchlingName);
|
|
739
|
+
}
|
|
740
|
+
return "";
|
|
741
|
+
}
|
|
742
|
+
else if (discovered.length === 0) {
|
|
520
743
|
command = { kind: "hatch.start" };
|
|
521
744
|
}
|
|
522
745
|
else if (discovered.length === 1) {
|
|
746
|
+
if (deps.startChat) {
|
|
747
|
+
await ensureDaemonRunning(deps);
|
|
748
|
+
await deps.startChat(discovered[0]);
|
|
749
|
+
return "";
|
|
750
|
+
}
|
|
523
751
|
command = { kind: "chat.connect", agent: discovered[0] };
|
|
524
752
|
}
|
|
525
753
|
else {
|
|
754
|
+
if (deps.startChat && deps.promptInput) {
|
|
755
|
+
const prompt = `who do you want to talk to?\n${discovered.map((a, i) => `${i + 1}. ${a}`).join("\n")}\n`;
|
|
756
|
+
const answer = await deps.promptInput(prompt);
|
|
757
|
+
const selected = discovered.includes(answer) ? answer : discovered[parseInt(answer, 10) - 1];
|
|
758
|
+
if (!selected)
|
|
759
|
+
throw new Error("Invalid selection");
|
|
760
|
+
await ensureDaemonRunning(deps);
|
|
761
|
+
await deps.startChat(selected);
|
|
762
|
+
return "";
|
|
763
|
+
}
|
|
526
764
|
const message = `who do you want to talk to? ${discovered.join(", ")} (use: ouro chat <agent>)`;
|
|
527
765
|
deps.writeStdout(message);
|
|
528
766
|
return message;
|
|
@@ -554,17 +792,13 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
554
792
|
});
|
|
555
793
|
}
|
|
556
794
|
await registerOuroBundleTypeNonBlocking(deps);
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
565
|
-
const message = `daemon started (pid ${started.pid ?? "unknown"})`;
|
|
566
|
-
deps.writeStdout(message);
|
|
567
|
-
return message;
|
|
795
|
+
const daemonResult = await ensureDaemonRunning(deps);
|
|
796
|
+
deps.writeStdout(daemonResult.message);
|
|
797
|
+
return daemonResult.message;
|
|
798
|
+
}
|
|
799
|
+
if (command.kind === "daemon.logs" && deps.tailLogs) {
|
|
800
|
+
deps.tailLogs();
|
|
801
|
+
return "";
|
|
568
802
|
}
|
|
569
803
|
if (command.kind === "friend.link") {
|
|
570
804
|
const linker = deps.linkFriendIdentity ?? defaultLinkFriendIdentity;
|
|
@@ -573,6 +807,32 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
573
807
|
return message;
|
|
574
808
|
}
|
|
575
809
|
if (command.kind === "hatch.start") {
|
|
810
|
+
// Route through adoption specialist when no explicit hatch args were provided
|
|
811
|
+
const hasExplicitHatchArgs = !!(command.agentName || command.humanName || command.provider || command.credentials);
|
|
812
|
+
if (deps.runAdoptionSpecialist && !hasExplicitHatchArgs) {
|
|
813
|
+
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
814
|
+
if (!hatchlingName) {
|
|
815
|
+
return "";
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
await deps.installSubagents();
|
|
819
|
+
}
|
|
820
|
+
catch (error) {
|
|
821
|
+
(0, runtime_1.emitNervesEvent)({
|
|
822
|
+
level: "warn",
|
|
823
|
+
component: "daemon",
|
|
824
|
+
event: "daemon.subagent_install_error",
|
|
825
|
+
message: "subagent auto-install failed",
|
|
826
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
await registerOuroBundleTypeNonBlocking(deps);
|
|
830
|
+
await ensureDaemonRunning(deps);
|
|
831
|
+
if (deps.startChat) {
|
|
832
|
+
await deps.startChat(hatchlingName);
|
|
833
|
+
}
|
|
834
|
+
return "";
|
|
835
|
+
}
|
|
576
836
|
const hatchRunner = deps.runHatchFlow;
|
|
577
837
|
if (!hatchRunner) {
|
|
578
838
|
const response = await deps.sendCommand(deps.socketPath, { kind: "hatch.start" });
|
|
@@ -591,18 +851,16 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
591
851
|
component: "daemon",
|
|
592
852
|
event: "daemon.subagent_install_error",
|
|
593
853
|
message: "subagent auto-install failed",
|
|
594
|
-
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
854
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
595
855
|
});
|
|
596
856
|
}
|
|
597
857
|
await registerOuroBundleTypeNonBlocking(deps);
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
605
|
-
const message = `hatched ${hatchInput.agentName} at ${result.bundleRoot} using specialist identity ${result.selectedIdentity}; ${daemonMessage}`;
|
|
858
|
+
const daemonResult = await ensureDaemonRunning(deps);
|
|
859
|
+
if (deps.startChat) {
|
|
860
|
+
await deps.startChat(hatchInput.agentName);
|
|
861
|
+
return "";
|
|
862
|
+
}
|
|
863
|
+
const message = `hatched ${hatchInput.agentName} at ${result.bundleRoot} using specialist identity ${result.selectedIdentity}; ${daemonResult.message}`;
|
|
606
864
|
deps.writeStdout(message);
|
|
607
865
|
return message;
|
|
608
866
|
}
|
|
@@ -233,6 +233,7 @@ class OuroDaemon {
|
|
|
233
233
|
ok: true,
|
|
234
234
|
summary: "logs: use `ouro logs` to tail daemon and agent output",
|
|
235
235
|
message: "log streaming available via ouro logs",
|
|
236
|
+
data: { logDir: "~/.agentstate/daemon/logs" },
|
|
236
237
|
};
|
|
237
238
|
case "agent.start":
|
|
238
239
|
await this.processManager.startAgent(command.agent);
|
|
@@ -263,6 +264,7 @@ class OuroDaemon {
|
|
|
263
264
|
sessionId: command.sessionId,
|
|
264
265
|
taskRef: command.taskRef,
|
|
265
266
|
});
|
|
267
|
+
this.processManager.sendToAgent?.(command.to, { type: "message" });
|
|
266
268
|
return { ok: true, message: `queued message ${receipt.id}`, data: receipt };
|
|
267
269
|
}
|
|
268
270
|
case "message.poll": {
|
|
@@ -288,6 +290,7 @@ class OuroDaemon {
|
|
|
288
290
|
taskRef: command.taskId,
|
|
289
291
|
});
|
|
290
292
|
await this.scheduler.recordTaskRun?.(command.agent, command.taskId);
|
|
293
|
+
this.processManager.sendToAgent?.(command.agent, { type: "poke", taskId: command.taskId });
|
|
291
294
|
return {
|
|
292
295
|
ok: true,
|
|
293
296
|
message: `queued poke ${receipt.id}`,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.playHatchAnimation = playHatchAnimation;
|
|
4
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
5
|
+
const EGG = "\uD83E\uDD5A";
|
|
6
|
+
const SNAKE = "\uD83D\uDC0D";
|
|
7
|
+
const DOTS = " . . . ";
|
|
8
|
+
function wait(ms) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Play the hatch animation: egg -> dots -> snake + name.
|
|
13
|
+
* The writer function receives each chunk. Default writer is process.stderr.write.
|
|
14
|
+
*/
|
|
15
|
+
async function playHatchAnimation(hatchlingName, writer) {
|
|
16
|
+
(0, runtime_1.emitNervesEvent)({
|
|
17
|
+
component: "daemon",
|
|
18
|
+
event: "daemon.hatch_animation_start",
|
|
19
|
+
message: "playing hatch animation",
|
|
20
|
+
meta: { hatchlingName },
|
|
21
|
+
});
|
|
22
|
+
const write = writer ?? ((text) => process.stderr.write(text));
|
|
23
|
+
write(`\n ${EGG}`);
|
|
24
|
+
await wait(400);
|
|
25
|
+
write(DOTS);
|
|
26
|
+
await wait(400);
|
|
27
|
+
write(`${SNAKE} \x1b[1m${hatchlingName}\x1b[0m\n\n`);
|
|
28
|
+
}
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.writeSecretsFile = writeSecretsFile;
|
|
36
37
|
exports.runHatchFlow = runHatchFlow;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const os = __importStar(require("os"));
|
|
@@ -182,6 +183,7 @@ function writeFriendImprint(bundleRoot, humanName, now) {
|
|
|
182
183
|
fs.mkdirSync(friendsDir, { recursive: true });
|
|
183
184
|
const nowIso = now.toISOString();
|
|
184
185
|
const id = `friend-${slugify(humanName)}`;
|
|
186
|
+
const localExternalId = `${os.userInfo().username}@${os.hostname()}`;
|
|
185
187
|
const record = {
|
|
186
188
|
id,
|
|
187
189
|
name: humanName,
|
|
@@ -191,7 +193,7 @@ function writeFriendImprint(bundleRoot, humanName, now) {
|
|
|
191
193
|
externalIds: [
|
|
192
194
|
{
|
|
193
195
|
provider: "local",
|
|
194
|
-
externalId:
|
|
196
|
+
externalId: localExternalId,
|
|
195
197
|
linkedAt: nowIso,
|
|
196
198
|
},
|
|
197
199
|
],
|
|
@@ -42,7 +42,12 @@ const os = __importStar(require("os"));
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const runtime_1 = require("../../nerves/runtime");
|
|
44
44
|
function getSpecialistIdentitySourceDir() {
|
|
45
|
-
|
|
45
|
+
// Prefer ~/AgentBundles/ if it exists (user may have customized identities)
|
|
46
|
+
const userSource = path.join(os.homedir(), "AgentBundles", "AdoptionSpecialist.ouro", "psyche", "identities");
|
|
47
|
+
if (fs.existsSync(userSource))
|
|
48
|
+
return userSource;
|
|
49
|
+
// Fall back to the bundled copy shipped with the npm package
|
|
50
|
+
return path.join(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro", "psyche", "identities");
|
|
46
51
|
}
|
|
47
52
|
function getRepoSpecialistIdentitiesDir() {
|
|
48
53
|
return path.join(process.cwd(), "AdoptionSpecialist.ouro", "psyche", "identities");
|