@opengsd/gsd-pi 1.0.2-dev.50223bc → 1.0.2-dev.5961fbf

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 (251) hide show
  1. package/dist/resource-loader.d.ts +5 -0
  2. package/dist/resource-loader.js +24 -8
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/extensions/gsd/auto/loop.js +19 -0
  5. package/dist/resources/extensions/gsd/auto/phases.js +1 -1
  6. package/dist/resources/extensions/gsd/auto-worktree.js +2 -54
  7. package/dist/resources/extensions/gsd/worktree-post-create-hook.js +117 -0
  8. package/dist/web/standalone/.next/BUILD_ID +1 -1
  9. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  10. package/dist/web/standalone/.next/build-manifest.json +2 -2
  11. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  12. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  29. package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
  30. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  31. package/dist/web/standalone/.next/server/app/index.html +1 -1
  32. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  39. package/dist/web/standalone/.next/server/chunks/1834.js +1 -1
  40. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  41. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  42. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  43. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  44. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  45. package/dist/web/standalone/package.json +0 -1
  46. package/dist/worktree-cli.d.ts +0 -2
  47. package/dist/worktree-cli.js +21 -9
  48. package/package.json +9 -4
  49. package/packages/cloud-mcp-gateway/bin/gsd-cloud-mcp-gateway.js +14 -0
  50. package/packages/cloud-mcp-gateway/package.json +5 -4
  51. package/packages/contracts/package.json +2 -2
  52. package/packages/daemon/bin/gsd-daemon.js +14 -0
  53. package/packages/daemon/bin/gsd-mcp-runtime.js +14 -0
  54. package/packages/daemon/bin/gsd-mcp.js +14 -0
  55. package/packages/daemon/dist/channel-manager.d.ts +53 -0
  56. package/packages/daemon/dist/channel-manager.d.ts.map +1 -0
  57. package/packages/daemon/dist/channel-manager.js +167 -0
  58. package/packages/daemon/dist/channel-manager.js.map +1 -0
  59. package/packages/daemon/dist/cli.d.ts +3 -0
  60. package/packages/daemon/dist/cli.d.ts.map +1 -0
  61. package/packages/daemon/dist/cli.js +94 -0
  62. package/packages/daemon/dist/cli.js.map +1 -0
  63. package/packages/daemon/dist/cloud-cli.d.ts +7 -0
  64. package/packages/daemon/dist/cloud-cli.d.ts.map +1 -0
  65. package/packages/daemon/dist/cloud-cli.js +96 -0
  66. package/packages/daemon/dist/cloud-cli.js.map +1 -0
  67. package/packages/daemon/dist/cloud-config.d.ts +18 -0
  68. package/packages/daemon/dist/cloud-config.d.ts.map +1 -0
  69. package/packages/daemon/dist/cloud-config.js +209 -0
  70. package/packages/daemon/dist/cloud-config.js.map +1 -0
  71. package/packages/daemon/dist/cloud-config.test.d.ts +2 -0
  72. package/packages/daemon/dist/cloud-config.test.d.ts.map +1 -0
  73. package/packages/daemon/dist/cloud-config.test.js +132 -0
  74. package/packages/daemon/dist/cloud-config.test.js.map +1 -0
  75. package/packages/daemon/dist/cloud-runtime.d.ts +26 -0
  76. package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -0
  77. package/packages/daemon/dist/cloud-runtime.js +180 -0
  78. package/packages/daemon/dist/cloud-runtime.js.map +1 -0
  79. package/packages/daemon/dist/cloud-runtime.test.d.ts +2 -0
  80. package/packages/daemon/dist/cloud-runtime.test.d.ts.map +1 -0
  81. package/packages/daemon/dist/cloud-runtime.test.js +28 -0
  82. package/packages/daemon/dist/cloud-runtime.test.js.map +1 -0
  83. package/packages/daemon/dist/cloud-token.d.ts +3 -0
  84. package/packages/daemon/dist/cloud-token.d.ts.map +1 -0
  85. package/packages/daemon/dist/cloud-token.js +37 -0
  86. package/packages/daemon/dist/cloud-token.js.map +1 -0
  87. package/packages/daemon/dist/commands.d.ts +25 -0
  88. package/packages/daemon/dist/commands.d.ts.map +1 -0
  89. package/packages/daemon/dist/commands.js +81 -0
  90. package/packages/daemon/dist/commands.js.map +1 -0
  91. package/packages/daemon/dist/config.d.ts +17 -0
  92. package/packages/daemon/dist/config.d.ts.map +1 -0
  93. package/packages/daemon/dist/config.js +146 -0
  94. package/packages/daemon/dist/config.js.map +1 -0
  95. package/packages/daemon/dist/daemon.d.ts +38 -0
  96. package/packages/daemon/dist/daemon.d.ts.map +1 -0
  97. package/packages/daemon/dist/daemon.js +194 -0
  98. package/packages/daemon/dist/daemon.js.map +1 -0
  99. package/packages/daemon/dist/daemon.test.d.ts +2 -0
  100. package/packages/daemon/dist/daemon.test.d.ts.map +1 -0
  101. package/packages/daemon/dist/daemon.test.js +692 -0
  102. package/packages/daemon/dist/daemon.test.js.map +1 -0
  103. package/packages/daemon/dist/discord-bot.d.ts +70 -0
  104. package/packages/daemon/dist/discord-bot.d.ts.map +1 -0
  105. package/packages/daemon/dist/discord-bot.js +433 -0
  106. package/packages/daemon/dist/discord-bot.js.map +1 -0
  107. package/packages/daemon/dist/discord-bot.test.d.ts +2 -0
  108. package/packages/daemon/dist/discord-bot.test.d.ts.map +1 -0
  109. package/packages/daemon/dist/discord-bot.test.js +667 -0
  110. package/packages/daemon/dist/discord-bot.test.js.map +1 -0
  111. package/packages/daemon/dist/event-bridge.d.ts +72 -0
  112. package/packages/daemon/dist/event-bridge.d.ts.map +1 -0
  113. package/packages/daemon/dist/event-bridge.js +366 -0
  114. package/packages/daemon/dist/event-bridge.js.map +1 -0
  115. package/packages/daemon/dist/event-bridge.test.d.ts +9 -0
  116. package/packages/daemon/dist/event-bridge.test.d.ts.map +1 -0
  117. package/packages/daemon/dist/event-bridge.test.js +528 -0
  118. package/packages/daemon/dist/event-bridge.test.js.map +1 -0
  119. package/packages/daemon/dist/event-formatter.d.ts +34 -0
  120. package/packages/daemon/dist/event-formatter.d.ts.map +1 -0
  121. package/packages/daemon/dist/event-formatter.js +355 -0
  122. package/packages/daemon/dist/event-formatter.js.map +1 -0
  123. package/packages/daemon/dist/event-formatter.test.d.ts +2 -0
  124. package/packages/daemon/dist/event-formatter.test.d.ts.map +1 -0
  125. package/packages/daemon/dist/event-formatter.test.js +333 -0
  126. package/packages/daemon/dist/event-formatter.test.js.map +1 -0
  127. package/packages/daemon/dist/index.d.ts +25 -0
  128. package/packages/daemon/dist/index.d.ts.map +1 -0
  129. package/packages/daemon/dist/index.js +17 -0
  130. package/packages/daemon/dist/index.js.map +1 -0
  131. package/packages/daemon/dist/launchd.d.ts +49 -0
  132. package/packages/daemon/dist/launchd.d.ts.map +1 -0
  133. package/packages/daemon/dist/launchd.js +188 -0
  134. package/packages/daemon/dist/launchd.js.map +1 -0
  135. package/packages/daemon/dist/launchd.test.d.ts +2 -0
  136. package/packages/daemon/dist/launchd.test.d.ts.map +1 -0
  137. package/packages/daemon/dist/launchd.test.js +296 -0
  138. package/packages/daemon/dist/launchd.test.js.map +1 -0
  139. package/packages/daemon/dist/local-tool-executor.d.ts +22 -0
  140. package/packages/daemon/dist/local-tool-executor.d.ts.map +1 -0
  141. package/packages/daemon/dist/local-tool-executor.js +307 -0
  142. package/packages/daemon/dist/local-tool-executor.js.map +1 -0
  143. package/packages/daemon/dist/local-tool-executor.test.d.ts +2 -0
  144. package/packages/daemon/dist/local-tool-executor.test.d.ts.map +1 -0
  145. package/packages/daemon/dist/local-tool-executor.test.js +111 -0
  146. package/packages/daemon/dist/local-tool-executor.test.js.map +1 -0
  147. package/packages/daemon/dist/logger.d.ts +25 -0
  148. package/packages/daemon/dist/logger.d.ts.map +1 -0
  149. package/packages/daemon/dist/logger.js +72 -0
  150. package/packages/daemon/dist/logger.js.map +1 -0
  151. package/packages/daemon/dist/mcp-cli.d.ts +3 -0
  152. package/packages/daemon/dist/mcp-cli.d.ts.map +1 -0
  153. package/packages/daemon/dist/mcp-cli.js +8 -0
  154. package/packages/daemon/dist/mcp-cli.js.map +1 -0
  155. package/packages/daemon/dist/mcp-cli.test.d.ts +2 -0
  156. package/packages/daemon/dist/mcp-cli.test.d.ts.map +1 -0
  157. package/packages/daemon/dist/mcp-cli.test.js +13 -0
  158. package/packages/daemon/dist/mcp-cli.test.js.map +1 -0
  159. package/packages/daemon/dist/mcp-runtime-cli.d.ts +3 -0
  160. package/packages/daemon/dist/mcp-runtime-cli.d.ts.map +1 -0
  161. package/packages/daemon/dist/mcp-runtime-cli.js +8 -0
  162. package/packages/daemon/dist/mcp-runtime-cli.js.map +1 -0
  163. package/packages/daemon/dist/message-batcher.d.ts +78 -0
  164. package/packages/daemon/dist/message-batcher.d.ts.map +1 -0
  165. package/packages/daemon/dist/message-batcher.js +173 -0
  166. package/packages/daemon/dist/message-batcher.js.map +1 -0
  167. package/packages/daemon/dist/message-batcher.test.d.ts +2 -0
  168. package/packages/daemon/dist/message-batcher.test.d.ts.map +1 -0
  169. package/packages/daemon/dist/message-batcher.test.js +242 -0
  170. package/packages/daemon/dist/message-batcher.test.js.map +1 -0
  171. package/packages/daemon/dist/orchestrator.d.ts +98 -0
  172. package/packages/daemon/dist/orchestrator.d.ts.map +1 -0
  173. package/packages/daemon/dist/orchestrator.js +359 -0
  174. package/packages/daemon/dist/orchestrator.js.map +1 -0
  175. package/packages/daemon/dist/orchestrator.test.d.ts +8 -0
  176. package/packages/daemon/dist/orchestrator.test.d.ts.map +1 -0
  177. package/packages/daemon/dist/orchestrator.test.js +425 -0
  178. package/packages/daemon/dist/orchestrator.test.js.map +1 -0
  179. package/packages/daemon/dist/project-scanner.d.ts +18 -0
  180. package/packages/daemon/dist/project-scanner.d.ts.map +1 -0
  181. package/packages/daemon/dist/project-scanner.js +90 -0
  182. package/packages/daemon/dist/project-scanner.js.map +1 -0
  183. package/packages/daemon/dist/project-scanner.test.d.ts +5 -0
  184. package/packages/daemon/dist/project-scanner.test.d.ts.map +1 -0
  185. package/packages/daemon/dist/project-scanner.test.js +183 -0
  186. package/packages/daemon/dist/project-scanner.test.js.map +1 -0
  187. package/packages/daemon/dist/session-manager.d.ts +70 -0
  188. package/packages/daemon/dist/session-manager.d.ts.map +1 -0
  189. package/packages/daemon/dist/session-manager.js +358 -0
  190. package/packages/daemon/dist/session-manager.js.map +1 -0
  191. package/packages/daemon/dist/session-manager.test.d.ts +9 -0
  192. package/packages/daemon/dist/session-manager.test.d.ts.map +1 -0
  193. package/packages/daemon/dist/session-manager.test.js +616 -0
  194. package/packages/daemon/dist/session-manager.test.js.map +1 -0
  195. package/packages/daemon/dist/types.d.ts +133 -0
  196. package/packages/daemon/dist/types.d.ts.map +1 -0
  197. package/packages/daemon/dist/types.js +8 -0
  198. package/packages/daemon/dist/types.js.map +1 -0
  199. package/packages/daemon/dist/verbosity.d.ts +27 -0
  200. package/packages/daemon/dist/verbosity.d.ts.map +1 -0
  201. package/packages/daemon/dist/verbosity.js +86 -0
  202. package/packages/daemon/dist/verbosity.js.map +1 -0
  203. package/packages/daemon/dist/verbosity.test.d.ts +2 -0
  204. package/packages/daemon/dist/verbosity.test.d.ts.map +1 -0
  205. package/packages/daemon/dist/verbosity.test.js +136 -0
  206. package/packages/daemon/dist/verbosity.test.js.map +1 -0
  207. package/packages/daemon/package.json +9 -8
  208. package/packages/gsd-agent-core/package.json +6 -6
  209. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  210. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +3 -1
  211. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  212. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  213. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
  214. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  215. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -0
  216. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
  217. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -0
  218. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
  219. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  220. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +2 -1
  221. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  222. package/packages/gsd-agent-modes/package.json +8 -8
  223. package/packages/mcp-server/bin/gsd-mcp-server.js +14 -0
  224. package/packages/mcp-server/package.json +6 -5
  225. package/packages/native/package.json +3 -3
  226. package/packages/pi-agent-core/package.json +4 -4
  227. package/packages/pi-ai/bin/pi-ai.js +14 -0
  228. package/packages/pi-ai/dist/models.generated.d.ts +0 -17
  229. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  230. package/packages/pi-ai/dist/models.generated.js +18 -35
  231. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  232. package/packages/pi-ai/package.json +5 -4
  233. package/packages/pi-coding-agent/dist/core/tools/read.d.ts +2 -2
  234. package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
  235. package/packages/pi-coding-agent/dist/core/tools/read.js +5 -3
  236. package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
  237. package/packages/pi-coding-agent/package.json +9 -9
  238. package/packages/pi-tui/package.json +2 -2
  239. package/packages/rpc-client/package.json +3 -3
  240. package/pkg/package.json +1 -1
  241. package/scripts/ensure-workspace-builds.cjs +4 -4
  242. package/scripts/install/deps.js +10 -0
  243. package/src/resources/extensions/gsd/auto/loop.ts +22 -0
  244. package/src/resources/extensions/gsd/auto/phases.ts +1 -1
  245. package/src/resources/extensions/gsd/auto-worktree.ts +2 -56
  246. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +64 -0
  247. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +141 -1
  248. package/src/resources/extensions/gsd/worktree-post-create-hook.ts +127 -0
  249. package/dist/tsconfig.extensions.tsbuildinfo +0 -1
  250. /package/dist/web/standalone/.next/static/{JP7xjsa5zSaO76XhE-mFJ → spUYLkQXoHJyxYOMH9VQy}/_buildManifest.js +0 -0
  251. /package/dist/web/standalone/.next/static/{JP7xjsa5zSaO76XhE-mFJ → spUYLkQXoHJyxYOMH9VQy}/_ssgManifest.js +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-batcher.test.js","sourceRoot":"","sources":["../src/message-batcher.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, beforeEach, afterEach, mock } from 'node:test';\nimport assert from 'node:assert/strict';\nimport { MessageBatcher } from './message-batcher.js';\nimport type { SendPayload, BatcherLogger } from './message-batcher.js';\nimport type { FormattedEvent } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Create a minimal FormattedEvent for testing. */\nfunction fakeEvent(content: string, hasEmbed = false): FormattedEvent {\n const fe: FormattedEvent = { content };\n if (hasEmbed) {\n // Minimal mock embed — just needs to be truthy and pass through\n fe.embed = { data: { title: content } } as any;\n }\n return fe;\n}\n\n/** Create a tracking send function. */\nfunction createSend() {\n const calls: SendPayload[] = [];\n const fn = mock.fn(async (payload: SendPayload) => {\n calls.push(payload);\n });\n return { fn, calls };\n}\n\n/** Create a logger that captures error/warn calls. */\nfunction createLogger() {\n const errors: string[] = [];\n const warns: string[] = [];\n const debugs: string[] = [];\n const logger: BatcherLogger = {\n error(msg: string) { errors.push(msg); },\n warn(msg: string) { warns.push(msg); },\n debug(msg: string) { debugs.push(msg); },\n };\n return { logger, errors, warns, debugs };\n}\n\n// ---------------------------------------------------------------------------\n// Tests\n// ---------------------------------------------------------------------------\n\ndescribe('MessageBatcher', () => {\n describe('enqueue + capacity flush', () => {\n it('flushes when buffer reaches maxBatchSize', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 3, flushIntervalMs: 60_000 });\n\n batcher.enqueue(fakeEvent('a'));\n batcher.enqueue(fakeEvent('b'));\n assert.equal(calls.length, 0, 'should not flush yet');\n\n batcher.enqueue(fakeEvent('c')); // hits capacity\n // flush is async — give it a tick\n await new Promise((r) => setTimeout(r, 10));\n\n assert.equal(calls.length, 1, 'should have flushed once');\n assert.equal(calls[0].content, 'a\\nb\\nc');\n assert.equal(batcher.pending, 0);\n\n await batcher.destroy();\n });\n\n it('skips embeds for batched messages (only content)', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 2, flushIntervalMs: 60_000 });\n\n batcher.enqueue(fakeEvent('a', true));\n batcher.enqueue(fakeEvent('b', true)); // triggers flush\n await new Promise((r) => setTimeout(r, 10));\n\n assert.equal(calls.length, 1);\n assert.equal(calls[0].embeds.length, 0, 'batched sends skip embeds to avoid duplication');\n assert.equal(calls[0].content, 'a\\nb');\n\n await batcher.destroy();\n });\n });\n\n describe('enqueueImmediate', () => {\n it('flushes pending buffer then sends immediately', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 10, flushIntervalMs: 60_000 });\n\n batcher.enqueue(fakeEvent('buffered-1'));\n batcher.enqueue(fakeEvent('buffered-2'));\n\n await batcher.enqueueImmediate(fakeEvent('blocker!'));\n\n // First call: the pending buffer flush\n // Second call: the immediate event\n assert.equal(calls.length, 2, 'should have two send calls');\n assert.equal(calls[0].content, 'buffered-1\\nbuffered-2');\n assert.equal(calls[1].content, 'blocker!');\n assert.equal(batcher.pending, 0);\n\n await batcher.destroy();\n });\n\n it('sends immediately when buffer is empty', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 10, flushIntervalMs: 60_000 });\n\n await batcher.enqueueImmediate(fakeEvent('urgent'));\n\n assert.equal(calls.length, 1);\n assert.equal(calls[0].content, 'urgent');\n\n await batcher.destroy();\n });\n });\n\n describe('timer-based flush', () => {\n it('flushes on interval', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 100, flushIntervalMs: 50 });\n batcher.start();\n\n batcher.enqueue(fakeEvent('timed-1'));\n batcher.enqueue(fakeEvent('timed-2'));\n\n // Wait longer than flushIntervalMs\n await new Promise((r) => setTimeout(r, 120));\n\n assert.ok(calls.length >= 1, 'timer should have triggered at least one flush');\n assert.equal(calls[0].content, 'timed-1\\ntimed-2');\n assert.equal(batcher.pending, 0);\n\n await batcher.destroy();\n });\n\n it('stop prevents further timer flushes', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 100, flushIntervalMs: 30 });\n batcher.start();\n batcher.stop();\n\n batcher.enqueue(fakeEvent('orphan'));\n await new Promise((r) => setTimeout(r, 80));\n\n assert.equal(calls.length, 0, 'no flush after stop');\n // Cleanup without triggering flush timer\n batcher.stop(); // idempotent\n // Manually drain for cleanup\n await batcher.destroy();\n });\n });\n\n describe('destroy', () => {\n it('flushes remaining buffer on destroy', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 100, flushIntervalMs: 60_000 });\n\n batcher.enqueue(fakeEvent('leftover-1'));\n batcher.enqueue(fakeEvent('leftover-2'));\n\n await batcher.destroy();\n\n assert.equal(calls.length, 1);\n assert.equal(calls[0].content, 'leftover-1\\nleftover-2');\n });\n\n it('is idempotent — second destroy is no-op', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 100, flushIntervalMs: 60_000 });\n\n batcher.enqueue(fakeEvent('once'));\n await batcher.destroy();\n await batcher.destroy(); // second call\n\n assert.equal(calls.length, 1, 'only flushed once');\n });\n\n it('enqueue after destroy is silently ignored', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 2, flushIntervalMs: 60_000 });\n await batcher.destroy();\n\n batcher.enqueue(fakeEvent('post-destroy'));\n await new Promise((r) => setTimeout(r, 10));\n\n assert.equal(calls.length, 0, 'no sends after destroy');\n });\n });\n\n describe('empty buffer', () => {\n it('flush of empty buffer is no-op', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 100, flushIntervalMs: 60_000 });\n batcher.start();\n\n // Force a timer tick with an empty buffer\n await new Promise((r) => setTimeout(r, 10));\n await batcher.destroy();\n\n // Only the destroy-triggered flush, which should also be a no-op\n assert.equal(calls.length, 0, 'no sends for empty buffer');\n });\n });\n\n describe('single-item flush', () => {\n it('handles a single item in buffer at destroy', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 100, flushIntervalMs: 60_000 });\n\n batcher.enqueue(fakeEvent('solo'));\n await batcher.destroy();\n\n assert.equal(calls.length, 1);\n assert.equal(calls[0].content, 'solo');\n assert.equal(calls[0].embeds.length, 0);\n assert.equal(calls[0].components.length, 0);\n });\n });\n\n describe('error handling', () => {\n it('logs error and continues when send throws', async () => {\n let attempt = 0;\n const sendFn = async () => {\n attempt++;\n throw new Error('Discord rate limit');\n };\n const { logger, errors, warns } = createLogger();\n const batcher = new MessageBatcher(sendFn, logger, { maxBatchSize: 2, flushIntervalMs: 60_000 });\n\n batcher.enqueue(fakeEvent('x'));\n batcher.enqueue(fakeEvent('y')); // triggers flush\n // Wait for flush + retry\n await new Promise((r) => setTimeout(r, 1500));\n\n assert.ok(errors.length >= 1, 'should have logged an error');\n assert.ok(warns.length >= 1, 'should have logged a warning on retry failure');\n assert.equal(batcher.pending, 0, 'buffer cleared even on error');\n\n // Batcher should still be alive — enqueue more\n batcher.enqueue(fakeEvent('after-error'));\n assert.equal(batcher.pending, 1, 'can still enqueue after error');\n\n await batcher.destroy();\n });\n\n it('succeeds on retry if first attempt fails', async () => {\n let attempt = 0;\n const calls: SendPayload[] = [];\n const sendFn = async (payload: SendPayload) => {\n attempt++;\n if (attempt === 1) throw new Error('transient');\n calls.push(payload);\n };\n const { logger, errors } = createLogger();\n const batcher = new MessageBatcher(sendFn, logger, { maxBatchSize: 2, flushIntervalMs: 60_000 });\n\n batcher.enqueue(fakeEvent('retry-me'));\n batcher.enqueue(fakeEvent('retry-too'));\n // Wait for flush + retry delay\n await new Promise((r) => setTimeout(r, 1500));\n\n assert.equal(errors.length, 1, 'logged one error on first attempt');\n assert.equal(calls.length, 1, 'retry succeeded');\n assert.equal(calls[0].content, 'retry-me\\nretry-too');\n\n await batcher.destroy();\n });\n });\n\n describe('buffer at exactly capacity', () => {\n it('flushes at exactly maxBatchSize', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 4, flushIntervalMs: 60_000 });\n\n batcher.enqueue(fakeEvent('1'));\n batcher.enqueue(fakeEvent('2'));\n batcher.enqueue(fakeEvent('3'));\n assert.equal(calls.length, 0, 'not flushed at 3/4');\n\n batcher.enqueue(fakeEvent('4')); // exactly at capacity\n await new Promise((r) => setTimeout(r, 10));\n\n assert.equal(calls.length, 1);\n assert.equal(calls[0].content, '1\\n2\\n3\\n4');\n\n await batcher.destroy();\n });\n });\n\n describe('components handling', () => {\n it('uses components from the last event that has them', async () => {\n const { fn, calls } = createSend();\n const batcher = new MessageBatcher(fn, undefined, { maxBatchSize: 3, flushIntervalMs: 60_000 });\n\n const fakeRow = { type: 'ActionRow', components: [] };\n batcher.enqueue(fakeEvent('no-components'));\n batcher.enqueue({ content: 'with-components', components: [fakeRow] } as any);\n batcher.enqueue(fakeEvent('also-no-components')); // triggers flush\n\n await new Promise((r) => setTimeout(r, 10));\n\n assert.equal(calls.length, 1);\n assert.deepEqual(calls[0].components, [fakeRow]);\n\n await batcher.destroy();\n });\n });\n});\n"]}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Orchestrator — LLM-powered agent for the #gsd-control Discord channel.
3
+ *
4
+ * Receives Discord messages, maintains conversation history, calls the
5
+ * Anthropic messages API with 5 tool definitions (list_projects, start_session,
6
+ * get_status, stop_session, get_session_detail), and sends the LLM's response
7
+ * back to Discord.
8
+ *
9
+ * Uses the standard messages.create() tool-use loop (not betaZodTool helpers,
10
+ * which don't exist in SDK v0.52). Zod schemas are used for input validation
11
+ * at the tool execution layer.
12
+ */
13
+ import type Anthropic from '@anthropic-ai/sdk';
14
+ import type { MessageParam } from '@anthropic-ai/sdk/resources/messages/messages';
15
+ import type { SessionManager } from './session-manager.js';
16
+ import type { ChannelManager } from './channel-manager.js';
17
+ import type { ProjectInfo } from './types.js';
18
+ import type { Logger } from './logger.js';
19
+ export interface OrchestratorConfig {
20
+ model: string;
21
+ max_tokens: number;
22
+ control_channel_id: string;
23
+ }
24
+ export interface OrchestratorDeps {
25
+ sessionManager: SessionManager;
26
+ channelManager: ChannelManager;
27
+ scanProjects: () => Promise<ProjectInfo[]>;
28
+ config: OrchestratorConfig;
29
+ logger: Logger;
30
+ ownerId: string;
31
+ }
32
+ export declare class Orchestrator {
33
+ private readonly deps;
34
+ private client;
35
+ private history;
36
+ /**
37
+ * @param deps - orchestrator dependencies (session manager, channel manager, etc.)
38
+ * @param client - optional Anthropic client for testability; if omitted, created from env
39
+ */
40
+ constructor(deps: OrchestratorDeps, client?: Anthropic);
41
+ /**
42
+ * Lazily initialise the Anthropic client. Dynamic import handles K007 module resolution.
43
+ * Requires ANTHROPIC_API_KEY environment variable.
44
+ */
45
+ private getClient;
46
+ /**
47
+ * Handle an incoming Discord message. Entry point called by the bot's
48
+ * message handler for every message in every channel.
49
+ *
50
+ * Guards: ignores bot messages, non-owner messages, and non-control-channel messages.
51
+ */
52
+ handleMessage(message: DiscordMessageLike): Promise<void>;
53
+ /**
54
+ * Run the tool-use loop: call messages.create(), execute any tool calls,
55
+ * feed results back, repeat until the model produces a final text response.
56
+ */
57
+ private runAgentLoop;
58
+ /**
59
+ * Execute a single tool by name. Returns a string result for the LLM.
60
+ * All errors are caught and returned as error strings (the LLM can reason about them).
61
+ */
62
+ private executeTool;
63
+ private toolListProjects;
64
+ private toolStartSession;
65
+ private toolGetStatus;
66
+ private toolStopSession;
67
+ private toolGetSessionDetail;
68
+ /**
69
+ * Trim conversation history to MAX_HISTORY entries.
70
+ * Removes the oldest user+assistant pair from the front to keep pairs aligned.
71
+ */
72
+ private trimHistory;
73
+ /**
74
+ * Return a copy of the conversation history (for debugging / observability).
75
+ */
76
+ getHistory(): MessageParam[];
77
+ /**
78
+ * Stop the orchestrator — clears history and nulls client reference.
79
+ */
80
+ stop(): void;
81
+ }
82
+ /**
83
+ * Minimal Discord message interface — avoids importing discord.js directly,
84
+ * making the orchestrator testable without full discord.js mocking.
85
+ */
86
+ export interface DiscordMessageLike {
87
+ author: {
88
+ id: string;
89
+ bot: boolean;
90
+ };
91
+ channelId: string;
92
+ content: string;
93
+ channel: {
94
+ send: (content: string) => Promise<unknown>;
95
+ sendTyping: () => Promise<unknown>;
96
+ };
97
+ }
98
+ //# sourceMappingURL=orchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EACV,YAAY,EAMb,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAqB1C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3C,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAyGD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,OAAO,CAAsB;IAErC;;;OAGG;gBACS,IAAI,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,SAAS;IAKtD;;;OAGG;YACW,SAAS;IAQvB;;;;;OAKG;IACG,aAAa,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoE/D;;;OAGG;YACW,YAAY;IAyD1B;;;OAGG;YACW,WAAW;YA2BX,gBAAgB;YAUhB,gBAAgB;IAS9B,OAAO,CAAC,aAAa;YAaP,eAAe;IA0B7B,OAAO,CAAC,oBAAoB;IAU5B;;;OAGG;IACH,OAAO,CAAC,WAAW;IAOnB;;OAEG;IACH,UAAU,IAAI,YAAY,EAAE;IAI5B;;OAEG;IACH,IAAI,IAAI,IAAI;CAIb;AAMD;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE;QACP,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5C,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;KACpC,CAAC;CACH"}
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Orchestrator — LLM-powered agent for the #gsd-control Discord channel.
3
+ *
4
+ * Receives Discord messages, maintains conversation history, calls the
5
+ * Anthropic messages API with 5 tool definitions (list_projects, start_session,
6
+ * get_status, stop_session, get_session_detail), and sends the LLM's response
7
+ * back to Discord.
8
+ *
9
+ * Uses the standard messages.create() tool-use loop (not betaZodTool helpers,
10
+ * which don't exist in SDK v0.52). Zod schemas are used for input validation
11
+ * at the tool execution layer.
12
+ */
13
+ import { z } from 'zod';
14
+ // ---------------------------------------------------------------------------
15
+ // API key resolution — requires ANTHROPIC_API_KEY env var
16
+ // Anthropic OAuth removed per TOS compliance (see docs/user-docs/claude-code-auth-compliance.md)
17
+ // ---------------------------------------------------------------------------
18
+ function resolveAnthropicApiKey() {
19
+ const apiKey = process.env.ANTHROPIC_API_KEY;
20
+ if (!apiKey) {
21
+ throw new Error('ANTHROPIC_API_KEY is required. Set it in your environment or run `gsd config`.');
22
+ }
23
+ return apiKey;
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // System Prompt
27
+ // ---------------------------------------------------------------------------
28
+ const SYSTEM_PROMPT = `You are GSD Control — a concise, capable orchestrator for managing GSD (Git Ship Done) coding agent sessions via Discord.
29
+
30
+ You have tools to list projects, start sessions, get status, stop sessions, and inspect session details. Use them to fulfill the user's requests.
31
+
32
+ Response guidelines:
33
+ - Be terse and direct. No filler, no performed enthusiasm.
34
+ - When reporting status, use bullet points with project name, status, duration, and cost.
35
+ - When starting a session, confirm with the project name and session ID.
36
+ - When stopping a session, confirm which session was stopped.
37
+ - If something fails, say what went wrong plainly.
38
+ - Use Discord markdown formatting (bold, code blocks) for readability.
39
+ - Never expose internal error stack traces to the user — summarize the issue.`;
40
+ // ---------------------------------------------------------------------------
41
+ // Tool Definitions (Anthropic API format)
42
+ // ---------------------------------------------------------------------------
43
+ const TOOLS = [
44
+ {
45
+ name: 'list_projects',
46
+ description: 'List all detected projects across configured scan roots. Returns project names, paths, and detected markers (git, node, gsd, etc.).',
47
+ input_schema: {
48
+ type: 'object',
49
+ properties: {},
50
+ required: [],
51
+ },
52
+ },
53
+ {
54
+ name: 'start_session',
55
+ description: 'Start a new GSD auto-mode session for a project. Provide the absolute project path. Optionally provide a command to run instead of the default "/gsd auto".',
56
+ input_schema: {
57
+ type: 'object',
58
+ properties: {
59
+ projectPath: { type: 'string', description: 'Absolute path to the project directory' },
60
+ command: { type: 'string', description: 'Optional command to send instead of "/gsd auto"' },
61
+ },
62
+ required: ['projectPath'],
63
+ },
64
+ },
65
+ {
66
+ name: 'get_status',
67
+ description: 'Get the current status of all active GSD sessions. Shows project name, status, duration, and cost for each.',
68
+ input_schema: {
69
+ type: 'object',
70
+ properties: {},
71
+ required: [],
72
+ },
73
+ },
74
+ {
75
+ name: 'stop_session',
76
+ description: 'Stop a running GSD session. Provide a session ID or project name — fuzzy matching is used to find the session.',
77
+ input_schema: {
78
+ type: 'object',
79
+ properties: {
80
+ identifier: { type: 'string', description: 'Session ID or project name to match' },
81
+ },
82
+ required: ['identifier'],
83
+ },
84
+ },
85
+ {
86
+ name: 'get_session_detail',
87
+ description: 'Get detailed information about a specific session including cost breakdown, recent events, pending blockers, and error state.',
88
+ input_schema: {
89
+ type: 'object',
90
+ properties: {
91
+ sessionId: { type: 'string', description: 'The session ID to inspect' },
92
+ },
93
+ required: ['sessionId'],
94
+ },
95
+ },
96
+ ];
97
+ // ---------------------------------------------------------------------------
98
+ // Zod schemas for tool input validation
99
+ // ---------------------------------------------------------------------------
100
+ const StartSessionInput = z.object({
101
+ projectPath: z.string(),
102
+ command: z.string().optional(),
103
+ });
104
+ const StopSessionInput = z.object({
105
+ identifier: z.string(),
106
+ });
107
+ const GetSessionDetailInput = z.object({
108
+ sessionId: z.string(),
109
+ });
110
+ // ---------------------------------------------------------------------------
111
+ // Conversation History Cap
112
+ // ---------------------------------------------------------------------------
113
+ const MAX_HISTORY = 30;
114
+ // ---------------------------------------------------------------------------
115
+ // Orchestrator
116
+ // ---------------------------------------------------------------------------
117
+ export class Orchestrator {
118
+ deps;
119
+ client;
120
+ history = [];
121
+ /**
122
+ * @param deps - orchestrator dependencies (session manager, channel manager, etc.)
123
+ * @param client - optional Anthropic client for testability; if omitted, created from env
124
+ */
125
+ constructor(deps, client) {
126
+ this.deps = deps;
127
+ this.client = client ?? null;
128
+ }
129
+ /**
130
+ * Lazily initialise the Anthropic client. Dynamic import handles K007 module resolution.
131
+ * Requires ANTHROPIC_API_KEY environment variable.
132
+ */
133
+ async getClient() {
134
+ if (this.client)
135
+ return this.client;
136
+ const apiKey = resolveAnthropicApiKey();
137
+ const { default: AnthropicSDK } = await import('@anthropic-ai/sdk');
138
+ this.client = new AnthropicSDK({ apiKey });
139
+ return this.client;
140
+ }
141
+ /**
142
+ * Handle an incoming Discord message. Entry point called by the bot's
143
+ * message handler for every message in every channel.
144
+ *
145
+ * Guards: ignores bot messages, non-owner messages, and non-control-channel messages.
146
+ */
147
+ async handleMessage(message) {
148
+ // Ignore bot messages
149
+ if (message.author.bot)
150
+ return;
151
+ // Ignore non-control-channel messages
152
+ if (message.channelId !== this.deps.config.control_channel_id)
153
+ return;
154
+ // Auth guard — only the owner can use the orchestrator
155
+ if (message.author.id !== this.deps.ownerId) {
156
+ this.deps.logger.debug('orchestrator auth rejected', { userId: message.author.id });
157
+ return;
158
+ }
159
+ const content = message.content?.trim();
160
+ if (!content)
161
+ return;
162
+ this.deps.logger.info('orchestrator message received', {
163
+ userId: message.author.id,
164
+ channelId: message.channelId,
165
+ contentLength: content.length,
166
+ });
167
+ // Append user message to history
168
+ this.history.push({ role: 'user', content });
169
+ try {
170
+ // Show typing indicator while processing
171
+ await message.channel.sendTyping().catch(() => { });
172
+ const responseText = await this.runAgentLoop();
173
+ // Send response to Discord
174
+ await message.channel.send(responseText);
175
+ this.deps.logger.info('orchestrator response sent', {
176
+ channelId: message.channelId,
177
+ responseLength: responseText.length,
178
+ });
179
+ }
180
+ catch (err) {
181
+ const errorMsg = err instanceof Error ? err.message : String(err);
182
+ // Invalidate cached client on auth errors so next call re-resolves OAuth token
183
+ if (errorMsg.includes('authentication') || errorMsg.includes('apiKey') || errorMsg.includes('authToken') || errorMsg.includes('401')) {
184
+ this.client = null;
185
+ }
186
+ this.deps.logger.error('orchestrator error', {
187
+ error: errorMsg,
188
+ userId: message.author.id,
189
+ channelId: message.channelId,
190
+ });
191
+ // Send error feedback to Discord
192
+ try {
193
+ await message.channel.send('⚠️ Something went wrong processing your request.');
194
+ }
195
+ catch (sendErr) {
196
+ this.deps.logger.warn('orchestrator error reply failed', {
197
+ error: sendErr instanceof Error ? sendErr.message : String(sendErr),
198
+ });
199
+ }
200
+ // Still append a synthetic assistant message so history stays paired
201
+ this.history.push({ role: 'assistant', content: '[error — see logs]' });
202
+ }
203
+ this.trimHistory();
204
+ }
205
+ /**
206
+ * Run the tool-use loop: call messages.create(), execute any tool calls,
207
+ * feed results back, repeat until the model produces a final text response.
208
+ */
209
+ async runAgentLoop() {
210
+ const client = await this.getClient();
211
+ const { model, max_tokens } = this.deps.config;
212
+ let loopMessages = [...this.history];
213
+ const maxIterations = 10; // safety valve
214
+ for (let i = 0; i < maxIterations; i++) {
215
+ const response = await client.messages.create({
216
+ model,
217
+ max_tokens,
218
+ system: SYSTEM_PROMPT,
219
+ tools: TOOLS,
220
+ messages: loopMessages,
221
+ });
222
+ // If the model stopped for end_turn (no tool calls), extract text and return
223
+ if (response.stop_reason === 'end_turn' || response.stop_reason !== 'tool_use') {
224
+ const textBlocks = response.content.filter((b) => b.type === 'text');
225
+ const finalText = textBlocks.map((b) => b.text).join('\n') || '(No response)';
226
+ // Append assistant message to conversation history
227
+ this.history.push({ role: 'assistant', content: finalText });
228
+ return finalText;
229
+ }
230
+ // Model wants to use tools — execute them all
231
+ const toolUseBlocks = response.content.filter((b) => b.type === 'tool_use');
232
+ // Build tool results
233
+ const toolResults = [];
234
+ for (const toolUse of toolUseBlocks) {
235
+ const result = await this.executeTool(toolUse.name, toolUse.input);
236
+ toolResults.push({
237
+ type: 'tool_result',
238
+ tool_use_id: toolUse.id,
239
+ content: result,
240
+ });
241
+ }
242
+ // Append the assistant message (with tool_use blocks) and user tool_result message
243
+ loopMessages = [
244
+ ...loopMessages,
245
+ { role: 'assistant', content: response.content },
246
+ { role: 'user', content: toolResults },
247
+ ];
248
+ }
249
+ // If we hit max iterations, return a fallback
250
+ return 'I hit the maximum number of tool iterations. Please try a simpler request.';
251
+ }
252
+ /**
253
+ * Execute a single tool by name. Returns a string result for the LLM.
254
+ * All errors are caught and returned as error strings (the LLM can reason about them).
255
+ */
256
+ async executeTool(name, input) {
257
+ try {
258
+ switch (name) {
259
+ case 'list_projects':
260
+ return await this.toolListProjects();
261
+ case 'start_session':
262
+ return await this.toolStartSession(input);
263
+ case 'get_status':
264
+ return this.toolGetStatus();
265
+ case 'get_session_detail':
266
+ return this.toolGetSessionDetail(input);
267
+ case 'stop_session':
268
+ return await this.toolStopSession(input);
269
+ default:
270
+ return `Unknown tool: ${name}`;
271
+ }
272
+ }
273
+ catch (err) {
274
+ const msg = err instanceof Error ? err.message : String(err);
275
+ this.deps.logger.error('tool execution error', { tool: name, error: msg });
276
+ return `Error: ${msg}`;
277
+ }
278
+ }
279
+ // ---------------------------------------------------------------------------
280
+ // Tool implementations
281
+ // ---------------------------------------------------------------------------
282
+ async toolListProjects() {
283
+ const projects = await this.deps.scanProjects();
284
+ if (projects.length === 0)
285
+ return 'No projects found.';
286
+ return JSON.stringify(projects.map((p) => ({ name: p.name, path: p.path, markers: p.markers })), null, 2);
287
+ }
288
+ async toolStartSession(input) {
289
+ const parsed = StartSessionInput.parse(input);
290
+ const sessionId = await this.deps.sessionManager.startSession({
291
+ projectDir: parsed.projectPath,
292
+ command: parsed.command,
293
+ });
294
+ return `Session started: ${sessionId} for ${parsed.projectPath}`;
295
+ }
296
+ toolGetStatus() {
297
+ const sessions = this.deps.sessionManager.getAllSessions();
298
+ if (sessions.length === 0)
299
+ return 'No active sessions.';
300
+ return sessions
301
+ .map((s) => {
302
+ const durationMin = Math.floor((Date.now() - s.startTime) / 60_000);
303
+ const cost = s.cost.totalCost.toFixed(4);
304
+ return `• ${s.projectName} — ${s.status} (${durationMin}m, $${cost})`;
305
+ })
306
+ .join('\n');
307
+ }
308
+ async toolStopSession(input) {
309
+ const parsed = StopSessionInput.parse(input);
310
+ const { identifier } = parsed;
311
+ // Try exact sessionId match first
312
+ const byId = this.deps.sessionManager.getSession(identifier);
313
+ if (byId) {
314
+ await this.deps.sessionManager.cancelSession(identifier);
315
+ return `Stopped session ${identifier} (${byId.projectName})`;
316
+ }
317
+ // Fuzzy match by project name
318
+ const all = this.deps.sessionManager.getAllSessions();
319
+ const match = all.find((s) => s.projectName.toLowerCase().includes(identifier.toLowerCase()) ||
320
+ s.projectDir.toLowerCase().includes(identifier.toLowerCase()));
321
+ if (match) {
322
+ await this.deps.sessionManager.cancelSession(match.sessionId);
323
+ return `Stopped session ${match.sessionId} (${match.projectName})`;
324
+ }
325
+ return `No session found matching "${identifier}"`;
326
+ }
327
+ toolGetSessionDetail(input) {
328
+ const parsed = GetSessionDetailInput.parse(input);
329
+ const result = this.deps.sessionManager.getResult(parsed.sessionId);
330
+ return JSON.stringify(result, null, 2);
331
+ }
332
+ // ---------------------------------------------------------------------------
333
+ // History management
334
+ // ---------------------------------------------------------------------------
335
+ /**
336
+ * Trim conversation history to MAX_HISTORY entries.
337
+ * Removes the oldest user+assistant pair from the front to keep pairs aligned.
338
+ */
339
+ trimHistory() {
340
+ while (this.history.length > MAX_HISTORY) {
341
+ // Remove from front — two messages at a time to keep user/assistant pairs
342
+ this.history.splice(0, 2);
343
+ }
344
+ }
345
+ /**
346
+ * Return a copy of the conversation history (for debugging / observability).
347
+ */
348
+ getHistory() {
349
+ return [...this.history];
350
+ }
351
+ /**
352
+ * Stop the orchestrator — clears history and nulls client reference.
353
+ */
354
+ stop() {
355
+ this.history = [];
356
+ this.client = null;
357
+ }
358
+ }
359
+ //# sourceMappingURL=orchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Orchestrator — LLM-powered agent for the #gsd-control Discord channel.\n *\n * Receives Discord messages, maintains conversation history, calls the\n * Anthropic messages API with 5 tool definitions (list_projects, start_session,\n * get_status, stop_session, get_session_detail), and sends the LLM's response\n * back to Discord.\n *\n * Uses the standard messages.create() tool-use loop (not betaZodTool helpers,\n * which don't exist in SDK v0.52). Zod schemas are used for input validation\n * at the tool execution layer.\n */\n\nimport { z } from 'zod';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type {\n MessageParam,\n ContentBlockParam,\n Tool,\n ToolResultBlockParam,\n ToolUseBlock,\n TextBlock,\n} from '@anthropic-ai/sdk/resources/messages/messages';\nimport type { SessionManager } from './session-manager.js';\nimport type { ChannelManager } from './channel-manager.js';\nimport type { ProjectInfo, ManagedSession } from './types.js';\nimport type { Logger } from './logger.js';\n\n// ---------------------------------------------------------------------------\n// API key resolution — requires ANTHROPIC_API_KEY env var\n// Anthropic OAuth removed per TOS compliance (see docs/user-docs/claude-code-auth-compliance.md)\n// ---------------------------------------------------------------------------\n\nfunction resolveAnthropicApiKey(): string {\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'ANTHROPIC_API_KEY is required. Set it in your environment or run `gsd config`.',\n );\n }\n return apiKey;\n}\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\nexport interface OrchestratorConfig {\n model: string;\n max_tokens: number;\n control_channel_id: string;\n}\n\nexport interface OrchestratorDeps {\n sessionManager: SessionManager;\n channelManager: ChannelManager;\n scanProjects: () => Promise<ProjectInfo[]>;\n config: OrchestratorConfig;\n logger: Logger;\n ownerId: string;\n}\n\n// ---------------------------------------------------------------------------\n// System Prompt\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PROMPT = `You are GSD Control — a concise, capable orchestrator for managing GSD (Git Ship Done) coding agent sessions via Discord.\n\nYou have tools to list projects, start sessions, get status, stop sessions, and inspect session details. Use them to fulfill the user's requests.\n\nResponse guidelines:\n- Be terse and direct. No filler, no performed enthusiasm.\n- When reporting status, use bullet points with project name, status, duration, and cost.\n- When starting a session, confirm with the project name and session ID.\n- When stopping a session, confirm which session was stopped.\n- If something fails, say what went wrong plainly.\n- Use Discord markdown formatting (bold, code blocks) for readability.\n- Never expose internal error stack traces to the user — summarize the issue.`;\n\n// ---------------------------------------------------------------------------\n// Tool Definitions (Anthropic API format)\n// ---------------------------------------------------------------------------\n\nconst TOOLS: Tool[] = [\n {\n name: 'list_projects',\n description: 'List all detected projects across configured scan roots. Returns project names, paths, and detected markers (git, node, gsd, etc.).',\n input_schema: {\n type: 'object' as const,\n properties: {},\n required: [],\n },\n },\n {\n name: 'start_session',\n description: 'Start a new GSD auto-mode session for a project. Provide the absolute project path. Optionally provide a command to run instead of the default \"/gsd auto\".',\n input_schema: {\n type: 'object' as const,\n properties: {\n projectPath: { type: 'string', description: 'Absolute path to the project directory' },\n command: { type: 'string', description: 'Optional command to send instead of \"/gsd auto\"' },\n },\n required: ['projectPath'],\n },\n },\n {\n name: 'get_status',\n description: 'Get the current status of all active GSD sessions. Shows project name, status, duration, and cost for each.',\n input_schema: {\n type: 'object' as const,\n properties: {},\n required: [],\n },\n },\n {\n name: 'stop_session',\n description: 'Stop a running GSD session. Provide a session ID or project name — fuzzy matching is used to find the session.',\n input_schema: {\n type: 'object' as const,\n properties: {\n identifier: { type: 'string', description: 'Session ID or project name to match' },\n },\n required: ['identifier'],\n },\n },\n {\n name: 'get_session_detail',\n description: 'Get detailed information about a specific session including cost breakdown, recent events, pending blockers, and error state.',\n input_schema: {\n type: 'object' as const,\n properties: {\n sessionId: { type: 'string', description: 'The session ID to inspect' },\n },\n required: ['sessionId'],\n },\n },\n];\n\n// ---------------------------------------------------------------------------\n// Zod schemas for tool input validation\n// ---------------------------------------------------------------------------\n\nconst StartSessionInput = z.object({\n projectPath: z.string(),\n command: z.string().optional(),\n});\n\nconst StopSessionInput = z.object({\n identifier: z.string(),\n});\n\nconst GetSessionDetailInput = z.object({\n sessionId: z.string(),\n});\n\n// ---------------------------------------------------------------------------\n// Conversation History Cap\n// ---------------------------------------------------------------------------\n\nconst MAX_HISTORY = 30;\n\n// ---------------------------------------------------------------------------\n// Orchestrator\n// ---------------------------------------------------------------------------\n\nexport class Orchestrator {\n private readonly deps: OrchestratorDeps;\n private client: Anthropic | null;\n private history: MessageParam[] = [];\n\n /**\n * @param deps - orchestrator dependencies (session manager, channel manager, etc.)\n * @param client - optional Anthropic client for testability; if omitted, created from env\n */\n constructor(deps: OrchestratorDeps, client?: Anthropic) {\n this.deps = deps;\n this.client = client ?? null;\n }\n\n /**\n * Lazily initialise the Anthropic client. Dynamic import handles K007 module resolution.\n * Requires ANTHROPIC_API_KEY environment variable.\n */\n private async getClient(): Promise<Anthropic> {\n if (this.client) return this.client;\n const apiKey = resolveAnthropicApiKey();\n const { default: AnthropicSDK } = await import('@anthropic-ai/sdk');\n this.client = new AnthropicSDK({ apiKey });\n return this.client;\n }\n\n /**\n * Handle an incoming Discord message. Entry point called by the bot's\n * message handler for every message in every channel.\n *\n * Guards: ignores bot messages, non-owner messages, and non-control-channel messages.\n */\n async handleMessage(message: DiscordMessageLike): Promise<void> {\n // Ignore bot messages\n if (message.author.bot) return;\n\n // Ignore non-control-channel messages\n if (message.channelId !== this.deps.config.control_channel_id) return;\n\n // Auth guard — only the owner can use the orchestrator\n if (message.author.id !== this.deps.ownerId) {\n this.deps.logger.debug('orchestrator auth rejected', { userId: message.author.id });\n return;\n }\n\n const content = message.content?.trim();\n if (!content) return;\n\n this.deps.logger.info('orchestrator message received', {\n userId: message.author.id,\n channelId: message.channelId,\n contentLength: content.length,\n });\n\n // Append user message to history\n this.history.push({ role: 'user', content });\n\n try {\n // Show typing indicator while processing\n await message.channel.sendTyping().catch(() => {});\n\n const responseText = await this.runAgentLoop();\n\n // Send response to Discord\n await message.channel.send(responseText);\n\n this.deps.logger.info('orchestrator response sent', {\n channelId: message.channelId,\n responseLength: responseText.length,\n });\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n\n // Invalidate cached client on auth errors so next call re-resolves OAuth token\n if (errorMsg.includes('authentication') || errorMsg.includes('apiKey') || errorMsg.includes('authToken') || errorMsg.includes('401')) {\n this.client = null;\n }\n\n this.deps.logger.error('orchestrator error', {\n error: errorMsg,\n userId: message.author.id,\n channelId: message.channelId,\n });\n\n // Send error feedback to Discord\n try {\n await message.channel.send('⚠️ Something went wrong processing your request.');\n } catch (sendErr) {\n this.deps.logger.warn('orchestrator error reply failed', {\n error: sendErr instanceof Error ? sendErr.message : String(sendErr),\n });\n }\n\n // Still append a synthetic assistant message so history stays paired\n this.history.push({ role: 'assistant', content: '[error — see logs]' });\n }\n\n this.trimHistory();\n }\n\n /**\n * Run the tool-use loop: call messages.create(), execute any tool calls,\n * feed results back, repeat until the model produces a final text response.\n */\n private async runAgentLoop(): Promise<string> {\n const client = await this.getClient();\n const { model, max_tokens } = this.deps.config;\n\n let loopMessages: MessageParam[] = [...this.history];\n const maxIterations = 10; // safety valve\n\n for (let i = 0; i < maxIterations; i++) {\n const response = await client.messages.create({\n model,\n max_tokens,\n system: SYSTEM_PROMPT,\n tools: TOOLS,\n messages: loopMessages,\n });\n\n // If the model stopped for end_turn (no tool calls), extract text and return\n if (response.stop_reason === 'end_turn' || response.stop_reason !== 'tool_use') {\n const textBlocks = response.content.filter(\n (b): b is TextBlock => b.type === 'text',\n );\n const finalText = textBlocks.map((b) => b.text).join('\\n') || '(No response)';\n\n // Append assistant message to conversation history\n this.history.push({ role: 'assistant', content: finalText });\n\n return finalText;\n }\n\n // Model wants to use tools — execute them all\n const toolUseBlocks = response.content.filter(\n (b): b is ToolUseBlock => b.type === 'tool_use',\n );\n\n // Build tool results\n const toolResults: ToolResultBlockParam[] = [];\n for (const toolUse of toolUseBlocks) {\n const result = await this.executeTool(toolUse.name, toolUse.input as Record<string, unknown>);\n toolResults.push({\n type: 'tool_result',\n tool_use_id: toolUse.id,\n content: result,\n });\n }\n\n // Append the assistant message (with tool_use blocks) and user tool_result message\n loopMessages = [\n ...loopMessages,\n { role: 'assistant', content: response.content as ContentBlockParam[] },\n { role: 'user', content: toolResults },\n ];\n }\n\n // If we hit max iterations, return a fallback\n return 'I hit the maximum number of tool iterations. Please try a simpler request.';\n }\n\n /**\n * Execute a single tool by name. Returns a string result for the LLM.\n * All errors are caught and returned as error strings (the LLM can reason about them).\n */\n private async executeTool(name: string, input: Record<string, unknown>): Promise<string> {\n try {\n switch (name) {\n case 'list_projects':\n return await this.toolListProjects();\n case 'start_session':\n return await this.toolStartSession(input);\n case 'get_status':\n return this.toolGetStatus();\n case 'get_session_detail':\n return this.toolGetSessionDetail(input);\n case 'stop_session':\n return await this.toolStopSession(input);\n default:\n return `Unknown tool: ${name}`;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n this.deps.logger.error('tool execution error', { tool: name, error: msg });\n return `Error: ${msg}`;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Tool implementations\n // ---------------------------------------------------------------------------\n\n private async toolListProjects(): Promise<string> {\n const projects = await this.deps.scanProjects();\n if (projects.length === 0) return 'No projects found.';\n return JSON.stringify(\n projects.map((p) => ({ name: p.name, path: p.path, markers: p.markers })),\n null,\n 2,\n );\n }\n\n private async toolStartSession(input: Record<string, unknown>): Promise<string> {\n const parsed = StartSessionInput.parse(input);\n const sessionId = await this.deps.sessionManager.startSession({\n projectDir: parsed.projectPath,\n command: parsed.command,\n });\n return `Session started: ${sessionId} for ${parsed.projectPath}`;\n }\n\n private toolGetStatus(): string {\n const sessions = this.deps.sessionManager.getAllSessions();\n if (sessions.length === 0) return 'No active sessions.';\n\n return sessions\n .map((s: ManagedSession) => {\n const durationMin = Math.floor((Date.now() - s.startTime) / 60_000);\n const cost = s.cost.totalCost.toFixed(4);\n return `• ${s.projectName} — ${s.status} (${durationMin}m, $${cost})`;\n })\n .join('\\n');\n }\n\n private async toolStopSession(input: Record<string, unknown>): Promise<string> {\n const parsed = StopSessionInput.parse(input);\n const { identifier } = parsed;\n\n // Try exact sessionId match first\n const byId = this.deps.sessionManager.getSession(identifier);\n if (byId) {\n await this.deps.sessionManager.cancelSession(identifier);\n return `Stopped session ${identifier} (${byId.projectName})`;\n }\n\n // Fuzzy match by project name\n const all = this.deps.sessionManager.getAllSessions();\n const match = all.find(\n (s: ManagedSession) =>\n s.projectName.toLowerCase().includes(identifier.toLowerCase()) ||\n s.projectDir.toLowerCase().includes(identifier.toLowerCase()),\n );\n if (match) {\n await this.deps.sessionManager.cancelSession(match.sessionId);\n return `Stopped session ${match.sessionId} (${match.projectName})`;\n }\n\n return `No session found matching \"${identifier}\"`;\n }\n\n private toolGetSessionDetail(input: Record<string, unknown>): string {\n const parsed = GetSessionDetailInput.parse(input);\n const result = this.deps.sessionManager.getResult(parsed.sessionId);\n return JSON.stringify(result, null, 2);\n }\n\n // ---------------------------------------------------------------------------\n // History management\n // ---------------------------------------------------------------------------\n\n /**\n * Trim conversation history to MAX_HISTORY entries.\n * Removes the oldest user+assistant pair from the front to keep pairs aligned.\n */\n private trimHistory(): void {\n while (this.history.length > MAX_HISTORY) {\n // Remove from front — two messages at a time to keep user/assistant pairs\n this.history.splice(0, 2);\n }\n }\n\n /**\n * Return a copy of the conversation history (for debugging / observability).\n */\n getHistory(): MessageParam[] {\n return [...this.history];\n }\n\n /**\n * Stop the orchestrator — clears history and nulls client reference.\n */\n stop(): void {\n this.history = [];\n this.client = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Discord message type (minimal interface for testability)\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal Discord message interface — avoids importing discord.js directly,\n * making the orchestrator testable without full discord.js mocking.\n */\nexport interface DiscordMessageLike {\n author: { id: string; bot: boolean };\n channelId: string;\n content: string;\n channel: {\n send: (content: string) => Promise<unknown>;\n sendTyping: () => Promise<unknown>;\n };\n}\n"]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tests for Orchestrator — LLM agent for #gsd-control channel.
3
+ *
4
+ * Uses a MockAnthropicClient that simulates messages.create() responses,
5
+ * allowing tool execution and conversation flow testing without real API calls.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=orchestrator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.test.d.ts","sourceRoot":"","sources":["../src/orchestrator.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}