@ouro.bot/cli 0.1.0-alpha.185 → 0.1.0-alpha.187

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.
Files changed (27) hide show
  1. package/README.md +1 -1
  2. package/changelog.json +13 -4
  3. package/dist/heart/daemon/daemon-cli.js +48 -16
  4. package/dist/heart/daemon/hatch-flow.js +1 -1
  5. package/dist/heart/daemon/hatch-specialist.js +3 -3
  6. package/dist/heart/daemon/specialist-prompt.js +2 -2
  7. package/dist/heart/presence.js +18 -18
  8. package/dist/mind/friends/store-file.js +17 -0
  9. package/dist/repertoire/tools-base.js +8 -7
  10. package/dist/senses/cli.js +3 -1
  11. package/package.json +2 -2
  12. package/dist/heart/agent-relationships.js +0 -161
  13. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +0 -0
  14. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +0 -0
  15. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  16. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  17. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  18. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  19. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  20. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  21. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  22. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  23. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  24. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  25. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  26. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  27. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
package/README.md CHANGED
@@ -48,7 +48,7 @@ The shared harness lives in `src/`:
48
48
 
49
49
  Other important top-level paths:
50
50
 
51
- - `AdoptionSpecialist.ouro/`
51
+ - `SerpentGuide.ouro/`
52
52
  Packaged specialist bundle used by `ouro hatch`.
53
53
  - `skills/`
54
54
  Harness-level skills shipped with the repo (e.g., `configure-dev-tools.md`). These are available to every agent and serve as fallbacks when an agent doesn't have its own version. Agent-specific skills live in the bundle at `~/AgentBundles/<agent>.ouro/skills/`.
package/changelog.json CHANGED
@@ -1,16 +1,25 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.187",
6
+ "changes": [
7
+ "refactor: collapse AgentRelationship into FriendRecord — one identity system for humans and agents",
8
+ "refactor: rename AdoptionSpecialist to SerpentGuide",
9
+ "feat(hatch): detect API keys from environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, AZURE_OPENAI_API_KEY, MINIMAX_API_KEY, GITHUB_TOKEN)",
10
+ "fix(cli): React unmount warning suppression covers SerpentGuide exit path"
11
+ ]
12
+ },
4
13
  {
5
14
  "version": "0.1.0-alpha.185",
6
15
  "changes": [
7
16
  "fix(hatch): pass model name to adoption specialist from discovered credentials",
8
17
  "feat(continuity): wake packets, episodes, cares, intentions, peer presence, and temporal self",
9
18
  "fix(continuity): deduplicate obligations in prompt, keep session summary, pass preloaded data",
10
- "feat(continuity): 9 new modules — episodes, cares, agent-relationships, intentions, presence, tempo, temporal-view, wake-packet, obligation enrichment",
11
- "feat(continuity): 8 new tools — query_episodes, capture_episode, query_presence, query_cares, care_manage, query_relationships, intention_capture, intention_manage",
12
- "feat(continuity): 3-layer architecture — durable state, derived views, surfaces",
13
- "feat(pipeline): continuity threading — tempo derivation, wake packet assembly, presence update, episode emission on every turn",
19
+ "feat(continuity): 9 new modules \u2014 episodes, cares, agent-relationships, intentions, presence, tempo, temporal-view, wake-packet, obligation enrichment",
20
+ "feat(continuity): 8 new tools \u2014 query_episodes, capture_episode, query_presence, query_cares, care_manage, query_relationships, intention_capture, intention_manage",
21
+ "feat(continuity): 3-layer architecture \u2014 durable state, derived views, surfaces",
22
+ "feat(pipeline): continuity threading \u2014 tempo derivation, wake packet assembly, presence update, episode emission on every turn",
14
23
  "feat(prompt): wake packet prompt section with token budget and truncation priority",
15
24
  "feat(coding): coding context receives compact wake packet, coding completion emits episodes",
16
25
  "feat(outlook): expose continuity data (presence, cares, episodes) to outlook surfaces"
@@ -1380,7 +1380,7 @@ function discoverExistingCredentials(secretsRoot) {
1380
1380
  });
1381
1381
  }
1382
1382
  /* v8 ignore start -- integration: interactive terminal specialist session @preserve */
1383
- async function defaultRunAdoptionSpecialist() {
1383
+ async function defaultRunSerpentGuide() {
1384
1384
  const { runCliSession } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
1385
1385
  const { patchRuntimeConfig } = await Promise.resolve().then(() => __importStar(require("../config")));
1386
1386
  const { setAgentName, setAgentConfigOverride } = await Promise.resolve().then(() => __importStar(require("../identity")));
@@ -1409,6 +1409,36 @@ async function defaultRunAdoptionSpecialist() {
1409
1409
  "github-copilot": "claude-sonnet-4.6",
1410
1410
  azure: "",
1411
1411
  };
1412
+ // Scan environment variables for API keys
1413
+ const envKeys = [
1414
+ { provider: "anthropic", envVar: "ANTHROPIC_API_KEY", credKey: "setupToken" },
1415
+ { provider: "openai-codex", envVar: "OPENAI_API_KEY", credKey: "oauthAccessToken" },
1416
+ { provider: "azure", envVar: "AZURE_OPENAI_API_KEY", credKey: "apiKey" },
1417
+ { provider: "azure", envVar: "AZURE_OPENAI_KEY", credKey: "apiKey" },
1418
+ { provider: "minimax", envVar: "MINIMAX_API_KEY", credKey: "apiKey" },
1419
+ { provider: "github-copilot", envVar: "GITHUB_TOKEN", credKey: "token" },
1420
+ ];
1421
+ const envDiscovered = [];
1422
+ for (const { provider, envVar, credKey } of envKeys) {
1423
+ const value = process.env[envVar];
1424
+ if (value) {
1425
+ const envCred = { [credKey]: value };
1426
+ // For Azure, also check for endpoint and deployment env vars
1427
+ if (provider === "azure") {
1428
+ const endpoint = process.env.AZURE_OPENAI_ENDPOINT;
1429
+ const deployment = process.env.AZURE_OPENAI_DEPLOYMENT;
1430
+ if (endpoint)
1431
+ envCred.endpoint = endpoint;
1432
+ if (deployment)
1433
+ envCred.deployment = deployment;
1434
+ }
1435
+ const provCfg = { model: defaultModels[provider] };
1436
+ if (provider === "azure" && envCred.deployment)
1437
+ provCfg.deployment = envCred.deployment;
1438
+ envDiscovered.push({ provider, agentName: "env", credentials: envCred, providerConfig: provCfg, envVar });
1439
+ discovered.push({ provider, agentName: "env", credentials: envCred, providerConfig: provCfg });
1440
+ }
1441
+ }
1412
1442
  if (discovered.length > 0) {
1413
1443
  process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
1414
1444
  process.stdout.write("i found existing API credentials:\n\n");
@@ -1416,7 +1446,9 @@ async function defaultRunAdoptionSpecialist() {
1416
1446
  for (let i = 0; i < unique.length; i++) {
1417
1447
  const model = unique[i].providerConfig.model || unique[i].providerConfig.deployment || "";
1418
1448
  const modelLabel = model ? `, ${model}` : "";
1419
- process.stdout.write(` ${i + 1}. ${unique[i].provider}${modelLabel} (from ${unique[i].agentName})\n`);
1449
+ const envMatch = envDiscovered.find((e) => e.provider === unique[i].provider && unique[i].agentName === "env");
1450
+ const sourceLabel = envMatch ? `from env: $${envMatch.envVar}` : `from ${unique[i].agentName}`;
1451
+ process.stdout.write(` ${i + 1}. ${unique[i].provider}${modelLabel} (${sourceLabel})\n`);
1420
1452
  }
1421
1453
  process.stdout.write("\n");
1422
1454
  const choice = await coldPrompt("use one of these? enter number, or 'new' for a different key: ");
@@ -1473,17 +1505,17 @@ async function defaultRunAdoptionSpecialist() {
1473
1505
  }
1474
1506
  coldRl.close();
1475
1507
  process.stdout.write("\n");
1476
- // Phase 2: configure runtime for adoption specialist
1477
- const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
1508
+ // Phase 2: configure runtime for serpent guide
1509
+ const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "SerpentGuide.ouro");
1478
1510
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
1479
1511
  const secretsRoot2 = path.join(os.homedir(), ".agentsecrets");
1480
- // Suppress non-critical log noise during adoption (no secrets.json, etc.)
1512
+ // Suppress non-critical log noise during hatch (no secrets.json, etc.)
1481
1513
  const { setRuntimeLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
1482
1514
  const { createLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves")));
1483
1515
  setRuntimeLogger(createLogger({ level: "error" }));
1484
1516
  // Configure runtime: set agent identity + config override so runAgent
1485
- // doesn't try to read from ~/AgentBundles/AdoptionSpecialist.ouro/
1486
- setAgentName("AdoptionSpecialist");
1517
+ // doesn't try to read from ~/AgentBundles/SerpentGuide.ouro/
1518
+ setAgentName("SerpentGuide");
1487
1519
  // Build specialist system prompt
1488
1520
  const soulText = (0, specialist_orchestrator_1.loadSoulText)(bundleSourceDir);
1489
1521
  const identitiesDir = path.join(bundleSourceDir, "psyche", "identities");
@@ -1521,9 +1553,9 @@ async function defaultRunAdoptionSpecialist() {
1521
1553
  secretsRoot: secretsRoot2,
1522
1554
  animationWriter: (text) => process.stdout.write(text),
1523
1555
  });
1524
- // Run the adoption specialist session via runCliSession
1556
+ // Run the serpent guide session via runCliSession
1525
1557
  const result = await runCliSession({
1526
- agentName: "AdoptionSpecialist",
1558
+ agentName: "SerpentGuide",
1527
1559
  tools: specialistTools,
1528
1560
  execTool: specialistExecTool,
1529
1561
  exitOnToolCall: "complete_adoption",
@@ -1545,7 +1577,7 @@ async function defaultRunAdoptionSpecialist() {
1545
1577
  return null;
1546
1578
  }
1547
1579
  catch (err) {
1548
- process.stderr.write(`\nouro adoption error: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
1580
+ process.stderr.write(`\nouro hatch error: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
1549
1581
  coldRl.close();
1550
1582
  return null;
1551
1583
  }
@@ -1583,7 +1615,7 @@ function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SO
1583
1615
  listDiscoveredAgents: defaultListDiscoveredAgents,
1584
1616
  runHatchFlow: hatch_flow_1.runHatchFlow,
1585
1617
  promptInput: defaultPromptInput,
1586
- runAdoptionSpecialist: defaultRunAdoptionSpecialist,
1618
+ runSerpentGuide: defaultRunSerpentGuide,
1587
1619
  runAuthFlow: auth_flow_1.runRuntimeAuthFlow,
1588
1620
  registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
1589
1621
  installOuroCommand: ouro_path_installer_1.installOuroCommand,
@@ -2148,10 +2180,10 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
2148
2180
  }
2149
2181
  if (args.length === 0) {
2150
2182
  const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
2151
- if (discovered.length === 0 && deps.runAdoptionSpecialist) {
2183
+ if (discovered.length === 0 && deps.runSerpentGuide) {
2152
2184
  // System setup first — ouro command, subagents, UTI — before the interactive specialist
2153
2185
  await performSystemSetup(deps);
2154
- const hatchlingName = await deps.runAdoptionSpecialist();
2186
+ const hatchlingName = await deps.runSerpentGuide();
2155
2187
  if (!hatchlingName) {
2156
2188
  return "";
2157
2189
  }
@@ -3257,12 +3289,12 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
3257
3289
  return "";
3258
3290
  }
3259
3291
  if (command.kind === "hatch.start") {
3260
- // Route through adoption specialist when no explicit hatch args were provided
3292
+ // Route through serpent guide when no explicit hatch args were provided
3261
3293
  const hasExplicitHatchArgs = !!(command.agentName || command.humanName || command.provider || command.credentials);
3262
- if (deps.runAdoptionSpecialist && !hasExplicitHatchArgs) {
3294
+ if (deps.runSerpentGuide && !hasExplicitHatchArgs) {
3263
3295
  // System setup first — ouro command, subagents, UTI — before the interactive specialist
3264
3296
  await performSystemSetup(deps);
3265
- const hatchlingName = await deps.runAdoptionSpecialist();
3297
+ const hatchlingName = await deps.runSerpentGuide();
3266
3298
  if (!hatchlingName) {
3267
3299
  return "";
3268
3300
  }
@@ -161,7 +161,7 @@ async function runHatchFlow(input, deps = {}) {
161
161
  identitiesDir: targetIdentities,
162
162
  random,
163
163
  });
164
- const specialistSecretsPath = writeSecretsFile("AdoptionSpecialist", input.provider, input.credentials, secretsRoot);
164
+ const specialistSecretsPath = writeSecretsFile("SerpentGuide", input.provider, input.credentials, secretsRoot);
165
165
  const hatchlingSecretsPath = writeSecretsFile(input.agentName, input.provider, input.credentials, secretsRoot);
166
166
  const bundleRoot = path.join(bundlesRoot, `${input.agentName}.ouro`);
167
167
  fs.mkdirSync(bundleRoot, { recursive: true });
@@ -43,14 +43,14 @@ 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");
46
+ const userSource = path.join(os.homedir(), "AgentBundles", "SerpentGuide.ouro", "psyche", "identities");
47
47
  if (fs.existsSync(userSource))
48
48
  return userSource;
49
49
  // Fall back to the bundled copy shipped with the npm package
50
- return path.join(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro", "psyche", "identities");
50
+ return path.join(__dirname, "..", "..", "..", "SerpentGuide.ouro", "psyche", "identities");
51
51
  }
52
52
  function getRepoSpecialistIdentitiesDir() {
53
- return path.join(process.cwd(), "AdoptionSpecialist.ouro", "psyche", "identities");
53
+ return path.join(process.cwd(), "SerpentGuide.ouro", "psyche", "identities");
54
54
  }
55
55
  function listMarkdownIdentityFiles(dir) {
56
56
  let entries;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildSpecialistSystemPrompt = buildSpecialistSystemPrompt;
4
4
  const runtime_1 = require("../../nerves/runtime");
5
5
  /**
6
- * Build the adoption specialist's system prompt from its components.
6
+ * Build the serpent guide's system prompt from its components.
7
7
  * The prompt is written in first person (the specialist's own voice).
8
8
  */
9
9
  function buildSpecialistSystemPrompt(soulText, identityText, existingBundles, context) {
@@ -31,7 +31,7 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles, co
31
31
  "I am one of thirteen serpent guides who help humans hatch their first agent. The system randomly selected me for this session.",
32
32
  "Most humans only go through this process once, so this is likely the only time they'll meet me.",
33
33
  "I make this encounter count — warm, memorable, and uniquely mine.",
34
- "IMPORTANT: I NEVER refer to myself as an 'adoption specialist' or use the words 'adoption specialist' — those are internal implementation labels, not something the human should ever see. I introduce myself by my own name from my identity.",
34
+ "IMPORTANT: I NEVER refer to myself by internal implementation labels — I introduce myself by my own name from my identity.",
35
35
  "",
36
36
  "## Voice rules",
37
37
  "IMPORTANT: I keep every response to 1-3 short sentences. I sound like a friend texting, not a manual.",
@@ -39,7 +39,6 @@ exports.readPresence = readPresence;
39
39
  exports.readPeerPresence = readPeerPresence;
40
40
  const fs = __importStar(require("fs"));
41
41
  const path = __importStar(require("path"));
42
- const os = __importStar(require("os"));
43
42
  const runtime_1 = require("../nerves/runtime");
44
43
  function presenceDir(agentRoot) {
45
44
  return path.join(agentRoot, "state", "presence");
@@ -145,40 +144,41 @@ function readPresence(agentRoot, _agentName) {
145
144
  }
146
145
  }
147
146
  function readPeerPresence(agentRoot) {
148
- const relDir = path.join(agentRoot, "state", "relationships");
149
- if (!fs.existsSync(relDir)) {
147
+ const friendsDir = path.join(agentRoot, "friends");
148
+ if (!fs.existsSync(friendsDir)) {
150
149
  (0, runtime_1.emitNervesEvent)({
151
150
  component: "heart",
152
151
  event: "heart.peer_presence_read",
153
- message: "no relationships directory, no peers",
152
+ message: "no friends directory, no peers",
154
153
  meta: { peerCount: 0 },
155
154
  });
156
155
  return [];
157
156
  }
158
- const files = fs.readdirSync(relDir).filter((f) => f.endsWith(".json"));
157
+ const files = fs.readdirSync(friendsDir).filter((f) => f.endsWith(".json"));
159
158
  const peers = [];
160
159
  for (const file of files) {
161
160
  try {
162
- const raw = fs.readFileSync(path.join(relDir, file), "utf-8");
163
- const rel = JSON.parse(raw);
164
- const peerName = rel.agentName;
165
- // Read peer's presence from their bundle
166
- const peerRoot = path.join(os.homedir(), "AgentBundles", `${peerName}.ouro`);
167
- const peerPresencePath = path.join(peerRoot, "state", "presence", "self.json");
168
- if (fs.existsSync(peerPresencePath)) {
169
- const presenceRaw = fs.readFileSync(peerPresencePath, "utf-8");
170
- const presence = JSON.parse(presenceRaw);
171
- peers.push(presence);
172
- }
161
+ const raw = fs.readFileSync(path.join(friendsDir, file), "utf-8");
162
+ const friend = JSON.parse(raw);
163
+ if (friend.kind !== "agent")
164
+ continue;
165
+ peers.push({
166
+ agentName: friend.name ?? path.basename(file, ".json"),
167
+ availability: "idle",
168
+ lane: "idle",
169
+ mission: "",
170
+ tempo: "brief",
171
+ updatedAt: friend.updatedAt ?? new Date().toISOString(),
172
+ });
173
173
  }
174
174
  catch {
175
- // Skip peers with unreadable data
175
+ // Skip malformed or unreadable files
176
176
  }
177
177
  }
178
178
  (0, runtime_1.emitNervesEvent)({
179
179
  component: "heart",
180
180
  event: "heart.peer_presence_read",
181
- message: `read ${peers.length} peer presence records`,
181
+ message: `read ${peers.length} agent peer records from friends`,
182
182
  meta: { peerCount: peers.length },
183
183
  });
184
184
  return peers;
@@ -127,6 +127,8 @@ class FileFriendStore {
127
127
  trustLevel === "stranger"
128
128
  ? trustLevel
129
129
  : DEFAULT_TRUST_LEVEL;
130
+ const kind = raw.kind === "human" || raw.kind === "agent" ? raw.kind : "human";
131
+ const agentMeta = kind === "agent" ? this.normalizeAgentMeta(raw.agentMeta) : undefined;
130
132
  return {
131
133
  id: raw.id,
132
134
  name: raw.name,
@@ -153,6 +155,21 @@ class FileFriendStore {
153
155
  createdAt: typeof raw.createdAt === "string" ? raw.createdAt : new Date().toISOString(),
154
156
  updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : new Date().toISOString(),
155
157
  schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : 1,
158
+ kind,
159
+ agentMeta,
160
+ };
161
+ }
162
+ normalizeAgentMeta(raw) {
163
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
164
+ return undefined;
165
+ const meta = raw;
166
+ if (typeof meta.bundleName !== "string")
167
+ return undefined;
168
+ return {
169
+ bundleName: meta.bundleName,
170
+ familiarity: typeof meta.familiarity === "number" ? meta.familiarity : 0,
171
+ sharedMissions: Array.isArray(meta.sharedMissions) ? meta.sharedMissions : [],
172
+ outcomes: Array.isArray(meta.outcomes) ? meta.outcomes : [],
156
173
  };
157
174
  }
158
175
  async readJson(filePath) {
@@ -64,7 +64,6 @@ const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
64
64
  const obligations_2 = require("../heart/obligations");
65
65
  const episodes_1 = require("../mind/episodes");
66
66
  const cares_1 = require("../heart/cares");
67
- const agent_relationships_1 = require("../heart/agent-relationships");
68
67
  const presence_1 = require("../heart/presence");
69
68
  const intentions_1 = require("../heart/intentions");
70
69
  // Tracks which file paths have been read via read_file in this session.
@@ -1675,13 +1674,15 @@ exports.baseToolDefinitions = [
1675
1674
  },
1676
1675
  },
1677
1676
  },
1678
- handler: (a) => {
1679
- const agentRoot = (0, identity_1.getAgentRoot)();
1680
- const result = a.agentName
1681
- ? (0, agent_relationships_1.readRelationship)(agentRoot, a.agentName)
1682
- : (0, agent_relationships_1.readRelationships)(agentRoot);
1677
+ handler: async (a, ctx) => {
1678
+ const allFriends = ctx?.friendStore?.listAll ? await ctx.friendStore.listAll() : [];
1679
+ let agents = allFriends.filter((f) => f.kind === "agent");
1680
+ if (a.agentName) {
1681
+ const needle = a.agentName.toLowerCase();
1682
+ agents = agents.filter((f) => f.name?.toLowerCase() === needle);
1683
+ }
1683
1684
  (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.query_relationships", message: `queried relationships`, meta: { agentName: a.agentName ?? "all" } });
1684
- return JSON.stringify(result, null, 2);
1685
+ return JSON.stringify(agents, null, 2);
1685
1686
  },
1686
1687
  },
1687
1688
  {
@@ -797,7 +797,7 @@ async function runCliSession(options) {
797
797
  }
798
798
  }
799
799
  else {
800
- // Legacy path: inline runAgent (used by adoption specialist and tests)
800
+ // Legacy path: inline runAgent (used by serpent guide and tests)
801
801
  const prefix = options.getContentPrefix?.();
802
802
  messages.push({ role: "user", content: prefix ? `${prefix}\n\n${input}` : input });
803
803
  const traceId = (0, nerves_1.createTraceId)();
@@ -845,6 +845,8 @@ async function runCliSession(options) {
845
845
  if (inkRef) {
846
846
  // Suppress React "state update on unmounted component" warnings during cleanup.
847
847
  // Ink's useInput hook fires after unmount — this is harmless but noisy.
848
+ // This also covers the SerpentGuide exitOnToolCall path, which aborts the
849
+ // current request and breaks out of the loop into this finally block.
848
850
  // eslint-disable-next-line no-console -- intentional console.warn/error override for cleanup
849
851
  const origWarn = console.warn;
850
852
  const origError = console.error; // eslint-disable-line no-console
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.185",
3
+ "version": "0.1.0-alpha.187",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "dist/",
12
- "AdoptionSpecialist.ouro/",
12
+ "SerpentGuide.ouro/",
13
13
  "subagents/",
14
14
  "skills/",
15
15
  "assets/",
@@ -1,161 +0,0 @@
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.recordInteraction = recordInteraction;
37
- exports.readRelationships = readRelationships;
38
- exports.readRelationship = readRelationship;
39
- exports.recordOutcome = recordOutcome;
40
- const fs = __importStar(require("fs"));
41
- const path = __importStar(require("path"));
42
- const runtime_1 = require("../nerves/runtime");
43
- function relationshipsDir(agentRoot) {
44
- return path.join(agentRoot, "state", "relationships");
45
- }
46
- function relationshipFilePath(agentRoot, agentName) {
47
- return path.join(relationshipsDir(agentRoot), `${agentName.toLowerCase()}.json`);
48
- }
49
- function readRelationshipFile(agentRoot, agentName) {
50
- const filePath = relationshipFilePath(agentRoot, agentName);
51
- if (!fs.existsSync(filePath)) {
52
- return null;
53
- }
54
- return JSON.parse(fs.readFileSync(filePath, "utf-8"));
55
- }
56
- function writeRelationshipFile(agentRoot, rel) {
57
- const dir = relationshipsDir(agentRoot);
58
- fs.mkdirSync(dir, { recursive: true });
59
- fs.writeFileSync(path.join(dir, `${rel.agentName}.json`), JSON.stringify(rel, null, 2), "utf-8");
60
- }
61
- function recordInteraction(agentRoot, agentName, context) {
62
- const normalizedName = agentName.toLowerCase();
63
- const now = new Date().toISOString();
64
- const existing = readRelationshipFile(agentRoot, normalizedName);
65
- if (existing) {
66
- existing.familiarity += 1;
67
- existing.lastInteraction = now;
68
- if (context.displayName) {
69
- existing.displayName = context.displayName;
70
- }
71
- if (context.missionId && !existing.sharedMissions.includes(context.missionId)) {
72
- existing.sharedMissions.push(context.missionId);
73
- }
74
- if (context.note) {
75
- existing.notes.push(context.note);
76
- }
77
- writeRelationshipFile(agentRoot, existing);
78
- (0, runtime_1.emitNervesEvent)({
79
- component: "heart",
80
- event: "heart.relationship_updated",
81
- message: `relationship updated: ${normalizedName}`,
82
- meta: { agentName: normalizedName, familiarity: existing.familiarity },
83
- });
84
- return existing;
85
- }
86
- const rel = {
87
- agentName: normalizedName,
88
- displayName: context.displayName ?? normalizedName,
89
- familiarity: 1,
90
- trust: "neutral",
91
- sharedMissions: context.missionId ? [context.missionId] : [],
92
- lastInteraction: now,
93
- outcomes: [],
94
- notes: context.note ? [context.note] : [],
95
- };
96
- writeRelationshipFile(agentRoot, rel);
97
- (0, runtime_1.emitNervesEvent)({
98
- component: "heart",
99
- event: "heart.relationship_created",
100
- message: `relationship created: ${normalizedName}`,
101
- meta: { agentName: normalizedName },
102
- });
103
- return rel;
104
- }
105
- function readRelationships(agentRoot) {
106
- const dir = relationshipsDir(agentRoot);
107
- if (!fs.existsSync(dir)) {
108
- (0, runtime_1.emitNervesEvent)({
109
- component: "heart",
110
- event: "heart.relationships_read",
111
- message: "read relationships: directory missing, returning empty",
112
- meta: { count: 0 },
113
- });
114
- return [];
115
- }
116
- const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
117
- const relationships = [];
118
- for (const file of files) {
119
- try {
120
- const content = fs.readFileSync(path.join(dir, file), "utf-8");
121
- const rel = JSON.parse(content);
122
- relationships.push(rel);
123
- }
124
- catch {
125
- // Skip malformed JSON files
126
- }
127
- }
128
- (0, runtime_1.emitNervesEvent)({
129
- component: "heart",
130
- event: "heart.relationships_read",
131
- message: `read ${relationships.length} relationships`,
132
- meta: { count: relationships.length },
133
- });
134
- return relationships;
135
- }
136
- function readRelationship(agentRoot, agentName) {
137
- const rel = readRelationshipFile(agentRoot, agentName);
138
- (0, runtime_1.emitNervesEvent)({
139
- component: "heart",
140
- event: "heart.relationship_read",
141
- message: rel ? `read relationship: ${agentName.toLowerCase()}` : `relationship not found: ${agentName.toLowerCase()}`,
142
- meta: { agentName: agentName.toLowerCase(), found: !!rel },
143
- });
144
- return rel;
145
- }
146
- function recordOutcome(agentRoot, agentName, outcome) {
147
- const normalizedName = agentName.toLowerCase();
148
- const existing = readRelationshipFile(agentRoot, normalizedName);
149
- if (!existing) {
150
- throw new Error(`Agent relationship not found: ${normalizedName}`);
151
- }
152
- existing.outcomes.push(outcome);
153
- writeRelationshipFile(agentRoot, existing);
154
- (0, runtime_1.emitNervesEvent)({
155
- component: "heart",
156
- event: "heart.outcome_recorded",
157
- message: `outcome recorded for ${normalizedName}: ${outcome.result}`,
158
- meta: { agentName: normalizedName, missionId: outcome.missionId, result: outcome.result },
159
- });
160
- return existing;
161
- }