@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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cloud-runtime.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-runtime.test.d.ts","sourceRoot":"","sources":["../src/cloud-runtime.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { CloudRuntime } from "./cloud-runtime.js";
4
+ function makeRuntime() {
5
+ return new CloudRuntime({ gateway_url: "ws://127.0.0.1:1", device_token: "fixture", runtime_id: "runtime" }, { execute: async () => ({}), advertisedProjects: async () => [] }, { info: () => undefined, warn: () => undefined, error: () => undefined, debug: () => undefined });
6
+ }
7
+ test("cloud runtime ignores stale socket close events after replacement", () => {
8
+ const runtime = makeRuntime();
9
+ const staleSocket = {};
10
+ const activeSocket = { close: () => undefined };
11
+ const heartbeat = setInterval(() => undefined, 30_000);
12
+ try {
13
+ Object.assign(runtime, {
14
+ socket: activeSocket,
15
+ heartbeat,
16
+ });
17
+ runtime.handleSocketClose(staleSocket);
18
+ const state = runtime;
19
+ assert.equal(state.socket, activeSocket);
20
+ assert.equal(state.heartbeat, heartbeat);
21
+ assert.equal(state.reconnect, undefined);
22
+ }
23
+ finally {
24
+ clearInterval(heartbeat);
25
+ runtime.stop();
26
+ }
27
+ });
28
+ //# sourceMappingURL=cloud-runtime.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-runtime.test.js","sourceRoot":"","sources":["../src/cloud-runtime.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,SAAS,WAAW;IAClB,OAAO,IAAI,YAAY,CACrB,EAAE,WAAW,EAAE,kBAAkB,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,EACnF,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,kBAAkB,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE,EAAW,EAC1E,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,SAAS,EAAW,CAC1G,CAAC;AACJ,CAAC;AAED,IAAI,CAAC,mEAAmE,EAAE,GAAG,EAAE;IAC7E,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,MAAM,YAAY,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC;IAChD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACvD,IAAI,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,OAAoF,EAAE;YAClG,MAAM,EAAE,YAAY;YACpB,SAAS;SACV,CAAC,CAAC;QAEF,OAAuE,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAExG,MAAM,KAAK,GAAG,OAIb,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC3C,CAAC;YAAS,CAAC;QACT,aAAa,CAAC,SAAS,CAAC,CAAC;QACzB,OAAO,CAAC,IAAI,EAAE,CAAC;IACjB,CAAC;AACH,CAAC,CAAC,CAAC","sourcesContent":["import { test } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { CloudRuntime } from \"./cloud-runtime.js\";\n\nfunction makeRuntime(): CloudRuntime {\n return new CloudRuntime(\n { gateway_url: \"ws://127.0.0.1:1\", device_token: \"fixture\", runtime_id: \"runtime\" },\n { execute: async () => ({}), advertisedProjects: async () => [] } as never,\n { info: () => undefined, warn: () => undefined, error: () => undefined, debug: () => undefined } as never,\n );\n}\n\ntest(\"cloud runtime ignores stale socket close events after replacement\", () => {\n const runtime = makeRuntime();\n const staleSocket = {};\n const activeSocket = { close: () => undefined };\n const heartbeat = setInterval(() => undefined, 30_000);\n try {\n Object.assign(runtime as unknown as { socket: unknown; heartbeat: ReturnType<typeof setInterval> }, {\n socket: activeSocket,\n heartbeat,\n });\n\n (runtime as unknown as { handleSocketClose: (socket: unknown) => void }).handleSocketClose(staleSocket);\n\n const state = runtime as unknown as {\n socket: unknown;\n heartbeat: ReturnType<typeof setInterval> | undefined;\n reconnect: ReturnType<typeof setTimeout> | undefined;\n };\n assert.equal(state.socket, activeSocket);\n assert.equal(state.heartbeat, heartbeat);\n assert.equal(state.reconnect, undefined);\n } finally {\n clearInterval(heartbeat);\n runtime.stop();\n }\n});\n"]}
@@ -0,0 +1,3 @@
1
+ export declare function protectCloudDeviceToken(token: string): string;
2
+ export declare function unprotectCloudDeviceToken(value: string): string | undefined;
3
+ //# sourceMappingURL=cloud-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-token.d.ts","sourceRoot":"","sources":["../src/cloud-token.ts"],"names":[],"mappings":"AAMA,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM7D;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAc3E"}
@@ -0,0 +1,37 @@
1
+ import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
2
+ import { homedir, hostname, userInfo } from "node:os";
3
+ const PREFIX = "v1.";
4
+ const ALGORITHM = "aes-256-gcm";
5
+ export function protectCloudDeviceToken(token) {
6
+ const iv = randomBytes(12);
7
+ const cipher = createCipheriv(ALGORITHM, cloudTokenKey(), iv);
8
+ const ciphertext = Buffer.concat([cipher.update(token, "utf8"), cipher.final()]);
9
+ const tag = cipher.getAuthTag();
10
+ return `${PREFIX}${Buffer.concat([iv, tag, ciphertext]).toString("base64url")}`;
11
+ }
12
+ export function unprotectCloudDeviceToken(value) {
13
+ if (!value.startsWith(PREFIX))
14
+ return undefined;
15
+ try {
16
+ const payload = Buffer.from(value.slice(PREFIX.length), "base64url");
17
+ if (payload.length <= 28)
18
+ return undefined;
19
+ const iv = payload.subarray(0, 12);
20
+ const tag = payload.subarray(12, 28);
21
+ const ciphertext = payload.subarray(28);
22
+ const decipher = createDecipheriv(ALGORITHM, cloudTokenKey(), iv);
23
+ decipher.setAuthTag(tag);
24
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
30
+ function cloudTokenKey() {
31
+ const configuredKey = process.env["GSD_CLOUD_TOKEN_KEY"];
32
+ const material = configuredKey && configuredKey.trim()
33
+ ? `env:${configuredKey}`
34
+ : `local:${hostname()}:${userInfo().username}:${homedir()}`;
35
+ return createHash("sha256").update(material, "utf8").digest();
36
+ }
37
+ //# sourceMappingURL=cloud-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-token.js","sourceRoot":"","sources":["../src/cloud-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEtD,MAAM,MAAM,GAAG,KAAK,CAAC;AACrB,MAAM,SAAS,GAAG,aAAa,CAAC;AAEhC,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC;IAC9D,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACjF,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,KAAa;IACrD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC;QACrE,IAAI,OAAO,CAAC,MAAM,IAAI,EAAE;YAAE,OAAO,SAAS,CAAC;QAC3C,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC;QAClE,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,aAAa;IACpB,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,aAAa,IAAI,aAAa,CAAC,IAAI,EAAE;QACpD,CAAC,CAAC,OAAO,aAAa,EAAE;QACxB,CAAC,CAAC,SAAS,QAAQ,EAAE,IAAI,QAAQ,EAAE,CAAC,QAAQ,IAAI,OAAO,EAAE,EAAE,CAAC;IAC9D,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;AAChE,CAAC","sourcesContent":["import { createCipheriv, createDecipheriv, createHash, randomBytes } from \"node:crypto\";\nimport { homedir, hostname, userInfo } from \"node:os\";\n\nconst PREFIX = \"v1.\";\nconst ALGORITHM = \"aes-256-gcm\";\n\nexport function protectCloudDeviceToken(token: string): string {\n const iv = randomBytes(12);\n const cipher = createCipheriv(ALGORITHM, cloudTokenKey(), iv);\n const ciphertext = Buffer.concat([cipher.update(token, \"utf8\"), cipher.final()]);\n const tag = cipher.getAuthTag();\n return `${PREFIX}${Buffer.concat([iv, tag, ciphertext]).toString(\"base64url\")}`;\n}\n\nexport function unprotectCloudDeviceToken(value: string): string | undefined {\n if (!value.startsWith(PREFIX)) return undefined;\n try {\n const payload = Buffer.from(value.slice(PREFIX.length), \"base64url\");\n if (payload.length <= 28) return undefined;\n const iv = payload.subarray(0, 12);\n const tag = payload.subarray(12, 28);\n const ciphertext = payload.subarray(28);\n const decipher = createDecipheriv(ALGORITHM, cloudTokenKey(), iv);\n decipher.setAuthTag(tag);\n return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString(\"utf8\");\n } catch {\n return undefined;\n }\n}\n\nfunction cloudTokenKey(): Buffer {\n const configuredKey = process.env[\"GSD_CLOUD_TOKEN_KEY\"];\n const material = configuredKey && configuredKey.trim()\n ? `env:${configuredKey}`\n : `local:${hostname()}:${userInfo().username}:${homedir()}`;\n return createHash(\"sha256\").update(material, \"utf8\").digest();\n}\n"]}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Slash command definitions, guild-scoped registration, and status formatting.
3
+ *
4
+ * Commands are registered per-guild (not globally) for instant availability.
5
+ * Registration failures are non-fatal — the bot continues without slash commands.
6
+ */
7
+ import { REST, type RESTPostAPIChatInputApplicationCommandsJSONBody } from 'discord.js';
8
+ import type { ManagedSession } from './types.js';
9
+ import type { Logger } from './logger.js';
10
+ /**
11
+ * Build the array of slash command JSON payloads for guild registration.
12
+ */
13
+ export declare function buildCommands(): RESTPostAPIChatInputApplicationCommandsJSONBody[];
14
+ /**
15
+ * Register slash commands for a specific guild via PUT.
16
+ * Non-fatal: logs errors and returns false on failure.
17
+ */
18
+ export declare function registerGuildCommands(rest: REST, clientId: string, guildId: string, commands: RESTPostAPIChatInputApplicationCommandsJSONBody[], logger?: Logger): Promise<boolean>;
19
+ /**
20
+ * Format session list for /gsd-status reply.
21
+ * Shows projectName, status, duration, and cost for each session.
22
+ * Returns 'No active sessions.' if the array is empty.
23
+ */
24
+ export declare function formatSessionStatus(sessions: ManagedSession[]): string;
25
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAEL,IAAI,EAEJ,KAAK,+CAA+C,EACrD,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAM1C;;GAEG;AACH,wBAAgB,aAAa,IAAI,+CAA+C,EAAE,CA8BjF;AAMD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,+CAA+C,EAAE,EAC3D,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAMD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAatE"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Slash command definitions, guild-scoped registration, and status formatting.
3
+ *
4
+ * Commands are registered per-guild (not globally) for instant availability.
5
+ * Registration failures are non-fatal — the bot continues without slash commands.
6
+ */
7
+ import { SlashCommandBuilder, Routes, } from 'discord.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Command definitions
10
+ // ---------------------------------------------------------------------------
11
+ /**
12
+ * Build the array of slash command JSON payloads for guild registration.
13
+ */
14
+ export function buildCommands() {
15
+ return [
16
+ new SlashCommandBuilder()
17
+ .setName('gsd-status')
18
+ .setDescription('Show status of all active GSD sessions')
19
+ .toJSON(),
20
+ new SlashCommandBuilder()
21
+ .setName('gsd-start')
22
+ .setDescription('Start a new GSD session')
23
+ .toJSON(),
24
+ new SlashCommandBuilder()
25
+ .setName('gsd-stop')
26
+ .setDescription('Stop a running GSD session')
27
+ .toJSON(),
28
+ new SlashCommandBuilder()
29
+ .setName('gsd-verbose')
30
+ .setDescription('Set event verbosity level for this channel')
31
+ .addStringOption((option) => option
32
+ .setName('level')
33
+ .setDescription('Verbosity level')
34
+ .setRequired(false)
35
+ .addChoices({ name: 'default', value: 'default' }, { name: 'verbose', value: 'verbose' }, { name: 'quiet', value: 'quiet' }))
36
+ .toJSON(),
37
+ ];
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Guild-scoped registration
41
+ // ---------------------------------------------------------------------------
42
+ /**
43
+ * Register slash commands for a specific guild via PUT.
44
+ * Non-fatal: logs errors and returns false on failure.
45
+ */
46
+ export async function registerGuildCommands(rest, clientId, guildId, commands, logger) {
47
+ try {
48
+ await rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands });
49
+ logger?.info('commands registered', { count: commands.length, guildId });
50
+ return true;
51
+ }
52
+ catch (err) {
53
+ const message = err instanceof Error ? err.message : String(err);
54
+ logger?.warn('command registration failed', {
55
+ guildId,
56
+ error: message,
57
+ });
58
+ return false;
59
+ }
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ // Status formatting
63
+ // ---------------------------------------------------------------------------
64
+ /**
65
+ * Format session list for /gsd-status reply.
66
+ * Shows projectName, status, duration, and cost for each session.
67
+ * Returns 'No active sessions.' if the array is empty.
68
+ */
69
+ export function formatSessionStatus(sessions) {
70
+ if (sessions.length === 0) {
71
+ return 'No active sessions.';
72
+ }
73
+ const lines = sessions.map((s) => {
74
+ const durationMs = Date.now() - s.startTime;
75
+ const durationMin = Math.floor(durationMs / 60_000);
76
+ const cost = s.cost.totalCost.toFixed(4);
77
+ return `• **${s.projectName}** — ${s.status} (${durationMin}m, $${cost})`;
78
+ });
79
+ return lines.join('\n');
80
+ }
81
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Slash command definitions, guild-scoped registration, and status formatting.\n *\n * Commands are registered per-guild (not globally) for instant availability.\n * Registration failures are non-fatal — the bot continues without slash commands.\n */\n\nimport {\n SlashCommandBuilder,\n REST,\n Routes,\n type RESTPostAPIChatInputApplicationCommandsJSONBody,\n} from 'discord.js';\nimport type { ManagedSession } from './types.js';\nimport type { Logger } from './logger.js';\n\n// ---------------------------------------------------------------------------\n// Command definitions\n// ---------------------------------------------------------------------------\n\n/**\n * Build the array of slash command JSON payloads for guild registration.\n */\nexport function buildCommands(): RESTPostAPIChatInputApplicationCommandsJSONBody[] {\n return [\n new SlashCommandBuilder()\n .setName('gsd-status')\n .setDescription('Show status of all active GSD sessions')\n .toJSON(),\n new SlashCommandBuilder()\n .setName('gsd-start')\n .setDescription('Start a new GSD session')\n .toJSON(),\n new SlashCommandBuilder()\n .setName('gsd-stop')\n .setDescription('Stop a running GSD session')\n .toJSON(),\n new SlashCommandBuilder()\n .setName('gsd-verbose')\n .setDescription('Set event verbosity level for this channel')\n .addStringOption((option) =>\n option\n .setName('level')\n .setDescription('Verbosity level')\n .setRequired(false)\n .addChoices(\n { name: 'default', value: 'default' },\n { name: 'verbose', value: 'verbose' },\n { name: 'quiet', value: 'quiet' },\n ),\n )\n .toJSON(),\n ];\n}\n\n// ---------------------------------------------------------------------------\n// Guild-scoped registration\n// ---------------------------------------------------------------------------\n\n/**\n * Register slash commands for a specific guild via PUT.\n * Non-fatal: logs errors and returns false on failure.\n */\nexport async function registerGuildCommands(\n rest: REST,\n clientId: string,\n guildId: string,\n commands: RESTPostAPIChatInputApplicationCommandsJSONBody[],\n logger?: Logger,\n): Promise<boolean> {\n try {\n await rest.put(\n Routes.applicationGuildCommands(clientId, guildId),\n { body: commands },\n );\n logger?.info('commands registered', { count: commands.length, guildId });\n return true;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger?.warn('command registration failed', {\n guildId,\n error: message,\n });\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Status formatting\n// ---------------------------------------------------------------------------\n\n/**\n * Format session list for /gsd-status reply.\n * Shows projectName, status, duration, and cost for each session.\n * Returns 'No active sessions.' if the array is empty.\n */\nexport function formatSessionStatus(sessions: ManagedSession[]): string {\n if (sessions.length === 0) {\n return 'No active sessions.';\n }\n\n const lines = sessions.map((s) => {\n const durationMs = Date.now() - s.startTime;\n const durationMin = Math.floor(durationMs / 60_000);\n const cost = s.cost.totalCost.toFixed(4);\n return `• **${s.projectName}** — ${s.status} (${durationMin}m, $${cost})`;\n });\n\n return lines.join('\\n');\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import type { DaemonConfig } from './types.js';
2
+ /**
3
+ * Resolve the config file path.
4
+ * Priority: explicit CLI arg → GSD_DAEMON_CONFIG env → ~/.gsd/daemon.yaml
5
+ */
6
+ export declare function resolveConfigPath(cliPath?: string): string;
7
+ /**
8
+ * Validate and normalise a raw parsed object into a DaemonConfig.
9
+ * Missing/invalid fields are filled with defaults. Invalid log level falls back to 'info'.
10
+ */
11
+ export declare function validateConfig(raw: unknown): DaemonConfig;
12
+ /**
13
+ * Load and validate a DaemonConfig from a YAML file.
14
+ * If the file doesn't exist, returns defaults. If the file is malformed YAML, throws.
15
+ */
16
+ export declare function loadConfig(configPath: string): DaemonConfig;
17
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,YAAY,CAAC;AA0BzD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CAwFzD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAgB3D"}
@@ -0,0 +1,146 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { resolve } from 'node:path';
4
+ import { parse as parseYaml } from 'yaml';
5
+ import { unprotectCloudDeviceToken } from './cloud-token.js';
6
+ const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error']);
7
+ /** Expand leading ~ to the user's home directory. */
8
+ function expandTilde(p) {
9
+ if (p.startsWith('~/') || p === '~') {
10
+ return resolve(homedir(), p.slice(2) || '.');
11
+ }
12
+ return p;
13
+ }
14
+ /** Default config values when no file is present or fields are missing. */
15
+ function defaults() {
16
+ return {
17
+ cloud: undefined,
18
+ discord: undefined,
19
+ projects: { scan_roots: [] },
20
+ log: {
21
+ file: resolve(homedir(), '.gsd', 'daemon.log'),
22
+ level: 'info',
23
+ max_size_mb: 50,
24
+ },
25
+ };
26
+ }
27
+ /**
28
+ * Resolve the config file path.
29
+ * Priority: explicit CLI arg → GSD_DAEMON_CONFIG env → ~/.gsd/daemon.yaml
30
+ */
31
+ export function resolveConfigPath(cliPath) {
32
+ if (cliPath)
33
+ return expandTilde(cliPath);
34
+ const envPath = process.env['GSD_DAEMON_CONFIG'];
35
+ if (envPath)
36
+ return expandTilde(envPath);
37
+ return resolve(homedir(), '.gsd', 'daemon.yaml');
38
+ }
39
+ /**
40
+ * Validate and normalise a raw parsed object into a DaemonConfig.
41
+ * Missing/invalid fields are filled with defaults. Invalid log level falls back to 'info'.
42
+ */
43
+ export function validateConfig(raw) {
44
+ const def = defaults();
45
+ if (raw == null || typeof raw !== 'object')
46
+ return def;
47
+ const obj = raw;
48
+ // --- discord ---
49
+ let cloud = undefined;
50
+ if (obj['cloud'] != null && typeof obj['cloud'] === 'object') {
51
+ const c = obj['cloud'];
52
+ const encryptedDeviceToken = typeof c['device_token_encrypted'] === 'string'
53
+ ? unprotectCloudDeviceToken(c['device_token_encrypted'])
54
+ : undefined;
55
+ const deviceToken = typeof c['device_token'] === 'string' ? c['device_token'] : encryptedDeviceToken;
56
+ cloud = {
57
+ gateway_url: typeof c['gateway_url'] === 'string' ? c['gateway_url'] : '',
58
+ ...(deviceToken ? { device_token: deviceToken } : {}),
59
+ ...(typeof c['runtime_id'] === 'string' ? { runtime_id: c['runtime_id'] } : {}),
60
+ ...(typeof c['runtime_name'] === 'string' ? { runtime_name: c['runtime_name'] } : {}),
61
+ ...(typeof c['enabled'] === 'boolean' ? { enabled: c['enabled'] } : {}),
62
+ };
63
+ }
64
+ // --- discord ---
65
+ let discord = undefined;
66
+ if (obj['discord'] != null && typeof obj['discord'] === 'object') {
67
+ const d = obj['discord'];
68
+ discord = {
69
+ token: typeof d['token'] === 'string' ? d['token'] : '',
70
+ guild_id: typeof d['guild_id'] === 'string' ? d['guild_id'] : '',
71
+ owner_id: typeof d['owner_id'] === 'string' ? d['owner_id'] : '',
72
+ ...(typeof d['dm_on_blocker'] === 'boolean' ? { dm_on_blocker: d['dm_on_blocker'] } : {}),
73
+ ...(typeof d['control_channel_id'] === 'string' ? { control_channel_id: d['control_channel_id'] } : {}),
74
+ };
75
+ // Parse orchestrator sub-block
76
+ if (d['orchestrator'] != null && typeof d['orchestrator'] === 'object') {
77
+ const orc = d['orchestrator'];
78
+ discord.orchestrator = {
79
+ ...(typeof orc['model'] === 'string' ? { model: orc['model'] } : {}),
80
+ ...(typeof orc['max_tokens'] === 'number' && orc['max_tokens'] > 0 ? { max_tokens: orc['max_tokens'] } : {}),
81
+ };
82
+ }
83
+ }
84
+ // --- projects ---
85
+ let scanRoots = [];
86
+ if (obj['projects'] != null && typeof obj['projects'] === 'object') {
87
+ const p = obj['projects'];
88
+ if (Array.isArray(p['scan_roots'])) {
89
+ scanRoots = p['scan_roots']
90
+ .filter((s) => typeof s === 'string')
91
+ .map(expandTilde);
92
+ }
93
+ }
94
+ // --- log ---
95
+ let logFile = def.log.file;
96
+ let logLevel = def.log.level;
97
+ let maxSizeMb = def.log.max_size_mb;
98
+ if (obj['log'] != null && typeof obj['log'] === 'object') {
99
+ const l = obj['log'];
100
+ if (typeof l['file'] === 'string')
101
+ logFile = expandTilde(l['file']);
102
+ if (typeof l['level'] === 'string') {
103
+ logLevel = VALID_LOG_LEVELS.has(l['level']) ? l['level'] : 'info';
104
+ }
105
+ if (typeof l['max_size_mb'] === 'number' && l['max_size_mb'] > 0) {
106
+ maxSizeMb = l['max_size_mb'];
107
+ }
108
+ }
109
+ // --- env override: DISCORD_BOT_TOKEN ---
110
+ const envToken = process.env['DISCORD_BOT_TOKEN'];
111
+ if (envToken) {
112
+ if (!discord) {
113
+ discord = { token: envToken, guild_id: '', owner_id: '' };
114
+ }
115
+ else {
116
+ discord = { ...discord, token: envToken };
117
+ }
118
+ }
119
+ return {
120
+ cloud,
121
+ discord,
122
+ projects: { scan_roots: scanRoots },
123
+ log: { file: logFile, level: logLevel, max_size_mb: maxSizeMb },
124
+ };
125
+ }
126
+ /**
127
+ * Load and validate a DaemonConfig from a YAML file.
128
+ * If the file doesn't exist, returns defaults. If the file is malformed YAML, throws.
129
+ */
130
+ export function loadConfig(configPath) {
131
+ if (!existsSync(configPath)) {
132
+ // Still apply env-var overrides even when file is missing
133
+ return validateConfig(null);
134
+ }
135
+ const raw = readFileSync(configPath, 'utf-8');
136
+ let parsed;
137
+ try {
138
+ parsed = parseYaml(raw);
139
+ }
140
+ catch (err) {
141
+ const msg = err instanceof Error ? err.message : String(err);
142
+ throw new Error(`Failed to parse YAML config at ${configPath}: ${msg}`);
143
+ }
144
+ return validateConfig(parsed);
145
+ }
146
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { unprotectCloudDeviceToken } from './cloud-token.js';\nimport type { DaemonConfig, LogLevel } from './types.js';\n\nconst VALID_LOG_LEVELS: ReadonlySet<string> = new Set(['debug', 'info', 'warn', 'error']);\n\n/** Expand leading ~ to the user's home directory. */\nfunction expandTilde(p: string): string {\n if (p.startsWith('~/') || p === '~') {\n return resolve(homedir(), p.slice(2) || '.');\n }\n return p;\n}\n\n/** Default config values when no file is present or fields are missing. */\nfunction defaults(): DaemonConfig {\n return {\n cloud: undefined,\n discord: undefined,\n projects: { scan_roots: [] },\n log: {\n file: resolve(homedir(), '.gsd', 'daemon.log'),\n level: 'info',\n max_size_mb: 50,\n },\n };\n}\n\n/**\n * Resolve the config file path.\n * Priority: explicit CLI arg → GSD_DAEMON_CONFIG env → ~/.gsd/daemon.yaml\n */\nexport function resolveConfigPath(cliPath?: string): string {\n if (cliPath) return expandTilde(cliPath);\n const envPath = process.env['GSD_DAEMON_CONFIG'];\n if (envPath) return expandTilde(envPath);\n return resolve(homedir(), '.gsd', 'daemon.yaml');\n}\n\n/**\n * Validate and normalise a raw parsed object into a DaemonConfig.\n * Missing/invalid fields are filled with defaults. Invalid log level falls back to 'info'.\n */\nexport function validateConfig(raw: unknown): DaemonConfig {\n const def = defaults();\n\n if (raw == null || typeof raw !== 'object') return def;\n const obj = raw as Record<string, unknown>;\n\n // --- discord ---\n let cloud: DaemonConfig['cloud'] = undefined;\n if (obj['cloud'] != null && typeof obj['cloud'] === 'object') {\n const c = obj['cloud'] as Record<string, unknown>;\n const encryptedDeviceToken = typeof c['device_token_encrypted'] === 'string'\n ? unprotectCloudDeviceToken(c['device_token_encrypted'])\n : undefined;\n const deviceToken = typeof c['device_token'] === 'string' ? c['device_token'] : encryptedDeviceToken;\n cloud = {\n gateway_url: typeof c['gateway_url'] === 'string' ? c['gateway_url'] : '',\n ...(deviceToken ? { device_token: deviceToken } : {}),\n ...(typeof c['runtime_id'] === 'string' ? { runtime_id: c['runtime_id'] } : {}),\n ...(typeof c['runtime_name'] === 'string' ? { runtime_name: c['runtime_name'] } : {}),\n ...(typeof c['enabled'] === 'boolean' ? { enabled: c['enabled'] } : {}),\n };\n }\n\n // --- discord ---\n let discord: DaemonConfig['discord'] = undefined;\n if (obj['discord'] != null && typeof obj['discord'] === 'object') {\n const d = obj['discord'] as Record<string, unknown>;\n discord = {\n token: typeof d['token'] === 'string' ? d['token'] : '',\n guild_id: typeof d['guild_id'] === 'string' ? d['guild_id'] : '',\n owner_id: typeof d['owner_id'] === 'string' ? d['owner_id'] : '',\n ...(typeof d['dm_on_blocker'] === 'boolean' ? { dm_on_blocker: d['dm_on_blocker'] } : {}),\n ...(typeof d['control_channel_id'] === 'string' ? { control_channel_id: d['control_channel_id'] } : {}),\n };\n\n // Parse orchestrator sub-block\n if (d['orchestrator'] != null && typeof d['orchestrator'] === 'object') {\n const orc = d['orchestrator'] as Record<string, unknown>;\n discord.orchestrator = {\n ...(typeof orc['model'] === 'string' ? { model: orc['model'] } : {}),\n ...(typeof orc['max_tokens'] === 'number' && orc['max_tokens'] > 0 ? { max_tokens: orc['max_tokens'] } : {}),\n };\n }\n }\n\n // --- projects ---\n let scanRoots: string[] = [];\n if (obj['projects'] != null && typeof obj['projects'] === 'object') {\n const p = obj['projects'] as Record<string, unknown>;\n if (Array.isArray(p['scan_roots'])) {\n scanRoots = (p['scan_roots'] as unknown[])\n .filter((s): s is string => typeof s === 'string')\n .map(expandTilde);\n }\n }\n\n // --- log ---\n let logFile = def.log.file;\n let logLevel: LogLevel = def.log.level;\n let maxSizeMb = def.log.max_size_mb;\n\n if (obj['log'] != null && typeof obj['log'] === 'object') {\n const l = obj['log'] as Record<string, unknown>;\n if (typeof l['file'] === 'string') logFile = expandTilde(l['file']);\n if (typeof l['level'] === 'string') {\n logLevel = VALID_LOG_LEVELS.has(l['level']) ? (l['level'] as LogLevel) : 'info';\n }\n if (typeof l['max_size_mb'] === 'number' && l['max_size_mb'] > 0) {\n maxSizeMb = l['max_size_mb'];\n }\n }\n\n // --- env override: DISCORD_BOT_TOKEN ---\n const envToken = process.env['DISCORD_BOT_TOKEN'];\n if (envToken) {\n if (!discord) {\n discord = { token: envToken, guild_id: '', owner_id: '' };\n } else {\n discord = { ...discord, token: envToken };\n }\n }\n\n return {\n cloud,\n discord,\n projects: { scan_roots: scanRoots },\n log: { file: logFile, level: logLevel, max_size_mb: maxSizeMb },\n };\n}\n\n/**\n * Load and validate a DaemonConfig from a YAML file.\n * If the file doesn't exist, returns defaults. If the file is malformed YAML, throws.\n */\nexport function loadConfig(configPath: string): DaemonConfig {\n if (!existsSync(configPath)) {\n // Still apply env-var overrides even when file is missing\n return validateConfig(null);\n }\n\n const raw = readFileSync(configPath, 'utf-8');\n let parsed: unknown;\n try {\n parsed = parseYaml(raw);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse YAML config at ${configPath}: ${msg}`);\n }\n\n return validateConfig(parsed);\n}\n"]}
@@ -0,0 +1,38 @@
1
+ import type { DaemonConfig, ProjectInfo } from './types.js';
2
+ import type { Logger } from './logger.js';
3
+ import { SessionManager } from './session-manager.js';
4
+ import { EventBridge } from './event-bridge.js';
5
+ import { Orchestrator } from './orchestrator.js';
6
+ /**
7
+ * Core daemon class — ties config + logger together with lifecycle management.
8
+ * Registers SIGTERM/SIGINT handlers for clean shutdown.
9
+ */
10
+ export declare class Daemon {
11
+ private readonly config;
12
+ private readonly logger;
13
+ private readonly healthIntervalMs;
14
+ private shuttingDown;
15
+ private keepaliveTimer;
16
+ private healthTimer;
17
+ private readonly onSigterm;
18
+ private readonly onSigint;
19
+ private sessionManager;
20
+ private discordBot;
21
+ private eventBridge;
22
+ private orchestrator;
23
+ private cloudRuntime;
24
+ constructor(config: DaemonConfig, logger: Logger, healthIntervalMs?: number);
25
+ /** Start the daemon: log startup info, register signal handlers, start keepalive. */
26
+ start(): Promise<void>;
27
+ /** Scan configured project roots for project directories. */
28
+ scanProjects(): Promise<ProjectInfo[]>;
29
+ /** Accessor for the session manager (available after start()). */
30
+ getSessionManager(): SessionManager;
31
+ /** Accessor for the event bridge (available after start() with Discord configured). */
32
+ getEventBridge(): EventBridge | undefined;
33
+ /** Accessor for the orchestrator (available after start() with control_channel_id configured). */
34
+ getOrchestrator(): Orchestrator | undefined;
35
+ /** Idempotent shutdown: log, cleanup sessions, close logger, exit. */
36
+ shutdown(): Promise<void>;
37
+ }
38
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIjD;;;GAGG;AACH,qBAAa,MAAM;IAaf,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAdnC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,cAAc,CAA6C;IACnE,OAAO,CAAC,WAAW,CAA6C;IAChE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IACtC,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,YAAY,CAA2B;gBAG5B,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,gBAAgB,GAAE,MAAgB;IAMrD,qFAAqF;IAC/E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwG5B,6DAA6D;IACvD,YAAY,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAI5C,kEAAkE;IAClE,iBAAiB,IAAI,cAAc;IAOnC,uFAAuF;IACvF,cAAc,IAAI,WAAW,GAAG,SAAS;IAIzC,kGAAkG;IAClG,eAAe,IAAI,YAAY,GAAG,SAAS;IAI3C,sEAAsE;IAChE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAqDhC"}