@ouro.bot/cli 0.1.0-alpha.2 → 0.1.0-alpha.21

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.
Files changed (58) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/assets/ouroboros.png +0 -0
  5. package/dist/heart/config.js +66 -4
  6. package/dist/heart/core.js +75 -2
  7. package/dist/heart/daemon/daemon-cli.js +523 -33
  8. package/dist/heart/daemon/daemon-entry.js +13 -5
  9. package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
  10. package/dist/heart/daemon/daemon.js +42 -9
  11. package/dist/heart/daemon/hatch-animation.js +35 -0
  12. package/dist/heart/daemon/hatch-flow.js +2 -11
  13. package/dist/heart/daemon/hatch-specialist.js +6 -1
  14. package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
  15. package/dist/heart/daemon/ouro-path-installer.js +178 -0
  16. package/dist/heart/daemon/ouro-uti.js +11 -2
  17. package/dist/heart/daemon/process-manager.js +1 -1
  18. package/dist/heart/daemon/runtime-logging.js +9 -5
  19. package/dist/heart/daemon/runtime-metadata.js +118 -0
  20. package/dist/heart/daemon/sense-manager.js +266 -0
  21. package/dist/heart/daemon/specialist-orchestrator.js +129 -0
  22. package/dist/heart/daemon/specialist-prompt.js +98 -0
  23. package/dist/heart/daemon/specialist-tools.js +237 -0
  24. package/dist/heart/daemon/subagent-installer.js +10 -1
  25. package/dist/heart/daemon/wrapper-publish-guard.js +48 -0
  26. package/dist/heart/identity.js +77 -1
  27. package/dist/heart/providers/anthropic.js +19 -2
  28. package/dist/heart/sense-truth.js +61 -0
  29. package/dist/heart/streaming.js +99 -21
  30. package/dist/mind/bundle-manifest.js +58 -0
  31. package/dist/mind/friends/channel.js +8 -0
  32. package/dist/mind/friends/types.js +1 -1
  33. package/dist/mind/prompt.js +77 -3
  34. package/dist/nerves/cli-logging.js +15 -2
  35. package/dist/repertoire/ado-client.js +4 -2
  36. package/dist/repertoire/coding/feedback.js +134 -0
  37. package/dist/repertoire/coding/index.js +4 -1
  38. package/dist/repertoire/coding/manager.js +61 -2
  39. package/dist/repertoire/coding/spawner.js +3 -3
  40. package/dist/repertoire/coding/tools.js +41 -2
  41. package/dist/repertoire/data/ado-endpoints.json +188 -0
  42. package/dist/repertoire/tools-base.js +69 -5
  43. package/dist/repertoire/tools-teams.js +57 -4
  44. package/dist/repertoire/tools.js +44 -11
  45. package/dist/senses/bluebubbles-client.js +433 -0
  46. package/dist/senses/bluebubbles-entry.js +11 -0
  47. package/dist/senses/bluebubbles-media.js +244 -0
  48. package/dist/senses/bluebubbles-model.js +253 -0
  49. package/dist/senses/bluebubbles-mutation-log.js +76 -0
  50. package/dist/senses/bluebubbles.js +421 -0
  51. package/dist/senses/cli.js +293 -133
  52. package/dist/senses/debug-activity.js +107 -0
  53. package/dist/senses/teams.js +173 -54
  54. package/package.json +11 -4
  55. package/subagents/work-doer.md +26 -24
  56. package/subagents/work-merger.md +24 -30
  57. package/subagents/work-planner.md +34 -25
  58. 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 (fs.existsSync(target)) {
69
+ if (pathExists(target)) {
61
70
  const stats = fs.lstatSync(target);
62
71
  if (stats.isSymbolicLink()) {
63
72
  const linkedPath = fs.readlinkSync(target);
@@ -0,0 +1,48 @@
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 (!wrapperPackageChanged(input.changedFiles)) {
11
+ result = {
12
+ ok: true,
13
+ message: "wrapper package unchanged",
14
+ };
15
+ (0, runtime_1.emitNervesEvent)({
16
+ component: "daemon",
17
+ event: "daemon.wrapper_publish_guard_checked",
18
+ message: "evaluated wrapper publish sync",
19
+ meta: { changed: false, localVersion: input.localVersion, publishedVersion: input.publishedVersion, ok: result.ok },
20
+ });
21
+ return result;
22
+ }
23
+ if (input.publishedVersion === input.localVersion) {
24
+ result = {
25
+ ok: false,
26
+ message: `ouro.bot wrapper changed but ouro.bot@${input.localVersion} is already published; bump packages/ouro.bot/package.json before merging`,
27
+ };
28
+ (0, runtime_1.emitNervesEvent)({
29
+ level: "warn",
30
+ component: "daemon",
31
+ event: "daemon.wrapper_publish_guard_checked",
32
+ message: "evaluated wrapper publish sync",
33
+ meta: { changed: true, localVersion: input.localVersion, publishedVersion: input.publishedVersion, ok: result.ok },
34
+ });
35
+ return result;
36
+ }
37
+ result = {
38
+ ok: true,
39
+ message: "wrapper package changed and local wrapper version is unpublished",
40
+ };
41
+ (0, runtime_1.emitNervesEvent)({
42
+ component: "daemon",
43
+ event: "daemon.wrapper_publish_guard_checked",
44
+ message: "evaluated wrapper publish sync",
45
+ meta: { changed: true, localVersion: input.localVersion, publishedVersion: input.publishedVersion, ok: result.ok },
46
+ });
47
+ return result;
48
+ }
@@ -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: String(block.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
- existing.arguments = mergeAnthropicToolArguments(existing.arguments, String(delta?.partial_json ?? ""));
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
+ }