@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
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildWakePacket = buildWakePacket;
4
+ exports.renderWakePacket = renderWakePacket;
5
+ exports.renderCompactWakePacket = renderCompactWakePacket;
6
+ const runtime_1 = require("../nerves/runtime");
7
+ const tempo_1 = require("./tempo");
8
+ function estimateTokens(text) {
9
+ return Math.ceil(text.length / 4);
10
+ }
11
+ // --- Section builders (pure derivation from authored data, no embellishment) ---
12
+ function buildPlotLine(episodes, tempo) {
13
+ if (episodes.length === 0)
14
+ return "";
15
+ const limit = tempo === "crisis" ? 3 : tempo === "brief" ? 2 : 5;
16
+ const selected = episodes.slice(0, limit);
17
+ const lines = selected.map((ep) => {
18
+ const parts = [`- ${ep.summary}`];
19
+ if (ep.whyItMattered && tempo !== "brief") {
20
+ parts[0] += ` (${ep.whyItMattered})`;
21
+ }
22
+ return parts[0];
23
+ });
24
+ return lines.join("\n");
25
+ }
26
+ function buildObligationsSection(obligations) {
27
+ if (obligations.length === 0)
28
+ return "";
29
+ return obligations
30
+ .map((ob) => {
31
+ const parts = [`- ${ob.content}`];
32
+ if (ob.meaning?.resumeHint) {
33
+ parts.push(` [hint: ${ob.meaning.resumeHint}]`);
34
+ }
35
+ if (ob.meaning?.stalenessClass && ob.meaning.stalenessClass !== "fresh") {
36
+ parts.push(` (${ob.meaning.stalenessClass})`);
37
+ }
38
+ if (ob.meaning?.waitingOn) {
39
+ parts.push(` waiting on ${ob.meaning.waitingOn.kind}: ${ob.meaning.waitingOn.target}`);
40
+ }
41
+ return parts.join("");
42
+ })
43
+ .join("\n");
44
+ }
45
+ function buildCaresSection(cares) {
46
+ if (cares.length === 0)
47
+ return "";
48
+ return cares
49
+ .map((c) => {
50
+ const parts = [`- ${c.label}`];
51
+ if (c.salience !== "low") {
52
+ parts.push(` [${c.salience}]`);
53
+ }
54
+ if (c.currentRisk) {
55
+ parts.push(` risk: ${c.currentRisk}`);
56
+ }
57
+ return parts.join("");
58
+ })
59
+ .join("\n");
60
+ }
61
+ function buildPresenceSection(peers) {
62
+ if (peers.length === 0)
63
+ return "";
64
+ return peers
65
+ .map((p) => `- ${p.agentName}: ${p.availability}/${p.lane}`)
66
+ .join("\n");
67
+ }
68
+ function buildResumeHint(view) {
69
+ // Compose from authored obligation resumeHints and top intentions
70
+ const hints = [];
71
+ for (const ob of view.activeObligations) {
72
+ if (ob.meaning?.resumeHint) {
73
+ hints.push(ob.meaning.resumeHint);
74
+ }
75
+ }
76
+ for (const intent of view.openIntentions.slice(0, 3)) {
77
+ hints.push(intent.content);
78
+ }
79
+ if (hints.length === 0) {
80
+ // Fall back to top obligation content
81
+ if (view.activeObligations.length > 0) {
82
+ hints.push(view.activeObligations[0].content);
83
+ }
84
+ }
85
+ if (hints.length === 0)
86
+ return "";
87
+ return hints.join("; ");
88
+ }
89
+ function buildWakePacket(view) {
90
+ const tempo = view.tempo;
91
+ const tokenBudget = tempo_1.TEMPO_BUDGETS[tempo];
92
+ const packet = {
93
+ plotLine: buildPlotLine(view.recentEpisodes, tempo),
94
+ obligations: buildObligationsSection(view.activeObligations),
95
+ cares: buildCaresSection(view.activeCares),
96
+ presence: buildPresenceSection(view.peerPresence),
97
+ resumeHint: buildResumeHint(view),
98
+ tempo,
99
+ tokenBudget,
100
+ assembledAt: new Date().toISOString(),
101
+ };
102
+ (0, runtime_1.emitNervesEvent)({
103
+ component: "heart",
104
+ event: "heart.wake_packet_built",
105
+ message: `wake packet built: tempo=${tempo}`,
106
+ meta: {
107
+ tempo,
108
+ plotLineTokens: estimateTokens(packet.plotLine),
109
+ obligationsTokens: estimateTokens(packet.obligations),
110
+ caresTokens: estimateTokens(packet.cares),
111
+ presenceTokens: estimateTokens(packet.presence),
112
+ resumeHintTokens: estimateTokens(packet.resumeHint),
113
+ },
114
+ });
115
+ return packet;
116
+ }
117
+ /**
118
+ * Renders a wake packet to prompt text, respecting token budget.
119
+ * Truncation priority (last truncated first):
120
+ * resumeHint (PROTECTED) > obligations > cares > plotLine > presence
121
+ * So presence is truncated first, then plotLine, then cares, then obligations.
122
+ * resumeHint is never truncated.
123
+ */
124
+ function renderWakePacket(packet) {
125
+ const budget = packet.tokenBudget;
126
+ // Assemble sections in priority order (highest priority first for budget allocation)
127
+ // Each section is { label, content, priority } where lower priority number = truncated first
128
+ const sections = [
129
+ { label: "resume", content: packet.resumeHint, priority: 5 },
130
+ { label: "obligations", content: packet.obligations, priority: 4 },
131
+ { label: "cares", content: packet.cares, priority: 3 },
132
+ { label: "plot", content: packet.plotLine, priority: 2 },
133
+ { label: "presence", content: packet.presence, priority: 1 },
134
+ ].filter((s) => s.content.length > 0);
135
+ if (sections.length === 0) {
136
+ (0, runtime_1.emitNervesEvent)({
137
+ component: "heart",
138
+ event: "heart.wake_packet_rendered",
139
+ message: "wake packet rendered: empty",
140
+ meta: { tokens: 0, tempo: packet.tempo },
141
+ });
142
+ return "";
143
+ }
144
+ // Build the rendered output, truncating lower-priority sections first
145
+ let rendered = formatSections(sections);
146
+ let tokens = estimateTokens(rendered);
147
+ // Truncate sections from lowest priority until we fit budget
148
+ const sortedByPriority = [...sections].sort((a, b) => a.priority - b.priority);
149
+ for (const section of sortedByPriority) {
150
+ if (tokens <= budget.max)
151
+ break;
152
+ // Skip resumeHint — it's PROTECTED
153
+ if (section.label === "resume")
154
+ continue;
155
+ // Remove this section entirely
156
+ const idx = sections.findIndex((s) => s.label === section.label);
157
+ sections.splice(idx, 1);
158
+ rendered = formatSections(sections);
159
+ tokens = estimateTokens(rendered);
160
+ }
161
+ // If still over budget after removing all non-protected sections, trim what's left
162
+ if (tokens > budget.max) {
163
+ const maxChars = budget.max * 4;
164
+ rendered = rendered.slice(0, maxChars);
165
+ }
166
+ (0, runtime_1.emitNervesEvent)({
167
+ component: "heart",
168
+ event: "heart.wake_packet_rendered",
169
+ message: `wake packet rendered: ${tokens} tokens`,
170
+ meta: { tokens, tempo: packet.tempo, sectionCount: sections.length },
171
+ });
172
+ return rendered;
173
+ }
174
+ function formatSections(sections) {
175
+ const parts = [];
176
+ for (const section of sections) {
177
+ switch (section.label) {
178
+ case "resume":
179
+ parts.push(`**Next:** ${section.content}`);
180
+ break;
181
+ case "obligations":
182
+ parts.push(`**Owed:**\n${section.content}`);
183
+ break;
184
+ case "cares":
185
+ parts.push(`**Cares:**\n${section.content}`);
186
+ break;
187
+ case "plot":
188
+ parts.push(`**Recent:**\n${section.content}`);
189
+ break;
190
+ case "presence":
191
+ parts.push(`**Peers:**\n${section.content}`);
192
+ break;
193
+ }
194
+ }
195
+ return parts.join("\n\n");
196
+ }
197
+ /**
198
+ * Ultra-compact version for coding context (max 200 tokens).
199
+ * Just resumeHint + top obligation + top care, single-line bullets.
200
+ */
201
+ function renderCompactWakePacket(packet) {
202
+ const parts = [];
203
+ if (packet.resumeHint) {
204
+ parts.push(`next: ${packet.resumeHint}`);
205
+ }
206
+ if (packet.obligations) {
207
+ // Just first line of obligations
208
+ const firstOb = packet.obligations.split("\n")[0];
209
+ parts.push(`owed: ${firstOb.replace(/^- /, "")}`);
210
+ }
211
+ if (packet.cares) {
212
+ const firstCare = packet.cares.split("\n")[0];
213
+ parts.push(`care: ${firstCare.replace(/^- /, "")}`);
214
+ }
215
+ const compact = parts.join(" | ");
216
+ // Hard cap at 200 tokens (800 chars)
217
+ const maxChars = 200 * 4;
218
+ const result = compact.length > maxChars ? compact.slice(0, maxChars) : compact;
219
+ (0, runtime_1.emitNervesEvent)({
220
+ component: "heart",
221
+ event: "heart.wake_packet_compact_rendered",
222
+ message: `compact wake packet: ${estimateTokens(result)} tokens`,
223
+ meta: { tokens: estimateTokens(result) },
224
+ });
225
+ return result;
226
+ }
@@ -0,0 +1,116 @@
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.emitEpisode = emitEpisode;
37
+ exports.readRecentEpisodes = readRecentEpisodes;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const runtime_1 = require("../nerves/runtime");
41
+ function episodesDir(agentRoot) {
42
+ return path.join(agentRoot, "state", "episodes");
43
+ }
44
+ function generateId() {
45
+ const timestamp = Date.now();
46
+ const random = Math.random().toString(36).slice(2, 10);
47
+ return `ep-${timestamp}-${random}`;
48
+ }
49
+ function emitEpisode(agentRoot, input) {
50
+ const now = new Date().toISOString();
51
+ const id = generateId();
52
+ const episode = {
53
+ id,
54
+ timestamp: now,
55
+ kind: input.kind,
56
+ summary: input.summary,
57
+ whyItMattered: input.whyItMattered,
58
+ relatedEntities: input.relatedEntities,
59
+ salience: input.salience,
60
+ ...(input.meta ? { meta: input.meta } : {}),
61
+ };
62
+ const dir = episodesDir(agentRoot);
63
+ fs.mkdirSync(dir, { recursive: true });
64
+ fs.writeFileSync(path.join(dir, `${id}.json`), JSON.stringify(episode, null, 2), "utf-8");
65
+ (0, runtime_1.emitNervesEvent)({
66
+ component: "mind",
67
+ event: "mind.episode_emitted",
68
+ message: `episode emitted: ${input.kind}`,
69
+ meta: {
70
+ episodeId: id,
71
+ kind: input.kind,
72
+ salience: input.salience,
73
+ },
74
+ });
75
+ return episode;
76
+ }
77
+ function readRecentEpisodes(agentRoot, options) {
78
+ const dir = episodesDir(agentRoot);
79
+ if (!fs.existsSync(dir)) {
80
+ (0, runtime_1.emitNervesEvent)({
81
+ component: "mind",
82
+ event: "mind.episodes_read",
83
+ message: "read episodes: directory missing, returning empty",
84
+ meta: { count: 0 },
85
+ });
86
+ return [];
87
+ }
88
+ const limit = options?.limit ?? 50;
89
+ const since = options?.since;
90
+ const kinds = options?.kinds;
91
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
92
+ const episodes = [];
93
+ for (const file of files) {
94
+ try {
95
+ const content = fs.readFileSync(path.join(dir, file), "utf-8");
96
+ const episode = JSON.parse(content);
97
+ if (since && episode.timestamp < since)
98
+ continue;
99
+ if (kinds && !kinds.includes(episode.kind))
100
+ continue;
101
+ episodes.push(episode);
102
+ }
103
+ catch {
104
+ // Skip malformed JSON files gracefully
105
+ }
106
+ }
107
+ episodes.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
108
+ const result = episodes.slice(0, limit);
109
+ (0, runtime_1.emitNervesEvent)({
110
+ component: "mind",
111
+ event: "mind.episodes_read",
112
+ message: `read ${result.length} recent episodes`,
113
+ meta: { count: result.length, total: episodes.length, limit },
114
+ });
115
+ return result;
116
+ }
@@ -39,6 +39,7 @@ exports.bodyMapSection = bodyMapSection;
39
39
  exports.mcpToolsSection = mcpToolsSection;
40
40
  exports.runtimeInfoSection = runtimeInfoSection;
41
41
  exports.toolRestrictionSection = toolRestrictionSection;
42
+ exports.wakePacketSection = wakePacketSection;
42
43
  exports.centerOfGravitySteeringSection = centerOfGravitySteeringSection;
43
44
  exports.commitmentsSection = commitmentsSection;
44
45
  exports.delegationHintSection = delegationHintSection;
@@ -481,10 +482,13 @@ function bridgeContextSection(options) {
481
482
  return "";
482
483
  return bridgeContext.startsWith("## ") ? bridgeContext : `## active bridge work\n${bridgeContext}`;
483
484
  }
485
+ function wakePacketSection(options) {
486
+ return options?.wakePacket ?? "";
487
+ }
484
488
  function activeWorkSection(options) {
485
489
  if (!options?.activeWorkFrame)
486
490
  return "";
487
- return (0, active_work_1.formatActiveWorkFrame)(options.activeWorkFrame);
491
+ return (0, active_work_1.formatActiveWorkFrame)(options.activeWorkFrame, { hasWakePacket: !!options?.wakePacket });
488
492
  }
489
493
  function liveWorldStateSection(options) {
490
494
  if (!options?.activeWorkFrame)
@@ -506,6 +510,10 @@ function familyCrossSessionTruthSection(context, options) {
506
510
  return "";
507
511
  if (context?.friend?.trustLevel !== "family")
508
512
  return "";
513
+ // When wake packet is present, compress to one line
514
+ if (options?.wakePacket) {
515
+ return "When family asks whole-self status, answer from the cross-session picture above.";
516
+ }
509
517
  return `## cross-session truth
510
518
  When family asks what I'm up to or how things are going, I answer from the live world-state across visible sessions and lanes, not just the current thread.
511
519
  When live state conflicts with older transcript history, live state wins.
@@ -1010,6 +1018,7 @@ async function buildSystem(channel = "cli", options, context) {
1010
1018
  ] : []),
1011
1019
  // Group 7: dynamic state for this turn
1012
1020
  "# dynamic state for this turn",
1021
+ wakePacketSection(options),
1013
1022
  liveWorldStateSection(options),
1014
1023
  pendingMessagesSection(options),
1015
1024
  activeWorkSection(options),
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.prepareCodingContextPack = prepareCodingContextPack;
37
+ exports.emitCodingEpisode = emitCodingEpisode;
37
38
  const crypto = __importStar(require("crypto"));
38
39
  const fs = __importStar(require("fs"));
39
40
  const path = __importStar(require("path"));
@@ -41,6 +42,7 @@ const child_process_1 = require("child_process");
41
42
  const active_work_1 = require("../../heart/active-work");
42
43
  const identity_1 = require("../../heart/identity");
43
44
  const runtime_1 = require("../../nerves/runtime");
45
+ const episodes_1 = require("../../mind/episodes");
44
46
  const skills_1 = require("../skills");
45
47
  const CONTEXT_FILENAMES = ["AGENTS.md", "CLAUDE.md"];
46
48
  function defaultRunCommand(command, args, cwd) {
@@ -173,7 +175,7 @@ function buildScopeContent(request, contextFiles, skills, agentName) {
173
175
  formatSkills(skills),
174
176
  ].join("\n");
175
177
  }
176
- function buildStateContent(request, contextKey, generatedAt, snapshot, existingSessions, agentName, activeWorkFrame) {
178
+ function buildStateContent(request, contextKey, generatedAt, snapshot, existingSessions, agentName, activeWorkFrame, wakePacket) {
177
179
  const gitSection = snapshot.available
178
180
  ? [
179
181
  `repoRoot: ${snapshot.repoRoot ?? "unknown"}`,
@@ -190,6 +192,7 @@ function buildStateContent(request, contextKey, generatedAt, snapshot, existingS
190
192
  `agent: ${request.parentAgent ?? agentName}`,
191
193
  formatOrigin(request),
192
194
  `obligationId: ${request.obligationId ?? "none"}`,
195
+ ...(wakePacket ? ["", "## Continuity", wakePacket] : []),
193
196
  "",
194
197
  "## Workspace Snapshot",
195
198
  gitSection,
@@ -226,7 +229,7 @@ function prepareCodingContextPack(input, deps = {}) {
226
229
  const snapshot = captureRepoSnapshot(input.request.workdir, runCommand);
227
230
  const generatedAt = nowIso();
228
231
  const scopeContent = buildScopeContent(input.request, contextFiles, skills, agentName);
229
- const stateContent = buildStateContent(input.request, contextKey, generatedAt, snapshot, existingSessions, agentName, input.activeWorkFrame);
232
+ const stateContent = buildStateContent(input.request, contextKey, generatedAt, snapshot, existingSessions, agentName, input.activeWorkFrame, input.wakePacket || undefined);
230
233
  mkdirSync(contextDir, { recursive: true });
231
234
  writeFileSync(scopeFile, `${scopeContent}\n`, "utf-8");
232
235
  writeFileSync(stateFile, `${stateContent}\n`, "utf-8");
@@ -252,3 +255,23 @@ function prepareCodingContextPack(input, deps = {}) {
252
255
  stateContent,
253
256
  };
254
257
  }
258
+ /**
259
+ * Emit a coding milestone episode when a session completes or blocks.
260
+ * This is the outbound leg of the coding round-trip: compact wake packet goes IN,
261
+ * episodes come BACK OUT.
262
+ */
263
+ function emitCodingEpisode(agentRoot, session, outcome) {
264
+ (0, episodes_1.emitEpisode)(agentRoot, {
265
+ kind: "coding_milestone",
266
+ summary: `coding session ${session.id} (${session.runner}): ${outcome}`,
267
+ whyItMattered: `coding lane ${session.status}`,
268
+ relatedEntities: [`coding:${session.id}`],
269
+ salience: session.status === "failed" ? "high" : "medium",
270
+ });
271
+ (0, runtime_1.emitNervesEvent)({
272
+ component: "repertoire",
273
+ event: "repertoire.coding_episode_emitted",
274
+ message: `coding episode emitted for ${session.id}`,
275
+ meta: { sessionId: session.id, outcome, status: session.status },
276
+ });
277
+ }