@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.
Files changed (84) 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/README.md +117 -188
  5. package/assets/ouroboros.png +0 -0
  6. package/changelog.json +170 -0
  7. package/dist/heart/config.js +81 -8
  8. package/dist/heart/core.js +78 -45
  9. package/dist/heart/daemon/agent-discovery.js +81 -0
  10. package/dist/heart/daemon/daemon-cli.js +987 -77
  11. package/dist/heart/daemon/daemon-entry.js +14 -5
  12. package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
  13. package/dist/heart/daemon/daemon.js +177 -9
  14. package/dist/heart/daemon/hatch-animation.js +35 -0
  15. package/dist/heart/daemon/hatch-flow.js +4 -20
  16. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  17. package/dist/heart/daemon/launchd.js +134 -0
  18. package/dist/heart/daemon/message-router.js +15 -6
  19. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  20. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  21. package/dist/heart/daemon/ouro-entry.js +0 -0
  22. package/dist/heart/daemon/ouro-path-installer.js +178 -0
  23. package/dist/heart/daemon/ouro-uti.js +11 -2
  24. package/dist/heart/daemon/process-manager.js +1 -1
  25. package/dist/heart/daemon/run-hooks.js +37 -0
  26. package/dist/heart/daemon/runtime-metadata.js +118 -0
  27. package/dist/heart/daemon/sense-manager.js +266 -0
  28. package/dist/heart/daemon/specialist-orchestrator.js +129 -0
  29. package/dist/heart/daemon/specialist-prompt.js +99 -0
  30. package/dist/heart/daemon/specialist-tools.js +283 -0
  31. package/dist/heart/daemon/staged-restart.js +114 -0
  32. package/dist/heart/daemon/subagent-installer.js +10 -1
  33. package/dist/heart/daemon/update-checker.js +111 -0
  34. package/dist/heart/daemon/update-hooks.js +138 -0
  35. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  36. package/dist/heart/identity.js +96 -4
  37. package/dist/heart/kicks.js +1 -19
  38. package/dist/heart/providers/anthropic.js +16 -2
  39. package/dist/heart/sense-truth.js +61 -0
  40. package/dist/heart/streaming.js +96 -21
  41. package/dist/mind/bundle-manifest.js +70 -0
  42. package/dist/mind/context.js +7 -7
  43. package/dist/mind/first-impressions.js +2 -1
  44. package/dist/mind/friends/channel.js +43 -0
  45. package/dist/mind/friends/store-file.js +19 -0
  46. package/dist/mind/friends/types.js +9 -1
  47. package/dist/mind/memory.js +10 -3
  48. package/dist/mind/pending.js +10 -2
  49. package/dist/mind/phrases.js +1 -0
  50. package/dist/mind/prompt.js +222 -7
  51. package/dist/mind/token-estimate.js +8 -12
  52. package/dist/nerves/cli-logging.js +15 -2
  53. package/dist/repertoire/ado-client.js +4 -2
  54. package/dist/repertoire/coding/feedback.js +134 -0
  55. package/dist/repertoire/coding/index.js +4 -1
  56. package/dist/repertoire/coding/manager.js +62 -4
  57. package/dist/repertoire/coding/spawner.js +3 -3
  58. package/dist/repertoire/coding/tools.js +41 -2
  59. package/dist/repertoire/data/ado-endpoints.json +188 -0
  60. package/dist/repertoire/tasks/index.js +2 -9
  61. package/dist/repertoire/tasks/transitions.js +1 -2
  62. package/dist/repertoire/tools-base.js +202 -219
  63. package/dist/repertoire/tools-bluebubbles.js +93 -0
  64. package/dist/repertoire/tools-teams.js +58 -25
  65. package/dist/repertoire/tools.js +55 -35
  66. package/dist/senses/bluebubbles-client.js +434 -0
  67. package/dist/senses/bluebubbles-entry.js +11 -0
  68. package/dist/senses/bluebubbles-media.js +338 -0
  69. package/dist/senses/bluebubbles-model.js +261 -0
  70. package/dist/senses/bluebubbles-mutation-log.js +74 -0
  71. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  72. package/dist/senses/bluebubbles.js +832 -0
  73. package/dist/senses/cli.js +327 -138
  74. package/dist/senses/debug-activity.js +127 -0
  75. package/dist/senses/inner-dialog.js +103 -55
  76. package/dist/senses/pipeline.js +124 -0
  77. package/dist/senses/teams.js +427 -112
  78. package/dist/senses/trust-gate.js +112 -2
  79. package/package.json +14 -3
  80. package/subagents/README.md +40 -53
  81. package/subagents/work-doer.md +26 -24
  82. package/subagents/work-merger.md +24 -30
  83. package/subagents/work-planner.md +34 -25
  84. package/dist/inner-worker-entry.js +0 -4
package/changelog.json ADDED
@@ -0,0 +1,170 @@
1
+ {
2
+ "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
+ "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.41",
6
+ "changes": [
7
+ "JSONL readers (memory facts, inter-agent inbox) now skip corrupt lines instead of crashing — partial writes from crashes no longer lose all data.",
8
+ "Inter-agent message router now parses before clearing the inbox file, and preserves unparsed lines so corrupt messages are not silently lost.",
9
+ "Inner-dialog checkpoint derivation no longer crashes on all-whitespace assistant content — returns fallback checkpoint instead.",
10
+ "Update checker interval now catches and logs errors from the onUpdate callback instead of silently swallowing them."
11
+ ]
12
+ },
13
+ {
14
+ "version": "0.1.0-alpha.40",
15
+ "changes": [
16
+ "Removed dead backward-compat re-exports from core.ts (tools, streaming, prompt, kicks) — consumers already import from the canonical modules.",
17
+ "Removed dead exports: baseToolHandlers, teamsToolHandlers, teamsTools, __internal (token-estimate), TASK_STEM_PATTERN, checkAndRecord403 no-op and METHOD_TO_ACTION.",
18
+ "Consolidated duplicate sanitizeKey (config.ts + bluebubbles-mutation-log.ts) and slugify (hatch-flow.ts + tasks/index.ts) into shared exports from config.ts.",
19
+ "Replaced all as-any casts in source with proper TypeScript narrowing or Record<string, unknown> — only 2 SDK-required casts remain.",
20
+ "Removed unnecessary as-unknown-as casts on readdirSync (4 locations) and spawner double-cast.",
21
+ "Cleaned up commented-out kick detection code, stale TODOs, misplaced imports, and unused type imports."
22
+ ]
23
+ },
24
+ {
25
+ "version": "0.1.0-alpha.39",
26
+ "changes": [
27
+ "All senses now route through a shared per-turn pipeline — friend resolution, trust gate, session load, pending drain, agent turn, post-turn, and token accumulation happen in one place instead of four.",
28
+ "Trust gate is now channel-aware: open senses (iMessage) enforce stranger/acquaintance rules, closed senses (Teams) trust the org, local and internal always pass through.",
29
+ "Tool access and prompt restrictions use a single shared isTrustedLevel check — no more scattered family/friend comparisons that could drift apart.",
30
+ "Pending messages now inject correctly into multimodal content (image attachments no longer silently drop pending messages).",
31
+ "ouro reminder create supports --agent flag, matching every other identity-scoped CLI command."
32
+ ]
33
+ },
34
+ {
35
+ "version": "0.1.0-alpha.38",
36
+ "changes": [
37
+ "You now have a proper body map — understanding of your home (bundle) and bones (harness), what each directory is for, and how to modify your own configuration.",
38
+ "Inner dialog is now genuine internal monologue with metacognitive framing, not a second CLI session. Heartbeat and bootstrap messages read as first-person awareness.",
39
+ "Cross-session communication works end-to-end: inner dialog thoughts surface as [inner thought: ...] in conversations, messages to yourself route to inner dialog, and you can proactively reach out to friends via iMessage and Teams.",
40
+ "Tool audit: removed wrapper tools (git_commit, gh_cli, get_current_time, list_directory), added surgical tools (edit_file, glob, grep, read_file with offset/limit), consolidated 7 task tools + schedule_reminder + friend tools into ouro CLI commands.",
41
+ "You now understand why certain tools are restricted in certain contexts — trust level and shared channels each have independent, explained gates.",
42
+ "ouro friend link/unlink commands handle orphan cleanup when linking external identities, merging duplicate friend records intelligently.",
43
+ "During onboarding, the adoption specialist can collect phone number and Teams handle to create an initial friend record with contact info."
44
+ ]
45
+ },
46
+ {
47
+ "version": "0.1.0-alpha.37",
48
+ "changes": [
49
+ "The in-repo docs now match the current harness instead of the old repo-local bundle era. README, CONTRIBUTING, ARCHITECTURE, testing guidance, merge guidance, versioning guidance, and subagent docs now describe external bundles, current senses, daemon bootstrap, and bundle-owned task docs truthfully."
50
+ ]
51
+ },
52
+ {
53
+ "version": "0.1.0-alpha.36",
54
+ "changes": [
55
+ "BlueBubbles now marks a chat read at the same moment it starts typing for a turn, so read receipts and typing begin together instead of the read state lagging until after the reply finishes."
56
+ ]
57
+ },
58
+ {
59
+ "version": "0.1.0-alpha.35",
60
+ "changes": [
61
+ "BlueBubbles no longer emits generic follow-up phrase bubbles like 'on it...' or 'followup...' into iMessage. The sense now uses typing plus concrete tool/error activity only, which keeps mobile turns quieter and less redundant."
62
+ ]
63
+ },
64
+ {
65
+ "version": "0.1.0-alpha.34",
66
+ "changes": [
67
+ "BlueBubbles now starts typing immediately when a turn begins and no longer sends a redundant first visible 'working...' bubble when typing already covers that initial thinking phase.",
68
+ "BlueBubbles still surfaces later meaningful progress on longer turns, and its lane metadata/tool feedback now makes the difference between the turn default and an explicit reply-target override much clearer."
69
+ ]
70
+ },
71
+ {
72
+ "version": "0.1.0-alpha.33",
73
+ "changes": [
74
+ "BlueBubbles now starts typing immediately when a turn begins and no longer sends a redundant first visible 'working...' bubble when typing already covers that initial thinking phase.",
75
+ "BlueBubbles still surfaces later meaningful progress on longer turns, and its lane metadata/tool feedback now makes the difference between the turn default and an explicit reply-target override much clearer.",
76
+ "Agent-owned runtime state now lives inside each bundle's `state/` directory instead of under `~/.agentstate/<agent>/...`, so sessions, logs, pending messages, coding session persistence, and BlueBubbles mutation logs stay co-located with the rest of the bundle.",
77
+ "Bundle-local state paths now stay durable on Azure too, and `state/` is treated as canonical bundle content while secrets continue to live in `~/.agentsecrets/<agent>/secrets.json`."
78
+ ]
79
+ },
80
+ {
81
+ "version": "0.1.0-alpha.32",
82
+ "changes": [
83
+ "BlueBubbles now treats thread-vs-top-level placement as an agent choice instead of a hard harness mirror. Slugger can deliberately stay in the current reply lane, widen back to top-level, or target another active thread when that makes the conversation flow better.",
84
+ "BlueBubbles turns now surface inbound lane metadata and recent active lanes from the shared chat trunk, so the model gets enough context to choose the right reply placement without splitting the conversation into separate persisted thread sessions."
85
+ ]
86
+ },
87
+ {
88
+ "version": "0.1.0-alpha.31",
89
+ "changes": [
90
+ "BlueBubbles no longer silently deletes old per-thread session files when loading a chat trunk. It now only detects and warns about those obsolete artifacts so local state is never mutated without an explicit human choice.",
91
+ "The cross-thread-awareness fix still keeps one chat trunk per conversation and preserves threaded reply targeting, but stale `_thread_*.json` files are now left in place until a person removes them."
92
+ ]
93
+ },
94
+ {
95
+ "version": "0.1.0-alpha.30",
96
+ "changes": [
97
+ "BlueBubbles reply threads now stay inside one shared chat trunk instead of creating separate persisted mini-sessions, so Slugger keeps parent-chat awareness and active work when you switch between threaded and top-level replies.",
98
+ "BlueBubbles now injects explicit current-turn thread scope metadata into inbound messages and automatically removes obsolete per-thread session artifacts that used to cause mid-task resets like 'hiya, what do ya need help with?'"
99
+ ]
100
+ },
101
+ {
102
+ "version": "0.1.0-alpha.29",
103
+ "changes": [
104
+ "Running `ouro up` now force-syncs the global `ouro.bot` wrapper so bare `npx ouro.bot` stops getting hijacked by stale global CLI bins and lands back on the intended latest runtime.",
105
+ "Bootstrap repair is now explicit about reclaiming the `ouro.bot` command from old global installs, which makes repeated bootstrap runs more trustworthy on machines with prior experimental installs."
106
+ ]
107
+ },
108
+ {
109
+ "version": "0.1.0-alpha.28",
110
+ "changes": [
111
+ "Bare npx ouro.bot now stays aligned with the current alpha CLI track because the published ouro.bot wrapper is version-locked and republished alongside the CLI instead of lagging behind it.",
112
+ "Slugger no longer re-opens active iMessage task threads with generic greetings like 'hiya' when work is already in motion; fresh idle conversations can still start warmly.",
113
+ "BlueBubbles voice notes now use a harness-managed whisper.cpp transcription path for the current OpenAI, Anthropic, and MiniMax runtime contracts, including automatic local provisioning and truthful error notices when transcription cannot complete."
114
+ ]
115
+ },
116
+ {
117
+ "version": "0.1.0-alpha.27",
118
+ "changes": [
119
+ "The daemon now discovers all enabled agents in ~/AgentBundles, so ouro status and managed workers reflect every real agent instead of only slugger and ouroboros.",
120
+ "BlueBubbles typing now wraps the visible working phase correctly, and phrase updates from agent.json take effect on the next turn without requiring a restart."
121
+ ]
122
+ },
123
+ {
124
+ "version": "0.1.0-alpha.26",
125
+ "changes": [
126
+ "The daemon now auto-checks npm for new runtime versions every 30 minutes and performs a staged restart when an update is available. You no longer need to manually run npm install."
127
+ ]
128
+ },
129
+ {
130
+ "version": "0.1.0-alpha.25",
131
+ "changes": [
132
+ "Runtime updates no longer downgrade your bundle-meta.json if you happen to be running a newer version than the installed CLI. Only forward updates are applied.",
133
+ "The 'ouro up' update summary is now a single consolidated line (e.g. 'updated 4 agents to runtime X (was Y)') instead of one line per agent."
134
+ ]
135
+ },
136
+ {
137
+ "version": "0.1.0-alpha.24",
138
+ "changes": [
139
+ "When you run 'ouro up', you now see which agents were updated and from what version, so you know exactly what happened during startup."
140
+ ]
141
+ },
142
+ {
143
+ "version": "0.1.0-alpha.23",
144
+ "changes": [
145
+ "You can now use 'ouro down' as an alias for 'ouro stop' to pair naturally with 'ouro up'."
146
+ ]
147
+ },
148
+ {
149
+ "version": "0.1.0-alpha.22",
150
+ "changes": [
151
+ "You now know your runtime version and can see when it changed. Your system prompt shows your current version and what you were running before, so you can track what's new.",
152
+ "A changelog file is now shipped with the runtime. You can read it to understand exactly what changed between versions.",
153
+ "When the runtime updates, your bundle-meta.json is automatically updated with the new version info. Your previous version is preserved so you always know your version history.",
154
+ "The daemon can now check for new versions of the runtime on npm and auto-update with a staged restart that validates hooks before switching to new code.",
155
+ "The daemon can now be managed as a macOS LaunchAgent for automatic restart on crash.",
156
+ "The daemon now auto-syncs the ouro.bot npm wrapper version on startup to keep the npx entry point current."
157
+ ]
158
+ },
159
+ {
160
+ "version": "0.1.0-alpha.21",
161
+ "changes": [
162
+ "You now know your runtime version and can see when it changed. Your system prompt shows your current version and what you were running before, so you can track what's new.",
163
+ "A changelog file is now shipped with the runtime. You can read it to understand exactly what changed between versions.",
164
+ "When the runtime updates, your bundle-meta.json is automatically updated with the new version info. Your previous version is preserved so you always know your version history.",
165
+ "The daemon can now check for new versions of the runtime on npm and auto-update with a staged restart that validates hooks before switching to new code.",
166
+ "The daemon can now be managed as a macOS LaunchAgent for automatic restart on crash."
167
+ ]
168
+ }
169
+ ]
170
+ }
@@ -35,23 +35,29 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.loadConfig = loadConfig;
37
37
  exports.resetConfigCache = resetConfigCache;
38
- exports.setTestConfig = setTestConfig;
38
+ exports.patchRuntimeConfig = patchRuntimeConfig;
39
39
  exports.getAzureConfig = getAzureConfig;
40
40
  exports.getMinimaxConfig = getMinimaxConfig;
41
41
  exports.getAnthropicConfig = getAnthropicConfig;
42
42
  exports.getOpenAICodexConfig = getOpenAICodexConfig;
43
43
  exports.getTeamsConfig = getTeamsConfig;
44
+ exports.getTeamsSecondaryConfig = getTeamsSecondaryConfig;
44
45
  exports.getContextConfig = getContextConfig;
45
46
  exports.getOAuthConfig = getOAuthConfig;
47
+ exports.resolveOAuthForTenant = resolveOAuthForTenant;
46
48
  exports.getTeamsChannelConfig = getTeamsChannelConfig;
49
+ exports.getBlueBubblesConfig = getBlueBubblesConfig;
50
+ exports.getBlueBubblesChannelConfig = getBlueBubblesChannelConfig;
47
51
  exports.getIntegrationsConfig = getIntegrationsConfig;
48
52
  exports.getOpenAIEmbeddingsApiKey = getOpenAIEmbeddingsApiKey;
49
53
  exports.getLogsDir = getLogsDir;
54
+ exports.sanitizeKey = sanitizeKey;
55
+ exports.slugify = slugify;
56
+ exports.resolveSessionPath = resolveSessionPath;
50
57
  exports.sessionPath = sessionPath;
51
58
  exports.logPath = logPath;
52
59
  const fs = __importStar(require("fs"));
53
60
  const path = __importStar(require("path"));
54
- const os = __importStar(require("os"));
55
61
  const identity_1 = require("./identity");
56
62
  const runtime_1 = require("../nerves/runtime");
57
63
  const DEFAULT_SECRETS_TEMPLATE = {
@@ -74,7 +80,7 @@ const DEFAULT_SECRETS_TEMPLATE = {
74
80
  setupToken: "",
75
81
  },
76
82
  "openai-codex": {
77
- model: "gpt-5.2",
83
+ model: "gpt-5.4",
78
84
  oauthAccessToken: "",
79
85
  },
80
86
  },
@@ -82,16 +88,33 @@ const DEFAULT_SECRETS_TEMPLATE = {
82
88
  clientId: "",
83
89
  clientSecret: "",
84
90
  tenantId: "",
91
+ managedIdentityClientId: "",
85
92
  },
86
93
  oauth: {
87
94
  graphConnectionName: "graph",
88
95
  adoConnectionName: "ado",
89
96
  githubConnectionName: "",
90
97
  },
98
+ teamsSecondary: {
99
+ clientId: "",
100
+ clientSecret: "",
101
+ tenantId: "",
102
+ managedIdentityClientId: "",
103
+ },
91
104
  teamsChannel: {
92
105
  skipConfirmation: true,
93
106
  port: 3978,
94
107
  },
108
+ bluebubbles: {
109
+ serverUrl: "",
110
+ password: "",
111
+ accountId: "default",
112
+ },
113
+ bluebubblesChannel: {
114
+ port: 18790,
115
+ webhookPath: "/bluebubbles-webhook",
116
+ requestTimeoutMs: 30000,
117
+ },
95
118
  integrations: {
96
119
  perplexityApiKey: "",
97
120
  openaiEmbeddingsApiKey: "",
@@ -106,9 +129,12 @@ function defaultRuntimeConfig() {
106
129
  "openai-codex": { ...DEFAULT_SECRETS_TEMPLATE.providers["openai-codex"] },
107
130
  },
108
131
  teams: { ...DEFAULT_SECRETS_TEMPLATE.teams },
132
+ teamsSecondary: { ...DEFAULT_SECRETS_TEMPLATE.teamsSecondary },
109
133
  oauth: { ...DEFAULT_SECRETS_TEMPLATE.oauth },
110
134
  context: { ...identity_1.DEFAULT_AGENT_CONTEXT },
111
135
  teamsChannel: { ...DEFAULT_SECRETS_TEMPLATE.teamsChannel },
136
+ bluebubbles: { ...DEFAULT_SECRETS_TEMPLATE.bluebubbles },
137
+ bluebubblesChannel: { ...DEFAULT_SECRETS_TEMPLATE.bluebubblesChannel },
112
138
  integrations: { ...DEFAULT_SECRETS_TEMPLATE.integrations },
113
139
  };
114
140
  }
@@ -219,7 +245,7 @@ function resetConfigCache() {
219
245
  _cachedConfig = null;
220
246
  _testContextOverride = null;
221
247
  }
222
- function setTestConfig(partial) {
248
+ function patchRuntimeConfig(partial) {
223
249
  loadConfig(); // ensure _cachedConfig exists
224
250
  const contextPatch = partial.context;
225
251
  if (contextPatch) {
@@ -248,6 +274,10 @@ function getTeamsConfig() {
248
274
  const config = loadConfig();
249
275
  return { ...config.teams };
250
276
  }
277
+ function getTeamsSecondaryConfig() {
278
+ const config = loadConfig();
279
+ return { ...config.teamsSecondary };
280
+ }
251
281
  function getContextConfig() {
252
282
  if (_testContextOverride) {
253
283
  return { ..._testContextOverride };
@@ -268,11 +298,41 @@ function getOAuthConfig() {
268
298
  const config = loadConfig();
269
299
  return { ...config.oauth };
270
300
  }
301
+ /** Resolve OAuth connection names for a specific tenant, falling back to defaults. */
302
+ function resolveOAuthForTenant(tenantId) {
303
+ const base = getOAuthConfig();
304
+ const overrides = tenantId ? base.tenantOverrides?.[tenantId] : undefined;
305
+ return {
306
+ graphConnectionName: overrides?.graphConnectionName ?? base.graphConnectionName,
307
+ adoConnectionName: overrides?.adoConnectionName ?? base.adoConnectionName,
308
+ githubConnectionName: overrides?.githubConnectionName ?? base.githubConnectionName,
309
+ };
310
+ }
271
311
  function getTeamsChannelConfig() {
272
312
  const config = loadConfig();
273
313
  const { skipConfirmation, flushIntervalMs, port } = config.teamsChannel;
274
314
  return { skipConfirmation, flushIntervalMs, port };
275
315
  }
316
+ function getBlueBubblesConfig() {
317
+ const config = loadConfig();
318
+ const { serverUrl, password, accountId } = config.bluebubbles;
319
+ if (!serverUrl.trim()) {
320
+ throw new Error("bluebubbles.serverUrl is required in secrets.json to run the BlueBubbles sense.");
321
+ }
322
+ if (!password.trim()) {
323
+ throw new Error("bluebubbles.password is required in secrets.json to run the BlueBubbles sense.");
324
+ }
325
+ return {
326
+ serverUrl: serverUrl.trim(),
327
+ password: password.trim(),
328
+ accountId: accountId.trim() || "default",
329
+ };
330
+ }
331
+ function getBlueBubblesChannelConfig() {
332
+ const config = loadConfig();
333
+ const { port, webhookPath, requestTimeoutMs } = config.bluebubblesChannel;
334
+ return { port, webhookPath, requestTimeoutMs };
335
+ }
276
336
  function getIntegrationsConfig() {
277
337
  const config = loadConfig();
278
338
  return { ...config.integrations };
@@ -281,16 +341,29 @@ function getOpenAIEmbeddingsApiKey() {
281
341
  return getIntegrationsConfig().openaiEmbeddingsApiKey;
282
342
  }
283
343
  function getLogsDir() {
284
- return path.join(os.homedir(), ".agentstate", (0, identity_1.getAgentName)(), "logs");
344
+ return path.join((0, identity_1.getAgentRoot)(), "state", "logs");
285
345
  }
286
346
  function sanitizeKey(key) {
287
347
  return key.replace(/[/:]/g, "_");
288
348
  }
289
- function sessionPath(friendId, channel, key) {
290
- const dir = path.join(os.homedir(), ".agentstate", (0, identity_1.getAgentName)(), "sessions", friendId, channel);
291
- fs.mkdirSync(dir, { recursive: true });
349
+ function slugify(value) {
350
+ return value
351
+ .trim()
352
+ .toLowerCase()
353
+ .replace(/[^a-z0-9]+/g, "-")
354
+ .replace(/^-+/, "")
355
+ .replace(/-+$/, "");
356
+ }
357
+ function resolveSessionPath(friendId, channel, key, options) {
358
+ const dir = path.join((0, identity_1.getAgentRoot)(), "state", "sessions", friendId, channel);
359
+ if (options?.ensureDir) {
360
+ fs.mkdirSync(dir, { recursive: true });
361
+ }
292
362
  return path.join(dir, sanitizeKey(key) + ".json");
293
363
  }
364
+ function sessionPath(friendId, channel, key) {
365
+ return resolveSessionPath(friendId, channel, key, { ensureDir: true });
366
+ }
294
367
  function logPath(channel, key) {
295
368
  return path.join(getLogsDir(), channel, sanitizeKey(key) + ".ndjson");
296
369
  }
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasToolIntent = exports.buildSystem = exports.toResponsesTools = exports.toResponsesInput = exports.streamResponsesApi = exports.streamChatCompletion = exports.getToolsForChannel = exports.summarizeArgs = exports.execTool = exports.tools = void 0;
4
3
  exports.createProviderRegistry = createProviderRegistry;
4
+ exports.resetProviderRuntime = resetProviderRuntime;
5
5
  exports.getModel = getModel;
6
6
  exports.getProvider = getProvider;
7
7
  exports.createSummarize = createSummarize;
8
8
  exports.getProviderDisplayLabel = getProviderDisplayLabel;
9
9
  exports.stripLastToolCalls = stripLastToolCalls;
10
+ exports.repairOrphanedToolCalls = repairOrphanedToolCalls;
10
11
  exports.isTransientError = isTransientError;
11
12
  exports.classifyTransientError = classifyTransientError;
12
13
  exports.runAgent = runAgent;
@@ -14,9 +15,6 @@ const config_1 = require("./config");
14
15
  const identity_1 = require("./identity");
15
16
  const tools_1 = require("../repertoire/tools");
16
17
  const channel_1 = require("../mind/friends/channel");
17
- // Kick detection preserved but disabled — see comment in agent loop below.
18
- // import { detectKick } from "./kicks";
19
- // import type { KickReason } from "./kicks";
20
18
  const runtime_1 = require("../nerves/runtime");
21
19
  const context_1 = require("../mind/context");
22
20
  const prompt_1 = require("../mind/prompt");
@@ -73,6 +71,14 @@ function getProviderRuntime() {
73
71
  }
74
72
  return _providerRuntime;
75
73
  }
74
+ /**
75
+ * Clear the cached provider runtime so the next call to getProviderRuntime()
76
+ * re-creates it from current config. Used by the adoption specialist to
77
+ * switch provider context without restarting the process.
78
+ */
79
+ function resetProviderRuntime() {
80
+ _providerRuntime = null;
81
+ }
76
82
  function getModel() {
77
83
  return getProviderRuntime().model;
78
84
  }
@@ -104,24 +110,6 @@ function getProviderDisplayLabel() {
104
110
  };
105
111
  return providerLabelBuilders[getProvider()]();
106
112
  }
107
- // Re-export tools, execTool, summarizeArgs from ./tools for backward compat
108
- var tools_2 = require("../repertoire/tools");
109
- Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_2.tools; } });
110
- Object.defineProperty(exports, "execTool", { enumerable: true, get: function () { return tools_2.execTool; } });
111
- Object.defineProperty(exports, "summarizeArgs", { enumerable: true, get: function () { return tools_2.summarizeArgs; } });
112
- Object.defineProperty(exports, "getToolsForChannel", { enumerable: true, get: function () { return tools_2.getToolsForChannel; } });
113
- // Re-export streaming functions for backward compat
114
- var streaming_1 = require("./streaming");
115
- Object.defineProperty(exports, "streamChatCompletion", { enumerable: true, get: function () { return streaming_1.streamChatCompletion; } });
116
- Object.defineProperty(exports, "streamResponsesApi", { enumerable: true, get: function () { return streaming_1.streamResponsesApi; } });
117
- Object.defineProperty(exports, "toResponsesInput", { enumerable: true, get: function () { return streaming_1.toResponsesInput; } });
118
- Object.defineProperty(exports, "toResponsesTools", { enumerable: true, get: function () { return streaming_1.toResponsesTools; } });
119
- // Re-export prompt functions for backward compat
120
- var prompt_2 = require("../mind/prompt");
121
- Object.defineProperty(exports, "buildSystem", { enumerable: true, get: function () { return prompt_2.buildSystem; } });
122
- // Re-export kick utilities for backward compat
123
- var kicks_1 = require("./kicks");
124
- Object.defineProperty(exports, "hasToolIntent", { enumerable: true, get: function () { return kicks_1.hasToolIntent; } });
125
113
  function upsertSystemPrompt(messages, systemText) {
126
114
  const systemMessage = { role: "system", content: systemText };
127
115
  if (messages[0]?.role === "system") {
@@ -151,6 +139,68 @@ function stripLastToolCalls(messages) {
151
139
  }
152
140
  }
153
141
  }
142
+ // Roles that end a tool-result scan. When scanning forward from an assistant
143
+ // message, stop at the next assistant or user message (tool results must be
144
+ // adjacent to their originating assistant message).
145
+ const TOOL_SCAN_BOUNDARY_ROLES = new Set(["assistant", "user"]);
146
+ // Repair orphaned tool_calls and tool results anywhere in the message history.
147
+ // 1. If an assistant message has tool_calls but missing tool results, inject synthetic error results.
148
+ // 2. If a tool result's tool_call_id doesn't match any tool_calls in a preceding assistant message, remove it.
149
+ // This prevents 400 errors from the API after an aborted turn.
150
+ function repairOrphanedToolCalls(messages) {
151
+ // Pass 1: collect all valid tool_call IDs from assistant messages
152
+ const validCallIds = new Set();
153
+ for (const msg of messages) {
154
+ if (msg.role === "assistant") {
155
+ const asst = msg;
156
+ if (asst.tool_calls) {
157
+ for (const tc of asst.tool_calls)
158
+ validCallIds.add(tc.id);
159
+ }
160
+ }
161
+ }
162
+ // Pass 2: remove orphaned tool results (tool_call_id not in any assistant's tool_calls)
163
+ for (let i = messages.length - 1; i >= 0; i--) {
164
+ if (messages[i].role === "tool") {
165
+ const toolMsg = messages[i];
166
+ if (!validCallIds.has(toolMsg.tool_call_id)) {
167
+ messages.splice(i, 1);
168
+ }
169
+ }
170
+ }
171
+ // Pass 3: inject synthetic results for tool_calls missing their tool results
172
+ for (let i = 0; i < messages.length; i++) {
173
+ const msg = messages[i];
174
+ if (msg.role !== "assistant")
175
+ continue;
176
+ const asst = msg;
177
+ if (!asst.tool_calls || asst.tool_calls.length === 0)
178
+ continue;
179
+ // Collect tool result IDs that follow this assistant message
180
+ const resultIds = new Set();
181
+ for (let j = i + 1; j < messages.length; j++) {
182
+ const following = messages[j];
183
+ if (following.role === "tool") {
184
+ resultIds.add(following.tool_call_id);
185
+ }
186
+ else if (TOOL_SCAN_BOUNDARY_ROLES.has(following.role)) {
187
+ break;
188
+ }
189
+ }
190
+ const missing = asst.tool_calls.filter((tc) => !resultIds.has(tc.id));
191
+ if (missing.length > 0) {
192
+ const syntheticResults = missing.map((tc) => ({
193
+ role: "tool",
194
+ tool_call_id: tc.id,
195
+ content: "error: tool call was interrupted (previous turn timed out or was aborted)",
196
+ }));
197
+ let insertAt = i + 1;
198
+ while (insertAt < messages.length && messages[insertAt].role === "tool")
199
+ insertAt++;
200
+ messages.splice(insertAt, 0, ...syntheticResults);
201
+ }
202
+ }
203
+ }
154
204
  // Detect context overflow errors from Azure or MiniMax
155
205
  function isContextOverflow(err) {
156
206
  if (!(err instanceof Error))
@@ -256,9 +306,6 @@ async function runAgent(messages, callbacks, channel, signal, options) {
256
306
  }
257
307
  }
258
308
  await (0, associative_recall_1.injectAssociativeRecall)(messages);
259
- // kickCount and lastKickReason preserved but unused while kick detection is disabled.
260
- // let kickCount = 0;
261
- // let lastKickReason: KickReason | null = null;
262
309
  let done = false;
263
310
  let lastUsage;
264
311
  let overflowRetried = false;
@@ -269,7 +316,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
269
316
  }
270
317
  catch { /* unsupported */ }
271
318
  const toolPreferences = currentContext?.friend?.toolPreferences;
272
- const baseTools = (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined);
319
+ const baseTools = options?.tools ?? (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined, currentContext);
273
320
  // Rebase provider-owned turn state from canonical messages at user-turn start.
274
321
  // This prevents stale provider caches from replaying prior-turn context.
275
322
  providerRuntime.resetTurnState(messages);
@@ -323,24 +370,9 @@ async function runAgent(messages, callbacks, channel, signal, options) {
323
370
  msg._reasoning_items = reasoningItems;
324
371
  }
325
372
  if (!result.toolCalls.length) {
326
- // Kick detection is disabled while tool_choice: required + final_answer
327
- // is the primary loop control mechanism. The model should never reach
328
- // this path (tool_choice: required forces a tool call), but if it does,
329
- // accept the response as-is rather than risk false-positive kicks.
330
- //
331
- // Preserved for future use — re-enable by uncommenting:
332
- // const kick = detectKick(result.content, options);
333
- // if (kick) {
334
- // kickCount++;
335
- // lastKickReason = kick.reason;
336
- // callbacks.onKick?.();
337
- // const kickContent = result.content
338
- // ? result.content + "\n\n" + kick.message
339
- // : kick.message;
340
- // messages.push({ role: "assistant", content: kickContent });
341
- // providerRuntime.resetTurnState(messages);
342
- // continue;
343
- // }
373
+ // No tool calls accept response as-is.
374
+ // (Kick detection disabled; tool_choice: required + final_answer
375
+ // is the primary loop control. See src/heart/kicks.ts to re-enable.)
344
376
  messages.push(msg);
345
377
  done = true;
346
378
  }
@@ -435,7 +467,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
435
467
  let toolResult;
436
468
  let success;
437
469
  try {
438
- toolResult = await (0, tools_1.execTool)(tc.name, args, options?.toolContext);
470
+ const execToolFn = options?.execTool ?? tools_1.execTool;
471
+ toolResult = await execToolFn(tc.name, args, options?.toolContext);
439
472
  success = true;
440
473
  }
441
474
  catch (e) {
@@ -0,0 +1,81 @@
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.listEnabledBundleAgents = listEnabledBundleAgents;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const identity_1 = require("../identity");
40
+ const runtime_1 = require("../../nerves/runtime");
41
+ function listEnabledBundleAgents(options = {}) {
42
+ const bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
43
+ const readdirSync = options.readdirSync ?? fs.readdirSync;
44
+ const readFileSync = options.readFileSync ?? fs.readFileSync;
45
+ let entries;
46
+ try {
47
+ entries = readdirSync(bundlesRoot, { withFileTypes: true });
48
+ }
49
+ catch {
50
+ (0, runtime_1.emitNervesEvent)({
51
+ level: "warn",
52
+ component: "daemon",
53
+ event: "daemon.agent_discovery_failed",
54
+ message: "failed to read bundle root for daemon agent discovery",
55
+ meta: { bundlesRoot },
56
+ });
57
+ return [];
58
+ }
59
+ const discovered = [];
60
+ for (const entry of entries) {
61
+ if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
62
+ continue;
63
+ const agentName = entry.name.slice(0, -5);
64
+ const configPath = path.join(bundlesRoot, entry.name, "agent.json");
65
+ let enabled = true;
66
+ try {
67
+ const raw = readFileSync(configPath, "utf-8");
68
+ const parsed = JSON.parse(raw);
69
+ if (typeof parsed.enabled === "boolean") {
70
+ enabled = parsed.enabled;
71
+ }
72
+ }
73
+ catch {
74
+ continue;
75
+ }
76
+ if (enabled) {
77
+ discovered.push(agentName);
78
+ }
79
+ }
80
+ return discovered.sort((left, right) => left.localeCompare(right));
81
+ }