@ouro.bot/cli 0.1.0-alpha.7 → 0.1.0-alpha.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/assets/ouroboros.png +0 -0
- package/changelog.json +387 -0
- package/dist/heart/active-work.js +178 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/config.js +68 -23
- package/dist/heart/core.js +282 -92
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +409 -0
- package/dist/heart/daemon/daemon-cli.js +1395 -248
- package/dist/heart/daemon/daemon-entry.js +55 -6
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +216 -10
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -82
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +178 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +14 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +307 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +53 -84
- package/dist/heart/daemon/specialist-prompt.js +64 -5
- package/dist/heart/daemon/specialist-tools.js +213 -58
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/thoughts.js +379 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +126 -21
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +74 -9
- package/dist/heart/providers/azure.js +86 -7
- package/dist/heart/providers/github-copilot.js +149 -0
- package/dist/heart/providers/minimax.js +4 -0
- package/dist/heart/providers/openai-codex.js +12 -3
- package/dist/heart/safe-workspace.js +228 -0
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/session-activity.js +169 -0
- package/dist/heart/session-recall.js +116 -0
- package/dist/heart/streaming.js +100 -22
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +27 -11
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/pending.js +72 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +358 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +62 -4
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +279 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +195 -0
- package/dist/repertoire/skills.js +3 -26
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +642 -251
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +93 -52
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +893 -45
- package/dist/senses/cli-layout.js +87 -0
- package/dist/senses/cli.js +348 -144
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +148 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +333 -84
- package/dist/senses/pipeline.js +278 -0
- package/dist/senses/teams.js +573 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/heart/daemon/subagent-installer.js +0 -125
- package/dist/inner-worker-entry.js +0 -4
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
package/dist/heart/streaming.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FinalAnswerParser = void 0;
|
|
3
|
+
exports.FinalAnswerStreamer = exports.FinalAnswerParser = void 0;
|
|
4
4
|
exports.toResponsesInput = toResponsesInput;
|
|
5
5
|
exports.toResponsesTools = toResponsesTools;
|
|
6
6
|
exports.streamChatCompletion = streamChatCompletion;
|
|
@@ -77,6 +77,89 @@ class FinalAnswerParser {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
exports.FinalAnswerParser = FinalAnswerParser;
|
|
80
|
+
// Shared helper: wraps FinalAnswerParser with onClearText + onTextChunk wiring.
|
|
81
|
+
// Used by all streaming providers (Chat Completions, Responses API, Anthropic)
|
|
82
|
+
// so the eager-match streaming pattern lives in one place.
|
|
83
|
+
class FinalAnswerStreamer {
|
|
84
|
+
parser = new FinalAnswerParser();
|
|
85
|
+
_detected = false;
|
|
86
|
+
callbacks;
|
|
87
|
+
constructor(callbacks) {
|
|
88
|
+
this.callbacks = callbacks;
|
|
89
|
+
}
|
|
90
|
+
get detected() { return this._detected; }
|
|
91
|
+
get streamed() { return this.parser.active; }
|
|
92
|
+
/** Mark final_answer as detected. Calls onClearText on the callbacks. */
|
|
93
|
+
activate() {
|
|
94
|
+
if (this._detected)
|
|
95
|
+
return;
|
|
96
|
+
this._detected = true;
|
|
97
|
+
this.callbacks.onClearText?.();
|
|
98
|
+
}
|
|
99
|
+
/** Feed an argument delta through the parser. Emits text via onTextChunk. */
|
|
100
|
+
processDelta(delta) {
|
|
101
|
+
if (!this._detected)
|
|
102
|
+
return;
|
|
103
|
+
const text = this.parser.process(delta);
|
|
104
|
+
if (text)
|
|
105
|
+
this.callbacks.onTextChunk(text);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.FinalAnswerStreamer = FinalAnswerStreamer;
|
|
109
|
+
function toResponsesUserContent(content) {
|
|
110
|
+
if (typeof content === "string") {
|
|
111
|
+
return content;
|
|
112
|
+
}
|
|
113
|
+
if (!Array.isArray(content)) {
|
|
114
|
+
return "";
|
|
115
|
+
}
|
|
116
|
+
const parts = [];
|
|
117
|
+
for (const part of content) {
|
|
118
|
+
if (!part || typeof part !== "object") {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
122
|
+
parts.push({ type: "input_text", text: part.text });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (part.type === "image_url") {
|
|
126
|
+
const imageUrl = typeof part.image_url?.url === "string" ? part.image_url.url : "";
|
|
127
|
+
if (!imageUrl)
|
|
128
|
+
continue;
|
|
129
|
+
parts.push({
|
|
130
|
+
type: "input_image",
|
|
131
|
+
image_url: imageUrl,
|
|
132
|
+
detail: part.image_url?.detail ?? "auto",
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (part.type === "input_audio" &&
|
|
137
|
+
typeof part.input_audio?.data === "string" &&
|
|
138
|
+
(part.input_audio.format === "mp3" || part.input_audio.format === "wav")) {
|
|
139
|
+
parts.push({
|
|
140
|
+
type: "input_audio",
|
|
141
|
+
input_audio: {
|
|
142
|
+
data: part.input_audio.data,
|
|
143
|
+
format: part.input_audio.format,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (part.type === "file") {
|
|
149
|
+
const fileRecord = { type: "input_file" };
|
|
150
|
+
if (typeof part.file?.file_data === "string")
|
|
151
|
+
fileRecord.file_data = part.file.file_data;
|
|
152
|
+
if (typeof part.file?.file_id === "string")
|
|
153
|
+
fileRecord.file_id = part.file.file_id;
|
|
154
|
+
if (typeof part.file?.filename === "string")
|
|
155
|
+
fileRecord.filename = part.file.filename;
|
|
156
|
+
if (typeof part.file?.file_data === "string" || typeof part.file?.file_id === "string") {
|
|
157
|
+
parts.push(fileRecord);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return parts.length > 0 ? parts : "";
|
|
162
|
+
}
|
|
80
163
|
function toResponsesInput(messages) {
|
|
81
164
|
let instructions = "";
|
|
82
165
|
const input = [];
|
|
@@ -90,7 +173,7 @@ function toResponsesInput(messages) {
|
|
|
90
173
|
}
|
|
91
174
|
if (msg.role === "user") {
|
|
92
175
|
const u = msg;
|
|
93
|
-
input.push({ role: "user", content:
|
|
176
|
+
input.push({ role: "user", content: toResponsesUserContent(u.content) });
|
|
94
177
|
continue;
|
|
95
178
|
}
|
|
96
179
|
if (msg.role === "assistant") {
|
|
@@ -102,7 +185,10 @@ function toResponsesInput(messages) {
|
|
|
102
185
|
}
|
|
103
186
|
}
|
|
104
187
|
if (a.content) {
|
|
105
|
-
|
|
188
|
+
const assistantItem = { role: "assistant", content: typeof a.content === "string" ? a.content : "" };
|
|
189
|
+
if (a.phase)
|
|
190
|
+
assistantItem.phase = a.phase;
|
|
191
|
+
input.push(assistantItem);
|
|
106
192
|
}
|
|
107
193
|
if (a.tool_calls) {
|
|
108
194
|
for (const tc of a.tool_calls) {
|
|
@@ -155,8 +241,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
155
241
|
let toolCalls = {};
|
|
156
242
|
let streamStarted = false;
|
|
157
243
|
let usage;
|
|
158
|
-
const
|
|
159
|
-
let finalAnswerDetected = false;
|
|
244
|
+
const answerStreamer = new FinalAnswerStreamer(callbacks);
|
|
160
245
|
// State machine for parsing inline <think> tags (MiniMax pattern)
|
|
161
246
|
let contentBuf = "";
|
|
162
247
|
let inThinkTag = false;
|
|
@@ -274,21 +359,18 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
274
359
|
// Detect final_answer tool call on first name delta.
|
|
275
360
|
// Only activate streaming if this is the sole tool call (index 0
|
|
276
361
|
// and no other indices seen). Mixed calls are rejected by core.ts.
|
|
277
|
-
if (tc.function.name === "final_answer" && !
|
|
362
|
+
if (tc.function.name === "final_answer" && !answerStreamer.detected
|
|
278
363
|
&& tc.index === 0 && Object.keys(toolCalls).length === 1) {
|
|
279
|
-
|
|
280
|
-
callbacks.onClearText?.();
|
|
364
|
+
answerStreamer.activate();
|
|
281
365
|
}
|
|
282
366
|
}
|
|
283
367
|
if (tc.function?.arguments) {
|
|
284
368
|
toolCalls[tc.index].arguments += tc.function.arguments;
|
|
285
369
|
// Feed final_answer argument deltas to the parser for progressive
|
|
286
370
|
// streaming, but only when it appears to be the sole tool call.
|
|
287
|
-
if (
|
|
371
|
+
if (answerStreamer.detected && toolCalls[tc.index].name === "final_answer"
|
|
288
372
|
&& Object.keys(toolCalls).length === 1) {
|
|
289
|
-
|
|
290
|
-
if (text)
|
|
291
|
-
callbacks.onTextChunk(text);
|
|
373
|
+
answerStreamer.processDelta(tc.function.arguments);
|
|
292
374
|
}
|
|
293
375
|
}
|
|
294
376
|
}
|
|
@@ -302,7 +384,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
302
384
|
toolCalls: Object.values(toolCalls),
|
|
303
385
|
outputItems: [],
|
|
304
386
|
usage,
|
|
305
|
-
finalAnswerStreamed:
|
|
387
|
+
finalAnswerStreamed: answerStreamer.streamed,
|
|
306
388
|
};
|
|
307
389
|
}
|
|
308
390
|
async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
@@ -320,9 +402,8 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
320
402
|
const outputItems = [];
|
|
321
403
|
let currentToolCall = null;
|
|
322
404
|
let usage;
|
|
323
|
-
const
|
|
405
|
+
const answerStreamer = new FinalAnswerStreamer(callbacks);
|
|
324
406
|
let functionCallCount = 0;
|
|
325
|
-
let finalAnswerDetected = false;
|
|
326
407
|
for await (const event of response) {
|
|
327
408
|
if (signal?.aborted)
|
|
328
409
|
break;
|
|
@@ -355,8 +436,7 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
355
436
|
// Only activate when this is the first (and so far only) function call.
|
|
356
437
|
// Mixed calls are rejected by core.ts; no need to stream their args.
|
|
357
438
|
if (String(event.item.name) === "final_answer" && functionCallCount === 1) {
|
|
358
|
-
|
|
359
|
-
callbacks.onClearText?.();
|
|
439
|
+
answerStreamer.activate();
|
|
360
440
|
}
|
|
361
441
|
}
|
|
362
442
|
break;
|
|
@@ -366,11 +446,9 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
366
446
|
currentToolCall.arguments += event.delta;
|
|
367
447
|
// Feed final_answer argument deltas to the parser for progressive
|
|
368
448
|
// streaming, but only when it appears to be the sole function call.
|
|
369
|
-
if (
|
|
449
|
+
if (answerStreamer.detected && currentToolCall.name === "final_answer"
|
|
370
450
|
&& functionCallCount === 1) {
|
|
371
|
-
|
|
372
|
-
if (text)
|
|
373
|
-
callbacks.onTextChunk(text);
|
|
451
|
+
answerStreamer.processDelta(String(event.delta));
|
|
374
452
|
}
|
|
375
453
|
}
|
|
376
454
|
break;
|
|
@@ -410,6 +488,6 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
410
488
|
toolCalls,
|
|
411
489
|
outputItems,
|
|
412
490
|
usage,
|
|
413
|
-
finalAnswerStreamed:
|
|
491
|
+
finalAnswerStreamed: answerStreamer.streamed,
|
|
414
492
|
};
|
|
415
493
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listTargetSessionCandidates = listTargetSessionCandidates;
|
|
4
|
+
exports.formatTargetSessionCandidates = formatTargetSessionCandidates;
|
|
5
|
+
const session_recall_1 = require("./session-recall");
|
|
6
|
+
const session_activity_1 = require("./session-activity");
|
|
7
|
+
const trust_explanation_1 = require("../mind/friends/trust-explanation");
|
|
8
|
+
const runtime_1 = require("../nerves/runtime");
|
|
9
|
+
function synthesizeFriendRecord(candidate) {
|
|
10
|
+
return {
|
|
11
|
+
id: candidate.friendId,
|
|
12
|
+
name: candidate.friendName,
|
|
13
|
+
role: "stranger",
|
|
14
|
+
trustLevel: "stranger",
|
|
15
|
+
connections: [],
|
|
16
|
+
externalIds: [],
|
|
17
|
+
tenantMemberships: [],
|
|
18
|
+
toolPreferences: {},
|
|
19
|
+
notes: {},
|
|
20
|
+
totalTokens: 0,
|
|
21
|
+
createdAt: new Date(0).toISOString(),
|
|
22
|
+
updatedAt: new Date(0).toISOString(),
|
|
23
|
+
schemaVersion: 1,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function deliveryPriority(mode) {
|
|
27
|
+
if (mode === "deliver_now")
|
|
28
|
+
return 0;
|
|
29
|
+
if (mode === "queue_only")
|
|
30
|
+
return 1;
|
|
31
|
+
return 2;
|
|
32
|
+
}
|
|
33
|
+
function activityPriority(source) {
|
|
34
|
+
return source === "friend-facing" ? 0 : 1;
|
|
35
|
+
}
|
|
36
|
+
function describeDelivery(candidate) {
|
|
37
|
+
if (candidate.channel !== "bluebubbles" && candidate.channel !== "teams") {
|
|
38
|
+
return { mode: "blocked", reason: "this channel does not support proactive outward delivery yet" };
|
|
39
|
+
}
|
|
40
|
+
if (candidate.trust.level === "family" || candidate.trust.level === "friend") {
|
|
41
|
+
return { mode: "deliver_now", reason: "directly trusted target on a proactive-delivery channel" };
|
|
42
|
+
}
|
|
43
|
+
return { mode: "queue_only", reason: "visible as a live chat, but immediate delivery still needs explicit cross-chat authorization" };
|
|
44
|
+
}
|
|
45
|
+
async function listTargetSessionCandidates(input) {
|
|
46
|
+
(0, runtime_1.emitNervesEvent)({
|
|
47
|
+
component: "engine",
|
|
48
|
+
event: "engine.target_resolution_start",
|
|
49
|
+
message: "listing live target session candidates",
|
|
50
|
+
meta: {
|
|
51
|
+
sessionsDir: input.sessionsDir,
|
|
52
|
+
currentSession: input.currentSession
|
|
53
|
+
? `${input.currentSession.friendId}/${input.currentSession.channel}/${input.currentSession.key}`
|
|
54
|
+
: null,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const activity = (0, session_activity_1.listSessionActivity)({
|
|
58
|
+
sessionsDir: input.sessionsDir,
|
|
59
|
+
friendsDir: input.friendsDir,
|
|
60
|
+
agentName: input.agentName,
|
|
61
|
+
currentSession: input.currentSession ?? null,
|
|
62
|
+
}).filter((entry) => entry.channel !== "inner");
|
|
63
|
+
const candidates = [];
|
|
64
|
+
for (const entry of activity) {
|
|
65
|
+
const friend = await input.friendStore.get(entry.friendId) ?? synthesizeFriendRecord(entry);
|
|
66
|
+
const trust = (0, trust_explanation_1.describeTrustContext)({
|
|
67
|
+
friend,
|
|
68
|
+
channel: entry.channel,
|
|
69
|
+
});
|
|
70
|
+
const recall = await (0, session_recall_1.recallSession)({
|
|
71
|
+
sessionPath: entry.sessionPath,
|
|
72
|
+
friendId: entry.friendId,
|
|
73
|
+
channel: entry.channel,
|
|
74
|
+
key: entry.key,
|
|
75
|
+
messageCount: 6,
|
|
76
|
+
summarize: input.summarize,
|
|
77
|
+
trustLevel: trust.level,
|
|
78
|
+
});
|
|
79
|
+
const snapshot = recall.kind === "ok"
|
|
80
|
+
? recall.snapshot
|
|
81
|
+
: recall.kind === "empty"
|
|
82
|
+
? "recent focus: no recent visible messages"
|
|
83
|
+
: "recent focus: session transcript unavailable";
|
|
84
|
+
const delivery = describeDelivery({
|
|
85
|
+
channel: entry.channel,
|
|
86
|
+
trust,
|
|
87
|
+
});
|
|
88
|
+
candidates.push({
|
|
89
|
+
friendId: entry.friendId,
|
|
90
|
+
friendName: entry.friendName,
|
|
91
|
+
channel: entry.channel,
|
|
92
|
+
key: entry.key,
|
|
93
|
+
sessionPath: entry.sessionPath,
|
|
94
|
+
snapshot,
|
|
95
|
+
trust,
|
|
96
|
+
delivery,
|
|
97
|
+
lastActivityAt: entry.lastActivityAt,
|
|
98
|
+
lastActivityMs: entry.lastActivityMs,
|
|
99
|
+
activitySource: entry.activitySource,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return candidates.sort((a, b) => {
|
|
103
|
+
const deliveryDiff = deliveryPriority(a.delivery.mode) - deliveryPriority(b.delivery.mode);
|
|
104
|
+
if (deliveryDiff !== 0)
|
|
105
|
+
return deliveryDiff;
|
|
106
|
+
const sourceDiff = activityPriority(a.activitySource) - activityPriority(b.activitySource);
|
|
107
|
+
if (sourceDiff !== 0)
|
|
108
|
+
return sourceDiff;
|
|
109
|
+
return b.lastActivityMs - a.lastActivityMs;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function formatTargetSessionCandidates(candidates) {
|
|
113
|
+
if (candidates.length === 0)
|
|
114
|
+
return "";
|
|
115
|
+
const lines = ["## candidate target chats"];
|
|
116
|
+
for (const candidate of candidates) {
|
|
117
|
+
lines.push(`- ${candidate.friendName} [${candidate.friendId}] via ${candidate.channel}/${candidate.key}`);
|
|
118
|
+
lines.push(` trust: ${candidate.trust.level} (${candidate.trust.basis}) — ${candidate.trust.summary}`);
|
|
119
|
+
lines.push(` delivery: ${candidate.delivery.mode} — ${candidate.delivery.reason}`);
|
|
120
|
+
lines.push(` snapshot: ${candidate.snapshot}`);
|
|
121
|
+
}
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createTurnCoordinator = createTurnCoordinator;
|
|
4
|
+
exports.withSharedTurnLock = withSharedTurnLock;
|
|
5
|
+
exports.tryBeginSharedTurn = tryBeginSharedTurn;
|
|
6
|
+
exports.endSharedTurn = endSharedTurn;
|
|
7
|
+
exports.isSharedTurnActive = isSharedTurnActive;
|
|
8
|
+
exports.enqueueSharedFollowUp = enqueueSharedFollowUp;
|
|
9
|
+
exports.drainSharedFollowUps = drainSharedFollowUps;
|
|
4
10
|
const runtime_1 = require("../nerves/runtime");
|
|
11
|
+
function scopedKey(scope, key) {
|
|
12
|
+
return `${scope}:${key}`;
|
|
13
|
+
}
|
|
5
14
|
function createTurnCoordinator() {
|
|
6
15
|
const turnLocks = new Map();
|
|
7
16
|
const activeTurns = new Set();
|
|
@@ -60,3 +69,22 @@ function createTurnCoordinator() {
|
|
|
60
69
|
},
|
|
61
70
|
};
|
|
62
71
|
}
|
|
72
|
+
const _sharedTurnCoordinator = createTurnCoordinator();
|
|
73
|
+
function withSharedTurnLock(scope, key, fn) {
|
|
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));
|
|
90
|
+
}
|
|
@@ -87,7 +87,19 @@ function readFacts(memoryRoot) {
|
|
|
87
87
|
const raw = fs.readFileSync(factsPath, "utf8").trim();
|
|
88
88
|
if (!raw)
|
|
89
89
|
return [];
|
|
90
|
-
|
|
90
|
+
const facts = [];
|
|
91
|
+
for (const line of raw.split("\n")) {
|
|
92
|
+
const trimmed = line.trim();
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
continue;
|
|
95
|
+
try {
|
|
96
|
+
facts.push(JSON.parse(trimmed));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Skip corrupt lines (e.g. partial write from a crash).
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return facts;
|
|
91
103
|
}
|
|
92
104
|
function getLatestUserText(messages) {
|
|
93
105
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -190,7 +202,7 @@ async function injectAssociativeRecall(messages, options) {
|
|
|
190
202
|
event: "mind.associative_recall_error",
|
|
191
203
|
message: "associative recall failed",
|
|
192
204
|
meta: {
|
|
193
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
205
|
+
reason: error instanceof Error ? error.message : /* v8 ignore start -- defensive: non-Error catch branch @preserve */ String(error) /* v8 ignore stop */,
|
|
194
206
|
},
|
|
195
207
|
});
|
|
196
208
|
}
|
|
@@ -34,6 +34,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CANONICAL_BUNDLE_MANIFEST = void 0;
|
|
37
|
+
exports.getChangelogPath = getChangelogPath;
|
|
38
|
+
exports.getPackageVersion = getPackageVersion;
|
|
39
|
+
exports.createBundleMeta = createBundleMeta;
|
|
40
|
+
exports.backfillBundleMeta = backfillBundleMeta;
|
|
41
|
+
exports.resetBackfillTracking = resetBackfillTracking;
|
|
37
42
|
exports.isCanonicalBundlePath = isCanonicalBundlePath;
|
|
38
43
|
exports.findNonCanonicalBundlePaths = findNonCanonicalBundlePaths;
|
|
39
44
|
const fs = __importStar(require("fs"));
|
|
@@ -41,6 +46,7 @@ const path = __importStar(require("path"));
|
|
|
41
46
|
const runtime_1 = require("../nerves/runtime");
|
|
42
47
|
exports.CANONICAL_BUNDLE_MANIFEST = [
|
|
43
48
|
{ path: "agent.json", kind: "file" },
|
|
49
|
+
{ path: "bundle-meta.json", kind: "file" },
|
|
44
50
|
{ path: "psyche/SOUL.md", kind: "file" },
|
|
45
51
|
{ path: "psyche/IDENTITY.md", kind: "file" },
|
|
46
52
|
{ path: "psyche/LORE.md", kind: "file" },
|
|
@@ -48,11 +54,75 @@ exports.CANONICAL_BUNDLE_MANIFEST = [
|
|
|
48
54
|
{ path: "psyche/ASPIRATIONS.md", kind: "file" },
|
|
49
55
|
{ path: "psyche/memory", kind: "dir" },
|
|
50
56
|
{ path: "friends", kind: "dir" },
|
|
57
|
+
{ path: "state", kind: "dir" },
|
|
51
58
|
{ path: "tasks", kind: "dir" },
|
|
52
59
|
{ path: "skills", kind: "dir" },
|
|
53
60
|
{ path: "senses", kind: "dir" },
|
|
54
61
|
{ path: "senses/teams", kind: "dir" },
|
|
55
62
|
];
|
|
63
|
+
function getChangelogPath() {
|
|
64
|
+
const changelogPath = path.resolve(__dirname, "../../changelog.json");
|
|
65
|
+
(0, runtime_1.emitNervesEvent)({
|
|
66
|
+
component: "mind",
|
|
67
|
+
event: "mind.changelog_path_resolved",
|
|
68
|
+
message: "resolved changelog path",
|
|
69
|
+
meta: { path: changelogPath },
|
|
70
|
+
});
|
|
71
|
+
return changelogPath;
|
|
72
|
+
}
|
|
73
|
+
function getPackageVersion() {
|
|
74
|
+
const packageJsonPath = path.resolve(__dirname, "../../package.json");
|
|
75
|
+
const raw = fs.readFileSync(packageJsonPath, "utf-8");
|
|
76
|
+
const parsed = JSON.parse(raw);
|
|
77
|
+
(0, runtime_1.emitNervesEvent)({
|
|
78
|
+
component: "mind",
|
|
79
|
+
event: "mind.package_version_read",
|
|
80
|
+
message: "read package version",
|
|
81
|
+
meta: { version: parsed.version },
|
|
82
|
+
});
|
|
83
|
+
return parsed.version;
|
|
84
|
+
}
|
|
85
|
+
function createBundleMeta() {
|
|
86
|
+
return {
|
|
87
|
+
runtimeVersion: getPackageVersion(),
|
|
88
|
+
bundleSchemaVersion: 1,
|
|
89
|
+
lastUpdated: new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const _backfilledRoots = new Set();
|
|
93
|
+
/**
|
|
94
|
+
* If bundle-meta.json is missing from the agent root, create it with current runtime version.
|
|
95
|
+
* This backfills existing agent bundles that were created before bundle-meta.json was introduced.
|
|
96
|
+
* Only attempts once per bundleRoot per process.
|
|
97
|
+
*/
|
|
98
|
+
function backfillBundleMeta(bundleRoot) {
|
|
99
|
+
if (_backfilledRoots.has(bundleRoot))
|
|
100
|
+
return;
|
|
101
|
+
_backfilledRoots.add(bundleRoot);
|
|
102
|
+
const metaPath = path.join(bundleRoot, "bundle-meta.json");
|
|
103
|
+
try {
|
|
104
|
+
if (fs.existsSync(metaPath)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const meta = createBundleMeta();
|
|
108
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + "\n", "utf-8");
|
|
109
|
+
(0, runtime_1.emitNervesEvent)({
|
|
110
|
+
component: "mind",
|
|
111
|
+
event: "mind.bundle_meta_backfill",
|
|
112
|
+
message: "backfilled missing bundle-meta.json",
|
|
113
|
+
meta: { bundleRoot },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Non-blocking: if we can't write, that's okay
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Reset the backfill tracking set. Used in tests.
|
|
122
|
+
*/
|
|
123
|
+
function resetBackfillTracking() {
|
|
124
|
+
_backfilledRoots.clear();
|
|
125
|
+
}
|
|
56
126
|
const CANONICAL_FILE_PATHS = new Set(exports.CANONICAL_BUNDLE_MANIFEST
|
|
57
127
|
.filter((entry) => entry.kind === "file")
|
|
58
128
|
.map((entry) => entry.path));
|
package/dist/mind/context.js
CHANGED
|
@@ -50,17 +50,17 @@ function buildTrimmableBlocks(messages) {
|
|
|
50
50
|
let i = 0;
|
|
51
51
|
while (i < messages.length) {
|
|
52
52
|
const msg = messages[i];
|
|
53
|
-
if (msg
|
|
53
|
+
if (msg.role === "system") {
|
|
54
54
|
i++;
|
|
55
55
|
continue;
|
|
56
56
|
}
|
|
57
57
|
// Tool coherence block: assistant message with tool_calls + immediately following tool results
|
|
58
|
-
if (msg
|
|
58
|
+
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
59
59
|
const indices = [i];
|
|
60
60
|
i++;
|
|
61
61
|
while (i < messages.length) {
|
|
62
62
|
const next = messages[i];
|
|
63
|
-
if (next
|
|
63
|
+
if (next.role !== "tool")
|
|
64
64
|
break;
|
|
65
65
|
indices.push(i);
|
|
66
66
|
i++;
|
|
@@ -78,13 +78,13 @@ function buildTrimmableBlocks(messages) {
|
|
|
78
78
|
function getSystemMessageIndices(messages) {
|
|
79
79
|
const indices = [];
|
|
80
80
|
for (let i = 0; i < messages.length; i++) {
|
|
81
|
-
if (messages[i]
|
|
81
|
+
if (messages[i].role === "system")
|
|
82
82
|
indices.push(i);
|
|
83
83
|
}
|
|
84
84
|
return indices;
|
|
85
85
|
}
|
|
86
86
|
function buildTrimmedMessages(messages, kept) {
|
|
87
|
-
return messages.filter((m, idx) => m
|
|
87
|
+
return messages.filter((m, idx) => m.role === "system" || kept.has(idx));
|
|
88
88
|
}
|
|
89
89
|
function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
90
90
|
const targetTokens = Math.floor(maxTokens * (1 - contextMargin / 100));
|
|
@@ -132,7 +132,7 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
|
132
132
|
let remaining = actualTokenCount;
|
|
133
133
|
const kept = new Set();
|
|
134
134
|
for (let i = 0; i < messages.length; i++) {
|
|
135
|
-
if (messages[i]
|
|
135
|
+
if (messages[i].role !== "system")
|
|
136
136
|
kept.add(i);
|
|
137
137
|
}
|
|
138
138
|
// Drop oldest blocks until we fall under target.
|
|
@@ -146,7 +146,7 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
|
146
146
|
let trimmed = buildTrimmedMessages(messages, kept);
|
|
147
147
|
// If we're still above budget after dropping everything trimmable, preserve system only.
|
|
148
148
|
if (remaining > targetTokens) {
|
|
149
|
-
trimmed = messages.filter((m) => m
|
|
149
|
+
trimmed = messages.filter((m) => m.role === "system");
|
|
150
150
|
}
|
|
151
151
|
const estimatedAfter = (0, token_estimate_1.estimateTokensForMessages)(trimmed);
|
|
152
152
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -227,7 +227,7 @@ function repairSessionMessages(messages) {
|
|
|
227
227
|
});
|
|
228
228
|
return result;
|
|
229
229
|
}
|
|
230
|
-
function saveSession(filePath, messages, lastUsage) {
|
|
230
|
+
function saveSession(filePath, messages, lastUsage, state) {
|
|
231
231
|
const violations = validateSessionMessages(messages);
|
|
232
232
|
if (violations.length > 0) {
|
|
233
233
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -243,6 +243,12 @@ function saveSession(filePath, messages, lastUsage) {
|
|
|
243
243
|
const envelope = { version: 1, messages };
|
|
244
244
|
if (lastUsage)
|
|
245
245
|
envelope.lastUsage = lastUsage;
|
|
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
|
+
};
|
|
251
|
+
}
|
|
246
252
|
fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
|
|
247
253
|
}
|
|
248
254
|
function loadSession(filePath) {
|
|
@@ -263,13 +269,23 @@ function loadSession(filePath) {
|
|
|
263
269
|
});
|
|
264
270
|
messages = repairSessionMessages(messages);
|
|
265
271
|
}
|
|
266
|
-
|
|
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
|
+
}
|
|
281
|
+
: undefined;
|
|
282
|
+
return { messages, lastUsage: data.lastUsage, state };
|
|
267
283
|
}
|
|
268
284
|
catch {
|
|
269
285
|
return null;
|
|
270
286
|
}
|
|
271
287
|
}
|
|
272
|
-
function postTurn(messages, sessPath, usage, hooks) {
|
|
288
|
+
function postTurn(messages, sessPath, usage, hooks, state) {
|
|
273
289
|
if (hooks?.beforeTrim) {
|
|
274
290
|
try {
|
|
275
291
|
hooks.beforeTrim([...messages]);
|
|
@@ -289,7 +305,7 @@ function postTurn(messages, sessPath, usage, hooks) {
|
|
|
289
305
|
const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
|
|
290
306
|
const trimmed = trimMessages(messages, maxTokens, contextMargin, usage?.input_tokens);
|
|
291
307
|
messages.splice(0, messages.length, ...trimmed);
|
|
292
|
-
saveSession(sessPath, messages, usage);
|
|
308
|
+
saveSession(sessPath, messages, usage, state);
|
|
293
309
|
}
|
|
294
310
|
function deleteSession(filePath) {
|
|
295
311
|
try {
|
|
@@ -11,9 +11,22 @@ exports.ONBOARDING_TOKEN_THRESHOLD = 100_000;
|
|
|
11
11
|
function isOnboarding(friend) {
|
|
12
12
|
return (friend.totalTokens ?? 0) < exports.ONBOARDING_TOKEN_THRESHOLD;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function hasLiveContinuityPressure(state) {
|
|
15
|
+
if (!state)
|
|
16
|
+
return false;
|
|
17
|
+
if (typeof state.currentObligation === "string" && state.currentObligation.trim().length > 0)
|
|
18
|
+
return true;
|
|
19
|
+
if (state.mustResolveBeforeHandoff === true)
|
|
20
|
+
return true;
|
|
21
|
+
if (state.hasQueuedFollowUp === true)
|
|
22
|
+
return true;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function getFirstImpressions(friend, state) {
|
|
15
26
|
if (!isOnboarding(friend))
|
|
16
27
|
return "";
|
|
28
|
+
if (hasLiveContinuityPressure(state))
|
|
29
|
+
return "";
|
|
17
30
|
(0, runtime_1.emitNervesEvent)({
|
|
18
31
|
component: "mind",
|
|
19
32
|
event: "mind.first_impressions",
|
|
@@ -37,7 +50,8 @@ function getFirstImpressions(friend) {
|
|
|
37
50
|
lines.push("- what do they do outside of work that they care about?");
|
|
38
51
|
lines.push("i don't ask all of these at once -- i weave them into conversation naturally, one or two at a time, and i genuinely follow up on what they share.");
|
|
39
52
|
lines.push("i introduce what i can do -- i have tools, integrations, and skills that can help them. i mention these naturally as they become relevant.");
|
|
40
|
-
lines.push("if
|
|
53
|
+
lines.push("if we're already in motion on a task, thread, or follow-up, i do not reset with a generic opener like 'hiya' or 'what do ya need help with?'. i continue directly or ask the specific next question.");
|
|
54
|
+
lines.push("only when the conversation is genuinely fresh and idle, with no active ask or thread in flight, a light opener is okay.");
|
|
41
55
|
lines.push("i save everything i learn immediately with save_friend_note -- names, roles, preferences, projects, anything. the bar is low: if i learned it, i save it.");
|
|
42
56
|
return lines.join("\n");
|
|
43
57
|
}
|