@ouro.bot/cli 0.1.0-alpha.1 → 0.1.0-alpha.100
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 +596 -0
- package/dist/heart/active-work.js +251 -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/commitments.js +109 -0
- package/dist/heart/config.js +102 -23
- package/dist/heart/core.js +512 -94
- 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 +430 -0
- package/dist/heart/daemon/daemon-cli.js +1935 -185
- 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 +218 -9
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +10 -83
- package/dist/heart/daemon/hatch-specialist.js +6 -1
- 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 +147 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron.js +260 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
- package/dist/heart/daemon/ouro-path-installer.js +260 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/process-manager.js +32 -2
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +61 -14
- 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 +129 -0
- package/dist/heart/daemon/specialist-prompt.js +99 -0
- package/dist/heart/daemon/specialist-tools.js +283 -0
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/task-scheduler.js +4 -1
- package/dist/heart/daemon/thoughts.js +507 -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 +153 -23
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +191 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +77 -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 +381 -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 +103 -22
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +37 -4
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +141 -11
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +43 -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 +9 -1
- package/dist/mind/memory.js +89 -26
- package/dist/mind/obligation-steering.js +31 -0
- package/dist/mind/pending.js +160 -0
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt-refresh.js +20 -0
- package/dist/mind/prompt.js +499 -8
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/file-completeness.js +14 -4
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/index.js +12 -0
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +210 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +69 -4
- package/dist/repertoire/coding/spawner.js +21 -3
- package/dist/repertoire/coding/tools.js +105 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +290 -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 +770 -213
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +106 -53
- 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 +339 -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 +1181 -0
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +452 -99
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +154 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +387 -70
- package/dist/senses/pipeline.js +307 -0
- package/dist/senses/session-lock.js +119 -0
- package/dist/senses/teams.js +574 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +16 -4
- package/subagents/README.md +4 -68
- 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 -593
- package/subagents/work-planner.md +0 -373
|
@@ -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") {
|
|
@@ -102,10 +185,16 @@ 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) {
|
|
195
|
+
/* v8 ignore next -- type narrowing: OpenAI SDK only emits function tool_calls @preserve */
|
|
196
|
+
if (tc.type !== "function")
|
|
197
|
+
continue;
|
|
109
198
|
input.push({
|
|
110
199
|
type: "function_call",
|
|
111
200
|
call_id: tc.id,
|
|
@@ -152,8 +241,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
152
241
|
let toolCalls = {};
|
|
153
242
|
let streamStarted = false;
|
|
154
243
|
let usage;
|
|
155
|
-
const
|
|
156
|
-
let finalAnswerDetected = false;
|
|
244
|
+
const answerStreamer = new FinalAnswerStreamer(callbacks);
|
|
157
245
|
// State machine for parsing inline <think> tags (MiniMax pattern)
|
|
158
246
|
let contentBuf = "";
|
|
159
247
|
let inThinkTag = false;
|
|
@@ -271,21 +359,18 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
271
359
|
// Detect final_answer tool call on first name delta.
|
|
272
360
|
// Only activate streaming if this is the sole tool call (index 0
|
|
273
361
|
// and no other indices seen). Mixed calls are rejected by core.ts.
|
|
274
|
-
if (tc.function.name === "final_answer" && !
|
|
362
|
+
if (tc.function.name === "final_answer" && !answerStreamer.detected
|
|
275
363
|
&& tc.index === 0 && Object.keys(toolCalls).length === 1) {
|
|
276
|
-
|
|
277
|
-
callbacks.onClearText?.();
|
|
364
|
+
answerStreamer.activate();
|
|
278
365
|
}
|
|
279
366
|
}
|
|
280
367
|
if (tc.function?.arguments) {
|
|
281
368
|
toolCalls[tc.index].arguments += tc.function.arguments;
|
|
282
369
|
// Feed final_answer argument deltas to the parser for progressive
|
|
283
370
|
// streaming, but only when it appears to be the sole tool call.
|
|
284
|
-
if (
|
|
371
|
+
if (answerStreamer.detected && toolCalls[tc.index].name === "final_answer"
|
|
285
372
|
&& Object.keys(toolCalls).length === 1) {
|
|
286
|
-
|
|
287
|
-
if (text)
|
|
288
|
-
callbacks.onTextChunk(text);
|
|
373
|
+
answerStreamer.processDelta(tc.function.arguments);
|
|
289
374
|
}
|
|
290
375
|
}
|
|
291
376
|
}
|
|
@@ -299,7 +384,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
299
384
|
toolCalls: Object.values(toolCalls),
|
|
300
385
|
outputItems: [],
|
|
301
386
|
usage,
|
|
302
|
-
finalAnswerStreamed:
|
|
387
|
+
finalAnswerStreamed: answerStreamer.streamed,
|
|
303
388
|
};
|
|
304
389
|
}
|
|
305
390
|
async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
@@ -317,9 +402,8 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
317
402
|
const outputItems = [];
|
|
318
403
|
let currentToolCall = null;
|
|
319
404
|
let usage;
|
|
320
|
-
const
|
|
405
|
+
const answerStreamer = new FinalAnswerStreamer(callbacks);
|
|
321
406
|
let functionCallCount = 0;
|
|
322
|
-
let finalAnswerDetected = false;
|
|
323
407
|
for await (const event of response) {
|
|
324
408
|
if (signal?.aborted)
|
|
325
409
|
break;
|
|
@@ -352,8 +436,7 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
352
436
|
// Only activate when this is the first (and so far only) function call.
|
|
353
437
|
// Mixed calls are rejected by core.ts; no need to stream their args.
|
|
354
438
|
if (String(event.item.name) === "final_answer" && functionCallCount === 1) {
|
|
355
|
-
|
|
356
|
-
callbacks.onClearText?.();
|
|
439
|
+
answerStreamer.activate();
|
|
357
440
|
}
|
|
358
441
|
}
|
|
359
442
|
break;
|
|
@@ -363,11 +446,9 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
363
446
|
currentToolCall.arguments += event.delta;
|
|
364
447
|
// Feed final_answer argument deltas to the parser for progressive
|
|
365
448
|
// streaming, but only when it appears to be the sole function call.
|
|
366
|
-
if (
|
|
449
|
+
if (answerStreamer.detected && currentToolCall.name === "final_answer"
|
|
367
450
|
&& functionCallCount === 1) {
|
|
368
|
-
|
|
369
|
-
if (text)
|
|
370
|
-
callbacks.onTextChunk(text);
|
|
451
|
+
answerStreamer.processDelta(String(event.delta));
|
|
371
452
|
}
|
|
372
453
|
}
|
|
373
454
|
break;
|
|
@@ -407,6 +488,6 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
407
488
|
toolCalls,
|
|
408
489
|
outputItems,
|
|
409
490
|
usage,
|
|
410
|
-
finalAnswerStreamed:
|
|
491
|
+
finalAnswerStreamed: answerStreamer.streamed,
|
|
411
492
|
};
|
|
412
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
|
+
}
|