@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,196 @@
1
+ "use strict";
2
+ // Shared turn runner for non-interactive senses (MCP, future senses).
3
+ // Follows the CLI pattern: resolves context, constructs InboundTurnInput,
4
+ // calls handleInboundTurn, collects response text, detects ponder deferral.
5
+ //
6
+ // Does NOT refactor CLI — CLI is stable with 2280+ tests. This is a new
7
+ // code path for new senses that follows the same pipeline pattern.
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.runSenseTurn = runSenseTurn;
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ const core_1 = require("../heart/core");
46
+ const identity_1 = require("../heart/identity");
47
+ const config_1 = require("../heart/config");
48
+ const context_1 = require("../mind/context");
49
+ const prompt_1 = require("../mind/prompt");
50
+ const channel_1 = require("../mind/friends/channel");
51
+ const resolver_1 = require("../mind/friends/resolver");
52
+ const store_file_1 = require("../mind/friends/store-file");
53
+ const pending_1 = require("../mind/pending");
54
+ const context_2 = require("../mind/context");
55
+ const tokens_1 = require("../mind/friends/tokens");
56
+ const trust_gate_1 = require("./trust-gate");
57
+ const pipeline_1 = require("./pipeline");
58
+ const runtime_1 = require("../nerves/runtime");
59
+ const RESPONSE_CAP = 50_000;
60
+ /**
61
+ * Run a single agent turn through the inbound pipeline.
62
+ * Caller provides channel, session key, friend, and message;
63
+ * this function handles all pipeline wiring.
64
+ */
65
+ async function runSenseTurn(options) {
66
+ const { agentName, channel, sessionKey, friendId, userMessage } = options;
67
+ (0, runtime_1.emitNervesEvent)({
68
+ component: "senses",
69
+ event: "senses.shared_turn_start",
70
+ message: "shared turn runner starting",
71
+ meta: { agentName, channel, sessionKey, friendId },
72
+ });
73
+ // Resolve context
74
+ const agentRoot = (0, identity_1.getAgentRoot)(agentName);
75
+ const friendsPath = path.join(agentRoot, "friends");
76
+ const friendStore = new store_file_1.FileFriendStore(friendsPath);
77
+ const capabilities = (0, channel_1.getChannelCapabilities)(channel);
78
+ // If friendId looks like a UUID, look up the friend record directly and use its identity.
79
+ // Otherwise, resolve as a local user (same pattern as CLI sense).
80
+ const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-/.test(friendId);
81
+ let resolverParams;
82
+ if (isUuid) {
83
+ const existingFriend = await friendStore.get(friendId);
84
+ if (existingFriend) {
85
+ // Use the friend's first external ID for resolver context
86
+ const ext = existingFriend.externalIds?.[0];
87
+ resolverParams = {
88
+ provider: (ext?.provider ?? "local"),
89
+ externalId: ext?.externalId ?? friendId,
90
+ displayName: existingFriend.name ?? friendId,
91
+ channel,
92
+ };
93
+ }
94
+ else {
95
+ resolverParams = { provider: "local", externalId: friendId, displayName: friendId, channel };
96
+ }
97
+ }
98
+ else {
99
+ // Treat as local user identity (username@hostname pattern)
100
+ const username = os.userInfo().username;
101
+ const hostname = os.hostname();
102
+ resolverParams = { provider: "local", externalId: `${username}@${hostname}`, displayName: username, channel };
103
+ }
104
+ const resolver = new resolver_1.FriendResolver(friendStore, resolverParams);
105
+ // Session path and loading
106
+ const sessPath = (0, config_1.sessionPath)(friendId, channel, sessionKey);
107
+ const existing = (0, context_1.loadSession)(sessPath);
108
+ let sessionState = existing?.state;
109
+ const sessionMessages = existing?.messages && existing.messages.length > 0
110
+ ? existing.messages
111
+ : [{ role: "system", content: await (0, prompt_1.buildSystem)(channel, {}, undefined) }];
112
+ // Pending dir
113
+ const pendingDir = (0, pending_1.getPendingDir)(agentName, friendId, channel, sessionKey);
114
+ // Accumulate response text via callbacks
115
+ let responseText = "";
116
+ /* v8 ignore start — no-op callback stubs; only onTextChunk does real work (covered via mock) */
117
+ const callbacks = {
118
+ onModelStart: () => { },
119
+ onModelStreamStart: () => { },
120
+ onTextChunk: (chunk) => { responseText += chunk; },
121
+ onReasoningChunk: () => { },
122
+ onToolStart: () => { },
123
+ onToolEnd: () => { },
124
+ onError: () => { },
125
+ };
126
+ /* v8 ignore stop */
127
+ // Run the pipeline
128
+ const result = await (0, pipeline_1.handleInboundTurn)({
129
+ channel,
130
+ sessionKey,
131
+ capabilities,
132
+ messages: [{ role: "user", content: userMessage }],
133
+ callbacks,
134
+ /* v8 ignore start — delegation wrappers; pipeline integration tested separately */
135
+ friendResolver: { resolve: () => resolver.resolve() },
136
+ sessionLoader: {
137
+ loadOrCreate: () => Promise.resolve({
138
+ messages: sessionMessages,
139
+ sessionPath: sessPath,
140
+ state: sessionState,
141
+ }),
142
+ },
143
+ /* v8 ignore stop */
144
+ pendingDir,
145
+ friendStore,
146
+ provider: "local",
147
+ externalId: friendId,
148
+ enforceTrustGate: trust_gate_1.enforceTrustGate,
149
+ drainPending: pending_1.drainPending,
150
+ /* v8 ignore start — delegation wrappers; these just forward to the real functions */
151
+ runAgent: (msgs, cb, ch, sig, opts) => (0, core_1.runAgent)(msgs, cb, ch, sig, opts),
152
+ postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
153
+ (0, context_2.postTurn)(turnMessages, sessionPathArg, usage, hooks, state);
154
+ sessionState = state;
155
+ },
156
+ /* v8 ignore stop */
157
+ accumulateFriendTokens: tokens_1.accumulateFriendTokens,
158
+ });
159
+ // Detect ponder deferral
160
+ const ponderDeferred = result.turnOutcome === "pondered";
161
+ // Build response
162
+ let finalResponse;
163
+ if (ponderDeferred) {
164
+ finalResponse = "the agent is pondering this — check back shortly via check_response.";
165
+ }
166
+ else if (responseText.length === 0) {
167
+ // Agent settled but no text came through callbacks — check session transcript for the settle answer
168
+ const postTurnSession = (0, context_1.loadSession)(sessPath);
169
+ if (postTurnSession?.messages) {
170
+ const lastAssistant = [...postTurnSession.messages].reverse().find(m => m.role === "assistant");
171
+ if (lastAssistant && typeof lastAssistant.content === "string" && lastAssistant.content.trim()) {
172
+ finalResponse = lastAssistant.content;
173
+ }
174
+ else {
175
+ finalResponse = "(agent responded but response was empty)";
176
+ }
177
+ }
178
+ else {
179
+ finalResponse = "(agent responded but response was empty)";
180
+ }
181
+ }
182
+ else {
183
+ finalResponse = responseText;
184
+ }
185
+ // Cap response length
186
+ if (finalResponse.length > RESPONSE_CAP) {
187
+ finalResponse = finalResponse.slice(0, RESPONSE_CAP) + "\n\n[truncated — response exceeded 50K characters]";
188
+ }
189
+ (0, runtime_1.emitNervesEvent)({
190
+ component: "senses",
191
+ event: "senses.shared_turn_end",
192
+ message: "shared turn runner complete",
193
+ meta: { agentName, channel, sessionKey, friendId, ponderDeferred, responseLength: finalResponse.length },
194
+ });
195
+ return { response: finalResponse, ponderDeferred };
196
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.surfaceToolDef = void 0;
4
+ exports.handleSurface = handleSurface;
5
+ const attention_queue_1 = require("./attention-queue");
6
+ const runtime_1 = require("../nerves/runtime");
7
+ // ── Tool definition ──────────────────────────────────────────────
8
+ exports.surfaceToolDef = {
9
+ type: "function",
10
+ function: {
11
+ name: "surface",
12
+ description: "share a thought outward — deliver an answer, ask a follow-up, or surface progress to whoever needs to hear it. pass delegationId to address a held thought (see your attention queue above), or friendId for spontaneous outreach. does not end your turn.",
13
+ parameters: {
14
+ type: "object",
15
+ properties: {
16
+ content: {
17
+ type: "string",
18
+ description: "the message to deliver",
19
+ },
20
+ delegationId: {
21
+ type: "string",
22
+ description: "ID from your attention queue — addresses a specific held thought",
23
+ },
24
+ friendId: {
25
+ type: "string",
26
+ description: "friend to reach out to spontaneously (when not addressing a held thought)",
27
+ },
28
+ },
29
+ required: ["content"],
30
+ },
31
+ },
32
+ };
33
+ async function handleSurface(input) {
34
+ const { content, delegationId, friendId, queue, routeToFriend, advanceObligation, fulfillHeartObligation } = input;
35
+ // Resolve target friend
36
+ let targetFriendId;
37
+ let queueItem;
38
+ if (delegationId) {
39
+ // Look up in attention queue
40
+ const found = queue.find((item) => item.id === delegationId);
41
+ if (!found) {
42
+ return `no delegation found with id ${delegationId} — check your attention queue`;
43
+ }
44
+ targetFriendId = found.friendId;
45
+ queueItem = found;
46
+ }
47
+ else if (friendId) {
48
+ targetFriendId = friendId;
49
+ }
50
+ else {
51
+ return "specify who this thought is for — use delegationId to address a held thought, or friendId for spontaneous outreach";
52
+ }
53
+ // Route to target
54
+ const result = await routeToFriend(targetFriendId, content, queueItem);
55
+ (0, runtime_1.emitNervesEvent)({
56
+ event: "senses.surface_routed",
57
+ component: "senses",
58
+ message: `surface routed to ${targetFriendId}: ${result.status}`,
59
+ meta: {
60
+ targetFriendId,
61
+ status: result.status,
62
+ hasDelegationId: !!delegationId,
63
+ ...(result.detail ? { detail: result.detail } : {}),
64
+ },
65
+ });
66
+ // On successful routing with delegationId:
67
+ // 1. Advance obligation to "returned" (disk FIRST — crash safety)
68
+ // 2. Dequeue from in-memory queue (AFTER obligation advance)
69
+ if (delegationId && queueItem && result.status !== "failed") {
70
+ if (queueItem.obligationId) {
71
+ advanceObligation(queueItem.obligationId, {
72
+ status: "returned",
73
+ returnedAt: Date.now(),
74
+ returnTarget: "surface",
75
+ });
76
+ }
77
+ // Fulfill the heart obligation for this origin (separate from inner/mind obligation)
78
+ if (fulfillHeartObligation) {
79
+ try {
80
+ fulfillHeartObligation({
81
+ friendId: queueItem.friendId,
82
+ channel: queueItem.channel,
83
+ key: queueItem.key,
84
+ });
85
+ }
86
+ catch {
87
+ // swallowed — heart obligation fulfillment must never break surface delivery
88
+ }
89
+ }
90
+ (0, attention_queue_1.dequeueAttentionItem)(queue, delegationId);
91
+ }
92
+ // Return delivery status
93
+ const detail = result.detail ? ` — ${result.detail}` : "";
94
+ return `${result.status}${detail}`;
95
+ }