@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":"daemon.test.js","sourceRoot":"","sources":["../src/daemon.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, afterEach, before, after } from 'node:test';\nimport assert from 'node:assert/strict';\nimport { mkdtempSync, writeFileSync, readFileSync, rmSync, existsSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { tmpdir, homedir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport { execFileSync, spawn } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\nimport { dirname } from 'node:path';\nimport { resolveConfigPath, loadConfig, validateConfig } from './config.js';\nimport { Logger } from './logger.js';\nimport { Daemon } from './daemon.js';\nimport { CloudRuntime } from './cloud-runtime.js';\nimport { SessionManager } from './session-manager.js';\nimport type { DaemonConfig, LogEntry } from './types.js';\n\n// ---------- helpers ----------\n\nfunction tmpDir(): string {\n return mkdtempSync(join(tmpdir(), `daemon-test-${randomUUID().slice(0, 8)}-`));\n}\n\nconst cleanupDirs: string[] = [];\nafterEach(() => {\n while (cleanupDirs.length) {\n const d = cleanupDirs.pop()!;\n if (existsSync(d)) rmSync(d, { recursive: true, force: true });\n }\n});\n\nit('cloud runtime handles gateway cancel for in-flight requests', async () => {\n let finishTool!: (value: unknown) => void;\n const calls: Array<{ toolName: string; args: Record<string, unknown>; projectAlias?: string }> = [];\n const executor = {\n execute: (toolName: string, args: Record<string, unknown>, projectAlias?: string) => {\n calls.push({ toolName, args, projectAlias });\n if (toolName === 'gsd_cancel') return Promise.resolve({ content: [] });\n return new Promise((resolve) => {\n finishTool = resolve;\n });\n },\n advertisedProjects: async () => [],\n };\n const sent: unknown[] = [];\n const runtime = new CloudRuntime(\n { gateway_url: 'ws://127.0.0.1:1', device_token: 'token', runtime_id: 'runtime' },\n executor as never,\n { info: () => undefined, warn: () => undefined, error: () => undefined, debug: () => undefined } as never,\n );\n (runtime as unknown as { socket: { readyState: number; send: (payload: string) => void } }).socket = {\n readyState: 1,\n send: (payload: string) => sent.push(JSON.parse(payload) as unknown),\n };\n\n const running = (runtime as unknown as { handleMessage: (text: string) => Promise<void> }).handleMessage(JSON.stringify({\n type: 'tool_call',\n requestId: 'request-1',\n toolName: 'gsd_progress',\n args: { projectDir: '/project' },\n }));\n await new Promise((resolve) => setImmediate(resolve));\n\n await (runtime as unknown as { handleMessage: (text: string) => Promise<void> }).handleMessage(JSON.stringify({\n type: 'cancel',\n requestId: 'request-1',\n }));\n finishTool({ late: true });\n await running;\n\n assert.deepEqual(calls.map((call) => call.toolName), ['gsd_progress', 'gsd_cancel']);\n assert.deepEqual(calls[1], { toolName: 'gsd_cancel', args: { projectDir: '/project' }, projectAlias: undefined });\n assert.deepEqual(sent, []);\n});\n\n// ---------- config ----------\n\ndescribe('resolveConfigPath', () => {\n it('prefers explicit CLI path', () => {\n const p = resolveConfigPath('/custom/config.yaml');\n assert.equal(p, '/custom/config.yaml');\n });\n\n it('expands ~ in CLI path', () => {\n const p = resolveConfigPath('~/my-daemon.yaml');\n assert.ok(p.startsWith(homedir()));\n assert.ok(p.endsWith('my-daemon.yaml'));\n });\n\n it('falls back to GSD_DAEMON_CONFIG env var', () => {\n const prev = process.env['GSD_DAEMON_CONFIG'];\n try {\n process.env['GSD_DAEMON_CONFIG'] = '/env/path.yaml';\n const p = resolveConfigPath();\n assert.equal(p, '/env/path.yaml');\n } finally {\n if (prev === undefined) delete process.env['GSD_DAEMON_CONFIG'];\n else process.env['GSD_DAEMON_CONFIG'] = prev;\n }\n });\n\n it('defaults to ~/.gsd/daemon.yaml', () => {\n const prev = process.env['GSD_DAEMON_CONFIG'];\n try {\n delete process.env['GSD_DAEMON_CONFIG'];\n const p = resolveConfigPath();\n assert.equal(p, join(homedir(), '.gsd', 'daemon.yaml'));\n } finally {\n if (prev !== undefined) process.env['GSD_DAEMON_CONFIG'] = prev;\n }\n });\n});\n\ndescribe('loadConfig', () => {\n // Save and clear DISCORD_BOT_TOKEN for this suite — env override interferes with file-token assertions\n let savedToken: string | undefined;\n before(() => {\n savedToken = process.env['DISCORD_BOT_TOKEN'];\n delete process.env['DISCORD_BOT_TOKEN'];\n });\n afterEach(() => {}); // cleanup dirs handled by top-level afterEach\n // Restore after all tests in this suite\n after(() => {\n if (savedToken !== undefined) process.env['DISCORD_BOT_TOKEN'] = savedToken;\n });\n\n it('parses valid YAML config', () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const configPath = join(dir, 'daemon.yaml');\n writeFileSync(configPath, `\ndiscord:\n token: \"fixture\"\n guild_id: \"g1\"\n owner_id: \"o1\"\nprojects:\n scan_roots:\n - ~/projects\n - /absolute/path\nlog:\n file: ~/logs/daemon.log\n level: debug\n max_size_mb: 100\n`);\n const cfg = loadConfig(configPath);\n assert.equal(cfg.discord?.token, 'fixture');\n assert.equal(cfg.discord?.guild_id, 'g1');\n assert.equal(cfg.log.level, 'debug');\n assert.equal(cfg.log.max_size_mb, 100);\n assert.ok(cfg.log.file.startsWith(homedir()));\n assert.ok(cfg.projects.scan_roots[0]!.startsWith(homedir()));\n assert.equal(cfg.projects.scan_roots[1], '/absolute/path');\n });\n\n it('returns defaults when config file is missing', () => {\n const cfg = loadConfig('/nonexistent/path/daemon.yaml');\n assert.equal(cfg.log.level, 'info');\n assert.equal(cfg.log.max_size_mb, 50);\n assert.ok(cfg.log.file.endsWith('daemon.log'));\n assert.deepEqual(cfg.projects.scan_roots, []);\n assert.equal(cfg.discord, undefined);\n });\n\n it('throws on malformed YAML', () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const configPath = join(dir, 'bad.yaml');\n writeFileSync(configPath, ':\\n :\\n bad: [unclosed');\n assert.throws(() => loadConfig(configPath), (err: unknown) => {\n assert.ok(err instanceof Error);\n assert.ok(err.message.includes('Failed to parse YAML'));\n assert.ok(err.message.includes(configPath));\n return true;\n });\n });\n\n it('returns defaults for empty YAML file', () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const configPath = join(dir, 'empty.yaml');\n writeFileSync(configPath, '');\n const cfg = loadConfig(configPath);\n assert.equal(cfg.log.level, 'info');\n assert.equal(cfg.log.max_size_mb, 50);\n assert.deepEqual(cfg.projects.scan_roots, []);\n });\n});\n\ndescribe('validateConfig', () => {\n // Save and clear DISCORD_BOT_TOKEN for tests that don't expect it\n let savedToken: string | undefined;\n before(() => {\n savedToken = process.env['DISCORD_BOT_TOKEN'];\n delete process.env['DISCORD_BOT_TOKEN'];\n });\n after(() => {\n if (savedToken !== undefined) process.env['DISCORD_BOT_TOKEN'] = savedToken;\n });\n\n it('fills remaining defaults for partial config', () => {\n const cfg = validateConfig({ projects: { scan_roots: ['/a'] } });\n assert.equal(cfg.log.level, 'info');\n assert.equal(cfg.log.max_size_mb, 50);\n assert.ok(cfg.log.file.endsWith('daemon.log'));\n assert.deepEqual(cfg.projects.scan_roots, ['/a']);\n assert.equal(cfg.discord, undefined);\n });\n\n it('falls back to info for invalid log level', () => {\n const cfg = validateConfig({ log: { level: 'trace' } });\n assert.equal(cfg.log.level, 'info');\n });\n\n it('returns full defaults for null input', () => {\n const cfg = validateConfig(null);\n assert.equal(cfg.log.level, 'info');\n assert.equal(cfg.log.max_size_mb, 50);\n });\n\n it('returns full defaults for non-object input', () => {\n const cfg = validateConfig('not-an-object');\n assert.equal(cfg.log.level, 'info');\n });\n\n it('expands ~ in log file path', () => {\n const cfg = validateConfig({ log: { file: '~/my.log' } });\n assert.ok(cfg.log.file.startsWith(homedir()));\n assert.ok(cfg.log.file.endsWith('my.log'));\n });\n\n it('overrides discord token from DISCORD_BOT_TOKEN env var', () => {\n const prev = process.env['DISCORD_BOT_TOKEN'];\n try {\n process.env['DISCORD_BOT_TOKEN'] = 'env-override-token';\n const cfg = validateConfig({\n discord: { token: 'fixture', guild_id: 'g1', owner_id: 'o1' },\n });\n assert.equal(cfg.discord?.token, 'env-override-token');\n assert.equal(cfg.discord?.guild_id, 'g1');\n } finally {\n if (prev === undefined) delete process.env['DISCORD_BOT_TOKEN'];\n else process.env['DISCORD_BOT_TOKEN'] = prev;\n }\n });\n\n it('creates discord block from env var even when absent in config', () => {\n const prev = process.env['DISCORD_BOT_TOKEN'];\n try {\n process.env['DISCORD_BOT_TOKEN'] = 'env-only-token';\n const cfg = validateConfig({});\n assert.equal(cfg.discord?.token, 'env-only-token');\n } finally {\n if (prev === undefined) delete process.env['DISCORD_BOT_TOKEN'];\n else process.env['DISCORD_BOT_TOKEN'] = prev;\n }\n });\n});\n\n// ---------- logger ----------\n\ndescribe('Logger', () => {\n it('writes JSON-lines entries to file', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'test.log');\n\n const logger = new Logger({ filePath: logPath, level: 'debug' });\n logger.info('hello world');\n logger.debug('detail', { key: 'val' });\n await logger.close();\n\n const lines = readFileSync(logPath, 'utf-8').trim().split('\\n');\n assert.equal(lines.length, 2);\n\n const entry0: LogEntry = JSON.parse(lines[0]!);\n assert.equal(entry0.level, 'info');\n assert.equal(entry0.msg, 'hello world');\n assert.ok(entry0.ts); // ISO-8601\n\n const entry1: LogEntry = JSON.parse(lines[1]!);\n assert.equal(entry1.level, 'debug');\n assert.equal(entry1.msg, 'detail');\n assert.deepEqual(entry1.data, { key: 'val' });\n });\n\n it('filters entries below configured level', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'filter.log');\n\n const logger = new Logger({ filePath: logPath, level: 'warn' });\n logger.debug('should not appear');\n logger.info('should not appear either');\n logger.warn('visible warning');\n logger.error('visible error');\n await logger.close();\n\n const lines = readFileSync(logPath, 'utf-8').trim().split('\\n');\n assert.equal(lines.length, 2);\n assert.equal((JSON.parse(lines[0]!) as LogEntry).level, 'warn');\n assert.equal((JSON.parse(lines[1]!) as LogEntry).level, 'error');\n });\n\n it('close() resolves after stream ends', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'close.log');\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n logger.info('before close');\n await logger.close();\n\n // File should be readable and contain the entry\n const content = readFileSync(logPath, 'utf-8');\n assert.ok(content.includes('before close'));\n });\n\n it('creates parent directories if they do not exist', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'nested', 'deep', 'test.log');\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n logger.info('nested dir test');\n await logger.close();\n\n assert.ok(existsSync(logPath));\n const content = readFileSync(logPath, 'utf-8');\n assert.ok(content.includes('nested dir test'));\n });\n\n it('does not include data field when not provided', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'nodata.log');\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n logger.info('no extra data');\n await logger.close();\n\n const entry: LogEntry = JSON.parse(readFileSync(logPath, 'utf-8').trim());\n assert.equal(entry.data, undefined);\n // Also verify the raw JSON doesn't contain \"data\" key\n assert.ok(!readFileSync(logPath, 'utf-8').includes('\"data\"'));\n });\n});\n\n// ---------- token safety ----------\n\ndescribe('token safety', () => {\n it('discord token never appears in log output', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'token-safety.log');\n\n // Config with a token\n const prev = process.env['DISCORD_BOT_TOKEN'];\n try {\n process.env['DISCORD_BOT_TOKEN'] = 'super-secret-token-value';\n const cfg = validateConfig({});\n\n const logger = new Logger({ filePath: logPath, level: 'debug' });\n // Log the config object — token must not leak\n logger.info('config loaded', { discord_configured: !!cfg.discord });\n logger.debug('startup complete');\n await logger.close();\n\n const content = readFileSync(logPath, 'utf-8');\n assert.ok(!content.includes('super-secret-token-value'));\n } finally {\n if (prev === undefined) delete process.env['DISCORD_BOT_TOKEN'];\n else process.env['DISCORD_BOT_TOKEN'] = prev;\n }\n });\n});\n\n// ---------- daemon lifecycle ----------\n\n// Resolve the dist/ directory for spawning CLI\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\ndescribe('Daemon', () => {\n it('logs lifecycle events on start and shutdown', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'daemon-lifecycle.log');\n\n const config: DaemonConfig = {\n discord: undefined,\n projects: { scan_roots: ['/a', '/b'] },\n log: { file: logPath, level: 'info', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n const daemon = new Daemon(config, logger);\n\n await daemon.start();\n\n // start() should have logged 'daemon started'\n // shutdown() directly — we override process.exit to prevent test runner from dying\n const origExit = process.exit;\n let exitCode: number | undefined;\n // @ts-expect-error — overriding process.exit for test\n process.exit = (code?: number) => { exitCode = code ?? 0; };\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n\n assert.equal(exitCode, 0);\n\n const content = readFileSync(logPath, 'utf-8');\n const lines = content.trim().split('\\n');\n\n // First line: daemon started\n const startEntry: LogEntry = JSON.parse(lines[0]!);\n assert.equal(startEntry.msg, 'daemon started');\n assert.equal(startEntry.data?.scan_roots, 2);\n assert.equal(startEntry.data?.discord_configured, false);\n\n // Second line: daemon shutting down\n const stopEntry: LogEntry = JSON.parse(lines[1]!);\n assert.equal(stopEntry.msg, 'daemon shutting down');\n });\n\n it('shutdown is idempotent — second call is a no-op', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'idempotent.log');\n\n const config: DaemonConfig = {\n discord: undefined,\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'info', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n const daemon = new Daemon(config, logger);\n\n await daemon.start();\n\n const origExit = process.exit;\n let exitCount = 0;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => { exitCount++; };\n try {\n await daemon.shutdown();\n await daemon.shutdown(); // second call — should be no-op\n } finally {\n process.exit = origExit;\n }\n\n assert.equal(exitCount, 1, 'process.exit should be called exactly once');\n\n const lines = readFileSync(logPath, 'utf-8').trim().split('\\n');\n const shutdownLines = lines.filter(l => {\n const e: LogEntry = JSON.parse(l);\n return e.msg === 'daemon shutting down';\n });\n assert.equal(shutdownLines.length, 1, 'shutdown log should appear exactly once');\n });\n});\n\n// ---------- Health heartbeat ----------\n\ndescribe('Health heartbeat', () => {\n it('logs health entry with expected fields after interval tick', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'health.log');\n\n const config: DaemonConfig = {\n discord: undefined,\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'info', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n // Use 50ms interval for fast test\n const daemon = new Daemon(config, logger, 50);\n\n await daemon.start();\n\n // Wait for at least one health tick\n await new Promise((r) => setTimeout(r, 120));\n\n const origExit = process.exit;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => {};\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n\n const content = readFileSync(logPath, 'utf-8');\n const lines = content.trim().split('\\n');\n const healthLines = lines.filter((l) => {\n const e: LogEntry = JSON.parse(l);\n return e.msg === 'health';\n });\n\n assert.ok(healthLines.length >= 1, 'should have at least one health log entry');\n\n const entry: LogEntry = JSON.parse(healthLines[0]!);\n assert.equal(entry.msg, 'health');\n assert.equal(typeof entry.data?.uptime_s, 'number');\n assert.equal(typeof entry.data?.active_sessions, 'number');\n assert.equal(typeof entry.data?.discord_connected, 'boolean');\n assert.equal(typeof entry.data?.memory_rss_mb, 'number');\n assert.equal(entry.data?.discord_connected, false); // no discord configured\n assert.equal(entry.data?.active_sessions, 0); // no sessions\n });\n\n it('health timer is cleared on shutdown — no lingering intervals', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'health-cleanup.log');\n\n const config: DaemonConfig = {\n discord: undefined,\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'info', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n // Use 50ms interval\n const daemon = new Daemon(config, logger, 50);\n\n await daemon.start();\n\n // Wait for one tick\n await new Promise((r) => setTimeout(r, 80));\n\n const origExit = process.exit;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => {};\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n\n // Count health entries at shutdown\n const contentAtShutdown = readFileSync(logPath, 'utf-8');\n const healthCountAtShutdown = contentAtShutdown\n .trim()\n .split('\\n')\n .filter((l) => JSON.parse(l).msg === 'health').length;\n\n // Wait another interval — no new health entries should appear\n await new Promise((r) => setTimeout(r, 120));\n\n // Re-read (logger is closed, so file shouldn't change)\n const contentAfterWait = readFileSync(logPath, 'utf-8');\n const healthCountAfterWait = contentAfterWait\n .trim()\n .split('\\n')\n .filter((l) => JSON.parse(l).msg === 'health').length;\n\n assert.equal(\n healthCountAfterWait,\n healthCountAtShutdown,\n 'no new health entries should appear after shutdown',\n );\n });\n});\n\ndescribe('CLI integration', () => {\n it('--help prints usage and exits 0', () => {\n const result = execFileSync(\n process.execPath,\n [join(__dirname, 'cli.js'), '--help'],\n { encoding: 'utf-8', timeout: 5000 },\n );\n assert.ok(result.includes('Usage: gsd-daemon'));\n assert.ok(result.includes('--config'));\n assert.ok(result.includes('--verbose'));\n });\n\n it('starts, logs to file, and exits cleanly on SIGTERM', { timeout: 15000 }, async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'integration.log');\n const configPath = join(dir, 'daemon.yaml');\n\n writeFileSync(configPath, `\nprojects:\n scan_roots:\n - /tmp/test-project\nlog:\n file: \"${logPath}\"\n level: info\n max_size_mb: 10\n`);\n\n // Use execFile with a wrapper script approach: spawn, wait for start, SIGTERM, verify\n const exitCode = await new Promise<number>((resolve, reject) => {\n const child = spawn(\n process.execPath,\n [join(__dirname, 'cli.js'), '--config', configPath],\n { stdio: 'ignore' },\n );\n\n let resolved = false;\n child.on('error', (err) => { if (!resolved) { resolved = true; reject(err); } });\n child.on('exit', (code) => { if (!resolved) { resolved = true; resolve(code ?? 1); } });\n\n // Poll for startup, then send SIGTERM\n const poll = setInterval(() => {\n if (existsSync(logPath)) {\n const content = readFileSync(logPath, 'utf-8');\n if (content.includes('daemon started')) {\n clearInterval(poll);\n child.kill('SIGTERM');\n }\n }\n }, 100);\n\n // Safety: kill child if it takes too long\n setTimeout(() => {\n clearInterval(poll);\n if (!resolved) {\n child.kill('SIGKILL');\n resolved = true;\n reject(new Error('timed out waiting for daemon'));\n }\n }, 10000);\n });\n\n assert.equal(exitCode, 0, 'daemon should exit with code 0 on SIGTERM');\n\n // Small delay for filesystem flush\n await new Promise(r => setTimeout(r, 100));\n\n // Verify log file contents\n const finalContent = readFileSync(logPath, 'utf-8');\n assert.ok(finalContent.includes('daemon started'), 'log should contain startup entry');\n assert.ok(finalContent.includes('daemon shutting down'), 'log should contain shutdown entry');\n\n // Verify log entries are valid JSON-lines\n const lines = finalContent.trim().split('\\n');\n for (const line of lines) {\n const entry: LogEntry = JSON.parse(line);\n assert.ok(entry.ts, 'each entry should have a timestamp');\n assert.ok(entry.level, 'each entry should have a level');\n assert.ok(entry.msg, 'each entry should have a message');\n }\n });\n\n it('exits with code 1 on invalid config', () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const configPath = join(dir, 'bad.yaml');\n writeFileSync(configPath, ':\\n :\\n bad: [unclosed');\n\n try {\n execFileSync(\n process.execPath,\n [join(__dirname, 'cli.js'), '--config', configPath],\n { encoding: 'utf-8', timeout: 5000 },\n );\n assert.fail('should have thrown');\n } catch (err: unknown) {\n // execFileSync throws on non-zero exit\n const execErr = err as { status: number; stderr: string };\n assert.equal(execErr.status, 1);\n assert.ok(execErr.stderr.includes('fatal'));\n }\n });\n});\n\n// ---------- Daemon + SessionManager integration ----------\n\ndescribe('Daemon integration', () => {\n it('getSessionManager() returns SessionManager after start()', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'daemon-sm.log');\n\n const config: DaemonConfig = {\n discord: undefined,\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'info', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n const daemon = new Daemon(config, logger);\n\n await daemon.start();\n\n const sm = daemon.getSessionManager();\n assert.ok(sm instanceof SessionManager);\n\n // Clean shutdown\n const origExit = process.exit;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => {};\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n });\n\n it('getSessionManager() throws before start()', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'daemon-nostart.log');\n\n const config: DaemonConfig = {\n discord: undefined,\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'info', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n const daemon = new Daemon(config, logger);\n\n assert.throws(\n () => daemon.getSessionManager(),\n (err: Error) => {\n assert.ok(err.message.includes('Daemon not started'));\n return true;\n }\n );\n\n // Close logger to prevent async write stream from hitting cleaned-up tmpdir\n await logger.close();\n });\n\n it('scanProjects() delegates to scanForProjects with configured roots', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'daemon-scan.log');\n\n // Create a fake project root with a project that has a .git marker\n const scanRoot = join(dir, 'projects');\n mkdirSync(scanRoot);\n const projectDir = join(scanRoot, 'my-project');\n mkdirSync(projectDir);\n mkdirSync(join(projectDir, '.git'));\n\n const config: DaemonConfig = {\n discord: undefined,\n projects: { scan_roots: [scanRoot] },\n log: { file: logPath, level: 'info', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n const daemon = new Daemon(config, logger);\n\n await daemon.start();\n\n const projects = await daemon.scanProjects();\n assert.ok(projects.length >= 1);\n const found = projects.find(p => p.name === 'my-project');\n assert.ok(found);\n assert.ok(found.markers.includes('git'));\n\n // Clean shutdown\n const origExit = process.exit;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => {};\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n });\n\n it('shutdown cleans up sessionManager before closing logger', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'daemon-cleanup.log');\n\n const config: DaemonConfig = {\n discord: undefined,\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'info', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'info' });\n const daemon = new Daemon(config, logger);\n\n await daemon.start();\n\n // Access sessionManager to verify it exists\n const sm = daemon.getSessionManager();\n assert.ok(sm);\n\n // Shutdown — should not throw even though sessionManager has no active sessions\n const origExit = process.exit;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => {};\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n\n // Verify log contains both started and shutting down\n const content = readFileSync(logPath, 'utf-8');\n assert.ok(content.includes('daemon started'));\n assert.ok(content.includes('daemon shutting down'));\n });\n});\n"]}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * DiscordBot — wraps discord.js Client with login/destroy lifecycle, auth guard,
3
+ * and integration with the daemon's SessionManager.
4
+ *
5
+ * Auth model (D016): single Discord user ID allowlist. All non-owner interactions
6
+ * silently ignored; rejections logged at debug level (userId only, no PII).
7
+ */
8
+ import { Client } from 'discord.js';
9
+ import type { DaemonConfig, ProjectInfo } from './types.js';
10
+ import type { Logger } from './logger.js';
11
+ import type { SessionManager } from './session-manager.js';
12
+ import { ChannelManager } from './channel-manager.js';
13
+ import type { EventBridge } from './event-bridge.js';
14
+ /**
15
+ * Auth guard: returns true iff userId matches the configured owner_id.
16
+ * Rejects empty or missing ownerId to fail closed.
17
+ */
18
+ export declare function isAuthorized(userId: string, ownerId: string): boolean;
19
+ /**
20
+ * Validates that all required discord config fields are present.
21
+ * Throws with a descriptive message on the first missing field.
22
+ */
23
+ export declare function validateDiscordConfig(config: DaemonConfig['discord']): asserts config is NonNullable<DaemonConfig['discord']>;
24
+ export interface DiscordBotOptions {
25
+ config: NonNullable<DaemonConfig['discord']>;
26
+ logger: Logger;
27
+ sessionManager: SessionManager;
28
+ /** Optional function to scan for projects (passed from Daemon). */
29
+ scanProjects?: () => Promise<ProjectInfo[]>;
30
+ }
31
+ export declare class DiscordBot {
32
+ private client;
33
+ private destroyed;
34
+ private channelManager;
35
+ private eventBridge;
36
+ private readonly config;
37
+ private readonly logger;
38
+ private readonly sessionManager;
39
+ private readonly scanProjects?;
40
+ constructor(opts: DiscordBotOptions);
41
+ /**
42
+ * Create the discord.js Client, register event handlers, and log in.
43
+ * Throws on login failure — the caller (Daemon) decides whether to continue without the bot.
44
+ */
45
+ login(): Promise<void>;
46
+ /**
47
+ * Destroy the discord.js Client. Idempotent — safe to call multiple times
48
+ * or before login().
49
+ */
50
+ destroy(): Promise<void>;
51
+ /**
52
+ * Lazily create a ChannelManager from the configured guild.
53
+ * Returns null if the client isn't ready or the guild isn't found.
54
+ */
55
+ getChannelManager(): ChannelManager | null;
56
+ /**
57
+ * Return the underlying discord.js Client, or null if not logged in.
58
+ * Used by Daemon to pass to EventBridge as BridgeClient.
59
+ */
60
+ getClient(): Client | null;
61
+ /**
62
+ * Set the EventBridge reference so the bot can dispatch /gsd-verbose commands.
63
+ * Called by Daemon after creating the EventBridge.
64
+ */
65
+ setEventBridge(bridge: EventBridge): void;
66
+ private handleInteraction;
67
+ private handleGsdStart;
68
+ private handleGsdStop;
69
+ }
70
+ //# sourceMappingURL=discord-bot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-bot.d.ts","sourceRoot":"","sources":["../src/discord-bot.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,MAAM,EASP,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,YAAY,EAAkB,WAAW,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAMrD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAGrE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,CAAC,SAAS,CAAC,GAC9B,OAAO,CAAC,MAAM,IAAI,WAAW,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAaxD;AAMD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,WAAW,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,cAAc,CAAC;IAC/B,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CAC7C;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,WAAW,CAA4B;IAE/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAA+B;gBAEjD,IAAI,EAAE,iBAAiB;IAOnC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4G5B;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB9B;;;OAGG;IACH,iBAAiB,IAAI,cAAc,GAAG,IAAI;IAc1C;;;OAGG;IACH,SAAS,IAAI,MAAM,GAAG,IAAI;IAI1B;;;OAGG;IACH,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAQzC,OAAO,CAAC,iBAAiB;YA4EX,cAAc;YAmFd,aAAa;CA8D5B"}
@@ -0,0 +1,433 @@
1
+ /**
2
+ * DiscordBot — wraps discord.js Client with login/destroy lifecycle, auth guard,
3
+ * and integration with the daemon's SessionManager.
4
+ *
5
+ * Auth model (D016): single Discord user ID allowlist. All non-owner interactions
6
+ * silently ignored; rejections logged at debug level (userId only, no PII).
7
+ */
8
+ import { Client, GatewayIntentBits, REST, StringSelectMenuBuilder, ActionRowBuilder, ComponentType, } from 'discord.js';
9
+ import { ChannelManager } from './channel-manager.js';
10
+ import { buildCommands, registerGuildCommands, formatSessionStatus } from './commands.js';
11
+ // ---------------------------------------------------------------------------
12
+ // Pure helpers — exported for testability
13
+ // ---------------------------------------------------------------------------
14
+ /**
15
+ * Auth guard: returns true iff userId matches the configured owner_id.
16
+ * Rejects empty or missing ownerId to fail closed.
17
+ */
18
+ export function isAuthorized(userId, ownerId) {
19
+ if (!ownerId || !userId)
20
+ return false;
21
+ return userId === ownerId;
22
+ }
23
+ /**
24
+ * Validates that all required discord config fields are present.
25
+ * Throws with a descriptive message on the first missing field.
26
+ */
27
+ export function validateDiscordConfig(config) {
28
+ if (!config) {
29
+ throw new Error('Discord config is undefined');
30
+ }
31
+ if (!config.token || config.token.trim() === '') {
32
+ throw new Error('Discord config missing required field: token');
33
+ }
34
+ if (!config.guild_id || config.guild_id.trim() === '') {
35
+ throw new Error('Discord config missing required field: guild_id');
36
+ }
37
+ if (!config.owner_id || config.owner_id.trim() === '') {
38
+ throw new Error('Discord config missing required field: owner_id');
39
+ }
40
+ }
41
+ export class DiscordBot {
42
+ client = null;
43
+ destroyed = false;
44
+ channelManager = null;
45
+ eventBridge = null;
46
+ config;
47
+ logger;
48
+ sessionManager;
49
+ scanProjects;
50
+ constructor(opts) {
51
+ this.config = opts.config;
52
+ this.logger = opts.logger;
53
+ this.sessionManager = opts.sessionManager;
54
+ this.scanProjects = opts.scanProjects;
55
+ }
56
+ /**
57
+ * Create the discord.js Client, register event handlers, and log in.
58
+ * Throws on login failure — the caller (Daemon) decides whether to continue without the bot.
59
+ */
60
+ async login() {
61
+ const client = new Client({
62
+ intents: [
63
+ GatewayIntentBits.Guilds,
64
+ GatewayIntentBits.GuildMessages,
65
+ GatewayIntentBits.MessageContent,
66
+ ],
67
+ });
68
+ client.once('ready', (readyClient) => {
69
+ const guildNames = readyClient.guilds.cache.map((g) => g.name).join(', ');
70
+ this.logger.info('bot ready', {
71
+ username: readyClient.user.tag,
72
+ guilds: guildNames,
73
+ });
74
+ // Register slash commands for the configured guild
75
+ const rest = new REST({ version: '10' }).setToken(this.config.token);
76
+ const commands = buildCommands();
77
+ registerGuildCommands(rest, readyClient.user.id, this.config.guild_id, commands, this.logger).catch((err) => {
78
+ // Should not reach here — registerGuildCommands catches internally
79
+ this.logger.warn('unexpected command registration error', {
80
+ error: err instanceof Error ? err.message : String(err),
81
+ });
82
+ });
83
+ });
84
+ client.on('interactionCreate', (interaction) => {
85
+ this.handleInteraction(interaction);
86
+ });
87
+ // Debug: log all incoming messages at debug level
88
+ client.on('messageCreate', (msg) => {
89
+ this.logger.debug('raw messageCreate', {
90
+ authorId: msg.author.id,
91
+ authorBot: msg.author.bot,
92
+ channelId: msg.channelId,
93
+ contentLength: msg.content.length,
94
+ hasContent: msg.content.length > 0,
95
+ });
96
+ });
97
+ // Reconnection observability — structured logging for all shard lifecycle events (R027)
98
+ client.on('shardError', (error) => {
99
+ this.logger.error('discord shard error', { error: error.message });
100
+ });
101
+ client.on('shardDisconnect', (event, shardId) => {
102
+ this.logger.warn('discord shard disconnected', { shardId, code: event.code });
103
+ });
104
+ client.on('shardReconnecting', (shardId) => {
105
+ this.logger.info('discord shard reconnecting', { shardId });
106
+ });
107
+ client.on('shardResume', (shardId, replayedEvents) => {
108
+ this.logger.info('discord shard resumed', { shardId, replayedEvents });
109
+ });
110
+ client.on('warn', (message) => {
111
+ this.logger.warn('discord warning', { message });
112
+ });
113
+ client.on('error', (error) => {
114
+ this.logger.error('discord error', { error: error.message });
115
+ });
116
+ // Wait for both login AND the 'ready' event.
117
+ // client.login() resolves on WebSocket auth, but the 'ready' event fires
118
+ // asynchronously later. We need 'ready' before getChannelManager() works.
119
+ let readyTimeout;
120
+ let readySettled = false;
121
+ const readyPromise = new Promise((resolve, reject) => {
122
+ readyTimeout = setTimeout(() => {
123
+ if (!readySettled) {
124
+ readySettled = true;
125
+ reject(new Error('Discord ready timeout (30s)'));
126
+ }
127
+ }, 30_000);
128
+ const cleanup = () => {
129
+ if (readyTimeout) {
130
+ clearTimeout(readyTimeout);
131
+ readyTimeout = undefined;
132
+ }
133
+ };
134
+ client.once('ready', () => {
135
+ cleanup();
136
+ if (!readySettled) {
137
+ readySettled = true;
138
+ resolve();
139
+ }
140
+ });
141
+ client.once('error', (err) => {
142
+ cleanup();
143
+ if (!readySettled) {
144
+ readySettled = true;
145
+ reject(err);
146
+ }
147
+ });
148
+ // shardDisconnect fires on fatal gateway errors (e.g. 4014 disallowed intents)
149
+ client.once('shardDisconnect', (event) => {
150
+ cleanup();
151
+ if (!readySettled) {
152
+ readySettled = true;
153
+ reject(new Error(`Shard disconnected: ${event.code}`));
154
+ }
155
+ });
156
+ });
157
+ try {
158
+ await client.login(this.config.token);
159
+ }
160
+ catch (err) {
161
+ // Login itself failed — clean up the ready timer so it doesn't fire as unhandled rejection
162
+ if (readyTimeout) {
163
+ clearTimeout(readyTimeout);
164
+ readyTimeout = undefined;
165
+ }
166
+ readySettled = true;
167
+ throw err;
168
+ }
169
+ await readyPromise;
170
+ this.client = client;
171
+ this.destroyed = false;
172
+ }
173
+ /**
174
+ * Destroy the discord.js Client. Idempotent — safe to call multiple times
175
+ * or before login().
176
+ */
177
+ async destroy() {
178
+ if (this.destroyed || !this.client) {
179
+ this.destroyed = true;
180
+ return;
181
+ }
182
+ try {
183
+ // discord.js destroy() is synchronous but may throw on double-destroy
184
+ this.client.destroy();
185
+ this.logger.info('bot destroyed');
186
+ }
187
+ catch (err) {
188
+ // Swallow cleanup errors — shutdown must not fail
189
+ this.logger.debug('bot destroy error (swallowed)', {
190
+ error: err instanceof Error ? err.message : String(err),
191
+ });
192
+ }
193
+ finally {
194
+ this.client = null;
195
+ this.destroyed = true;
196
+ }
197
+ }
198
+ // ---------------------------------------------------------------------------
199
+ // Public accessors
200
+ // ---------------------------------------------------------------------------
201
+ /**
202
+ * Lazily create a ChannelManager from the configured guild.
203
+ * Returns null if the client isn't ready or the guild isn't found.
204
+ */
205
+ getChannelManager() {
206
+ if (this.channelManager)
207
+ return this.channelManager;
208
+ if (!this.client?.isReady())
209
+ return null;
210
+ const guild = this.client.guilds.cache.get(this.config.guild_id);
211
+ if (!guild) {
212
+ this.logger.warn('guild not found for channel manager', { guildId: this.config.guild_id });
213
+ return null;
214
+ }
215
+ this.channelManager = new ChannelManager({ guild, logger: this.logger });
216
+ return this.channelManager;
217
+ }
218
+ /**
219
+ * Return the underlying discord.js Client, or null if not logged in.
220
+ * Used by Daemon to pass to EventBridge as BridgeClient.
221
+ */
222
+ getClient() {
223
+ return this.client;
224
+ }
225
+ /**
226
+ * Set the EventBridge reference so the bot can dispatch /gsd-verbose commands.
227
+ * Called by Daemon after creating the EventBridge.
228
+ */
229
+ setEventBridge(bridge) {
230
+ this.eventBridge = bridge;
231
+ }
232
+ // ---------------------------------------------------------------------------
233
+ // Private: interaction handling
234
+ // ---------------------------------------------------------------------------
235
+ handleInteraction(interaction) {
236
+ if (!isAuthorized(interaction.user.id, this.config.owner_id)) {
237
+ this.logger.debug('auth rejected', { userId: interaction.user.id });
238
+ return;
239
+ }
240
+ // Only handle chat input (slash) commands
241
+ if (!interaction.isChatInputCommand()) {
242
+ this.logger.debug('non-command interaction', {
243
+ type: interaction.type,
244
+ userId: interaction.user.id,
245
+ });
246
+ return;
247
+ }
248
+ const { commandName } = interaction;
249
+ this.logger.info('command handled', { commandName, userId: interaction.user.id });
250
+ switch (commandName) {
251
+ case 'gsd-status': {
252
+ const sessions = this.sessionManager.getAllSessions();
253
+ const content = formatSessionStatus(sessions);
254
+ interaction.reply({ content, ephemeral: true }).catch((err) => {
255
+ this.logger.warn('gsd-status reply failed', {
256
+ error: err instanceof Error ? err.message : String(err),
257
+ });
258
+ });
259
+ break;
260
+ }
261
+ case 'gsd-start':
262
+ this.handleGsdStart(interaction).catch((err) => {
263
+ this.logger.warn('gsd-start handler error', {
264
+ error: err instanceof Error ? err.message : String(err),
265
+ });
266
+ });
267
+ break;
268
+ case 'gsd-stop':
269
+ this.handleGsdStop(interaction).catch((err) => {
270
+ this.logger.warn('gsd-stop handler error', {
271
+ error: err instanceof Error ? err.message : String(err),
272
+ });
273
+ });
274
+ break;
275
+ case 'gsd-verbose': {
276
+ if (!this.eventBridge) {
277
+ interaction.reply({ content: 'Event bridge not available.', ephemeral: true }).catch((err) => {
278
+ this.logger.warn('gsd-verbose reply failed', {
279
+ error: err instanceof Error ? err.message : String(err),
280
+ });
281
+ });
282
+ break;
283
+ }
284
+ const level = (interaction.options.getString('level') ?? 'default');
285
+ const channelId = interaction.channelId;
286
+ this.eventBridge.getVerbosityManager().setLevel(channelId, level);
287
+ interaction.reply({ content: `Verbosity set to **${level}** for this channel.`, ephemeral: true }).catch((err) => {
288
+ this.logger.warn('gsd-verbose reply failed', {
289
+ error: err instanceof Error ? err.message : String(err),
290
+ });
291
+ });
292
+ break;
293
+ }
294
+ default:
295
+ interaction.reply({ content: 'Unknown command', ephemeral: true }).catch((err) => {
296
+ this.logger.warn('unknown command reply failed', {
297
+ error: err instanceof Error ? err.message : String(err),
298
+ });
299
+ });
300
+ break;
301
+ }
302
+ }
303
+ // ---------------------------------------------------------------------------
304
+ // Private: /gsd-start handler
305
+ // ---------------------------------------------------------------------------
306
+ async handleGsdStart(interaction) {
307
+ await interaction.deferReply({ ephemeral: true });
308
+ this.logger.info('gsd-start: scanning projects');
309
+ if (!this.scanProjects) {
310
+ await interaction.editReply({ content: 'Project scanning not available.' });
311
+ return;
312
+ }
313
+ let projects;
314
+ try {
315
+ projects = await this.scanProjects();
316
+ }
317
+ catch (err) {
318
+ this.logger.error('gsd-start: scan failed', {
319
+ error: err instanceof Error ? err.message : String(err),
320
+ });
321
+ await interaction.editReply({ content: 'Failed to scan for projects.' });
322
+ return;
323
+ }
324
+ if (projects.length === 0) {
325
+ await interaction.editReply({ content: 'No projects found.' });
326
+ return;
327
+ }
328
+ // Discord select menus support max 25 options
329
+ const truncated = projects.slice(0, 25);
330
+ const select = new StringSelectMenuBuilder()
331
+ .setCustomId('gsd-start-select')
332
+ .setPlaceholder('Select a project to start')
333
+ .addOptions(truncated.map((p) => ({
334
+ label: p.name.slice(0, 100), // Discord label max 100 chars
335
+ value: p.path,
336
+ description: p.markers.join(', ').slice(0, 100) || undefined,
337
+ })));
338
+ const row = new ActionRowBuilder().addComponents(select);
339
+ const reply = await interaction.editReply({
340
+ content: `Select a project to start (${truncated.length}${projects.length > 25 ? ` of ${projects.length}` : ''} projects):`,
341
+ components: [row],
342
+ });
343
+ try {
344
+ const collected = await reply.awaitMessageComponent({
345
+ componentType: ComponentType.StringSelect,
346
+ time: 60_000,
347
+ filter: (i) => i.user.id === interaction.user.id,
348
+ });
349
+ const projectPath = collected.values[0];
350
+ this.logger.info('gsd-start: project selected', { projectPath });
351
+ // Defer the update immediately — startSession can take 10-30s to spawn the GSD process,
352
+ // and Discord's component interaction token expires in 3 seconds without deferral.
353
+ await collected.deferUpdate();
354
+ try {
355
+ const sessionId = await this.sessionManager.startSession({ projectDir: projectPath });
356
+ await interaction.editReply({
357
+ content: `✅ Session started for **${projectPath}** (ID: \`${sessionId}\`)`,
358
+ components: [],
359
+ });
360
+ }
361
+ catch (err) {
362
+ const errMsg = err instanceof Error ? err.message : String(err);
363
+ this.logger.error('gsd-start: startSession failed', { error: errMsg, projectPath });
364
+ await interaction.editReply({
365
+ content: `❌ Failed to start session: ${errMsg}`,
366
+ components: [],
367
+ });
368
+ }
369
+ }
370
+ catch {
371
+ // Timeout or other collector error
372
+ this.logger.info('gsd-start: selection timed out');
373
+ await interaction.editReply({ content: 'Selection timed out.', components: [] });
374
+ }
375
+ }
376
+ // ---------------------------------------------------------------------------
377
+ // Private: /gsd-stop handler
378
+ // ---------------------------------------------------------------------------
379
+ async handleGsdStop(interaction) {
380
+ await interaction.deferReply({ ephemeral: true });
381
+ this.logger.info('gsd-stop: listing sessions');
382
+ const allSessions = this.sessionManager.getAllSessions();
383
+ const activeSessions = allSessions.filter((s) => s.status === 'running' || s.status === 'blocked' || s.status === 'starting');
384
+ if (activeSessions.length === 0) {
385
+ await interaction.editReply({ content: 'No active sessions.' });
386
+ return;
387
+ }
388
+ // Discord select menus support max 25 options
389
+ const truncated = activeSessions.slice(0, 25);
390
+ const select = new StringSelectMenuBuilder()
391
+ .setCustomId('gsd-stop-select')
392
+ .setPlaceholder('Select a session to stop')
393
+ .addOptions(truncated.map((s) => ({
394
+ label: `${s.projectName} (${s.status})`.slice(0, 100),
395
+ value: s.sessionId,
396
+ })));
397
+ const row = new ActionRowBuilder().addComponents(select);
398
+ const reply = await interaction.editReply({
399
+ content: `Select a session to stop (${truncated.length} active):`,
400
+ components: [row],
401
+ });
402
+ try {
403
+ const collected = await reply.awaitMessageComponent({
404
+ componentType: ComponentType.StringSelect,
405
+ time: 60_000,
406
+ filter: (i) => i.user.id === interaction.user.id,
407
+ });
408
+ const sessionId = collected.values[0];
409
+ this.logger.info('gsd-stop: session selected', { sessionId });
410
+ try {
411
+ await this.sessionManager.cancelSession(sessionId);
412
+ await collected.update({
413
+ content: `✅ Session \`${sessionId}\` stopped.`,
414
+ components: [],
415
+ });
416
+ }
417
+ catch (err) {
418
+ const errMsg = err instanceof Error ? err.message : String(err);
419
+ this.logger.error('gsd-stop: cancelSession failed', { error: errMsg, sessionId });
420
+ await collected.update({
421
+ content: `❌ Failed to stop session: ${errMsg}`,
422
+ components: [],
423
+ });
424
+ }
425
+ }
426
+ catch {
427
+ // Timeout or other collector error
428
+ this.logger.info('gsd-stop: selection timed out');
429
+ await interaction.editReply({ content: 'Selection timed out.', components: [] });
430
+ }
431
+ }
432
+ }
433
+ //# sourceMappingURL=discord-bot.js.map