@ouro.bot/cli 0.1.0-alpha.10 → 0.1.0-alpha.101

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 (128) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +3 -2
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/README.md +147 -205
  5. package/assets/ouroboros.png +0 -0
  6. package/changelog.json +603 -0
  7. package/dist/heart/active-work.js +275 -0
  8. package/dist/heart/bridges/manager.js +358 -0
  9. package/dist/heart/bridges/state-machine.js +135 -0
  10. package/dist/heart/bridges/store.js +123 -0
  11. package/dist/heart/commitments.js +109 -0
  12. package/dist/heart/config.js +68 -23
  13. package/dist/heart/core.js +474 -95
  14. package/dist/heart/cross-chat-delivery.js +146 -0
  15. package/dist/heart/daemon/agent-discovery.js +81 -0
  16. package/dist/heart/daemon/auth-flow.js +430 -0
  17. package/dist/heart/daemon/daemon-cli.js +1773 -281
  18. package/dist/heart/daemon/daemon-entry.js +55 -6
  19. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  20. package/dist/heart/daemon/daemon.js +216 -10
  21. package/dist/heart/daemon/hatch-animation.js +10 -3
  22. package/dist/heart/daemon/hatch-flow.js +7 -82
  23. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  24. package/dist/heart/daemon/launchd.js +159 -0
  25. package/dist/heart/daemon/log-tailer.js +4 -3
  26. package/dist/heart/daemon/message-router.js +17 -8
  27. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  28. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  29. package/dist/heart/daemon/ouro-entry.js +0 -0
  30. package/dist/heart/daemon/ouro-path-installer.js +260 -0
  31. package/dist/heart/daemon/ouro-uti.js +11 -2
  32. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  33. package/dist/heart/daemon/process-manager.js +14 -1
  34. package/dist/heart/daemon/run-hooks.js +37 -0
  35. package/dist/heart/daemon/runtime-logging.js +58 -15
  36. package/dist/heart/daemon/runtime-metadata.js +219 -0
  37. package/dist/heart/daemon/runtime-mode.js +67 -0
  38. package/dist/heart/daemon/sense-manager.js +307 -0
  39. package/dist/heart/daemon/skill-management-installer.js +94 -0
  40. package/dist/heart/daemon/socket-client.js +202 -0
  41. package/dist/heart/daemon/specialist-orchestrator.js +53 -85
  42. package/dist/heart/daemon/specialist-prompt.js +60 -17
  43. package/dist/heart/daemon/specialist-tools.js +211 -60
  44. package/dist/heart/daemon/staged-restart.js +114 -0
  45. package/dist/heart/daemon/thoughts.js +507 -0
  46. package/dist/heart/daemon/update-checker.js +111 -0
  47. package/dist/heart/daemon/update-hooks.js +138 -0
  48. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  49. package/dist/heart/delegation.js +62 -0
  50. package/dist/heart/identity.js +126 -21
  51. package/dist/heart/kicks.js +1 -19
  52. package/dist/heart/model-capabilities.js +48 -0
  53. package/dist/heart/obligations.js +191 -0
  54. package/dist/heart/progress-story.js +42 -0
  55. package/dist/heart/providers/anthropic.js +74 -9
  56. package/dist/heart/providers/azure.js +86 -7
  57. package/dist/heart/providers/github-copilot.js +149 -0
  58. package/dist/heart/providers/minimax.js +4 -0
  59. package/dist/heart/providers/openai-codex.js +12 -3
  60. package/dist/heart/safe-workspace.js +381 -0
  61. package/dist/heart/sense-truth.js +61 -0
  62. package/dist/heart/session-activity.js +169 -0
  63. package/dist/heart/session-recall.js +116 -0
  64. package/dist/heart/streaming.js +100 -22
  65. package/dist/heart/target-resolution.js +123 -0
  66. package/dist/heart/turn-coordinator.js +28 -0
  67. package/dist/mind/associative-recall.js +14 -2
  68. package/dist/mind/bundle-manifest.js +70 -0
  69. package/dist/mind/context.js +60 -14
  70. package/dist/mind/first-impressions.js +16 -2
  71. package/dist/mind/friends/channel.js +35 -0
  72. package/dist/mind/friends/group-context.js +144 -0
  73. package/dist/mind/friends/store-file.js +19 -0
  74. package/dist/mind/friends/trust-explanation.js +74 -0
  75. package/dist/mind/friends/types.js +8 -0
  76. package/dist/mind/memory.js +27 -26
  77. package/dist/mind/obligation-steering.js +31 -0
  78. package/dist/mind/pending.js +76 -9
  79. package/dist/mind/phrases.js +1 -0
  80. package/dist/mind/prompt.js +484 -77
  81. package/dist/mind/token-estimate.js +8 -12
  82. package/dist/nerves/cli-logging.js +15 -2
  83. package/dist/nerves/coverage/run-artifacts.js +1 -1
  84. package/dist/nerves/index.js +12 -0
  85. package/dist/repertoire/ado-client.js +4 -2
  86. package/dist/repertoire/coding/feedback.js +210 -0
  87. package/dist/repertoire/coding/index.js +4 -1
  88. package/dist/repertoire/coding/manager.js +69 -4
  89. package/dist/repertoire/coding/spawner.js +21 -3
  90. package/dist/repertoire/coding/tools.js +146 -4
  91. package/dist/repertoire/data/ado-endpoints.json +188 -0
  92. package/dist/repertoire/guardrails.js +290 -0
  93. package/dist/repertoire/mcp-client.js +254 -0
  94. package/dist/repertoire/mcp-manager.js +195 -0
  95. package/dist/repertoire/skills.js +3 -26
  96. package/dist/repertoire/tasks/board.js +12 -0
  97. package/dist/repertoire/tasks/index.js +23 -9
  98. package/dist/repertoire/tasks/transitions.js +1 -2
  99. package/dist/repertoire/tools-base.js +714 -249
  100. package/dist/repertoire/tools-bluebubbles.js +93 -0
  101. package/dist/repertoire/tools-teams.js +58 -25
  102. package/dist/repertoire/tools.js +106 -53
  103. package/dist/senses/bluebubbles-client.js +210 -5
  104. package/dist/senses/bluebubbles-entry.js +2 -0
  105. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  106. package/dist/senses/bluebubbles-media.js +339 -0
  107. package/dist/senses/bluebubbles-model.js +12 -4
  108. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  109. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  110. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  111. package/dist/senses/bluebubbles.js +894 -45
  112. package/dist/senses/cli-layout.js +187 -0
  113. package/dist/senses/cli.js +442 -170
  114. package/dist/senses/continuity.js +94 -0
  115. package/dist/senses/debug-activity.js +154 -0
  116. package/dist/senses/inner-dialog-worker.js +47 -18
  117. package/dist/senses/inner-dialog.js +385 -83
  118. package/dist/senses/pipeline.js +328 -0
  119. package/dist/senses/teams.js +573 -129
  120. package/dist/senses/trust-gate.js +112 -2
  121. package/package.json +14 -3
  122. package/subagents/README.md +4 -70
  123. package/dist/heart/daemon/specialist-session.js +0 -150
  124. package/dist/heart/daemon/subagent-installer.js +0 -125
  125. package/dist/inner-worker-entry.js +0 -4
  126. package/subagents/work-doer.md +0 -233
  127. package/subagents/work-merger.md +0 -624
  128. package/subagents/work-planner.md +0 -373
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.suggestBridgeForActiveWork = suggestBridgeForActiveWork;
4
+ exports.buildActiveWorkFrame = buildActiveWorkFrame;
5
+ exports.formatActiveWorkFrame = formatActiveWorkFrame;
6
+ const runtime_1 = require("../nerves/runtime");
7
+ const state_machine_1 = require("./bridges/state-machine");
8
+ const obligations_1 = require("./obligations");
9
+ const target_resolution_1 = require("./target-resolution");
10
+ function activityPriority(source) {
11
+ return source === "friend-facing" ? 0 : 1;
12
+ }
13
+ function compareActivity(a, b) {
14
+ const sourceDiff = activityPriority(a.activitySource) - activityPriority(b.activitySource);
15
+ if (sourceDiff !== 0)
16
+ return sourceDiff;
17
+ return b.lastActivityMs - a.lastActivityMs;
18
+ }
19
+ function summarizeLiveTasks(taskBoard) {
20
+ const live = [
21
+ ...taskBoard.byStatus.processing,
22
+ ...taskBoard.byStatus.validating,
23
+ ...taskBoard.byStatus.collaborating,
24
+ ];
25
+ return [...new Set(live)];
26
+ }
27
+ function isActiveBridge(bridge) {
28
+ return bridge.lifecycle === "active";
29
+ }
30
+ function hasSharedObligationPressure(input) {
31
+ return (typeof input.currentObligation === "string"
32
+ && input.currentObligation.trim().length > 0) || input.mustResolveBeforeHandoff
33
+ || summarizeLiveTasks(input.taskBoard).length > 0;
34
+ }
35
+ function formatCodingLaneLabel(session) {
36
+ return `${session.runner} ${session.id}`;
37
+ }
38
+ function describeCodingSessionScope(session, currentSession) {
39
+ if (!session.originSession)
40
+ return "";
41
+ if (currentSession
42
+ && session.originSession.friendId === currentSession.friendId
43
+ && session.originSession.channel === currentSession.channel
44
+ && session.originSession.key === currentSession.key) {
45
+ return " for this thread";
46
+ }
47
+ return ` for ${session.originSession.channel}/${session.originSession.key}`;
48
+ }
49
+ function activeObligationCount(obligations) {
50
+ return (obligations ?? []).filter((ob) => (0, obligations_1.isOpenObligationStatus)(ob.status)).length;
51
+ }
52
+ function formatObligationSurface(obligation) {
53
+ if (!obligation.currentSurface?.label)
54
+ return "";
55
+ switch (obligation.status) {
56
+ case "investigating":
57
+ return ` (working in ${obligation.currentSurface.label})`;
58
+ case "waiting_for_merge":
59
+ return ` (waiting at ${obligation.currentSurface.label})`;
60
+ case "updating_runtime":
61
+ return ` (updating via ${obligation.currentSurface.label})`;
62
+ default:
63
+ return ` (${obligation.currentSurface.label})`;
64
+ }
65
+ }
66
+ function suggestBridgeForActiveWork(input) {
67
+ const targetCandidates = (input.targetCandidates ?? [])
68
+ .filter((candidate) => {
69
+ if (candidate.delivery.mode === "blocked") {
70
+ return false;
71
+ }
72
+ if (candidate.activitySource !== "friend-facing" || candidate.channel === "inner") {
73
+ return false;
74
+ }
75
+ if (!input.currentSession) {
76
+ return true;
77
+ }
78
+ return !(candidate.friendId === input.currentSession.friendId
79
+ && candidate.channel === input.currentSession.channel
80
+ && candidate.key === input.currentSession.key);
81
+ })
82
+ .sort((a, b) => {
83
+ return b.lastActivityMs - a.lastActivityMs;
84
+ });
85
+ if (!hasSharedObligationPressure(input) || targetCandidates.length === 0) {
86
+ return null;
87
+ }
88
+ const targetSession = targetCandidates[0];
89
+ const activeBridge = input.bridges.find(isActiveBridge) ?? null;
90
+ if (activeBridge) {
91
+ const alreadyAttached = activeBridge.attachedSessions.some((session) => session.friendId === targetSession.friendId
92
+ && session.channel === targetSession.channel
93
+ && session.key === targetSession.key);
94
+ if (alreadyAttached) {
95
+ return null;
96
+ }
97
+ return {
98
+ kind: "attach-existing",
99
+ bridgeId: activeBridge.id,
100
+ targetSession,
101
+ reason: "shared-work-candidate",
102
+ };
103
+ }
104
+ return {
105
+ kind: "begin-new",
106
+ targetSession,
107
+ objectiveHint: input.currentObligation?.trim() || "keep this shared work aligned",
108
+ reason: "shared-work-candidate",
109
+ };
110
+ }
111
+ function formatSessionLabel(session) {
112
+ return `${session.channel}/${session.key}`;
113
+ }
114
+ function buildActiveWorkFrame(input) {
115
+ const friendSessions = input.currentSession
116
+ ? input.friendActivity
117
+ .filter((entry) => entry.friendId === input.currentSession?.friendId)
118
+ .sort(compareActivity)
119
+ : [];
120
+ const liveTaskNames = summarizeLiveTasks(input.taskBoard);
121
+ const activeBridgePresent = input.bridges.some(isActiveBridge);
122
+ const openObligations = activeObligationCount(input.pendingObligations);
123
+ const liveCodingSessions = input.codingSessions ?? [];
124
+ const centerOfGravity = activeBridgePresent
125
+ ? "shared-work"
126
+ : (input.inner.status === "running" || input.inner.hasPending || input.mustResolveBeforeHandoff || openObligations > 0 || liveCodingSessions.length > 0)
127
+ ? "inward-work"
128
+ : "local-turn";
129
+ const frame = {
130
+ currentSession: input.currentSession ?? null,
131
+ currentObligation: input.currentObligation?.trim() || null,
132
+ mustResolveBeforeHandoff: input.mustResolveBeforeHandoff,
133
+ centerOfGravity,
134
+ inner: input.inner,
135
+ bridges: input.bridges,
136
+ taskPressure: {
137
+ compactBoard: input.taskBoard.compact,
138
+ liveTaskNames,
139
+ activeBridges: input.taskBoard.activeBridges,
140
+ },
141
+ friendActivity: {
142
+ freshestForCurrentFriend: friendSessions[0] ?? null,
143
+ otherLiveSessionsForCurrentFriend: friendSessions,
144
+ },
145
+ codingSessions: liveCodingSessions,
146
+ pendingObligations: input.pendingObligations ?? [],
147
+ targetCandidates: input.targetCandidates ?? [],
148
+ bridgeSuggestion: suggestBridgeForActiveWork({
149
+ currentSession: input.currentSession,
150
+ currentObligation: input.currentObligation,
151
+ mustResolveBeforeHandoff: input.mustResolveBeforeHandoff,
152
+ bridges: input.bridges,
153
+ taskBoard: input.taskBoard,
154
+ targetCandidates: input.targetCandidates,
155
+ }),
156
+ };
157
+ (0, runtime_1.emitNervesEvent)({
158
+ component: "engine",
159
+ event: "engine.active_work_build",
160
+ message: "built shared active-work frame",
161
+ meta: {
162
+ centerOfGravity: frame.centerOfGravity,
163
+ friendId: frame.currentSession?.friendId ?? null,
164
+ bridges: frame.bridges.length,
165
+ liveTasks: frame.taskPressure.liveTaskNames.length,
166
+ liveSessions: frame.friendActivity.otherLiveSessionsForCurrentFriend.length,
167
+ codingSessions: frame.codingSessions.length,
168
+ pendingObligations: openObligations,
169
+ hasBridgeSuggestion: frame.bridgeSuggestion !== null,
170
+ },
171
+ });
172
+ return frame;
173
+ }
174
+ function formatActiveWorkFrame(frame) {
175
+ const lines = ["## what i'm holding"];
176
+ // Session line
177
+ if (frame.currentSession) {
178
+ let sessionLine = `i'm in a conversation on ${formatSessionLabel(frame.currentSession)}.`;
179
+ if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0) {
180
+ sessionLine += ` i told them i'd ${frame.currentObligation.trim()}.`;
181
+ }
182
+ else if (frame.mustResolveBeforeHandoff) {
183
+ sessionLine += " i need to finish what i started here before moving on.";
184
+ }
185
+ lines.push("");
186
+ lines.push(sessionLine);
187
+ }
188
+ else {
189
+ lines.push("");
190
+ lines.push("i'm not in a conversation right now.");
191
+ }
192
+ // Inner status block
193
+ const job = frame.inner?.job;
194
+ if (job) {
195
+ if (job.status === "queued") {
196
+ let queuedLine = "i have a thought queued up for private attention.";
197
+ if (frame.inner?.contentSnippet) {
198
+ queuedLine += `\nit's about: "${frame.inner.contentSnippet}"`;
199
+ }
200
+ lines.push("");
201
+ lines.push(queuedLine);
202
+ }
203
+ else if (job.status === "running") {
204
+ const originName = job.origin?.friendName ?? job.origin?.friendId;
205
+ let runningLine = originName
206
+ ? `i'm thinking through something privately right now. ${originName} asked about something and i wanted to give it real thought.`
207
+ : "i'm thinking through something privately right now.";
208
+ if (frame.inner?.obligationPending) {
209
+ runningLine += "\ni still owe them an answer.";
210
+ }
211
+ lines.push("");
212
+ lines.push(runningLine);
213
+ }
214
+ else if (job.status === "surfaced") {
215
+ let surfacedLine = "i finished thinking about something privately. i should bring my answer back.";
216
+ if (job.surfacedResult) {
217
+ const truncated = job.surfacedResult.length > 120 ? job.surfacedResult.slice(0, 117) + "..." : job.surfacedResult;
218
+ surfacedLine += `\nwhat i came to: ${truncated}`;
219
+ }
220
+ lines.push("");
221
+ lines.push(surfacedLine);
222
+ }
223
+ // idle, returned, abandoned: omitted
224
+ }
225
+ if ((frame.codingSessions ?? []).length > 0) {
226
+ lines.push("");
227
+ lines.push("## live coding work");
228
+ for (const session of frame.codingSessions) {
229
+ lines.push(`- [${session.status}] ${formatCodingLaneLabel(session)}${describeCodingSessionScope(session, frame.currentSession)}`);
230
+ }
231
+ }
232
+ // Task pressure
233
+ if ((frame.taskPressure?.liveTaskNames ?? []).length > 0) {
234
+ lines.push("");
235
+ lines.push(`i'm also tracking: ${frame.taskPressure.liveTaskNames.join(", ")}.`);
236
+ }
237
+ // Bridges
238
+ if ((frame.bridges ?? []).length > 0) {
239
+ const bridgeLabels = frame.bridges.map((bridge) => `${bridge.id} [${(0, state_machine_1.bridgeStateLabel)(bridge)}]`);
240
+ lines.push("");
241
+ lines.push(`i have shared work spanning sessions: ${bridgeLabels.join(", ")}.`);
242
+ }
243
+ // Target candidates (keep factual format)
244
+ const targetCandidatesBlock = frame.targetCandidates && frame.targetCandidates.length > 0
245
+ ? (0, target_resolution_1.formatTargetSessionCandidates)(frame.targetCandidates)
246
+ : "";
247
+ if (targetCandidatesBlock) {
248
+ lines.push("");
249
+ lines.push(targetCandidatesBlock);
250
+ }
251
+ if ((frame.pendingObligations ?? []).length > 0) {
252
+ lines.push("");
253
+ lines.push("## return obligations");
254
+ for (const obligation of frame.pendingObligations) {
255
+ if (!(0, obligations_1.isOpenObligationStatus)(obligation.status))
256
+ continue;
257
+ let obligationLine = `- [${obligation.status}] ${obligation.origin.friendId}/${obligation.origin.channel}/${obligation.origin.key}: ${obligation.content}${formatObligationSurface(obligation)}`;
258
+ if (obligation.latestNote?.trim()) {
259
+ obligationLine += `\n latest: ${obligation.latestNote.trim()}`;
260
+ }
261
+ lines.push(obligationLine);
262
+ }
263
+ }
264
+ // Bridge suggestion
265
+ if (frame.bridgeSuggestion) {
266
+ lines.push("");
267
+ if (frame.bridgeSuggestion.kind === "begin-new") {
268
+ lines.push(`this work touches my conversation on ${formatSessionLabel(frame.bridgeSuggestion.targetSession)} too -- i should connect these threads.`);
269
+ }
270
+ else {
271
+ lines.push(`this work relates to bridge ${frame.bridgeSuggestion.bridgeId} -- i should connect ${formatSessionLabel(frame.bridgeSuggestion.targetSession)} to it.`);
272
+ }
273
+ }
274
+ return lines.join("\n");
275
+ }
@@ -0,0 +1,358 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatBridgeStatus = formatBridgeStatus;
4
+ exports.formatBridgeContext = formatBridgeContext;
5
+ exports.createBridgeManager = createBridgeManager;
6
+ const runtime_1 = require("../../nerves/runtime");
7
+ const state_machine_1 = require("./state-machine");
8
+ const store_1 = require("./store");
9
+ const turn_coordinator_1 = require("../turn-coordinator");
10
+ const tasks_1 = require("../../repertoire/tasks");
11
+ function defaultIdFactory() {
12
+ return `bridge-${Date.now().toString(36)}`;
13
+ }
14
+ function sessionIdentityKey(session) {
15
+ return `${session.friendId}/${session.channel}/${session.key}`;
16
+ }
17
+ function assertBridgeMutable(bridge, action) {
18
+ if (bridge.lifecycle === "completed" || bridge.lifecycle === "cancelled") {
19
+ throw new Error(`cannot ${action} a terminal bridge`);
20
+ }
21
+ }
22
+ function defaultTaskBody(bridge) {
23
+ const lines = [
24
+ "## scope",
25
+ bridge.objective,
26
+ "",
27
+ "## bridge",
28
+ `id: ${bridge.id}`,
29
+ ];
30
+ if (bridge.attachedSessions.length > 0) {
31
+ lines.push("sessions:");
32
+ for (const session of bridge.attachedSessions) {
33
+ lines.push(`- ${sessionIdentityKey(session)}`);
34
+ }
35
+ }
36
+ return lines.join("\n");
37
+ }
38
+ function formatBridgeStatus(bridge) {
39
+ const summary = typeof bridge.summary === "string" ? bridge.summary.trim() : "";
40
+ const lines = [
41
+ `bridge: ${bridge.id}`,
42
+ `objective: ${bridge.objective}`,
43
+ `state: ${(0, state_machine_1.bridgeStateLabel)(bridge)}`,
44
+ `sessions: ${bridge.attachedSessions.length}`,
45
+ `task: ${bridge.task?.taskName ?? "none"}`,
46
+ ];
47
+ if (summary) {
48
+ lines.push(`summary: ${summary}`);
49
+ }
50
+ return lines.join("\n");
51
+ }
52
+ function formatBridgeContext(bridges) {
53
+ if (bridges.length === 0)
54
+ return "";
55
+ const lines = ["## active bridge work"];
56
+ for (const bridge of bridges) {
57
+ const task = bridge.task?.taskName ? ` (task: ${bridge.task.taskName})` : "";
58
+ const label = typeof bridge.summary === "string" && bridge.summary.trim().length > 0 ? bridge.summary.trim() : bridge.objective;
59
+ lines.push(`- ${bridge.id}: ${label} [${(0, state_machine_1.bridgeStateLabel)(bridge)}]${task}`);
60
+ }
61
+ return lines.join("\n");
62
+ }
63
+ function ensureRunnable(bridge, now, store) {
64
+ if (bridge.lifecycle === "forming" || bridge.lifecycle === "suspended") {
65
+ const activated = {
66
+ ...bridge,
67
+ ...(0, state_machine_1.activateBridge)(bridge),
68
+ updatedAt: now(),
69
+ };
70
+ return store.save(activated);
71
+ }
72
+ if (bridge.lifecycle === "completed" || bridge.lifecycle === "cancelled") {
73
+ throw new Error(`bridge is terminal: ${bridge.id}`);
74
+ }
75
+ return bridge;
76
+ }
77
+ function sessionMatches(left, right) {
78
+ return left.friendId === right.friendId && left.channel === right.channel && left.key === right.key;
79
+ }
80
+ function hasAttachedSessionActivity(bridge, sessionActivity) {
81
+ return sessionActivity.some((activity) => activity.channel !== "inner"
82
+ && bridge.attachedSessions.some((session) => sessionMatches(activity, session)));
83
+ }
84
+ function hasLiveTaskStatus(bridge, taskBoard) {
85
+ const taskName = bridge.task?.taskName;
86
+ if (!taskName)
87
+ return false;
88
+ return (taskBoard.byStatus.processing.includes(taskName)
89
+ || taskBoard.byStatus.collaborating.includes(taskName)
90
+ || taskBoard.byStatus.validating.includes(taskName));
91
+ }
92
+ function isCurrentSessionAttached(bridge, currentSession) {
93
+ if (!currentSession)
94
+ return false;
95
+ return bridge.attachedSessions.some((session) => sessionMatches(session, currentSession));
96
+ }
97
+ function createBridgeManager(options = {}) {
98
+ const store = options.store ?? (0, store_1.createBridgeStore)();
99
+ const now = options.now ?? (() => new Date().toISOString());
100
+ const idFactory = options.idFactory ?? defaultIdFactory;
101
+ function requireBridge(bridgeId) {
102
+ const bridge = store.get(bridgeId);
103
+ if (!bridge) {
104
+ throw new Error(`bridge not found: ${bridgeId}`);
105
+ }
106
+ return bridge;
107
+ }
108
+ function save(bridge) {
109
+ return store.save(bridge);
110
+ }
111
+ return {
112
+ beginBridge(input) {
113
+ const timestamp = now();
114
+ const state = (0, state_machine_1.activateBridge)((0, state_machine_1.createBridgeState)());
115
+ const bridge = {
116
+ id: idFactory(),
117
+ objective: input.objective,
118
+ summary: input.summary,
119
+ lifecycle: state.lifecycle,
120
+ runtime: state.runtime,
121
+ createdAt: timestamp,
122
+ updatedAt: timestamp,
123
+ attachedSessions: [input.session],
124
+ task: null,
125
+ };
126
+ (0, runtime_1.emitNervesEvent)({
127
+ component: "engine",
128
+ event: "engine.bridge_begin",
129
+ message: "created bridge",
130
+ meta: {
131
+ bridgeId: bridge.id,
132
+ session: sessionIdentityKey(input.session),
133
+ },
134
+ });
135
+ return save(bridge);
136
+ },
137
+ attachSession(bridgeId, session) {
138
+ const bridge = requireBridge(bridgeId);
139
+ assertBridgeMutable(bridge, "attach session to");
140
+ const existing = bridge.attachedSessions.some((candidate) => sessionIdentityKey(candidate) === sessionIdentityKey(session));
141
+ if (existing)
142
+ return bridge;
143
+ const updated = {
144
+ ...bridge,
145
+ attachedSessions: [...bridge.attachedSessions, session],
146
+ updatedAt: now(),
147
+ };
148
+ (0, runtime_1.emitNervesEvent)({
149
+ component: "engine",
150
+ event: "engine.bridge_attach_session",
151
+ message: "attached canonical session to bridge",
152
+ meta: {
153
+ bridgeId,
154
+ session: sessionIdentityKey(session),
155
+ },
156
+ });
157
+ return save(updated);
158
+ },
159
+ detachSession(bridgeId, session) {
160
+ const bridge = requireBridge(bridgeId);
161
+ assertBridgeMutable(bridge, "detach session from");
162
+ const updated = {
163
+ ...bridge,
164
+ attachedSessions: bridge.attachedSessions.filter((candidate) => sessionIdentityKey(candidate) !== sessionIdentityKey(session)),
165
+ updatedAt: now(),
166
+ };
167
+ (0, runtime_1.emitNervesEvent)({
168
+ component: "engine",
169
+ event: "engine.bridge_detach_session",
170
+ message: "detached canonical session from bridge",
171
+ meta: {
172
+ bridgeId,
173
+ session: sessionIdentityKey(session),
174
+ },
175
+ });
176
+ return save(updated);
177
+ },
178
+ getBridge(bridgeId) {
179
+ return store.get(bridgeId);
180
+ },
181
+ listBridges() {
182
+ return store.list();
183
+ },
184
+ findBridgesForSession(session) {
185
+ return store.findBySession(session)
186
+ .filter((bridge) => bridge.lifecycle !== "completed" && bridge.lifecycle !== "cancelled");
187
+ },
188
+ reconcileLifecycles(input) {
189
+ return store.list().map((bridge) => {
190
+ const nextState = (0, state_machine_1.reconcileBridgeState)(bridge, {
191
+ hasAttachedSessionActivity: hasAttachedSessionActivity(bridge, input.sessionActivity),
192
+ hasLiveTask: hasLiveTaskStatus(bridge, input.taskBoard),
193
+ currentSessionAttached: isCurrentSessionAttached(bridge, input.currentSession),
194
+ });
195
+ if (nextState.lifecycle === bridge.lifecycle && nextState.runtime === bridge.runtime) {
196
+ return bridge;
197
+ }
198
+ return save({
199
+ ...bridge,
200
+ ...nextState,
201
+ updatedAt: now(),
202
+ });
203
+ });
204
+ },
205
+ promoteBridgeToTask(bridgeId, input = {}) {
206
+ const bridge = requireBridge(bridgeId);
207
+ assertBridgeMutable(bridge, "promote");
208
+ if (bridge.task)
209
+ return bridge;
210
+ const taskPath = (0, tasks_1.getTaskModule)().createTask({
211
+ title: input.title?.trim() || bridge.objective,
212
+ type: "ongoing",
213
+ category: input.category?.trim() || "coordination",
214
+ status: "processing",
215
+ body: input.body?.trim() || defaultTaskBody(bridge),
216
+ activeBridge: bridge.id,
217
+ bridgeSessions: bridge.attachedSessions.map((session) => sessionIdentityKey(session)),
218
+ });
219
+ const taskName = taskPath.replace(/^.*\//, "").replace(/\.md$/, "");
220
+ const updated = save({
221
+ ...bridge,
222
+ task: {
223
+ taskName,
224
+ path: taskPath,
225
+ mode: "promoted",
226
+ boundAt: now(),
227
+ },
228
+ updatedAt: now(),
229
+ });
230
+ (0, runtime_1.emitNervesEvent)({
231
+ component: "engine",
232
+ event: "engine.bridge_promote_task",
233
+ message: "promoted bridge to task-backed work",
234
+ meta: {
235
+ bridgeId,
236
+ taskName,
237
+ },
238
+ });
239
+ return updated;
240
+ },
241
+ completeBridge(bridgeId) {
242
+ const bridge = requireBridge(bridgeId);
243
+ const updated = save({
244
+ ...bridge,
245
+ ...(0, state_machine_1.completeBridge)(bridge),
246
+ updatedAt: now(),
247
+ });
248
+ (0, runtime_1.emitNervesEvent)({
249
+ component: "engine",
250
+ event: "engine.bridge_complete",
251
+ message: "completed bridge",
252
+ meta: {
253
+ bridgeId,
254
+ },
255
+ });
256
+ return updated;
257
+ },
258
+ cancelBridge(bridgeId) {
259
+ const bridge = requireBridge(bridgeId);
260
+ const updated = save({
261
+ ...bridge,
262
+ ...(0, state_machine_1.cancelBridge)(bridge),
263
+ updatedAt: now(),
264
+ });
265
+ (0, runtime_1.emitNervesEvent)({
266
+ component: "engine",
267
+ event: "engine.bridge_cancel",
268
+ message: "cancelled bridge",
269
+ meta: {
270
+ bridgeId,
271
+ },
272
+ });
273
+ return updated;
274
+ },
275
+ async runBridgeTurn(bridgeId, fn) {
276
+ if (!(0, turn_coordinator_1.tryBeginSharedTurn)("bridge", bridgeId)) {
277
+ const bridge = requireBridge(bridgeId);
278
+ const queued = bridge.runtime === "awaiting-follow-up"
279
+ ? bridge
280
+ : save({
281
+ ...bridge,
282
+ ...(0, state_machine_1.queueBridgeFollowUp)(bridge),
283
+ updatedAt: now(),
284
+ });
285
+ (0, turn_coordinator_1.enqueueSharedFollowUp)("bridge", bridgeId, {
286
+ conversationId: bridgeId,
287
+ text: "bridge follow-up",
288
+ receivedAt: Date.now(),
289
+ effect: "none",
290
+ });
291
+ (0, runtime_1.emitNervesEvent)({
292
+ component: "engine",
293
+ event: "engine.bridge_turn_queued",
294
+ message: "queued follow-up bridge turn",
295
+ meta: {
296
+ bridgeId,
297
+ },
298
+ });
299
+ return {
300
+ queued: true,
301
+ bridge: queued,
302
+ };
303
+ }
304
+ try {
305
+ let current = ensureRunnable(requireBridge(bridgeId), now, store);
306
+ current = save({
307
+ ...current,
308
+ ...(0, state_machine_1.beginBridgeProcessing)(current),
309
+ updatedAt: now(),
310
+ });
311
+ while (true) {
312
+ (0, runtime_1.emitNervesEvent)({
313
+ component: "engine",
314
+ event: "engine.bridge_turn_start",
315
+ message: "running bridge turn",
316
+ meta: {
317
+ bridgeId,
318
+ },
319
+ });
320
+ await fn();
321
+ let next = requireBridge(bridgeId);
322
+ const bufferedFollowUps = (0, turn_coordinator_1.drainSharedFollowUps)("bridge", bridgeId);
323
+ if (bufferedFollowUps.length > 0 && next.runtime !== "awaiting-follow-up") {
324
+ next = save({
325
+ ...next,
326
+ ...(0, state_machine_1.queueBridgeFollowUp)(next),
327
+ updatedAt: now(),
328
+ });
329
+ }
330
+ const advanced = save({
331
+ ...next,
332
+ ...(0, state_machine_1.advanceBridgeAfterTurn)(next),
333
+ updatedAt: now(),
334
+ });
335
+ if (advanced.runtime === "processing") {
336
+ current = advanced;
337
+ continue;
338
+ }
339
+ (0, runtime_1.emitNervesEvent)({
340
+ component: "engine",
341
+ event: "engine.bridge_turn_end",
342
+ message: "bridge turn finished",
343
+ meta: {
344
+ bridgeId,
345
+ },
346
+ });
347
+ return {
348
+ queued: false,
349
+ bridge: current = advanced,
350
+ };
351
+ }
352
+ }
353
+ finally {
354
+ (0, turn_coordinator_1.endSharedTurn)("bridge", bridgeId);
355
+ }
356
+ },
357
+ };
358
+ }