@ouro.bot/cli 0.1.0-alpha.8 → 0.1.0-alpha.81

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 (127) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -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 +468 -0
  7. package/dist/heart/active-work.js +218 -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 +89 -0
  12. package/dist/heart/config.js +68 -23
  13. package/dist/heart/core.js +452 -93
  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 +1779 -247
  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 +164 -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 -84
  42. package/dist/heart/daemon/specialist-prompt.js +63 -11
  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 +141 -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 +228 -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 +27 -11
  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/pending.js +76 -9
  78. package/dist/mind/phrases.js +1 -0
  79. package/dist/mind/prompt.js +445 -77
  80. package/dist/mind/token-estimate.js +8 -12
  81. package/dist/nerves/cli-logging.js +15 -2
  82. package/dist/nerves/coverage/run-artifacts.js +1 -1
  83. package/dist/nerves/index.js +12 -0
  84. package/dist/repertoire/ado-client.js +4 -2
  85. package/dist/repertoire/coding/feedback.js +134 -0
  86. package/dist/repertoire/coding/index.js +4 -1
  87. package/dist/repertoire/coding/manager.js +62 -4
  88. package/dist/repertoire/coding/spawner.js +3 -3
  89. package/dist/repertoire/coding/tools.js +41 -2
  90. package/dist/repertoire/data/ado-endpoints.json +188 -0
  91. package/dist/repertoire/guardrails.js +290 -0
  92. package/dist/repertoire/mcp-client.js +254 -0
  93. package/dist/repertoire/mcp-manager.js +195 -0
  94. package/dist/repertoire/skills.js +3 -26
  95. package/dist/repertoire/tasks/board.js +12 -0
  96. package/dist/repertoire/tasks/index.js +23 -9
  97. package/dist/repertoire/tasks/transitions.js +1 -2
  98. package/dist/repertoire/tools-base.js +686 -251
  99. package/dist/repertoire/tools-bluebubbles.js +93 -0
  100. package/dist/repertoire/tools-teams.js +58 -25
  101. package/dist/repertoire/tools.js +95 -53
  102. package/dist/senses/bluebubbles-client.js +210 -5
  103. package/dist/senses/bluebubbles-entry.js +2 -0
  104. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  105. package/dist/senses/bluebubbles-media.js +339 -0
  106. package/dist/senses/bluebubbles-model.js +12 -4
  107. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  108. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  109. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  110. package/dist/senses/bluebubbles.js +894 -45
  111. package/dist/senses/cli-layout.js +187 -0
  112. package/dist/senses/cli.js +405 -156
  113. package/dist/senses/continuity.js +94 -0
  114. package/dist/senses/debug-activity.js +154 -0
  115. package/dist/senses/inner-dialog-worker.js +47 -18
  116. package/dist/senses/inner-dialog.js +377 -83
  117. package/dist/senses/pipeline.js +307 -0
  118. package/dist/senses/teams.js +573 -129
  119. package/dist/senses/trust-gate.js +112 -2
  120. package/package.json +14 -3
  121. package/subagents/README.md +4 -70
  122. package/dist/heart/daemon/specialist-session.js +0 -142
  123. package/dist/heart/daemon/subagent-installer.js +0 -125
  124. package/dist/inner-worker-entry.js +0 -4
  125. package/subagents/work-doer.md +0 -233
  126. package/subagents/work-merger.md +0 -624
  127. package/subagents/work-planner.md +0 -373
@@ -0,0 +1,218 @@
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 target_resolution_1 = require("./target-resolution");
9
+ function activityPriority(source) {
10
+ return source === "friend-facing" ? 0 : 1;
11
+ }
12
+ function compareActivity(a, b) {
13
+ const sourceDiff = activityPriority(a.activitySource) - activityPriority(b.activitySource);
14
+ if (sourceDiff !== 0)
15
+ return sourceDiff;
16
+ return b.lastActivityMs - a.lastActivityMs;
17
+ }
18
+ function summarizeLiveTasks(taskBoard) {
19
+ const live = [
20
+ ...taskBoard.byStatus.processing,
21
+ ...taskBoard.byStatus.validating,
22
+ ...taskBoard.byStatus.collaborating,
23
+ ];
24
+ return [...new Set(live)];
25
+ }
26
+ function isActiveBridge(bridge) {
27
+ return bridge.lifecycle === "active";
28
+ }
29
+ function hasSharedObligationPressure(input) {
30
+ return (typeof input.currentObligation === "string"
31
+ && input.currentObligation.trim().length > 0) || input.mustResolveBeforeHandoff
32
+ || summarizeLiveTasks(input.taskBoard).length > 0;
33
+ }
34
+ function suggestBridgeForActiveWork(input) {
35
+ const targetCandidates = (input.targetCandidates ?? [])
36
+ .filter((candidate) => {
37
+ if (candidate.delivery.mode === "blocked") {
38
+ return false;
39
+ }
40
+ if (candidate.activitySource !== "friend-facing" || candidate.channel === "inner") {
41
+ return false;
42
+ }
43
+ if (!input.currentSession) {
44
+ return true;
45
+ }
46
+ return !(candidate.friendId === input.currentSession.friendId
47
+ && candidate.channel === input.currentSession.channel
48
+ && candidate.key === input.currentSession.key);
49
+ })
50
+ .sort((a, b) => {
51
+ return b.lastActivityMs - a.lastActivityMs;
52
+ });
53
+ if (!hasSharedObligationPressure(input) || targetCandidates.length === 0) {
54
+ return null;
55
+ }
56
+ const targetSession = targetCandidates[0];
57
+ const activeBridge = input.bridges.find(isActiveBridge) ?? null;
58
+ if (activeBridge) {
59
+ const alreadyAttached = activeBridge.attachedSessions.some((session) => session.friendId === targetSession.friendId
60
+ && session.channel === targetSession.channel
61
+ && session.key === targetSession.key);
62
+ if (alreadyAttached) {
63
+ return null;
64
+ }
65
+ return {
66
+ kind: "attach-existing",
67
+ bridgeId: activeBridge.id,
68
+ targetSession,
69
+ reason: "shared-work-candidate",
70
+ };
71
+ }
72
+ return {
73
+ kind: "begin-new",
74
+ targetSession,
75
+ objectiveHint: input.currentObligation?.trim() || "keep this shared work aligned",
76
+ reason: "shared-work-candidate",
77
+ };
78
+ }
79
+ function formatSessionLabel(session) {
80
+ return `${session.channel}/${session.key}`;
81
+ }
82
+ function buildActiveWorkFrame(input) {
83
+ const friendSessions = input.currentSession
84
+ ? input.friendActivity
85
+ .filter((entry) => entry.friendId === input.currentSession?.friendId)
86
+ .sort(compareActivity)
87
+ : [];
88
+ const liveTaskNames = summarizeLiveTasks(input.taskBoard);
89
+ const activeBridgePresent = input.bridges.some(isActiveBridge);
90
+ const centerOfGravity = activeBridgePresent
91
+ ? "shared-work"
92
+ : (input.inner.status === "running" || input.inner.hasPending || input.mustResolveBeforeHandoff)
93
+ ? "inward-work"
94
+ : "local-turn";
95
+ const frame = {
96
+ currentSession: input.currentSession ?? null,
97
+ currentObligation: input.currentObligation?.trim() || null,
98
+ mustResolveBeforeHandoff: input.mustResolveBeforeHandoff,
99
+ centerOfGravity,
100
+ inner: input.inner,
101
+ bridges: input.bridges,
102
+ taskPressure: {
103
+ compactBoard: input.taskBoard.compact,
104
+ liveTaskNames,
105
+ activeBridges: input.taskBoard.activeBridges,
106
+ },
107
+ friendActivity: {
108
+ freshestForCurrentFriend: friendSessions[0] ?? null,
109
+ otherLiveSessionsForCurrentFriend: friendSessions,
110
+ },
111
+ pendingObligations: input.pendingObligations ?? [],
112
+ targetCandidates: input.targetCandidates ?? [],
113
+ bridgeSuggestion: suggestBridgeForActiveWork({
114
+ currentSession: input.currentSession,
115
+ currentObligation: input.currentObligation,
116
+ mustResolveBeforeHandoff: input.mustResolveBeforeHandoff,
117
+ bridges: input.bridges,
118
+ taskBoard: input.taskBoard,
119
+ targetCandidates: input.targetCandidates,
120
+ }),
121
+ };
122
+ (0, runtime_1.emitNervesEvent)({
123
+ component: "engine",
124
+ event: "engine.active_work_build",
125
+ message: "built shared active-work frame",
126
+ meta: {
127
+ centerOfGravity: frame.centerOfGravity,
128
+ friendId: frame.currentSession?.friendId ?? null,
129
+ bridges: frame.bridges.length,
130
+ liveTasks: frame.taskPressure.liveTaskNames.length,
131
+ liveSessions: frame.friendActivity.otherLiveSessionsForCurrentFriend.length,
132
+ hasBridgeSuggestion: frame.bridgeSuggestion !== null,
133
+ },
134
+ });
135
+ return frame;
136
+ }
137
+ function formatActiveWorkFrame(frame) {
138
+ const lines = ["## what i'm holding"];
139
+ // Session line
140
+ if (frame.currentSession) {
141
+ let sessionLine = `i'm in a conversation on ${formatSessionLabel(frame.currentSession)}.`;
142
+ if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0) {
143
+ sessionLine += ` i told them i'd ${frame.currentObligation.trim()}.`;
144
+ }
145
+ else if (frame.mustResolveBeforeHandoff) {
146
+ sessionLine += " i need to finish what i started here before moving on.";
147
+ }
148
+ lines.push("");
149
+ lines.push(sessionLine);
150
+ }
151
+ else {
152
+ lines.push("");
153
+ lines.push("i'm not in a conversation right now.");
154
+ }
155
+ // Inner status block
156
+ const job = frame.inner?.job;
157
+ if (job) {
158
+ if (job.status === "queued") {
159
+ let queuedLine = "i have a thought queued up for private attention.";
160
+ if (frame.inner?.contentSnippet) {
161
+ queuedLine += `\nit's about: "${frame.inner.contentSnippet}"`;
162
+ }
163
+ lines.push("");
164
+ lines.push(queuedLine);
165
+ }
166
+ else if (job.status === "running") {
167
+ const originName = job.origin?.friendName ?? job.origin?.friendId;
168
+ let runningLine = originName
169
+ ? `i'm thinking through something privately right now. ${originName} asked about something and i wanted to give it real thought.`
170
+ : "i'm thinking through something privately right now.";
171
+ if (frame.inner?.obligationPending) {
172
+ runningLine += "\ni still owe them an answer.";
173
+ }
174
+ lines.push("");
175
+ lines.push(runningLine);
176
+ }
177
+ else if (job.status === "surfaced") {
178
+ let surfacedLine = "i finished thinking about something privately. i should bring my answer back.";
179
+ if (job.surfacedResult) {
180
+ const truncated = job.surfacedResult.length > 120 ? job.surfacedResult.slice(0, 117) + "..." : job.surfacedResult;
181
+ surfacedLine += `\nwhat i came to: ${truncated}`;
182
+ }
183
+ lines.push("");
184
+ lines.push(surfacedLine);
185
+ }
186
+ // idle, returned, abandoned: omitted
187
+ }
188
+ // Task pressure
189
+ if ((frame.taskPressure?.liveTaskNames ?? []).length > 0) {
190
+ lines.push("");
191
+ lines.push(`i'm also tracking: ${frame.taskPressure.liveTaskNames.join(", ")}.`);
192
+ }
193
+ // Bridges
194
+ if ((frame.bridges ?? []).length > 0) {
195
+ const bridgeLabels = frame.bridges.map((bridge) => `${bridge.id} [${(0, state_machine_1.bridgeStateLabel)(bridge)}]`);
196
+ lines.push("");
197
+ lines.push(`i have shared work spanning sessions: ${bridgeLabels.join(", ")}.`);
198
+ }
199
+ // Target candidates (keep factual format)
200
+ const targetCandidatesBlock = frame.targetCandidates && frame.targetCandidates.length > 0
201
+ ? (0, target_resolution_1.formatTargetSessionCandidates)(frame.targetCandidates)
202
+ : "";
203
+ if (targetCandidatesBlock) {
204
+ lines.push("");
205
+ lines.push(targetCandidatesBlock);
206
+ }
207
+ // Bridge suggestion
208
+ if (frame.bridgeSuggestion) {
209
+ lines.push("");
210
+ if (frame.bridgeSuggestion.kind === "begin-new") {
211
+ lines.push(`this work touches my conversation on ${formatSessionLabel(frame.bridgeSuggestion.targetSession)} too -- i should connect these threads.`);
212
+ }
213
+ else {
214
+ lines.push(`this work relates to bridge ${frame.bridgeSuggestion.bridgeId} -- i should connect ${formatSessionLabel(frame.bridgeSuggestion.targetSession)} to it.`);
215
+ }
216
+ }
217
+ return lines.join("\n");
218
+ }
@@ -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
+ }