@ouro.bot/cli 0.1.0-alpha.19 → 0.1.0-alpha.190

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 (202) 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 +1280 -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 +174 -0
  10. package/dist/heart/commitments.js +111 -0
  11. package/dist/heart/config.js +40 -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-discovery.js +81 -0
  15. package/dist/heart/daemon/agent-service.js +360 -0
  16. package/dist/heart/daemon/auth-flow.js +477 -0
  17. package/dist/heart/daemon/cadence.js +70 -0
  18. package/dist/heart/daemon/daemon-cli.js +2470 -210
  19. package/dist/heart/daemon/daemon-entry.js +157 -4
  20. package/dist/heart/daemon/daemon-health.js +136 -0
  21. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  22. package/dist/heart/daemon/daemon-tombstone.js +123 -0
  23. package/dist/heart/daemon/daemon.js +449 -17
  24. package/dist/heart/daemon/habit-migration.js +181 -0
  25. package/dist/heart/daemon/habit-parser.js +119 -0
  26. package/dist/heart/daemon/habit-scheduler.js +369 -0
  27. package/dist/heart/daemon/hatch-flow.js +31 -112
  28. package/dist/heart/daemon/hatch-specialist.js +3 -3
  29. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  30. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  31. package/dist/heart/daemon/inner-status.js +89 -0
  32. package/dist/heart/daemon/launchd.js +165 -0
  33. package/dist/heart/daemon/log-tailer.js +4 -3
  34. package/dist/heart/daemon/mcp-server.js +591 -0
  35. package/dist/heart/daemon/message-router.js +17 -8
  36. package/dist/heart/daemon/os-cron-deps.js +134 -0
  37. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  38. package/dist/heart/daemon/ouro-path-installer.js +78 -34
  39. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  40. package/dist/heart/daemon/outlook-http.js +399 -0
  41. package/dist/heart/daemon/outlook-read.js +1331 -0
  42. package/dist/heart/daemon/outlook-render.js +1032 -0
  43. package/dist/heart/daemon/outlook-types.js +7 -0
  44. package/dist/heart/daemon/outlook-view.js +194 -0
  45. package/dist/heart/daemon/process-manager.js +75 -0
  46. package/dist/heart/daemon/run-hooks.js +39 -0
  47. package/dist/heart/daemon/runtime-logging.js +58 -15
  48. package/dist/heart/daemon/runtime-metadata.js +219 -0
  49. package/dist/heart/daemon/runtime-mode.js +67 -0
  50. package/dist/heart/daemon/safe-mode.js +161 -0
  51. package/dist/heart/daemon/sense-manager.js +50 -2
  52. package/dist/heart/daemon/session-id-resolver.js +131 -0
  53. package/dist/heart/daemon/skill-management-installer.js +94 -0
  54. package/dist/heart/daemon/socket-client.js +202 -0
  55. package/dist/heart/daemon/specialist-prompt.js +10 -7
  56. package/dist/heart/daemon/specialist-tools.js +49 -3
  57. package/dist/heart/daemon/staged-restart.js +114 -0
  58. package/dist/heart/daemon/task-scheduler.js +3 -25
  59. package/dist/heart/daemon/thoughts.js +507 -0
  60. package/dist/heart/daemon/update-checker.js +111 -0
  61. package/dist/heart/daemon/update-hooks.js +138 -0
  62. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  63. package/dist/heart/delegation.js +62 -0
  64. package/dist/heart/identity.js +136 -55
  65. package/dist/heart/intentions.js +168 -0
  66. package/dist/heart/kicks.js +2 -20
  67. package/dist/heart/migrate-config.js +127 -0
  68. package/dist/heart/model-capabilities.js +48 -0
  69. package/dist/heart/obligations.js +220 -0
  70. package/dist/heart/presence.js +185 -0
  71. package/dist/heart/progress-story.js +42 -0
  72. package/dist/heart/provider-failover.js +88 -0
  73. package/dist/heart/provider-ping.js +172 -0
  74. package/dist/heart/providers/anthropic-token.js +163 -0
  75. package/dist/heart/providers/anthropic.js +187 -41
  76. package/dist/heart/providers/azure.js +116 -10
  77. package/dist/heart/providers/github-copilot.js +157 -0
  78. package/dist/heart/providers/minimax.js +33 -3
  79. package/dist/heart/providers/openai-codex.js +51 -16
  80. package/dist/heart/session-activity.js +172 -0
  81. package/dist/heart/session-recall.js +216 -0
  82. package/dist/heart/streaming.js +36 -27
  83. package/dist/heart/target-resolution.js +127 -0
  84. package/dist/heart/tempo.js +93 -0
  85. package/dist/heart/temporal-view.js +41 -0
  86. package/dist/heart/tool-activity-callbacks.js +36 -0
  87. package/dist/heart/tool-description.js +135 -0
  88. package/dist/heart/tool-loop.js +200 -0
  89. package/dist/heart/turn-coordinator.js +28 -0
  90. package/dist/heart/wake-packet.js +226 -0
  91. package/dist/mind/associative-recall.js +122 -32
  92. package/dist/mind/bundle-manifest.js +14 -0
  93. package/dist/mind/context.js +125 -14
  94. package/dist/mind/{memory.js → diary.js} +70 -56
  95. package/dist/mind/episodes.js +116 -0
  96. package/dist/mind/file-state.js +179 -0
  97. package/dist/mind/first-impressions.js +16 -2
  98. package/dist/mind/friends/channel.js +56 -0
  99. package/dist/mind/friends/group-context.js +144 -0
  100. package/dist/mind/friends/store-file.js +36 -0
  101. package/dist/mind/friends/trust-explanation.js +74 -0
  102. package/dist/mind/friends/types.js +8 -0
  103. package/dist/mind/journal-index.js +161 -0
  104. package/dist/mind/obligation-steering.js +221 -0
  105. package/dist/mind/obligations.js +134 -0
  106. package/dist/mind/pending.js +76 -9
  107. package/dist/mind/phrases.js +1 -0
  108. package/dist/mind/prompt.js +779 -102
  109. package/dist/mind/scrutiny.js +173 -0
  110. package/dist/mind/token-estimate.js +8 -12
  111. package/dist/nerves/coverage/audit.js +1 -1
  112. package/dist/nerves/coverage/run-artifacts.js +1 -1
  113. package/dist/nerves/event-buffer.js +111 -0
  114. package/dist/nerves/index.js +63 -3
  115. package/dist/nerves/observation.js +20 -0
  116. package/dist/nerves/runtime.js +5 -1
  117. package/dist/outlook-ui/assets/index-DN6vVYPm.css +1 -0
  118. package/dist/outlook-ui/assets/index-M1l1tDTX.js +61 -0
  119. package/dist/outlook-ui/index.html +15 -0
  120. package/dist/repertoire/ado-semantic.js +11 -5
  121. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  122. package/dist/repertoire/coding/context-pack.js +330 -0
  123. package/dist/repertoire/coding/feedback.js +197 -30
  124. package/dist/repertoire/coding/manager.js +149 -2
  125. package/dist/repertoire/coding/spawner.js +55 -9
  126. package/dist/repertoire/coding/tools.js +170 -7
  127. package/dist/repertoire/guardrails.js +291 -0
  128. package/dist/repertoire/mcp-client.js +254 -0
  129. package/dist/repertoire/mcp-manager.js +198 -0
  130. package/dist/repertoire/shell-sessions.js +133 -0
  131. package/dist/repertoire/skills.js +14 -23
  132. package/dist/repertoire/tasks/board.js +43 -5
  133. package/dist/repertoire/tasks/fix.js +182 -0
  134. package/dist/repertoire/tasks/index.js +28 -10
  135. package/dist/repertoire/tasks/lifecycle.js +2 -2
  136. package/dist/repertoire/tasks/parser.js +3 -2
  137. package/dist/repertoire/tasks/scanner.js +194 -37
  138. package/dist/repertoire/tasks/transitions.js +11 -28
  139. package/dist/repertoire/tools-base.js +1345 -320
  140. package/dist/repertoire/tools-bluebubbles.js +94 -0
  141. package/dist/repertoire/tools-github.js +1 -6
  142. package/dist/repertoire/tools-teams.js +12 -59
  143. package/dist/repertoire/tools.js +318 -124
  144. package/dist/scripts/claude-code-hook.js +41 -0
  145. package/dist/scripts/claude-code-stop-hook.js +47 -0
  146. package/dist/senses/attention-queue.js +97 -0
  147. package/dist/senses/bluebubbles-client.js +102 -3
  148. package/dist/senses/bluebubbles-entry.js +2 -0
  149. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  150. package/dist/senses/bluebubbles-media.js +118 -23
  151. package/dist/senses/bluebubbles-model.js +12 -4
  152. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  153. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  154. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  155. package/dist/senses/bluebubbles.js +1008 -102
  156. package/dist/senses/cli/adapter.js +274 -0
  157. package/dist/senses/cli/ink-app.js +156 -0
  158. package/dist/senses/cli/inline-diff.js +64 -0
  159. package/dist/senses/cli/message-list.js +51 -0
  160. package/dist/senses/cli/ouro-tui.js +349 -0
  161. package/dist/senses/cli/spinner-imperative.js +135 -0
  162. package/dist/senses/cli/spinner.js +101 -0
  163. package/dist/senses/cli/status-line.js +60 -0
  164. package/dist/senses/cli/streaming-markdown.js +526 -0
  165. package/dist/senses/cli/tool-display.js +83 -0
  166. package/dist/senses/cli/tool-render.js +85 -0
  167. package/dist/senses/cli/tui-store.js +198 -0
  168. package/dist/senses/cli/virtual-list.js +35 -0
  169. package/dist/senses/cli-entry.js +1 -1
  170. package/dist/senses/cli-layout.js +187 -0
  171. package/dist/senses/cli.js +508 -243
  172. package/dist/senses/commands.js +65 -1
  173. package/dist/senses/continuity.js +94 -0
  174. package/dist/senses/habit-turn-message.js +108 -0
  175. package/dist/senses/inner-dialog-worker.js +112 -19
  176. package/dist/senses/inner-dialog.js +592 -84
  177. package/dist/senses/pipeline.js +633 -0
  178. package/dist/senses/shared-turn.js +196 -0
  179. package/dist/senses/surface-tool.js +95 -0
  180. package/dist/senses/teams.js +667 -120
  181. package/dist/senses/trust-gate.js +112 -2
  182. package/package.json +16 -6
  183. package/skills/configure-dev-tools.md +81 -0
  184. package/subagents/README.md +4 -70
  185. package/dist/heart/daemon/subagent-installer.js +0 -134
  186. package/dist/senses/debug-activity.js +0 -107
  187. package/subagents/work-doer.md +0 -235
  188. package/subagents/work-merger.md +0 -618
  189. package/subagents/work-planner.md +0 -382
  190. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  191. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  192. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  193. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  194. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  195. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  196. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  197. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  198. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  199. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  200. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  201. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  202. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createToolActivityCallbacks = createToolActivityCallbacks;
4
+ const tool_description_1 = require("./tool-description");
5
+ const runtime_1 = require("../nerves/runtime");
6
+ function createToolActivityCallbacks(options) {
7
+ (0, runtime_1.emitNervesEvent)({
8
+ component: "engine",
9
+ event: "engine.tool_activity_callbacks_created",
10
+ message: "tool activity callbacks initialized",
11
+ meta: {},
12
+ });
13
+ // Track the last description so we can reference it in END messages
14
+ let lastDescription = null;
15
+ return {
16
+ onToolStart(name, args) {
17
+ const description = (0, tool_description_1.humanReadableToolDescription)(name, args);
18
+ if (description === null)
19
+ return; // hidden tool (settle, rest, descend)
20
+ lastDescription = description;
21
+ options.onDescription(description);
22
+ },
23
+ onToolEnd(name, summary, success) {
24
+ const desc = lastDescription ?? name;
25
+ // Strip trailing "..." from description for the result line
26
+ const cleanDesc = desc.endsWith("...") ? desc.slice(0, -3) : desc;
27
+ if (!success) {
28
+ options.onFailure(`\u2717 ${cleanDesc} — ${summary}`);
29
+ return;
30
+ }
31
+ if (options.isDebug()) {
32
+ options.onResult(`\u2713 ${cleanDesc}`);
33
+ }
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.humanReadableToolDescription = humanReadableToolDescription;
4
+ const runtime_1 = require("../nerves/runtime");
5
+ function basename(filePath) {
6
+ const idx = filePath.lastIndexOf("/");
7
+ return idx >= 0 ? filePath.slice(idx + 1) : filePath;
8
+ }
9
+ function truncate(text, max) {
10
+ if (text.length <= max)
11
+ return text;
12
+ // Cut at last space before maxLen to avoid mid-word truncation
13
+ const slice = text.slice(0, max - 1);
14
+ const lastSpace = slice.lastIndexOf(" ");
15
+ if (lastSpace > 0)
16
+ return text.slice(0, lastSpace) + "\u2026";
17
+ // No space found (single long word) — hard truncate
18
+ return slice + "\u2026";
19
+ }
20
+ const TOOL_DESCRIPTIONS = {
21
+ // File operations
22
+ shell: (args) => {
23
+ const cmd = args.command;
24
+ if (!cmd)
25
+ return "running a command...";
26
+ return `running ${truncate(cmd, 50)}...`;
27
+ },
28
+ read_file: (args) => {
29
+ const fp = args.path || args.file_path;
30
+ if (!fp)
31
+ return "reading a file...";
32
+ return `reading ${basename(fp)}...`;
33
+ },
34
+ write_file: (args) => {
35
+ const fp = args.path || args.file_path;
36
+ if (!fp)
37
+ return "writing a file...";
38
+ return `writing ${basename(fp)}...`;
39
+ },
40
+ edit_file: (args) => {
41
+ const fp = args.path || args.file_path;
42
+ if (!fp)
43
+ return "editing a file...";
44
+ return `editing ${basename(fp)}...`;
45
+ },
46
+ glob: (args) => {
47
+ const p = args.pattern;
48
+ if (!p)
49
+ return "searching for files...";
50
+ return `searching for ${truncate(p, 40)}...`;
51
+ },
52
+ grep: (args) => {
53
+ const p = args.pattern;
54
+ if (!p)
55
+ return "searching code...";
56
+ return `searching code for '${truncate(p, 40)}'...`;
57
+ },
58
+ // Memory and knowledge
59
+ recall: (args) => {
60
+ const q = args.query;
61
+ if (!q)
62
+ return "searching memory...";
63
+ return `searching memory for '${truncate(q, 40)}'...`;
64
+ },
65
+ diary_write: (args) => {
66
+ const about = args.about;
67
+ return about ? `noting something about ${truncate(about, 30)}...` : "noting something down...";
68
+ },
69
+ save_friend_note: () => "making a note about you...",
70
+ get_friend_note: () => "checking my notes...",
71
+ load_skill: (args) => {
72
+ const name = args.name || args.skill;
73
+ return name ? `loading ${name} skill...` : "loading a skill...";
74
+ },
75
+ // Session and context
76
+ query_session: (args) => {
77
+ const mode = args.mode;
78
+ if (mode === "search")
79
+ return `searching session for '${truncate(args.query || "", 30)}'...`;
80
+ if (mode === "status")
81
+ return "checking inner dialog status...";
82
+ return "checking session history...";
83
+ },
84
+ web_search: (args) => {
85
+ const q = args.query;
86
+ return q ? `searching the web for '${truncate(q, 35)}'...` : "searching the web...";
87
+ },
88
+ coding_spawn: (args) => {
89
+ const runner = args.runner;
90
+ return runner ? `starting ${runner} coding session...` : "starting coding session...";
91
+ },
92
+ coding_status: () => "checking coding sessions...",
93
+ coding_tail: () => "reading coding output...",
94
+ coding_kill: () => "stopping coding session...",
95
+ bridge_manage: () => "managing conversation bridge...",
96
+ // Communication
97
+ send_message: (args) => {
98
+ const to = args.to;
99
+ return to ? `sending a message to ${to}...` : "sending a message...";
100
+ },
101
+ surface: () => "sharing a thought...",
102
+ // Metacognitive (agent's inner life)
103
+ ponder: (args) => {
104
+ const thought = args.thought;
105
+ return thought ? `thinking about ${truncate(thought, 40)}...` : "thinking deeper...";
106
+ },
107
+ observe: () => null,
108
+ claude: () => "reasoning...",
109
+ set_reasoning_effort: (args) => {
110
+ const level = args.level;
111
+ return level ? `setting thinking depth to ${level}...` : "adjusting thinking depth...";
112
+ },
113
+ // Hidden — these ARE the response or end-of-turn
114
+ settle: () => null,
115
+ rest: () => null,
116
+ descend: () => null,
117
+ };
118
+ function humanReadableToolDescription(name, args) {
119
+ (0, runtime_1.emitNervesEvent)({
120
+ component: "engine",
121
+ event: "engine.tool_description",
122
+ message: "generated human-readable tool description",
123
+ meta: { tool: name },
124
+ });
125
+ const builder = TOOL_DESCRIPTIONS[name];
126
+ if (builder)
127
+ return builder(args);
128
+ // MCP tools: mcp__server__toolname — extract last segment
129
+ if (name.startsWith("mcp__")) {
130
+ const parts = name.split("__");
131
+ const toolName = parts[parts.length - 1];
132
+ return `using ${toolName}...`;
133
+ }
134
+ return `using ${name}...`;
135
+ }
@@ -0,0 +1,200 @@
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.GLOBAL_CIRCUIT_BREAKER_LIMIT = exports.PING_PONG_WINDOW = exports.GENERIC_REPEAT_LIMIT = exports.POLL_NO_PROGRESS_LIMIT = exports.TOOL_LOOP_HISTORY_LIMIT = void 0;
37
+ exports.createToolLoopState = createToolLoopState;
38
+ exports.recordToolOutcome = recordToolOutcome;
39
+ exports.detectToolLoop = detectToolLoop;
40
+ const crypto = __importStar(require("crypto"));
41
+ const runtime_1 = require("../nerves/runtime");
42
+ exports.TOOL_LOOP_HISTORY_LIMIT = 30;
43
+ exports.POLL_NO_PROGRESS_LIMIT = 3;
44
+ exports.GENERIC_REPEAT_LIMIT = 4;
45
+ exports.PING_PONG_WINDOW = 6;
46
+ exports.GLOBAL_CIRCUIT_BREAKER_LIMIT = 24;
47
+ function stableStringify(value) {
48
+ if (value === null || typeof value !== "object") {
49
+ return JSON.stringify(value);
50
+ }
51
+ /* v8 ignore next -- stableStringify currently receives only objects/strings from normalized loop inputs @preserve */
52
+ if (Array.isArray(value)) {
53
+ return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
54
+ }
55
+ const record = value;
56
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
57
+ }
58
+ function digest(value) {
59
+ return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
60
+ }
61
+ function normalizeArgs(toolName, args) {
62
+ if (toolName === "query_active_work") {
63
+ return { toolName };
64
+ }
65
+ if (toolName === "coding_status" || toolName === "coding_tail") {
66
+ return {
67
+ toolName,
68
+ sessionId: args.sessionId ?? "",
69
+ };
70
+ }
71
+ if (toolName === "query_session") {
72
+ return {
73
+ toolName,
74
+ mode: args.mode ?? "",
75
+ friendId: args.friendId ?? "",
76
+ channel: args.channel ?? "",
77
+ key: args.key ?? "",
78
+ query: args.query ?? "",
79
+ };
80
+ }
81
+ return {
82
+ toolName,
83
+ args,
84
+ };
85
+ }
86
+ function normalizeOutcome(result, success) {
87
+ return {
88
+ success,
89
+ result: result.replace(/\s+/g, " ").trim(),
90
+ };
91
+ }
92
+ function isKnownPollTool(toolName, args) {
93
+ return toolName === "query_active_work"
94
+ || toolName === "coding_status"
95
+ || toolName === "coding_tail"
96
+ || (toolName === "query_session" && (args.mode ?? "").trim() === "status");
97
+ }
98
+ function emitDetection(detector, toolName, count, message, pairedToolName) {
99
+ (0, runtime_1.emitNervesEvent)({
100
+ level: "warn",
101
+ component: "engine",
102
+ event: "engine.tool_loop_detected",
103
+ message: "tool loop guard detected repeated non-progress work",
104
+ meta: {
105
+ detector,
106
+ toolName,
107
+ count,
108
+ pairedToolName: pairedToolName ?? null,
109
+ },
110
+ });
111
+ return {
112
+ stuck: true,
113
+ detector,
114
+ count,
115
+ message,
116
+ pairedToolName,
117
+ };
118
+ }
119
+ function countTrailingRepeats(history, toolName, callHash) {
120
+ let count = 0;
121
+ let outcomeHash;
122
+ for (let index = history.length - 1; index >= 0; index--) {
123
+ const record = history[index];
124
+ if (record.toolName !== toolName || record.callHash !== callHash) {
125
+ break;
126
+ }
127
+ if (!outcomeHash) {
128
+ outcomeHash = record.outcomeHash;
129
+ }
130
+ if (record.outcomeHash !== outcomeHash) {
131
+ break;
132
+ }
133
+ count += 1;
134
+ }
135
+ return { count, outcomeHash };
136
+ }
137
+ function detectPingPong(history, toolName, callHash) {
138
+ if (history.length < exports.PING_PONG_WINDOW) {
139
+ return { stuck: false };
140
+ }
141
+ const recent = history.slice(-exports.PING_PONG_WINDOW);
142
+ const first = recent[0];
143
+ const second = recent[1];
144
+ if (!first.isKnownPoll || !second.isKnownPoll) {
145
+ return { stuck: false };
146
+ }
147
+ if (first.toolName === second.toolName && first.callHash === second.callHash) {
148
+ return { stuck: false };
149
+ }
150
+ for (let index = 0; index < recent.length; index++) {
151
+ const expected = index % 2 === 0 ? first : second;
152
+ const actual = recent[index];
153
+ if (actual.toolName !== expected.toolName
154
+ || actual.callHash !== expected.callHash
155
+ || actual.outcomeHash !== expected.outcomeHash) {
156
+ return { stuck: false };
157
+ }
158
+ }
159
+ const matchesPair = (toolName === first.toolName && callHash === first.callHash)
160
+ || (toolName === second.toolName && callHash === second.callHash);
161
+ if (!matchesPair) {
162
+ return { stuck: false };
163
+ }
164
+ const pairedToolName = toolName === first.toolName ? second.toolName : first.toolName;
165
+ return emitDetection("ping_pong", toolName, exports.PING_PONG_WINDOW, `repeated ${first.toolName}/${second.toolName} polling is not changing. stop cycling between status checks and either act on the current state, change approach, or answer truthfully with what is known.`, pairedToolName);
166
+ }
167
+ function createToolLoopState() {
168
+ return { history: [] };
169
+ }
170
+ function recordToolOutcome(state, toolName, args, result, success) {
171
+ state.history.push({
172
+ toolName,
173
+ callHash: digest(normalizeArgs(toolName, args)),
174
+ outcomeHash: digest(normalizeOutcome(result, success)),
175
+ isKnownPoll: isKnownPollTool(toolName, args),
176
+ });
177
+ if (state.history.length > exports.TOOL_LOOP_HISTORY_LIMIT) {
178
+ state.history.splice(0, state.history.length - exports.TOOL_LOOP_HISTORY_LIMIT);
179
+ }
180
+ }
181
+ // Tools that must never be blocked by the circuit breaker.
182
+ // settle = end the turn, surface = deliver results outward.
183
+ // ponder = continue thinking (inner dialog) or hand off to inner dialog (outer).
184
+ // rest = end inner dialog turn (added in Unit 8b).
185
+ // Blocking these traps the agent: it can think all it wants but can never speak or stop.
186
+ const CIRCUIT_BREAKER_EXEMPT = new Set(["settle", "surface", "ponder", "rest"]);
187
+ function detectToolLoop(state, toolName, args) {
188
+ if (state.history.length >= exports.GLOBAL_CIRCUIT_BREAKER_LIMIT && !CIRCUIT_BREAKER_EXEMPT.has(toolName)) {
189
+ return emitDetection("global_circuit_breaker", toolName, state.history.length, `this turn has already made ${state.history.length} tool calls. stop thrashing, use the current evidence, and either change approach or answer truthfully with the best grounded status.`);
190
+ }
191
+ const callHash = digest(normalizeArgs(toolName, args));
192
+ const trailing = countTrailingRepeats(state.history, toolName, callHash);
193
+ if (isKnownPollTool(toolName, args) && trailing.count >= exports.POLL_NO_PROGRESS_LIMIT) {
194
+ return emitDetection("known_poll_no_progress", toolName, trailing.count, `repeated ${toolName} calls have returned the same state ${trailing.count} times. stop polling and either act on the current state, wait for a meaningful change, or answer truthfully with what is known.`);
195
+ }
196
+ if (trailing.count >= exports.GENERIC_REPEAT_LIMIT) {
197
+ return emitDetection("generic_repeat", toolName, trailing.count, `repeating ${toolName} with the same inputs is not producing new information. change approach, use the evidence already gathered, or answer truthfully with what is known.`);
198
+ }
199
+ return detectPingPong(state.history, toolName, callHash);
200
+ }
@@ -1,7 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createTurnCoordinator = createTurnCoordinator;
4
+ exports.withSharedTurnLock = withSharedTurnLock;
5
+ exports.tryBeginSharedTurn = tryBeginSharedTurn;
6
+ exports.endSharedTurn = endSharedTurn;
7
+ exports.isSharedTurnActive = isSharedTurnActive;
8
+ exports.enqueueSharedFollowUp = enqueueSharedFollowUp;
9
+ exports.drainSharedFollowUps = drainSharedFollowUps;
4
10
  const runtime_1 = require("../nerves/runtime");
11
+ function scopedKey(scope, key) {
12
+ return `${scope}:${key}`;
13
+ }
5
14
  function createTurnCoordinator() {
6
15
  const turnLocks = new Map();
7
16
  const activeTurns = new Set();
@@ -60,3 +69,22 @@ function createTurnCoordinator() {
60
69
  },
61
70
  };
62
71
  }
72
+ const _sharedTurnCoordinator = createTurnCoordinator();
73
+ function withSharedTurnLock(scope, key, fn) {
74
+ return _sharedTurnCoordinator.withTurnLock(scopedKey(scope, key), fn);
75
+ }
76
+ function tryBeginSharedTurn(scope, key) {
77
+ return _sharedTurnCoordinator.tryBeginTurn(scopedKey(scope, key));
78
+ }
79
+ function endSharedTurn(scope, key) {
80
+ _sharedTurnCoordinator.endTurn(scopedKey(scope, key));
81
+ }
82
+ function isSharedTurnActive(scope, key) {
83
+ return _sharedTurnCoordinator.isTurnActive(scopedKey(scope, key));
84
+ }
85
+ function enqueueSharedFollowUp(scope, key, followUp) {
86
+ _sharedTurnCoordinator.enqueueFollowUp(scopedKey(scope, key), followUp);
87
+ }
88
+ function drainSharedFollowUps(scope, key) {
89
+ return _sharedTurnCoordinator.drainFollowUps(scopedKey(scope, key));
90
+ }
@@ -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
+ }