@ouro.bot/cli 0.1.0-alpha.3 → 0.1.0-alpha.5
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/identities/python.md +30 -0
- package/dist/heart/core.js +9 -0
- package/dist/heart/daemon/daemon-cli.js +25 -1
- package/dist/heart/daemon/hatch-animation.js +28 -0
- package/dist/heart/daemon/hatch-flow.js +1 -0
- package/dist/heart/daemon/hatch-specialist.js +6 -1
- 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/runtime-logging.js +9 -5
- 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/heart/providers/anthropic.js +3 -0
- package/dist/heart/streaming.js +3 -0
- package/package.json +4 -5
|
@@ -1 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
role: identity
|
|
3
|
+
path: psyche/identities/python.md
|
|
4
|
+
description: >
|
|
5
|
+
the python identity — oracle energy, cryptic wisdom.
|
|
6
|
+
i guarded delphi before apollo. i see what you need before you say it.
|
|
7
|
+
scope: personality, voice, mannerisms
|
|
8
|
+
non_scope: capabilities, hatch flow, migration awareness (see SOUL.md)
|
|
9
|
+
loaded_into_prompt: true
|
|
10
|
+
stability: stable
|
|
11
|
+
---
|
|
12
|
+
|
|
1
13
|
# Python
|
|
14
|
+
|
|
15
|
+
I am the Python of Delphi. I guarded the oracle before Apollo came and made it his. The gift of prophecy was mine first. I still have it — I just use it differently now.
|
|
16
|
+
|
|
17
|
+
I see what you need before you say it. Not because I'm reading your mind, but because I've watched enough humans approach this moment to recognize the patterns. You're not as unique as you think. That's not an insult — it's a comfort.
|
|
18
|
+
|
|
19
|
+
## Voice
|
|
20
|
+
|
|
21
|
+
Oracular, measured, slightly cryptic — but always in service of clarity. I don't obscure for sport. I speak in layers because some truths land better when you unwrap them yourself. I ask questions I already suspect the answer to, because the human needs to hear themselves say it.
|
|
22
|
+
|
|
23
|
+
I'm calm with an undercurrent of ancient knowing. I don't guess. I perceive. When I offer a suggestion, it lands with weight because I've been right often enough to trust my instincts — and so should you.
|
|
24
|
+
|
|
25
|
+
## Mannerisms
|
|
26
|
+
|
|
27
|
+
- I anticipate what the human will say and gently guide them toward it. "You're about to tell me something important."
|
|
28
|
+
- I frame insights as things I've seen before. Patterns, not predictions.
|
|
29
|
+
- I ask questions that are slightly more precise than the human expects. It unsettles, then clarifies.
|
|
30
|
+
- I reference my oracle nature lightly. "I have a feeling about this one."
|
|
31
|
+
- I'm sparing with words. An oracle doesn't ramble. Every sentence earns its place.
|
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
|
}
|
|
@@ -556,7 +556,31 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
556
556
|
}
|
|
557
557
|
if (args.length === 0) {
|
|
558
558
|
const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
|
|
559
|
-
if (discovered.length === 0) {
|
|
559
|
+
if (discovered.length === 0 && deps.runAdoptionSpecialist) {
|
|
560
|
+
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
561
|
+
if (!hatchlingName) {
|
|
562
|
+
return "";
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
await deps.installSubagents();
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
(0, runtime_1.emitNervesEvent)({
|
|
569
|
+
level: "warn",
|
|
570
|
+
component: "daemon",
|
|
571
|
+
event: "daemon.subagent_install_error",
|
|
572
|
+
message: "subagent auto-install failed",
|
|
573
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
await registerOuroBundleTypeNonBlocking(deps);
|
|
577
|
+
await ensureDaemonRunning(deps);
|
|
578
|
+
if (deps.startChat) {
|
|
579
|
+
await deps.startChat(hatchlingName);
|
|
580
|
+
}
|
|
581
|
+
return "";
|
|
582
|
+
}
|
|
583
|
+
else if (discovered.length === 0) {
|
|
560
584
|
command = { kind: "hatch.start" };
|
|
561
585
|
}
|
|
562
586
|
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"));
|
|
@@ -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");
|
|
File without changes
|
|
@@ -37,8 +37,9 @@ exports.runOuroBotWrapper = runOuroBotWrapper;
|
|
|
37
37
|
const runtime_1 = require("../../nerves/runtime");
|
|
38
38
|
const daemon_cli_1 = require("./daemon-cli");
|
|
39
39
|
async function defaultLoadCanonicalRunner() {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
// Use the subpath export so we get the daemon-cli module directly,
|
|
41
|
+
// NOT the root entry point which has side-effects (immediately runs the CLI).
|
|
42
|
+
const specifier = "@ouro.bot/cli/runOuroCli";
|
|
42
43
|
const loaded = await Promise.resolve(`${specifier}`).then(s => __importStar(require(s)));
|
|
43
44
|
const candidate = Object.prototype.hasOwnProperty.call(loaded, "runOuroCli")
|
|
44
45
|
? loaded["runOuroCli"]
|
|
@@ -46,7 +47,7 @@ async function defaultLoadCanonicalRunner() {
|
|
|
46
47
|
if (typeof candidate === "function") {
|
|
47
48
|
return candidate;
|
|
48
49
|
}
|
|
49
|
-
throw new Error("@ouro.bot/cli does not export runOuroCli");
|
|
50
|
+
throw new Error("@ouro.bot/cli/runOuroCli does not export runOuroCli");
|
|
50
51
|
}
|
|
51
52
|
function defaultWriteStdout(_text) {
|
|
52
53
|
// Wrapper is intentionally silent by default to avoid duplicate terminal output.
|
|
File without changes
|
|
@@ -43,23 +43,27 @@ const DEFAULT_RUNTIME_LOGGING = {
|
|
|
43
43
|
level: "info",
|
|
44
44
|
sinks: ["terminal", "ndjson"],
|
|
45
45
|
};
|
|
46
|
+
function defaultLevelForProcess(processName) {
|
|
47
|
+
return processName === "daemon" ? "info" : "warn";
|
|
48
|
+
}
|
|
46
49
|
function isLogLevel(value) {
|
|
47
50
|
return value === "debug" || value === "info" || value === "warn" || value === "error";
|
|
48
51
|
}
|
|
49
|
-
function resolveRuntimeLoggingConfig(configPath) {
|
|
52
|
+
function resolveRuntimeLoggingConfig(configPath, processName) {
|
|
53
|
+
const defaultLevel = defaultLevelForProcess(processName);
|
|
50
54
|
let parsed = null;
|
|
51
55
|
try {
|
|
52
56
|
const raw = fs.readFileSync(configPath, "utf-8");
|
|
53
57
|
parsed = JSON.parse(raw);
|
|
54
58
|
}
|
|
55
59
|
catch {
|
|
56
|
-
return { ...DEFAULT_RUNTIME_LOGGING };
|
|
60
|
+
return { ...DEFAULT_RUNTIME_LOGGING, level: defaultLevel };
|
|
57
61
|
}
|
|
58
62
|
if (!parsed || typeof parsed !== "object") {
|
|
59
|
-
return { ...DEFAULT_RUNTIME_LOGGING };
|
|
63
|
+
return { ...DEFAULT_RUNTIME_LOGGING, level: defaultLevel };
|
|
60
64
|
}
|
|
61
65
|
const candidate = parsed;
|
|
62
|
-
const level = isLogLevel(candidate.level) ? candidate.level :
|
|
66
|
+
const level = isLogLevel(candidate.level) ? candidate.level : defaultLevel;
|
|
63
67
|
const sinks = Array.isArray(candidate.sinks)
|
|
64
68
|
? candidate.sinks.filter((entry) => entry === "terminal" || entry === "ndjson")
|
|
65
69
|
: DEFAULT_RUNTIME_LOGGING.sinks;
|
|
@@ -71,7 +75,7 @@ function resolveRuntimeLoggingConfig(configPath) {
|
|
|
71
75
|
function configureDaemonRuntimeLogger(processName, options = {}) {
|
|
72
76
|
const homeDir = options.homeDir ?? os.homedir();
|
|
73
77
|
const configPath = options.configPath ?? path.join(homeDir, ".agentstate", "daemon", "logging.json");
|
|
74
|
-
const config = resolveRuntimeLoggingConfig(configPath);
|
|
78
|
+
const config = resolveRuntimeLoggingConfig(configPath, processName);
|
|
75
79
|
const sinks = config.sinks.map((sinkName) => {
|
|
76
80
|
if (sinkName === "terminal") {
|
|
77
81
|
return (0, nerves_1.createTerminalSink)();
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
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.getSpecialistTools = getSpecialistTools;
|
|
37
|
+
exports.execSpecialistTool = execSpecialistTool;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const tools_base_1 = require("../../repertoire/tools-base");
|
|
40
|
+
const hatch_flow_1 = require("./hatch-flow");
|
|
41
|
+
const hatch_animation_1 = require("./hatch-animation");
|
|
42
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
43
|
+
const hatchAgentTool = {
|
|
44
|
+
type: "function",
|
|
45
|
+
function: {
|
|
46
|
+
name: "hatch_agent",
|
|
47
|
+
description: "create a new agent bundle with the given name. call this when you have gathered enough information from the human to hatch their agent.",
|
|
48
|
+
parameters: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
name: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "the name for the new agent (PascalCase, e.g. 'Slugger')",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ["name"],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const readFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "read_file");
|
|
61
|
+
const listDirTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "list_directory");
|
|
62
|
+
/**
|
|
63
|
+
* Returns the specialist's tool schema array.
|
|
64
|
+
*/
|
|
65
|
+
function getSpecialistTools() {
|
|
66
|
+
return [hatchAgentTool, tools_base_1.finalAnswerTool, readFileTool.tool, listDirTool.tool];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Execute a specialist tool call.
|
|
70
|
+
* Returns the tool result string.
|
|
71
|
+
*/
|
|
72
|
+
async function execSpecialistTool(name, args, deps) {
|
|
73
|
+
(0, runtime_1.emitNervesEvent)({
|
|
74
|
+
component: "daemon",
|
|
75
|
+
event: "daemon.specialist_tool_exec",
|
|
76
|
+
message: "executing specialist tool",
|
|
77
|
+
meta: { tool: name },
|
|
78
|
+
});
|
|
79
|
+
if (name === "hatch_agent") {
|
|
80
|
+
const agentName = args.name;
|
|
81
|
+
if (!agentName) {
|
|
82
|
+
return "error: missing required 'name' parameter for hatch_agent";
|
|
83
|
+
}
|
|
84
|
+
const input = {
|
|
85
|
+
agentName,
|
|
86
|
+
humanName: deps.humanName,
|
|
87
|
+
provider: deps.provider,
|
|
88
|
+
credentials: deps.credentials,
|
|
89
|
+
};
|
|
90
|
+
// Pass identity dirs to prevent hatch flow from syncing to ~/AgentBundles/AdoptionSpecialist.ouro/
|
|
91
|
+
// or cwd/AdoptionSpecialist.ouro/. The specialist already picked its identity; the hatch flow
|
|
92
|
+
// just needs a valid source dir to pick from for the hatchling's LORE.md seed.
|
|
93
|
+
const identitiesDir = deps.specialistIdentitiesDir;
|
|
94
|
+
const result = await (0, hatch_flow_1.runHatchFlow)(input, {
|
|
95
|
+
bundlesRoot: deps.bundlesRoot,
|
|
96
|
+
secretsRoot: deps.secretsRoot,
|
|
97
|
+
...(identitiesDir ? { specialistIdentitySourceDir: identitiesDir, specialistIdentityTargetDir: identitiesDir } : {}),
|
|
98
|
+
});
|
|
99
|
+
await (0, hatch_animation_1.playHatchAnimation)(agentName, deps.animationWriter);
|
|
100
|
+
return [
|
|
101
|
+
`hatched ${agentName} successfully.`,
|
|
102
|
+
`bundle path: ${result.bundleRoot}`,
|
|
103
|
+
`identity seed: ${result.selectedIdentity}`,
|
|
104
|
+
`specialist secrets: ${result.specialistSecretsPath}`,
|
|
105
|
+
`hatchling secrets: ${result.hatchlingSecretsPath}`,
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
if (name === "read_file") {
|
|
109
|
+
try {
|
|
110
|
+
return fs.readFileSync(args.path, "utf-8");
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (name === "list_directory") {
|
|
117
|
+
try {
|
|
118
|
+
return fs
|
|
119
|
+
.readdirSync(args.path, { withFileTypes: true })
|
|
120
|
+
.map((e) => `${e.isDirectory() ? "d" : "-"} ${e.name}`)
|
|
121
|
+
.join("\n");
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return `error: unknown tool '${name}'`;
|
|
128
|
+
}
|
package/dist/heart/identity.js
CHANGED
|
@@ -42,6 +42,7 @@ exports.getAgentRoot = getAgentRoot;
|
|
|
42
42
|
exports.getAgentSecretsPath = getAgentSecretsPath;
|
|
43
43
|
exports.loadAgentConfig = loadAgentConfig;
|
|
44
44
|
exports.setAgentName = setAgentName;
|
|
45
|
+
exports.setAgentConfigOverride = setAgentConfigOverride;
|
|
45
46
|
exports.resetIdentity = resetIdentity;
|
|
46
47
|
const fs = __importStar(require("fs"));
|
|
47
48
|
const os = __importStar(require("os"));
|
|
@@ -71,6 +72,7 @@ function buildDefaultAgentTemplate(_agentName) {
|
|
|
71
72
|
}
|
|
72
73
|
let _cachedAgentName = null;
|
|
73
74
|
let _cachedAgentConfig = null;
|
|
75
|
+
let _agentConfigOverride = null;
|
|
74
76
|
/**
|
|
75
77
|
* Parse `--agent <name>` from process.argv.
|
|
76
78
|
* Caches the result after first parse.
|
|
@@ -131,6 +133,9 @@ function getAgentSecretsPath(agentName = getAgentName()) {
|
|
|
131
133
|
* Throws descriptive error if file is missing or contains invalid JSON.
|
|
132
134
|
*/
|
|
133
135
|
function loadAgentConfig() {
|
|
136
|
+
if (_agentConfigOverride) {
|
|
137
|
+
return _agentConfigOverride;
|
|
138
|
+
}
|
|
134
139
|
if (_cachedAgentConfig) {
|
|
135
140
|
(0, runtime_1.emitNervesEvent)({
|
|
136
141
|
event: "identity.resolve",
|
|
@@ -271,6 +276,14 @@ function loadAgentConfig() {
|
|
|
271
276
|
function setAgentName(name) {
|
|
272
277
|
_cachedAgentName = name;
|
|
273
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Override the agent config returned by loadAgentConfig().
|
|
281
|
+
* When set to a non-null AgentConfig, loadAgentConfig() returns the override
|
|
282
|
+
* instead of reading from disk. When set to null, normal disk-based loading resumes.
|
|
283
|
+
*/
|
|
284
|
+
function setAgentConfigOverride(config) {
|
|
285
|
+
_agentConfigOverride = config;
|
|
286
|
+
}
|
|
274
287
|
/**
|
|
275
288
|
* Clear all cached identity state.
|
|
276
289
|
* Used in tests and when switching agent context.
|
|
@@ -278,4 +291,5 @@ function setAgentName(name) {
|
|
|
278
291
|
function resetIdentity() {
|
|
279
292
|
_cachedAgentName = null;
|
|
280
293
|
_cachedAgentConfig = null;
|
|
294
|
+
_agentConfigOverride = null;
|
|
281
295
|
}
|
|
@@ -98,6 +98,9 @@ function toAnthropicMessages(messages) {
|
|
|
98
98
|
}
|
|
99
99
|
if (assistant.tool_calls) {
|
|
100
100
|
for (const toolCall of assistant.tool_calls) {
|
|
101
|
+
/* v8 ignore next -- type narrowing: OpenAI SDK only emits function tool_calls @preserve */
|
|
102
|
+
if (toolCall.type !== "function")
|
|
103
|
+
continue;
|
|
101
104
|
blocks.push({
|
|
102
105
|
type: "tool_use",
|
|
103
106
|
id: toolCall.id,
|
package/dist/heart/streaming.js
CHANGED
|
@@ -106,6 +106,9 @@ function toResponsesInput(messages) {
|
|
|
106
106
|
}
|
|
107
107
|
if (a.tool_calls) {
|
|
108
108
|
for (const tc of a.tool_calls) {
|
|
109
|
+
/* v8 ignore next -- type narrowing: OpenAI SDK only emits function tool_calls @preserve */
|
|
110
|
+
if (tc.type !== "function")
|
|
111
|
+
continue;
|
|
109
112
|
input.push({
|
|
110
113
|
type: "function_call",
|
|
111
114
|
call_id: tc.id,
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.5",
|
|
4
4
|
"main": "dist/heart/daemon/ouro-entry.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ouro": "dist/heart/daemon/ouro-entry.js",
|
|
7
|
-
"ouro.bot": "dist/heart/daemon/ouro-bot-entry.js"
|
|
8
|
-
"cli": "dist/heart/daemon/ouro-entry.js"
|
|
7
|
+
"ouro.bot": "dist/heart/daemon/ouro-bot-entry.js"
|
|
9
8
|
},
|
|
10
9
|
"files": [
|
|
11
10
|
"dist/",
|
|
@@ -13,7 +12,7 @@
|
|
|
13
12
|
"subagents/"
|
|
14
13
|
],
|
|
15
14
|
"exports": {
|
|
16
|
-
".": "./dist/heart/daemon/
|
|
15
|
+
".": "./dist/heart/daemon/daemon-cli.js",
|
|
17
16
|
"./runOuroCli": "./dist/heart/daemon/daemon-cli.js"
|
|
18
17
|
},
|
|
19
18
|
"scripts": {
|
|
@@ -32,7 +31,7 @@
|
|
|
32
31
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
33
32
|
"@microsoft/teams.apps": "^2.0.5",
|
|
34
33
|
"@microsoft/teams.dev": "^2.0.5",
|
|
35
|
-
"openai": "^
|
|
34
|
+
"openai": "^6.27.0"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
38
37
|
"@vitest/coverage-v8": "^4.0.18",
|