@ouro.bot/cli 0.1.0-alpha.48 → 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 +8 -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 +24 -1
- 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 +1 -0
- 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/changelog.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
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
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"version": "0.1.0-alpha.48",
|
|
6
14
|
"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",
|
|
@@ -0,0 +1,116 @@
|
|
|
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.recallSession = recallSession;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const runtime_1 = require("../nerves/runtime");
|
|
39
|
+
function normalizeContent(content) {
|
|
40
|
+
if (typeof content === "string")
|
|
41
|
+
return content;
|
|
42
|
+
if (!Array.isArray(content))
|
|
43
|
+
return "";
|
|
44
|
+
return content
|
|
45
|
+
.map((part) => (part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part
|
|
46
|
+
? String(part.text ?? "")
|
|
47
|
+
: ""))
|
|
48
|
+
.filter((text) => text.length > 0)
|
|
49
|
+
.join("");
|
|
50
|
+
}
|
|
51
|
+
function buildSummaryInstruction(friendId, channel, trustLevel) {
|
|
52
|
+
if (friendId === "self" && channel === "inner") {
|
|
53
|
+
return "summarize this session transcript fully and transparently. this is my own inner dialog — include all details, decisions, and reasoning.";
|
|
54
|
+
}
|
|
55
|
+
return `summarize this session transcript. the person asking has trust level: ${trustLevel}. family=full transparency, friend=share work and general topics but protect other people's identities, acquaintance=very guarded minimal disclosure.`;
|
|
56
|
+
}
|
|
57
|
+
function clip(text, limit = 160) {
|
|
58
|
+
const compact = text.replace(/\s+/g, " ").trim();
|
|
59
|
+
return compact.length > limit ? compact.slice(0, limit - 1) + "…" : compact;
|
|
60
|
+
}
|
|
61
|
+
function buildSnapshot(summary, tailMessages) {
|
|
62
|
+
const lines = [`recent focus: ${clip(summary, 240)}`];
|
|
63
|
+
const latestUser = [...tailMessages].reverse().find((message) => message.role === "user")?.content;
|
|
64
|
+
const latestAssistant = [...tailMessages].reverse().find((message) => message.role === "assistant")?.content;
|
|
65
|
+
if (latestUser) {
|
|
66
|
+
lines.push(`latest user: ${clip(latestUser)}`);
|
|
67
|
+
}
|
|
68
|
+
if (latestAssistant) {
|
|
69
|
+
lines.push(`latest assistant: ${clip(latestAssistant)}`);
|
|
70
|
+
}
|
|
71
|
+
return lines.join("\n");
|
|
72
|
+
}
|
|
73
|
+
async function recallSession(options) {
|
|
74
|
+
(0, runtime_1.emitNervesEvent)({
|
|
75
|
+
component: "daemon",
|
|
76
|
+
event: "daemon.session_recall",
|
|
77
|
+
message: "recalling session transcript tail",
|
|
78
|
+
meta: {
|
|
79
|
+
friendId: options.friendId,
|
|
80
|
+
channel: options.channel,
|
|
81
|
+
key: options.key,
|
|
82
|
+
messageCount: options.messageCount,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
let raw;
|
|
86
|
+
try {
|
|
87
|
+
raw = fs.readFileSync(options.sessionPath, "utf-8");
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return { kind: "missing" };
|
|
91
|
+
}
|
|
92
|
+
const parsed = JSON.parse(raw);
|
|
93
|
+
const tailMessages = (parsed.messages ?? [])
|
|
94
|
+
.map((message) => ({
|
|
95
|
+
role: typeof message.role === "string" ? message.role : "",
|
|
96
|
+
content: normalizeContent(message.content),
|
|
97
|
+
}))
|
|
98
|
+
.filter((message) => message.role !== "system" && message.content.length > 0)
|
|
99
|
+
.slice(-options.messageCount);
|
|
100
|
+
if (tailMessages.length === 0) {
|
|
101
|
+
return { kind: "empty" };
|
|
102
|
+
}
|
|
103
|
+
const transcript = tailMessages
|
|
104
|
+
.map((message) => `[${message.role}] ${message.content}`)
|
|
105
|
+
.join("\n");
|
|
106
|
+
const summary = options.summarize
|
|
107
|
+
? await options.summarize(transcript, buildSummaryInstruction(options.friendId, options.channel, options.trustLevel ?? "family"))
|
|
108
|
+
: transcript;
|
|
109
|
+
return {
|
|
110
|
+
kind: "ok",
|
|
111
|
+
transcript,
|
|
112
|
+
summary,
|
|
113
|
+
snapshot: buildSnapshot(summary, tailMessages),
|
|
114
|
+
tailMessages,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createTurnCoordinator = createTurnCoordinator;
|
|
4
4
|
exports.withSharedTurnLock = withSharedTurnLock;
|
|
5
|
+
exports.tryBeginSharedTurn = tryBeginSharedTurn;
|
|
6
|
+
exports.endSharedTurn = endSharedTurn;
|
|
7
|
+
exports.isSharedTurnActive = isSharedTurnActive;
|
|
8
|
+
exports.enqueueSharedFollowUp = enqueueSharedFollowUp;
|
|
9
|
+
exports.drainSharedFollowUps = drainSharedFollowUps;
|
|
5
10
|
const runtime_1 = require("../nerves/runtime");
|
|
11
|
+
function scopedKey(scope, key) {
|
|
12
|
+
return `${scope}:${key}`;
|
|
13
|
+
}
|
|
6
14
|
function createTurnCoordinator() {
|
|
7
15
|
const turnLocks = new Map();
|
|
8
16
|
const activeTurns = new Set();
|
|
@@ -63,5 +71,20 @@ function createTurnCoordinator() {
|
|
|
63
71
|
}
|
|
64
72
|
const _sharedTurnCoordinator = createTurnCoordinator();
|
|
65
73
|
function withSharedTurnLock(scope, key, fn) {
|
|
66
|
-
return _sharedTurnCoordinator.withTurnLock(
|
|
74
|
+
return _sharedTurnCoordinator.withTurnLock(scopedKey(scope, key), fn);
|
|
75
|
+
}
|
|
76
|
+
function tryBeginSharedTurn(scope, key) {
|
|
77
|
+
return _sharedTurnCoordinator.tryBeginTurn(scopedKey(scope, key));
|
|
78
|
+
}
|
|
79
|
+
function endSharedTurn(scope, key) {
|
|
80
|
+
_sharedTurnCoordinator.endTurn(scopedKey(scope, key));
|
|
81
|
+
}
|
|
82
|
+
function isSharedTurnActive(scope, key) {
|
|
83
|
+
return _sharedTurnCoordinator.isTurnActive(scopedKey(scope, key));
|
|
84
|
+
}
|
|
85
|
+
function enqueueSharedFollowUp(scope, key, followUp) {
|
|
86
|
+
_sharedTurnCoordinator.enqueueFollowUp(scopedKey(scope, key), followUp);
|
|
87
|
+
}
|
|
88
|
+
function drainSharedFollowUps(scope, key) {
|
|
89
|
+
return _sharedTurnCoordinator.drainFollowUps(scopedKey(scope, key));
|
|
67
90
|
}
|
package/dist/mind/pending.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.INNER_DIALOG_PENDING = void 0;
|
|
37
37
|
exports.getPendingDir = getPendingDir;
|
|
38
38
|
exports.getInnerDialogPendingDir = getInnerDialogPendingDir;
|
|
39
|
+
exports.hasPendingMessages = hasPendingMessages;
|
|
39
40
|
exports.drainPending = drainPending;
|
|
40
41
|
const fs = __importStar(require("fs"));
|
|
41
42
|
const path = __importStar(require("path"));
|
|
@@ -50,6 +51,16 @@ exports.INNER_DIALOG_PENDING = { friendId: "self", channel: "inner", key: "dialo
|
|
|
50
51
|
function getInnerDialogPendingDir(agentName) {
|
|
51
52
|
return getPendingDir(agentName, exports.INNER_DIALOG_PENDING.friendId, exports.INNER_DIALOG_PENDING.channel, exports.INNER_DIALOG_PENDING.key);
|
|
52
53
|
}
|
|
54
|
+
function hasPendingMessages(pendingDir) {
|
|
55
|
+
if (!fs.existsSync(pendingDir))
|
|
56
|
+
return false;
|
|
57
|
+
try {
|
|
58
|
+
return fs.readdirSync(pendingDir).some((entry) => entry.endsWith(".json") || entry.endsWith(".json.processing"));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
53
64
|
function drainPending(pendingDir) {
|
|
54
65
|
if (!fs.existsSync(pendingDir))
|
|
55
66
|
return [];
|
package/dist/mind/prompt.js
CHANGED
|
@@ -412,6 +412,12 @@ function memoryFriendToolContractSection() {
|
|
|
412
412
|
- My psyche files (SOUL, IDENTITY, TACIT, LORE, ASPIRATIONS) are always loaded - I already know who I am.
|
|
413
413
|
- My task board is always loaded - I already know my work.`;
|
|
414
414
|
}
|
|
415
|
+
function bridgeContextSection(options) {
|
|
416
|
+
const bridgeContext = options?.bridgeContext?.trim() ?? "";
|
|
417
|
+
if (!bridgeContext)
|
|
418
|
+
return "";
|
|
419
|
+
return bridgeContext.startsWith("## ") ? bridgeContext : `## active bridge work\n${bridgeContext}`;
|
|
420
|
+
}
|
|
415
421
|
function toolBehaviorSection(options) {
|
|
416
422
|
if (!(options?.toolChoiceRequired ?? true))
|
|
417
423
|
return "";
|
|
@@ -543,13 +549,14 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
543
549
|
mixedTrustGroupSection(context),
|
|
544
550
|
skillsSection(),
|
|
545
551
|
taskBoardSection(),
|
|
552
|
+
bridgeContextSection(options),
|
|
546
553
|
buildSessionSummary({
|
|
547
554
|
sessionsDir: path.join((0, identity_1.getAgentRoot)(), "state", "sessions"),
|
|
548
555
|
friendsDir: path.join((0, identity_1.getAgentRoot)(), "friends"),
|
|
549
556
|
agentName: (0, identity_1.getAgentName)(),
|
|
550
557
|
currentFriendId: context?.friend?.id,
|
|
551
558
|
currentChannel: channel,
|
|
552
|
-
currentKey: "session",
|
|
559
|
+
currentKey: options?.currentSessionKey ?? "session",
|
|
553
560
|
}),
|
|
554
561
|
memoryFriendToolContractSection(),
|
|
555
562
|
toolBehaviorSection(options),
|
|
@@ -59,6 +59,12 @@ function activeSessionLines(tasks) {
|
|
|
59
59
|
});
|
|
60
60
|
return active.map((task) => task.stem).sort();
|
|
61
61
|
}
|
|
62
|
+
function activeBridgeLines(tasks) {
|
|
63
|
+
return tasks
|
|
64
|
+
.filter((task) => typeof task.frontmatter.active_bridge === "string" && String(task.frontmatter.active_bridge).trim())
|
|
65
|
+
.map((task) => `${task.stem} -> ${String(task.frontmatter.active_bridge).trim()}`)
|
|
66
|
+
.sort();
|
|
67
|
+
}
|
|
62
68
|
function actionRequired(index, byStatus) {
|
|
63
69
|
const actions = [...index.parseErrors, ...index.invalidFilenames.map((filePath) => `bad filename: ${filePath}`)];
|
|
64
70
|
if (byStatus.blocked.length > 0) {
|
|
@@ -99,6 +105,11 @@ function buildTaskBoard(index) {
|
|
|
99
105
|
fullLines.push("## active sessions");
|
|
100
106
|
fullLines.push(active.map((line) => `- ${line}`).join("\n"));
|
|
101
107
|
}
|
|
108
|
+
const activeBridges = activeBridgeLines(index.tasks);
|
|
109
|
+
if (activeBridges.length > 0) {
|
|
110
|
+
fullLines.push("## active bridges");
|
|
111
|
+
fullLines.push(activeBridges.map((line) => `- ${line}`).join("\n"));
|
|
112
|
+
}
|
|
102
113
|
return {
|
|
103
114
|
compact,
|
|
104
115
|
full: fullLines.join("\n\n"),
|
|
@@ -106,6 +117,7 @@ function buildTaskBoard(index) {
|
|
|
106
117
|
actionRequired: actionRequired(index, byStatus),
|
|
107
118
|
unresolvedDependencies: unresolved,
|
|
108
119
|
activeSessions: active,
|
|
120
|
+
activeBridges,
|
|
109
121
|
};
|
|
110
122
|
}
|
|
111
123
|
function boardStatus(board, status) {
|
|
@@ -115,6 +115,12 @@ class FileTaskModule {
|
|
|
115
115
|
frontmatter.parent_task = null;
|
|
116
116
|
frontmatter.depends_on = [];
|
|
117
117
|
}
|
|
118
|
+
if (input.activeBridge && input.activeBridge.trim()) {
|
|
119
|
+
frontmatter.active_bridge = input.activeBridge.trim();
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(input.bridgeSessions) && input.bridgeSessions.length > 0) {
|
|
122
|
+
frontmatter.bridge_sessions = input.bridgeSessions.filter((value) => typeof value === "string" && value.trim());
|
|
123
|
+
}
|
|
118
124
|
const content = (0, parser_1.renderTaskFile)(frontmatter, input.body);
|
|
119
125
|
const validation = (0, middleware_1.validateWrite)(filePath, content);
|
|
120
126
|
if (!validation.ok) {
|
|
@@ -125,6 +131,21 @@ class FileTaskModule {
|
|
|
125
131
|
(0, scanner_1.clearTaskScanCache)();
|
|
126
132
|
return filePath;
|
|
127
133
|
}
|
|
134
|
+
bindBridge(name, input) {
|
|
135
|
+
const task = this.getTask(name);
|
|
136
|
+
if (!task) {
|
|
137
|
+
return { ok: false, reason: `task not found: ${name}` };
|
|
138
|
+
}
|
|
139
|
+
const content = fs.readFileSync(task.path, "utf-8");
|
|
140
|
+
const parsed = (0, parser_1.parseTaskFile)(content, task.path);
|
|
141
|
+
const frontmatter = removeRuntimeFrontmatter(parsed.frontmatter);
|
|
142
|
+
frontmatter.active_bridge = input.bridgeId.trim();
|
|
143
|
+
frontmatter.bridge_sessions = input.sessionRefs.filter((value) => value.trim().length > 0);
|
|
144
|
+
frontmatter.updated = formatDate();
|
|
145
|
+
fs.writeFileSync(task.path, (0, parser_1.renderTaskFile)(frontmatter, parsed.body), "utf-8");
|
|
146
|
+
(0, scanner_1.clearTaskScanCache)();
|
|
147
|
+
return { ok: true, path: task.path };
|
|
148
|
+
}
|
|
128
149
|
updateStatus(name, toStatus) {
|
|
129
150
|
const normalized = (0, transitions_1.normalizeTaskStatus)(toStatus);
|
|
130
151
|
if (!normalized) {
|
|
@@ -44,6 +44,8 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
44
44
|
const identity_1 = require("../heart/identity");
|
|
45
45
|
const socket_client_1 = require("../heart/daemon/socket-client");
|
|
46
46
|
const thoughts_1 = require("../heart/daemon/thoughts");
|
|
47
|
+
const manager_1 = require("../heart/bridges/manager");
|
|
48
|
+
const session_recall_1 = require("../heart/session-recall");
|
|
47
49
|
const tools_1 = require("./coding/tools");
|
|
48
50
|
const memory_1 = require("../mind/memory");
|
|
49
51
|
const pending_1 = require("../mind/pending");
|
|
@@ -61,6 +63,16 @@ function buildContextDiff(lines, changeStart, changeEnd, contextSize = 3) {
|
|
|
61
63
|
}
|
|
62
64
|
return result.join("\n");
|
|
63
65
|
}
|
|
66
|
+
const NO_SESSION_FOUND_MESSAGE = "no session found for that friend/channel/key combination.";
|
|
67
|
+
const EMPTY_SESSION_MESSAGE = "session exists but has no non-system messages.";
|
|
68
|
+
async function recallSessionSafely(options) {
|
|
69
|
+
try {
|
|
70
|
+
return await (0, session_recall_1.recallSession)(options);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return { kind: "missing" };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
64
76
|
exports.baseToolDefinitions = [
|
|
65
77
|
{
|
|
66
78
|
tool: {
|
|
@@ -585,6 +597,103 @@ exports.baseToolDefinitions = [
|
|
|
585
597
|
},
|
|
586
598
|
},
|
|
587
599
|
// -- cross-session awareness --
|
|
600
|
+
{
|
|
601
|
+
tool: {
|
|
602
|
+
type: "function",
|
|
603
|
+
function: {
|
|
604
|
+
name: "bridge_manage",
|
|
605
|
+
description: "create and manage shared live-work bridges across already-active sessions.",
|
|
606
|
+
parameters: {
|
|
607
|
+
type: "object",
|
|
608
|
+
properties: {
|
|
609
|
+
action: {
|
|
610
|
+
type: "string",
|
|
611
|
+
enum: ["begin", "attach", "status", "promote_task", "complete", "cancel"],
|
|
612
|
+
},
|
|
613
|
+
bridgeId: { type: "string", description: "bridge id for all actions except begin" },
|
|
614
|
+
objective: { type: "string", description: "objective for begin" },
|
|
615
|
+
summary: { type: "string", description: "optional concise shared-work summary" },
|
|
616
|
+
friendId: { type: "string", description: "target friend id for attach" },
|
|
617
|
+
channel: { type: "string", description: "target channel for attach" },
|
|
618
|
+
key: { type: "string", description: "target session key for attach (defaults to 'session')" },
|
|
619
|
+
title: { type: "string", description: "task title override for promote_task" },
|
|
620
|
+
category: { type: "string", description: "task category override for promote_task" },
|
|
621
|
+
body: { type: "string", description: "task body override for promote_task" },
|
|
622
|
+
},
|
|
623
|
+
required: ["action"],
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
handler: async (args, ctx) => {
|
|
628
|
+
const manager = (0, manager_1.createBridgeManager)();
|
|
629
|
+
const action = (args.action || "").trim();
|
|
630
|
+
if (action === "begin") {
|
|
631
|
+
if (!ctx?.currentSession) {
|
|
632
|
+
return "bridge_manage begin requires an active session context.";
|
|
633
|
+
}
|
|
634
|
+
const objective = (args.objective || "").trim();
|
|
635
|
+
if (!objective)
|
|
636
|
+
return "objective is required for bridge begin.";
|
|
637
|
+
return (0, manager_1.formatBridgeStatus)(manager.beginBridge({
|
|
638
|
+
objective,
|
|
639
|
+
summary: (args.summary || objective).trim(),
|
|
640
|
+
session: ctx.currentSession,
|
|
641
|
+
}));
|
|
642
|
+
}
|
|
643
|
+
const bridgeId = (args.bridgeId || "").trim();
|
|
644
|
+
if (!bridgeId) {
|
|
645
|
+
return "bridgeId is required for this bridge action.";
|
|
646
|
+
}
|
|
647
|
+
if (action === "attach") {
|
|
648
|
+
const friendId = (args.friendId || "").trim();
|
|
649
|
+
const channel = (args.channel || "").trim();
|
|
650
|
+
const key = (args.key || "session").trim();
|
|
651
|
+
if (!friendId || !channel) {
|
|
652
|
+
return "friendId and channel are required for bridge attach.";
|
|
653
|
+
}
|
|
654
|
+
const sessionPath = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
655
|
+
const recall = await recallSessionSafely({
|
|
656
|
+
sessionPath,
|
|
657
|
+
friendId,
|
|
658
|
+
channel,
|
|
659
|
+
key,
|
|
660
|
+
messageCount: 20,
|
|
661
|
+
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
662
|
+
summarize: ctx?.summarize,
|
|
663
|
+
});
|
|
664
|
+
if (recall.kind === "missing") {
|
|
665
|
+
return NO_SESSION_FOUND_MESSAGE;
|
|
666
|
+
}
|
|
667
|
+
return (0, manager_1.formatBridgeStatus)(manager.attachSession(bridgeId, {
|
|
668
|
+
friendId,
|
|
669
|
+
channel,
|
|
670
|
+
key,
|
|
671
|
+
sessionPath,
|
|
672
|
+
snapshot: recall.kind === "ok" ? recall.snapshot : EMPTY_SESSION_MESSAGE,
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
if (action === "status") {
|
|
676
|
+
const bridge = manager.getBridge(bridgeId);
|
|
677
|
+
if (!bridge)
|
|
678
|
+
return `bridge not found: ${bridgeId}`;
|
|
679
|
+
return (0, manager_1.formatBridgeStatus)(bridge);
|
|
680
|
+
}
|
|
681
|
+
if (action === "promote_task") {
|
|
682
|
+
return (0, manager_1.formatBridgeStatus)(manager.promoteBridgeToTask(bridgeId, {
|
|
683
|
+
title: args.title,
|
|
684
|
+
category: args.category,
|
|
685
|
+
body: args.body,
|
|
686
|
+
}));
|
|
687
|
+
}
|
|
688
|
+
if (action === "complete") {
|
|
689
|
+
return (0, manager_1.formatBridgeStatus)(manager.completeBridge(bridgeId));
|
|
690
|
+
}
|
|
691
|
+
if (action === "cancel") {
|
|
692
|
+
return (0, manager_1.formatBridgeStatus)(manager.cancelBridge(bridgeId));
|
|
693
|
+
}
|
|
694
|
+
return `unknown bridge action: ${action}`;
|
|
695
|
+
},
|
|
696
|
+
},
|
|
588
697
|
{
|
|
589
698
|
tool: {
|
|
590
699
|
type: "function",
|
|
@@ -605,43 +714,36 @@ exports.baseToolDefinitions = [
|
|
|
605
714
|
},
|
|
606
715
|
},
|
|
607
716
|
handler: async (args, ctx) => {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
if (
|
|
615
|
-
|
|
616
|
-
return "status mode is only available for self/inner dialog.";
|
|
617
|
-
}
|
|
618
|
-
const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)((0, identity_1.getAgentRoot)());
|
|
619
|
-
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
620
|
-
return (0, thoughts_1.formatInnerDialogStatus)((0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir));
|
|
621
|
-
}
|
|
622
|
-
const sessFile = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
623
|
-
const raw = fs.readFileSync(sessFile, "utf-8");
|
|
624
|
-
const data = JSON.parse(raw);
|
|
625
|
-
const messages = (data.messages || [])
|
|
626
|
-
.filter((m) => m.role !== "system");
|
|
627
|
-
const tail = messages.slice(-count);
|
|
628
|
-
if (tail.length === 0)
|
|
629
|
-
return "session exists but has no non-system messages.";
|
|
630
|
-
const transcript = tail.map((m) => `[${m.role}] ${m.content}`).join("\n");
|
|
631
|
-
// LLM summarization when summarize function is available
|
|
632
|
-
if (ctx?.summarize) {
|
|
633
|
-
const trustLevel = ctx.context?.friend?.trustLevel ?? "family";
|
|
634
|
-
const isSelfQuery = friendId === "self";
|
|
635
|
-
const instruction = isSelfQuery
|
|
636
|
-
? "summarize this session transcript fully and transparently. this is my own inner dialog — include all details, decisions, and reasoning."
|
|
637
|
-
: `summarize this session transcript. the person asking has trust level: ${trustLevel}. family=full transparency, friend=share work and general topics but protect other people's identities, acquaintance=very guarded minimal disclosure.`;
|
|
638
|
-
return await ctx.summarize(transcript, instruction);
|
|
717
|
+
const friendId = args.friendId;
|
|
718
|
+
const channel = args.channel;
|
|
719
|
+
const key = args.key || "session";
|
|
720
|
+
const count = parseInt(args.messageCount || "20", 10);
|
|
721
|
+
const mode = args.mode || "transcript";
|
|
722
|
+
if (mode === "status") {
|
|
723
|
+
if (friendId !== "self" || channel !== "inner") {
|
|
724
|
+
return "status mode is only available for self/inner dialog.";
|
|
639
725
|
}
|
|
640
|
-
|
|
726
|
+
const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)((0, identity_1.getAgentRoot)());
|
|
727
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
728
|
+
return (0, thoughts_1.formatInnerDialogStatus)((0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir));
|
|
729
|
+
}
|
|
730
|
+
const sessFile = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
731
|
+
const recall = await recallSessionSafely({
|
|
732
|
+
sessionPath: sessFile,
|
|
733
|
+
friendId,
|
|
734
|
+
channel,
|
|
735
|
+
key,
|
|
736
|
+
messageCount: count,
|
|
737
|
+
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
738
|
+
summarize: ctx?.summarize,
|
|
739
|
+
});
|
|
740
|
+
if (recall.kind === "missing") {
|
|
741
|
+
return NO_SESSION_FOUND_MESSAGE;
|
|
641
742
|
}
|
|
642
|
-
|
|
643
|
-
return
|
|
743
|
+
if (recall.kind === "empty") {
|
|
744
|
+
return EMPTY_SESSION_MESSAGE;
|
|
644
745
|
}
|
|
746
|
+
return recall.summary;
|
|
645
747
|
},
|
|
646
748
|
},
|
|
647
749
|
{
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -205,6 +205,8 @@ function summarizeArgs(name, args) {
|
|
|
205
205
|
if (name === "save_friend_note") {
|
|
206
206
|
return summarizeKeyValues(args, ["type", "key", "content"]);
|
|
207
207
|
}
|
|
208
|
+
if (name === "bridge_manage")
|
|
209
|
+
return summarizeKeyValues(args, ["action", "bridgeId", "objective", "friendId", "channel", "key"]);
|
|
208
210
|
if (name === "ado_backlog_list")
|
|
209
211
|
return summarizeKeyValues(args, ["organization", "project"]);
|
|
210
212
|
if (name === "ado_batch_update")
|
|
@@ -604,6 +604,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
|
|
|
604
604
|
try {
|
|
605
605
|
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
606
606
|
channel: "bluebubbles",
|
|
607
|
+
sessionKey: event.chat.sessionKey,
|
|
607
608
|
capabilities: bbCapabilities,
|
|
608
609
|
messages: [userMessage],
|
|
609
610
|
continuityIngressTexts: getBlueBubblesContinuityIngressTexts(event),
|
package/dist/senses/cli.js
CHANGED
|
@@ -752,6 +752,7 @@ async function main(agentName, options) {
|
|
|
752
752
|
// User message passed via input.messages so the pipeline can prepend pending messages to it.
|
|
753
753
|
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
754
754
|
channel: "cli",
|
|
755
|
+
sessionKey: "session",
|
|
755
756
|
capabilities: cliCapabilities,
|
|
756
757
|
messages: [{ role: "user", content: userInput }],
|
|
757
758
|
continuityIngressTexts: getCliContinuityIngressTexts(userInput),
|
|
@@ -4,26 +4,52 @@ exports.createInnerDialogWorker = createInnerDialogWorker;
|
|
|
4
4
|
exports.startInnerDialogWorker = startInnerDialogWorker;
|
|
5
5
|
const inner_dialog_1 = require("./inner-dialog");
|
|
6
6
|
const runtime_1 = require("../nerves/runtime");
|
|
7
|
-
|
|
7
|
+
const identity_1 = require("../heart/identity");
|
|
8
|
+
const pending_1 = require("../mind/pending");
|
|
9
|
+
function createInnerDialogWorker(runTurn = (options) => (0, inner_dialog_1.runInnerDialogTurn)(options), hasPendingWork = () => (0, pending_1.hasPendingMessages)((0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)()))) {
|
|
8
10
|
let running = false;
|
|
11
|
+
let rerunRequested = false;
|
|
12
|
+
let rerunReason = "instinct";
|
|
13
|
+
let rerunTaskId;
|
|
9
14
|
async function run(reason, taskId) {
|
|
10
|
-
if (running)
|
|
15
|
+
if (running) {
|
|
16
|
+
rerunRequested = true;
|
|
17
|
+
rerunReason = reason;
|
|
18
|
+
if (taskId !== undefined) {
|
|
19
|
+
rerunTaskId = taskId;
|
|
20
|
+
}
|
|
11
21
|
return;
|
|
22
|
+
}
|
|
12
23
|
running = true;
|
|
13
24
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
let nextReason = reason;
|
|
26
|
+
let nextTaskId = taskId;
|
|
27
|
+
do {
|
|
28
|
+
rerunRequested = false;
|
|
29
|
+
try {
|
|
30
|
+
await runTurn({ reason: nextReason, taskId: nextTaskId });
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
(0, runtime_1.emitNervesEvent)({
|
|
34
|
+
level: "error",
|
|
35
|
+
component: "senses",
|
|
36
|
+
event: "senses.inner_dialog_worker_error",
|
|
37
|
+
message: "inner dialog worker turn failed",
|
|
38
|
+
meta: {
|
|
39
|
+
reason: nextReason,
|
|
40
|
+
error: error instanceof Error ? error.message : String(error),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (!rerunRequested && hasPendingWork()) {
|
|
45
|
+
rerunRequested = true;
|
|
46
|
+
rerunReason = "instinct";
|
|
47
|
+
}
|
|
48
|
+
nextReason = rerunReason;
|
|
49
|
+
nextTaskId = rerunTaskId;
|
|
50
|
+
rerunReason = "instinct";
|
|
51
|
+
rerunTaskId = undefined;
|
|
52
|
+
} while (rerunRequested);
|
|
27
53
|
}
|
|
28
54
|
finally {
|
|
29
55
|
running = false;
|
|
@@ -337,6 +337,7 @@ async function runInnerDialogTurn(options) {
|
|
|
337
337
|
const traceId = (0, nerves_1.createTraceId)();
|
|
338
338
|
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
339
339
|
channel: "inner",
|
|
340
|
+
sessionKey: "dialog",
|
|
340
341
|
capabilities: innerCapabilities,
|
|
341
342
|
messages: [userMessage],
|
|
342
343
|
continuityIngressTexts: [],
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
8
8
|
exports.handleInboundTurn = handleInboundTurn;
|
|
9
9
|
const runtime_1 = require("../nerves/runtime");
|
|
10
10
|
const continuity_1 = require("./continuity");
|
|
11
|
+
const manager_1 = require("../heart/bridges/manager");
|
|
11
12
|
// ── Pipeline ──────────────────────────────────────────────────────
|
|
12
13
|
async function handleInboundTurn(input) {
|
|
13
14
|
// Step 1: Resolve friend
|
|
@@ -60,6 +61,18 @@ async function handleInboundTurn(input) {
|
|
|
60
61
|
?.map((text) => text.trim())
|
|
61
62
|
.filter((text) => text.length > 0)
|
|
62
63
|
.at(-1);
|
|
64
|
+
const currentSession = {
|
|
65
|
+
friendId: resolvedContext.friend.id,
|
|
66
|
+
channel: input.channel,
|
|
67
|
+
key: input.sessionKey ?? "session",
|
|
68
|
+
sessionPath: session.sessionPath,
|
|
69
|
+
};
|
|
70
|
+
const activeBridges = (0, manager_1.createBridgeManager)().findBridgesForSession({
|
|
71
|
+
friendId: currentSession.friendId,
|
|
72
|
+
channel: currentSession.channel,
|
|
73
|
+
key: currentSession.key,
|
|
74
|
+
});
|
|
75
|
+
const bridgeContext = (0, manager_1.formatBridgeContext)(activeBridges) || undefined;
|
|
63
76
|
// Step 4: Drain pending messages
|
|
64
77
|
const pending = input.drainPending(input.pendingDir);
|
|
65
78
|
// Assemble messages: session messages + pending (formatted) + inbound user messages
|
|
@@ -98,6 +111,8 @@ async function handleInboundTurn(input) {
|
|
|
98
111
|
const existingToolContext = input.runAgentOptions?.toolContext;
|
|
99
112
|
const runAgentOptions = {
|
|
100
113
|
...input.runAgentOptions,
|
|
114
|
+
bridgeContext,
|
|
115
|
+
currentSessionKey: currentSession.key,
|
|
101
116
|
currentObligation,
|
|
102
117
|
mustResolveBeforeHandoff,
|
|
103
118
|
setMustResolveBeforeHandoff: (value) => {
|
|
@@ -109,6 +124,8 @@ async function handleInboundTurn(input) {
|
|
|
109
124
|
...existingToolContext,
|
|
110
125
|
context: resolvedContext,
|
|
111
126
|
friendStore: input.friendStore,
|
|
127
|
+
currentSession,
|
|
128
|
+
activeBridges,
|
|
112
129
|
},
|
|
113
130
|
};
|
|
114
131
|
const result = await input.runAgent(sessionMessages, input.callbacks, input.channel, input.signal, runAgentOptions);
|
package/dist/senses/teams.js
CHANGED
|
@@ -534,6 +534,7 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
534
534
|
// ── Call shared pipeline ──────────────────────────────────────────
|
|
535
535
|
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
536
536
|
channel: "teams",
|
|
537
|
+
sessionKey: conversationId,
|
|
537
538
|
capabilities: teamsCapabilities,
|
|
538
539
|
messages: [{ role: "user", content: currentText }],
|
|
539
540
|
continuityIngressTexts: [currentText],
|