@ouro.bot/cli 0.1.0-alpha.184 → 0.1.0-alpha.186

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 (38) hide show
  1. package/README.md +1 -1
  2. package/changelog.json +19 -2
  3. package/dist/heart/active-work.js +19 -10
  4. package/dist/heart/agent-relationships.js +161 -0
  5. package/dist/heart/cares.js +174 -0
  6. package/dist/heart/daemon/daemon-cli.js +48 -16
  7. package/dist/heart/daemon/hatch-flow.js +1 -1
  8. package/dist/heart/daemon/hatch-specialist.js +3 -3
  9. package/dist/heart/daemon/outlook-read.js +40 -0
  10. package/dist/heart/daemon/specialist-prompt.js +2 -2
  11. package/dist/heart/intentions.js +168 -0
  12. package/dist/heart/obligations.js +23 -0
  13. package/dist/heart/presence.js +185 -0
  14. package/dist/heart/tempo.js +93 -0
  15. package/dist/heart/temporal-view.js +41 -0
  16. package/dist/heart/wake-packet.js +226 -0
  17. package/dist/mind/episodes.js +116 -0
  18. package/dist/mind/prompt.js +10 -1
  19. package/dist/repertoire/coding/context-pack.js +25 -2
  20. package/dist/repertoire/tools-base.js +240 -0
  21. package/dist/senses/cli.js +3 -1
  22. package/dist/senses/pipeline.js +88 -0
  23. package/package.json +2 -2
  24. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +0 -0
  25. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +0 -0
  26. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  27. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  28. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  29. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  30. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  31. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  32. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  33. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  34. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  35. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  36. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  37. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  38. /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
@@ -2,9 +2,26 @@
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
4
  {
5
- "version": "0.1.0-alpha.184",
5
+ "version": "0.1.0-alpha.186",
6
6
  "changes": [
7
- "fix(hatch): pass model name to adoption specialist from discovered credentials"
7
+ "refactor: rename AdoptionSpecialist to SerpentGuide",
8
+ "feat(hatch): detect API keys from environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, AZURE_OPENAI_API_KEY, MINIMAX_API_KEY, GITHUB_TOKEN)",
9
+ "fix(cli): React unmount warning suppression covers SerpentGuide exit path"
10
+ ]
11
+ },
12
+ {
13
+ "version": "0.1.0-alpha.185",
14
+ "changes": [
15
+ "fix(hatch): pass model name to adoption specialist from discovered credentials",
16
+ "feat(continuity): wake packets, episodes, cares, intentions, peer presence, and temporal self",
17
+ "fix(continuity): deduplicate obligations in prompt, keep session summary, pass preloaded data",
18
+ "feat(continuity): 9 new modules \u2014 episodes, cares, agent-relationships, intentions, presence, tempo, temporal-view, wake-packet, obligation enrichment",
19
+ "feat(continuity): 8 new tools \u2014 query_episodes, capture_episode, query_presence, query_cares, care_manage, query_relationships, intention_capture, intention_manage",
20
+ "feat(continuity): 3-layer architecture \u2014 durable state, derived views, surfaces",
21
+ "feat(pipeline): continuity threading \u2014 tempo derivation, wake packet assembly, presence update, episode emission on every turn",
22
+ "feat(prompt): wake packet prompt section with token budget and truncation priority",
23
+ "feat(coding): coding context receives compact wake packet, coding completion emits episodes",
24
+ "feat(outlook): expose continuity data (presence, cares, episodes) to outlook surfaces"
8
25
  ]
9
26
  },
10
27
  {
@@ -469,7 +469,7 @@ function buildActiveWorkFrame(input) {
469
469
  });
470
470
  return frame;
471
471
  }
472
- function formatActiveWorkFrame(frame) {
472
+ function formatActiveWorkFrame(frame, options) {
473
473
  const lines = ["## what i'm holding"];
474
474
  lines.push("this is my top-level live world-state right now. inner work, coding lanes, other sessions, and return obligations all belong inside this picture.");
475
475
  lines.push("if older checkpoints elsewhere in the transcript disagree with this picture, this picture wins.");
@@ -577,16 +577,25 @@ function formatActiveWorkFrame(frame) {
577
577
  lines.push(targetCandidatesBlock);
578
578
  }
579
579
  if ((frame.pendingObligations ?? []).length > 0) {
580
- lines.push("");
581
- lines.push("## return obligations");
582
- for (const obligation of frame.pendingObligations) {
583
- if (!(0, obligations_1.isOpenObligationStatus)(obligation.status))
584
- continue;
585
- let obligationLine = `- [${obligation.status}] ${obligation.origin.friendId}/${obligation.origin.channel}/${obligation.origin.key}: ${obligation.content}${formatObligationSurface(obligation)}`;
586
- if (obligation.latestNote?.trim()) {
587
- obligationLine += `\n latest: ${obligation.latestNote.trim()}`;
580
+ const openCount = frame.pendingObligations.filter((ob) => (0, obligations_1.isOpenObligationStatus)(ob.status)).length;
581
+ if (options?.hasWakePacket && openCount > 0) {
582
+ // Wake packet carries enriched obligations (with resumeHint, stalenessClass, waitingOn).
583
+ // Avoid duplicating the full list — just anchor the count here.
584
+ lines.push("");
585
+ lines.push(`return obligations: ${openCount} active (canonical details in **Owed** section of wake packet)`);
586
+ }
587
+ else if (openCount > 0) {
588
+ lines.push("");
589
+ lines.push("## return obligations");
590
+ for (const obligation of frame.pendingObligations) {
591
+ if (!(0, obligations_1.isOpenObligationStatus)(obligation.status))
592
+ continue;
593
+ let obligationLine = `- [${obligation.status}] ${obligation.origin.friendId}/${obligation.origin.channel}/${obligation.origin.key}: ${obligation.content}${formatObligationSurface(obligation)}`;
594
+ if (obligation.latestNote?.trim()) {
595
+ obligationLine += `\n latest: ${obligation.latestNote.trim()}`;
596
+ }
597
+ lines.push(obligationLine);
588
598
  }
589
- lines.push(obligationLine);
590
599
  }
591
600
  }
592
601
  if (frame.innerReturnObligations && frame.innerReturnObligations.length > 0) {
@@ -0,0 +1,161 @@
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
+ }
@@ -0,0 +1,174 @@
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.createCare = createCare;
37
+ exports.readCares = readCares;
38
+ exports.readActiveCares = readActiveCares;
39
+ exports.updateCare = updateCare;
40
+ exports.resolveCare = resolveCare;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const runtime_1 = require("../nerves/runtime");
44
+ function caresDir(agentRoot) {
45
+ return path.join(agentRoot, "state", "cares");
46
+ }
47
+ function careFilePath(agentRoot, id) {
48
+ return path.join(caresDir(agentRoot), `${id}.json`);
49
+ }
50
+ function generateId() {
51
+ const timestamp = Date.now();
52
+ const random = Math.random().toString(36).slice(2, 10);
53
+ return `care-${timestamp}-${random}`;
54
+ }
55
+ function createCare(agentRoot, input) {
56
+ const now = new Date().toISOString();
57
+ const id = generateId();
58
+ const care = {
59
+ id,
60
+ label: input.label,
61
+ why: input.why,
62
+ kind: input.kind,
63
+ status: input.status,
64
+ salience: input.salience,
65
+ steward: input.steward,
66
+ relatedFriendIds: input.relatedFriendIds,
67
+ relatedAgentIds: input.relatedAgentIds,
68
+ relatedObligationIds: input.relatedObligationIds,
69
+ relatedEpisodeIds: input.relatedEpisodeIds,
70
+ currentRisk: input.currentRisk,
71
+ nextCheckAt: input.nextCheckAt,
72
+ createdAt: now,
73
+ updatedAt: now,
74
+ };
75
+ const dir = caresDir(agentRoot);
76
+ fs.mkdirSync(dir, { recursive: true });
77
+ fs.writeFileSync(careFilePath(agentRoot, id), JSON.stringify(care, null, 2), "utf-8");
78
+ (0, runtime_1.emitNervesEvent)({
79
+ component: "heart",
80
+ event: "heart.care_created",
81
+ message: `care created: ${input.label}`,
82
+ meta: { careId: id, status: input.status, salience: input.salience },
83
+ });
84
+ return care;
85
+ }
86
+ function readCares(agentRoot) {
87
+ const dir = caresDir(agentRoot);
88
+ if (!fs.existsSync(dir)) {
89
+ (0, runtime_1.emitNervesEvent)({
90
+ component: "heart",
91
+ event: "heart.cares_read",
92
+ message: "read cares: directory missing, returning empty",
93
+ meta: { count: 0 },
94
+ });
95
+ return [];
96
+ }
97
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
98
+ const cares = [];
99
+ for (const file of files) {
100
+ try {
101
+ const content = fs.readFileSync(path.join(dir, file), "utf-8");
102
+ const care = JSON.parse(content);
103
+ cares.push(care);
104
+ }
105
+ catch {
106
+ // Skip malformed JSON files gracefully
107
+ }
108
+ }
109
+ (0, runtime_1.emitNervesEvent)({
110
+ component: "heart",
111
+ event: "heart.cares_read",
112
+ message: `read ${cares.length} cares`,
113
+ meta: { count: cares.length },
114
+ });
115
+ return cares;
116
+ }
117
+ function readActiveCares(agentRoot) {
118
+ const all = readCares(agentRoot);
119
+ const active = all.filter((c) => c.status === "active" || c.status === "watching");
120
+ (0, runtime_1.emitNervesEvent)({
121
+ component: "heart",
122
+ event: "heart.active_cares_read",
123
+ message: `read ${active.length} active cares`,
124
+ meta: { count: active.length, total: all.length },
125
+ });
126
+ return active;
127
+ }
128
+ function readCareFile(agentRoot, id) {
129
+ const filePath = careFilePath(agentRoot, id);
130
+ if (!fs.existsSync(filePath)) {
131
+ throw new Error(`Care not found: ${id}`);
132
+ }
133
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
134
+ }
135
+ function writeCareFile(agentRoot, care) {
136
+ fs.writeFileSync(careFilePath(agentRoot, care.id), JSON.stringify(care, null, 2), "utf-8");
137
+ }
138
+ function updateCare(agentRoot, id, updates) {
139
+ const care = readCareFile(agentRoot, id);
140
+ const now = new Date().toISOString();
141
+ const updated = {
142
+ ...care,
143
+ ...updates,
144
+ id: care.id, // protect ID from overwrite
145
+ createdAt: care.createdAt, // protect createdAt
146
+ updatedAt: now,
147
+ };
148
+ writeCareFile(agentRoot, updated);
149
+ (0, runtime_1.emitNervesEvent)({
150
+ component: "heart",
151
+ event: "heart.care_updated",
152
+ message: `care updated: ${updated.label}`,
153
+ meta: { careId: id, updates: Object.keys(updates) },
154
+ });
155
+ return updated;
156
+ }
157
+ function resolveCare(agentRoot, id) {
158
+ const care = readCareFile(agentRoot, id);
159
+ const now = new Date().toISOString();
160
+ const resolved = {
161
+ ...care,
162
+ status: "resolved",
163
+ resolvedAt: now,
164
+ updatedAt: now,
165
+ };
166
+ writeCareFile(agentRoot, resolved);
167
+ (0, runtime_1.emitNervesEvent)({
168
+ component: "heart",
169
+ event: "heart.care_resolved",
170
+ message: `care resolved: ${resolved.label}`,
171
+ meta: { careId: id },
172
+ });
173
+ return resolved;
174
+ }
@@ -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;
@@ -47,6 +47,7 @@ exports.readLogView = readLogView;
47
47
  exports.readHabitView = readHabitView;
48
48
  exports.readNeedsMeView = readNeedsMeView;
49
49
  exports.readDeskPrefs = readDeskPrefs;
50
+ exports.readOutlookContinuity = readOutlookContinuity;
50
51
  const fs = __importStar(require("fs"));
51
52
  const path = __importStar(require("path"));
52
53
  const runtime_1 = require("../../nerves/runtime");
@@ -59,6 +60,9 @@ const agent_discovery_1 = require("./agent-discovery");
59
60
  const runtime_metadata_1 = require("./runtime-metadata");
60
61
  const thoughts_1 = require("./thoughts");
61
62
  const outlook_types_1 = require("./outlook-types");
63
+ const presence_1 = require("../presence");
64
+ const cares_1 = require("../cares");
65
+ const episodes_1 = require("../../mind/episodes");
62
66
  const LIVE_TASK_STATUSES = ["processing", "validating", "collaborating", "blocked"];
63
67
  const ACTIVE_CODING_STATUSES = new Set(["spawning", "running", "waiting_input", "stalled"]);
64
68
  const BLOCKED_CODING_STATUSES = new Set(["waiting_input", "stalled"]);
@@ -1289,3 +1293,39 @@ function parseCadenceMs(cadence) {
1289
1293
  /* v8 ignore next */
1290
1294
  return null;
1291
1295
  }
1296
+ // ---------------------------------------------------------------------------
1297
+ // Continuity — presence, cares, episodes for outlook surfaces
1298
+ // ---------------------------------------------------------------------------
1299
+ function readOutlookContinuity(agentRoot, agentName) {
1300
+ const self = (0, presence_1.readPresence)(agentRoot, agentName);
1301
+ const peers = (0, presence_1.readPeerPresence)(agentRoot);
1302
+ const cares = (0, cares_1.readActiveCares)(agentRoot);
1303
+ const episodes = (0, episodes_1.readRecentEpisodes)(agentRoot, { limit: 10 });
1304
+ (0, runtime_1.emitNervesEvent)({
1305
+ component: "heart",
1306
+ event: "heart.outlook_continuity_read",
1307
+ message: `outlook continuity: ${cares.length} cares, ${episodes.length} episodes`,
1308
+ meta: { careCount: cares.length, episodeCount: episodes.length, hasSelf: self != null, peerCount: peers.length },
1309
+ });
1310
+ return {
1311
+ presence: { self, peers },
1312
+ cares: {
1313
+ activeCount: cares.length,
1314
+ items: cares.map((c) => ({
1315
+ id: c.id,
1316
+ label: c.label,
1317
+ status: c.status,
1318
+ salience: c.salience,
1319
+ })),
1320
+ },
1321
+ episodes: {
1322
+ recentCount: episodes.length,
1323
+ items: episodes.map((ep) => ({
1324
+ id: ep.id,
1325
+ kind: ep.kind,
1326
+ summary: ep.summary,
1327
+ timestamp: ep.timestamp,
1328
+ })),
1329
+ },
1330
+ };
1331
+ }
@@ -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.",