@ouro.bot/cli 0.1.0-alpha.4 → 0.1.0-alpha.41
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 +170 -0
- package/dist/heart/config.js +81 -8
- package/dist/heart/core.js +78 -45
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/daemon-cli.js +987 -77
- 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 +177 -9
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +4 -20
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +134 -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 +266 -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/subagent-installer.js +10 -1
- 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/identity.js +96 -4
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/providers/anthropic.js +16 -2
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/streaming.js +96 -21
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +7 -7
- package/dist/mind/first-impressions.js +2 -1
- 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 +10 -2
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +222 -7
- 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/index.js +2 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +202 -219
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +55 -35
- package/dist/senses/bluebubbles-client.js +434 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-media.js +338 -0
- package/dist/senses/bluebubbles-model.js +261 -0
- package/dist/senses/bluebubbles-mutation-log.js +74 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +832 -0
- package/dist/senses/cli.js +327 -138
- package/dist/senses/debug-activity.js +127 -0
- package/dist/senses/inner-dialog.js +103 -55
- package/dist/senses/pipeline.js +124 -0
- package/dist/senses/teams.js +427 -112
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +40 -53
- package/subagents/work-doer.md +26 -24
- package/subagents/work-merger.md +24 -30
- package/subagents/work-planner.md +34 -25
- package/dist/inner-worker-entry.js +0 -4
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assessWrapperPublishSync = assessWrapperPublishSync;
|
|
4
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
5
|
+
function wrapperPackageChanged(changedFiles) {
|
|
6
|
+
return changedFiles.some((file) => file.startsWith("packages/ouro.bot/"));
|
|
7
|
+
}
|
|
8
|
+
function assessWrapperPublishSync(input) {
|
|
9
|
+
let result;
|
|
10
|
+
if (input.localVersion !== input.cliVersion) {
|
|
11
|
+
result = {
|
|
12
|
+
ok: false,
|
|
13
|
+
message: `ouro.bot wrapper version ${input.localVersion} must match @ouro.bot/cli version ${input.cliVersion}`,
|
|
14
|
+
};
|
|
15
|
+
(0, runtime_1.emitNervesEvent)({
|
|
16
|
+
level: "warn",
|
|
17
|
+
component: "daemon",
|
|
18
|
+
event: "daemon.wrapper_publish_guard_checked",
|
|
19
|
+
message: "evaluated wrapper publish sync",
|
|
20
|
+
meta: {
|
|
21
|
+
changed: wrapperPackageChanged(input.changedFiles),
|
|
22
|
+
localVersion: input.localVersion,
|
|
23
|
+
cliVersion: input.cliVersion,
|
|
24
|
+
publishedVersion: input.publishedVersion,
|
|
25
|
+
ok: result.ok,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
if (!wrapperPackageChanged(input.changedFiles)) {
|
|
31
|
+
result = {
|
|
32
|
+
ok: true,
|
|
33
|
+
message: "wrapper package unchanged",
|
|
34
|
+
};
|
|
35
|
+
(0, runtime_1.emitNervesEvent)({
|
|
36
|
+
component: "daemon",
|
|
37
|
+
event: "daemon.wrapper_publish_guard_checked",
|
|
38
|
+
message: "evaluated wrapper publish sync",
|
|
39
|
+
meta: {
|
|
40
|
+
changed: false,
|
|
41
|
+
localVersion: input.localVersion,
|
|
42
|
+
cliVersion: input.cliVersion,
|
|
43
|
+
publishedVersion: input.publishedVersion,
|
|
44
|
+
ok: result.ok,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
if (input.publishedVersion === input.localVersion) {
|
|
50
|
+
result = {
|
|
51
|
+
ok: false,
|
|
52
|
+
message: `ouro.bot wrapper changed but ouro.bot@${input.localVersion} is already published; bump packages/ouro.bot/package.json before merging`,
|
|
53
|
+
};
|
|
54
|
+
(0, runtime_1.emitNervesEvent)({
|
|
55
|
+
level: "warn",
|
|
56
|
+
component: "daemon",
|
|
57
|
+
event: "daemon.wrapper_publish_guard_checked",
|
|
58
|
+
message: "evaluated wrapper publish sync",
|
|
59
|
+
meta: {
|
|
60
|
+
changed: true,
|
|
61
|
+
localVersion: input.localVersion,
|
|
62
|
+
cliVersion: input.cliVersion,
|
|
63
|
+
publishedVersion: input.publishedVersion,
|
|
64
|
+
ok: result.ok,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
result = {
|
|
70
|
+
ok: true,
|
|
71
|
+
message: "wrapper package changed and local wrapper version is unpublished",
|
|
72
|
+
};
|
|
73
|
+
(0, runtime_1.emitNervesEvent)({
|
|
74
|
+
component: "daemon",
|
|
75
|
+
event: "daemon.wrapper_publish_guard_checked",
|
|
76
|
+
message: "evaluated wrapper publish sync",
|
|
77
|
+
meta: {
|
|
78
|
+
changed: true,
|
|
79
|
+
localVersion: input.localVersion,
|
|
80
|
+
cliVersion: input.cliVersion,
|
|
81
|
+
publishedVersion: input.publishedVersion,
|
|
82
|
+
ok: result.ok,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
return result;
|
|
86
|
+
}
|
package/dist/heart/identity.js
CHANGED
|
@@ -33,15 +33,18 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.DEFAULT_AGENT_PHRASES = exports.DEFAULT_AGENT_CONTEXT = void 0;
|
|
36
|
+
exports.DEFAULT_AGENT_SENSES = exports.DEFAULT_AGENT_PHRASES = exports.DEFAULT_AGENT_CONTEXT = void 0;
|
|
37
37
|
exports.buildDefaultAgentTemplate = buildDefaultAgentTemplate;
|
|
38
38
|
exports.getAgentName = getAgentName;
|
|
39
39
|
exports.getRepoRoot = getRepoRoot;
|
|
40
40
|
exports.getAgentBundlesRoot = getAgentBundlesRoot;
|
|
41
41
|
exports.getAgentRoot = getAgentRoot;
|
|
42
|
+
exports.getAgentStateRoot = getAgentStateRoot;
|
|
42
43
|
exports.getAgentSecretsPath = getAgentSecretsPath;
|
|
43
44
|
exports.loadAgentConfig = loadAgentConfig;
|
|
44
45
|
exports.setAgentName = setAgentName;
|
|
46
|
+
exports.setAgentConfigOverride = setAgentConfigOverride;
|
|
47
|
+
exports.resetAgentConfigCache = resetAgentConfigCache;
|
|
45
48
|
exports.resetIdentity = resetIdentity;
|
|
46
49
|
const fs = __importStar(require("fs"));
|
|
47
50
|
const os = __importStar(require("os"));
|
|
@@ -56,12 +59,73 @@ exports.DEFAULT_AGENT_PHRASES = {
|
|
|
56
59
|
tool: ["running tool"],
|
|
57
60
|
followup: ["processing"],
|
|
58
61
|
};
|
|
62
|
+
exports.DEFAULT_AGENT_SENSES = {
|
|
63
|
+
cli: { enabled: true },
|
|
64
|
+
teams: { enabled: false },
|
|
65
|
+
bluebubbles: { enabled: false },
|
|
66
|
+
};
|
|
67
|
+
function normalizeSenses(value, configFile) {
|
|
68
|
+
const defaults = {
|
|
69
|
+
cli: { ...exports.DEFAULT_AGENT_SENSES.cli },
|
|
70
|
+
teams: { ...exports.DEFAULT_AGENT_SENSES.teams },
|
|
71
|
+
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
72
|
+
};
|
|
73
|
+
if (value === undefined) {
|
|
74
|
+
return defaults;
|
|
75
|
+
}
|
|
76
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
77
|
+
(0, runtime_1.emitNervesEvent)({
|
|
78
|
+
level: "error",
|
|
79
|
+
event: "config_identity.error",
|
|
80
|
+
component: "config/identity",
|
|
81
|
+
message: "agent config has invalid senses block",
|
|
82
|
+
meta: { path: configFile },
|
|
83
|
+
});
|
|
84
|
+
throw new Error(`agent.json at ${configFile} must include senses as an object when present.`);
|
|
85
|
+
}
|
|
86
|
+
const raw = value;
|
|
87
|
+
const senseNames = ["cli", "teams", "bluebubbles"];
|
|
88
|
+
for (const senseName of senseNames) {
|
|
89
|
+
const rawSense = raw[senseName];
|
|
90
|
+
if (rawSense === undefined) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (!rawSense || typeof rawSense !== "object" || Array.isArray(rawSense)) {
|
|
94
|
+
(0, runtime_1.emitNervesEvent)({
|
|
95
|
+
level: "error",
|
|
96
|
+
event: "config_identity.error",
|
|
97
|
+
component: "config/identity",
|
|
98
|
+
message: "agent config has invalid sense config",
|
|
99
|
+
meta: { path: configFile, sense: senseName },
|
|
100
|
+
});
|
|
101
|
+
throw new Error(`agent.json at ${configFile} has invalid senses.${senseName} config.`);
|
|
102
|
+
}
|
|
103
|
+
const enabled = rawSense.enabled;
|
|
104
|
+
if (typeof enabled !== "boolean") {
|
|
105
|
+
(0, runtime_1.emitNervesEvent)({
|
|
106
|
+
level: "error",
|
|
107
|
+
event: "config_identity.error",
|
|
108
|
+
component: "config/identity",
|
|
109
|
+
message: "agent config has invalid sense enabled flag",
|
|
110
|
+
meta: { path: configFile, sense: senseName, enabled: enabled ?? null },
|
|
111
|
+
});
|
|
112
|
+
throw new Error(`agent.json at ${configFile} must include senses.${senseName}.enabled as boolean.`);
|
|
113
|
+
}
|
|
114
|
+
defaults[senseName] = { enabled };
|
|
115
|
+
}
|
|
116
|
+
return defaults;
|
|
117
|
+
}
|
|
59
118
|
function buildDefaultAgentTemplate(_agentName) {
|
|
60
119
|
return {
|
|
61
120
|
version: 1,
|
|
62
121
|
enabled: true,
|
|
63
122
|
provider: "anthropic",
|
|
64
123
|
context: { ...exports.DEFAULT_AGENT_CONTEXT },
|
|
124
|
+
senses: {
|
|
125
|
+
cli: { ...exports.DEFAULT_AGENT_SENSES.cli },
|
|
126
|
+
teams: { ...exports.DEFAULT_AGENT_SENSES.teams },
|
|
127
|
+
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
128
|
+
},
|
|
65
129
|
phrases: {
|
|
66
130
|
thinking: [...exports.DEFAULT_AGENT_PHRASES.thinking],
|
|
67
131
|
tool: [...exports.DEFAULT_AGENT_PHRASES.tool],
|
|
@@ -71,6 +135,7 @@ function buildDefaultAgentTemplate(_agentName) {
|
|
|
71
135
|
}
|
|
72
136
|
let _cachedAgentName = null;
|
|
73
137
|
let _cachedAgentConfig = null;
|
|
138
|
+
let _agentConfigOverride = null;
|
|
74
139
|
/**
|
|
75
140
|
* Parse `--agent <name>` from process.argv.
|
|
76
141
|
* Caches the result after first parse.
|
|
@@ -111,13 +176,20 @@ function getRepoRoot() {
|
|
|
111
176
|
* Returns the shared bundle root directory: `~/AgentBundles/`
|
|
112
177
|
*/
|
|
113
178
|
function getAgentBundlesRoot() {
|
|
114
|
-
|
|
179
|
+
const homeBase = process.env.WEBSITE_SITE_NAME ? "/home" : os.homedir();
|
|
180
|
+
return path.join(homeBase, "AgentBundles");
|
|
115
181
|
}
|
|
116
182
|
/**
|
|
117
183
|
* Returns the agent-specific bundle directory: `~/AgentBundles/<agentName>.ouro/`
|
|
118
184
|
*/
|
|
119
|
-
function getAgentRoot() {
|
|
120
|
-
return path.join(getAgentBundlesRoot(), `${
|
|
185
|
+
function getAgentRoot(agentName = getAgentName()) {
|
|
186
|
+
return path.join(getAgentBundlesRoot(), `${agentName}.ouro`);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Returns the bundle-local runtime state directory: `~/AgentBundles/<agentName>.ouro/state/`
|
|
190
|
+
*/
|
|
191
|
+
function getAgentStateRoot(agentName = getAgentName()) {
|
|
192
|
+
return path.join(getAgentRoot(agentName), "state");
|
|
121
193
|
}
|
|
122
194
|
/**
|
|
123
195
|
* Returns the conventional secrets path: `~/.agentsecrets/<agentName>/secrets.json`
|
|
@@ -131,6 +203,9 @@ function getAgentSecretsPath(agentName = getAgentName()) {
|
|
|
131
203
|
* Throws descriptive error if file is missing or contains invalid JSON.
|
|
132
204
|
*/
|
|
133
205
|
function loadAgentConfig() {
|
|
206
|
+
if (_agentConfigOverride) {
|
|
207
|
+
return _agentConfigOverride;
|
|
208
|
+
}
|
|
134
209
|
if (_cachedAgentConfig) {
|
|
135
210
|
(0, runtime_1.emitNervesEvent)({
|
|
136
211
|
event: "identity.resolve",
|
|
@@ -252,6 +327,7 @@ function loadAgentConfig() {
|
|
|
252
327
|
provider: rawProvider,
|
|
253
328
|
context: parsed.context,
|
|
254
329
|
logging: parsed.logging,
|
|
330
|
+
senses: normalizeSenses(parsed.senses, configFile),
|
|
255
331
|
phrases: parsed.phrases,
|
|
256
332
|
};
|
|
257
333
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -271,6 +347,21 @@ function loadAgentConfig() {
|
|
|
271
347
|
function setAgentName(name) {
|
|
272
348
|
_cachedAgentName = name;
|
|
273
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Override the agent config returned by loadAgentConfig().
|
|
352
|
+
* When set to a non-null AgentConfig, loadAgentConfig() returns the override
|
|
353
|
+
* instead of reading from disk. When set to null, normal disk-based loading resumes.
|
|
354
|
+
*/
|
|
355
|
+
function setAgentConfigOverride(config) {
|
|
356
|
+
_agentConfigOverride = config;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Clear only the cached agent config while preserving the resolved agent identity.
|
|
360
|
+
* Used when a running agent should pick up updated disk-backed config on the next turn.
|
|
361
|
+
*/
|
|
362
|
+
function resetAgentConfigCache() {
|
|
363
|
+
_cachedAgentConfig = null;
|
|
364
|
+
}
|
|
274
365
|
/**
|
|
275
366
|
* Clear all cached identity state.
|
|
276
367
|
* Used in tests and when switching agent context.
|
|
@@ -278,4 +369,5 @@ function setAgentName(name) {
|
|
|
278
369
|
function resetIdentity() {
|
|
279
370
|
_cachedAgentName = null;
|
|
280
371
|
_cachedAgentConfig = null;
|
|
372
|
+
_agentConfigOverride = null;
|
|
281
373
|
}
|
package/dist/heart/kicks.js
CHANGED
|
@@ -1,25 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// TODO: Kicks enforce "any action" but not "meaningful action". After a narration
|
|
3
|
-
// kick, the model can satisfy the constraint by calling a no-op tool like
|
|
4
|
-
// get_current_time({}). We need to detect trivial compliance and either re-kick
|
|
5
|
-
// or discount the tool call. Ideally, the kick message would suggest a specific
|
|
6
|
-
// tool call based on conversation context (what the user asked, what tools are
|
|
7
|
-
// relevant) rather than just saying "call a tool". That's a bigger piece of work —
|
|
8
|
-
// it requires the kick system to be context-aware.
|
|
9
|
-
// See ouroboros' observation: "i'm not chickening out. i'm satisfying a crude
|
|
10
|
-
// constraint. poorly."
|
|
11
|
-
//
|
|
12
|
-
// A kick is a self-correction. When the harness detects a malformed response,
|
|
13
|
-
// it injects an assistant-role message as if the model caught its own mistake.
|
|
14
|
-
//
|
|
15
|
-
// Kicks are:
|
|
16
|
-
// - assistant role (self-correction, not external rebuke)
|
|
17
|
-
// - first person ("I" not "you")
|
|
18
|
-
// - forward-looking (what I'm doing next, not what I did wrong)
|
|
19
|
-
// - short (one sentence)
|
|
20
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
3
|
exports.hasToolIntent = hasToolIntent;
|
|
22
4
|
exports.detectKick = detectKick;
|
|
5
|
+
const runtime_1 = require("../nerves/runtime");
|
|
23
6
|
const KICK_MESSAGES = {
|
|
24
7
|
empty: "I sent an empty message by accident — let me try again.",
|
|
25
8
|
narration: "I narrated instead of acting. Using the tool now -- if done, calling final_answer.",
|
|
@@ -141,4 +124,3 @@ function detectKick(content, options) {
|
|
|
141
124
|
}
|
|
142
125
|
return null;
|
|
143
126
|
}
|
|
144
|
-
const runtime_1 = require("../nerves/runtime");
|
|
@@ -8,6 +8,7 @@ const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
|
8
8
|
const config_1 = require("../config");
|
|
9
9
|
const identity_1 = require("../identity");
|
|
10
10
|
const runtime_1 = require("../../nerves/runtime");
|
|
11
|
+
const streaming_1 = require("../streaming");
|
|
11
12
|
const ANTHROPIC_SETUP_TOKEN_PREFIX = "sk-ant-oat01-";
|
|
12
13
|
const ANTHROPIC_SETUP_TOKEN_MIN_LENGTH = 80;
|
|
13
14
|
const ANTHROPIC_OAUTH_BETA_HEADER = "claude-code-20250219,oauth-2025-04-20,fine-grained-tool-streaming-2025-05-14,interleaved-thinking-2025-05-14";
|
|
@@ -218,6 +219,7 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
218
219
|
let streamStarted = false;
|
|
219
220
|
let usage;
|
|
220
221
|
const toolCalls = new Map();
|
|
222
|
+
const answerStreamer = new streaming_1.FinalAnswerStreamer(request.callbacks);
|
|
221
223
|
try {
|
|
222
224
|
for await (const event of response) {
|
|
223
225
|
if (request.signal?.aborted)
|
|
@@ -231,11 +233,17 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
231
233
|
const input = rawInput && typeof rawInput === "object"
|
|
232
234
|
? JSON.stringify(rawInput)
|
|
233
235
|
: "";
|
|
236
|
+
const name = String(block.name ?? "");
|
|
234
237
|
toolCalls.set(index, {
|
|
235
238
|
id: String(block.id ?? ""),
|
|
236
|
-
name
|
|
239
|
+
name,
|
|
237
240
|
arguments: input,
|
|
238
241
|
});
|
|
242
|
+
// Activate eager streaming for sole final_answer tool call
|
|
243
|
+
/* v8 ignore next -- final_answer streaming activation, tested via FinalAnswerStreamer unit tests @preserve */
|
|
244
|
+
if (name === "final_answer" && toolCalls.size === 1) {
|
|
245
|
+
answerStreamer.activate();
|
|
246
|
+
}
|
|
239
247
|
}
|
|
240
248
|
continue;
|
|
241
249
|
}
|
|
@@ -264,7 +272,12 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
264
272
|
const index = Number(event.index);
|
|
265
273
|
const existing = toolCalls.get(index);
|
|
266
274
|
if (existing) {
|
|
267
|
-
|
|
275
|
+
const partialJson = String(delta?.partial_json ?? "");
|
|
276
|
+
existing.arguments = mergeAnthropicToolArguments(existing.arguments, partialJson);
|
|
277
|
+
/* v8 ignore next -- final_answer delta streaming, tested via FinalAnswerStreamer unit tests @preserve */
|
|
278
|
+
if (existing.name === "final_answer" && toolCalls.size === 1) {
|
|
279
|
+
answerStreamer.processDelta(partialJson);
|
|
280
|
+
}
|
|
268
281
|
}
|
|
269
282
|
continue;
|
|
270
283
|
}
|
|
@@ -293,6 +306,7 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
293
306
|
toolCalls: [...toolCalls.values()],
|
|
294
307
|
outputItems: [],
|
|
295
308
|
usage,
|
|
309
|
+
finalAnswerStreamed: answerStreamer.streamed,
|
|
296
310
|
};
|
|
297
311
|
}
|
|
298
312
|
function createAnthropicProviderRuntime() {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSenseInventory = getSenseInventory;
|
|
4
|
+
const runtime_1 = require("../nerves/runtime");
|
|
5
|
+
const identity_1 = require("./identity");
|
|
6
|
+
const SENSES = [
|
|
7
|
+
{ sense: "cli", label: "CLI", daemonManaged: false },
|
|
8
|
+
{ sense: "teams", label: "Teams", daemonManaged: true },
|
|
9
|
+
{ sense: "bluebubbles", label: "BlueBubbles", daemonManaged: true },
|
|
10
|
+
];
|
|
11
|
+
function configuredSenses(senses) {
|
|
12
|
+
return senses ?? {
|
|
13
|
+
cli: { ...identity_1.DEFAULT_AGENT_SENSES.cli },
|
|
14
|
+
teams: { ...identity_1.DEFAULT_AGENT_SENSES.teams },
|
|
15
|
+
bluebubbles: { ...identity_1.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function resolveStatus(enabled, daemonManaged, runtimeInfo) {
|
|
19
|
+
if (!enabled) {
|
|
20
|
+
return "disabled";
|
|
21
|
+
}
|
|
22
|
+
if (!daemonManaged) {
|
|
23
|
+
return "interactive";
|
|
24
|
+
}
|
|
25
|
+
if (runtimeInfo?.runtime === "error") {
|
|
26
|
+
return "error";
|
|
27
|
+
}
|
|
28
|
+
if (runtimeInfo?.runtime === "running") {
|
|
29
|
+
return "running";
|
|
30
|
+
}
|
|
31
|
+
if (runtimeInfo?.configured === false) {
|
|
32
|
+
return "needs_config";
|
|
33
|
+
}
|
|
34
|
+
return "ready";
|
|
35
|
+
}
|
|
36
|
+
function getSenseInventory(agent, runtime = {}) {
|
|
37
|
+
const senses = configuredSenses(agent.senses);
|
|
38
|
+
const inventory = SENSES.map(({ sense, label, daemonManaged }) => {
|
|
39
|
+
const enabled = senses[sense].enabled;
|
|
40
|
+
return {
|
|
41
|
+
sense,
|
|
42
|
+
label,
|
|
43
|
+
enabled,
|
|
44
|
+
daemonManaged,
|
|
45
|
+
status: resolveStatus(enabled, daemonManaged, runtime[sense]),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
(0, runtime_1.emitNervesEvent)({
|
|
49
|
+
component: "channels",
|
|
50
|
+
event: "channel.sense_inventory_built",
|
|
51
|
+
message: "built sense inventory",
|
|
52
|
+
meta: {
|
|
53
|
+
senses: inventory.map((entry) => ({
|
|
54
|
+
sense: entry.sense,
|
|
55
|
+
enabled: entry.enabled,
|
|
56
|
+
status: entry.status,
|
|
57
|
+
})),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
return inventory;
|
|
61
|
+
}
|
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
|
}
|