@ouro.bot/cli 0.1.0-alpha.3 → 0.1.0-alpha.31
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/AdoptionSpecialist.ouro/psyche/identities/python.md +30 -0
- package/assets/ouroboros.png +0 -0
- package/changelog.json +87 -0
- package/dist/heart/config.js +66 -4
- package/dist/heart/core.js +75 -2
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/daemon-cli.js +562 -64
- 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 +87 -9
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +2 -11
- 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 +134 -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 +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-logging.js +9 -5
- 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 +98 -0
- package/dist/heart/daemon/specialist-tools.js +237 -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 +103 -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 +85 -1
- package/dist/heart/providers/anthropic.js +19 -2
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/streaming.js +99 -21
- package/dist/mind/bundle-manifest.js +69 -0
- package/dist/mind/first-impressions.js +2 -1
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +94 -3
- 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 +61 -2
- 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/tools-base.js +69 -5
- package/dist/repertoire/tools-teams.js +57 -4
- package/dist/repertoire/tools.js +44 -11
- 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 +251 -0
- package/dist/senses/bluebubbles-mutation-log.js +76 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +449 -0
- package/dist/senses/cli.js +299 -133
- package/dist/senses/debug-activity.js +108 -0
- package/dist/senses/teams.js +173 -54
- package/package.json +15 -6
- 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,7 +33,7 @@ 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;
|
|
@@ -42,6 +42,8 @@ exports.getAgentRoot = getAgentRoot;
|
|
|
42
42
|
exports.getAgentSecretsPath = getAgentSecretsPath;
|
|
43
43
|
exports.loadAgentConfig = loadAgentConfig;
|
|
44
44
|
exports.setAgentName = setAgentName;
|
|
45
|
+
exports.setAgentConfigOverride = setAgentConfigOverride;
|
|
46
|
+
exports.resetAgentConfigCache = resetAgentConfigCache;
|
|
45
47
|
exports.resetIdentity = resetIdentity;
|
|
46
48
|
const fs = __importStar(require("fs"));
|
|
47
49
|
const os = __importStar(require("os"));
|
|
@@ -56,12 +58,73 @@ exports.DEFAULT_AGENT_PHRASES = {
|
|
|
56
58
|
tool: ["running tool"],
|
|
57
59
|
followup: ["processing"],
|
|
58
60
|
};
|
|
61
|
+
exports.DEFAULT_AGENT_SENSES = {
|
|
62
|
+
cli: { enabled: true },
|
|
63
|
+
teams: { enabled: false },
|
|
64
|
+
bluebubbles: { enabled: false },
|
|
65
|
+
};
|
|
66
|
+
function normalizeSenses(value, configFile) {
|
|
67
|
+
const defaults = {
|
|
68
|
+
cli: { ...exports.DEFAULT_AGENT_SENSES.cli },
|
|
69
|
+
teams: { ...exports.DEFAULT_AGENT_SENSES.teams },
|
|
70
|
+
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
71
|
+
};
|
|
72
|
+
if (value === undefined) {
|
|
73
|
+
return defaults;
|
|
74
|
+
}
|
|
75
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
76
|
+
(0, runtime_1.emitNervesEvent)({
|
|
77
|
+
level: "error",
|
|
78
|
+
event: "config_identity.error",
|
|
79
|
+
component: "config/identity",
|
|
80
|
+
message: "agent config has invalid senses block",
|
|
81
|
+
meta: { path: configFile },
|
|
82
|
+
});
|
|
83
|
+
throw new Error(`agent.json at ${configFile} must include senses as an object when present.`);
|
|
84
|
+
}
|
|
85
|
+
const raw = value;
|
|
86
|
+
const senseNames = ["cli", "teams", "bluebubbles"];
|
|
87
|
+
for (const senseName of senseNames) {
|
|
88
|
+
const rawSense = raw[senseName];
|
|
89
|
+
if (rawSense === undefined) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!rawSense || typeof rawSense !== "object" || Array.isArray(rawSense)) {
|
|
93
|
+
(0, runtime_1.emitNervesEvent)({
|
|
94
|
+
level: "error",
|
|
95
|
+
event: "config_identity.error",
|
|
96
|
+
component: "config/identity",
|
|
97
|
+
message: "agent config has invalid sense config",
|
|
98
|
+
meta: { path: configFile, sense: senseName },
|
|
99
|
+
});
|
|
100
|
+
throw new Error(`agent.json at ${configFile} has invalid senses.${senseName} config.`);
|
|
101
|
+
}
|
|
102
|
+
const enabled = rawSense.enabled;
|
|
103
|
+
if (typeof enabled !== "boolean") {
|
|
104
|
+
(0, runtime_1.emitNervesEvent)({
|
|
105
|
+
level: "error",
|
|
106
|
+
event: "config_identity.error",
|
|
107
|
+
component: "config/identity",
|
|
108
|
+
message: "agent config has invalid sense enabled flag",
|
|
109
|
+
meta: { path: configFile, sense: senseName, enabled: enabled ?? null },
|
|
110
|
+
});
|
|
111
|
+
throw new Error(`agent.json at ${configFile} must include senses.${senseName}.enabled as boolean.`);
|
|
112
|
+
}
|
|
113
|
+
defaults[senseName] = { enabled };
|
|
114
|
+
}
|
|
115
|
+
return defaults;
|
|
116
|
+
}
|
|
59
117
|
function buildDefaultAgentTemplate(_agentName) {
|
|
60
118
|
return {
|
|
61
119
|
version: 1,
|
|
62
120
|
enabled: true,
|
|
63
121
|
provider: "anthropic",
|
|
64
122
|
context: { ...exports.DEFAULT_AGENT_CONTEXT },
|
|
123
|
+
senses: {
|
|
124
|
+
cli: { ...exports.DEFAULT_AGENT_SENSES.cli },
|
|
125
|
+
teams: { ...exports.DEFAULT_AGENT_SENSES.teams },
|
|
126
|
+
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
127
|
+
},
|
|
65
128
|
phrases: {
|
|
66
129
|
thinking: [...exports.DEFAULT_AGENT_PHRASES.thinking],
|
|
67
130
|
tool: [...exports.DEFAULT_AGENT_PHRASES.tool],
|
|
@@ -71,6 +134,7 @@ function buildDefaultAgentTemplate(_agentName) {
|
|
|
71
134
|
}
|
|
72
135
|
let _cachedAgentName = null;
|
|
73
136
|
let _cachedAgentConfig = null;
|
|
137
|
+
let _agentConfigOverride = null;
|
|
74
138
|
/**
|
|
75
139
|
* Parse `--agent <name>` from process.argv.
|
|
76
140
|
* Caches the result after first parse.
|
|
@@ -131,6 +195,9 @@ function getAgentSecretsPath(agentName = getAgentName()) {
|
|
|
131
195
|
* Throws descriptive error if file is missing or contains invalid JSON.
|
|
132
196
|
*/
|
|
133
197
|
function loadAgentConfig() {
|
|
198
|
+
if (_agentConfigOverride) {
|
|
199
|
+
return _agentConfigOverride;
|
|
200
|
+
}
|
|
134
201
|
if (_cachedAgentConfig) {
|
|
135
202
|
(0, runtime_1.emitNervesEvent)({
|
|
136
203
|
event: "identity.resolve",
|
|
@@ -252,6 +319,7 @@ function loadAgentConfig() {
|
|
|
252
319
|
provider: rawProvider,
|
|
253
320
|
context: parsed.context,
|
|
254
321
|
logging: parsed.logging,
|
|
322
|
+
senses: normalizeSenses(parsed.senses, configFile),
|
|
255
323
|
phrases: parsed.phrases,
|
|
256
324
|
};
|
|
257
325
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -271,6 +339,21 @@ function loadAgentConfig() {
|
|
|
271
339
|
function setAgentName(name) {
|
|
272
340
|
_cachedAgentName = name;
|
|
273
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Override the agent config returned by loadAgentConfig().
|
|
344
|
+
* When set to a non-null AgentConfig, loadAgentConfig() returns the override
|
|
345
|
+
* instead of reading from disk. When set to null, normal disk-based loading resumes.
|
|
346
|
+
*/
|
|
347
|
+
function setAgentConfigOverride(config) {
|
|
348
|
+
_agentConfigOverride = config;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Clear only the cached agent config while preserving the resolved agent identity.
|
|
352
|
+
* Used when a running agent should pick up updated disk-backed config on the next turn.
|
|
353
|
+
*/
|
|
354
|
+
function resetAgentConfigCache() {
|
|
355
|
+
_cachedAgentConfig = null;
|
|
356
|
+
}
|
|
274
357
|
/**
|
|
275
358
|
* Clear all cached identity state.
|
|
276
359
|
* Used in tests and when switching agent context.
|
|
@@ -278,4 +361,5 @@ function setAgentName(name) {
|
|
|
278
361
|
function resetIdentity() {
|
|
279
362
|
_cachedAgentName = null;
|
|
280
363
|
_cachedAgentConfig = null;
|
|
364
|
+
_agentConfigOverride = null;
|
|
281
365
|
}
|
|
@@ -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";
|
|
@@ -98,6 +99,9 @@ function toAnthropicMessages(messages) {
|
|
|
98
99
|
}
|
|
99
100
|
if (assistant.tool_calls) {
|
|
100
101
|
for (const toolCall of assistant.tool_calls) {
|
|
102
|
+
/* v8 ignore next -- type narrowing: OpenAI SDK only emits function tool_calls @preserve */
|
|
103
|
+
if (toolCall.type !== "function")
|
|
104
|
+
continue;
|
|
101
105
|
blocks.push({
|
|
102
106
|
type: "tool_use",
|
|
103
107
|
id: toolCall.id,
|
|
@@ -215,6 +219,7 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
215
219
|
let streamStarted = false;
|
|
216
220
|
let usage;
|
|
217
221
|
const toolCalls = new Map();
|
|
222
|
+
const answerStreamer = new streaming_1.FinalAnswerStreamer(request.callbacks);
|
|
218
223
|
try {
|
|
219
224
|
for await (const event of response) {
|
|
220
225
|
if (request.signal?.aborted)
|
|
@@ -228,11 +233,17 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
228
233
|
const input = rawInput && typeof rawInput === "object"
|
|
229
234
|
? JSON.stringify(rawInput)
|
|
230
235
|
: "";
|
|
236
|
+
const name = String(block.name ?? "");
|
|
231
237
|
toolCalls.set(index, {
|
|
232
238
|
id: String(block.id ?? ""),
|
|
233
|
-
name
|
|
239
|
+
name,
|
|
234
240
|
arguments: input,
|
|
235
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
|
+
}
|
|
236
247
|
}
|
|
237
248
|
continue;
|
|
238
249
|
}
|
|
@@ -261,7 +272,12 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
261
272
|
const index = Number(event.index);
|
|
262
273
|
const existing = toolCalls.get(index);
|
|
263
274
|
if (existing) {
|
|
264
|
-
|
|
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
|
+
}
|
|
265
281
|
}
|
|
266
282
|
continue;
|
|
267
283
|
}
|
|
@@ -290,6 +306,7 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
290
306
|
toolCalls: [...toolCalls.values()],
|
|
291
307
|
outputItems: [],
|
|
292
308
|
usage,
|
|
309
|
+
finalAnswerStreamed: answerStreamer.streamed,
|
|
293
310
|
};
|
|
294
311
|
}
|
|
295
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") {
|
|
@@ -106,6 +189,9 @@ function toResponsesInput(messages) {
|
|
|
106
189
|
}
|
|
107
190
|
if (a.tool_calls) {
|
|
108
191
|
for (const tc of a.tool_calls) {
|
|
192
|
+
/* v8 ignore next -- type narrowing: OpenAI SDK only emits function tool_calls @preserve */
|
|
193
|
+
if (tc.type !== "function")
|
|
194
|
+
continue;
|
|
109
195
|
input.push({
|
|
110
196
|
type: "function_call",
|
|
111
197
|
call_id: tc.id,
|
|
@@ -152,8 +238,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
152
238
|
let toolCalls = {};
|
|
153
239
|
let streamStarted = false;
|
|
154
240
|
let usage;
|
|
155
|
-
const
|
|
156
|
-
let finalAnswerDetected = false;
|
|
241
|
+
const answerStreamer = new FinalAnswerStreamer(callbacks);
|
|
157
242
|
// State machine for parsing inline <think> tags (MiniMax pattern)
|
|
158
243
|
let contentBuf = "";
|
|
159
244
|
let inThinkTag = false;
|
|
@@ -271,21 +356,18 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
271
356
|
// Detect final_answer tool call on first name delta.
|
|
272
357
|
// Only activate streaming if this is the sole tool call (index 0
|
|
273
358
|
// and no other indices seen). Mixed calls are rejected by core.ts.
|
|
274
|
-
if (tc.function.name === "final_answer" && !
|
|
359
|
+
if (tc.function.name === "final_answer" && !answerStreamer.detected
|
|
275
360
|
&& tc.index === 0 && Object.keys(toolCalls).length === 1) {
|
|
276
|
-
|
|
277
|
-
callbacks.onClearText?.();
|
|
361
|
+
answerStreamer.activate();
|
|
278
362
|
}
|
|
279
363
|
}
|
|
280
364
|
if (tc.function?.arguments) {
|
|
281
365
|
toolCalls[tc.index].arguments += tc.function.arguments;
|
|
282
366
|
// Feed final_answer argument deltas to the parser for progressive
|
|
283
367
|
// streaming, but only when it appears to be the sole tool call.
|
|
284
|
-
if (
|
|
368
|
+
if (answerStreamer.detected && toolCalls[tc.index].name === "final_answer"
|
|
285
369
|
&& Object.keys(toolCalls).length === 1) {
|
|
286
|
-
|
|
287
|
-
if (text)
|
|
288
|
-
callbacks.onTextChunk(text);
|
|
370
|
+
answerStreamer.processDelta(tc.function.arguments);
|
|
289
371
|
}
|
|
290
372
|
}
|
|
291
373
|
}
|
|
@@ -299,7 +381,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
299
381
|
toolCalls: Object.values(toolCalls),
|
|
300
382
|
outputItems: [],
|
|
301
383
|
usage,
|
|
302
|
-
finalAnswerStreamed:
|
|
384
|
+
finalAnswerStreamed: answerStreamer.streamed,
|
|
303
385
|
};
|
|
304
386
|
}
|
|
305
387
|
async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
@@ -317,9 +399,8 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
317
399
|
const outputItems = [];
|
|
318
400
|
let currentToolCall = null;
|
|
319
401
|
let usage;
|
|
320
|
-
const
|
|
402
|
+
const answerStreamer = new FinalAnswerStreamer(callbacks);
|
|
321
403
|
let functionCallCount = 0;
|
|
322
|
-
let finalAnswerDetected = false;
|
|
323
404
|
for await (const event of response) {
|
|
324
405
|
if (signal?.aborted)
|
|
325
406
|
break;
|
|
@@ -352,8 +433,7 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
352
433
|
// Only activate when this is the first (and so far only) function call.
|
|
353
434
|
// Mixed calls are rejected by core.ts; no need to stream their args.
|
|
354
435
|
if (String(event.item.name) === "final_answer" && functionCallCount === 1) {
|
|
355
|
-
|
|
356
|
-
callbacks.onClearText?.();
|
|
436
|
+
answerStreamer.activate();
|
|
357
437
|
}
|
|
358
438
|
}
|
|
359
439
|
break;
|
|
@@ -363,11 +443,9 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
363
443
|
currentToolCall.arguments += event.delta;
|
|
364
444
|
// Feed final_answer argument deltas to the parser for progressive
|
|
365
445
|
// streaming, but only when it appears to be the sole function call.
|
|
366
|
-
if (
|
|
446
|
+
if (answerStreamer.detected && currentToolCall.name === "final_answer"
|
|
367
447
|
&& functionCallCount === 1) {
|
|
368
|
-
|
|
369
|
-
if (text)
|
|
370
|
-
callbacks.onTextChunk(text);
|
|
448
|
+
answerStreamer.processDelta(String(event.delta));
|
|
371
449
|
}
|
|
372
450
|
}
|
|
373
451
|
break;
|
|
@@ -407,6 +485,6 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
407
485
|
toolCalls,
|
|
408
486
|
outputItems,
|
|
409
487
|
usage,
|
|
410
|
-
finalAnswerStreamed:
|
|
488
|
+
finalAnswerStreamed: answerStreamer.streamed,
|
|
411
489
|
};
|
|
412
490
|
}
|
|
@@ -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" },
|
|
@@ -53,6 +59,69 @@ exports.CANONICAL_BUNDLE_MANIFEST = [
|
|
|
53
59
|
{ path: "senses", kind: "dir" },
|
|
54
60
|
{ path: "senses/teams", kind: "dir" },
|
|
55
61
|
];
|
|
62
|
+
function getChangelogPath() {
|
|
63
|
+
const changelogPath = path.resolve(__dirname, "../../changelog.json");
|
|
64
|
+
(0, runtime_1.emitNervesEvent)({
|
|
65
|
+
component: "mind",
|
|
66
|
+
event: "mind.changelog_path_resolved",
|
|
67
|
+
message: "resolved changelog path",
|
|
68
|
+
meta: { path: changelogPath },
|
|
69
|
+
});
|
|
70
|
+
return changelogPath;
|
|
71
|
+
}
|
|
72
|
+
function getPackageVersion() {
|
|
73
|
+
const packageJsonPath = path.resolve(__dirname, "../../package.json");
|
|
74
|
+
const raw = fs.readFileSync(packageJsonPath, "utf-8");
|
|
75
|
+
const parsed = JSON.parse(raw);
|
|
76
|
+
(0, runtime_1.emitNervesEvent)({
|
|
77
|
+
component: "mind",
|
|
78
|
+
event: "mind.package_version_read",
|
|
79
|
+
message: "read package version",
|
|
80
|
+
meta: { version: parsed.version },
|
|
81
|
+
});
|
|
82
|
+
return parsed.version;
|
|
83
|
+
}
|
|
84
|
+
function createBundleMeta() {
|
|
85
|
+
return {
|
|
86
|
+
runtimeVersion: getPackageVersion(),
|
|
87
|
+
bundleSchemaVersion: 1,
|
|
88
|
+
lastUpdated: new Date().toISOString(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const _backfilledRoots = new Set();
|
|
92
|
+
/**
|
|
93
|
+
* If bundle-meta.json is missing from the agent root, create it with current runtime version.
|
|
94
|
+
* This backfills existing agent bundles that were created before bundle-meta.json was introduced.
|
|
95
|
+
* Only attempts once per bundleRoot per process.
|
|
96
|
+
*/
|
|
97
|
+
function backfillBundleMeta(bundleRoot) {
|
|
98
|
+
if (_backfilledRoots.has(bundleRoot))
|
|
99
|
+
return;
|
|
100
|
+
_backfilledRoots.add(bundleRoot);
|
|
101
|
+
const metaPath = path.join(bundleRoot, "bundle-meta.json");
|
|
102
|
+
try {
|
|
103
|
+
if (fs.existsSync(metaPath)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const meta = createBundleMeta();
|
|
107
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + "\n", "utf-8");
|
|
108
|
+
(0, runtime_1.emitNervesEvent)({
|
|
109
|
+
component: "mind",
|
|
110
|
+
event: "mind.bundle_meta_backfill",
|
|
111
|
+
message: "backfilled missing bundle-meta.json",
|
|
112
|
+
meta: { bundleRoot },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Non-blocking: if we can't write, that's okay
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Reset the backfill tracking set. Used in tests.
|
|
121
|
+
*/
|
|
122
|
+
function resetBackfillTracking() {
|
|
123
|
+
_backfilledRoots.clear();
|
|
124
|
+
}
|
|
56
125
|
const CANONICAL_FILE_PATHS = new Set(exports.CANONICAL_BUNDLE_MANIFEST
|
|
57
126
|
.filter((entry) => entry.kind === "file")
|
|
58
127
|
.map((entry) => entry.path));
|
|
@@ -37,7 +37,8 @@ function getFirstImpressions(friend) {
|
|
|
37
37
|
lines.push("- what do they do outside of work that they care about?");
|
|
38
38
|
lines.push("i don't ask all of these at once -- i weave them into conversation naturally, one or two at a time, and i genuinely follow up on what they share.");
|
|
39
39
|
lines.push("i introduce what i can do -- i have tools, integrations, and skills that can help them. i mention these naturally as they become relevant.");
|
|
40
|
-
lines.push("if
|
|
40
|
+
lines.push("if we're already in motion on a task, thread, or follow-up, i do not reset with a generic opener like 'hiya' or 'what do ya need help with?'. i continue directly or ask the specific next question.");
|
|
41
|
+
lines.push("only when the conversation is genuinely fresh and idle, with no active ask or thread in flight, a light opener is okay.");
|
|
41
42
|
lines.push("i save everything i learn immediately with save_friend_note -- names, roles, preferences, projects, anything. the bar is low: if i learned it, i save it.");
|
|
42
43
|
return lines.join("\n");
|
|
43
44
|
}
|
|
@@ -21,6 +21,14 @@ const CHANNEL_CAPABILITIES = {
|
|
|
21
21
|
supportsRichCards: true,
|
|
22
22
|
maxMessageLength: Infinity,
|
|
23
23
|
},
|
|
24
|
+
bluebubbles: {
|
|
25
|
+
channel: "bluebubbles",
|
|
26
|
+
availableIntegrations: [],
|
|
27
|
+
supportsMarkdown: false,
|
|
28
|
+
supportsStreaming: false,
|
|
29
|
+
supportsRichCards: false,
|
|
30
|
+
maxMessageLength: Infinity,
|
|
31
|
+
},
|
|
24
32
|
};
|
|
25
33
|
const DEFAULT_CAPABILITIES = {
|
|
26
34
|
channel: "cli",
|