@ouro.bot/cli 0.1.0-alpha.20 → 0.1.0-alpha.200

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 (204) hide show
  1. package/README.md +187 -191
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
  4. package/changelog.json +1248 -0
  5. package/dist/heart/active-work.js +832 -0
  6. package/dist/heart/bridges/manager.js +358 -0
  7. package/dist/heart/bridges/state-machine.js +135 -0
  8. package/dist/heart/bridges/store.js +123 -0
  9. package/dist/heart/cares.js +179 -0
  10. package/dist/heart/commitments.js +111 -0
  11. package/dist/heart/config.js +54 -30
  12. package/dist/heart/core.js +620 -124
  13. package/dist/heart/cross-chat-delivery.js +146 -0
  14. package/dist/heart/daemon/agent-config-check.js +149 -0
  15. package/dist/heart/daemon/agent-discovery.js +81 -0
  16. package/dist/heart/daemon/agent-service.js +360 -0
  17. package/dist/heart/daemon/auth-flow.js +477 -0
  18. package/dist/heart/daemon/cadence.js +70 -0
  19. package/dist/heart/daemon/daemon-cli.js +2443 -217
  20. package/dist/heart/daemon/daemon-entry.js +182 -4
  21. package/dist/heart/daemon/daemon-health.js +136 -0
  22. package/dist/heart/daemon/daemon-runtime-sync.js +231 -0
  23. package/dist/heart/daemon/daemon-tombstone.js +123 -0
  24. package/dist/heart/daemon/daemon.js +460 -18
  25. package/dist/heart/daemon/habit-migration.js +181 -0
  26. package/dist/heart/daemon/habit-parser.js +119 -0
  27. package/dist/heart/daemon/habit-scheduler.js +369 -0
  28. package/dist/heart/daemon/hatch-flow.js +31 -112
  29. package/dist/heart/daemon/hatch-specialist.js +3 -3
  30. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  31. package/dist/heart/daemon/hooks/bundle-meta.js +206 -0
  32. package/dist/heart/daemon/inner-status.js +89 -0
  33. package/dist/heart/daemon/launchd.js +165 -0
  34. package/dist/heart/daemon/log-tailer.js +4 -3
  35. package/dist/heart/daemon/mcp-server.js +591 -0
  36. package/dist/heart/daemon/message-router.js +17 -8
  37. package/dist/heart/daemon/os-cron-deps.js +134 -0
  38. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  39. package/dist/heart/daemon/ouro-path-installer.js +78 -34
  40. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  41. package/dist/heart/daemon/outlook-http.js +439 -0
  42. package/dist/heart/daemon/outlook-read.js +1618 -0
  43. package/dist/heart/daemon/outlook-render.js +1032 -0
  44. package/dist/heart/daemon/outlook-types.js +7 -0
  45. package/dist/heart/daemon/outlook-view.js +194 -0
  46. package/dist/heart/daemon/process-manager.js +93 -0
  47. package/dist/heart/daemon/run-hooks.js +39 -0
  48. package/dist/heart/daemon/runtime-logging.js +58 -15
  49. package/dist/heart/daemon/runtime-metadata.js +101 -0
  50. package/dist/heart/daemon/runtime-mode.js +67 -0
  51. package/dist/heart/daemon/safe-mode.js +161 -0
  52. package/dist/heart/daemon/sense-manager.js +50 -2
  53. package/dist/heart/daemon/session-id-resolver.js +131 -0
  54. package/dist/heart/daemon/skill-management-installer.js +94 -0
  55. package/dist/heart/daemon/socket-client.js +202 -0
  56. package/dist/heart/daemon/specialist-prompt.js +10 -7
  57. package/dist/heart/daemon/specialist-tools.js +49 -3
  58. package/dist/heart/daemon/staged-restart.js +138 -0
  59. package/dist/heart/daemon/task-scheduler.js +3 -25
  60. package/dist/heart/daemon/thoughts.js +507 -0
  61. package/dist/heart/daemon/update-checker.js +111 -0
  62. package/dist/heart/daemon/update-hooks.js +138 -0
  63. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  64. package/dist/heart/delegation.js +62 -0
  65. package/dist/heart/identity.js +147 -55
  66. package/dist/heart/intentions.js +173 -0
  67. package/dist/heart/kicks.js +2 -20
  68. package/dist/heart/migrate-config.js +127 -0
  69. package/dist/heart/model-capabilities.js +48 -0
  70. package/dist/heart/obligations.js +225 -0
  71. package/dist/heart/presence.js +185 -0
  72. package/dist/heart/progress-story.js +42 -0
  73. package/dist/heart/provider-failover.js +88 -0
  74. package/dist/heart/provider-ping.js +178 -0
  75. package/dist/heart/providers/anthropic-token.js +163 -0
  76. package/dist/heart/providers/anthropic.js +187 -41
  77. package/dist/heart/providers/azure.js +116 -10
  78. package/dist/heart/providers/github-copilot.js +157 -0
  79. package/dist/heart/providers/minimax.js +33 -3
  80. package/dist/heart/providers/openai-codex.js +51 -16
  81. package/dist/heart/session-activity.js +172 -0
  82. package/dist/heart/session-recall.js +216 -0
  83. package/dist/heart/start-of-turn-packet.js +232 -0
  84. package/dist/heart/streaming.js +36 -27
  85. package/dist/heart/sync.js +217 -0
  86. package/dist/heart/target-resolution.js +127 -0
  87. package/dist/heart/tempo.js +93 -0
  88. package/dist/heart/temporal-view.js +41 -0
  89. package/dist/heart/tool-activity-callbacks.js +36 -0
  90. package/dist/heart/tool-description.js +135 -0
  91. package/dist/heart/tool-loop.js +200 -0
  92. package/dist/heart/turn-coordinator.js +28 -0
  93. package/dist/mind/associative-recall.js +122 -32
  94. package/dist/mind/bundle-manifest.js +19 -1
  95. package/dist/mind/context.js +125 -14
  96. package/dist/mind/{memory.js → diary.js} +69 -56
  97. package/dist/mind/episodes.js +119 -0
  98. package/dist/mind/file-state.js +179 -0
  99. package/dist/mind/first-impressions.js +16 -2
  100. package/dist/mind/friends/channel.js +56 -0
  101. package/dist/mind/friends/group-context.js +144 -0
  102. package/dist/mind/friends/store-file.js +36 -0
  103. package/dist/mind/friends/trust-explanation.js +74 -0
  104. package/dist/mind/friends/types.js +8 -0
  105. package/dist/mind/journal-index.js +161 -0
  106. package/dist/mind/obligation-steering.js +221 -0
  107. package/dist/mind/obligations.js +138 -0
  108. package/dist/mind/pending.js +76 -9
  109. package/dist/mind/phrases.js +1 -0
  110. package/dist/mind/prompt.js +785 -103
  111. package/dist/mind/scrutiny.js +173 -0
  112. package/dist/mind/token-estimate.js +8 -12
  113. package/dist/nerves/coverage/audit.js +1 -1
  114. package/dist/nerves/coverage/run-artifacts.js +1 -1
  115. package/dist/nerves/event-buffer.js +111 -0
  116. package/dist/nerves/index.js +63 -3
  117. package/dist/nerves/observation.js +20 -0
  118. package/dist/nerves/runtime.js +5 -1
  119. package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
  120. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  121. package/dist/outlook-ui/index.html +15 -0
  122. package/dist/repertoire/ado-semantic.js +11 -5
  123. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  124. package/dist/repertoire/coding/context-pack.js +330 -0
  125. package/dist/repertoire/coding/feedback.js +197 -30
  126. package/dist/repertoire/coding/manager.js +149 -2
  127. package/dist/repertoire/coding/spawner.js +55 -9
  128. package/dist/repertoire/coding/tools.js +170 -7
  129. package/dist/repertoire/guardrails.js +291 -0
  130. package/dist/repertoire/mcp-client.js +254 -0
  131. package/dist/repertoire/mcp-manager.js +251 -0
  132. package/dist/repertoire/shell-sessions.js +133 -0
  133. package/dist/repertoire/skills.js +14 -23
  134. package/dist/repertoire/tasks/board.js +43 -5
  135. package/dist/repertoire/tasks/fix.js +182 -0
  136. package/dist/repertoire/tasks/index.js +28 -10
  137. package/dist/repertoire/tasks/lifecycle.js +2 -2
  138. package/dist/repertoire/tasks/parser.js +3 -2
  139. package/dist/repertoire/tasks/scanner.js +194 -37
  140. package/dist/repertoire/tasks/transitions.js +11 -28
  141. package/dist/repertoire/tools-base.js +1345 -320
  142. package/dist/repertoire/tools-bluebubbles.js +94 -0
  143. package/dist/repertoire/tools-github.js +1 -6
  144. package/dist/repertoire/tools-teams.js +12 -59
  145. package/dist/repertoire/tools.js +318 -124
  146. package/dist/scripts/claude-code-hook.js +41 -0
  147. package/dist/scripts/claude-code-stop-hook.js +47 -0
  148. package/dist/senses/attention-queue.js +97 -0
  149. package/dist/senses/bluebubbles-client.js +102 -3
  150. package/dist/senses/bluebubbles-entry.js +2 -0
  151. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  152. package/dist/senses/bluebubbles-media.js +118 -23
  153. package/dist/senses/bluebubbles-model.js +12 -4
  154. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  155. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  156. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  157. package/dist/senses/bluebubbles.js +1008 -102
  158. package/dist/senses/cli/adapter.js +274 -0
  159. package/dist/senses/cli/ink-app.js +156 -0
  160. package/dist/senses/cli/inline-diff.js +64 -0
  161. package/dist/senses/cli/message-list.js +51 -0
  162. package/dist/senses/cli/ouro-tui.js +349 -0
  163. package/dist/senses/cli/spinner-imperative.js +135 -0
  164. package/dist/senses/cli/spinner.js +101 -0
  165. package/dist/senses/cli/status-line.js +60 -0
  166. package/dist/senses/cli/streaming-markdown.js +526 -0
  167. package/dist/senses/cli/tool-display.js +83 -0
  168. package/dist/senses/cli/tool-render.js +85 -0
  169. package/dist/senses/cli/tui-store.js +198 -0
  170. package/dist/senses/cli/virtual-list.js +35 -0
  171. package/dist/senses/cli-entry.js +1 -1
  172. package/dist/senses/cli-layout.js +187 -0
  173. package/dist/senses/cli.js +508 -243
  174. package/dist/senses/commands.js +65 -1
  175. package/dist/senses/continuity.js +94 -0
  176. package/dist/senses/habit-turn-message.js +108 -0
  177. package/dist/senses/inner-dialog-worker.js +112 -19
  178. package/dist/senses/inner-dialog.js +592 -84
  179. package/dist/senses/pipeline.js +693 -0
  180. package/dist/senses/shared-turn.js +196 -0
  181. package/dist/senses/surface-tool.js +95 -0
  182. package/dist/senses/teams.js +667 -120
  183. package/dist/senses/trust-gate.js +112 -2
  184. package/package.json +16 -6
  185. package/skills/configure-dev-tools.md +81 -0
  186. package/subagents/README.md +4 -70
  187. package/dist/heart/daemon/subagent-installer.js +0 -134
  188. package/dist/senses/debug-activity.js +0 -107
  189. package/subagents/work-doer.md +0 -235
  190. package/subagents/work-merger.md +0 -618
  191. package/subagents/work-planner.md +0 -382
  192. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  193. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  194. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  195. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  196. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  197. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  198. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  199. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  200. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  201. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  202. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  203. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  204. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
@@ -0,0 +1,216 @@
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.recallSession = recallSession;
37
+ exports.searchSessionTranscript = searchSessionTranscript;
38
+ const fs = __importStar(require("fs"));
39
+ const runtime_1 = require("../nerves/runtime");
40
+ function normalizeContent(content) {
41
+ if (typeof content === "string")
42
+ return content;
43
+ if (!Array.isArray(content))
44
+ return "";
45
+ return content
46
+ .map((part) => (part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part
47
+ ? String(part.text ?? "")
48
+ : ""))
49
+ .filter((text) => text.length > 0)
50
+ .join("");
51
+ }
52
+ function normalizeSessionMessages(messages) {
53
+ if (!Array.isArray(messages))
54
+ return [];
55
+ return messages
56
+ .map((message) => {
57
+ const record = message && typeof message === "object" ? message : {};
58
+ return {
59
+ role: typeof record.role === "string" ? record.role : "",
60
+ content: normalizeContent(record.content),
61
+ };
62
+ })
63
+ .filter((message) => message.role !== "system" && message.content.length > 0);
64
+ }
65
+ function buildSummaryInstruction(friendId, channel, trustLevel) {
66
+ if (friendId === "self" && channel === "inner") {
67
+ return "summarize this session transcript fully and transparently. this is my own inner dialog — include all details, decisions, and reasoning.";
68
+ }
69
+ return `summarize this session transcript. the person asking has trust level: ${trustLevel}. family=full transparency, friend=share work and general topics but protect other people's identities, acquaintance=very guarded minimal disclosure.`;
70
+ }
71
+ function clip(text, limit = 160) {
72
+ const compact = text.replace(/\s+/g, " ").trim();
73
+ return compact.length > limit ? compact.slice(0, limit - 1) + "…" : compact;
74
+ }
75
+ function buildSnapshot(summary, tailMessages) {
76
+ const lines = [`recent focus: ${clip(summary, 240)}`];
77
+ const latestUser = [...tailMessages].reverse().find((message) => message.role === "user")?.content;
78
+ const latestAssistant = [...tailMessages].reverse().find((message) => message.role === "assistant")?.content;
79
+ if (latestUser) {
80
+ lines.push(`latest user: ${clip(latestUser)}`);
81
+ }
82
+ if (latestAssistant) {
83
+ lines.push(`latest assistant: ${clip(latestAssistant)}`);
84
+ }
85
+ return lines.join("\n");
86
+ }
87
+ function buildSearchSnapshot(query, messages, includeLatestTurn = true) {
88
+ const lines = [`history query: "${clip(query, 120)}"`];
89
+ if (!includeLatestTurn) {
90
+ return lines.join("\n");
91
+ }
92
+ const latestUser = [...messages].reverse().find((message) => message.role === "user")?.content;
93
+ const latestAssistant = [...messages].reverse().find((message) => message.role === "assistant")?.content;
94
+ if (latestUser) {
95
+ lines.push(`latest user: ${clip(latestUser)}`);
96
+ }
97
+ if (latestAssistant) {
98
+ lines.push(`latest assistant: ${clip(latestAssistant)}`);
99
+ }
100
+ return lines.join("\n");
101
+ }
102
+ function buildSearchExcerpts(messages, query, maxMatches) {
103
+ const normalizedQuery = query.trim().toLowerCase();
104
+ if (!normalizedQuery)
105
+ return [];
106
+ const candidates = [];
107
+ let lastMatchIndex = -2;
108
+ for (let i = 0; i < messages.length; i++) {
109
+ if (!messages[i].content.toLowerCase().includes(normalizedQuery))
110
+ continue;
111
+ if (i <= lastMatchIndex + 1)
112
+ continue;
113
+ lastMatchIndex = i;
114
+ const start = Math.max(0, i - 1);
115
+ const end = Math.min(messages.length, i + 2);
116
+ const excerpt = messages
117
+ .slice(start, end)
118
+ .map((message) => `[${message.role}] ${clip(message.content, 200)}`)
119
+ .join("\n");
120
+ const score = messages
121
+ .slice(start, end)
122
+ .filter((message) => message.content.toLowerCase().includes(normalizedQuery))
123
+ .length;
124
+ candidates.push({ excerpt, score, index: i });
125
+ }
126
+ const seen = new Set();
127
+ return candidates
128
+ .sort((a, b) => b.score - a.score || a.index - b.index)
129
+ .filter((candidate) => {
130
+ if (seen.has(candidate.excerpt))
131
+ return false;
132
+ seen.add(candidate.excerpt);
133
+ return true;
134
+ })
135
+ .slice(0, maxMatches)
136
+ .map((candidate) => candidate.excerpt);
137
+ }
138
+ async function recallSession(options) {
139
+ (0, runtime_1.emitNervesEvent)({
140
+ component: "daemon",
141
+ event: "daemon.session_recall",
142
+ message: "recalling session transcript tail",
143
+ meta: {
144
+ friendId: options.friendId,
145
+ channel: options.channel,
146
+ key: options.key,
147
+ messageCount: options.messageCount,
148
+ },
149
+ });
150
+ let raw;
151
+ try {
152
+ raw = fs.readFileSync(options.sessionPath, "utf-8");
153
+ }
154
+ catch {
155
+ return { kind: "missing" };
156
+ }
157
+ const parsed = JSON.parse(raw);
158
+ const tailMessages = normalizeSessionMessages(parsed.messages).slice(-options.messageCount);
159
+ if (tailMessages.length === 0) {
160
+ return { kind: "empty" };
161
+ }
162
+ const transcript = tailMessages
163
+ .map((message) => `[${message.role}] ${message.content}`)
164
+ .join("\n");
165
+ const summary = options.summarize
166
+ ? await options.summarize(transcript, buildSummaryInstruction(options.friendId, options.channel, options.trustLevel ?? "family"))
167
+ : transcript;
168
+ return {
169
+ kind: "ok",
170
+ transcript,
171
+ summary,
172
+ snapshot: buildSnapshot(summary, tailMessages),
173
+ tailMessages,
174
+ };
175
+ }
176
+ async function searchSessionTranscript(options) {
177
+ (0, runtime_1.emitNervesEvent)({
178
+ component: "daemon",
179
+ event: "daemon.session_search",
180
+ message: "searching session transcript",
181
+ meta: {
182
+ friendId: options.friendId,
183
+ channel: options.channel,
184
+ key: options.key,
185
+ query: options.query,
186
+ maxMatches: options.maxMatches ?? 5,
187
+ },
188
+ });
189
+ let raw;
190
+ try {
191
+ raw = fs.readFileSync(options.sessionPath, "utf-8");
192
+ }
193
+ catch {
194
+ return { kind: "missing" };
195
+ }
196
+ const parsed = JSON.parse(raw);
197
+ const messages = normalizeSessionMessages(parsed.messages);
198
+ if (messages.length === 0) {
199
+ return { kind: "empty" };
200
+ }
201
+ const query = options.query.trim();
202
+ const matches = buildSearchExcerpts(messages, query, options.maxMatches ?? 5);
203
+ if (matches.length === 0) {
204
+ return {
205
+ kind: "no_match",
206
+ query,
207
+ snapshot: buildSearchSnapshot(query, messages),
208
+ };
209
+ }
210
+ return {
211
+ kind: "ok",
212
+ query,
213
+ snapshot: buildSearchSnapshot(query, messages, false),
214
+ matches,
215
+ };
216
+ }
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildStartOfTurnPacket = buildStartOfTurnPacket;
4
+ exports.renderStartOfTurnPacket = renderStartOfTurnPacket;
5
+ exports.renderCompactStartOfTurnPacket = renderCompactStartOfTurnPacket;
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, obligations) {
69
+ // Compose from authored obligation resumeHints and top intentions
70
+ const hints = [];
71
+ const effectiveObligations = obligations ?? view.activeObligations;
72
+ for (const ob of effectiveObligations) {
73
+ if (ob.meaning?.resumeHint) {
74
+ hints.push(ob.meaning.resumeHint);
75
+ }
76
+ }
77
+ for (const intent of view.openIntentions.slice(0, 3)) {
78
+ hints.push(intent.content);
79
+ }
80
+ if (hints.length === 0) {
81
+ // Fall back to top obligation content
82
+ if (effectiveObligations.length > 0) {
83
+ hints.push(effectiveObligations[0].content);
84
+ }
85
+ }
86
+ if (hints.length === 0)
87
+ return "";
88
+ return hints.join("; ");
89
+ }
90
+ function buildStartOfTurnPacket(view, opts) {
91
+ const tempo = view.tempo;
92
+ const tokenBudget = tempo_1.TEMPO_BUDGETS[tempo];
93
+ const effectiveObligations = opts?.canonicalObligations ? opts.canonicalObligations.all : view.activeObligations;
94
+ const packet = {
95
+ plotLine: buildPlotLine(view.recentEpisodes, tempo),
96
+ obligations: buildObligationsSection(effectiveObligations),
97
+ cares: buildCaresSection(view.activeCares),
98
+ presence: buildPresenceSection(view.peerPresence),
99
+ resumeHint: buildResumeHint(view, opts?.canonicalObligations ? effectiveObligations : undefined),
100
+ tempo,
101
+ tokenBudget,
102
+ assembledAt: new Date().toISOString(),
103
+ };
104
+ (0, runtime_1.emitNervesEvent)({
105
+ component: "heart",
106
+ event: "heart.start_of_turn_packet_built",
107
+ message: `start-of-turn packet built: tempo=${tempo}`,
108
+ meta: {
109
+ tempo,
110
+ plotLineTokens: estimateTokens(packet.plotLine),
111
+ obligationsTokens: estimateTokens(packet.obligations),
112
+ caresTokens: estimateTokens(packet.cares),
113
+ presenceTokens: estimateTokens(packet.presence),
114
+ resumeHintTokens: estimateTokens(packet.resumeHint),
115
+ },
116
+ });
117
+ return packet;
118
+ }
119
+ /**
120
+ * Renders a start-of-turn packet to prompt text, respecting token budget.
121
+ * Truncation priority (last truncated first):
122
+ * resumeHint (PROTECTED) > obligations > cares > plotLine > presence
123
+ * So presence is truncated first, then plotLine, then cares, then obligations.
124
+ * resumeHint is never truncated.
125
+ */
126
+ function renderStartOfTurnPacket(packet) {
127
+ const budget = packet.tokenBudget;
128
+ // Assemble sections in priority order (highest priority first for budget allocation)
129
+ // Each section is { label, content, priority } where lower priority number = truncated first
130
+ const sections = [
131
+ { label: "syncFailure", content: packet.syncFailure ?? "", priority: 6 },
132
+ { label: "resume", content: packet.resumeHint, priority: 5 },
133
+ { label: "obligations", content: packet.obligations, priority: 4 },
134
+ { label: "cares", content: packet.cares, priority: 3 },
135
+ { label: "plot", content: packet.plotLine, priority: 2 },
136
+ { label: "presence", content: packet.presence, priority: 1 },
137
+ ].filter((s) => s.content.length > 0);
138
+ if (sections.length === 0) {
139
+ (0, runtime_1.emitNervesEvent)({
140
+ component: "heart",
141
+ event: "heart.start_of_turn_packet_rendered",
142
+ message: "start-of-turn packet rendered: empty",
143
+ meta: { tokens: 0, tempo: packet.tempo },
144
+ });
145
+ return "";
146
+ }
147
+ // Build the rendered output, truncating lower-priority sections first
148
+ let rendered = formatSections(sections);
149
+ let tokens = estimateTokens(rendered);
150
+ // Truncate sections from lowest priority until we fit budget
151
+ const sortedByPriority = [...sections].sort((a, b) => a.priority - b.priority);
152
+ for (const section of sortedByPriority) {
153
+ if (tokens <= budget.max)
154
+ break;
155
+ // Skip resumeHint — it's PROTECTED
156
+ if (section.label === "resume")
157
+ continue;
158
+ // Remove this section entirely
159
+ const idx = sections.findIndex((s) => s.label === section.label);
160
+ sections.splice(idx, 1);
161
+ rendered = formatSections(sections);
162
+ tokens = estimateTokens(rendered);
163
+ }
164
+ // If still over budget after removing all non-protected sections, trim what's left
165
+ if (tokens > budget.max) {
166
+ const maxChars = budget.max * 4;
167
+ rendered = rendered.slice(0, maxChars);
168
+ }
169
+ (0, runtime_1.emitNervesEvent)({
170
+ component: "heart",
171
+ event: "heart.start_of_turn_packet_rendered",
172
+ message: `start-of-turn packet rendered: ${tokens} tokens`,
173
+ meta: { tokens, tempo: packet.tempo, sectionCount: sections.length },
174
+ });
175
+ return rendered;
176
+ }
177
+ function formatSections(sections) {
178
+ const parts = [];
179
+ for (const section of sections) {
180
+ switch (section.label) {
181
+ case "resume":
182
+ parts.push(`**Next:** ${section.content}`);
183
+ break;
184
+ case "obligations":
185
+ parts.push(`**Owed:**\n${section.content}`);
186
+ break;
187
+ case "cares":
188
+ parts.push(`**Cares:**\n${section.content}`);
189
+ break;
190
+ case "plot":
191
+ parts.push(`**Recent:**\n${section.content}`);
192
+ break;
193
+ case "presence":
194
+ parts.push(`**Peers:**\n${section.content}`);
195
+ break;
196
+ case "syncFailure":
197
+ parts.push(`**Sync warning:** ${section.content}`);
198
+ break;
199
+ }
200
+ }
201
+ return parts.join("\n\n");
202
+ }
203
+ /**
204
+ * Ultra-compact version for coding context (max 200 tokens).
205
+ * Just resumeHint + top obligation + top care, single-line bullets.
206
+ */
207
+ function renderCompactStartOfTurnPacket(packet) {
208
+ const parts = [];
209
+ if (packet.resumeHint) {
210
+ parts.push(`next: ${packet.resumeHint}`);
211
+ }
212
+ if (packet.obligations) {
213
+ // Just first line of obligations
214
+ const firstOb = packet.obligations.split("\n")[0];
215
+ parts.push(`owed: ${firstOb.replace(/^- /, "")}`);
216
+ }
217
+ if (packet.cares) {
218
+ const firstCare = packet.cares.split("\n")[0];
219
+ parts.push(`care: ${firstCare.replace(/^- /, "")}`);
220
+ }
221
+ const compact = parts.join(" | ");
222
+ // Hard cap at 200 tokens (800 chars)
223
+ const maxChars = 200 * 4;
224
+ const result = compact.length > maxChars ? compact.slice(0, maxChars) : compact;
225
+ (0, runtime_1.emitNervesEvent)({
226
+ component: "heart",
227
+ event: "heart.start_of_turn_packet_compact_rendered",
228
+ message: `compact start-of-turn packet: ${estimateTokens(result)} tokens`,
229
+ meta: { tokens: estimateTokens(result) },
230
+ });
231
+ return result;
232
+ }
@@ -1,16 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FinalAnswerStreamer = exports.FinalAnswerParser = void 0;
3
+ exports.SettleStreamer = exports.SettleParser = void 0;
4
4
  exports.toResponsesInput = toResponsesInput;
5
5
  exports.toResponsesTools = toResponsesTools;
6
6
  exports.streamChatCompletion = streamChatCompletion;
7
7
  exports.streamResponsesApi = streamResponsesApi;
8
8
  const runtime_1 = require("../nerves/runtime");
9
9
  // Character-level state machine that extracts the answer value from
10
- // `final_answer` tool call JSON arguments as they stream in.
10
+ // `settle` tool call JSON arguments as they stream in.
11
11
  // Scans for prefix `"answer":"` or `"answer": "` in the character stream,
12
12
  // then emits text handling JSON escapes, stopping at unescaped closing `"`.
13
- class FinalAnswerParser {
13
+ class SettleParser {
14
14
  // Possible prefixes to match (with and without space after colon)
15
15
  static PREFIXES = ['"answer":"', '"answer": "'];
16
16
  // Buffer of characters seen so far (pre-activation only)
@@ -29,7 +29,7 @@ class FinalAnswerParser {
29
29
  if (!this._active) {
30
30
  this.buf += ch;
31
31
  // Check if any prefix has been fully matched in the buffer
32
- for (const prefix of FinalAnswerParser.PREFIXES) {
32
+ for (const prefix of SettleParser.PREFIXES) {
33
33
  if (this.buf.endsWith(prefix)) {
34
34
  this._active = true;
35
35
  break;
@@ -76,21 +76,25 @@ class FinalAnswerParser {
76
76
  return out;
77
77
  }
78
78
  }
79
- exports.FinalAnswerParser = FinalAnswerParser;
80
- // Shared helper: wraps FinalAnswerParser with onClearText + onTextChunk wiring.
79
+ exports.SettleParser = SettleParser;
80
+ // Shared helper: wraps SettleParser with onClearText + onTextChunk wiring.
81
81
  // Used by all streaming providers (Chat Completions, Responses API, Anthropic)
82
- // so the eager-match streaming pattern lives in one place.
83
- class FinalAnswerStreamer {
84
- parser = new FinalAnswerParser();
82
+ // so the eager-match settle streaming pattern lives in one place.
83
+ class SettleStreamer {
84
+ parser = new SettleParser();
85
85
  _detected = false;
86
86
  callbacks;
87
- constructor(callbacks) {
87
+ enabled;
88
+ constructor(callbacks, enabled = true) {
88
89
  this.callbacks = callbacks;
90
+ this.enabled = enabled;
89
91
  }
90
92
  get detected() { return this._detected; }
91
93
  get streamed() { return this.parser.active; }
92
- /** Mark final_answer as detected. Calls onClearText on the callbacks. */
94
+ /** Mark settle as detected. Calls onClearText on the callbacks. */
93
95
  activate() {
96
+ if (!this.enabled)
97
+ return;
94
98
  if (this._detected)
95
99
  return;
96
100
  this._detected = true;
@@ -98,6 +102,8 @@ class FinalAnswerStreamer {
98
102
  }
99
103
  /** Feed an argument delta through the parser. Emits text via onTextChunk. */
100
104
  processDelta(delta) {
105
+ if (!this.enabled)
106
+ return;
101
107
  if (!this._detected)
102
108
  return;
103
109
  const text = this.parser.process(delta);
@@ -105,7 +111,7 @@ class FinalAnswerStreamer {
105
111
  this.callbacks.onTextChunk(text);
106
112
  }
107
113
  }
108
- exports.FinalAnswerStreamer = FinalAnswerStreamer;
114
+ exports.SettleStreamer = SettleStreamer;
109
115
  function toResponsesUserContent(content) {
110
116
  if (typeof content === "string") {
111
117
  return content;
@@ -185,7 +191,10 @@ function toResponsesInput(messages) {
185
191
  }
186
192
  }
187
193
  if (a.content) {
188
- input.push({ role: "assistant", content: typeof a.content === "string" ? a.content : "" });
194
+ const assistantItem = { role: "assistant", content: typeof a.content === "string" ? a.content : "" };
195
+ if (a.phase)
196
+ assistantItem.phase = a.phase;
197
+ input.push(assistantItem);
189
198
  }
190
199
  if (a.tool_calls) {
191
200
  for (const tc of a.tool_calls) {
@@ -224,7 +233,7 @@ function toResponsesTools(ccTools) {
224
233
  strict: false,
225
234
  }));
226
235
  }
227
- async function streamChatCompletion(client, createParams, callbacks, signal) {
236
+ async function streamChatCompletion(client, createParams, callbacks, signal, eagerSettleStreaming = true) {
228
237
  (0, runtime_1.emitNervesEvent)({
229
238
  component: "engine",
230
239
  event: "engine.stream_start",
@@ -238,7 +247,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
238
247
  let toolCalls = {};
239
248
  let streamStarted = false;
240
249
  let usage;
241
- const answerStreamer = new FinalAnswerStreamer(callbacks);
250
+ const answerStreamer = new SettleStreamer(callbacks, eagerSettleStreaming);
242
251
  // State machine for parsing inline <think> tags (MiniMax pattern)
243
252
  let contentBuf = "";
244
253
  let inThinkTag = false;
@@ -353,19 +362,19 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
353
362
  toolCalls[tc.index].id = tc.id;
354
363
  if (tc.function?.name) {
355
364
  toolCalls[tc.index].name = tc.function.name;
356
- // Detect final_answer tool call on first name delta.
365
+ // Detect settle tool call on first name delta.
357
366
  // Only activate streaming if this is the sole tool call (index 0
358
367
  // and no other indices seen). Mixed calls are rejected by core.ts.
359
- if (tc.function.name === "final_answer" && !answerStreamer.detected
368
+ if (tc.function.name === "settle" && !answerStreamer.detected
360
369
  && tc.index === 0 && Object.keys(toolCalls).length === 1) {
361
370
  answerStreamer.activate();
362
371
  }
363
372
  }
364
373
  if (tc.function?.arguments) {
365
374
  toolCalls[tc.index].arguments += tc.function.arguments;
366
- // Feed final_answer argument deltas to the parser for progressive
375
+ // Feed settle argument deltas to the parser for progressive
367
376
  // streaming, but only when it appears to be the sole tool call.
368
- if (answerStreamer.detected && toolCalls[tc.index].name === "final_answer"
377
+ if (answerStreamer.detected && toolCalls[tc.index].name === "settle"
369
378
  && Object.keys(toolCalls).length === 1) {
370
379
  answerStreamer.processDelta(tc.function.arguments);
371
380
  }
@@ -381,10 +390,10 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
381
390
  toolCalls: Object.values(toolCalls),
382
391
  outputItems: [],
383
392
  usage,
384
- finalAnswerStreamed: answerStreamer.streamed,
393
+ settleStreamed: answerStreamer.streamed,
385
394
  };
386
395
  }
387
- async function streamResponsesApi(client, createParams, callbacks, signal) {
396
+ async function streamResponsesApi(client, createParams, callbacks, signal, eagerSettleStreaming = true) {
388
397
  (0, runtime_1.emitNervesEvent)({
389
398
  component: "engine",
390
399
  event: "engine.stream_start",
@@ -399,7 +408,7 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
399
408
  const outputItems = [];
400
409
  let currentToolCall = null;
401
410
  let usage;
402
- const answerStreamer = new FinalAnswerStreamer(callbacks);
411
+ const answerStreamer = new SettleStreamer(callbacks, eagerSettleStreaming);
403
412
  let functionCallCount = 0;
404
413
  for await (const event of response) {
405
414
  if (signal?.aborted)
@@ -429,10 +438,10 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
429
438
  name: String(event.item.name),
430
439
  arguments: "",
431
440
  };
432
- // Detect final_answer function call -- clear any streamed noise.
441
+ // Detect settle function call -- clear any streamed noise.
433
442
  // Only activate when this is the first (and so far only) function call.
434
443
  // Mixed calls are rejected by core.ts; no need to stream their args.
435
- if (String(event.item.name) === "final_answer" && functionCallCount === 1) {
444
+ if (String(event.item.name) === "settle" && functionCallCount === 1) {
436
445
  answerStreamer.activate();
437
446
  }
438
447
  }
@@ -441,9 +450,9 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
441
450
  case "response.function_call_arguments.delta": {
442
451
  if (currentToolCall) {
443
452
  currentToolCall.arguments += event.delta;
444
- // Feed final_answer argument deltas to the parser for progressive
453
+ // Feed settle argument deltas to the parser for progressive
445
454
  // streaming, but only when it appears to be the sole function call.
446
- if (answerStreamer.detected && currentToolCall.name === "final_answer"
455
+ if (answerStreamer.detected && currentToolCall.name === "settle"
447
456
  && functionCallCount === 1) {
448
457
  answerStreamer.processDelta(String(event.delta));
449
458
  }
@@ -485,6 +494,6 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
485
494
  toolCalls,
486
495
  outputItems,
487
496
  usage,
488
- finalAnswerStreamed: answerStreamer.streamed,
497
+ settleStreamed: answerStreamer.streamed,
489
498
  };
490
499
  }