@ouro.bot/cli 0.1.0-alpha.2 → 0.1.0-alpha.20
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/assets/ouroboros.png +0 -0
- package/dist/heart/config.js +66 -4
- package/dist/heart/core.js +75 -2
- package/dist/heart/daemon/daemon-cli.js +507 -29
- package/dist/heart/daemon/daemon-entry.js +13 -5
- package/dist/heart/daemon/daemon.js +42 -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/ouro-bot-wrapper.js +4 -3
- package/dist/heart/daemon/ouro-path-installer.js +177 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +1 -1
- 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/subagent-installer.js +10 -1
- package/dist/heart/identity.js +77 -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 +58 -0
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/prompt.js +77 -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 +433 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-media.js +244 -0
- package/dist/senses/bluebubbles-model.js +253 -0
- package/dist/senses/bluebubbles-mutation-log.js +76 -0
- package/dist/senses/bluebubbles.js +421 -0
- package/dist/senses/cli.js +293 -133
- package/dist/senses/debug-activity.js +107 -0
- package/dist/senses/teams.js +173 -54
- package/package.json +11 -4
- 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,237 @@
|
|
|
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.getSpecialistTools = getSpecialistTools;
|
|
37
|
+
exports.createSpecialistExecTool = createSpecialistExecTool;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const tools_base_1 = require("../../repertoire/tools-base");
|
|
41
|
+
const hatch_flow_1 = require("./hatch-flow");
|
|
42
|
+
const hatch_animation_1 = require("./hatch-animation");
|
|
43
|
+
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
44
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
45
|
+
const completeAdoptionTool = {
|
|
46
|
+
type: "function",
|
|
47
|
+
function: {
|
|
48
|
+
name: "complete_adoption",
|
|
49
|
+
description: "finalize the agent bundle and hatch the new agent. call this only when you have written all 5 psyche files and agent.json to the temp directory, and the human has approved the bundle.",
|
|
50
|
+
parameters: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
name: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "the PascalCase name for the new agent (e.g. 'Slugger')",
|
|
56
|
+
},
|
|
57
|
+
handoff_message: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "a warm handoff message to display to the human after the agent is hatched",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ["name", "handoff_message"],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const readFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "read_file");
|
|
67
|
+
const writeFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "write_file");
|
|
68
|
+
const listDirTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "list_directory");
|
|
69
|
+
/**
|
|
70
|
+
* Returns the specialist's tool schema array.
|
|
71
|
+
*/
|
|
72
|
+
function getSpecialistTools() {
|
|
73
|
+
return [completeAdoptionTool, tools_base_1.finalAnswerTool, readFileTool.tool, writeFileTool.tool, listDirTool.tool];
|
|
74
|
+
}
|
|
75
|
+
const PSYCHE_FILES = ["SOUL.md", "IDENTITY.md", "LORE.md", "TACIT.md", "ASPIRATIONS.md"];
|
|
76
|
+
function isPascalCase(name) {
|
|
77
|
+
return /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
78
|
+
}
|
|
79
|
+
function writeReadme(dir, purpose) {
|
|
80
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
81
|
+
const readmePath = path.join(dir, "README.md");
|
|
82
|
+
/* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
|
|
83
|
+
if (!fs.existsSync(readmePath)) {
|
|
84
|
+
fs.writeFileSync(readmePath, `# ${path.basename(dir)}\n\n${purpose}\n`, "utf-8");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function scaffoldBundle(bundleRoot) {
|
|
88
|
+
writeReadme(path.join(bundleRoot, "memory"), "Persistent memory store.");
|
|
89
|
+
writeReadme(path.join(bundleRoot, "memory", "daily"), "Daily memory entries.");
|
|
90
|
+
writeReadme(path.join(bundleRoot, "memory", "archive"), "Archived memory.");
|
|
91
|
+
writeReadme(path.join(bundleRoot, "friends"), "Known friend records.");
|
|
92
|
+
writeReadme(path.join(bundleRoot, "tasks"), "Task files.");
|
|
93
|
+
writeReadme(path.join(bundleRoot, "tasks", "habits"), "Recurring tasks.");
|
|
94
|
+
writeReadme(path.join(bundleRoot, "tasks", "one-shots"), "One-shot tasks.");
|
|
95
|
+
writeReadme(path.join(bundleRoot, "tasks", "ongoing"), "Ongoing tasks.");
|
|
96
|
+
writeReadme(path.join(bundleRoot, "skills"), "Local skill files.");
|
|
97
|
+
writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
|
|
98
|
+
writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
|
|
99
|
+
// Memory scaffold files
|
|
100
|
+
const memoryRoot = path.join(bundleRoot, "memory");
|
|
101
|
+
const factsPath = path.join(memoryRoot, "facts.jsonl");
|
|
102
|
+
const entitiesPath = path.join(memoryRoot, "entities.json");
|
|
103
|
+
/* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
|
|
104
|
+
if (!fs.existsSync(factsPath))
|
|
105
|
+
fs.writeFileSync(factsPath, "", "utf-8");
|
|
106
|
+
/* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
|
|
107
|
+
if (!fs.existsSync(entitiesPath))
|
|
108
|
+
fs.writeFileSync(entitiesPath, "{}\n", "utf-8");
|
|
109
|
+
// bundle-meta.json
|
|
110
|
+
const meta = (0, bundle_manifest_1.createBundleMeta)();
|
|
111
|
+
fs.writeFileSync(path.join(bundleRoot, "bundle-meta.json"), JSON.stringify(meta, null, 2) + "\n", "utf-8");
|
|
112
|
+
}
|
|
113
|
+
function moveDir(src, dest) {
|
|
114
|
+
try {
|
|
115
|
+
fs.renameSync(src, dest);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* v8 ignore start -- cross-device fallback: only triggers on EXDEV (e.g. /tmp → different mount), untestable in CI @preserve */
|
|
119
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
120
|
+
fs.rmSync(src, { recursive: true, force: true });
|
|
121
|
+
/* v8 ignore stop */
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function execCompleteAdoption(args, deps) {
|
|
125
|
+
const name = args.name;
|
|
126
|
+
const handoffMessage = args.handoff_message;
|
|
127
|
+
if (!name) {
|
|
128
|
+
return "error: missing required 'name' parameter";
|
|
129
|
+
}
|
|
130
|
+
if (!isPascalCase(name)) {
|
|
131
|
+
return `error: name '${name}' must be PascalCase (e.g. 'Slugger', 'MyAgent')`;
|
|
132
|
+
}
|
|
133
|
+
// Validate psyche files exist
|
|
134
|
+
const psycheDir = path.join(deps.tempDir, "psyche");
|
|
135
|
+
const missingPsyche = PSYCHE_FILES.filter((f) => !fs.existsSync(path.join(psycheDir, f)));
|
|
136
|
+
if (missingPsyche.length > 0) {
|
|
137
|
+
return `error: missing psyche files in temp directory: ${missingPsyche.join(", ")}. write them first using write_file.`;
|
|
138
|
+
}
|
|
139
|
+
// Validate agent.json exists
|
|
140
|
+
const agentJsonPath = path.join(deps.tempDir, "agent.json");
|
|
141
|
+
if (!fs.existsSync(agentJsonPath)) {
|
|
142
|
+
return "error: agent.json not found in temp directory. write it first using write_file.";
|
|
143
|
+
}
|
|
144
|
+
// Validate target doesn't exist
|
|
145
|
+
const targetBundle = path.join(deps.bundlesRoot, `${name}.ouro`);
|
|
146
|
+
if (fs.existsSync(targetBundle)) {
|
|
147
|
+
return `error: bundle '${name}.ouro' already exists at ${deps.bundlesRoot}. choose a different name.`;
|
|
148
|
+
}
|
|
149
|
+
// Scaffold structural dirs into tempDir
|
|
150
|
+
scaffoldBundle(deps.tempDir);
|
|
151
|
+
// Move tempDir -> final bundle location
|
|
152
|
+
moveDir(deps.tempDir, targetBundle);
|
|
153
|
+
// Write secrets
|
|
154
|
+
try {
|
|
155
|
+
(0, hatch_flow_1.writeSecretsFile)(name, deps.provider, deps.credentials, deps.secretsRoot);
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
// Rollback: remove the moved bundle
|
|
159
|
+
try {
|
|
160
|
+
fs.rmSync(targetBundle, { recursive: true, force: true });
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Best effort cleanup
|
|
164
|
+
}
|
|
165
|
+
return `error: failed to write secrets: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
166
|
+
}
|
|
167
|
+
// Play hatch animation
|
|
168
|
+
await (0, hatch_animation_1.playHatchAnimation)(name, deps.animationWriter);
|
|
169
|
+
// Display handoff message
|
|
170
|
+
/* v8 ignore next -- UI-only: handoff message display, covered by integration @preserve */
|
|
171
|
+
if (handoffMessage && deps.animationWriter) {
|
|
172
|
+
deps.animationWriter(`\n${handoffMessage}\n`);
|
|
173
|
+
}
|
|
174
|
+
(0, runtime_1.emitNervesEvent)({
|
|
175
|
+
component: "daemon",
|
|
176
|
+
event: "daemon.adoption_complete",
|
|
177
|
+
message: "adoption completed successfully",
|
|
178
|
+
meta: { agentName: name, bundlePath: targetBundle },
|
|
179
|
+
});
|
|
180
|
+
return JSON.stringify({ success: true, agentName: name, bundlePath: targetBundle });
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Create a specialist tool executor with the given dependencies captured in closure.
|
|
184
|
+
*/
|
|
185
|
+
function createSpecialistExecTool(deps) {
|
|
186
|
+
(0, runtime_1.emitNervesEvent)({
|
|
187
|
+
component: "daemon",
|
|
188
|
+
event: "daemon.specialist_exec_tool_created",
|
|
189
|
+
message: "specialist exec tool created",
|
|
190
|
+
meta: { tempDir: deps.tempDir },
|
|
191
|
+
});
|
|
192
|
+
return async (name, args) => {
|
|
193
|
+
(0, runtime_1.emitNervesEvent)({
|
|
194
|
+
component: "daemon",
|
|
195
|
+
event: "daemon.specialist_tool_exec",
|
|
196
|
+
message: "executing specialist tool",
|
|
197
|
+
meta: { tool: name },
|
|
198
|
+
});
|
|
199
|
+
if (name === "complete_adoption") {
|
|
200
|
+
return execCompleteAdoption(args, deps);
|
|
201
|
+
}
|
|
202
|
+
if (name === "read_file") {
|
|
203
|
+
try {
|
|
204
|
+
return fs.readFileSync(args.path, "utf-8");
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive @preserve */ String(e)}`;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (name === "write_file") {
|
|
211
|
+
try {
|
|
212
|
+
const dir = path.dirname(args.path);
|
|
213
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
214
|
+
const content = typeof args.content === "string"
|
|
215
|
+
? args.content
|
|
216
|
+
: JSON.stringify(args.content, null, 2);
|
|
217
|
+
fs.writeFileSync(args.path, content, "utf-8");
|
|
218
|
+
return `wrote ${args.path}`;
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive @preserve */ String(e)}`;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (name === "list_directory") {
|
|
225
|
+
try {
|
|
226
|
+
return fs
|
|
227
|
+
.readdirSync(args.path, { withFileTypes: true })
|
|
228
|
+
.map((e) => `${e.isDirectory() ? "d" : "-"} ${e.name}`)
|
|
229
|
+
.join("\n");
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive @preserve */ String(e)}`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return `error: unknown tool '${name}'`;
|
|
236
|
+
};
|
|
237
|
+
}
|
|
@@ -55,9 +55,18 @@ function listSubagentSources(subagentsDir) {
|
|
|
55
55
|
.map((name) => path.join(subagentsDir, name))
|
|
56
56
|
.sort((a, b) => a.localeCompare(b));
|
|
57
57
|
}
|
|
58
|
+
function pathExists(target) {
|
|
59
|
+
try {
|
|
60
|
+
fs.lstatSync(target);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
58
67
|
function ensureSymlink(source, target) {
|
|
59
68
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
60
|
-
if (
|
|
69
|
+
if (pathExists(target)) {
|
|
61
70
|
const stats = fs.lstatSync(target);
|
|
62
71
|
if (stats.isSymbolicLink()) {
|
|
63
72
|
const linkedPath = fs.readlinkSync(target);
|
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,7 @@ exports.getAgentRoot = getAgentRoot;
|
|
|
42
42
|
exports.getAgentSecretsPath = getAgentSecretsPath;
|
|
43
43
|
exports.loadAgentConfig = loadAgentConfig;
|
|
44
44
|
exports.setAgentName = setAgentName;
|
|
45
|
+
exports.setAgentConfigOverride = setAgentConfigOverride;
|
|
45
46
|
exports.resetIdentity = resetIdentity;
|
|
46
47
|
const fs = __importStar(require("fs"));
|
|
47
48
|
const os = __importStar(require("os"));
|
|
@@ -56,12 +57,73 @@ exports.DEFAULT_AGENT_PHRASES = {
|
|
|
56
57
|
tool: ["running tool"],
|
|
57
58
|
followup: ["processing"],
|
|
58
59
|
};
|
|
60
|
+
exports.DEFAULT_AGENT_SENSES = {
|
|
61
|
+
cli: { enabled: true },
|
|
62
|
+
teams: { enabled: false },
|
|
63
|
+
bluebubbles: { enabled: false },
|
|
64
|
+
};
|
|
65
|
+
function normalizeSenses(value, configFile) {
|
|
66
|
+
const defaults = {
|
|
67
|
+
cli: { ...exports.DEFAULT_AGENT_SENSES.cli },
|
|
68
|
+
teams: { ...exports.DEFAULT_AGENT_SENSES.teams },
|
|
69
|
+
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
70
|
+
};
|
|
71
|
+
if (value === undefined) {
|
|
72
|
+
return defaults;
|
|
73
|
+
}
|
|
74
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
75
|
+
(0, runtime_1.emitNervesEvent)({
|
|
76
|
+
level: "error",
|
|
77
|
+
event: "config_identity.error",
|
|
78
|
+
component: "config/identity",
|
|
79
|
+
message: "agent config has invalid senses block",
|
|
80
|
+
meta: { path: configFile },
|
|
81
|
+
});
|
|
82
|
+
throw new Error(`agent.json at ${configFile} must include senses as an object when present.`);
|
|
83
|
+
}
|
|
84
|
+
const raw = value;
|
|
85
|
+
const senseNames = ["cli", "teams", "bluebubbles"];
|
|
86
|
+
for (const senseName of senseNames) {
|
|
87
|
+
const rawSense = raw[senseName];
|
|
88
|
+
if (rawSense === undefined) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!rawSense || typeof rawSense !== "object" || Array.isArray(rawSense)) {
|
|
92
|
+
(0, runtime_1.emitNervesEvent)({
|
|
93
|
+
level: "error",
|
|
94
|
+
event: "config_identity.error",
|
|
95
|
+
component: "config/identity",
|
|
96
|
+
message: "agent config has invalid sense config",
|
|
97
|
+
meta: { path: configFile, sense: senseName },
|
|
98
|
+
});
|
|
99
|
+
throw new Error(`agent.json at ${configFile} has invalid senses.${senseName} config.`);
|
|
100
|
+
}
|
|
101
|
+
const enabled = rawSense.enabled;
|
|
102
|
+
if (typeof enabled !== "boolean") {
|
|
103
|
+
(0, runtime_1.emitNervesEvent)({
|
|
104
|
+
level: "error",
|
|
105
|
+
event: "config_identity.error",
|
|
106
|
+
component: "config/identity",
|
|
107
|
+
message: "agent config has invalid sense enabled flag",
|
|
108
|
+
meta: { path: configFile, sense: senseName, enabled: enabled ?? null },
|
|
109
|
+
});
|
|
110
|
+
throw new Error(`agent.json at ${configFile} must include senses.${senseName}.enabled as boolean.`);
|
|
111
|
+
}
|
|
112
|
+
defaults[senseName] = { enabled };
|
|
113
|
+
}
|
|
114
|
+
return defaults;
|
|
115
|
+
}
|
|
59
116
|
function buildDefaultAgentTemplate(_agentName) {
|
|
60
117
|
return {
|
|
61
118
|
version: 1,
|
|
62
119
|
enabled: true,
|
|
63
120
|
provider: "anthropic",
|
|
64
121
|
context: { ...exports.DEFAULT_AGENT_CONTEXT },
|
|
122
|
+
senses: {
|
|
123
|
+
cli: { ...exports.DEFAULT_AGENT_SENSES.cli },
|
|
124
|
+
teams: { ...exports.DEFAULT_AGENT_SENSES.teams },
|
|
125
|
+
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
126
|
+
},
|
|
65
127
|
phrases: {
|
|
66
128
|
thinking: [...exports.DEFAULT_AGENT_PHRASES.thinking],
|
|
67
129
|
tool: [...exports.DEFAULT_AGENT_PHRASES.tool],
|
|
@@ -71,6 +133,7 @@ function buildDefaultAgentTemplate(_agentName) {
|
|
|
71
133
|
}
|
|
72
134
|
let _cachedAgentName = null;
|
|
73
135
|
let _cachedAgentConfig = null;
|
|
136
|
+
let _agentConfigOverride = null;
|
|
74
137
|
/**
|
|
75
138
|
* Parse `--agent <name>` from process.argv.
|
|
76
139
|
* Caches the result after first parse.
|
|
@@ -131,6 +194,9 @@ function getAgentSecretsPath(agentName = getAgentName()) {
|
|
|
131
194
|
* Throws descriptive error if file is missing or contains invalid JSON.
|
|
132
195
|
*/
|
|
133
196
|
function loadAgentConfig() {
|
|
197
|
+
if (_agentConfigOverride) {
|
|
198
|
+
return _agentConfigOverride;
|
|
199
|
+
}
|
|
134
200
|
if (_cachedAgentConfig) {
|
|
135
201
|
(0, runtime_1.emitNervesEvent)({
|
|
136
202
|
event: "identity.resolve",
|
|
@@ -252,6 +318,7 @@ function loadAgentConfig() {
|
|
|
252
318
|
provider: rawProvider,
|
|
253
319
|
context: parsed.context,
|
|
254
320
|
logging: parsed.logging,
|
|
321
|
+
senses: normalizeSenses(parsed.senses, configFile),
|
|
255
322
|
phrases: parsed.phrases,
|
|
256
323
|
};
|
|
257
324
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -271,6 +338,14 @@ function loadAgentConfig() {
|
|
|
271
338
|
function setAgentName(name) {
|
|
272
339
|
_cachedAgentName = name;
|
|
273
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Override the agent config returned by loadAgentConfig().
|
|
343
|
+
* When set to a non-null AgentConfig, loadAgentConfig() returns the override
|
|
344
|
+
* instead of reading from disk. When set to null, normal disk-based loading resumes.
|
|
345
|
+
*/
|
|
346
|
+
function setAgentConfigOverride(config) {
|
|
347
|
+
_agentConfigOverride = config;
|
|
348
|
+
}
|
|
274
349
|
/**
|
|
275
350
|
* Clear all cached identity state.
|
|
276
351
|
* Used in tests and when switching agent context.
|
|
@@ -278,4 +353,5 @@ function setAgentName(name) {
|
|
|
278
353
|
function resetIdentity() {
|
|
279
354
|
_cachedAgentName = null;
|
|
280
355
|
_cachedAgentConfig = null;
|
|
356
|
+
_agentConfigOverride = null;
|
|
281
357
|
}
|
|
@@ -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
|
+
}
|