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