@ouro.bot/cli 0.1.0-alpha.47 → 0.1.0-alpha.49
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/changelog.json +16 -0
- package/dist/heart/bridges/manager.js +321 -0
- package/dist/heart/bridges/state-machine.js +115 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/daemon/thoughts.js +9 -1
- package/dist/heart/session-recall.js +116 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/pending.js +11 -0
- package/dist/mind/prompt.js +8 -1
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +21 -0
- package/dist/repertoire/tools-base.js +136 -34
- package/dist/repertoire/tools.js +2 -0
- package/dist/senses/bluebubbles.js +127 -123
- package/dist/senses/cli.js +1 -0
- package/dist/senses/inner-dialog-worker.js +41 -15
- package/dist/senses/inner-dialog.js +1 -0
- package/dist/senses/pipeline.js +17 -0
- package/dist/senses/teams.js +1 -0
- package/package.json +1 -1
- package/subagents/work-doer.md +1 -1
- package/subagents/work-planner.md +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.49",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Inner dialog no longer drops self-directed wake signals that arrive mid-turn; overlapping inward requests now guarantee a truthful follow-up inner pass instead of mailbox-like limbo.",
|
|
8
|
+
"The harness now has a shared core bridge layer with lifecycle/runtime state, persistence, task linkage, and one `bridge_manage` path so already-live sessions can coordinate as one coherent piece of work.",
|
|
9
|
+
"`query_session` and bridge attachment now share one session-recall helper, reducing duplicate cross-session summarization logic and setting up thinner outer surfaces around a stronger inner/core."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.48",
|
|
14
|
+
"changes": [
|
|
15
|
+
"BlueBubbles same-chat turns now serialize through the shared heart-level turn coordinator, so duplicate delivery of one inbound message no longer races into two handled turns or duplicate replies.",
|
|
16
|
+
"BlueBubbles duplicate-check, session load, inbound turn execution, and inbound sidecar recording now happen inside one canonical chat-trunk critical section keyed by the resolved session path.",
|
|
17
|
+
"Workflow docs now let agents auto-create the required dedicated task worktree and branch by default when the human has not asked to control naming or layout."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
4
20
|
{
|
|
5
21
|
"version": "0.1.0-alpha.47",
|
|
6
22
|
"changes": [
|
|
@@ -0,0 +1,321 @@
|
|
|
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 createBridgeManager(options = {}) {
|
|
78
|
+
const store = options.store ?? (0, store_1.createBridgeStore)();
|
|
79
|
+
const now = options.now ?? (() => new Date().toISOString());
|
|
80
|
+
const idFactory = options.idFactory ?? defaultIdFactory;
|
|
81
|
+
function requireBridge(bridgeId) {
|
|
82
|
+
const bridge = store.get(bridgeId);
|
|
83
|
+
if (!bridge) {
|
|
84
|
+
throw new Error(`bridge not found: ${bridgeId}`);
|
|
85
|
+
}
|
|
86
|
+
return bridge;
|
|
87
|
+
}
|
|
88
|
+
function save(bridge) {
|
|
89
|
+
return store.save(bridge);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
beginBridge(input) {
|
|
93
|
+
const timestamp = now();
|
|
94
|
+
const state = (0, state_machine_1.activateBridge)((0, state_machine_1.createBridgeState)());
|
|
95
|
+
const bridge = {
|
|
96
|
+
id: idFactory(),
|
|
97
|
+
objective: input.objective,
|
|
98
|
+
summary: input.summary,
|
|
99
|
+
lifecycle: state.lifecycle,
|
|
100
|
+
runtime: state.runtime,
|
|
101
|
+
createdAt: timestamp,
|
|
102
|
+
updatedAt: timestamp,
|
|
103
|
+
attachedSessions: [input.session],
|
|
104
|
+
task: null,
|
|
105
|
+
};
|
|
106
|
+
(0, runtime_1.emitNervesEvent)({
|
|
107
|
+
component: "engine",
|
|
108
|
+
event: "engine.bridge_begin",
|
|
109
|
+
message: "created bridge",
|
|
110
|
+
meta: {
|
|
111
|
+
bridgeId: bridge.id,
|
|
112
|
+
session: sessionIdentityKey(input.session),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
return save(bridge);
|
|
116
|
+
},
|
|
117
|
+
attachSession(bridgeId, session) {
|
|
118
|
+
const bridge = requireBridge(bridgeId);
|
|
119
|
+
assertBridgeMutable(bridge, "attach session to");
|
|
120
|
+
const existing = bridge.attachedSessions.some((candidate) => sessionIdentityKey(candidate) === sessionIdentityKey(session));
|
|
121
|
+
if (existing)
|
|
122
|
+
return bridge;
|
|
123
|
+
const updated = {
|
|
124
|
+
...bridge,
|
|
125
|
+
attachedSessions: [...bridge.attachedSessions, session],
|
|
126
|
+
updatedAt: now(),
|
|
127
|
+
};
|
|
128
|
+
(0, runtime_1.emitNervesEvent)({
|
|
129
|
+
component: "engine",
|
|
130
|
+
event: "engine.bridge_attach_session",
|
|
131
|
+
message: "attached canonical session to bridge",
|
|
132
|
+
meta: {
|
|
133
|
+
bridgeId,
|
|
134
|
+
session: sessionIdentityKey(session),
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
return save(updated);
|
|
138
|
+
},
|
|
139
|
+
detachSession(bridgeId, session) {
|
|
140
|
+
const bridge = requireBridge(bridgeId);
|
|
141
|
+
assertBridgeMutable(bridge, "detach session from");
|
|
142
|
+
const updated = {
|
|
143
|
+
...bridge,
|
|
144
|
+
attachedSessions: bridge.attachedSessions.filter((candidate) => sessionIdentityKey(candidate) !== sessionIdentityKey(session)),
|
|
145
|
+
updatedAt: now(),
|
|
146
|
+
};
|
|
147
|
+
(0, runtime_1.emitNervesEvent)({
|
|
148
|
+
component: "engine",
|
|
149
|
+
event: "engine.bridge_detach_session",
|
|
150
|
+
message: "detached canonical session from bridge",
|
|
151
|
+
meta: {
|
|
152
|
+
bridgeId,
|
|
153
|
+
session: sessionIdentityKey(session),
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
return save(updated);
|
|
157
|
+
},
|
|
158
|
+
getBridge(bridgeId) {
|
|
159
|
+
return store.get(bridgeId);
|
|
160
|
+
},
|
|
161
|
+
listBridges() {
|
|
162
|
+
return store.list();
|
|
163
|
+
},
|
|
164
|
+
findBridgesForSession(session) {
|
|
165
|
+
return store.findBySession(session)
|
|
166
|
+
.filter((bridge) => bridge.lifecycle !== "completed" && bridge.lifecycle !== "cancelled");
|
|
167
|
+
},
|
|
168
|
+
promoteBridgeToTask(bridgeId, input = {}) {
|
|
169
|
+
const bridge = requireBridge(bridgeId);
|
|
170
|
+
assertBridgeMutable(bridge, "promote");
|
|
171
|
+
if (bridge.task)
|
|
172
|
+
return bridge;
|
|
173
|
+
const taskPath = (0, tasks_1.getTaskModule)().createTask({
|
|
174
|
+
title: input.title?.trim() || bridge.objective,
|
|
175
|
+
type: "ongoing",
|
|
176
|
+
category: input.category?.trim() || "coordination",
|
|
177
|
+
status: "processing",
|
|
178
|
+
body: input.body?.trim() || defaultTaskBody(bridge),
|
|
179
|
+
activeBridge: bridge.id,
|
|
180
|
+
bridgeSessions: bridge.attachedSessions.map((session) => sessionIdentityKey(session)),
|
|
181
|
+
});
|
|
182
|
+
const taskName = taskPath.replace(/^.*\//, "").replace(/\.md$/, "");
|
|
183
|
+
const updated = save({
|
|
184
|
+
...bridge,
|
|
185
|
+
task: {
|
|
186
|
+
taskName,
|
|
187
|
+
path: taskPath,
|
|
188
|
+
mode: "promoted",
|
|
189
|
+
boundAt: now(),
|
|
190
|
+
},
|
|
191
|
+
updatedAt: now(),
|
|
192
|
+
});
|
|
193
|
+
(0, runtime_1.emitNervesEvent)({
|
|
194
|
+
component: "engine",
|
|
195
|
+
event: "engine.bridge_promote_task",
|
|
196
|
+
message: "promoted bridge to task-backed work",
|
|
197
|
+
meta: {
|
|
198
|
+
bridgeId,
|
|
199
|
+
taskName,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
return updated;
|
|
203
|
+
},
|
|
204
|
+
completeBridge(bridgeId) {
|
|
205
|
+
const bridge = requireBridge(bridgeId);
|
|
206
|
+
const updated = save({
|
|
207
|
+
...bridge,
|
|
208
|
+
...(0, state_machine_1.completeBridge)(bridge),
|
|
209
|
+
updatedAt: now(),
|
|
210
|
+
});
|
|
211
|
+
(0, runtime_1.emitNervesEvent)({
|
|
212
|
+
component: "engine",
|
|
213
|
+
event: "engine.bridge_complete",
|
|
214
|
+
message: "completed bridge",
|
|
215
|
+
meta: {
|
|
216
|
+
bridgeId,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
return updated;
|
|
220
|
+
},
|
|
221
|
+
cancelBridge(bridgeId) {
|
|
222
|
+
const bridge = requireBridge(bridgeId);
|
|
223
|
+
const updated = save({
|
|
224
|
+
...bridge,
|
|
225
|
+
...(0, state_machine_1.cancelBridge)(bridge),
|
|
226
|
+
updatedAt: now(),
|
|
227
|
+
});
|
|
228
|
+
(0, runtime_1.emitNervesEvent)({
|
|
229
|
+
component: "engine",
|
|
230
|
+
event: "engine.bridge_cancel",
|
|
231
|
+
message: "cancelled bridge",
|
|
232
|
+
meta: {
|
|
233
|
+
bridgeId,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
return updated;
|
|
237
|
+
},
|
|
238
|
+
async runBridgeTurn(bridgeId, fn) {
|
|
239
|
+
if (!(0, turn_coordinator_1.tryBeginSharedTurn)("bridge", bridgeId)) {
|
|
240
|
+
const bridge = requireBridge(bridgeId);
|
|
241
|
+
const queued = bridge.runtime === "awaiting-follow-up"
|
|
242
|
+
? bridge
|
|
243
|
+
: save({
|
|
244
|
+
...bridge,
|
|
245
|
+
...(0, state_machine_1.queueBridgeFollowUp)(bridge),
|
|
246
|
+
updatedAt: now(),
|
|
247
|
+
});
|
|
248
|
+
(0, turn_coordinator_1.enqueueSharedFollowUp)("bridge", bridgeId, {
|
|
249
|
+
conversationId: bridgeId,
|
|
250
|
+
text: "bridge follow-up",
|
|
251
|
+
receivedAt: Date.now(),
|
|
252
|
+
effect: "none",
|
|
253
|
+
});
|
|
254
|
+
(0, runtime_1.emitNervesEvent)({
|
|
255
|
+
component: "engine",
|
|
256
|
+
event: "engine.bridge_turn_queued",
|
|
257
|
+
message: "queued follow-up bridge turn",
|
|
258
|
+
meta: {
|
|
259
|
+
bridgeId,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
return {
|
|
263
|
+
queued: true,
|
|
264
|
+
bridge: queued,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
let current = ensureRunnable(requireBridge(bridgeId), now, store);
|
|
269
|
+
current = save({
|
|
270
|
+
...current,
|
|
271
|
+
...(0, state_machine_1.beginBridgeProcessing)(current),
|
|
272
|
+
updatedAt: now(),
|
|
273
|
+
});
|
|
274
|
+
while (true) {
|
|
275
|
+
(0, runtime_1.emitNervesEvent)({
|
|
276
|
+
component: "engine",
|
|
277
|
+
event: "engine.bridge_turn_start",
|
|
278
|
+
message: "running bridge turn",
|
|
279
|
+
meta: {
|
|
280
|
+
bridgeId,
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
await fn();
|
|
284
|
+
let next = requireBridge(bridgeId);
|
|
285
|
+
const bufferedFollowUps = (0, turn_coordinator_1.drainSharedFollowUps)("bridge", bridgeId);
|
|
286
|
+
if (bufferedFollowUps.length > 0 && next.runtime !== "awaiting-follow-up") {
|
|
287
|
+
next = save({
|
|
288
|
+
...next,
|
|
289
|
+
...(0, state_machine_1.queueBridgeFollowUp)(next),
|
|
290
|
+
updatedAt: now(),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const advanced = save({
|
|
294
|
+
...next,
|
|
295
|
+
...(0, state_machine_1.advanceBridgeAfterTurn)(next),
|
|
296
|
+
updatedAt: now(),
|
|
297
|
+
});
|
|
298
|
+
if (advanced.runtime === "processing") {
|
|
299
|
+
current = advanced;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
(0, runtime_1.emitNervesEvent)({
|
|
303
|
+
component: "engine",
|
|
304
|
+
event: "engine.bridge_turn_end",
|
|
305
|
+
message: "bridge turn finished",
|
|
306
|
+
meta: {
|
|
307
|
+
bridgeId,
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
return {
|
|
311
|
+
queued: false,
|
|
312
|
+
bridge: current = advanced,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
finally {
|
|
317
|
+
(0, turn_coordinator_1.endSharedTurn)("bridge", bridgeId);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
13
|
+
function transition(state, next, action) {
|
|
14
|
+
(0, runtime_1.emitNervesEvent)({
|
|
15
|
+
component: "engine",
|
|
16
|
+
event: "engine.bridge_state_transition",
|
|
17
|
+
message: "bridge state transitioned",
|
|
18
|
+
meta: {
|
|
19
|
+
action,
|
|
20
|
+
from: bridgeStateLabel(state),
|
|
21
|
+
to: bridgeStateLabel(next),
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
return next;
|
|
25
|
+
}
|
|
26
|
+
function assertNonTerminal(state, action) {
|
|
27
|
+
if (state.lifecycle === "completed" || state.lifecycle === "cancelled") {
|
|
28
|
+
throw new Error(`cannot ${action} a terminal bridge`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function createBridgeState() {
|
|
32
|
+
return {
|
|
33
|
+
lifecycle: "forming",
|
|
34
|
+
runtime: "idle",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function bridgeStateLabel(state) {
|
|
38
|
+
switch (state.lifecycle) {
|
|
39
|
+
case "forming":
|
|
40
|
+
return "forming";
|
|
41
|
+
case "suspended":
|
|
42
|
+
return "suspended";
|
|
43
|
+
case "completed":
|
|
44
|
+
return "completed";
|
|
45
|
+
case "cancelled":
|
|
46
|
+
return "cancelled";
|
|
47
|
+
case "active":
|
|
48
|
+
if (state.runtime === "processing")
|
|
49
|
+
return "active-processing";
|
|
50
|
+
if (state.runtime === "awaiting-follow-up")
|
|
51
|
+
return "awaiting-follow-up";
|
|
52
|
+
return "active-idle";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function activateBridge(state) {
|
|
56
|
+
assertNonTerminal(state, "activate");
|
|
57
|
+
if (state.lifecycle !== "forming" && state.lifecycle !== "suspended") {
|
|
58
|
+
throw new Error("cannot activate bridge from current state");
|
|
59
|
+
}
|
|
60
|
+
return transition(state, { lifecycle: "active", runtime: "idle" }, "activate");
|
|
61
|
+
}
|
|
62
|
+
function beginBridgeProcessing(state) {
|
|
63
|
+
assertNonTerminal(state, "process");
|
|
64
|
+
if (state.lifecycle !== "active" || state.runtime !== "idle") {
|
|
65
|
+
throw new Error("cannot process bridge from current state");
|
|
66
|
+
}
|
|
67
|
+
return transition(state, { lifecycle: "active", runtime: "processing" }, "begin-processing");
|
|
68
|
+
}
|
|
69
|
+
function queueBridgeFollowUp(state) {
|
|
70
|
+
assertNonTerminal(state, "queue");
|
|
71
|
+
if (state.lifecycle !== "active") {
|
|
72
|
+
throw new Error("cannot queue follow-up for non-active bridge");
|
|
73
|
+
}
|
|
74
|
+
if (state.runtime === "processing") {
|
|
75
|
+
return transition(state, { lifecycle: "active", runtime: "awaiting-follow-up" }, "queue-follow-up");
|
|
76
|
+
}
|
|
77
|
+
if (state.runtime === "awaiting-follow-up") {
|
|
78
|
+
return state;
|
|
79
|
+
}
|
|
80
|
+
throw new Error("cannot queue follow-up when bridge is not processing");
|
|
81
|
+
}
|
|
82
|
+
function advanceBridgeAfterTurn(state) {
|
|
83
|
+
assertNonTerminal(state, "advance");
|
|
84
|
+
if (state.lifecycle !== "active") {
|
|
85
|
+
throw new Error("cannot advance non-active bridge");
|
|
86
|
+
}
|
|
87
|
+
if (state.runtime === "processing") {
|
|
88
|
+
return transition(state, { lifecycle: "active", runtime: "idle" }, "finish-processing");
|
|
89
|
+
}
|
|
90
|
+
if (state.runtime === "awaiting-follow-up") {
|
|
91
|
+
return transition(state, { lifecycle: "active", runtime: "processing" }, "resume-follow-up");
|
|
92
|
+
}
|
|
93
|
+
throw new Error("cannot advance an idle bridge");
|
|
94
|
+
}
|
|
95
|
+
function suspendBridge(state) {
|
|
96
|
+
assertNonTerminal(state, "suspend");
|
|
97
|
+
if ((state.lifecycle !== "forming" && state.lifecycle !== "active") || state.runtime !== "idle") {
|
|
98
|
+
throw new Error("cannot suspend bridge from current state");
|
|
99
|
+
}
|
|
100
|
+
return transition(state, { lifecycle: "suspended", runtime: "idle" }, "suspend");
|
|
101
|
+
}
|
|
102
|
+
function completeBridge(state) {
|
|
103
|
+
assertNonTerminal(state, "complete");
|
|
104
|
+
if (state.runtime !== "idle") {
|
|
105
|
+
throw new Error("cannot complete a bridge mid-turn");
|
|
106
|
+
}
|
|
107
|
+
return transition(state, { lifecycle: "completed", runtime: "idle" }, "complete");
|
|
108
|
+
}
|
|
109
|
+
function cancelBridge(state) {
|
|
110
|
+
assertNonTerminal(state, "cancel");
|
|
111
|
+
if (state.runtime !== "idle") {
|
|
112
|
+
throw new Error("cannot cancel a bridge mid-turn");
|
|
113
|
+
}
|
|
114
|
+
return transition(state, { lifecycle: "cancelled", runtime: "idle" }, "cancel");
|
|
115
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -151,8 +151,16 @@ function formatSurfacedValue(text, maxLength = 120) {
|
|
|
151
151
|
}
|
|
152
152
|
function deriveInnerDialogStatus(pendingMessages, turns, runtimeState) {
|
|
153
153
|
if (runtimeState?.status === "running") {
|
|
154
|
+
if (pendingMessages.length > 0) {
|
|
155
|
+
return {
|
|
156
|
+
queue: "queued to inner/dialog",
|
|
157
|
+
wake: "queued behind active turn",
|
|
158
|
+
processing: "pending",
|
|
159
|
+
surfaced: "nothing yet",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
154
162
|
return {
|
|
155
|
-
queue:
|
|
163
|
+
queue: "clear",
|
|
156
164
|
wake: "in progress",
|
|
157
165
|
processing: "started",
|
|
158
166
|
surfaced: "nothing yet",
|