@ouro.bot/cli 0.1.0-alpha.49 → 0.1.0-alpha.50
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 +7 -0
- package/dist/heart/active-work.js +157 -0
- package/dist/heart/bridges/manager.js +37 -0
- package/dist/heart/bridges/state-machine.js +20 -0
- package/dist/heart/core.js +6 -1
- package/dist/heart/daemon/daemon-cli.js +15 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/session-activity.js +169 -0
- package/dist/mind/context.js +14 -6
- package/dist/mind/pending.js +52 -8
- package/dist/mind/prompt.js +33 -66
- package/dist/repertoire/tools-base.js +89 -10
- package/dist/senses/bluebubbles.js +110 -0
- package/dist/senses/cli.js +4 -2
- package/dist/senses/debug-activity.js +26 -5
- package/dist/senses/inner-dialog.js +86 -2
- package/dist/senses/pipeline.js +104 -6
- package/dist/senses/teams.js +17 -3
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
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.50",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Delegated inner work can now proactively surface its completion back into the active BlueBubbles session instead of waiting for a later inbox drain.",
|
|
8
|
+
"Session recall now falls back to the raw transcript when summarization fails, so bridge attachment and cross-session inspection stay truthful instead of claiming the session is missing."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
4
11
|
{
|
|
5
12
|
"version": "0.1.0-alpha.49",
|
|
6
13
|
"changes": [
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.suggestBridgeForActiveWork = suggestBridgeForActiveWork;
|
|
4
|
+
exports.buildActiveWorkFrame = buildActiveWorkFrame;
|
|
5
|
+
exports.formatActiveWorkFrame = formatActiveWorkFrame;
|
|
6
|
+
const runtime_1 = require("../nerves/runtime");
|
|
7
|
+
const state_machine_1 = require("./bridges/state-machine");
|
|
8
|
+
function activityPriority(source) {
|
|
9
|
+
return source === "friend-facing" ? 0 : 1;
|
|
10
|
+
}
|
|
11
|
+
function compareActivity(a, b) {
|
|
12
|
+
const sourceDiff = activityPriority(a.activitySource) - activityPriority(b.activitySource);
|
|
13
|
+
if (sourceDiff !== 0)
|
|
14
|
+
return sourceDiff;
|
|
15
|
+
return b.lastActivityMs - a.lastActivityMs;
|
|
16
|
+
}
|
|
17
|
+
function summarizeLiveTasks(taskBoard) {
|
|
18
|
+
const live = [
|
|
19
|
+
...taskBoard.byStatus.processing,
|
|
20
|
+
...taskBoard.byStatus.validating,
|
|
21
|
+
...taskBoard.byStatus.collaborating,
|
|
22
|
+
];
|
|
23
|
+
return [...new Set(live)];
|
|
24
|
+
}
|
|
25
|
+
function isActiveBridge(bridge) {
|
|
26
|
+
return bridge.lifecycle === "active";
|
|
27
|
+
}
|
|
28
|
+
function hasSharedObligationPressure(input) {
|
|
29
|
+
return (typeof input.currentObligation === "string"
|
|
30
|
+
&& input.currentObligation.trim().length > 0) || input.mustResolveBeforeHandoff
|
|
31
|
+
|| summarizeLiveTasks(input.taskBoard).length > 0;
|
|
32
|
+
}
|
|
33
|
+
function suggestBridgeForActiveWork(input) {
|
|
34
|
+
const candidateSessions = input.friendSessions
|
|
35
|
+
.filter((session) => !input.currentSession
|
|
36
|
+
|| session.friendId !== input.currentSession.friendId
|
|
37
|
+
|| session.channel !== input.currentSession.channel
|
|
38
|
+
|| session.key !== input.currentSession.key)
|
|
39
|
+
.sort(compareActivity);
|
|
40
|
+
const targetSession = candidateSessions[0] ?? null;
|
|
41
|
+
if (!targetSession || !hasSharedObligationPressure(input)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const activeBridge = input.bridges.find(isActiveBridge) ?? null;
|
|
45
|
+
if (activeBridge) {
|
|
46
|
+
const alreadyAttached = activeBridge.attachedSessions.some((session) => session.friendId === targetSession.friendId
|
|
47
|
+
&& session.channel === targetSession.channel
|
|
48
|
+
&& session.key === targetSession.key);
|
|
49
|
+
if (alreadyAttached) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
kind: "attach-existing",
|
|
54
|
+
bridgeId: activeBridge.id,
|
|
55
|
+
targetSession,
|
|
56
|
+
reason: "same-friend-shared-work",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
kind: "begin-new",
|
|
61
|
+
targetSession,
|
|
62
|
+
objectiveHint: input.currentObligation?.trim() || "keep this shared work aligned",
|
|
63
|
+
reason: "same-friend-shared-work",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function formatSessionLabel(session) {
|
|
67
|
+
return `${session.channel}/${session.key}`;
|
|
68
|
+
}
|
|
69
|
+
function buildActiveWorkFrame(input) {
|
|
70
|
+
const friendSessions = input.currentSession
|
|
71
|
+
? input.friendActivity
|
|
72
|
+
.filter((entry) => entry.friendId === input.currentSession?.friendId)
|
|
73
|
+
.sort(compareActivity)
|
|
74
|
+
: [];
|
|
75
|
+
const liveTaskNames = summarizeLiveTasks(input.taskBoard);
|
|
76
|
+
const activeBridgePresent = input.bridges.some(isActiveBridge);
|
|
77
|
+
const centerOfGravity = activeBridgePresent
|
|
78
|
+
? "shared-work"
|
|
79
|
+
: (input.inner.status === "running" || input.inner.hasPending || input.mustResolveBeforeHandoff)
|
|
80
|
+
? "inward-work"
|
|
81
|
+
: "local-turn";
|
|
82
|
+
const frame = {
|
|
83
|
+
currentSession: input.currentSession ?? null,
|
|
84
|
+
currentObligation: input.currentObligation?.trim() || null,
|
|
85
|
+
mustResolveBeforeHandoff: input.mustResolveBeforeHandoff,
|
|
86
|
+
centerOfGravity,
|
|
87
|
+
inner: input.inner,
|
|
88
|
+
bridges: input.bridges,
|
|
89
|
+
taskPressure: {
|
|
90
|
+
compactBoard: input.taskBoard.compact,
|
|
91
|
+
liveTaskNames,
|
|
92
|
+
activeBridges: input.taskBoard.activeBridges,
|
|
93
|
+
},
|
|
94
|
+
friendActivity: {
|
|
95
|
+
freshestForCurrentFriend: friendSessions[0] ?? null,
|
|
96
|
+
otherLiveSessionsForCurrentFriend: friendSessions,
|
|
97
|
+
},
|
|
98
|
+
bridgeSuggestion: suggestBridgeForActiveWork({
|
|
99
|
+
currentSession: input.currentSession,
|
|
100
|
+
currentObligation: input.currentObligation,
|
|
101
|
+
mustResolveBeforeHandoff: input.mustResolveBeforeHandoff,
|
|
102
|
+
bridges: input.bridges,
|
|
103
|
+
taskBoard: input.taskBoard,
|
|
104
|
+
friendSessions,
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
(0, runtime_1.emitNervesEvent)({
|
|
108
|
+
component: "engine",
|
|
109
|
+
event: "engine.active_work_build",
|
|
110
|
+
message: "built shared active-work frame",
|
|
111
|
+
meta: {
|
|
112
|
+
centerOfGravity: frame.centerOfGravity,
|
|
113
|
+
friendId: frame.currentSession?.friendId ?? null,
|
|
114
|
+
bridges: frame.bridges.length,
|
|
115
|
+
liveTasks: frame.taskPressure.liveTaskNames.length,
|
|
116
|
+
liveSessions: frame.friendActivity.otherLiveSessionsForCurrentFriend.length,
|
|
117
|
+
hasBridgeSuggestion: frame.bridgeSuggestion !== null,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
return frame;
|
|
121
|
+
}
|
|
122
|
+
function formatActiveWorkFrame(frame) {
|
|
123
|
+
const lines = ["## active work"];
|
|
124
|
+
if (frame.currentSession) {
|
|
125
|
+
lines.push(`current session: ${formatSessionLabel(frame.currentSession)}`);
|
|
126
|
+
}
|
|
127
|
+
lines.push(`center: ${frame.centerOfGravity}`);
|
|
128
|
+
if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0) {
|
|
129
|
+
lines.push(`obligation: ${frame.currentObligation.trim()}`);
|
|
130
|
+
}
|
|
131
|
+
if (frame.mustResolveBeforeHandoff) {
|
|
132
|
+
lines.push("handoff pressure: must resolve before handoff");
|
|
133
|
+
}
|
|
134
|
+
const innerStatus = frame.inner?.status ?? "idle";
|
|
135
|
+
const innerHasPending = frame.inner?.hasPending === true;
|
|
136
|
+
lines.push(`inner status: ${innerStatus}${innerHasPending ? " (pending queued)" : ""}`);
|
|
137
|
+
if ((frame.taskPressure?.liveTaskNames ?? []).length > 0) {
|
|
138
|
+
lines.push(`live tasks: ${frame.taskPressure.liveTaskNames.join(", ")}`);
|
|
139
|
+
}
|
|
140
|
+
if ((frame.bridges ?? []).length > 0) {
|
|
141
|
+
const bridgeLabels = frame.bridges.map((bridge) => `${bridge.id} [${(0, state_machine_1.bridgeStateLabel)(bridge)}]`);
|
|
142
|
+
lines.push(`bridges: ${bridgeLabels.join(", ")}`);
|
|
143
|
+
}
|
|
144
|
+
if (frame.friendActivity?.freshestForCurrentFriend) {
|
|
145
|
+
lines.push(`freshest friend-facing session: ${formatSessionLabel(frame.friendActivity.freshestForCurrentFriend)}`);
|
|
146
|
+
}
|
|
147
|
+
if (frame.bridgeSuggestion) {
|
|
148
|
+
if (frame.bridgeSuggestion.kind === "attach-existing") {
|
|
149
|
+
lines.push(`suggested bridge: attach ${frame.bridgeSuggestion.bridgeId} -> ${formatSessionLabel(frame.bridgeSuggestion.targetSession)}`);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
lines.push(`suggested bridge: begin -> ${formatSessionLabel(frame.bridgeSuggestion.targetSession)}`);
|
|
153
|
+
lines.push(`bridge objective hint: ${frame.bridgeSuggestion.objectiveHint}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return lines.join("\n");
|
|
157
|
+
}
|
|
@@ -74,6 +74,26 @@ function ensureRunnable(bridge, now, store) {
|
|
|
74
74
|
}
|
|
75
75
|
return bridge;
|
|
76
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
|
+
}
|
|
77
97
|
function createBridgeManager(options = {}) {
|
|
78
98
|
const store = options.store ?? (0, store_1.createBridgeStore)();
|
|
79
99
|
const now = options.now ?? (() => new Date().toISOString());
|
|
@@ -165,6 +185,23 @@ function createBridgeManager(options = {}) {
|
|
|
165
185
|
return store.findBySession(session)
|
|
166
186
|
.filter((bridge) => bridge.lifecycle !== "completed" && bridge.lifecycle !== "cancelled");
|
|
167
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
|
+
},
|
|
168
205
|
promoteBridgeToTask(bridgeId, input = {}) {
|
|
169
206
|
const bridge = requireBridge(bridgeId);
|
|
170
207
|
assertBridgeMutable(bridge, "promote");
|
|
@@ -9,6 +9,7 @@ exports.advanceBridgeAfterTurn = advanceBridgeAfterTurn;
|
|
|
9
9
|
exports.suspendBridge = suspendBridge;
|
|
10
10
|
exports.completeBridge = completeBridge;
|
|
11
11
|
exports.cancelBridge = cancelBridge;
|
|
12
|
+
exports.reconcileBridgeState = reconcileBridgeState;
|
|
12
13
|
const runtime_1 = require("../../nerves/runtime");
|
|
13
14
|
function transition(state, next, action) {
|
|
14
15
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -113,3 +114,22 @@ function cancelBridge(state) {
|
|
|
113
114
|
}
|
|
114
115
|
return transition(state, { lifecycle: "cancelled", runtime: "idle" }, "cancel");
|
|
115
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
|
+
}
|
package/dist/heart/core.js
CHANGED
|
@@ -359,6 +359,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
359
359
|
let overflowRetried = false;
|
|
360
360
|
let retryCount = 0;
|
|
361
361
|
let outcome = "complete";
|
|
362
|
+
let completion;
|
|
362
363
|
let sawSteeringFollowUp = false;
|
|
363
364
|
let mustResolveBeforeHandoffActive = options?.mustResolveBeforeHandoff === true;
|
|
364
365
|
// Prevent MaxListenersExceeded warning — each iteration adds a listener
|
|
@@ -453,6 +454,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
453
454
|
const validClosure = answer != null
|
|
454
455
|
&& (!mustResolveBeforeHandoffActive || validDirectReply || validTerminalIntent);
|
|
455
456
|
if (validClosure) {
|
|
457
|
+
completion = {
|
|
458
|
+
answer,
|
|
459
|
+
intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
|
|
460
|
+
};
|
|
456
461
|
if (result.finalAnswerStreamed) {
|
|
457
462
|
// The streaming layer already parsed and emitted the answer
|
|
458
463
|
// progressively via FinalAnswerParser. Skip clearing and
|
|
@@ -610,5 +615,5 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
610
615
|
message: "runAgent turn completed",
|
|
611
616
|
meta: { done },
|
|
612
617
|
});
|
|
613
|
-
return { usage: lastUsage, outcome };
|
|
618
|
+
return { usage: lastUsage, outcome, completion };
|
|
614
619
|
}
|
|
@@ -65,6 +65,7 @@ const thoughts_1 = require("./thoughts");
|
|
|
65
65
|
const ouro_bot_global_installer_1 = require("./ouro-bot-global-installer");
|
|
66
66
|
const launchd_1 = require("./launchd");
|
|
67
67
|
const socket_client_1 = require("./socket-client");
|
|
68
|
+
const session_activity_1 = require("../session-activity");
|
|
68
69
|
function stringField(value) {
|
|
69
70
|
return typeof value === "string" ? value : null;
|
|
70
71
|
}
|
|
@@ -1026,6 +1027,20 @@ function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SO
|
|
|
1026
1027
|
const { main } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
1027
1028
|
await main(agentName);
|
|
1028
1029
|
},
|
|
1030
|
+
scanSessions: async () => {
|
|
1031
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
1032
|
+
const agentRoot = (0, identity_1.getAgentRoot)(agentName);
|
|
1033
|
+
return (0, session_activity_1.listSessionActivity)({
|
|
1034
|
+
sessionsDir: path.join(agentRoot, "state", "sessions"),
|
|
1035
|
+
friendsDir: path.join(agentRoot, "friends"),
|
|
1036
|
+
agentName,
|
|
1037
|
+
}).map((entry) => ({
|
|
1038
|
+
friendId: entry.friendId,
|
|
1039
|
+
friendName: entry.friendName,
|
|
1040
|
+
channel: entry.channel,
|
|
1041
|
+
lastActivity: entry.lastActivityAt,
|
|
1042
|
+
}));
|
|
1043
|
+
},
|
|
1029
1044
|
};
|
|
1030
1045
|
}
|
|
1031
1046
|
function toDaemonCommand(command) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decideDelegation = decideDelegation;
|
|
4
|
+
const runtime_1 = require("../nerves/runtime");
|
|
5
|
+
const CROSS_SESSION_TOOLS = new Set(["query_session", "send_message", "bridge_manage"]);
|
|
6
|
+
const FAST_PATH_TOOLS = new Set(["final_answer"]);
|
|
7
|
+
const REFLECTION_PATTERN = /\b(think|reflect|ponder|surface|surfaces|surfaced|sit with|metaboli[sz]e)\b/i;
|
|
8
|
+
const CROSS_SESSION_PATTERN = /\b(other chat|other session|across chats?|across sessions?|keep .* aligned|relay|carry .* across)\b/i;
|
|
9
|
+
function hasExplicitReflection(ingressTexts) {
|
|
10
|
+
return ingressTexts.some((text) => REFLECTION_PATTERN.test(text));
|
|
11
|
+
}
|
|
12
|
+
function hasCrossSessionPressure(ingressTexts, requestedToolNames) {
|
|
13
|
+
if (requestedToolNames.some((name) => CROSS_SESSION_TOOLS.has(name))) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return ingressTexts.some((text) => CROSS_SESSION_PATTERN.test(text));
|
|
17
|
+
}
|
|
18
|
+
function hasNonFastPathToolRequest(requestedToolNames) {
|
|
19
|
+
return requestedToolNames.some((name) => !FAST_PATH_TOOLS.has(name));
|
|
20
|
+
}
|
|
21
|
+
function decideDelegation(input) {
|
|
22
|
+
const requestedToolNames = (input.requestedToolNames ?? [])
|
|
23
|
+
.map((name) => name.trim())
|
|
24
|
+
.filter((name) => name.length > 0);
|
|
25
|
+
const reasons = [];
|
|
26
|
+
if (hasExplicitReflection(input.ingressTexts)) {
|
|
27
|
+
reasons.push("explicit_reflection");
|
|
28
|
+
}
|
|
29
|
+
if (hasCrossSessionPressure(input.ingressTexts, requestedToolNames)) {
|
|
30
|
+
reasons.push("cross_session");
|
|
31
|
+
}
|
|
32
|
+
if (input.activeWork.centerOfGravity === "shared-work" || input.activeWork.bridges.some((bridge) => bridge.lifecycle === "active")) {
|
|
33
|
+
reasons.push("bridge_state");
|
|
34
|
+
}
|
|
35
|
+
if (input.activeWork.taskPressure.liveTaskNames.length > 0) {
|
|
36
|
+
reasons.push("task_state");
|
|
37
|
+
}
|
|
38
|
+
if (hasNonFastPathToolRequest(requestedToolNames)) {
|
|
39
|
+
reasons.push("non_fast_path_tool");
|
|
40
|
+
}
|
|
41
|
+
if (input.mustResolveBeforeHandoff || input.activeWork.mustResolveBeforeHandoff) {
|
|
42
|
+
reasons.push("unresolved_obligation");
|
|
43
|
+
}
|
|
44
|
+
const target = reasons.length === 0 ? "fast-path" : "delegate-inward";
|
|
45
|
+
const decision = {
|
|
46
|
+
target,
|
|
47
|
+
reasons,
|
|
48
|
+
outwardClosureRequired: target === "delegate-inward" && input.channel !== "inner",
|
|
49
|
+
};
|
|
50
|
+
(0, runtime_1.emitNervesEvent)({
|
|
51
|
+
component: "engine",
|
|
52
|
+
event: "engine.delegation_decide",
|
|
53
|
+
message: "computed delegation hint",
|
|
54
|
+
meta: {
|
|
55
|
+
channel: input.channel,
|
|
56
|
+
target: decision.target,
|
|
57
|
+
reasons: decision.reasons,
|
|
58
|
+
outwardClosureRequired: decision.outwardClosureRequired,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
return decision;
|
|
62
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildProgressStory = buildProgressStory;
|
|
4
|
+
exports.renderProgressStory = renderProgressStory;
|
|
5
|
+
const runtime_1 = require("../nerves/runtime");
|
|
6
|
+
function labelForScope(scope) {
|
|
7
|
+
return scope === "inner-delegation" ? "inner work" : "shared work";
|
|
8
|
+
}
|
|
9
|
+
function compactDetail(text) {
|
|
10
|
+
if (typeof text !== "string")
|
|
11
|
+
return null;
|
|
12
|
+
const trimmed = text.trim();
|
|
13
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
14
|
+
}
|
|
15
|
+
function buildProgressStory(input) {
|
|
16
|
+
const detailLines = [
|
|
17
|
+
compactDetail(input.objective),
|
|
18
|
+
compactDetail(input.outcomeText),
|
|
19
|
+
compactDetail(input.bridgeId ? `bridge: ${input.bridgeId}` : null),
|
|
20
|
+
compactDetail(input.taskName ? `task: ${input.taskName}` : null),
|
|
21
|
+
].filter((line) => Boolean(line));
|
|
22
|
+
const story = {
|
|
23
|
+
statusLine: `${labelForScope(input.scope)}: ${input.phase}`,
|
|
24
|
+
detailLines,
|
|
25
|
+
};
|
|
26
|
+
(0, runtime_1.emitNervesEvent)({
|
|
27
|
+
component: "engine",
|
|
28
|
+
event: "engine.progress_story_build",
|
|
29
|
+
message: "built shared progress story",
|
|
30
|
+
meta: {
|
|
31
|
+
scope: input.scope,
|
|
32
|
+
phase: input.phase,
|
|
33
|
+
detailLines: detailLines.length,
|
|
34
|
+
hasBridge: Boolean(input.bridgeId),
|
|
35
|
+
hasTask: Boolean(input.taskName),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
return story;
|
|
39
|
+
}
|
|
40
|
+
function renderProgressStory(story) {
|
|
41
|
+
return [story.statusLine, ...story.detailLines].join("\n");
|
|
42
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
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.listSessionActivity = listSessionActivity;
|
|
37
|
+
exports.findFreshestFriendSession = findFreshestFriendSession;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const runtime_1 = require("../nerves/runtime");
|
|
41
|
+
const DEFAULT_ACTIVE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
|
|
42
|
+
function activityPriority(source) {
|
|
43
|
+
return source === "friend-facing" ? 0 : 1;
|
|
44
|
+
}
|
|
45
|
+
function resolveFriendName(friendId, friendsDir, agentName) {
|
|
46
|
+
if (friendId === "self")
|
|
47
|
+
return agentName;
|
|
48
|
+
try {
|
|
49
|
+
const raw = fs.readFileSync(path.join(friendsDir, `${friendId}.json`), "utf-8");
|
|
50
|
+
const parsed = JSON.parse(raw);
|
|
51
|
+
return parsed.name ?? friendId;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return friendId;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function parseFriendActivity(sessionPath) {
|
|
58
|
+
let mtimeMs;
|
|
59
|
+
try {
|
|
60
|
+
mtimeMs = fs.statSync(sessionPath).mtimeMs;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const raw = fs.readFileSync(sessionPath, "utf-8");
|
|
67
|
+
const parsed = JSON.parse(raw);
|
|
68
|
+
const explicit = parsed?.state?.lastFriendActivityAt;
|
|
69
|
+
if (typeof explicit === "string") {
|
|
70
|
+
const parsedMs = Date.parse(explicit);
|
|
71
|
+
if (Number.isFinite(parsedMs)) {
|
|
72
|
+
return {
|
|
73
|
+
lastActivityMs: parsedMs,
|
|
74
|
+
lastActivityAt: new Date(parsedMs).toISOString(),
|
|
75
|
+
activitySource: "friend-facing",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// fall back to file mtime below
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
lastActivityMs: mtimeMs,
|
|
85
|
+
lastActivityAt: new Date(mtimeMs).toISOString(),
|
|
86
|
+
activitySource: "mtime-fallback",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function listSessionActivity(query) {
|
|
90
|
+
const { sessionsDir, friendsDir, agentName, activeThresholdMs = DEFAULT_ACTIVE_THRESHOLD_MS, currentSession = null, } = query;
|
|
91
|
+
(0, runtime_1.emitNervesEvent)({
|
|
92
|
+
component: "daemon",
|
|
93
|
+
event: "daemon.session_activity_scan",
|
|
94
|
+
message: "scanning session activity",
|
|
95
|
+
meta: {
|
|
96
|
+
sessionsDir,
|
|
97
|
+
currentSession: currentSession ? `${currentSession.friendId}/${currentSession.channel}/${currentSession.key}` : null,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
if (!fs.existsSync(sessionsDir))
|
|
101
|
+
return [];
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
const results = [];
|
|
104
|
+
let friendDirs;
|
|
105
|
+
try {
|
|
106
|
+
friendDirs = fs.readdirSync(sessionsDir);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
for (const friendId of friendDirs) {
|
|
112
|
+
const friendPath = path.join(sessionsDir, friendId);
|
|
113
|
+
let channels;
|
|
114
|
+
try {
|
|
115
|
+
channels = fs.readdirSync(friendPath);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
for (const channel of channels) {
|
|
121
|
+
const channelPath = path.join(friendPath, channel);
|
|
122
|
+
let keys;
|
|
123
|
+
try {
|
|
124
|
+
keys = fs.readdirSync(channelPath);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
for (const keyFile of keys) {
|
|
130
|
+
if (!keyFile.endsWith(".json"))
|
|
131
|
+
continue;
|
|
132
|
+
const key = keyFile.replace(/\.json$/, "");
|
|
133
|
+
if (currentSession && friendId === currentSession.friendId && channel === currentSession.channel && key === currentSession.key) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const sessionPath = path.join(channelPath, keyFile);
|
|
137
|
+
const activity = parseFriendActivity(sessionPath);
|
|
138
|
+
if (!activity)
|
|
139
|
+
continue;
|
|
140
|
+
if (now - activity.lastActivityMs > activeThresholdMs)
|
|
141
|
+
continue;
|
|
142
|
+
results.push({
|
|
143
|
+
friendId,
|
|
144
|
+
friendName: resolveFriendName(friendId, friendsDir, agentName),
|
|
145
|
+
channel,
|
|
146
|
+
key,
|
|
147
|
+
sessionPath,
|
|
148
|
+
lastActivityAt: activity.lastActivityAt,
|
|
149
|
+
lastActivityMs: activity.lastActivityMs,
|
|
150
|
+
activitySource: activity.activitySource,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return results.sort((a, b) => {
|
|
156
|
+
const sourceDiff = activityPriority(a.activitySource) - activityPriority(b.activitySource);
|
|
157
|
+
if (sourceDiff !== 0)
|
|
158
|
+
return sourceDiff;
|
|
159
|
+
return b.lastActivityMs - a.lastActivityMs;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function findFreshestFriendSession(query) {
|
|
163
|
+
const { activeOnly = false, activeThresholdMs = DEFAULT_ACTIVE_THRESHOLD_MS, ...rest } = query;
|
|
164
|
+
const currentSession = rest.currentSession ?? null;
|
|
165
|
+
const all = activeOnly
|
|
166
|
+
? listSessionActivity({ ...rest, activeThresholdMs, currentSession })
|
|
167
|
+
: listSessionActivity({ ...rest, activeThresholdMs: Number.MAX_SAFE_INTEGER, currentSession });
|
|
168
|
+
return all.find((entry) => entry.friendId === query.friendId) ?? null;
|
|
169
|
+
}
|
package/dist/mind/context.js
CHANGED
|
@@ -243,8 +243,11 @@ function saveSession(filePath, messages, lastUsage, state) {
|
|
|
243
243
|
const envelope = { version: 1, messages };
|
|
244
244
|
if (lastUsage)
|
|
245
245
|
envelope.lastUsage = lastUsage;
|
|
246
|
-
if (state?.mustResolveBeforeHandoff === true) {
|
|
247
|
-
envelope.state = {
|
|
246
|
+
if (state?.mustResolveBeforeHandoff === true || typeof state?.lastFriendActivityAt === "string") {
|
|
247
|
+
envelope.state = {
|
|
248
|
+
...(state?.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
|
|
249
|
+
...(typeof state?.lastFriendActivityAt === "string" ? { lastFriendActivityAt: state.lastFriendActivityAt } : {}),
|
|
250
|
+
};
|
|
248
251
|
}
|
|
249
252
|
fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
|
|
250
253
|
}
|
|
@@ -266,10 +269,15 @@ function loadSession(filePath) {
|
|
|
266
269
|
});
|
|
267
270
|
messages = repairSessionMessages(messages);
|
|
268
271
|
}
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
const rawState = data?.state && typeof data.state === "object" && data.state !== null
|
|
273
|
+
? data.state
|
|
274
|
+
: undefined;
|
|
275
|
+
const state = rawState && (rawState.mustResolveBeforeHandoff === true
|
|
276
|
+
|| typeof rawState.lastFriendActivityAt === "string")
|
|
277
|
+
? {
|
|
278
|
+
...(rawState.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
|
|
279
|
+
...(typeof rawState.lastFriendActivityAt === "string" ? { lastFriendActivityAt: rawState.lastFriendActivityAt } : {}),
|
|
280
|
+
}
|
|
273
281
|
: undefined;
|
|
274
282
|
return { messages, lastUsage: data.lastUsage, state };
|
|
275
283
|
}
|