@ouro.bot/cli 0.1.0-alpha.6 → 0.1.0-alpha.60
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.
- package/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/assets/ouroboros.png +0 -0
- package/changelog.json +325 -0
- package/dist/heart/active-work.js +178 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/config.js +57 -23
- package/dist/heart/core.js +236 -90
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +351 -0
- package/dist/heart/daemon/daemon-cli.js +1173 -227
- package/dist/heart/daemon/daemon-entry.js +55 -6
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +189 -10
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +4 -82
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +178 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +14 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +307 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +53 -84
- package/dist/heart/daemon/specialist-prompt.js +64 -5
- package/dist/heart/daemon/specialist-tools.js +213 -58
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/subagent-installer.js +48 -7
- package/dist/heart/daemon/thoughts.js +379 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +122 -19
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +40 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +74 -9
- package/dist/heart/providers/azure.js +86 -7
- package/dist/heart/providers/minimax.js +4 -0
- package/dist/heart/providers/openai-codex.js +12 -3
- package/dist/heart/safe-workspace.js +228 -0
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/session-activity.js +169 -0
- package/dist/heart/session-recall.js +116 -0
- package/dist/heart/streaming.js +100 -22
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +27 -11
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/pending.js +72 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +299 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +62 -4
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +629 -251
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +92 -48
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +890 -45
- package/dist/senses/cli-layout.js +87 -0
- package/dist/senses/cli.js +345 -144
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +148 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +330 -84
- package/dist/senses/pipeline.js +278 -0
- package/dist/senses/teams.js +570 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +46 -33
- package/subagents/work-doer.md +28 -24
- package/subagents/work-merger.md +24 -30
- package/subagents/work-planner.md +44 -27
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/inner-worker-entry.js +0 -4
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createBridgeState = createBridgeState;
|
|
4
|
+
exports.bridgeStateLabel = bridgeStateLabel;
|
|
5
|
+
exports.activateBridge = activateBridge;
|
|
6
|
+
exports.beginBridgeProcessing = beginBridgeProcessing;
|
|
7
|
+
exports.queueBridgeFollowUp = queueBridgeFollowUp;
|
|
8
|
+
exports.advanceBridgeAfterTurn = advanceBridgeAfterTurn;
|
|
9
|
+
exports.suspendBridge = suspendBridge;
|
|
10
|
+
exports.completeBridge = completeBridge;
|
|
11
|
+
exports.cancelBridge = cancelBridge;
|
|
12
|
+
exports.reconcileBridgeState = reconcileBridgeState;
|
|
13
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
14
|
+
function transition(state, next, action) {
|
|
15
|
+
(0, runtime_1.emitNervesEvent)({
|
|
16
|
+
component: "engine",
|
|
17
|
+
event: "engine.bridge_state_transition",
|
|
18
|
+
message: "bridge state transitioned",
|
|
19
|
+
meta: {
|
|
20
|
+
action,
|
|
21
|
+
from: bridgeStateLabel(state),
|
|
22
|
+
to: bridgeStateLabel(next),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
return next;
|
|
26
|
+
}
|
|
27
|
+
function assertNonTerminal(state, action) {
|
|
28
|
+
if (state.lifecycle === "completed" || state.lifecycle === "cancelled") {
|
|
29
|
+
throw new Error(`cannot ${action} a terminal bridge`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function createBridgeState() {
|
|
33
|
+
return {
|
|
34
|
+
lifecycle: "forming",
|
|
35
|
+
runtime: "idle",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function bridgeStateLabel(state) {
|
|
39
|
+
switch (state.lifecycle) {
|
|
40
|
+
case "forming":
|
|
41
|
+
return "forming";
|
|
42
|
+
case "suspended":
|
|
43
|
+
return "suspended";
|
|
44
|
+
case "completed":
|
|
45
|
+
return "completed";
|
|
46
|
+
case "cancelled":
|
|
47
|
+
return "cancelled";
|
|
48
|
+
case "active":
|
|
49
|
+
if (state.runtime === "processing")
|
|
50
|
+
return "active-processing";
|
|
51
|
+
if (state.runtime === "awaiting-follow-up")
|
|
52
|
+
return "awaiting-follow-up";
|
|
53
|
+
return "active-idle";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function activateBridge(state) {
|
|
57
|
+
assertNonTerminal(state, "activate");
|
|
58
|
+
if (state.lifecycle !== "forming" && state.lifecycle !== "suspended") {
|
|
59
|
+
throw new Error("cannot activate bridge from current state");
|
|
60
|
+
}
|
|
61
|
+
return transition(state, { lifecycle: "active", runtime: "idle" }, "activate");
|
|
62
|
+
}
|
|
63
|
+
function beginBridgeProcessing(state) {
|
|
64
|
+
assertNonTerminal(state, "process");
|
|
65
|
+
if (state.lifecycle !== "active" || state.runtime !== "idle") {
|
|
66
|
+
throw new Error("cannot process bridge from current state");
|
|
67
|
+
}
|
|
68
|
+
return transition(state, { lifecycle: "active", runtime: "processing" }, "begin-processing");
|
|
69
|
+
}
|
|
70
|
+
function queueBridgeFollowUp(state) {
|
|
71
|
+
assertNonTerminal(state, "queue");
|
|
72
|
+
if (state.lifecycle !== "active") {
|
|
73
|
+
throw new Error("cannot queue follow-up for non-active bridge");
|
|
74
|
+
}
|
|
75
|
+
if (state.runtime === "processing") {
|
|
76
|
+
return transition(state, { lifecycle: "active", runtime: "awaiting-follow-up" }, "queue-follow-up");
|
|
77
|
+
}
|
|
78
|
+
if (state.runtime === "awaiting-follow-up") {
|
|
79
|
+
return state;
|
|
80
|
+
}
|
|
81
|
+
throw new Error("cannot queue follow-up when bridge is not processing");
|
|
82
|
+
}
|
|
83
|
+
function advanceBridgeAfterTurn(state) {
|
|
84
|
+
assertNonTerminal(state, "advance");
|
|
85
|
+
if (state.lifecycle !== "active") {
|
|
86
|
+
throw new Error("cannot advance non-active bridge");
|
|
87
|
+
}
|
|
88
|
+
if (state.runtime === "processing") {
|
|
89
|
+
return transition(state, { lifecycle: "active", runtime: "idle" }, "finish-processing");
|
|
90
|
+
}
|
|
91
|
+
if (state.runtime === "awaiting-follow-up") {
|
|
92
|
+
return transition(state, { lifecycle: "active", runtime: "processing" }, "resume-follow-up");
|
|
93
|
+
}
|
|
94
|
+
throw new Error("cannot advance an idle bridge");
|
|
95
|
+
}
|
|
96
|
+
function suspendBridge(state) {
|
|
97
|
+
assertNonTerminal(state, "suspend");
|
|
98
|
+
if ((state.lifecycle !== "forming" && state.lifecycle !== "active") || state.runtime !== "idle") {
|
|
99
|
+
throw new Error("cannot suspend bridge from current state");
|
|
100
|
+
}
|
|
101
|
+
return transition(state, { lifecycle: "suspended", runtime: "idle" }, "suspend");
|
|
102
|
+
}
|
|
103
|
+
function completeBridge(state) {
|
|
104
|
+
assertNonTerminal(state, "complete");
|
|
105
|
+
if (state.runtime !== "idle") {
|
|
106
|
+
throw new Error("cannot complete a bridge mid-turn");
|
|
107
|
+
}
|
|
108
|
+
return transition(state, { lifecycle: "completed", runtime: "idle" }, "complete");
|
|
109
|
+
}
|
|
110
|
+
function cancelBridge(state) {
|
|
111
|
+
assertNonTerminal(state, "cancel");
|
|
112
|
+
if (state.runtime !== "idle") {
|
|
113
|
+
throw new Error("cannot cancel a bridge mid-turn");
|
|
114
|
+
}
|
|
115
|
+
return transition(state, { lifecycle: "cancelled", runtime: "idle" }, "cancel");
|
|
116
|
+
}
|
|
117
|
+
function reconcileBridgeState(state, input) {
|
|
118
|
+
if (state.lifecycle === "completed" || state.lifecycle === "cancelled") {
|
|
119
|
+
return state;
|
|
120
|
+
}
|
|
121
|
+
if (state.runtime !== "idle") {
|
|
122
|
+
return state;
|
|
123
|
+
}
|
|
124
|
+
const hasLiveSignal = input.hasAttachedSessionActivity || input.hasLiveTask || input.currentSessionAttached;
|
|
125
|
+
if (state.lifecycle === "suspended") {
|
|
126
|
+
return hasLiveSignal ? activateBridge(state) : state;
|
|
127
|
+
}
|
|
128
|
+
if (state.lifecycle === "forming") {
|
|
129
|
+
return hasLiveSignal ? activateBridge(state) : suspendBridge(state);
|
|
130
|
+
}
|
|
131
|
+
if (state.lifecycle === "active") {
|
|
132
|
+
return hasLiveSignal ? state : suspendBridge(state);
|
|
133
|
+
}
|
|
134
|
+
return state;
|
|
135
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
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.getBridgeStateRoot = getBridgeStateRoot;
|
|
37
|
+
exports.createBridgeStore = createBridgeStore;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const identity_1 = require("../identity");
|
|
41
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
42
|
+
function sessionIdentityMatches(session, candidate) {
|
|
43
|
+
return (session.friendId === candidate.friendId
|
|
44
|
+
&& session.channel === candidate.channel
|
|
45
|
+
&& session.key === candidate.key);
|
|
46
|
+
}
|
|
47
|
+
function bridgeFilePath(rootDir, id) {
|
|
48
|
+
return path.join(rootDir, `${id}.json`);
|
|
49
|
+
}
|
|
50
|
+
function getBridgeStateRoot() {
|
|
51
|
+
return path.join((0, identity_1.getAgentStateRoot)(), "bridges");
|
|
52
|
+
}
|
|
53
|
+
function createBridgeStore(options = {}) {
|
|
54
|
+
const rootDir = options.rootDir ?? getBridgeStateRoot();
|
|
55
|
+
function ensureRoot() {
|
|
56
|
+
fs.mkdirSync(rootDir, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
save(bridge) {
|
|
60
|
+
ensureRoot();
|
|
61
|
+
fs.writeFileSync(bridgeFilePath(rootDir, bridge.id), JSON.stringify(bridge, null, 2), "utf-8");
|
|
62
|
+
(0, runtime_1.emitNervesEvent)({
|
|
63
|
+
component: "engine",
|
|
64
|
+
event: "engine.bridge_store_save",
|
|
65
|
+
message: "saved bridge record",
|
|
66
|
+
meta: {
|
|
67
|
+
bridgeId: bridge.id,
|
|
68
|
+
rootDir,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return bridge;
|
|
72
|
+
},
|
|
73
|
+
get(id) {
|
|
74
|
+
const filePath = bridgeFilePath(rootDir, id);
|
|
75
|
+
try {
|
|
76
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
77
|
+
return JSON.parse(raw);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
list() {
|
|
84
|
+
ensureRoot();
|
|
85
|
+
const files = fs.readdirSync(rootDir).filter((entry) => entry.endsWith(".json")).sort();
|
|
86
|
+
const bridges = files
|
|
87
|
+
.map((fileName) => {
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(fs.readFileSync(path.join(rootDir, fileName), "utf-8"));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
.filter((bridge) => bridge !== null);
|
|
96
|
+
(0, runtime_1.emitNervesEvent)({
|
|
97
|
+
component: "engine",
|
|
98
|
+
event: "engine.bridge_store_list",
|
|
99
|
+
message: "listed bridge records",
|
|
100
|
+
meta: {
|
|
101
|
+
rootDir,
|
|
102
|
+
count: bridges.length,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
return bridges;
|
|
106
|
+
},
|
|
107
|
+
findBySession(session) {
|
|
108
|
+
const matches = this.list().filter((bridge) => bridge.attachedSessions.some((candidate) => sessionIdentityMatches(session, candidate)));
|
|
109
|
+
(0, runtime_1.emitNervesEvent)({
|
|
110
|
+
component: "engine",
|
|
111
|
+
event: "engine.bridge_store_find_by_session",
|
|
112
|
+
message: "located bridges for canonical session",
|
|
113
|
+
meta: {
|
|
114
|
+
friendId: session.friendId,
|
|
115
|
+
channel: session.channel,
|
|
116
|
+
key: session.key,
|
|
117
|
+
count: matches.length,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
return matches;
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|