@ouro.bot/cli 0.1.0-alpha.5 → 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/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 +117 -188
- package/assets/ouroboros.png +0 -0
- package/changelog.json +242 -0
- package/dist/heart/active-work.js +157 -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 +81 -8
- package/dist/heart/core.js +145 -50
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/daemon-cli.js +1099 -164
- package/dist/heart/daemon/daemon-entry.js +14 -5
- package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
- package/dist/heart/daemon/daemon.js +184 -9
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +3 -20
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +151 -0
- package/dist/heart/daemon/message-router.js +15 -6
- 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 +1 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-metadata.js +118 -0
- package/dist/heart/daemon/sense-manager.js +290 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +53 -84
- package/dist/heart/daemon/specialist-prompt.js +64 -5
- package/dist/heart/daemon/specialist-tools.js +213 -58
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/subagent-installer.js +48 -7
- package/dist/heart/daemon/thoughts.js +379 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +82 -4
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +16 -2
- 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 +96 -21
- 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 +43 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/types.js +9 -1
- package/dist/mind/memory.js +10 -3
- package/dist/mind/pending.js +72 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +266 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +62 -4
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +462 -245
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +57 -35
- package/dist/senses/bluebubbles-client.js +484 -0
- package/dist/senses/bluebubbles-entry.js +13 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +338 -0
- package/dist/senses/bluebubbles-model.js +261 -0
- package/dist/senses/bluebubbles-mutation-log.js +116 -0
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +1142 -0
- package/dist/senses/cli.js +340 -138
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +148 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +330 -84
- package/dist/senses/pipeline.js +256 -0
- package/dist/senses/teams.js +541 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +46 -33
- package/subagents/work-doer.md +28 -24
- package/subagents/work-merger.md +24 -30
- package/subagents/work-planner.md +44 -27
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/inner-worker-entry.js +0 -4
|
@@ -0,0 +1,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
|
+
}
|
|
@@ -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
|
+
}
|
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") {
|
|
@@ -155,8 +238,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
155
238
|
let toolCalls = {};
|
|
156
239
|
let streamStarted = false;
|
|
157
240
|
let usage;
|
|
158
|
-
const
|
|
159
|
-
let finalAnswerDetected = false;
|
|
241
|
+
const answerStreamer = new FinalAnswerStreamer(callbacks);
|
|
160
242
|
// State machine for parsing inline <think> tags (MiniMax pattern)
|
|
161
243
|
let contentBuf = "";
|
|
162
244
|
let inThinkTag = false;
|
|
@@ -274,21 +356,18 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
274
356
|
// Detect final_answer tool call on first name delta.
|
|
275
357
|
// Only activate streaming if this is the sole tool call (index 0
|
|
276
358
|
// and no other indices seen). Mixed calls are rejected by core.ts.
|
|
277
|
-
if (tc.function.name === "final_answer" && !
|
|
359
|
+
if (tc.function.name === "final_answer" && !answerStreamer.detected
|
|
278
360
|
&& tc.index === 0 && Object.keys(toolCalls).length === 1) {
|
|
279
|
-
|
|
280
|
-
callbacks.onClearText?.();
|
|
361
|
+
answerStreamer.activate();
|
|
281
362
|
}
|
|
282
363
|
}
|
|
283
364
|
if (tc.function?.arguments) {
|
|
284
365
|
toolCalls[tc.index].arguments += tc.function.arguments;
|
|
285
366
|
// Feed final_answer argument deltas to the parser for progressive
|
|
286
367
|
// streaming, but only when it appears to be the sole tool call.
|
|
287
|
-
if (
|
|
368
|
+
if (answerStreamer.detected && toolCalls[tc.index].name === "final_answer"
|
|
288
369
|
&& Object.keys(toolCalls).length === 1) {
|
|
289
|
-
|
|
290
|
-
if (text)
|
|
291
|
-
callbacks.onTextChunk(text);
|
|
370
|
+
answerStreamer.processDelta(tc.function.arguments);
|
|
292
371
|
}
|
|
293
372
|
}
|
|
294
373
|
}
|
|
@@ -302,7 +381,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
302
381
|
toolCalls: Object.values(toolCalls),
|
|
303
382
|
outputItems: [],
|
|
304
383
|
usage,
|
|
305
|
-
finalAnswerStreamed:
|
|
384
|
+
finalAnswerStreamed: answerStreamer.streamed,
|
|
306
385
|
};
|
|
307
386
|
}
|
|
308
387
|
async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
@@ -320,9 +399,8 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
320
399
|
const outputItems = [];
|
|
321
400
|
let currentToolCall = null;
|
|
322
401
|
let usage;
|
|
323
|
-
const
|
|
402
|
+
const answerStreamer = new FinalAnswerStreamer(callbacks);
|
|
324
403
|
let functionCallCount = 0;
|
|
325
|
-
let finalAnswerDetected = false;
|
|
326
404
|
for await (const event of response) {
|
|
327
405
|
if (signal?.aborted)
|
|
328
406
|
break;
|
|
@@ -355,8 +433,7 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
355
433
|
// Only activate when this is the first (and so far only) function call.
|
|
356
434
|
// Mixed calls are rejected by core.ts; no need to stream their args.
|
|
357
435
|
if (String(event.item.name) === "final_answer" && functionCallCount === 1) {
|
|
358
|
-
|
|
359
|
-
callbacks.onClearText?.();
|
|
436
|
+
answerStreamer.activate();
|
|
360
437
|
}
|
|
361
438
|
}
|
|
362
439
|
break;
|
|
@@ -366,11 +443,9 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
366
443
|
currentToolCall.arguments += event.delta;
|
|
367
444
|
// Feed final_answer argument deltas to the parser for progressive
|
|
368
445
|
// streaming, but only when it appears to be the sole function call.
|
|
369
|
-
if (
|
|
446
|
+
if (answerStreamer.detected && currentToolCall.name === "final_answer"
|
|
370
447
|
&& functionCallCount === 1) {
|
|
371
|
-
|
|
372
|
-
if (text)
|
|
373
|
-
callbacks.onTextChunk(text);
|
|
448
|
+
answerStreamer.processDelta(String(event.delta));
|
|
374
449
|
}
|
|
375
450
|
}
|
|
376
451
|
break;
|
|
@@ -410,6 +485,6 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
410
485
|
toolCalls,
|
|
411
486
|
outputItems,
|
|
412
487
|
usage,
|
|
413
|
-
finalAnswerStreamed:
|
|
488
|
+
finalAnswerStreamed: answerStreamer.streamed,
|
|
414
489
|
};
|
|
415
490
|
}
|
|
@@ -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));
|