@ouro.bot/cli 0.1.0-alpha.4 → 0.1.0-alpha.6
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/dist/heart/config.js +34 -0
- package/dist/heart/core.js +9 -0
- package/dist/heart/daemon/daemon-cli.js +86 -1
- package/dist/heart/daemon/hatch-animation.js +28 -0
- package/dist/heart/daemon/hatch-flow.js +1 -0
- package/dist/heart/daemon/specialist-orchestrator.js +160 -0
- package/dist/heart/daemon/specialist-prompt.js +40 -0
- package/dist/heart/daemon/specialist-session.js +142 -0
- package/dist/heart/daemon/specialist-tools.js +128 -0
- package/dist/heart/identity.js +14 -0
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/prompt.js +3 -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/package.json +2 -1
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,6 +2,7 @@
|
|
|
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;
|
|
7
8
|
exports.createSummarize = createSummarize;
|
|
@@ -73,6 +74,14 @@ function getProviderRuntime() {
|
|
|
73
74
|
}
|
|
74
75
|
return _providerRuntime;
|
|
75
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
|
+
}
|
|
76
85
|
function getModel() {
|
|
77
86
|
return getProviderRuntime().model;
|
|
78
87
|
}
|
|
@@ -49,6 +49,7 @@ const types_1 = require("../../mind/friends/types");
|
|
|
49
49
|
const ouro_uti_1 = require("./ouro-uti");
|
|
50
50
|
const subagent_installer_1 = require("./subagent-installer");
|
|
51
51
|
const hatch_flow_1 = require("./hatch-flow");
|
|
52
|
+
const specialist_orchestrator_1 = require("./specialist-orchestrator");
|
|
52
53
|
async function ensureDaemonRunning(deps) {
|
|
53
54
|
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
54
55
|
if (alive) {
|
|
@@ -457,6 +458,65 @@ async function defaultLinkFriendIdentity(command) {
|
|
|
457
458
|
});
|
|
458
459
|
return `linked ${command.provider}:${command.externalId} to ${command.friendId}`;
|
|
459
460
|
}
|
|
461
|
+
/* v8 ignore next 49 -- integration: interactive terminal specialist session @preserve */
|
|
462
|
+
async function defaultRunAdoptionSpecialist() {
|
|
463
|
+
const readline = await Promise.resolve().then(() => __importStar(require("readline/promises")));
|
|
464
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
465
|
+
const prompt = async (q) => {
|
|
466
|
+
const answer = await rl.question(q);
|
|
467
|
+
return answer.trim();
|
|
468
|
+
};
|
|
469
|
+
try {
|
|
470
|
+
const humanName = await prompt("Your name: ");
|
|
471
|
+
const providerRaw = await prompt("Provider (azure|anthropic|minimax|openai-codex): ");
|
|
472
|
+
if (!humanName || !isAgentProvider(providerRaw)) {
|
|
473
|
+
process.stdout.write("Invalid input. Run `ouro hatch` to try again.\n");
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
const credentials = {};
|
|
477
|
+
if (providerRaw === "anthropic")
|
|
478
|
+
credentials.setupToken = await prompt("Anthropic API key: ");
|
|
479
|
+
if (providerRaw === "openai-codex")
|
|
480
|
+
credentials.oauthAccessToken = await prompt("OpenAI Codex OAuth token: ");
|
|
481
|
+
if (providerRaw === "minimax")
|
|
482
|
+
credentials.apiKey = await prompt("MiniMax API key: ");
|
|
483
|
+
if (providerRaw === "azure") {
|
|
484
|
+
credentials.apiKey = await prompt("Azure API key: ");
|
|
485
|
+
credentials.endpoint = await prompt("Azure endpoint: ");
|
|
486
|
+
credentials.deployment = await prompt("Azure deployment: ");
|
|
487
|
+
}
|
|
488
|
+
rl.close();
|
|
489
|
+
// Locate the bundled AdoptionSpecialist.ouro shipped with the npm package
|
|
490
|
+
const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
|
|
491
|
+
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
492
|
+
const secretsRoot = path.join(os.homedir(), ".agentsecrets");
|
|
493
|
+
return await (0, specialist_orchestrator_1.runAdoptionSpecialist)({
|
|
494
|
+
bundleSourceDir,
|
|
495
|
+
bundlesRoot,
|
|
496
|
+
secretsRoot,
|
|
497
|
+
provider: providerRaw,
|
|
498
|
+
credentials,
|
|
499
|
+
humanName,
|
|
500
|
+
createReadline: () => {
|
|
501
|
+
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
502
|
+
return { question: (q) => rl2.question(q), close: () => rl2.close() };
|
|
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`),
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
catch {
|
|
516
|
+
rl.close();
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
460
520
|
function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
|
|
461
521
|
return {
|
|
462
522
|
socketPath,
|
|
@@ -471,6 +531,7 @@ function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
|
|
|
471
531
|
listDiscoveredAgents: defaultListDiscoveredAgents,
|
|
472
532
|
runHatchFlow: hatch_flow_1.runHatchFlow,
|
|
473
533
|
promptInput: defaultPromptInput,
|
|
534
|
+
runAdoptionSpecialist: defaultRunAdoptionSpecialist,
|
|
474
535
|
registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
|
|
475
536
|
/* v8 ignore next 3 -- integration: launches interactive CLI session @preserve */
|
|
476
537
|
startChat: async (agentName) => {
|
|
@@ -556,7 +617,31 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
556
617
|
}
|
|
557
618
|
if (args.length === 0) {
|
|
558
619
|
const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
|
|
559
|
-
if (discovered.length === 0) {
|
|
620
|
+
if (discovered.length === 0 && deps.runAdoptionSpecialist) {
|
|
621
|
+
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
622
|
+
if (!hatchlingName) {
|
|
623
|
+
return "";
|
|
624
|
+
}
|
|
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
|
+
await ensureDaemonRunning(deps);
|
|
639
|
+
if (deps.startChat) {
|
|
640
|
+
await deps.startChat(hatchlingName);
|
|
641
|
+
}
|
|
642
|
+
return "";
|
|
643
|
+
}
|
|
644
|
+
else if (discovered.length === 0) {
|
|
560
645
|
command = { kind: "hatch.start" };
|
|
561
646
|
}
|
|
562
647
|
else if (discovered.length === 1) {
|
|
@@ -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"));
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runAdoptionSpecialist = runAdoptionSpecialist;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
40
|
+
const identity_1 = require("../identity");
|
|
41
|
+
const config_1 = require("../config");
|
|
42
|
+
const core_1 = require("../core");
|
|
43
|
+
const hatch_flow_1 = require("./hatch-flow");
|
|
44
|
+
const specialist_prompt_1 = require("./specialist-prompt");
|
|
45
|
+
const specialist_tools_1 = require("./specialist-tools");
|
|
46
|
+
const specialist_session_1 = require("./specialist-session");
|
|
47
|
+
function listExistingBundles(bundlesRoot) {
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = fs.readdirSync(bundlesRoot, { withFileTypes: true });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const discovered = [];
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
|
|
58
|
+
continue;
|
|
59
|
+
const agentName = entry.name.slice(0, -5);
|
|
60
|
+
discovered.push(agentName);
|
|
61
|
+
}
|
|
62
|
+
return discovered.sort((a, b) => a.localeCompare(b));
|
|
63
|
+
}
|
|
64
|
+
function pickRandomIdentity(identitiesDir, random) {
|
|
65
|
+
const files = fs.readdirSync(identitiesDir).filter((f) => f.endsWith(".md"));
|
|
66
|
+
if (files.length === 0) {
|
|
67
|
+
return { fileName: "default", content: "I am the adoption specialist." };
|
|
68
|
+
}
|
|
69
|
+
const idx = Math.floor(random() * files.length);
|
|
70
|
+
const fileName = files[idx];
|
|
71
|
+
const content = fs.readFileSync(path.join(identitiesDir, fileName), "utf-8");
|
|
72
|
+
return { fileName, content };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Run the full adoption specialist flow:
|
|
76
|
+
* 1. Pick a random identity from the bundled AdoptionSpecialist.ouro
|
|
77
|
+
* 2. Read SOUL.md
|
|
78
|
+
* 3. List existing bundles
|
|
79
|
+
* 4. Build system prompt
|
|
80
|
+
* 5. Set up provider (setAgentName, setAgentConfigOverride, writeSecretsFile, reset caches)
|
|
81
|
+
* 6. Run the specialist session
|
|
82
|
+
* 7. Clean up identity/config overrides
|
|
83
|
+
* 8. Return hatchling name
|
|
84
|
+
*/
|
|
85
|
+
async function runAdoptionSpecialist(deps) {
|
|
86
|
+
const { bundleSourceDir, bundlesRoot, secretsRoot, provider, credentials, humanName, callbacks, signal } = deps;
|
|
87
|
+
const random = deps.random ?? Math.random;
|
|
88
|
+
(0, runtime_1.emitNervesEvent)({
|
|
89
|
+
component: "daemon",
|
|
90
|
+
event: "daemon.specialist_orchestrator_start",
|
|
91
|
+
message: "starting adoption specialist orchestrator",
|
|
92
|
+
meta: { provider, bundleSourceDir },
|
|
93
|
+
});
|
|
94
|
+
// 1. Read SOUL.md
|
|
95
|
+
const soulPath = path.join(bundleSourceDir, "psyche", "SOUL.md");
|
|
96
|
+
let soulText = "";
|
|
97
|
+
try {
|
|
98
|
+
soulText = fs.readFileSync(soulPath, "utf-8");
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// No SOUL.md -- proceed without it
|
|
102
|
+
}
|
|
103
|
+
// 2. Pick random identity
|
|
104
|
+
const identitiesDir = path.join(bundleSourceDir, "psyche", "identities");
|
|
105
|
+
const identity = pickRandomIdentity(identitiesDir, random);
|
|
106
|
+
(0, runtime_1.emitNervesEvent)({
|
|
107
|
+
component: "daemon",
|
|
108
|
+
event: "daemon.specialist_identity_picked",
|
|
109
|
+
message: "picked specialist identity",
|
|
110
|
+
meta: { identity: identity.fileName },
|
|
111
|
+
});
|
|
112
|
+
// 3. List existing bundles
|
|
113
|
+
const existingBundles = listExistingBundles(bundlesRoot);
|
|
114
|
+
// 4. Build system prompt
|
|
115
|
+
const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles);
|
|
116
|
+
// 5. Set up provider
|
|
117
|
+
(0, identity_1.setAgentName)("AdoptionSpecialist");
|
|
118
|
+
(0, identity_1.setAgentConfigOverride)({
|
|
119
|
+
version: 1,
|
|
120
|
+
enabled: true,
|
|
121
|
+
provider,
|
|
122
|
+
phrases: { thinking: ["thinking"], tool: ["checking"], followup: ["processing"] },
|
|
123
|
+
});
|
|
124
|
+
(0, hatch_flow_1.writeSecretsFile)("AdoptionSpecialist", provider, credentials, secretsRoot);
|
|
125
|
+
(0, config_1.resetConfigCache)();
|
|
126
|
+
(0, core_1.resetProviderRuntime)();
|
|
127
|
+
try {
|
|
128
|
+
// Create provider runtime
|
|
129
|
+
const providerRuntime = (0, core_1.createProviderRegistry)().resolve();
|
|
130
|
+
if (!providerRuntime) {
|
|
131
|
+
throw new Error("Failed to create provider runtime for adoption specialist");
|
|
132
|
+
}
|
|
133
|
+
// 6. Run session
|
|
134
|
+
const tools = (0, specialist_tools_1.getSpecialistTools)();
|
|
135
|
+
const readline = deps.createReadline();
|
|
136
|
+
const result = await (0, specialist_session_1.runSpecialistSession)({
|
|
137
|
+
providerRuntime,
|
|
138
|
+
systemPrompt,
|
|
139
|
+
tools,
|
|
140
|
+
execTool: (name, args) => (0, specialist_tools_1.execSpecialistTool)(name, args, {
|
|
141
|
+
humanName,
|
|
142
|
+
provider,
|
|
143
|
+
credentials,
|
|
144
|
+
bundlesRoot,
|
|
145
|
+
secretsRoot,
|
|
146
|
+
specialistIdentitiesDir: identitiesDir,
|
|
147
|
+
}),
|
|
148
|
+
readline,
|
|
149
|
+
callbacks,
|
|
150
|
+
signal,
|
|
151
|
+
});
|
|
152
|
+
return result.hatchedAgentName;
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
// 7. Cleanup: restore identity/config state
|
|
156
|
+
(0, identity_1.setAgentConfigOverride)(null);
|
|
157
|
+
(0, config_1.resetConfigCache)();
|
|
158
|
+
(0, core_1.resetProviderRuntime)();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildSpecialistSystemPrompt = buildSpecialistSystemPrompt;
|
|
4
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
5
|
+
/**
|
|
6
|
+
* Build the adoption specialist's system prompt from its components.
|
|
7
|
+
* The prompt is written in first person (the specialist's own voice).
|
|
8
|
+
*/
|
|
9
|
+
function buildSpecialistSystemPrompt(soulText, identityText, existingBundles) {
|
|
10
|
+
(0, runtime_1.emitNervesEvent)({
|
|
11
|
+
component: "daemon",
|
|
12
|
+
event: "daemon.specialist_prompt_build",
|
|
13
|
+
message: "building specialist system prompt",
|
|
14
|
+
meta: { bundleCount: existingBundles.length },
|
|
15
|
+
});
|
|
16
|
+
const sections = [];
|
|
17
|
+
if (soulText) {
|
|
18
|
+
sections.push(soulText);
|
|
19
|
+
}
|
|
20
|
+
if (identityText) {
|
|
21
|
+
sections.push(identityText);
|
|
22
|
+
}
|
|
23
|
+
if (existingBundles.length > 0) {
|
|
24
|
+
sections.push(`## Existing agents\nThe human already has these agents: ${existingBundles.join(", ")}.`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
sections.push("## Existing agents\nThe human has no agents yet. This will be their first hatchling.");
|
|
28
|
+
}
|
|
29
|
+
sections.push([
|
|
30
|
+
"## Tools",
|
|
31
|
+
"I have these tools available:",
|
|
32
|
+
"- `hatch_agent`: Create a new agent bundle. I call this with a `name` parameter once I have enough information from the human.",
|
|
33
|
+
"- `final_answer`: End the conversation with a final message to the human. I call this when the adoption process is complete.",
|
|
34
|
+
"- `read_file`: Read a file from disk. Useful for reviewing existing agent bundles or migration sources.",
|
|
35
|
+
"- `list_directory`: List directory contents. Useful for exploring existing agent bundles.",
|
|
36
|
+
"",
|
|
37
|
+
"I must call `final_answer` when I am done to end the session cleanly.",
|
|
38
|
+
].join("\n"));
|
|
39
|
+
return sections.join("\n\n");
|
|
40
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSpecialistSession = runSpecialistSession;
|
|
4
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
5
|
+
/**
|
|
6
|
+
* Run the specialist conversation session loop.
|
|
7
|
+
*
|
|
8
|
+
* The loop:
|
|
9
|
+
* 1. Initialize messages with system prompt
|
|
10
|
+
* 2. Prompt user -> add to messages -> call streamTurn -> process result
|
|
11
|
+
* 3. If result has no tool calls: push assistant message, re-prompt
|
|
12
|
+
* 4. If result has final_answer sole call: extract answer, emit via callbacks, done
|
|
13
|
+
* 5. If result has other tool calls: execute each, push tool results, continue loop
|
|
14
|
+
* 6. On abort signal: clean exit
|
|
15
|
+
* 7. Return { hatchedAgentName } -- name from hatch_agent if called
|
|
16
|
+
*/
|
|
17
|
+
async function runSpecialistSession(deps) {
|
|
18
|
+
const { providerRuntime, systemPrompt, tools, execTool, readline, callbacks, signal } = deps;
|
|
19
|
+
(0, runtime_1.emitNervesEvent)({
|
|
20
|
+
component: "daemon",
|
|
21
|
+
event: "daemon.specialist_session_start",
|
|
22
|
+
message: "starting specialist session loop",
|
|
23
|
+
meta: {},
|
|
24
|
+
});
|
|
25
|
+
const messages = [
|
|
26
|
+
{ role: "system", content: systemPrompt },
|
|
27
|
+
];
|
|
28
|
+
let hatchedAgentName = null;
|
|
29
|
+
let done = false;
|
|
30
|
+
try {
|
|
31
|
+
while (!done) {
|
|
32
|
+
if (signal?.aborted)
|
|
33
|
+
break;
|
|
34
|
+
// Get user input
|
|
35
|
+
const userInput = await readline.question("> ");
|
|
36
|
+
if (!userInput.trim())
|
|
37
|
+
continue;
|
|
38
|
+
messages.push({ role: "user", content: userInput });
|
|
39
|
+
providerRuntime.resetTurnState(messages);
|
|
40
|
+
// Inner loop: process tool calls until we get a final_answer or plain text
|
|
41
|
+
let turnDone = false;
|
|
42
|
+
while (!turnDone) {
|
|
43
|
+
if (signal?.aborted) {
|
|
44
|
+
done = true;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
callbacks.onModelStart();
|
|
48
|
+
const result = await providerRuntime.streamTurn({
|
|
49
|
+
messages,
|
|
50
|
+
activeTools: tools,
|
|
51
|
+
callbacks,
|
|
52
|
+
signal,
|
|
53
|
+
});
|
|
54
|
+
// Build assistant message
|
|
55
|
+
const assistantMsg = {
|
|
56
|
+
role: "assistant",
|
|
57
|
+
};
|
|
58
|
+
if (result.content)
|
|
59
|
+
assistantMsg.content = result.content;
|
|
60
|
+
if (result.toolCalls.length) {
|
|
61
|
+
assistantMsg.tool_calls = result.toolCalls.map((tc) => ({
|
|
62
|
+
id: tc.id,
|
|
63
|
+
type: "function",
|
|
64
|
+
function: { name: tc.name, arguments: tc.arguments },
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
if (!result.toolCalls.length) {
|
|
68
|
+
// Plain text response -- push and re-prompt
|
|
69
|
+
messages.push(assistantMsg);
|
|
70
|
+
turnDone = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Check for final_answer
|
|
74
|
+
const isSoleFinalAnswer = result.toolCalls.length === 1 && result.toolCalls[0].name === "final_answer";
|
|
75
|
+
if (isSoleFinalAnswer) {
|
|
76
|
+
let answer;
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(result.toolCalls[0].arguments);
|
|
79
|
+
if (typeof parsed === "string") {
|
|
80
|
+
answer = parsed;
|
|
81
|
+
}
|
|
82
|
+
else if (parsed.answer != null) {
|
|
83
|
+
answer = parsed.answer;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// malformed
|
|
88
|
+
}
|
|
89
|
+
if (answer != null) {
|
|
90
|
+
callbacks.onTextChunk(answer);
|
|
91
|
+
messages.push(assistantMsg);
|
|
92
|
+
done = true;
|
|
93
|
+
turnDone = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Malformed final_answer -- ask model to retry
|
|
97
|
+
messages.push(assistantMsg);
|
|
98
|
+
messages.push({
|
|
99
|
+
role: "tool",
|
|
100
|
+
tool_call_id: result.toolCalls[0].id,
|
|
101
|
+
content: "your final_answer was incomplete or malformed. call final_answer again with your complete response.",
|
|
102
|
+
});
|
|
103
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, "retry");
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// Execute tool calls
|
|
107
|
+
messages.push(assistantMsg);
|
|
108
|
+
for (const tc of result.toolCalls) {
|
|
109
|
+
if (signal?.aborted)
|
|
110
|
+
break;
|
|
111
|
+
let args = {};
|
|
112
|
+
try {
|
|
113
|
+
args = JSON.parse(tc.arguments);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// ignore parse error
|
|
117
|
+
}
|
|
118
|
+
callbacks.onToolStart(tc.name, args);
|
|
119
|
+
let toolResult;
|
|
120
|
+
try {
|
|
121
|
+
toolResult = await execTool(tc.name, args);
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
toolResult = `error: ${e}`;
|
|
125
|
+
}
|
|
126
|
+
callbacks.onToolEnd(tc.name, tc.name, true);
|
|
127
|
+
// Track hatchling name
|
|
128
|
+
if (tc.name === "hatch_agent" && args.name) {
|
|
129
|
+
hatchedAgentName = args.name;
|
|
130
|
+
}
|
|
131
|
+
messages.push({ role: "tool", tool_call_id: tc.id, content: toolResult });
|
|
132
|
+
providerRuntime.appendToolOutput(tc.id, toolResult);
|
|
133
|
+
}
|
|
134
|
+
// After processing tool calls, continue inner loop for tool result processing
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
readline.close();
|
|
140
|
+
}
|
|
141
|
+
return { hatchedAgentName };
|
|
142
|
+
}
|