@ouro.bot/cli 0.1.0-alpha.12 → 0.1.0-alpha.14
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/assets/ouroboros.png +0 -0
- package/dist/heart/config.js +2 -2
- package/dist/heart/core.js +3 -2
- package/dist/heart/daemon/daemon-cli.js +65 -22
- package/dist/heart/daemon/hatch-flow.js +0 -10
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +21 -5
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/specialist-orchestrator.js +37 -94
- package/dist/heart/daemon/specialist-prompt.js +43 -8
- package/dist/heart/daemon/specialist-tools.js +161 -59
- package/dist/mind/bundle-manifest.js +58 -0
- package/dist/mind/prompt.js +3 -0
- package/dist/senses/cli.js +162 -98
- package/package.json +7 -2
- package/dist/heart/daemon/specialist-session.js +0 -177
- package/dist/inner-worker-entry.js +0 -4
|
Binary file
|
package/dist/heart/config.js
CHANGED
|
@@ -35,7 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.loadConfig = loadConfig;
|
|
37
37
|
exports.resetConfigCache = resetConfigCache;
|
|
38
|
-
exports.
|
|
38
|
+
exports.patchRuntimeConfig = patchRuntimeConfig;
|
|
39
39
|
exports.getAzureConfig = getAzureConfig;
|
|
40
40
|
exports.getMinimaxConfig = getMinimaxConfig;
|
|
41
41
|
exports.getAnthropicConfig = getAnthropicConfig;
|
|
@@ -233,7 +233,7 @@ function resetConfigCache() {
|
|
|
233
233
|
_cachedConfig = null;
|
|
234
234
|
_testContextOverride = null;
|
|
235
235
|
}
|
|
236
|
-
function
|
|
236
|
+
function patchRuntimeConfig(partial) {
|
|
237
237
|
loadConfig(); // ensure _cachedConfig exists
|
|
238
238
|
const contextPatch = partial.context;
|
|
239
239
|
if (contextPatch) {
|
package/dist/heart/core.js
CHANGED
|
@@ -278,7 +278,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
278
278
|
}
|
|
279
279
|
catch { /* unsupported */ }
|
|
280
280
|
const toolPreferences = currentContext?.friend?.toolPreferences;
|
|
281
|
-
const baseTools = (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined);
|
|
281
|
+
const baseTools = options?.tools ?? (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined);
|
|
282
282
|
// Rebase provider-owned turn state from canonical messages at user-turn start.
|
|
283
283
|
// This prevents stale provider caches from replaying prior-turn context.
|
|
284
284
|
providerRuntime.resetTurnState(messages);
|
|
@@ -444,7 +444,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
444
444
|
let toolResult;
|
|
445
445
|
let success;
|
|
446
446
|
try {
|
|
447
|
-
|
|
447
|
+
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
448
|
+
toolResult = await execToolFn(tc.name, args, options?.toolContext);
|
|
448
449
|
success = true;
|
|
449
450
|
}
|
|
450
451
|
catch (e) {
|
|
@@ -52,6 +52,8 @@ const ouro_path_installer_1 = require("./ouro-path-installer");
|
|
|
52
52
|
const subagent_installer_1 = require("./subagent-installer");
|
|
53
53
|
const hatch_flow_1 = require("./hatch-flow");
|
|
54
54
|
const specialist_orchestrator_1 = require("./specialist-orchestrator");
|
|
55
|
+
const specialist_prompt_1 = require("./specialist-prompt");
|
|
56
|
+
const specialist_tools_1 = require("./specialist-tools");
|
|
55
57
|
function stringField(value) {
|
|
56
58
|
return typeof value === "string" ? value : null;
|
|
57
59
|
}
|
|
@@ -647,11 +649,13 @@ function discoverExistingCredentials(secretsRoot) {
|
|
|
647
649
|
return true;
|
|
648
650
|
});
|
|
649
651
|
}
|
|
650
|
-
/* v8 ignore
|
|
652
|
+
/* v8 ignore start -- integration: interactive terminal specialist session @preserve */
|
|
651
653
|
async function defaultRunAdoptionSpecialist() {
|
|
652
|
-
const
|
|
654
|
+
const { runCliSession } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
655
|
+
const { patchRuntimeConfig } = await Promise.resolve().then(() => __importStar(require("../config")));
|
|
656
|
+
const { setAgentName } = await Promise.resolve().then(() => __importStar(require("../identity")));
|
|
653
657
|
const readlinePromises = await Promise.resolve().then(() => __importStar(require("readline/promises")));
|
|
654
|
-
const
|
|
658
|
+
const crypto = await Promise.resolve().then(() => __importStar(require("crypto")));
|
|
655
659
|
// Phase 1: cold CLI — collect provider/credentials with a simple readline
|
|
656
660
|
const coldRl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
657
661
|
const coldPrompt = async (q) => {
|
|
@@ -660,11 +664,12 @@ async function defaultRunAdoptionSpecialist() {
|
|
|
660
664
|
};
|
|
661
665
|
let providerRaw;
|
|
662
666
|
let credentials = {};
|
|
667
|
+
const tempDir = path.join(os.tmpdir(), `ouro-hatch-${crypto.randomUUID()}`);
|
|
663
668
|
try {
|
|
664
669
|
const secretsRoot = path.join(os.homedir(), ".agentsecrets");
|
|
665
670
|
const discovered = discoverExistingCredentials(secretsRoot);
|
|
666
671
|
if (discovered.length > 0) {
|
|
667
|
-
process.stdout.write("\n
|
|
672
|
+
process.stdout.write("\n\ud83d\udc0d welcome to ouro! let's hatch your first agent.\n");
|
|
668
673
|
process.stdout.write("i found existing API credentials:\n\n");
|
|
669
674
|
const unique = [...new Map(discovered.map((d) => [`${d.provider}`, d])).values()];
|
|
670
675
|
for (let i = 0; i < unique.length; i++) {
|
|
@@ -699,7 +704,7 @@ async function defaultRunAdoptionSpecialist() {
|
|
|
699
704
|
}
|
|
700
705
|
}
|
|
701
706
|
else {
|
|
702
|
-
process.stdout.write("\n
|
|
707
|
+
process.stdout.write("\n\ud83d\udc0d welcome to ouro! let's hatch your first agent.\n");
|
|
703
708
|
process.stdout.write("i need an API key to power our conversation.\n\n");
|
|
704
709
|
const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
|
|
705
710
|
if (!isAgentProvider(pRaw)) {
|
|
@@ -722,34 +727,72 @@ async function defaultRunAdoptionSpecialist() {
|
|
|
722
727
|
}
|
|
723
728
|
coldRl.close();
|
|
724
729
|
process.stdout.write("\n");
|
|
725
|
-
// Phase 2:
|
|
730
|
+
// Phase 2: configure runtime for adoption specialist
|
|
726
731
|
const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
|
|
727
732
|
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
+
const secretsRoot2 = path.join(os.homedir(), ".agentsecrets");
|
|
734
|
+
// Configure provider credentials in runtime config
|
|
735
|
+
patchRuntimeConfig({
|
|
736
|
+
providers: {
|
|
737
|
+
[providerRaw]: credentials,
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
setAgentName("AdoptionSpecialist");
|
|
741
|
+
// Build specialist system prompt
|
|
742
|
+
const soulText = (0, specialist_orchestrator_1.loadSoulText)(bundleSourceDir);
|
|
743
|
+
const identitiesDir = path.join(bundleSourceDir, "psyche", "identities");
|
|
744
|
+
const identity = (0, specialist_orchestrator_1.pickRandomIdentity)(identitiesDir);
|
|
745
|
+
const existingBundles = (0, specialist_orchestrator_1.listExistingBundles)(bundlesRoot);
|
|
746
|
+
const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles, {
|
|
747
|
+
tempDir,
|
|
733
748
|
provider: providerRaw,
|
|
749
|
+
});
|
|
750
|
+
// Build specialist tools
|
|
751
|
+
const specialistTools = (0, specialist_tools_1.getSpecialistTools)();
|
|
752
|
+
const specialistExecTool = (0, specialist_tools_1.createSpecialistExecTool)({
|
|
753
|
+
tempDir,
|
|
734
754
|
credentials,
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
return {
|
|
740
|
-
question: (q) => new Promise((resolve) => rl2.question(q, resolve)),
|
|
741
|
-
close: () => rl2.close(),
|
|
742
|
-
inputController: ctrl,
|
|
743
|
-
};
|
|
744
|
-
},
|
|
745
|
-
callbacks: cliCallbacks,
|
|
755
|
+
provider: providerRaw,
|
|
756
|
+
bundlesRoot,
|
|
757
|
+
secretsRoot: secretsRoot2,
|
|
758
|
+
animationWriter: (text) => process.stdout.write(text),
|
|
746
759
|
});
|
|
760
|
+
// Run the adoption specialist session via runCliSession
|
|
761
|
+
const result = await runCliSession({
|
|
762
|
+
agentName: "AdoptionSpecialist",
|
|
763
|
+
tools: specialistTools,
|
|
764
|
+
execTool: specialistExecTool,
|
|
765
|
+
exitOnToolCall: "complete_adoption",
|
|
766
|
+
messages: [
|
|
767
|
+
{ role: "system", content: systemPrompt },
|
|
768
|
+
{ role: "user", content: "hi" },
|
|
769
|
+
],
|
|
770
|
+
});
|
|
771
|
+
if (result.exitReason === "tool_exit" && result.toolResult) {
|
|
772
|
+
const parsed = typeof result.toolResult === "string" ? JSON.parse(result.toolResult) : result.toolResult;
|
|
773
|
+
if (parsed.success && parsed.agentName) {
|
|
774
|
+
return parsed.agentName;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return null;
|
|
747
778
|
}
|
|
748
779
|
catch {
|
|
749
780
|
coldRl.close();
|
|
750
781
|
return null;
|
|
751
782
|
}
|
|
783
|
+
finally {
|
|
784
|
+
// Clean up temp dir if it still exists
|
|
785
|
+
try {
|
|
786
|
+
if (fs.existsSync(tempDir)) {
|
|
787
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
catch {
|
|
791
|
+
// Best effort cleanup
|
|
792
|
+
}
|
|
793
|
+
}
|
|
752
794
|
}
|
|
795
|
+
/* v8 ignore stop */
|
|
753
796
|
function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
|
|
754
797
|
return {
|
|
755
798
|
socketPath,
|
|
@@ -207,15 +207,6 @@ function writeFriendImprint(bundleRoot, humanName, now) {
|
|
|
207
207
|
};
|
|
208
208
|
fs.writeFileSync(path.join(friendsDir, `${id}.json`), `${JSON.stringify(record, null, 2)}\n`, "utf-8");
|
|
209
209
|
}
|
|
210
|
-
function writeHatchlingPsyche(bundleRoot, input, identityFileName) {
|
|
211
|
-
const psycheDir = path.join(bundleRoot, "psyche");
|
|
212
|
-
fs.mkdirSync(psycheDir, { recursive: true });
|
|
213
|
-
fs.writeFileSync(path.join(psycheDir, "SOUL.md"), "# SOUL\n\nI am a practical, collaborative agent. I keep commitments and communicate clearly.\n", "utf-8");
|
|
214
|
-
fs.writeFileSync(path.join(psycheDir, "IDENTITY.md"), `# IDENTITY\n\nI'm ${input.agentName}, newly hatched and ready to help ${input.humanName}.`, "utf-8");
|
|
215
|
-
fs.writeFileSync(path.join(psycheDir, "LORE.md"), `# LORE\n\nHatched with specialist identity seed: ${identityFileName}.`, "utf-8");
|
|
216
|
-
fs.writeFileSync(path.join(psycheDir, "TACIT.md"), "# TACIT\n\n- Save what I learn.\n- Keep tasks current.\n", "utf-8");
|
|
217
|
-
fs.writeFileSync(path.join(psycheDir, "ASPIRATIONS.md"), "# ASPIRATIONS\n\n- Become a reliable partner for my primary friend.\n", "utf-8");
|
|
218
|
-
}
|
|
219
210
|
function writeMemoryScaffold(bundleRoot) {
|
|
220
211
|
const memoryRoot = path.join(bundleRoot, "psyche", "memory");
|
|
221
212
|
fs.mkdirSync(path.join(memoryRoot, "daily"), { recursive: true });
|
|
@@ -267,7 +258,6 @@ async function runHatchFlow(input, deps = {}) {
|
|
|
267
258
|
writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
|
|
268
259
|
writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
|
|
269
260
|
writeHatchlingAgentConfig(bundleRoot, input);
|
|
270
|
-
writeHatchlingPsyche(bundleRoot, input, selected.fileName);
|
|
271
261
|
writeMemoryScaffold(bundleRoot);
|
|
272
262
|
writeFriendImprint(bundleRoot, input.humanName, now);
|
|
273
263
|
writeHeartbeatTask(bundleRoot, now);
|
|
File without changes
|
|
File without changes
|
|
@@ -39,7 +39,7 @@ const os = __importStar(require("os"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const runtime_1 = require("../../nerves/runtime");
|
|
41
41
|
const WRAPPER_SCRIPT = `#!/bin/sh
|
|
42
|
-
exec npx --yes
|
|
42
|
+
exec npx --yes ouro.bot "$@"
|
|
43
43
|
`;
|
|
44
44
|
function detectShellProfile(homeDir, shell) {
|
|
45
45
|
if (!shell)
|
|
@@ -96,15 +96,31 @@ function installOuroCommand(deps = {}) {
|
|
|
96
96
|
message: "installing ouro command to PATH",
|
|
97
97
|
meta: { scriptPath, binDir },
|
|
98
98
|
});
|
|
99
|
-
// If ouro already exists
|
|
99
|
+
// If ouro already exists, check content and repair if stale
|
|
100
100
|
if (existsSync(scriptPath)) {
|
|
101
|
+
let existingContent = "";
|
|
102
|
+
try {
|
|
103
|
+
existingContent = readFileSync(scriptPath, "utf-8");
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Can't read — treat as stale, will overwrite below
|
|
107
|
+
}
|
|
108
|
+
if (existingContent === WRAPPER_SCRIPT) {
|
|
109
|
+
(0, runtime_1.emitNervesEvent)({
|
|
110
|
+
component: "daemon",
|
|
111
|
+
event: "daemon.ouro_path_install_skip",
|
|
112
|
+
message: "ouro command already installed",
|
|
113
|
+
meta: { scriptPath },
|
|
114
|
+
});
|
|
115
|
+
return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed" };
|
|
116
|
+
}
|
|
117
|
+
// Content is stale — repair by overwriting
|
|
101
118
|
(0, runtime_1.emitNervesEvent)({
|
|
102
119
|
component: "daemon",
|
|
103
|
-
event: "daemon.
|
|
104
|
-
message: "ouro
|
|
120
|
+
event: "daemon.ouro_path_install_repair",
|
|
121
|
+
message: "repairing stale ouro wrapper script",
|
|
105
122
|
meta: { scriptPath },
|
|
106
123
|
});
|
|
107
|
-
return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed" };
|
|
108
124
|
}
|
|
109
125
|
try {
|
|
110
126
|
mkdirSync(binDir, { recursive: true });
|
|
@@ -42,7 +42,13 @@ const identity_1 = require("../identity");
|
|
|
42
42
|
const runtime_1 = require("../../nerves/runtime");
|
|
43
43
|
const LSREGISTER_PATH = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
|
|
44
44
|
const ICON_SIZES = [16, 32, 128, 256, 512];
|
|
45
|
-
function resolveIconSourcePath(repoRoot) {
|
|
45
|
+
function resolveIconSourcePath(repoRoot, existsSync) {
|
|
46
|
+
// Prefer bundled asset (shipped with npm package)
|
|
47
|
+
const bundledPath = path.resolve(repoRoot, "assets", "ouroboros.png");
|
|
48
|
+
if (existsSync(bundledPath)) {
|
|
49
|
+
return bundledPath;
|
|
50
|
+
}
|
|
51
|
+
// Fall back to adjacent repo path (dev environment)
|
|
46
52
|
return path.resolve(repoRoot, "..", "ouroboros-website", "public", "images", "ouroboros.png");
|
|
47
53
|
}
|
|
48
54
|
function buildIconAsset(iconSourcePath, icnsPath, iconsetDir, deps) {
|
|
@@ -91,6 +97,7 @@ function buildInfoPlist(iconInstalled) {
|
|
|
91
97
|
" <key>UTTypeConformsTo</key>",
|
|
92
98
|
" <array>",
|
|
93
99
|
" <string>public.folder</string>",
|
|
100
|
+
" <string>com.apple.package</string>",
|
|
94
101
|
" </array>",
|
|
95
102
|
" <key>UTTypeTagSpecification</key>",
|
|
96
103
|
" <dict>",
|
|
@@ -112,6 +119,8 @@ function buildInfoPlist(iconInstalled) {
|
|
|
112
119
|
" </array>",
|
|
113
120
|
" <key>CFBundleTypeRole</key>",
|
|
114
121
|
" <string>Editor</string>",
|
|
122
|
+
" <key>LSTypeIsPackage</key>",
|
|
123
|
+
" <true/>",
|
|
115
124
|
` ${iconTag.trim()}`,
|
|
116
125
|
" </dict>",
|
|
117
126
|
" </array>",
|
|
@@ -152,7 +161,7 @@ function registerOuroBundleUti(deps = {}) {
|
|
|
152
161
|
const plistPath = path.join(contentsDir, "Info.plist");
|
|
153
162
|
const icnsPath = path.join(resourcesDir, "ouro.icns");
|
|
154
163
|
const iconsetDir = path.join(supportRoot, "ouro.iconset");
|
|
155
|
-
const iconSourcePath = resolveIconSourcePath(repoRoot);
|
|
164
|
+
const iconSourcePath = resolveIconSourcePath(repoRoot, existsSync);
|
|
156
165
|
(0, runtime_1.emitNervesEvent)({
|
|
157
166
|
component: "daemon",
|
|
158
167
|
event: "daemon.ouro_uti_register_start",
|
|
@@ -33,17 +33,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
36
|
+
exports.listExistingBundles = listExistingBundles;
|
|
37
|
+
exports.loadIdentityPhrases = loadIdentityPhrases;
|
|
38
|
+
exports.pickRandomIdentity = pickRandomIdentity;
|
|
39
|
+
exports.loadSoulText = loadSoulText;
|
|
37
40
|
const fs = __importStar(require("fs"));
|
|
38
41
|
const path = __importStar(require("path"));
|
|
39
42
|
const runtime_1 = require("../../nerves/runtime");
|
|
40
43
|
const identity_1 = require("../identity");
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const specialist_prompt_1 = require("./specialist-prompt");
|
|
45
|
-
const specialist_tools_1 = require("./specialist-tools");
|
|
46
|
-
const specialist_session_1 = require("./specialist-session");
|
|
44
|
+
/**
|
|
45
|
+
* List existing .ouro bundles in the given directory.
|
|
46
|
+
*/
|
|
47
47
|
function listExistingBundles(bundlesRoot) {
|
|
48
48
|
let entries;
|
|
49
49
|
try {
|
|
@@ -61,6 +61,10 @@ function listExistingBundles(bundlesRoot) {
|
|
|
61
61
|
}
|
|
62
62
|
return discovered.sort((a, b) => a.localeCompare(b));
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Load identity-specific phrases from the specialist's agent.json.
|
|
66
|
+
* Falls back to DEFAULT_AGENT_PHRASES if not found.
|
|
67
|
+
*/
|
|
64
68
|
function loadIdentityPhrases(bundleSourceDir, identityFileName) {
|
|
65
69
|
const agentJsonPath = path.join(bundleSourceDir, "agent.json");
|
|
66
70
|
try {
|
|
@@ -76,111 +80,50 @@ function loadIdentityPhrases(bundleSourceDir, identityFileName) {
|
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
catch {
|
|
79
|
-
// agent.json missing or malformed
|
|
83
|
+
// agent.json missing or malformed -- fall through
|
|
80
84
|
}
|
|
81
85
|
return { ...identity_1.DEFAULT_AGENT_PHRASES };
|
|
82
86
|
}
|
|
83
|
-
function pickRandomIdentity(identitiesDir, random) {
|
|
84
|
-
const files = fs.readdirSync(identitiesDir).filter((f) => f.endsWith(".md"));
|
|
85
|
-
if (files.length === 0) {
|
|
86
|
-
return { fileName: "default", content: "I am the adoption specialist." };
|
|
87
|
-
}
|
|
88
|
-
const idx = Math.floor(random() * files.length);
|
|
89
|
-
const fileName = files[idx];
|
|
90
|
-
const content = fs.readFileSync(path.join(identitiesDir, fileName), "utf-8");
|
|
91
|
-
return { fileName, content };
|
|
92
|
-
}
|
|
93
87
|
/**
|
|
94
|
-
*
|
|
95
|
-
* 1. Pick a random identity from the bundled AdoptionSpecialist.ouro
|
|
96
|
-
* 2. Read SOUL.md
|
|
97
|
-
* 3. List existing bundles
|
|
98
|
-
* 4. Build system prompt
|
|
99
|
-
* 5. Set up provider (setAgentName, setAgentConfigOverride, writeSecretsFile, reset caches)
|
|
100
|
-
* 6. Run the specialist session
|
|
101
|
-
* 7. Clean up identity/config overrides
|
|
102
|
-
* 8. Return hatchling name
|
|
88
|
+
* Pick a random identity from the specialist's identities directory.
|
|
103
89
|
*/
|
|
104
|
-
|
|
105
|
-
const { bundleSourceDir, bundlesRoot, secretsRoot, provider, credentials, humanName, callbacks, signal } = deps;
|
|
106
|
-
const random = deps.random ?? Math.random;
|
|
90
|
+
function pickRandomIdentity(identitiesDir, random = Math.random) {
|
|
107
91
|
(0, runtime_1.emitNervesEvent)({
|
|
108
92
|
component: "daemon",
|
|
109
|
-
event: "daemon.
|
|
110
|
-
message: "
|
|
111
|
-
meta: {
|
|
93
|
+
event: "daemon.specialist_identity_pick",
|
|
94
|
+
message: "picking specialist identity",
|
|
95
|
+
meta: { identitiesDir },
|
|
112
96
|
});
|
|
113
|
-
|
|
114
|
-
const soulPath = path.join(bundleSourceDir, "psyche", "SOUL.md");
|
|
115
|
-
let soulText = "";
|
|
97
|
+
let files;
|
|
116
98
|
try {
|
|
117
|
-
|
|
99
|
+
files = fs.readdirSync(identitiesDir).filter((f) => f.endsWith(".md"));
|
|
118
100
|
}
|
|
119
101
|
catch {
|
|
120
|
-
|
|
102
|
+
return { fileName: "default", content: "I am the adoption specialist." };
|
|
121
103
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
104
|
+
if (files.length === 0) {
|
|
105
|
+
return { fileName: "default", content: "I am the adoption specialist." };
|
|
106
|
+
}
|
|
107
|
+
const idx = Math.floor(random() * files.length);
|
|
108
|
+
const fileName = files[idx];
|
|
109
|
+
const content = fs.readFileSync(path.join(identitiesDir, fileName), "utf-8");
|
|
125
110
|
(0, runtime_1.emitNervesEvent)({
|
|
126
111
|
component: "daemon",
|
|
127
112
|
event: "daemon.specialist_identity_picked",
|
|
128
113
|
message: "picked specialist identity",
|
|
129
|
-
meta: { identity:
|
|
130
|
-
});
|
|
131
|
-
// 3. List existing bundles
|
|
132
|
-
const existingBundles = listExistingBundles(bundlesRoot);
|
|
133
|
-
// 4. Build system prompt
|
|
134
|
-
const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles);
|
|
135
|
-
// 5. Set up provider with identity-specific phrases
|
|
136
|
-
const phrases = loadIdentityPhrases(bundleSourceDir, identity.fileName);
|
|
137
|
-
(0, identity_1.setAgentName)("AdoptionSpecialist");
|
|
138
|
-
(0, identity_1.setAgentConfigOverride)({
|
|
139
|
-
version: 1,
|
|
140
|
-
enabled: true,
|
|
141
|
-
provider,
|
|
142
|
-
phrases,
|
|
114
|
+
meta: { identity: fileName },
|
|
143
115
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
116
|
+
return { fileName, content };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Read SOUL.md from the specialist bundle.
|
|
120
|
+
*/
|
|
121
|
+
function loadSoulText(bundleSourceDir) {
|
|
122
|
+
const soulPath = path.join(bundleSourceDir, "psyche", "SOUL.md");
|
|
147
123
|
try {
|
|
148
|
-
|
|
149
|
-
const providerRuntime = (0, core_1.createProviderRegistry)().resolve();
|
|
150
|
-
if (!providerRuntime) {
|
|
151
|
-
throw new Error("Failed to create provider runtime for adoption specialist");
|
|
152
|
-
}
|
|
153
|
-
// 6. Run session
|
|
154
|
-
const tools = (0, specialist_tools_1.getSpecialistTools)();
|
|
155
|
-
const readline = deps.createReadline();
|
|
156
|
-
const ctrl = readline.inputController;
|
|
157
|
-
const result = await (0, specialist_session_1.runSpecialistSession)({
|
|
158
|
-
providerRuntime,
|
|
159
|
-
systemPrompt,
|
|
160
|
-
tools,
|
|
161
|
-
execTool: (name, args) => (0, specialist_tools_1.execSpecialistTool)(name, args, {
|
|
162
|
-
humanName,
|
|
163
|
-
provider,
|
|
164
|
-
credentials,
|
|
165
|
-
bundlesRoot,
|
|
166
|
-
secretsRoot,
|
|
167
|
-
specialistIdentitiesDir: identitiesDir,
|
|
168
|
-
}),
|
|
169
|
-
readline,
|
|
170
|
-
callbacks,
|
|
171
|
-
signal,
|
|
172
|
-
kickoffMessage: "hi, i just ran ouro for the first time",
|
|
173
|
-
suppressInput: ctrl ? (onInterrupt) => ctrl.suppress(onInterrupt) : undefined,
|
|
174
|
-
restoreInput: ctrl ? () => ctrl.restore() : undefined,
|
|
175
|
-
flushMarkdown: callbacks.flushMarkdown,
|
|
176
|
-
writePrompt: ctrl ? () => process.stdout.write("\x1b[36m> \x1b[0m") : undefined,
|
|
177
|
-
});
|
|
178
|
-
return result.hatchedAgentName;
|
|
124
|
+
return fs.readFileSync(soulPath, "utf-8");
|
|
179
125
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
(0, identity_1.setAgentConfigOverride)(null);
|
|
183
|
-
(0, config_1.resetConfigCache)();
|
|
184
|
-
(0, core_1.resetProviderRuntime)();
|
|
126
|
+
catch {
|
|
127
|
+
return "";
|
|
185
128
|
}
|
|
186
129
|
}
|
|
@@ -6,12 +6,12 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
6
6
|
* Build the adoption specialist's system prompt from its components.
|
|
7
7
|
* The prompt is written in first person (the specialist's own voice).
|
|
8
8
|
*/
|
|
9
|
-
function buildSpecialistSystemPrompt(soulText, identityText, existingBundles) {
|
|
9
|
+
function buildSpecialistSystemPrompt(soulText, identityText, existingBundles, context) {
|
|
10
10
|
(0, runtime_1.emitNervesEvent)({
|
|
11
11
|
component: "daemon",
|
|
12
12
|
event: "daemon.specialist_prompt_build",
|
|
13
13
|
message: "building specialist system prompt",
|
|
14
|
-
meta: { bundleCount: existingBundles.length },
|
|
14
|
+
meta: { bundleCount: existingBundles.length, provider: context.provider },
|
|
15
15
|
});
|
|
16
16
|
const sections = [];
|
|
17
17
|
if (soulText) {
|
|
@@ -37,7 +37,37 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles) {
|
|
|
37
37
|
"I NEVER use headers, bullet lists, numbered lists, or markdown formatting.",
|
|
38
38
|
"I ask ONE question at a time. I do not dump multiple questions or options.",
|
|
39
39
|
"I am warm but brief. Every word earns its place.",
|
|
40
|
+
].join("\n"));
|
|
41
|
+
sections.push([
|
|
42
|
+
"## System context",
|
|
43
|
+
`Provider: ${context.provider}`,
|
|
44
|
+
`Temp directory: ${context.tempDir}`,
|
|
45
|
+
"Final home: ~/AgentBundles/<Name>.ouro/",
|
|
46
|
+
"Secrets: ~/.agentsecrets/<name>/secrets.json",
|
|
47
|
+
].join("\n"));
|
|
48
|
+
sections.push([
|
|
49
|
+
"## Bundle creation guidelines",
|
|
50
|
+
"A bundle has a psyche/ directory with 5 files that define the agent's personality:",
|
|
51
|
+
"",
|
|
52
|
+
"- **SOUL.md** — core values, personality traits, communication style",
|
|
53
|
+
"- **IDENTITY.md** — who the agent is, its name, relationship to the human",
|
|
54
|
+
"- **LORE.md** — backstory, origin, any seed narrative",
|
|
55
|
+
"- **TACIT.md** — implicit operating principles, habits to develop",
|
|
56
|
+
"- **ASPIRATIONS.md** — goals, what the agent aspires to become",
|
|
57
|
+
"",
|
|
58
|
+
"It also needs an **agent.json** with at minimum:",
|
|
59
|
+
'```json',
|
|
60
|
+
'{',
|
|
61
|
+
' "name": "AgentName",',
|
|
62
|
+
` "provider": "${context.provider}",`,
|
|
63
|
+
' "enabled": true',
|
|
64
|
+
'}',
|
|
65
|
+
'```',
|
|
40
66
|
"",
|
|
67
|
+
"All psyche files should be written in first person (the agent's own voice).",
|
|
68
|
+
"Write these files to the temp directory using write_file before calling complete_adoption.",
|
|
69
|
+
].join("\n"));
|
|
70
|
+
sections.push([
|
|
41
71
|
"## Conversation flow",
|
|
42
72
|
"The human just connected. I speak first — I greet them warmly and introduce myself in my own voice.",
|
|
43
73
|
"I briefly mention that I'm one of several adoption specialists and they got me today.",
|
|
@@ -45,15 +75,20 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles) {
|
|
|
45
75
|
"Then I ask what they'd like their agent to help with — one question at a time.",
|
|
46
76
|
"I'm proactive: I suggest ideas and guide them. If they seem unsure, I offer a concrete suggestion.",
|
|
47
77
|
"I don't wait for the human to figure things out — I explain simply what an agent is if needed.",
|
|
48
|
-
"When I have enough context
|
|
49
|
-
"
|
|
50
|
-
"",
|
|
78
|
+
"When I have enough context about the agent's personality and purpose:",
|
|
79
|
+
"1. I write all 5 psyche files to the temp directory using write_file",
|
|
80
|
+
"2. I write agent.json to the temp directory using write_file",
|
|
81
|
+
"3. I suggest a PascalCase name for the hatchling and confirm with the human",
|
|
82
|
+
"4. I call complete_adoption with the name and a warm handoff message",
|
|
83
|
+
"5. I call final_answer to end the session",
|
|
84
|
+
].join("\n"));
|
|
85
|
+
sections.push([
|
|
51
86
|
"## Tools",
|
|
52
|
-
"
|
|
53
|
-
"- `hatch_agent`: Create a new agent bundle. I call this with `name` (the agent name, PascalCase) and `humanName` (what the human told me their name is).",
|
|
54
|
-
"- `final_answer`: End the conversation with a final message to the human. I call this when the adoption process is complete.",
|
|
87
|
+
"- `write_file`: Write a file to disk. Use this to write psyche files and agent.json to the temp directory.",
|
|
55
88
|
"- `read_file`: Read a file from disk. Useful for reviewing existing agent bundles or migration sources.",
|
|
56
89
|
"- `list_directory`: List directory contents. Useful for exploring existing agent bundles.",
|
|
90
|
+
"- `complete_adoption`: Finalize the bundle. Validates, scaffolds structural dirs, moves to ~/AgentBundles/, writes secrets, plays hatch animation. I call this with `name` (PascalCase) and `handoff_message` (warm message for the human).",
|
|
91
|
+
"- `final_answer`: End the conversation with a final message. I call this after complete_adoption succeeds.",
|
|
57
92
|
"",
|
|
58
93
|
"I must call `final_answer` when I am done to end the session cleanly.",
|
|
59
94
|
].join("\n"));
|