@chozzz/vargos 3.1.5 → 3.2.1

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 (295) hide show
  1. package/CONTRIBUTING.md +2 -3
  2. package/README.md +25 -34
  3. package/dist/.templates/workspace/AGENTS.md +2 -2
  4. package/dist/boot.d.ts +5 -0
  5. package/dist/boot.d.ts.map +1 -1
  6. package/dist/boot.js +62 -87
  7. package/dist/boot.js.map +1 -1
  8. package/dist/cli/channels.d.ts +6 -23
  9. package/dist/cli/channels.d.ts.map +1 -1
  10. package/dist/cli/channels.js +15 -124
  11. package/dist/cli/channels.js.map +1 -1
  12. package/dist/cli/chat.d.ts +6 -0
  13. package/dist/cli/chat.d.ts.map +1 -0
  14. package/dist/cli/chat.js +49 -0
  15. package/dist/cli/chat.js.map +1 -0
  16. package/dist/cli/onboard.d.ts.map +1 -1
  17. package/dist/cli/onboard.js +2 -8
  18. package/dist/cli/onboard.js.map +1 -1
  19. package/dist/cli.d.ts +9 -7
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +207 -290
  22. package/dist/cli.js.map +1 -1
  23. package/dist/core/bus.d.ts +32 -0
  24. package/dist/core/bus.d.ts.map +1 -0
  25. package/dist/core/bus.js +133 -0
  26. package/dist/core/bus.js.map +1 -0
  27. package/dist/core/cli.d.ts +38 -0
  28. package/dist/core/cli.d.ts.map +1 -0
  29. package/dist/core/cli.js +199 -0
  30. package/dist/core/cli.js.map +1 -0
  31. package/dist/core/errors.d.ts +21 -0
  32. package/dist/core/errors.d.ts.map +1 -0
  33. package/dist/core/errors.js +30 -0
  34. package/dist/core/errors.js.map +1 -0
  35. package/dist/core/loader.d.ts +31 -0
  36. package/dist/core/loader.d.ts.map +1 -0
  37. package/dist/core/loader.js +73 -0
  38. package/dist/core/loader.js.map +1 -0
  39. package/dist/core/local.d.ts +12 -0
  40. package/dist/core/local.d.ts.map +1 -0
  41. package/dist/core/local.js +23 -0
  42. package/dist/core/local.js.map +1 -0
  43. package/dist/core/rpc-server.d.ts +11 -0
  44. package/dist/core/rpc-server.d.ts.map +1 -0
  45. package/dist/core/rpc-server.js +69 -0
  46. package/dist/core/rpc-server.js.map +1 -0
  47. package/dist/core/services.d.ts +8 -0
  48. package/dist/core/services.d.ts.map +1 -0
  49. package/dist/core/services.js +33 -0
  50. package/dist/core/services.js.map +1 -0
  51. package/dist/core/types.d.ts +63 -0
  52. package/dist/core/types.d.ts.map +1 -0
  53. package/dist/core/types.js +2 -0
  54. package/dist/core/types.js.map +1 -0
  55. package/dist/edge/mcp/index.d.ts +11 -11
  56. package/dist/edge/mcp/index.d.ts.map +1 -1
  57. package/dist/edge/mcp/index.js +24 -28
  58. package/dist/edge/mcp/index.js.map +1 -1
  59. package/dist/edge/webhooks/index.d.ts +8 -14
  60. package/dist/edge/webhooks/index.d.ts.map +1 -1
  61. package/dist/edge/webhooks/index.js +140 -194
  62. package/dist/edge/webhooks/index.js.map +1 -1
  63. package/dist/lib/logger.d.ts +2 -3
  64. package/dist/lib/logger.d.ts.map +1 -1
  65. package/dist/lib/logger.js +1 -1
  66. package/dist/lib/logger.js.map +1 -1
  67. package/dist/lib/paginate.d.ts +5 -1
  68. package/dist/lib/paginate.d.ts.map +1 -1
  69. package/dist/lib/retry.js +1 -1
  70. package/dist/lib/retry.js.map +1 -1
  71. package/dist/lib/util.d.ts +16 -0
  72. package/dist/lib/util.d.ts.map +1 -0
  73. package/dist/lib/util.js +63 -0
  74. package/dist/lib/util.js.map +1 -0
  75. package/dist/scripts/verify-core.d.ts +8 -0
  76. package/dist/scripts/verify-core.d.ts.map +1 -0
  77. package/dist/scripts/verify-core.js +191 -0
  78. package/dist/scripts/verify-core.js.map +1 -0
  79. package/dist/services/agent/index.d.ts +14 -18
  80. package/dist/services/agent/index.d.ts.map +1 -1
  81. package/dist/services/agent/index.js +469 -530
  82. package/dist/services/agent/index.js.map +1 -1
  83. package/dist/services/agent/tools.d.ts +3 -3
  84. package/dist/services/agent/tools.d.ts.map +1 -1
  85. package/dist/services/agent/tools.js +11 -19
  86. package/dist/services/agent/tools.js.map +1 -1
  87. package/dist/services/agent/types.d.ts +1 -1
  88. package/dist/services/agent/types.d.ts.map +1 -1
  89. package/dist/services/{channels → channel}/base-adapter.d.ts +5 -6
  90. package/dist/services/channel/base-adapter.d.ts.map +1 -0
  91. package/dist/services/channel/base-adapter.js.map +1 -0
  92. package/dist/services/channel/debounce.d.ts.map +1 -0
  93. package/dist/services/channel/debounce.js.map +1 -0
  94. package/dist/services/channel/dedupe.d.ts.map +1 -0
  95. package/dist/services/channel/dedupe.js.map +1 -0
  96. package/dist/services/channel/delivery.d.ts.map +1 -0
  97. package/dist/services/{channels → channel}/delivery.js +1 -1
  98. package/dist/services/channel/delivery.js.map +1 -0
  99. package/dist/services/{channels → channel}/index.d.ts +14 -17
  100. package/dist/services/channel/index.d.ts.map +1 -0
  101. package/dist/services/channel/index.js +354 -0
  102. package/dist/services/channel/index.js.map +1 -0
  103. package/dist/services/channel/link-expand.d.ts.map +1 -0
  104. package/dist/services/channel/link-expand.js.map +1 -0
  105. package/dist/services/channel/media-paths.d.ts.map +1 -0
  106. package/dist/services/channel/media-paths.js.map +1 -0
  107. package/dist/services/{channels → channel}/pipeline.d.ts +1 -1
  108. package/dist/services/channel/pipeline.d.ts.map +1 -0
  109. package/dist/services/channel/pipeline.js.map +1 -0
  110. package/dist/services/channel/provider-loader.d.ts.map +1 -0
  111. package/dist/services/channel/provider-loader.js.map +1 -0
  112. package/dist/services/channel/providers/telegram/adapter.d.ts.map +1 -0
  113. package/dist/services/{channels → channel}/providers/telegram/adapter.js +1 -1
  114. package/dist/services/channel/providers/telegram/adapter.js.map +1 -0
  115. package/dist/services/channel/providers/telegram/index.d.ts.map +1 -0
  116. package/dist/services/channel/providers/telegram/index.js.map +1 -0
  117. package/dist/services/channel/providers/telegram/normalizer.d.ts.map +1 -0
  118. package/dist/services/channel/providers/telegram/normalizer.js.map +1 -0
  119. package/dist/services/channel/providers/telegram/types.d.ts.map +1 -0
  120. package/dist/services/channel/providers/telegram/types.js.map +1 -0
  121. package/dist/services/channel/providers/whatsapp/adapter.d.ts.map +1 -0
  122. package/dist/services/channel/providers/whatsapp/adapter.js.map +1 -0
  123. package/dist/services/channel/providers/whatsapp/index.d.ts.map +1 -0
  124. package/dist/services/channel/providers/whatsapp/index.js.map +1 -0
  125. package/dist/services/channel/providers/whatsapp/normalizer.d.ts.map +1 -0
  126. package/dist/services/channel/providers/whatsapp/normalizer.js.map +1 -0
  127. package/dist/services/channel/providers/whatsapp/session.d.ts.map +1 -0
  128. package/dist/services/channel/providers/whatsapp/session.js.map +1 -0
  129. package/dist/services/channel/providers/whatsapp/types.d.ts.map +1 -0
  130. package/dist/services/channel/providers/whatsapp/types.js.map +1 -0
  131. package/dist/services/channel/reconnect.d.ts.map +1 -0
  132. package/dist/services/channel/reconnect.js.map +1 -0
  133. package/dist/services/channel/status-reactions.d.ts.map +1 -0
  134. package/dist/services/channel/status-reactions.js.map +1 -0
  135. package/dist/services/{channels → channel}/types.d.ts +1 -1
  136. package/dist/services/channel/types.d.ts.map +1 -0
  137. package/dist/services/channel/types.js.map +1 -0
  138. package/dist/services/channel/typing-state.d.ts.map +1 -0
  139. package/dist/services/channel/typing-state.js.map +1 -0
  140. package/dist/services/config/index.d.ts +45 -46
  141. package/dist/services/config/index.d.ts.map +1 -1
  142. package/dist/services/config/index.js +89 -182
  143. package/dist/services/config/index.js.map +1 -1
  144. package/dist/services/config/schemas/cron.d.ts +3 -3
  145. package/dist/services/config/schemas/providers.d.ts +12 -12
  146. package/dist/services/config/schemas/webhooks.d.ts +2 -2
  147. package/dist/services/cron/index.d.ts +16 -19
  148. package/dist/services/cron/index.d.ts.map +1 -1
  149. package/dist/services/cron/index.js +340 -396
  150. package/dist/services/cron/index.js.map +1 -1
  151. package/dist/services/log/index.d.ts +9 -8
  152. package/dist/services/log/index.d.ts.map +1 -1
  153. package/dist/services/log/index.js +71 -123
  154. package/dist/services/log/index.js.map +1 -1
  155. package/dist/services/{mcp-client → mcp}/index.d.ts +9 -11
  156. package/dist/services/mcp/index.d.ts.map +1 -0
  157. package/dist/services/{mcp-client → mcp}/index.js +19 -35
  158. package/dist/services/mcp/index.js.map +1 -0
  159. package/dist/services/media/index.d.ts +9 -13
  160. package/dist/services/media/index.d.ts.map +1 -1
  161. package/dist/services/media/index.js +53 -105
  162. package/dist/services/media/index.js.map +1 -1
  163. package/dist/services/memory/index.d.ts +12 -18
  164. package/dist/services/memory/index.d.ts.map +1 -1
  165. package/dist/services/memory/index.js +70 -132
  166. package/dist/services/memory/index.js.map +1 -1
  167. package/dist/services/web/index.d.ts +7 -7
  168. package/dist/services/web/index.d.ts.map +1 -1
  169. package/dist/services/web/index.js +41 -86
  170. package/dist/services/web/index.js.map +1 -1
  171. package/package.json +3 -2
  172. package/dist/gateway/bus.d.ts +0 -50
  173. package/dist/gateway/bus.d.ts.map +0 -1
  174. package/dist/gateway/bus.js +0 -2
  175. package/dist/gateway/bus.js.map +0 -1
  176. package/dist/gateway/decorators.d.ts +0 -40
  177. package/dist/gateway/decorators.d.ts.map +0 -1
  178. package/dist/gateway/decorators.js +0 -43
  179. package/dist/gateway/decorators.js.map +0 -1
  180. package/dist/gateway/emitter.d.ts +0 -52
  181. package/dist/gateway/emitter.d.ts.map +0 -1
  182. package/dist/gateway/emitter.js +0 -304
  183. package/dist/gateway/emitter.js.map +0 -1
  184. package/dist/gateway/events.d.ts +0 -329
  185. package/dist/gateway/events.d.ts.map +0 -1
  186. package/dist/gateway/events.js +0 -7
  187. package/dist/gateway/events.js.map +0 -1
  188. package/dist/gateway/tcp-server.d.ts +0 -7
  189. package/dist/gateway/tcp-server.d.ts.map +0 -1
  190. package/dist/gateway/tcp-server.js +0 -118
  191. package/dist/gateway/tcp-server.js.map +0 -1
  192. package/dist/lib/id.d.ts +0 -3
  193. package/dist/lib/id.d.ts.map +0 -1
  194. package/dist/lib/id.js +0 -5
  195. package/dist/lib/id.js.map +0 -1
  196. package/dist/lib/sleep.d.ts +0 -6
  197. package/dist/lib/sleep.d.ts.map +0 -1
  198. package/dist/lib/sleep.js +0 -22
  199. package/dist/lib/sleep.js.map +0 -1
  200. package/dist/lib/strip-markdown.d.ts +0 -7
  201. package/dist/lib/strip-markdown.d.ts.map +0 -1
  202. package/dist/lib/strip-markdown.js +0 -32
  203. package/dist/lib/strip-markdown.js.map +0 -1
  204. package/dist/lib/timeout.d.ts +0 -6
  205. package/dist/lib/timeout.d.ts.map +0 -1
  206. package/dist/lib/timeout.js +0 -11
  207. package/dist/lib/timeout.js.map +0 -1
  208. package/dist/lib/truncate.d.ts +0 -11
  209. package/dist/lib/truncate.d.ts.map +0 -1
  210. package/dist/lib/truncate.js +0 -17
  211. package/dist/lib/truncate.js.map +0 -1
  212. package/dist/services/channels/base-adapter.d.ts.map +0 -1
  213. package/dist/services/channels/base-adapter.js.map +0 -1
  214. package/dist/services/channels/debounce.d.ts.map +0 -1
  215. package/dist/services/channels/debounce.js.map +0 -1
  216. package/dist/services/channels/dedupe.d.ts.map +0 -1
  217. package/dist/services/channels/dedupe.js.map +0 -1
  218. package/dist/services/channels/delivery.d.ts.map +0 -1
  219. package/dist/services/channels/delivery.js.map +0 -1
  220. package/dist/services/channels/index.d.ts.map +0 -1
  221. package/dist/services/channels/index.js +0 -413
  222. package/dist/services/channels/index.js.map +0 -1
  223. package/dist/services/channels/link-expand.d.ts.map +0 -1
  224. package/dist/services/channels/link-expand.js.map +0 -1
  225. package/dist/services/channels/media-paths.d.ts.map +0 -1
  226. package/dist/services/channels/media-paths.js.map +0 -1
  227. package/dist/services/channels/pipeline.d.ts.map +0 -1
  228. package/dist/services/channels/pipeline.js.map +0 -1
  229. package/dist/services/channels/provider-loader.d.ts.map +0 -1
  230. package/dist/services/channels/provider-loader.js.map +0 -1
  231. package/dist/services/channels/providers/telegram/adapter.d.ts.map +0 -1
  232. package/dist/services/channels/providers/telegram/adapter.js.map +0 -1
  233. package/dist/services/channels/providers/telegram/index.d.ts.map +0 -1
  234. package/dist/services/channels/providers/telegram/index.js.map +0 -1
  235. package/dist/services/channels/providers/telegram/normalizer.d.ts.map +0 -1
  236. package/dist/services/channels/providers/telegram/normalizer.js.map +0 -1
  237. package/dist/services/channels/providers/telegram/types.d.ts.map +0 -1
  238. package/dist/services/channels/providers/telegram/types.js.map +0 -1
  239. package/dist/services/channels/providers/whatsapp/adapter.d.ts.map +0 -1
  240. package/dist/services/channels/providers/whatsapp/adapter.js.map +0 -1
  241. package/dist/services/channels/providers/whatsapp/index.d.ts.map +0 -1
  242. package/dist/services/channels/providers/whatsapp/index.js.map +0 -1
  243. package/dist/services/channels/providers/whatsapp/normalizer.d.ts.map +0 -1
  244. package/dist/services/channels/providers/whatsapp/normalizer.js.map +0 -1
  245. package/dist/services/channels/providers/whatsapp/session.d.ts.map +0 -1
  246. package/dist/services/channels/providers/whatsapp/session.js.map +0 -1
  247. package/dist/services/channels/providers/whatsapp/types.d.ts.map +0 -1
  248. package/dist/services/channels/providers/whatsapp/types.js.map +0 -1
  249. package/dist/services/channels/reconnect.d.ts.map +0 -1
  250. package/dist/services/channels/reconnect.js.map +0 -1
  251. package/dist/services/channels/status-reactions.d.ts.map +0 -1
  252. package/dist/services/channels/status-reactions.js.map +0 -1
  253. package/dist/services/channels/types.d.ts.map +0 -1
  254. package/dist/services/channels/types.js.map +0 -1
  255. package/dist/services/channels/typing-state.d.ts.map +0 -1
  256. package/dist/services/channels/typing-state.js.map +0 -1
  257. package/dist/services/mcp-client/index.d.ts.map +0 -1
  258. package/dist/services/mcp-client/index.js.map +0 -1
  259. /package/dist/services/{channels → channel}/base-adapter.js +0 -0
  260. /package/dist/services/{channels → channel}/debounce.d.ts +0 -0
  261. /package/dist/services/{channels → channel}/debounce.js +0 -0
  262. /package/dist/services/{channels → channel}/dedupe.d.ts +0 -0
  263. /package/dist/services/{channels → channel}/dedupe.js +0 -0
  264. /package/dist/services/{channels → channel}/delivery.d.ts +0 -0
  265. /package/dist/services/{channels → channel}/link-expand.d.ts +0 -0
  266. /package/dist/services/{channels → channel}/link-expand.js +0 -0
  267. /package/dist/services/{channels → channel}/media-paths.d.ts +0 -0
  268. /package/dist/services/{channels → channel}/media-paths.js +0 -0
  269. /package/dist/services/{channels → channel}/pipeline.js +0 -0
  270. /package/dist/services/{channels → channel}/provider-loader.d.ts +0 -0
  271. /package/dist/services/{channels → channel}/provider-loader.js +0 -0
  272. /package/dist/services/{channels → channel}/providers/telegram/adapter.d.ts +0 -0
  273. /package/dist/services/{channels → channel}/providers/telegram/index.d.ts +0 -0
  274. /package/dist/services/{channels → channel}/providers/telegram/index.js +0 -0
  275. /package/dist/services/{channels → channel}/providers/telegram/normalizer.d.ts +0 -0
  276. /package/dist/services/{channels → channel}/providers/telegram/normalizer.js +0 -0
  277. /package/dist/services/{channels → channel}/providers/telegram/types.d.ts +0 -0
  278. /package/dist/services/{channels → channel}/providers/telegram/types.js +0 -0
  279. /package/dist/services/{channels → channel}/providers/whatsapp/adapter.d.ts +0 -0
  280. /package/dist/services/{channels → channel}/providers/whatsapp/adapter.js +0 -0
  281. /package/dist/services/{channels → channel}/providers/whatsapp/index.d.ts +0 -0
  282. /package/dist/services/{channels → channel}/providers/whatsapp/index.js +0 -0
  283. /package/dist/services/{channels → channel}/providers/whatsapp/normalizer.d.ts +0 -0
  284. /package/dist/services/{channels → channel}/providers/whatsapp/normalizer.js +0 -0
  285. /package/dist/services/{channels → channel}/providers/whatsapp/session.d.ts +0 -0
  286. /package/dist/services/{channels → channel}/providers/whatsapp/session.js +0 -0
  287. /package/dist/services/{channels → channel}/providers/whatsapp/types.d.ts +0 -0
  288. /package/dist/services/{channels → channel}/providers/whatsapp/types.js +0 -0
  289. /package/dist/services/{channels → channel}/reconnect.d.ts +0 -0
  290. /package/dist/services/{channels → channel}/reconnect.js +0 -0
  291. /package/dist/services/{channels → channel}/status-reactions.d.ts +0 -0
  292. /package/dist/services/{channels → channel}/status-reactions.js +0 -0
  293. /package/dist/services/{channels → channel}/types.js +0 -0
  294. /package/dist/services/{channels → channel}/typing-state.d.ts +0 -0
  295. /package/dist/services/{channels → channel}/typing-state.js +0 -0
@@ -7,48 +7,13 @@
7
7
  * - Debug mode for inspecting tools, prompts, history
8
8
  * - Streaming events passthrough to bus (agent.onDelta, agent.onTool, agent.onCompleted)
9
9
  */
10
- var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
11
- var useValue = arguments.length > 2;
12
- for (var i = 0; i < initializers.length; i++) {
13
- value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
14
- }
15
- return useValue ? value : void 0;
16
- };
17
- var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
18
- function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
19
- var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
20
- var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
21
- var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
22
- var _, done = false;
23
- for (var i = decorators.length - 1; i >= 0; i--) {
24
- var context = {};
25
- for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
26
- for (var p in contextIn.access) context.access[p] = contextIn.access[p];
27
- context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
28
- var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
29
- if (kind === "accessor") {
30
- if (result === void 0) continue;
31
- if (result === null || typeof result !== "object") throw new TypeError("Object expected");
32
- if (_ = accept(result.get)) descriptor.get = _;
33
- if (_ = accept(result.set)) descriptor.set = _;
34
- if (_ = accept(result.init)) initializers.unshift(_);
35
- }
36
- else if (_ = accept(result)) {
37
- if (kind === "field") initializers.unshift(_);
38
- else descriptor[key] = _;
39
- }
40
- }
41
- if (target) Object.defineProperty(target, contextIn.name, descriptor);
42
- done = true;
43
- };
44
10
  import { z } from 'zod';
45
11
  import path from 'node:path';
46
- import { register } from '../../gateway/decorators.js';
47
12
  import { createLogger } from '../../lib/logger.js';
48
13
  import { parseDirectives } from './directives.js';
49
- import { withTimeout } from '../../lib/timeout.js';
14
+ import { withTimeout } from '../../lib/util.js';
50
15
  import { interpolatePrompt } from './prompt-interpolate.js';
51
- import { truncate } from '../../lib/truncate.js';
16
+ import { truncate } from '../../lib/util.js';
52
17
  import { existsSync, promises as fs } from 'node:fs';
53
18
  import { getDataPaths } from '../../lib/paths.js';
54
19
  import { parseSessionKey, isSubagentSession, rootSessionKey } from '../../lib/session-key.js';
@@ -62,533 +27,507 @@ const log = createLogger('agent');
62
27
  // Hardcoded agent execution constants
63
28
  const EXECUTION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
64
29
  // ── AgentService ─────────────────────────────────────────────────────────────
65
- let AgentService = (() => {
66
- let _instanceExtraInitializers = [];
67
- let _execute_decorators;
68
- let _appendMessage_decorators;
69
- let _status_decorators;
70
- return class AgentService {
71
- static {
72
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
73
- _execute_decorators = [register('agent.execute', {
74
- description: 'Executes a task with the agent, optionally delegating to a subagent.',
75
- schema: z.object({
76
- /**
77
- * `sessionKey` is intentionally not registered as schema here because when agent.execute is called as a tool from within an agent session, the `sessionKey` is auto-injected by wrapEventAsToolDefinition(). For direct calls from channels, cron, webhooks, TCP, the `sessionKey` is provided in the EventMap and is required for execution.
78
- * @see wrapEventAsToolDefinition in tools.ts for how `sessionKey` is injected when called as a tool.
79
- */
80
- task: z.string().describe('The task to execute.'),
81
- cwd: z.string().optional().describe('Working directory for the agent — defaults to workspace dir.'),
82
- model: z.string().optional().describe('Optional model override as "provider:modelId" (e.g. "anthropic:claude-opus-4"). Omit to use the agent default an unknown value falls back to the default.'),
83
- }),
84
- })];
85
- _appendMessage_decorators = [register('agent.appendMessage')];
86
- _status_decorators = [register('agent.status', {
87
- description: 'Return the agent session inventory (state, parent links, model). Pass sessionKey to scope to one session and its subagents.',
88
- schema: z.object({ sessionKey: z.string().optional() }),
89
- })];
90
- __esDecorate(this, null, _execute_decorators, { kind: "method", name: "execute", static: false, private: false, access: { has: obj => "execute" in obj, get: obj => obj.execute }, metadata: _metadata }, null, _instanceExtraInitializers);
91
- __esDecorate(this, null, _appendMessage_decorators, { kind: "method", name: "appendMessage", static: false, private: false, access: { has: obj => "appendMessage" in obj, get: obj => obj.appendMessage }, metadata: _metadata }, null, _instanceExtraInitializers);
92
- __esDecorate(this, null, _status_decorators, { kind: "method", name: "status", static: false, private: false, access: { has: obj => "status" in obj, get: obj => obj.status }, metadata: _metadata }, null, _instanceExtraInitializers);
93
- if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
30
+ export class AgentService {
31
+ name = 'agent';
32
+ bus;
33
+ config;
34
+ sessions = new Map();
35
+ /** sessionKey epoch ms when the session entered the cache (for agent.status). */
36
+ sessionMeta = new Map();
37
+ activeRuns = new Set();
38
+ agentDir;
39
+ authStorage;
40
+ modelRegistry;
41
+ settings;
42
+ async init(bus) {
43
+ this.bus = bus;
44
+ this.config = await bus.call('config.get', {});
45
+ const paths = getDataPaths();
46
+ this.agentDir = path.join(paths.dataDir, 'agent');
47
+ // Use ~/.vargos/agent for auth and models (override PiAgent defaults)
48
+ const authJsonPath = path.join(this.agentDir, 'auth.json');
49
+ const modelsJsonPath = path.join(this.agentDir, 'models.json');
50
+ this.authStorage = AuthStorage.create(authJsonPath);
51
+ this.modelRegistry = ModelRegistry.create(this.authStorage, modelsJsonPath);
52
+ const modelError = this.modelRegistry.getError();
53
+ if (modelError) {
54
+ throw new Error(`Failed to load models from ${modelsJsonPath}: ${modelError}`);
94
55
  }
95
- bus = __runInitializers(this, _instanceExtraInitializers);
96
- config;
97
- sessions = new Map();
98
- /** sessionKey → epoch ms when the session entered the cache (for agent.status). */
99
- sessionMeta = new Map();
100
- activeRuns = new Set();
101
- agentDir;
102
- authStorage;
103
- modelRegistry;
104
- settings;
105
- constructor(deps) {
106
- this.bus = deps.bus;
107
- this.config = deps.config;
108
- const paths = getDataPaths();
109
- this.agentDir = path.join(paths.dataDir, 'agent');
110
- // Use ~/.vargos/agent for auth and models (override PiAgent defaults)
111
- const authJsonPath = path.join(this.agentDir, 'auth.json');
112
- const modelsJsonPath = path.join(this.agentDir, 'models.json');
113
- this.authStorage = AuthStorage.create(authJsonPath);
114
- this.modelRegistry = ModelRegistry.create(this.authStorage, modelsJsonPath);
115
- // Report model loading errors instead of silently falling back
116
- const modelError = this.modelRegistry.getError();
117
- if (modelError) {
118
- throw new Error(`Failed to load models from ${modelsJsonPath}: ${modelError}`);
119
- }
120
- this.settings = SettingsManager.create(paths.dataDir, this.agentDir);
121
- // NOTE: SettingsManager loads ~/.vargos/agent/models.json which has the
122
- // authoritative provider + model definitions. Pi Agent is the source of truth.
123
- // Apply retry settings for transient error recovery
124
- this.settings.applyOverrides({
56
+ this.settings = SettingsManager.create(paths.dataDir, this.agentDir);
57
+ this.settings.applyOverrides({
58
+ retry: {
59
+ enabled: true,
60
+ maxRetries: 3,
61
+ baseDelayMs: 1000,
62
+ provider: { timeoutMs: 120000, maxRetries: 3, maxRetryDelayMs: 30000 },
63
+ },
64
+ });
65
+ this.registerMethods(bus);
66
+ await this.persistRetrySettings();
67
+ }
68
+ registerMethods(bus) {
69
+ bus.register('agent.execute', {
70
+ description: 'Executes a task with the agent, optionally delegating to a subagent.',
71
+ // passthrough keeps `sessionKey` (auto-injected by the tool wrapper, supplied by
72
+ // direct callers) alive through validation without advertising it as a tool param.
73
+ schema: z.object({
74
+ task: z.string().describe('The task to execute.'),
75
+ cwd: z.string().optional().describe('Working directory for the agent — defaults to workspace dir.'),
76
+ model: z.string().optional().describe('Optional model override as "provider:modelId" (e.g. "anthropic:claude-opus-4"). Omit to use the agent default.'),
77
+ }).passthrough(),
78
+ cli: { positional: ['task'] },
79
+ }, (p) => this.execute(p));
80
+ bus.register('agent.appendMessage', {
81
+ description: 'Append a message to a session\'s history without executing the agent.',
82
+ schema: z.object({ sessionKey: z.string(), content: z.string() }),
83
+ internal: true,
84
+ }, (p) => this.appendMessage(p));
85
+ bus.register('agent.status', {
86
+ description: 'Return the agent session inventory (state, parent links, model). Pass sessionKey to scope to one session and its subagents.',
87
+ schema: z.object({ sessionKey: z.string().optional() }),
88
+ cli: { positional: ['sessionKey'] },
89
+ }, (p) => this.status(p));
90
+ }
91
+ dispose() {
92
+ this.sessions.forEach((session) => session.dispose());
93
+ this.sessions.clear();
94
+ this.sessionMeta.clear();
95
+ }
96
+ /**
97
+ * Persist retry settings to disk during boot
98
+ */
99
+ async persistRetrySettings() {
100
+ try {
101
+ const settingsPath = path.join(this.agentDir, 'settings.json');
102
+ const currentData = await fs.readFile(settingsPath, 'utf-8');
103
+ const currentSettings = JSON.parse(currentData);
104
+ const updated = {
105
+ ...currentSettings,
125
106
  retry: {
126
107
  enabled: true,
127
108
  maxRetries: 3,
128
109
  baseDelayMs: 1000,
129
110
  provider: {
130
- timeoutMs: 120000, // 2 min per API call
111
+ timeoutMs: 120000,
131
112
  maxRetries: 3,
132
- maxRetryDelayMs: 30000, // exponential backoff up to 30s
113
+ maxRetryDelayMs: 30000,
133
114
  },
134
115
  },
135
- });
116
+ };
117
+ await fs.writeFile(settingsPath, JSON.stringify(updated, null, 2), 'utf-8');
118
+ log.debug('Agent retry settings persisted to settings.json');
136
119
  }
137
- /**
138
- * Persist retry settings to disk during boot
139
- */
140
- async start() {
141
- try {
142
- const settingsPath = path.join(this.agentDir, 'settings.json');
143
- const currentData = await fs.readFile(settingsPath, 'utf-8');
144
- const currentSettings = JSON.parse(currentData);
145
- const updated = {
146
- ...currentSettings,
147
- retry: {
148
- enabled: true,
149
- maxRetries: 3,
150
- baseDelayMs: 1000,
151
- provider: {
152
- timeoutMs: 120000,
153
- maxRetries: 3,
154
- maxRetryDelayMs: 30000,
155
- },
156
- },
157
- };
158
- await fs.writeFile(settingsPath, JSON.stringify(updated, null, 2), 'utf-8');
159
- log.debug('Agent retry settings persisted to settings.json');
160
- }
161
- catch (err) {
162
- log.warn(`Failed to persist retry settings: ${err instanceof Error ? err.message : String(err)}`);
163
- }
120
+ catch (err) {
121
+ log.warn(`Failed to persist retry settings: ${err instanceof Error ? err.message : String(err)}`);
164
122
  }
165
- /**
166
- * agent.execute — Run a task
167
- *
168
- * Note: sessionKey is declared optional here because it's auto-injected by
169
- * wrapEventAsToolDefinition() when the agent calls this as a tool. Direct callers
170
- * (channels, cron, webhooks, TCP) still provide sessionKey via EventMap.
171
- */
172
- async execute(params) {
173
- if (!params.sessionKey) {
174
- throw new Error('sessionKey is required for agent.execute');
175
- }
176
- log.debug(`execute: START ${params.sessionKey}`);
177
- // Fall back to the session's default model when the override is missing or unknown,
178
- // instead of failing the run (agents sometimes pass an ill-formed or stale model id).
179
- let model = params.model;
180
- if (model && !this.isValidModel(model)) {
181
- log.warn(`agent.execute: ignoring invalid model "${model}" (expected provider:modelId) — using default`);
182
- model = undefined;
183
- }
184
- const directives = parseDirectives(params.task);
185
- const task = interpolatePrompt(directives.cleaned || params.task);
186
- const session = await this.getOrCreateSession(params.sessionKey, { cwd: params.cwd, model });
187
- if (directives.thinkingLevel) {
188
- session.setThinkingLevel(directives.thinkingLevel);
189
- }
190
- this.activeRuns.add(params.sessionKey);
191
- const startTime = Date.now();
192
- const modelTag = `${session.model?.provider}:${session.model?.id}`;
193
- try {
194
- await withTimeout(session.prompt(task, { streamingBehavior: 'steer' }), EXECUTION_TIMEOUT_MS, `Agent execution timeout after ${EXECUTION_TIMEOUT_MS}ms`);
195
- }
196
- finally {
197
- this.activeRuns.delete(params.sessionKey);
198
- }
199
- const { content, error } = this.extractFinalAssistant(session);
200
- if (error) {
201
- log.error(`execute: ${params.sessionKey} ended with error [model=${modelTag}]: ${error}`);
202
- throw new Error(error);
203
- }
204
- const elapsed = Date.now() - startTime;
205
- log.info(`${params.sessionKey} → ${content.length} chars in ${elapsed}ms (${modelTag})`);
206
- return { response: content };
123
+ }
124
+ /**
125
+ * agent.execute — Run a task. `sessionKey` is auto-injected by the tool wrapper when
126
+ * the agent calls this as a tool; direct callers (channels, cron, webhooks, RPC)
127
+ * supply it. It survives validation via the schema's .passthrough().
128
+ */
129
+ async execute(params) {
130
+ if (!params.sessionKey) {
131
+ throw new Error('sessionKey is required for agent.execute');
207
132
  }
208
- /**
209
- * agent.appendMessage Append message to session JSONL without executing agent.
210
- * Records inbound messages in session history (observe-only for non-whitelisted).
211
- * Internal only — not exposed as an agent tool.
212
- */
213
- async appendMessage(params) {
214
- const session = await this.getOrCreateSession(params.sessionKey);
215
- const sessionFile = session.sessionManager.getSessionFile();
216
- if (!sessionFile) {
217
- log.debug(`No session file for ${params.sessionKey}, skipping append`);
218
- return;
219
- }
220
- log.debug(`Appending message to session ${params.sessionKey} (no execution)`);
221
- const isExecuting = this.activeRuns.has(params.sessionKey);
222
- session.sessionManager.appendMessage({
223
- timestamp: Date.now(),
224
- role: 'user',
225
- content: params.content,
226
- });
227
- if (!isExecuting) {
228
- // Manually force write to disk so other Vargos instances on the NAS/cluster can see the history
229
- session.exportToJsonl(sessionFile);
230
- // We MUST wipe the session from the local Vargos cache if the agent is not executing.
231
- // Why? The Pi SDK deliberately defers JSONL file creation until the FIRST assistant message.
232
- // By forcing `exportToJsonl`, we circumvented Pi SDK and created the file early on disk.
233
- // But this in-memory AgentSession's `flushed` flag remains `false`.
234
- // If kept in cache, the NEXT time this node executes the LLM, the Pi SDK will attempt
235
- // an exclusive create (`openSync(..., "wx")`) and violently crash with `EEXIST` because
236
- // the file is already there! Evicting cache forces full reload via `continueRecent()`.
237
- session.dispose();
238
- this.sessions.delete(params.sessionKey);
239
- this.sessionMeta.delete(params.sessionKey);
240
- }
133
+ log.debug(`execute: START ${params.sessionKey}`);
134
+ // Fall back to the session's default model when the override is missing or unknown,
135
+ // instead of failing the run (agents sometimes pass an ill-formed or stale model id).
136
+ let model = params.model;
137
+ if (model && !this.isValidModel(model)) {
138
+ log.warn(`agent.execute: ignoring invalid model "${model}" (expected provider:modelId) — using default`);
139
+ model = undefined;
241
140
  }
242
- /**
243
- * agent.status Inventory cached sessions (parents, subagents, idle) with their
244
- * run state, parent relationship, and model. When `sessionKey` is given, the result
245
- * is scoped to that session and its subagents — letting a parent observe its own
246
- * subtree. `activeRuns` is kept for callers that only need the executing keys.
247
- */
248
- async status(params) {
249
- const scope = params.sessionKey;
250
- const inScope = (key) => !scope || key === scope || key.startsWith(`${scope}:subagent:`);
251
- const sessions = Array.from(this.sessions.entries())
252
- .filter(([key]) => inScope(key))
253
- .map(([sessionKey, session]) => ({
254
- sessionKey,
255
- state: this.activeRuns.has(sessionKey) ? 'running' : 'idle',
256
- parentKey: isSubagentSession(sessionKey) ? rootSessionKey(sessionKey) : undefined,
257
- model: session.model ? `${session.model.provider}:${session.model.id}` : undefined,
258
- startedAt: this.sessionMeta.get(sessionKey),
259
- }));
260
- const activeRuns = Array.from(this.activeRuns).filter(inScope);
261
- return { sessions, activeRuns };
141
+ const directives = parseDirectives(params.task);
142
+ const task = interpolatePrompt(directives.cleaned || params.task);
143
+ const session = await this.getOrCreateSession(params.sessionKey, { cwd: params.cwd, model });
144
+ if (directives.thinkingLevel) {
145
+ session.setThinkingLevel(directives.thinkingLevel);
262
146
  }
263
- /**
264
- * Get or create AgentSession for sessionKey.
265
- * Uses SessionManager.continueRecent() to load the latest session file,
266
- * preserving conversation history across restarts.
267
- */
268
- async getOrCreateSession(sessionKey, options) {
269
- const cached = this.sessions.get(sessionKey);
270
- if (cached) {
271
- await this.applyModelOverride(cached, sessionKey, options?.model);
272
- return cached;
273
- }
274
- const paths = getDataPaths();
275
- const effectiveCwd = options?.cwd ?? paths.dataDir;
276
- const sessionDir = path.join(paths.sessionsDir, sessionKey.replace(/:/g, path.sep));
277
- // Use continueRecent to find and load the latest session file (preserves history).
278
- // Falls back to create() if no existing session file is found.
279
- let sessionManager;
280
- try {
281
- sessionManager = SessionManager.create(effectiveCwd, sessionDir);
282
- }
283
- catch (err) {
284
- if (err instanceof Error && 'code' in err && err.code === 'EEXIST') {
285
- // File was created by another code path (e.g. concurrent message for same session).
286
- // Fall back to continueRecent which opens existing files gracefully.
287
- log.debug(`session ${sessionKey}: create() hit EEXIST, falling back to continueRecent()`);
288
- sessionManager = SessionManager.continueRecent(effectiveCwd, sessionDir);
289
- }
290
- else {
291
- throw err;
292
- }
147
+ this.activeRuns.add(params.sessionKey);
148
+ const startTime = Date.now();
149
+ const modelTag = `${session.model?.provider}:${session.model?.id}`;
150
+ try {
151
+ await withTimeout(session.prompt(task, { streamingBehavior: 'steer' }), EXECUTION_TIMEOUT_MS, `Agent execution timeout after ${EXECUTION_TIMEOUT_MS}ms`);
152
+ }
153
+ finally {
154
+ this.activeRuns.delete(params.sessionKey);
155
+ }
156
+ const { content, error } = this.extractFinalAssistant(session);
157
+ if (error) {
158
+ log.error(`execute: ${params.sessionKey} ended with error [model=${modelTag}]: ${error}`);
159
+ throw new Error(error);
160
+ }
161
+ const elapsed = Date.now() - startTime;
162
+ log.info(`${params.sessionKey} ${content.length} chars in ${elapsed}ms (${modelTag})`);
163
+ return { response: content };
164
+ }
165
+ /**
166
+ * agent.appendMessage — Append message to session JSONL without executing agent.
167
+ * Records inbound messages in session history (observe-only for non-whitelisted).
168
+ * Internal only not exposed as an agent tool.
169
+ */
170
+ async appendMessage(params) {
171
+ const session = await this.getOrCreateSession(params.sessionKey);
172
+ const sessionFile = session.sessionManager.getSessionFile();
173
+ if (!sessionFile) {
174
+ log.debug(`No session file for ${params.sessionKey}, skipping append`);
175
+ return;
176
+ }
177
+ log.debug(`Appending message to session ${params.sessionKey} (no execution)`);
178
+ const isExecuting = this.activeRuns.has(params.sessionKey);
179
+ session.sessionManager.appendMessage({
180
+ timestamp: Date.now(),
181
+ role: 'user',
182
+ content: params.content,
183
+ });
184
+ if (!isExecuting) {
185
+ // Manually force write to disk so other Vargos instances on the NAS/cluster can see the history
186
+ session.exportToJsonl(sessionFile);
187
+ // We MUST wipe the session from the local Vargos cache if the agent is not executing.
188
+ // Why? The Pi SDK deliberately defers JSONL file creation until the FIRST assistant message.
189
+ // By forcing `exportToJsonl`, we circumvented Pi SDK and created the file early on disk.
190
+ // But this in-memory AgentSession's `flushed` flag remains `false`.
191
+ // If kept in cache, the NEXT time this node executes the LLM, the Pi SDK will attempt
192
+ // an exclusive create (`openSync(..., "wx")`) and violently crash with `EEXIST` because
193
+ // the file is already there! Evicting cache forces full reload via `continueRecent()`.
194
+ session.dispose();
195
+ this.sessions.delete(params.sessionKey);
196
+ this.sessionMeta.delete(params.sessionKey);
197
+ }
198
+ }
199
+ /**
200
+ * agent.status — Inventory cached sessions (parents, subagents, idle) with their
201
+ * run state, parent relationship, and model. When `sessionKey` is given, the result
202
+ * is scoped to that session and its subagents — letting a parent observe its own
203
+ * subtree. `activeRuns` is kept for callers that only need the executing keys.
204
+ */
205
+ async status(params) {
206
+ const scope = params.sessionKey;
207
+ const inScope = (key) => !scope || key === scope || key.startsWith(`${scope}:subagent:`);
208
+ const sessions = Array.from(this.sessions.entries())
209
+ .filter(([key]) => inScope(key))
210
+ .map(([sessionKey, session]) => ({
211
+ sessionKey,
212
+ state: this.activeRuns.has(sessionKey) ? 'running' : 'idle',
213
+ parentKey: isSubagentSession(sessionKey) ? rootSessionKey(sessionKey) : undefined,
214
+ model: session.model ? `${session.model.provider}:${session.model.id}` : undefined,
215
+ startedAt: this.sessionMeta.get(sessionKey),
216
+ }));
217
+ const activeRuns = Array.from(this.activeRuns).filter(inScope);
218
+ return { sessions, activeRuns };
219
+ }
220
+ /**
221
+ * Get or create AgentSession for sessionKey.
222
+ * Uses SessionManager.continueRecent() to load the latest session file,
223
+ * preserving conversation history across restarts.
224
+ */
225
+ async getOrCreateSession(sessionKey, options) {
226
+ const cached = this.sessions.get(sessionKey);
227
+ if (cached) {
228
+ await this.applyModelOverride(cached, sessionKey, options?.model);
229
+ return cached;
230
+ }
231
+ const paths = getDataPaths();
232
+ const effectiveCwd = options?.cwd ?? paths.dataDir;
233
+ const sessionDir = path.join(paths.sessionsDir, sessionKey.replace(/:/g, path.sep));
234
+ // Use continueRecent to find and load the latest session file (preserves history).
235
+ // Falls back to create() if no existing session file is found.
236
+ let sessionManager;
237
+ try {
238
+ sessionManager = SessionManager.create(effectiveCwd, sessionDir);
239
+ }
240
+ catch (err) {
241
+ if (err instanceof Error && 'code' in err && err.code === 'EEXIST') {
242
+ // File was created by another code path (e.g. concurrent message for same session).
243
+ // Fall back to continueRecent which opens existing files gracefully.
244
+ log.debug(`session ${sessionKey}: create() hit EEXIST, falling back to continueRecent()`);
245
+ sessionManager = SessionManager.continueRecent(effectiveCwd, sessionDir);
293
246
  }
294
- await fs.mkdir(sessionDir, { recursive: true });
295
- await fs.mkdir(this.agentDir, { recursive: true });
296
- const persona = await this.loadPersonaIfChannel(sessionKey);
297
- const customTools = await this.getCustomTools(sessionKey, persona?.meta.allowedTools);
298
- const rawSystemPrompt = await this.getSystemPrompt(sessionKey, persona?.body);
299
- const resourceLoader = await this.createResourceLoader(rawSystemPrompt, effectiveCwd);
300
- log.debug(`session: ${sessionKey} created (${customTools.length} tools, ${rawSystemPrompt?.length ?? 0} chars prompt)`);
301
- // Apply the per-call/channel model override at creation time, when provided and known.
302
- const model = this.resolveModel(options?.model);
303
- const { session } = await this.createPiSession({
304
- cwd: effectiveCwd,
305
- agentDir: this.agentDir,
306
- sessionManager,
307
- settingsManager: this.settings,
308
- authStorage: this.authStorage,
309
- modelRegistry: this.modelRegistry,
310
- customTools,
311
- resourceLoader,
312
- ...(model && { model }),
313
- });
314
- if (process.env.LOG_LEVEL === 'debug') {
315
- const debugDir = path.join(sessionDir, '.debug');
316
- if (!existsSync(debugDir)) {
317
- await fs.mkdir(debugDir, { recursive: true });
318
- }
319
- log.debug(`Storing debug files in session's debug directory: ${debugDir}`);
320
- await fs.writeFile(path.join(debugDir, `systemPrompt.md`), session.systemPrompt ?? '', 'utf-8');
247
+ else {
248
+ throw err;
321
249
  }
322
- this.subscribeToSessionEvents(session, sessionKey);
323
- this.sessions.set(sessionKey, session);
324
- this.sessionMeta.set(sessionKey, Date.now());
325
- return session;
326
250
  }
327
- /**
328
- * Create the underlying Pi SDK session. Isolated as a seam so tests can
329
- * substitute a fake session without the SDK's model/auth machinery.
330
- */
331
- createPiSession(options) {
332
- return createAgentSession(options);
251
+ await fs.mkdir(sessionDir, { recursive: true });
252
+ await fs.mkdir(this.agentDir, { recursive: true });
253
+ const persona = await this.loadPersonaIfChannel(sessionKey);
254
+ const customTools = await this.getCustomTools(sessionKey, persona?.meta.allowedTools);
255
+ const rawSystemPrompt = await this.getSystemPrompt(sessionKey, persona?.body);
256
+ const resourceLoader = await this.createResourceLoader(rawSystemPrompt, effectiveCwd);
257
+ log.debug(`session: ${sessionKey} created (${customTools.length} tools, ${rawSystemPrompt?.length ?? 0} chars prompt)`);
258
+ // Apply the per-call/channel model override at creation time, when provided and known.
259
+ const model = this.resolveModel(options?.model);
260
+ const { session } = await this.createPiSession({
261
+ cwd: effectiveCwd,
262
+ agentDir: this.agentDir,
263
+ sessionManager,
264
+ settingsManager: this.settings,
265
+ authStorage: this.authStorage,
266
+ modelRegistry: this.modelRegistry,
267
+ customTools,
268
+ resourceLoader,
269
+ ...(model && { model }),
270
+ });
271
+ if (process.env.LOG_LEVEL === 'debug') {
272
+ const debugDir = path.join(sessionDir, '.debug');
273
+ if (!existsSync(debugDir)) {
274
+ await fs.mkdir(debugDir, { recursive: true });
275
+ }
276
+ log.debug(`Storing debug files in session's debug directory: ${debugDir}`);
277
+ await fs.writeFile(path.join(debugDir, `systemPrompt.md`), session.systemPrompt ?? '', 'utf-8');
333
278
  }
334
- /**
335
- * Switch a cached session's model when an override differs from the current one.
336
- * Unknown/missing overrides are a no-op — the session keeps its existing model.
337
- */
338
- async applyModelOverride(session, sessionKey, modelSpec) {
339
- const resolved = this.resolveModel(modelSpec);
340
- if (!resolved)
341
- return;
342
- if (session.model?.provider === resolved.provider && session.model?.id === resolved.id)
279
+ this.subscribeToSessionEvents(session, sessionKey);
280
+ this.sessions.set(sessionKey, session);
281
+ this.sessionMeta.set(sessionKey, Date.now());
282
+ return session;
283
+ }
284
+ /**
285
+ * Create the underlying Pi SDK session. Isolated as a seam so tests can
286
+ * substitute a fake session without the SDK's model/auth machinery.
287
+ */
288
+ createPiSession(options) {
289
+ return createAgentSession(options);
290
+ }
291
+ /**
292
+ * Switch a cached session's model when an override differs from the current one.
293
+ * Unknown/missing overrides are a no-op — the session keeps its existing model.
294
+ */
295
+ async applyModelOverride(session, sessionKey, modelSpec) {
296
+ const resolved = this.resolveModel(modelSpec);
297
+ if (!resolved)
298
+ return;
299
+ if (session.model?.provider === resolved.provider && session.model?.id === resolved.id)
300
+ return;
301
+ await session.setModel(resolved);
302
+ log.info(`session ${sessionKey}: model → ${resolved.provider}:${resolved.id}`);
303
+ }
304
+ /** Resolve a `provider:modelId` override to a Pi SDK model, or undefined if unknown. */
305
+ resolveModel(modelSpec) {
306
+ if (!modelSpec)
307
+ return undefined;
308
+ const [provider, modelId] = modelSpec.split(':');
309
+ return this.modelRegistry.find(provider, modelId);
310
+ }
311
+ /**
312
+ * Subscribe to PiAgent sessionsubscription - emit to bus for streaming + debug logging.
313
+ */
314
+ subscribeToSessionEvents(session, sessionKey) {
315
+ session.subscribe((event) => {
316
+ const eventType = event.type;
317
+ // log.debug(` --- :: Agent Lifecycle = ${eventType} --- :: ${sessionKey} --- :: `);
318
+ // Skip session-specific events (auto_retry_start, auto_retry_end) - not emitted as bus events
319
+ if (eventType === 'auto_retry_start' || eventType === 'auto_retry_end') {
343
320
  return;
344
- await session.setModel(resolved);
345
- log.info(`session ${sessionKey}: model ${resolved.provider}:${resolved.id}`);
346
- }
347
- /** Resolve a `provider:modelId` override to a Pi SDK model, or undefined if unknown. */
348
- resolveModel(modelSpec) {
349
- if (!modelSpec)
350
- return undefined;
351
- const [provider, modelId] = modelSpec.split(':');
352
- return this.modelRegistry.find(provider, modelId);
353
- }
354
- /**
355
- * Subscribe to PiAgent sessionsubscription - emit to bus for streaming + debug logging.
356
- */
357
- subscribeToSessionEvents(session, sessionKey) {
358
- session.subscribe((event) => {
359
- const eventType = event.type;
360
- // log.debug(` --- :: Agent Lifecycle = ${eventType} --- :: ${sessionKey} --- :: `);
361
- // Skip session-specific events (auto_retry_start, auto_retry_end) - not emitted as bus events
362
- if (eventType === 'auto_retry_start' || eventType === 'auto_retry_end') {
363
- return;
364
- }
365
- // Map PiAgent events to our bus events
366
- // Bridge PiAgent's untyped event structure to our typed EventMap
367
- switch (eventType) {
368
- case 'tool_execution_start': {
369
- const e = event;
370
- if (e.toolName) {
371
- this.bus.emit('agent.onTool', {
372
- sessionKey,
373
- toolName: e.toolName,
374
- phase: 'start',
375
- args: (e.args ?? {}),
376
- });
377
- }
378
- break;
379
- }
380
- case 'tool_execution_end': {
381
- const e = event;
382
- if (e.toolName) {
383
- this.bus.emit('agent.onTool', {
384
- sessionKey,
385
- toolName: e.toolName,
386
- phase: 'end',
387
- result: (e.result ?? {}),
388
- });
389
- }
390
- break;
321
+ }
322
+ // Map PiAgent events to our bus events
323
+ // Bridge PiAgent's untyped event structure to our typed EventMap
324
+ switch (eventType) {
325
+ case 'tool_execution_start': {
326
+ const e = event;
327
+ if (e.toolName) {
328
+ this.bus.emit('agent.onTool', {
329
+ sessionKey,
330
+ toolName: e.toolName,
331
+ phase: 'start',
332
+ args: (e.args ?? {}),
333
+ });
391
334
  }
392
- case 'message_update': {
393
- const e = event;
394
- const delta = e.delta || e.text || '';
395
- if (delta) {
396
- this.bus.emit('agent.onDelta', { sessionKey, chunk: delta });
397
- }
398
- break;
335
+ break;
336
+ }
337
+ case 'tool_execution_end': {
338
+ const e = event;
339
+ if (e.toolName) {
340
+ this.bus.emit('agent.onTool', {
341
+ sessionKey,
342
+ toolName: e.toolName,
343
+ phase: 'end',
344
+ result: (e.result ?? {}),
345
+ });
399
346
  }
400
- case 'turn_end': {
401
- break;
347
+ break;
348
+ }
349
+ case 'message_update': {
350
+ const e = event;
351
+ const delta = e.delta || e.text || '';
352
+ if (delta) {
353
+ this.bus.emit('agent.onDelta', { sessionKey, chunk: delta });
402
354
  }
403
- case 'agent_end': {
404
- const { content, error } = this.extractFinalAssistant(session);
405
- if (error) {
406
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
407
- const model = session.model ?? 'unknown';
408
- log.error(`agent_end with error for ${sessionKey} [model=${model}]: ${error}`);
409
- this.bus.emit('agent.onCompleted', { sessionKey, success: false, error });
410
- }
411
- else {
412
- log.debug(` emitting agent.onCompleted with ${content.length} chars`);
413
- this.bus.emit('agent.onCompleted', { sessionKey, success: true, response: content });
414
- }
415
- break;
355
+ break;
356
+ }
357
+ case 'turn_end': {
358
+ break;
359
+ }
360
+ case 'agent_end': {
361
+ const { content, error } = this.extractFinalAssistant(session);
362
+ if (error) {
363
+ const model = session.model?.id ?? 'unknown';
364
+ log.error(`agent_end with error for ${sessionKey} [model=${model}]: ${error}`);
365
+ this.bus.emit('agent.onCompleted', { sessionKey, success: false, error });
416
366
  }
417
- default: {
418
- break;
367
+ else {
368
+ log.debug(` emitting agent.onCompleted with ${content.length} chars`);
369
+ this.bus.emit('agent.onCompleted', { sessionKey, success: true, response: content });
419
370
  }
371
+ break;
420
372
  }
421
- });
422
- }
423
- /**
424
- * Create ResourceLoader. PiAgent's DefaultResourceLoader handles skills, themes, and
425
- * prompt templates. We override systemPrompt with our Vargos bootstrap files.
426
- */
427
- async createResourceLoader(systemPromptOverride, cwd) {
428
- const paths = getDataPaths();
429
- const effectiveCwd = cwd ?? paths.workspaceDir;
430
- // Only workspace + cwd here — Pi SDK already auto-loads <agentDir>/skills and <cwd>/.pi/skills.
431
- const skillPaths = resolveSkillPaths(paths.workspaceDir, ...(cwd ? [cwd] : []));
432
- const resourceLoader = new DefaultResourceLoader({
433
- cwd: effectiveCwd,
434
- agentDir: this.agentDir,
435
- settingsManager: this.settings,
436
- extensionFactories: [],
437
- additionalSkillPaths: skillPaths,
438
- noSkills: false,
439
- ...(systemPromptOverride && { systemPrompt: systemPromptOverride }),
440
- });
441
- await resourceLoader.reload();
442
- const { skills } = resourceLoader.getSkills();
443
- log.debug(`Resource loader loaded with ${skills.length} skills.`);
444
- return resourceLoader;
445
- }
446
- /**
447
- * Load persona for the given sessionKey.
448
- * - Subagent sessions: load `agents/subagent.md` (preamble + allowedTools whitelist).
449
- * - Channel sessions: load `agents/<channelId>.md` (persona + tool filter).
450
- * - Cron / CLI / other types: return null (no persona override applied).
451
- */
452
- async loadPersonaIfChannel(sessionKey) {
453
- if (isSubagentSession(sessionKey))
454
- return loadSubagentPersona();
455
- const { type } = parseSessionKey(sessionKey);
456
- const isChannel = this.config.channels.some(c => c.id === type);
457
- if (!isChannel)
458
- return null;
459
- return loadChannelPersona(type);
460
- }
461
- /**
462
- * Build system prompt.
463
- * - Subagent sessions: return the persona body from `agents/subagent.md`.
464
- * No bootstrap files (AGENTS.md, SOUL.md, TOOLS.md) are loaded — the parent's
465
- * task description is the subagent's sole context.
466
- * - Parent/other sessions: merge AGENTS.md + SOUL.md + TOOLS.md from workspace/cwd,
467
- * then append channel persona body if provided.
468
- */
469
- async getSystemPrompt(sessionKey, personaBody) {
470
- if (isSubagentSession(sessionKey)) {
471
- return personaBody?.trim() || undefined;
472
- }
473
- const bootstrapFiles = ['AGENTS.md', 'SOUL.md', 'TOOLS.md'];
474
- const maxCharsPerFile = 6000;
475
- const dirs = [getDataPaths().workspaceDir];
476
- const filePathsToLoad = [];
477
- for (const dir of dirs) {
478
- for (const filename of bootstrapFiles) {
479
- filePathsToLoad.push({ dir, filename, path: path.join(dir, filename) });
373
+ default: {
374
+ break;
480
375
  }
481
376
  }
482
- const fileContents = await Promise.all(filePathsToLoad.map(async (item) => {
483
- try {
484
- const content = await fs.readFile(item.path, 'utf-8');
485
- const truncated = truncate(content, maxCharsPerFile);
486
- log.debug(`Loaded ${item.dir}/${item.filename}: ${truncated.length} chars`);
487
- return {
488
- label: `<!-- ${item.dir}/${item.filename} -->`,
489
- content: truncated.trim(),
490
- };
491
- }
492
- catch {
493
- log.debug(`${item.dir}/${item.filename}: not found`);
494
- return null;
495
- }
496
- }));
497
- const sections = [];
498
- for (const result of fileContents) {
499
- if (result)
500
- sections.push(result.label, result.content, '');
377
+ });
378
+ }
379
+ /**
380
+ * Create ResourceLoader. PiAgent's DefaultResourceLoader handles skills, themes, and
381
+ * prompt templates. We override systemPrompt with our Vargos bootstrap files.
382
+ */
383
+ async createResourceLoader(systemPromptOverride, cwd) {
384
+ const paths = getDataPaths();
385
+ const effectiveCwd = cwd ?? paths.workspaceDir;
386
+ // Only workspace + cwd here — Pi SDK already auto-loads <agentDir>/skills and <cwd>/.pi/skills.
387
+ const skillPaths = resolveSkillPaths(paths.workspaceDir, ...(cwd ? [cwd] : []));
388
+ const resourceLoader = new DefaultResourceLoader({
389
+ cwd: effectiveCwd,
390
+ agentDir: this.agentDir,
391
+ settingsManager: this.settings,
392
+ extensionFactories: [],
393
+ additionalSkillPaths: skillPaths,
394
+ noSkills: false,
395
+ ...(systemPromptOverride && { systemPrompt: systemPromptOverride }),
396
+ });
397
+ await resourceLoader.reload();
398
+ const { skills } = resourceLoader.getSkills();
399
+ log.debug(`Resource loader loaded with ${skills.length} skills.`);
400
+ return resourceLoader;
401
+ }
402
+ /**
403
+ * Load persona for the given sessionKey.
404
+ * - Subagent sessions: load `agents/subagent.md` (preamble + allowedTools whitelist).
405
+ * - Channel sessions: load `agents/<channelId>.md` (persona + tool filter).
406
+ * - Cron / CLI / other types: return null (no persona override applied).
407
+ */
408
+ async loadPersonaIfChannel(sessionKey) {
409
+ if (isSubagentSession(sessionKey))
410
+ return loadSubagentPersona();
411
+ const { type } = parseSessionKey(sessionKey);
412
+ const isChannel = this.config.channels.some(c => c.id === type);
413
+ if (!isChannel)
414
+ return null;
415
+ return loadChannelPersona(type);
416
+ }
417
+ /**
418
+ * Build system prompt.
419
+ * - Subagent sessions: return the persona body from `agents/subagent.md`.
420
+ * No bootstrap files (AGENTS.md, SOUL.md, TOOLS.md) are loaded — the parent's
421
+ * task description is the subagent's sole context.
422
+ * - Parent/other sessions: merge AGENTS.md + SOUL.md + TOOLS.md from workspace/cwd,
423
+ * then append channel persona body if provided.
424
+ */
425
+ async getSystemPrompt(sessionKey, personaBody) {
426
+ if (isSubagentSession(sessionKey)) {
427
+ return personaBody?.trim() || undefined;
428
+ }
429
+ const bootstrapFiles = ['AGENTS.md', 'SOUL.md', 'TOOLS.md'];
430
+ const maxCharsPerFile = 6000;
431
+ const dirs = [getDataPaths().workspaceDir];
432
+ const filePathsToLoad = [];
433
+ for (const dir of dirs) {
434
+ for (const filename of bootstrapFiles) {
435
+ filePathsToLoad.push({ dir, filename, path: path.join(dir, filename) });
501
436
  }
502
- // Also log bootstrap files loaded
503
- log.debug(`session: ${sessionKey} bootstrap ${sections.filter(s => s.startsWith('<!--')).length} files, ${sections.join('\n').length} chars`);
504
- if (personaBody) {
505
- sections.push('<!-- channel persona -->', '<channel-persona>', personaBody.trim(), '</channel-persona>');
437
+ }
438
+ const fileContents = await Promise.all(filePathsToLoad.map(async (item) => {
439
+ try {
440
+ const content = await fs.readFile(item.path, 'utf-8');
441
+ const truncated = truncate(content, maxCharsPerFile);
442
+ log.debug(`Loaded ${item.dir}/${item.filename}: ${truncated.length} chars`);
443
+ return {
444
+ label: `<!-- ${item.dir}/${item.filename} -->`,
445
+ content: truncated.trim(),
446
+ };
506
447
  }
507
- if (sections.length === 0) {
508
- log.debug('No bootstrap files found, using PiAgent default');
509
- return undefined;
448
+ catch {
449
+ log.debug(`${item.dir}/${item.filename}: not found`);
450
+ return null;
510
451
  }
511
- const prompt = sections.join('\n');
512
- return interpolatePrompt(prompt, { SESSION_KEY: sessionKey });
452
+ }));
453
+ const sections = [];
454
+ for (const result of fileContents) {
455
+ if (result)
456
+ sections.push(result.label, result.content, '');
513
457
  }
514
- /**
515
- * Load custom tools from bus callable events. When `allowedPatterns` is provided
516
- * (from a channel persona), filter the tool list down to names matching at least
517
- * one glob pattern. Empty/undefined patterns = all tools allowed.
518
- */
519
- async getCustomTools(sessionKey, allowedPatterns) {
520
- const tools = await createCustomTools(sessionKey, this.bus);
521
- if (!allowedPatterns?.length)
522
- return tools;
523
- // Match on `label` (original event name with dots, e.g. "memory.search")
524
- // rather than `name` (sanitized with dashes, e.g. "memory-search"),
525
- // so that frontmatter patterns like "memory.*" work as expected.
526
- return tools.filter(t => allowedPatterns.some(p => matchesGlob(p, t.label)));
458
+ // Also log bootstrap files loaded
459
+ log.debug(`session: ${sessionKey} bootstrap ${sections.filter(s => s.startsWith('<!--')).length} files, ${sections.join('\n').length} chars`);
460
+ if (personaBody) {
461
+ sections.push('<!-- channel persona -->', '<channel-persona>', personaBody.trim(), '</channel-persona>');
527
462
  }
528
- /**
529
- * Validate model override if provided.
530
- */
531
- isValidModel(modelSpec) {
532
- return !!this.resolveModel(modelSpec);
463
+ if (sections.length === 0) {
464
+ log.debug('No bootstrap files found, using PiAgent default');
465
+ return undefined;
533
466
  }
534
- // ── Private Helpers ────────────────────────────────────────────────────────
535
- /**
536
- * Extract the final assistant message: text content + error (when stopReason === 'error').
537
- * Pi SDK records inference failures (e.g. missing API key) as assistant messages with
538
- * empty content and `errorMessage` populated, instead of throwing without inspecting
539
- * `stopReason`/`errorMessage` here, those would surface as silent empty completions.
540
- */
541
- extractFinalAssistant(session) {
542
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
543
- const messages = session.state?.messages || [];
544
- for (let i = messages.length - 1; i >= 0; i--) {
545
- const msg = messages[i];
546
- if (msg?.role !== 'assistant')
547
- continue;
548
- let error;
549
- if (msg.stopReason === 'error') {
550
- error = msg.errorMessage ?? 'unknown inference error';
551
- // Log full message details for debugging connection/auth issues
552
- log.debug('agent error details:', {
553
- stopReason: msg.stopReason,
554
- errorMessage: msg.errorMessage,
555
- model: session.model,
556
- messageCount: messages.length,
557
- });
558
- }
559
- let content = '';
560
- if (typeof msg.content === 'string') {
561
- content = msg.content;
562
- }
563
- else if (Array.isArray(msg.content)) {
564
- content = msg.content
565
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
566
- .filter((block) => block.type === 'text')
567
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
568
- .map((block) => block.text || '')
569
- .filter(Boolean)
570
- .join('\n');
571
- }
572
- return error ? { content, error } : { content };
467
+ const prompt = sections.join('\n');
468
+ return interpolatePrompt(prompt, { SESSION_KEY: sessionKey });
469
+ }
470
+ /**
471
+ * Load custom tools from bus callable events. When `allowedPatterns` is provided
472
+ * (from a channel persona), filter the tool list down to names matching at least
473
+ * one glob pattern. Empty/undefined patterns = all tools allowed.
474
+ */
475
+ async getCustomTools(sessionKey, allowedPatterns) {
476
+ const tools = createCustomTools(sessionKey, this.bus);
477
+ if (!allowedPatterns?.length)
478
+ return tools;
479
+ // Match on `label` (original event name with dots, e.g. "memory.search")
480
+ // rather than `name` (sanitized with dashes, e.g. "memory-search"),
481
+ // so that frontmatter patterns like "memory.*" work as expected.
482
+ return tools.filter(t => allowedPatterns.some(p => matchesGlob(p, t.label)));
483
+ }
484
+ /**
485
+ * Validate model override if provided.
486
+ */
487
+ isValidModel(modelSpec) {
488
+ return !!this.resolveModel(modelSpec);
489
+ }
490
+ // ── Private Helpers ────────────────────────────────────────────────────────
491
+ /**
492
+ * Extract the final assistant message: text content + error (when stopReason === 'error').
493
+ * Pi SDK records inference failures (e.g. missing API key) as assistant messages with
494
+ * empty content and `errorMessage` populated, instead of throwing — without inspecting
495
+ * `stopReason`/`errorMessage` here, those would surface as silent empty completions.
496
+ */
497
+ extractFinalAssistant(session) {
498
+ const messages = session.state.messages;
499
+ for (let i = messages.length - 1; i >= 0; i--) {
500
+ const msg = messages[i];
501
+ if (msg?.role !== 'assistant')
502
+ continue;
503
+ let error;
504
+ if (msg.stopReason === 'error') {
505
+ error = msg.errorMessage ?? 'unknown inference error';
506
+ // Log full message details for debugging connection/auth issues
507
+ log.debug('agent error details:', {
508
+ stopReason: msg.stopReason,
509
+ errorMessage: msg.errorMessage,
510
+ model: session.model?.id,
511
+ messageCount: messages.length,
512
+ });
573
513
  }
574
- return { content: '' };
575
- }
576
- stop() {
577
- this.sessions.forEach((_session) => {
578
- _session.dispose();
579
- });
580
- this.sessions.clear();
581
- this.sessionMeta.clear();
514
+ let content = '';
515
+ if (typeof msg.content === 'string') {
516
+ content = msg.content;
517
+ }
518
+ else if (Array.isArray(msg.content)) {
519
+ content = msg.content
520
+ .filter(block => block.type === 'text')
521
+ .map(block => block.text || '')
522
+ .filter(Boolean)
523
+ .join('\n');
524
+ }
525
+ return error ? { content, error } : { content };
582
526
  }
583
- };
584
- })();
585
- export { AgentService };
586
- // ── Boot ─────────────────────────────────────────────────────────────────────
587
- export async function boot(bus) {
588
- const config = await bus.call('config.get', {});
589
- const runtime = new AgentService({ bus, config });
590
- bus.bootstrap(runtime);
591
- await runtime.start(); // Persist retry settings
592
- return { stop: () => runtime.stop() };
527
+ return { content: '' };
528
+ }
529
+ }
530
+ export function createService() {
531
+ return new AgentService();
593
532
  }
594
533
  //# sourceMappingURL=index.js.map