@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,333 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { formatToolStart, formatToolEnd, formatMessage, formatBlocker, formatCompletion, formatError, formatCostUpdate, formatSessionStarted, formatTaskTransition, formatGenericEvent, formatEvent, } from './event-formatter.js';
4
+ // ---------------------------------------------------------------------------
5
+ // Helpers
6
+ // ---------------------------------------------------------------------------
7
+ function embedColor(fe) {
8
+ return fe.embed?.data.color ?? null;
9
+ }
10
+ function embedTitle(fe) {
11
+ return fe.embed?.data.title;
12
+ }
13
+ function embedDescription(fe) {
14
+ return fe.embed?.data.description;
15
+ }
16
+ // ---------------------------------------------------------------------------
17
+ // formatToolStart
18
+ // ---------------------------------------------------------------------------
19
+ describe('formatToolStart', () => {
20
+ it('produces grey embed with tool name', () => {
21
+ const result = formatToolStart({ type: 'tool_execution_start', name: 'read_file' });
22
+ assert.ok(result.content.includes('read_file'));
23
+ assert.equal(embedColor(result), 0x95a5a6); // grey
24
+ assert.ok(embedTitle(result)?.includes('read_file'));
25
+ });
26
+ it('handles missing name gracefully', () => {
27
+ const result = formatToolStart({ type: 'tool_execution_start' });
28
+ assert.ok(result.content.includes('unknown'));
29
+ });
30
+ it('includes input in description when present', () => {
31
+ const result = formatToolStart({ type: 'tool_execution_start', name: 'bash', input: 'ls -la' });
32
+ assert.ok(embedDescription(result)?.includes('ls -la'));
33
+ });
34
+ });
35
+ // ---------------------------------------------------------------------------
36
+ // formatToolEnd
37
+ // ---------------------------------------------------------------------------
38
+ describe('formatToolEnd', () => {
39
+ it('shows success icon for normal completion', () => {
40
+ const result = formatToolEnd({ type: 'tool_execution_end', name: 'read_file', output: 'done' });
41
+ assert.ok(result.content.includes('✅'));
42
+ assert.equal(embedColor(result), 0x95a5a6); // grey
43
+ });
44
+ it('shows error icon and red color for errored tool', () => {
45
+ const result = formatToolEnd({ type: 'tool_execution_end', name: 'bash', isError: true });
46
+ assert.ok(result.content.includes('❌'));
47
+ assert.equal(embedColor(result), 0xe74c3c); // red
48
+ });
49
+ it('includes duration when present', () => {
50
+ const result = formatToolEnd({ type: 'tool_execution_end', name: 'bash', duration: 3500 });
51
+ assert.ok(result.embed?.data.footer?.text?.includes('3.5s'));
52
+ });
53
+ });
54
+ // ---------------------------------------------------------------------------
55
+ // formatMessage
56
+ // ---------------------------------------------------------------------------
57
+ describe('formatMessage', () => {
58
+ it('extracts text from content blocks', () => {
59
+ const result = formatMessage({
60
+ type: 'message',
61
+ content: [{ type: 'text', text: 'Hello world' }],
62
+ });
63
+ assert.ok(embedDescription(result)?.includes('Hello world'));
64
+ assert.equal(embedColor(result), 0x3498db); // blue
65
+ });
66
+ it('falls back to message field when content is a string', () => {
67
+ const result = formatMessage({ type: 'message', message: 'plain text' });
68
+ assert.ok(embedDescription(result)?.includes('plain text'));
69
+ });
70
+ it('handles empty content blocks', () => {
71
+ const result = formatMessage({ type: 'message', content: [] });
72
+ assert.ok(result.content.includes('empty message'));
73
+ assert.equal(result.embed, undefined);
74
+ });
75
+ it('handles null content gracefully', () => {
76
+ const result = formatMessage({ type: 'message' });
77
+ assert.ok(result.content.includes('empty message'));
78
+ });
79
+ });
80
+ // ---------------------------------------------------------------------------
81
+ // formatBlocker — select
82
+ // ---------------------------------------------------------------------------
83
+ describe('formatBlocker', () => {
84
+ it('produces ActionRow with numbered buttons for select', () => {
85
+ const blocker = {
86
+ id: 'req-1',
87
+ method: 'select',
88
+ message: 'Choose an option',
89
+ event: {
90
+ type: 'extension_ui_request',
91
+ id: 'req-1',
92
+ method: 'select',
93
+ title: 'Choose',
94
+ options: ['Option A', 'Option B', 'Option C'],
95
+ },
96
+ };
97
+ const result = formatBlocker(blocker, '12345');
98
+ assert.ok(result.content.includes('<@12345>'));
99
+ assert.equal(embedColor(result), 0xf1c40f); // yellow
100
+ assert.ok(result.components);
101
+ assert.ok(result.components.length > 0);
102
+ // Check buttons
103
+ const row = result.components[0];
104
+ const buttons = row.components;
105
+ assert.equal(buttons.length, 3);
106
+ });
107
+ it('handles empty options array for select', () => {
108
+ const blocker = {
109
+ id: 'req-2',
110
+ method: 'select',
111
+ message: 'Pick one',
112
+ event: {
113
+ type: 'extension_ui_request',
114
+ id: 'req-2',
115
+ method: 'select',
116
+ title: 'Pick',
117
+ options: [],
118
+ },
119
+ };
120
+ const result = formatBlocker(blocker, '12345');
121
+ // No components when no options
122
+ assert.equal(result.components, undefined);
123
+ // Embed should show 'No options'
124
+ const fields = result.embed?.data.fields;
125
+ assert.ok(fields?.some((f) => f.value.includes('No options')));
126
+ });
127
+ it('produces Yes/No buttons for confirm', () => {
128
+ const blocker = {
129
+ id: 'req-3',
130
+ method: 'confirm',
131
+ message: 'Are you sure?',
132
+ event: {
133
+ type: 'extension_ui_request',
134
+ id: 'req-3',
135
+ method: 'confirm',
136
+ title: 'Confirm',
137
+ message: 'This will delete everything',
138
+ },
139
+ };
140
+ const result = formatBlocker(blocker, '99999');
141
+ assert.ok(result.components);
142
+ assert.equal(result.components.length, 1);
143
+ const buttons = result.components[0].components;
144
+ assert.equal(buttons.length, 2);
145
+ });
146
+ it('produces text instructions for input method', () => {
147
+ const blocker = {
148
+ id: 'req-4',
149
+ method: 'input',
150
+ message: 'Enter your name',
151
+ event: {
152
+ type: 'extension_ui_request',
153
+ id: 'req-4',
154
+ method: 'input',
155
+ title: 'Name',
156
+ placeholder: 'John Doe',
157
+ },
158
+ };
159
+ const result = formatBlocker(blocker, '12345');
160
+ // No interactive buttons for input — text instructions only
161
+ assert.equal(result.components, undefined);
162
+ const fields = result.embed?.data.fields;
163
+ assert.ok(fields?.some((f) => f.value.includes('Reply in this channel')));
164
+ });
165
+ it('produces text instructions for editor method', () => {
166
+ const blocker = {
167
+ id: 'req-5',
168
+ method: 'editor',
169
+ message: 'Edit the config',
170
+ event: {
171
+ type: 'extension_ui_request',
172
+ id: 'req-5',
173
+ method: 'editor',
174
+ title: 'Config',
175
+ prefill: 'key: value',
176
+ },
177
+ };
178
+ const result = formatBlocker(blocker, '12345');
179
+ assert.equal(result.components, undefined);
180
+ const fields = result.embed?.data.fields;
181
+ assert.ok(fields?.some((f) => f.value.includes('Reply in this channel')));
182
+ assert.ok(fields?.some((f) => f.value.includes('key: value')));
183
+ });
184
+ });
185
+ // ---------------------------------------------------------------------------
186
+ // formatCompletion
187
+ // ---------------------------------------------------------------------------
188
+ describe('formatCompletion', () => {
189
+ it('shows green for completed', () => {
190
+ const result = formatCompletion({ type: 'execution_complete', status: 'completed' });
191
+ assert.equal(embedColor(result), 0x2ecc71); // green
192
+ assert.ok(result.content.includes('🏁'));
193
+ });
194
+ it('shows red for error status', () => {
195
+ const result = formatCompletion({
196
+ type: 'execution_complete',
197
+ status: 'error',
198
+ reason: 'Out of tokens',
199
+ });
200
+ assert.equal(embedColor(result), 0xe74c3c); // red
201
+ assert.ok(embedDescription(result)?.includes('Out of tokens'));
202
+ });
203
+ it('includes stats when present', () => {
204
+ const result = formatCompletion({
205
+ type: 'execution_complete',
206
+ status: 'completed',
207
+ stats: { cost: 0.42, tokens: { total: 10000 } },
208
+ });
209
+ const fields = result.embed?.data.fields;
210
+ assert.ok(fields?.some((f) => f.value.includes('$0.42')));
211
+ assert.ok(fields?.some((f) => f.value.includes('10,000')));
212
+ });
213
+ });
214
+ // ---------------------------------------------------------------------------
215
+ // formatError
216
+ // ---------------------------------------------------------------------------
217
+ describe('formatError', () => {
218
+ it('includes session ID and error message', () => {
219
+ const result = formatError('sess-abc', 'Connection refused');
220
+ assert.equal(embedColor(result), 0xe74c3c); // red
221
+ assert.ok(embedDescription(result)?.includes('Connection refused'));
222
+ assert.ok(result.embed?.data.footer?.text?.includes('sess-abc'));
223
+ });
224
+ });
225
+ // ---------------------------------------------------------------------------
226
+ // formatCostUpdate
227
+ // ---------------------------------------------------------------------------
228
+ describe('formatCostUpdate', () => {
229
+ it('formats cumulative cost', () => {
230
+ const result = formatCostUpdate({
231
+ type: 'cost_update',
232
+ cumulativeCost: 1.23,
233
+ tokens: { input: 5000, output: 2000 },
234
+ });
235
+ assert.ok(result.content.includes('$1.23'));
236
+ assert.equal(embedColor(result), 0x3498db); // blue
237
+ });
238
+ it('handles zero cost', () => {
239
+ const result = formatCostUpdate({
240
+ type: 'cost_update',
241
+ cumulativeCost: 0,
242
+ tokens: { input: 0, output: 0 },
243
+ });
244
+ assert.ok(result.content.includes('$0.0000'));
245
+ });
246
+ });
247
+ // ---------------------------------------------------------------------------
248
+ // formatSessionStarted
249
+ // ---------------------------------------------------------------------------
250
+ describe('formatSessionStarted', () => {
251
+ it('includes project name', () => {
252
+ const result = formatSessionStarted('my-project');
253
+ assert.ok(result.content.includes('my-project'));
254
+ assert.ok(embedDescription(result)?.includes('my-project'));
255
+ assert.equal(embedColor(result), 0x3498db); // blue
256
+ });
257
+ });
258
+ // ---------------------------------------------------------------------------
259
+ // formatTaskTransition
260
+ // ---------------------------------------------------------------------------
261
+ describe('formatTaskTransition', () => {
262
+ it('shows complete icon for completed tasks', () => {
263
+ const result = formatTaskTransition({
264
+ type: 'task_transition',
265
+ taskId: 'T01',
266
+ sliceId: 'S01',
267
+ status: 'complete',
268
+ });
269
+ assert.ok(result.content.includes('✅'));
270
+ assert.equal(embedColor(result), 0x2ecc71); // green
271
+ });
272
+ it('shows error icon for errored tasks', () => {
273
+ const result = formatTaskTransition({
274
+ type: 'task_transition',
275
+ taskId: 'T02',
276
+ status: 'error',
277
+ });
278
+ assert.ok(result.content.includes('❌'));
279
+ assert.equal(embedColor(result), 0xe74c3c); // red
280
+ });
281
+ });
282
+ // ---------------------------------------------------------------------------
283
+ // formatGenericEvent
284
+ // ---------------------------------------------------------------------------
285
+ describe('formatGenericEvent', () => {
286
+ it('renders unknown event type as grey embed', () => {
287
+ const result = formatGenericEvent({ type: 'some_custom_event', data: 'hello' });
288
+ assert.equal(embedColor(result), 0x95a5a6); // grey
289
+ assert.ok(embedTitle(result)?.includes('some_custom_event'));
290
+ });
291
+ it('handles events with no extra fields', () => {
292
+ const result = formatGenericEvent({ type: 'bare_event' });
293
+ assert.ok(result.content.includes('bare_event'));
294
+ });
295
+ });
296
+ // ---------------------------------------------------------------------------
297
+ // formatEvent — dispatch
298
+ // ---------------------------------------------------------------------------
299
+ describe('formatEvent', () => {
300
+ it('dispatches tool_execution_start', () => {
301
+ const result = formatEvent({ type: 'tool_execution_start', name: 'read' });
302
+ assert.ok(result.content.includes('🔧'));
303
+ });
304
+ it('dispatches execution_complete', () => {
305
+ const result = formatEvent({ type: 'execution_complete', status: 'completed' });
306
+ assert.ok(result.content.includes('🏁'));
307
+ });
308
+ it('falls back to generic for unknown types', () => {
309
+ const result = formatEvent({ type: 'totally_unknown' });
310
+ assert.ok(result.content.includes('📡'));
311
+ });
312
+ it('dispatches cost_update', () => {
313
+ const result = formatEvent({ type: 'cost_update', cumulativeCost: 0.5 });
314
+ assert.ok(result.content.includes('💰'));
315
+ });
316
+ it('dispatches message types', () => {
317
+ for (const type of ['message_start', 'message_end', 'message']) {
318
+ const result = formatEvent({ type, message: 'hi' });
319
+ assert.ok(result.content.includes('💬'), `Failed for type: ${type}`);
320
+ }
321
+ });
322
+ // Negative: missing type field
323
+ it('handles event with missing type gracefully', () => {
324
+ const result = formatEvent({});
325
+ assert.ok(result.content); // should not throw
326
+ });
327
+ // Negative: null fields
328
+ it('handles event with null fields gracefully', () => {
329
+ const result = formatEvent({ type: 'tool_execution_start', name: null });
330
+ assert.ok(result.content);
331
+ });
332
+ });
333
+ //# sourceMappingURL=event-formatter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-formatter.test.js","sourceRoot":"","sources":["../src/event-formatter.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from 'node:test';\nimport assert from 'node:assert/strict';\nimport { EmbedBuilder, ActionRowBuilder, ButtonBuilder } from 'discord.js';\nimport type { RpcExtensionUIRequest, SdkAgentEvent } from '@opengsd/contracts';\nimport type { PendingBlocker, FormattedEvent } from './types.js';\nimport {\n formatToolStart,\n formatToolEnd,\n formatMessage,\n formatBlocker,\n formatCompletion,\n formatError,\n formatCostUpdate,\n formatSessionStarted,\n formatTaskTransition,\n formatGenericEvent,\n formatEvent,\n} from './event-formatter.js';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction embedColor(fe: FormattedEvent): number | null {\n return fe.embed?.data.color ?? null;\n}\n\nfunction embedTitle(fe: FormattedEvent): string | undefined {\n return fe.embed?.data.title;\n}\n\nfunction embedDescription(fe: FormattedEvent): string | undefined {\n return fe.embed?.data.description;\n}\n\n// ---------------------------------------------------------------------------\n// formatToolStart\n// ---------------------------------------------------------------------------\n\ndescribe('formatToolStart', () => {\n it('produces grey embed with tool name', () => {\n const result = formatToolStart({ type: 'tool_execution_start', name: 'read_file' });\n assert.ok(result.content.includes('read_file'));\n assert.equal(embedColor(result), 0x95a5a6); // grey\n assert.ok(embedTitle(result)?.includes('read_file'));\n });\n\n it('handles missing name gracefully', () => {\n const result = formatToolStart({ type: 'tool_execution_start' });\n assert.ok(result.content.includes('unknown'));\n });\n\n it('includes input in description when present', () => {\n const result = formatToolStart({ type: 'tool_execution_start', name: 'bash', input: 'ls -la' });\n assert.ok(embedDescription(result)?.includes('ls -la'));\n });\n});\n\n// ---------------------------------------------------------------------------\n// formatToolEnd\n// ---------------------------------------------------------------------------\n\ndescribe('formatToolEnd', () => {\n it('shows success icon for normal completion', () => {\n const result = formatToolEnd({ type: 'tool_execution_end', name: 'read_file', output: 'done' });\n assert.ok(result.content.includes('✅'));\n assert.equal(embedColor(result), 0x95a5a6); // grey\n });\n\n it('shows error icon and red color for errored tool', () => {\n const result = formatToolEnd({ type: 'tool_execution_end', name: 'bash', isError: true });\n assert.ok(result.content.includes('❌'));\n assert.equal(embedColor(result), 0xe74c3c); // red\n });\n\n it('includes duration when present', () => {\n const result = formatToolEnd({ type: 'tool_execution_end', name: 'bash', duration: 3500 });\n assert.ok(result.embed?.data.footer?.text?.includes('3.5s'));\n });\n});\n\n// ---------------------------------------------------------------------------\n// formatMessage\n// ---------------------------------------------------------------------------\n\ndescribe('formatMessage', () => {\n it('extracts text from content blocks', () => {\n const result = formatMessage({\n type: 'message',\n content: [{ type: 'text', text: 'Hello world' }],\n });\n assert.ok(embedDescription(result)?.includes('Hello world'));\n assert.equal(embedColor(result), 0x3498db); // blue\n });\n\n it('falls back to message field when content is a string', () => {\n const result = formatMessage({ type: 'message', message: 'plain text' });\n assert.ok(embedDescription(result)?.includes('plain text'));\n });\n\n it('handles empty content blocks', () => {\n const result = formatMessage({ type: 'message', content: [] });\n assert.ok(result.content.includes('empty message'));\n assert.equal(result.embed, undefined);\n });\n\n it('handles null content gracefully', () => {\n const result = formatMessage({ type: 'message' });\n assert.ok(result.content.includes('empty message'));\n });\n});\n\n// ---------------------------------------------------------------------------\n// formatBlocker — select\n// ---------------------------------------------------------------------------\n\ndescribe('formatBlocker', () => {\n it('produces ActionRow with numbered buttons for select', () => {\n const blocker: PendingBlocker = {\n id: 'req-1',\n method: 'select',\n message: 'Choose an option',\n event: {\n type: 'extension_ui_request',\n id: 'req-1',\n method: 'select',\n title: 'Choose',\n options: ['Option A', 'Option B', 'Option C'],\n },\n };\n\n const result = formatBlocker(blocker, '12345');\n assert.ok(result.content.includes('<@12345>'));\n assert.equal(embedColor(result), 0xf1c40f); // yellow\n assert.ok(result.components);\n assert.ok(result.components!.length > 0);\n\n // Check buttons\n const row = result.components![0];\n const buttons = row.components;\n assert.equal(buttons.length, 3);\n });\n\n it('handles empty options array for select', () => {\n const blocker: PendingBlocker = {\n id: 'req-2',\n method: 'select',\n message: 'Pick one',\n event: {\n type: 'extension_ui_request',\n id: 'req-2',\n method: 'select',\n title: 'Pick',\n options: [],\n },\n };\n\n const result = formatBlocker(blocker, '12345');\n // No components when no options\n assert.equal(result.components, undefined);\n // Embed should show 'No options'\n const fields = result.embed?.data.fields;\n assert.ok(fields?.some((f) => f.value.includes('No options')));\n });\n\n it('produces Yes/No buttons for confirm', () => {\n const blocker: PendingBlocker = {\n id: 'req-3',\n method: 'confirm',\n message: 'Are you sure?',\n event: {\n type: 'extension_ui_request',\n id: 'req-3',\n method: 'confirm',\n title: 'Confirm',\n message: 'This will delete everything',\n },\n };\n\n const result = formatBlocker(blocker, '99999');\n assert.ok(result.components);\n assert.equal(result.components!.length, 1);\n const buttons = result.components![0].components;\n assert.equal(buttons.length, 2);\n });\n\n it('produces text instructions for input method', () => {\n const blocker: PendingBlocker = {\n id: 'req-4',\n method: 'input',\n message: 'Enter your name',\n event: {\n type: 'extension_ui_request',\n id: 'req-4',\n method: 'input',\n title: 'Name',\n placeholder: 'John Doe',\n },\n };\n\n const result = formatBlocker(blocker, '12345');\n // No interactive buttons for input — text instructions only\n assert.equal(result.components, undefined);\n const fields = result.embed?.data.fields;\n assert.ok(fields?.some((f) => f.value.includes('Reply in this channel')));\n });\n\n it('produces text instructions for editor method', () => {\n const blocker: PendingBlocker = {\n id: 'req-5',\n method: 'editor',\n message: 'Edit the config',\n event: {\n type: 'extension_ui_request',\n id: 'req-5',\n method: 'editor',\n title: 'Config',\n prefill: 'key: value',\n },\n };\n\n const result = formatBlocker(blocker, '12345');\n assert.equal(result.components, undefined);\n const fields = result.embed?.data.fields;\n assert.ok(fields?.some((f) => f.value.includes('Reply in this channel')));\n assert.ok(fields?.some((f) => f.value.includes('key: value')));\n });\n});\n\n// ---------------------------------------------------------------------------\n// formatCompletion\n// ---------------------------------------------------------------------------\n\ndescribe('formatCompletion', () => {\n it('shows green for completed', () => {\n const result = formatCompletion({ type: 'execution_complete', status: 'completed' });\n assert.equal(embedColor(result), 0x2ecc71); // green\n assert.ok(result.content.includes('🏁'));\n });\n\n it('shows red for error status', () => {\n const result = formatCompletion({\n type: 'execution_complete',\n status: 'error',\n reason: 'Out of tokens',\n });\n assert.equal(embedColor(result), 0xe74c3c); // red\n assert.ok(embedDescription(result)?.includes('Out of tokens'));\n });\n\n it('includes stats when present', () => {\n const result = formatCompletion({\n type: 'execution_complete',\n status: 'completed',\n stats: { cost: 0.42, tokens: { total: 10000 } },\n });\n const fields = result.embed?.data.fields;\n assert.ok(fields?.some((f) => f.value.includes('$0.42')));\n assert.ok(fields?.some((f) => f.value.includes('10,000')));\n });\n});\n\n// ---------------------------------------------------------------------------\n// formatError\n// ---------------------------------------------------------------------------\n\ndescribe('formatError', () => {\n it('includes session ID and error message', () => {\n const result = formatError('sess-abc', 'Connection refused');\n assert.equal(embedColor(result), 0xe74c3c); // red\n assert.ok(embedDescription(result)?.includes('Connection refused'));\n assert.ok(result.embed?.data.footer?.text?.includes('sess-abc'));\n });\n});\n\n// ---------------------------------------------------------------------------\n// formatCostUpdate\n// ---------------------------------------------------------------------------\n\ndescribe('formatCostUpdate', () => {\n it('formats cumulative cost', () => {\n const result = formatCostUpdate({\n type: 'cost_update',\n cumulativeCost: 1.23,\n tokens: { input: 5000, output: 2000 },\n });\n assert.ok(result.content.includes('$1.23'));\n assert.equal(embedColor(result), 0x3498db); // blue\n });\n\n it('handles zero cost', () => {\n const result = formatCostUpdate({\n type: 'cost_update',\n cumulativeCost: 0,\n tokens: { input: 0, output: 0 },\n });\n assert.ok(result.content.includes('$0.0000'));\n });\n});\n\n// ---------------------------------------------------------------------------\n// formatSessionStarted\n// ---------------------------------------------------------------------------\n\ndescribe('formatSessionStarted', () => {\n it('includes project name', () => {\n const result = formatSessionStarted('my-project');\n assert.ok(result.content.includes('my-project'));\n assert.ok(embedDescription(result)?.includes('my-project'));\n assert.equal(embedColor(result), 0x3498db); // blue\n });\n});\n\n// ---------------------------------------------------------------------------\n// formatTaskTransition\n// ---------------------------------------------------------------------------\n\ndescribe('formatTaskTransition', () => {\n it('shows complete icon for completed tasks', () => {\n const result = formatTaskTransition({\n type: 'task_transition',\n taskId: 'T01',\n sliceId: 'S01',\n status: 'complete',\n });\n assert.ok(result.content.includes('✅'));\n assert.equal(embedColor(result), 0x2ecc71); // green\n });\n\n it('shows error icon for errored tasks', () => {\n const result = formatTaskTransition({\n type: 'task_transition',\n taskId: 'T02',\n status: 'error',\n });\n assert.ok(result.content.includes('❌'));\n assert.equal(embedColor(result), 0xe74c3c); // red\n });\n});\n\n// ---------------------------------------------------------------------------\n// formatGenericEvent\n// ---------------------------------------------------------------------------\n\ndescribe('formatGenericEvent', () => {\n it('renders unknown event type as grey embed', () => {\n const result = formatGenericEvent({ type: 'some_custom_event', data: 'hello' });\n assert.equal(embedColor(result), 0x95a5a6); // grey\n assert.ok(embedTitle(result)?.includes('some_custom_event'));\n });\n\n it('handles events with no extra fields', () => {\n const result = formatGenericEvent({ type: 'bare_event' });\n assert.ok(result.content.includes('bare_event'));\n });\n});\n\n// ---------------------------------------------------------------------------\n// formatEvent — dispatch\n// ---------------------------------------------------------------------------\n\ndescribe('formatEvent', () => {\n it('dispatches tool_execution_start', () => {\n const result = formatEvent({ type: 'tool_execution_start', name: 'read' });\n assert.ok(result.content.includes('🔧'));\n });\n\n it('dispatches execution_complete', () => {\n const result = formatEvent({ type: 'execution_complete', status: 'completed' });\n assert.ok(result.content.includes('🏁'));\n });\n\n it('falls back to generic for unknown types', () => {\n const result = formatEvent({ type: 'totally_unknown' });\n assert.ok(result.content.includes('📡'));\n });\n\n it('dispatches cost_update', () => {\n const result = formatEvent({ type: 'cost_update', cumulativeCost: 0.5 });\n assert.ok(result.content.includes('💰'));\n });\n\n it('dispatches message types', () => {\n for (const type of ['message_start', 'message_end', 'message']) {\n const result = formatEvent({ type, message: 'hi' });\n assert.ok(result.content.includes('💬'), `Failed for type: ${type}`);\n }\n });\n\n // Negative: missing type field\n it('handles event with missing type gracefully', () => {\n const result = formatEvent({} as SdkAgentEvent);\n assert.ok(result.content); // should not throw\n });\n\n // Negative: null fields\n it('handles event with null fields gracefully', () => {\n const result = formatEvent({ type: 'tool_execution_start', name: null } as unknown as SdkAgentEvent);\n assert.ok(result.content);\n });\n});\n"]}
@@ -0,0 +1,25 @@
1
+ export type { DaemonConfig, LogLevel, LogEntry, SessionStatus, ManagedSession, PendingBlocker, CostAccumulator, ProjectInfo, ProjectMarker, StartSessionOptions, FormattedEvent, VerbosityLevel, } from './types.js';
2
+ export { MAX_EVENTS, INIT_TIMEOUT_MS } from './types.js';
3
+ export { resolveConfigPath, loadConfig, validateConfig } from './config.js';
4
+ export { Logger } from './logger.js';
5
+ export type { LoggerOptions } from './logger.js';
6
+ export { Daemon } from './daemon.js';
7
+ export { handleCloudRuntimeCommand, formatCloudRuntimeUsage } from './cloud-cli.js';
8
+ export { scanForProjects } from './project-scanner.js';
9
+ export { SessionManager } from './session-manager.js';
10
+ export { DiscordBot, isAuthorized, validateDiscordConfig } from './discord-bot.js';
11
+ export type { DiscordBotOptions } from './discord-bot.js';
12
+ export { ChannelManager, sanitizeChannelName } from './channel-manager.js';
13
+ export type { ChannelManagerOptions } from './channel-manager.js';
14
+ export { buildCommands, formatSessionStatus, registerGuildCommands } from './commands.js';
15
+ export { EventBridge } from './event-bridge.js';
16
+ export type { BridgeClient, EventBridgeOptions } from './event-bridge.js';
17
+ export { Orchestrator } from './orchestrator.js';
18
+ export type { OrchestratorConfig, OrchestratorDeps, DiscordMessageLike } from './orchestrator.js';
19
+ export { MessageBatcher } from './message-batcher.js';
20
+ export type { SendPayload, SendFn, BatcherLogger, BatcherOptions } from './message-batcher.js';
21
+ export { VerbosityManager, shouldShowAtLevel } from './verbosity.js';
22
+ export { formatToolStart, formatToolEnd, formatMessage, formatBlocker, formatCompletion, formatError, formatCostUpdate, formatSessionStarted, formatTaskTransition, formatGenericEvent, formatEvent, } from './event-formatter.js';
23
+ export { escapeXml, generatePlist, getPlistPath, install as installLaunchAgent, uninstall as uninstallLaunchAgent, status as launchAgentStatus, } from './launchd.js';
24
+ export type { PlistOptions, LaunchdStatus, RunCommandFn } from './launchd.js';
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,cAAc,EACd,cAAc,EACd,eAAe,EACf,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACnF,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3E,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EACL,eAAe,EACf,aAAa,EACb,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,SAAS,EACT,aAAa,EACb,YAAY,EACZ,OAAO,IAAI,kBAAkB,EAC7B,SAAS,IAAI,oBAAoB,EACjC,MAAM,IAAI,iBAAiB,GAC5B,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,17 @@
1
+ export { MAX_EVENTS, INIT_TIMEOUT_MS } from './types.js';
2
+ export { resolveConfigPath, loadConfig, validateConfig } from './config.js';
3
+ export { Logger } from './logger.js';
4
+ export { Daemon } from './daemon.js';
5
+ export { handleCloudRuntimeCommand, formatCloudRuntimeUsage } from './cloud-cli.js';
6
+ export { scanForProjects } from './project-scanner.js';
7
+ export { SessionManager } from './session-manager.js';
8
+ export { DiscordBot, isAuthorized, validateDiscordConfig } from './discord-bot.js';
9
+ export { ChannelManager, sanitizeChannelName } from './channel-manager.js';
10
+ export { buildCommands, formatSessionStatus, registerGuildCommands } from './commands.js';
11
+ export { EventBridge } from './event-bridge.js';
12
+ export { Orchestrator } from './orchestrator.js';
13
+ export { MessageBatcher } from './message-batcher.js';
14
+ export { VerbosityManager, shouldShowAtLevel } from './verbosity.js';
15
+ export { formatToolStart, formatToolEnd, formatMessage, formatBlocker, formatCompletion, formatError, formatCostUpdate, formatSessionStarted, formatTaskTransition, formatGenericEvent, formatEvent, } from './event-formatter.js';
16
+ export { escapeXml, generatePlist, getPlistPath, install as installLaunchAgent, uninstall as uninstallLaunchAgent, status as launchAgentStatus, } from './launchd.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEnF,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3E,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EACL,eAAe,EACf,aAAa,EACb,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,SAAS,EACT,aAAa,EACb,YAAY,EACZ,OAAO,IAAI,kBAAkB,EAC7B,SAAS,IAAI,oBAAoB,EACjC,MAAM,IAAI,iBAAiB,GAC5B,MAAM,cAAc,CAAC","sourcesContent":["export type {\n DaemonConfig,\n LogLevel,\n LogEntry,\n SessionStatus,\n ManagedSession,\n PendingBlocker,\n CostAccumulator,\n ProjectInfo,\n ProjectMarker,\n StartSessionOptions,\n FormattedEvent,\n VerbosityLevel,\n} from './types.js';\nexport { MAX_EVENTS, INIT_TIMEOUT_MS } from './types.js';\nexport { resolveConfigPath, loadConfig, validateConfig } from './config.js';\nexport { Logger } from './logger.js';\nexport type { LoggerOptions } from './logger.js';\nexport { Daemon } from './daemon.js';\nexport { handleCloudRuntimeCommand, formatCloudRuntimeUsage } from './cloud-cli.js';\nexport { scanForProjects } from './project-scanner.js';\nexport { SessionManager } from './session-manager.js';\nexport { DiscordBot, isAuthorized, validateDiscordConfig } from './discord-bot.js';\nexport type { DiscordBotOptions } from './discord-bot.js';\nexport { ChannelManager, sanitizeChannelName } from './channel-manager.js';\nexport type { ChannelManagerOptions } from './channel-manager.js';\nexport { buildCommands, formatSessionStatus, registerGuildCommands } from './commands.js';\nexport { EventBridge } from './event-bridge.js';\nexport type { BridgeClient, EventBridgeOptions } from './event-bridge.js';\nexport { Orchestrator } from './orchestrator.js';\nexport type { OrchestratorConfig, OrchestratorDeps, DiscordMessageLike } from './orchestrator.js';\nexport { MessageBatcher } from './message-batcher.js';\nexport type { SendPayload, SendFn, BatcherLogger, BatcherOptions } from './message-batcher.js';\nexport { VerbosityManager, shouldShowAtLevel } from './verbosity.js';\nexport {\n formatToolStart,\n formatToolEnd,\n formatMessage,\n formatBlocker,\n formatCompletion,\n formatError,\n formatCostUpdate,\n formatSessionStarted,\n formatTaskTransition,\n formatGenericEvent,\n formatEvent,\n} from './event-formatter.js';\nexport {\n escapeXml,\n generatePlist,\n getPlistPath,\n install as installLaunchAgent,\n uninstall as uninstallLaunchAgent,\n status as launchAgentStatus,\n} from './launchd.js';\nexport type { PlistOptions, LaunchdStatus, RunCommandFn } from './launchd.js';\n"]}
@@ -0,0 +1,49 @@
1
+ export interface PlistOptions {
2
+ /** Absolute path to the Node.js binary */
3
+ nodePath: string;
4
+ /** Absolute path to the daemon script (cli.js) */
5
+ scriptPath: string;
6
+ /** Absolute path to the config file */
7
+ configPath: string;
8
+ /** Directory to use as WorkingDirectory in the plist (defaults to homedir) */
9
+ workingDirectory?: string;
10
+ /** Override stdout log path */
11
+ stdoutPath?: string;
12
+ /** Override stderr log path */
13
+ stderrPath?: string;
14
+ }
15
+ export interface LaunchdStatus {
16
+ /** Whether the daemon is registered with launchd */
17
+ registered: boolean;
18
+ /** PID if currently running, null otherwise */
19
+ pid: number | null;
20
+ /** Last exit status code, null if never exited or not available */
21
+ lastExitStatus: number | null;
22
+ }
23
+ export type RunCommandFn = (cmd: string) => string;
24
+ /** Escape special XML characters in a string. */
25
+ export declare function escapeXml(str: string): string;
26
+ /** Return the canonical plist path under ~/Library/LaunchAgents/. */
27
+ export declare function getPlistPath(): string;
28
+ /** Generate valid launchd plist XML for the GSD daemon. */
29
+ export declare function generatePlist(opts: PlistOptions): string;
30
+ /**
31
+ * Install the launchd agent: write plist and load it.
32
+ * Idempotent — unloads first if already loaded.
33
+ */
34
+ export declare function install(opts: PlistOptions, runCommand?: RunCommandFn): void;
35
+ /**
36
+ * Uninstall the launchd agent: unload and remove plist.
37
+ * Graceful — does not throw if already uninstalled.
38
+ */
39
+ export declare function uninstall(runCommand?: RunCommandFn): void;
40
+ /**
41
+ * Query launchd for the daemon's status.
42
+ * Returns structured information about registration, PID, and last exit code.
43
+ *
44
+ * Handles two launchctl output formats:
45
+ * 1. Tabular: "PID\tStatus\tLabel" (older macOS)
46
+ * 2. JSON-style dict: `"PID" = 1234;` / `"LastExitStatus" = 0;` (newer macOS)
47
+ */
48
+ export declare function status(runCommand?: RunCommandFn): LaunchdStatus;
49
+ //# sourceMappingURL=launchd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launchd.d.ts","sourceRoot":"","sources":["../src/launchd.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+BAA+B;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,UAAU,EAAE,OAAO,CAAC;IACpB,+CAA+C;IAC/C,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;AASnD,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO7C;AAED,qEAAqE;AACrE,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAeD,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAyDxD;AASD;;;GAGG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,YAAY,EAClB,UAAU,GAAE,YAAgC,GAC3C,IAAI,CA0BN;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,UAAU,GAAE,YAAgC,GAAG,IAAI,CAY5E;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,UAAU,GAAE,YAAgC,GAAG,aAAa,CA4ClF"}