@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.
- package/dist/resource-loader.d.ts +5 -0
- package/dist/resource-loader.js +24 -8
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +19 -0
- package/dist/resources/extensions/gsd/auto/phases.js +1 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +2 -54
- package/dist/resources/extensions/gsd/worktree-post-create-hook.js +117 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
- package/dist/web/standalone/.next/server/chunks/1834.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/web/standalone/package.json +0 -1
- package/dist/worktree-cli.d.ts +0 -2
- package/dist/worktree-cli.js +21 -9
- package/package.json +9 -4
- package/packages/cloud-mcp-gateway/bin/gsd-cloud-mcp-gateway.js +14 -0
- package/packages/cloud-mcp-gateway/package.json +5 -4
- package/packages/contracts/package.json +2 -2
- package/packages/daemon/bin/gsd-daemon.js +14 -0
- package/packages/daemon/bin/gsd-mcp-runtime.js +14 -0
- package/packages/daemon/bin/gsd-mcp.js +14 -0
- package/packages/daemon/dist/channel-manager.d.ts +53 -0
- package/packages/daemon/dist/channel-manager.d.ts.map +1 -0
- package/packages/daemon/dist/channel-manager.js +167 -0
- package/packages/daemon/dist/channel-manager.js.map +1 -0
- package/packages/daemon/dist/cli.d.ts +3 -0
- package/packages/daemon/dist/cli.d.ts.map +1 -0
- package/packages/daemon/dist/cli.js +94 -0
- package/packages/daemon/dist/cli.js.map +1 -0
- package/packages/daemon/dist/cloud-cli.d.ts +7 -0
- package/packages/daemon/dist/cloud-cli.d.ts.map +1 -0
- package/packages/daemon/dist/cloud-cli.js +96 -0
- package/packages/daemon/dist/cloud-cli.js.map +1 -0
- package/packages/daemon/dist/cloud-config.d.ts +18 -0
- package/packages/daemon/dist/cloud-config.d.ts.map +1 -0
- package/packages/daemon/dist/cloud-config.js +209 -0
- package/packages/daemon/dist/cloud-config.js.map +1 -0
- package/packages/daemon/dist/cloud-config.test.d.ts +2 -0
- package/packages/daemon/dist/cloud-config.test.d.ts.map +1 -0
- package/packages/daemon/dist/cloud-config.test.js +132 -0
- package/packages/daemon/dist/cloud-config.test.js.map +1 -0
- package/packages/daemon/dist/cloud-runtime.d.ts +26 -0
- package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -0
- package/packages/daemon/dist/cloud-runtime.js +180 -0
- package/packages/daemon/dist/cloud-runtime.js.map +1 -0
- package/packages/daemon/dist/cloud-runtime.test.d.ts +2 -0
- package/packages/daemon/dist/cloud-runtime.test.d.ts.map +1 -0
- package/packages/daemon/dist/cloud-runtime.test.js +28 -0
- package/packages/daemon/dist/cloud-runtime.test.js.map +1 -0
- package/packages/daemon/dist/cloud-token.d.ts +3 -0
- package/packages/daemon/dist/cloud-token.d.ts.map +1 -0
- package/packages/daemon/dist/cloud-token.js +37 -0
- package/packages/daemon/dist/cloud-token.js.map +1 -0
- package/packages/daemon/dist/commands.d.ts +25 -0
- package/packages/daemon/dist/commands.d.ts.map +1 -0
- package/packages/daemon/dist/commands.js +81 -0
- package/packages/daemon/dist/commands.js.map +1 -0
- package/packages/daemon/dist/config.d.ts +17 -0
- package/packages/daemon/dist/config.d.ts.map +1 -0
- package/packages/daemon/dist/config.js +146 -0
- package/packages/daemon/dist/config.js.map +1 -0
- package/packages/daemon/dist/daemon.d.ts +38 -0
- package/packages/daemon/dist/daemon.d.ts.map +1 -0
- package/packages/daemon/dist/daemon.js +194 -0
- package/packages/daemon/dist/daemon.js.map +1 -0
- package/packages/daemon/dist/daemon.test.d.ts +2 -0
- package/packages/daemon/dist/daemon.test.d.ts.map +1 -0
- package/packages/daemon/dist/daemon.test.js +692 -0
- package/packages/daemon/dist/daemon.test.js.map +1 -0
- package/packages/daemon/dist/discord-bot.d.ts +70 -0
- package/packages/daemon/dist/discord-bot.d.ts.map +1 -0
- package/packages/daemon/dist/discord-bot.js +433 -0
- package/packages/daemon/dist/discord-bot.js.map +1 -0
- package/packages/daemon/dist/discord-bot.test.d.ts +2 -0
- package/packages/daemon/dist/discord-bot.test.d.ts.map +1 -0
- package/packages/daemon/dist/discord-bot.test.js +667 -0
- package/packages/daemon/dist/discord-bot.test.js.map +1 -0
- package/packages/daemon/dist/event-bridge.d.ts +72 -0
- package/packages/daemon/dist/event-bridge.d.ts.map +1 -0
- package/packages/daemon/dist/event-bridge.js +366 -0
- package/packages/daemon/dist/event-bridge.js.map +1 -0
- package/packages/daemon/dist/event-bridge.test.d.ts +9 -0
- package/packages/daemon/dist/event-bridge.test.d.ts.map +1 -0
- package/packages/daemon/dist/event-bridge.test.js +528 -0
- package/packages/daemon/dist/event-bridge.test.js.map +1 -0
- package/packages/daemon/dist/event-formatter.d.ts +34 -0
- package/packages/daemon/dist/event-formatter.d.ts.map +1 -0
- package/packages/daemon/dist/event-formatter.js +355 -0
- package/packages/daemon/dist/event-formatter.js.map +1 -0
- package/packages/daemon/dist/event-formatter.test.d.ts +2 -0
- package/packages/daemon/dist/event-formatter.test.d.ts.map +1 -0
- package/packages/daemon/dist/event-formatter.test.js +333 -0
- package/packages/daemon/dist/event-formatter.test.js.map +1 -0
- package/packages/daemon/dist/index.d.ts +25 -0
- package/packages/daemon/dist/index.d.ts.map +1 -0
- package/packages/daemon/dist/index.js +17 -0
- package/packages/daemon/dist/index.js.map +1 -0
- package/packages/daemon/dist/launchd.d.ts +49 -0
- package/packages/daemon/dist/launchd.d.ts.map +1 -0
- package/packages/daemon/dist/launchd.js +188 -0
- package/packages/daemon/dist/launchd.js.map +1 -0
- package/packages/daemon/dist/launchd.test.d.ts +2 -0
- package/packages/daemon/dist/launchd.test.d.ts.map +1 -0
- package/packages/daemon/dist/launchd.test.js +296 -0
- package/packages/daemon/dist/launchd.test.js.map +1 -0
- package/packages/daemon/dist/local-tool-executor.d.ts +22 -0
- package/packages/daemon/dist/local-tool-executor.d.ts.map +1 -0
- package/packages/daemon/dist/local-tool-executor.js +307 -0
- package/packages/daemon/dist/local-tool-executor.js.map +1 -0
- package/packages/daemon/dist/local-tool-executor.test.d.ts +2 -0
- package/packages/daemon/dist/local-tool-executor.test.d.ts.map +1 -0
- package/packages/daemon/dist/local-tool-executor.test.js +111 -0
- package/packages/daemon/dist/local-tool-executor.test.js.map +1 -0
- package/packages/daemon/dist/logger.d.ts +25 -0
- package/packages/daemon/dist/logger.d.ts.map +1 -0
- package/packages/daemon/dist/logger.js +72 -0
- package/packages/daemon/dist/logger.js.map +1 -0
- package/packages/daemon/dist/mcp-cli.d.ts +3 -0
- package/packages/daemon/dist/mcp-cli.d.ts.map +1 -0
- package/packages/daemon/dist/mcp-cli.js +8 -0
- package/packages/daemon/dist/mcp-cli.js.map +1 -0
- package/packages/daemon/dist/mcp-cli.test.d.ts +2 -0
- package/packages/daemon/dist/mcp-cli.test.d.ts.map +1 -0
- package/packages/daemon/dist/mcp-cli.test.js +13 -0
- package/packages/daemon/dist/mcp-cli.test.js.map +1 -0
- package/packages/daemon/dist/mcp-runtime-cli.d.ts +3 -0
- package/packages/daemon/dist/mcp-runtime-cli.d.ts.map +1 -0
- package/packages/daemon/dist/mcp-runtime-cli.js +8 -0
- package/packages/daemon/dist/mcp-runtime-cli.js.map +1 -0
- package/packages/daemon/dist/message-batcher.d.ts +78 -0
- package/packages/daemon/dist/message-batcher.d.ts.map +1 -0
- package/packages/daemon/dist/message-batcher.js +173 -0
- package/packages/daemon/dist/message-batcher.js.map +1 -0
- package/packages/daemon/dist/message-batcher.test.d.ts +2 -0
- package/packages/daemon/dist/message-batcher.test.d.ts.map +1 -0
- package/packages/daemon/dist/message-batcher.test.js +242 -0
- package/packages/daemon/dist/message-batcher.test.js.map +1 -0
- package/packages/daemon/dist/orchestrator.d.ts +98 -0
- package/packages/daemon/dist/orchestrator.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator.js +359 -0
- package/packages/daemon/dist/orchestrator.js.map +1 -0
- package/packages/daemon/dist/orchestrator.test.d.ts +8 -0
- package/packages/daemon/dist/orchestrator.test.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator.test.js +425 -0
- package/packages/daemon/dist/orchestrator.test.js.map +1 -0
- package/packages/daemon/dist/project-scanner.d.ts +18 -0
- package/packages/daemon/dist/project-scanner.d.ts.map +1 -0
- package/packages/daemon/dist/project-scanner.js +90 -0
- package/packages/daemon/dist/project-scanner.js.map +1 -0
- package/packages/daemon/dist/project-scanner.test.d.ts +5 -0
- package/packages/daemon/dist/project-scanner.test.d.ts.map +1 -0
- package/packages/daemon/dist/project-scanner.test.js +183 -0
- package/packages/daemon/dist/project-scanner.test.js.map +1 -0
- package/packages/daemon/dist/session-manager.d.ts +70 -0
- package/packages/daemon/dist/session-manager.d.ts.map +1 -0
- package/packages/daemon/dist/session-manager.js +358 -0
- package/packages/daemon/dist/session-manager.js.map +1 -0
- package/packages/daemon/dist/session-manager.test.d.ts +9 -0
- package/packages/daemon/dist/session-manager.test.d.ts.map +1 -0
- package/packages/daemon/dist/session-manager.test.js +616 -0
- package/packages/daemon/dist/session-manager.test.js.map +1 -0
- package/packages/daemon/dist/types.d.ts +133 -0
- package/packages/daemon/dist/types.d.ts.map +1 -0
- package/packages/daemon/dist/types.js +8 -0
- package/packages/daemon/dist/types.js.map +1 -0
- package/packages/daemon/dist/verbosity.d.ts +27 -0
- package/packages/daemon/dist/verbosity.d.ts.map +1 -0
- package/packages/daemon/dist/verbosity.js +86 -0
- package/packages/daemon/dist/verbosity.js.map +1 -0
- package/packages/daemon/dist/verbosity.test.d.ts +2 -0
- package/packages/daemon/dist/verbosity.test.d.ts.map +1 -0
- package/packages/daemon/dist/verbosity.test.js +136 -0
- package/packages/daemon/dist/verbosity.test.js.map +1 -0
- package/packages/daemon/package.json +9 -8
- package/packages/gsd-agent-core/package.json +6 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +3 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +2 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +8 -8
- package/packages/mcp-server/bin/gsd-mcp-server.js +14 -0
- package/packages/mcp-server/package.json +6 -5
- package/packages/native/package.json +3 -3
- package/packages/pi-agent-core/package.json +4 -4
- package/packages/pi-ai/bin/pi-ai.js +14 -0
- package/packages/pi-ai/dist/models.generated.d.ts +0 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +18 -35
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +5 -4
- package/packages/pi-coding-agent/dist/core/tools/read.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/read.js +5 -3
- package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
- package/packages/pi-coding-agent/package.json +9 -9
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +3 -3
- package/pkg/package.json +1 -1
- package/scripts/ensure-workspace-builds.cjs +4 -4
- package/scripts/install/deps.js +10 -0
- package/src/resources/extensions/gsd/auto/loop.ts +22 -0
- package/src/resources/extensions/gsd/auto/phases.ts +1 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +2 -56
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +141 -1
- package/src/resources/extensions/gsd/worktree-post-create-hook.ts +127 -0
- package/dist/tsconfig.extensions.tsbuildinfo +0 -1
- /package/dist/web/standalone/.next/static/{JP7xjsa5zSaO76XhE-mFJ → spUYLkQXoHJyxYOMH9VQy}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{JP7xjsa5zSaO76XhE-mFJ → spUYLkQXoHJyxYOMH9VQy}/_ssgManifest.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord-bot.test.js","sourceRoot":"","sources":["../src/discord-bot.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, afterEach } from 'node:test';\nimport assert from 'node:assert/strict';\nimport { mkdtempSync, readFileSync, rmSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport { ChannelType } from 'discord.js';\nimport { isAuthorized, validateDiscordConfig } from './discord-bot.js';\nimport { sanitizeChannelName, ChannelManager } from './channel-manager.js';\nimport { buildCommands, formatSessionStatus } from './commands.js';\nimport { Daemon } from './daemon.js';\nimport { Logger } from './logger.js';\nimport { validateConfig } from './config.js';\nimport type { DaemonConfig, LogEntry, ManagedSession } from './types.js';\n\n// ---------- helpers ----------\n\nfunction tmpDir(): string {\n return mkdtempSync(join(tmpdir(), `discord-test-${randomUUID().slice(0, 8)}-`));\n}\n\nconst cleanupDirs: string[] = [];\nafterEach(() => {\n while (cleanupDirs.length) {\n const d = cleanupDirs.pop()!;\n if (existsSync(d)) rmSync(d, { recursive: true, force: true });\n }\n});\n\n// ---------- isAuthorized ----------\n\ndescribe('isAuthorized', () => {\n it('returns true when userId matches ownerId', () => {\n assert.equal(isAuthorized('12345', '12345'), true);\n });\n\n it('returns false when userId does not match ownerId', () => {\n assert.equal(isAuthorized('12345', '99999'), false);\n });\n\n it('returns false when ownerId is empty', () => {\n assert.equal(isAuthorized('12345', ''), false);\n });\n\n it('returns false when userId is empty', () => {\n assert.equal(isAuthorized('', '12345'), false);\n });\n\n it('returns false when both are empty', () => {\n assert.equal(isAuthorized('', ''), false);\n });\n});\n\n// ---------- validateDiscordConfig ----------\n\ndescribe('validateDiscordConfig', () => {\n it('passes with all required fields', () => {\n assert.doesNotThrow(() => {\n validateDiscordConfig({\n token: 'test-token',\n guild_id: 'g123',\n owner_id: 'o456',\n });\n });\n });\n\n it('throws on undefined config', () => {\n assert.throws(\n () => validateDiscordConfig(undefined),\n (err: Error) => {\n assert.ok(err.message.includes('undefined'));\n return true;\n },\n );\n });\n\n it('throws on missing token', () => {\n assert.throws(\n () => validateDiscordConfig({ token: '', guild_id: 'g1', owner_id: 'o1' }),\n (err: Error) => {\n assert.ok(err.message.includes('token'));\n return true;\n },\n );\n });\n\n it('throws on whitespace-only token', () => {\n assert.throws(\n () => validateDiscordConfig({ token: ' ', guild_id: 'g1', owner_id: 'o1' }),\n (err: Error) => {\n assert.ok(err.message.includes('token'));\n return true;\n },\n );\n });\n\n it('throws on missing guild_id', () => {\n assert.throws(\n () => validateDiscordConfig({ token: 'tok', guild_id: '', owner_id: 'o1' }),\n (err: Error) => {\n assert.ok(err.message.includes('guild_id'));\n return true;\n },\n );\n });\n\n it('throws on missing owner_id', () => {\n assert.throws(\n () => validateDiscordConfig({ token: 'tok', guild_id: 'g1', owner_id: '' }),\n (err: Error) => {\n assert.ok(err.message.includes('owner_id'));\n return true;\n },\n );\n });\n});\n\n// ---------- Daemon wiring ----------\n\ndescribe('Daemon + DiscordBot wiring', () => {\n it('does not create DiscordBot when discord config is absent', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'no-discord.log');\n\n const config: DaemonConfig = {\n discord: undefined,\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'debug', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'debug' });\n const daemon = new Daemon(config, logger);\n\n await daemon.start();\n\n const origExit = process.exit;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => {};\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n\n const content = readFileSync(logPath, 'utf-8');\n // Should NOT have any bot-related log entries\n assert.ok(!content.includes('bot ready'));\n assert.ok(!content.includes('discord bot login failed'));\n assert.ok(!content.includes('bot destroyed'));\n });\n\n it('logs error when discord config has token but login fails (no real gateway)', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'bad-token.log');\n\n const config: DaemonConfig = {\n discord: {\n token: 'invalid-token-that-will-fail-login',\n guild_id: 'g1',\n owner_id: 'o1',\n },\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'debug', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'debug' });\n const daemon = new Daemon(config, logger);\n\n // start() should NOT throw — bot login failure is non-fatal\n await daemon.start();\n\n const origExit = process.exit;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => {};\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n\n // Small flush delay\n await new Promise((r) => setTimeout(r, 50));\n\n const content = readFileSync(logPath, 'utf-8');\n // Should have logged the login failure\n assert.ok(content.includes('discord bot login failed'), 'should log bot login failure');\n // Token should never appear in logs\n assert.ok(!content.includes('invalid-token-that-will-fail-login'), 'token must not appear in logs');\n });\n\n it('does not attempt login when discord config has no token', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'no-token.log');\n\n // Config with discord block but empty token\n const config: DaemonConfig = {\n discord: {\n token: '',\n guild_id: 'g1',\n owner_id: 'o1',\n },\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'debug', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'debug' });\n const daemon = new Daemon(config, logger);\n\n await daemon.start();\n\n const origExit = process.exit;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => {};\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n\n const content = readFileSync(logPath, 'utf-8');\n // Should not attempt login — no token\n assert.ok(!content.includes('discord bot login failed'));\n assert.ok(!content.includes('bot ready'));\n });\n});\n\n// ---------- sanitizeChannelName ----------\n\ndescribe('sanitizeChannelName', () => {\n it('converts basic path to gsd-prefixed name', () => {\n assert.equal(sanitizeChannelName('/home/user/my-project'), 'gsd-my-project');\n });\n\n it('converts path with special characters to hyphens', () => {\n assert.equal(sanitizeChannelName('/home/user/My_Cool.Project!v2'), 'gsd-my-cool-project-v2');\n });\n\n it('truncates very long names to 100 chars', () => {\n const longName = 'a'.repeat(200);\n const result = sanitizeChannelName(`/home/${longName}`);\n assert.ok(result.length <= 100, `Expected <= 100 chars, got ${result.length}`);\n assert.ok(result.startsWith('gsd-'));\n });\n\n it('cleans leading/trailing dots and underscores', () => {\n assert.equal(sanitizeChannelName('/home/...___project___...'), 'gsd-project');\n });\n\n it('returns gsd-unnamed for empty basename', () => {\n assert.equal(sanitizeChannelName(''), 'gsd-unnamed');\n assert.equal(sanitizeChannelName('/'), 'gsd-unnamed');\n });\n\n it('returns gsd-unnamed for basename with only special chars', () => {\n assert.equal(sanitizeChannelName('/home/!!!'), 'gsd-unnamed');\n });\n\n it('collapses consecutive hyphens', () => {\n assert.equal(sanitizeChannelName('/home/a---b---c'), 'gsd-a-b-c');\n });\n\n it('handles Windows-style backslash paths', () => {\n assert.equal(sanitizeChannelName('C:\\\\Users\\\\lex\\\\my-project'), 'gsd-my-project');\n });\n\n it('handles name at exact prefix + 96 chars = 100 char limit', () => {\n // gsd- is 4 chars, so a 96-char basename should produce exactly 100\n const name96 = 'a'.repeat(96);\n const result = sanitizeChannelName(`/home/${name96}`);\n assert.equal(result.length, 100);\n assert.equal(result, `gsd-${'a'.repeat(96)}`);\n });\n\n it('handles whitespace-only basename', () => {\n assert.equal(sanitizeChannelName('/home/ '), 'gsd-unnamed');\n });\n});\n\n// ---------- ChannelManager ----------\n\ndescribe('ChannelManager', () => {\n // Helper to create a mock Guild with controllable channel cache and create method\n function createMockGuild() {\n const channels = new Map<string, { id: string; name: string; type: number; parentId: string | null; edit?: Function }>();\n let createCounter = 0;\n\n const mockGuild = {\n id: 'guild-123', // @everyone role ID matches guild ID\n channels: {\n cache: {\n get: (id: string) => channels.get(id),\n find: (fn: (ch: any) => boolean) => {\n for (const ch of channels.values()) {\n if (fn(ch)) return ch;\n }\n return undefined;\n },\n },\n create: async (opts: { name: string; type: number; parent?: string; permissionOverwrites?: any[] }) => {\n createCounter++;\n const id = `chan-${createCounter}`;\n const ch = {\n id,\n name: opts.name,\n type: opts.type,\n parentId: opts.parent ?? null,\n edit: async (editOpts: any) => {\n // Simulate edit — update parent\n ch.parentId = editOpts.parent ?? ch.parentId;\n return ch;\n },\n };\n channels.set(id, ch);\n return ch;\n },\n },\n _channels: channels, // internal for test inspection\n _getCreateCount: () => createCounter,\n };\n\n return mockGuild;\n }\n\n function createMockLogger() {\n const entries: { level: string; msg: string; data?: any }[] = [];\n return {\n debug: (msg: string, data?: any) => entries.push({ level: 'debug', msg, data }),\n info: (msg: string, data?: any) => entries.push({ level: 'info', msg, data }),\n warn: (msg: string, data?: any) => entries.push({ level: 'warn', msg, data }),\n error: (msg: string, data?: any) => entries.push({ level: 'error', msg, data }),\n entries,\n close: async () => {},\n };\n }\n\n it('resolveCategory creates category when not found', async () => {\n const guild = createMockGuild();\n const logger = createMockLogger();\n const mgr = new ChannelManager({ guild: guild as any, logger: logger as any });\n\n const cat = await mgr.resolveCategory();\n assert.equal(cat.name, 'GSD Projects');\n assert.equal(cat.type, ChannelType.GuildCategory);\n });\n\n it('resolveCategory returns cached category on second call', async () => {\n const guild = createMockGuild();\n const logger = createMockLogger();\n const mgr = new ChannelManager({ guild: guild as any, logger: logger as any });\n\n const cat1 = await mgr.resolveCategory();\n const cat2 = await mgr.resolveCategory();\n assert.equal(cat1.id, cat2.id);\n // Only one create call should have been made\n assert.equal(guild._getCreateCount(), 1);\n });\n\n it('resolveCategory finds existing category by name', async () => {\n const guild = createMockGuild();\n // Pre-populate a matching category\n guild._channels.set('existing-cat', {\n id: 'existing-cat',\n name: 'GSD Projects',\n type: ChannelType.GuildCategory,\n parentId: null,\n });\n\n const logger = createMockLogger();\n const mgr = new ChannelManager({ guild: guild as any, logger: logger as any });\n\n const cat = await mgr.resolveCategory();\n assert.equal(cat.id, 'existing-cat');\n // No create calls — found existing\n assert.equal(guild._getCreateCount(), 0);\n });\n\n it('createProjectChannel creates text channel under category', async () => {\n const guild = createMockGuild();\n const logger = createMockLogger();\n const mgr = new ChannelManager({ guild: guild as any, logger: logger as any });\n\n const channel = await mgr.createProjectChannel('/home/user/my-project');\n assert.equal(channel.name, 'gsd-my-project');\n assert.equal(channel.type, ChannelType.GuildText);\n // Category was created first (chan-1), then channel (chan-2)\n assert.equal(channel.parentId, 'chan-1');\n });\n\n it('archiveChannel moves channel to archive category', async () => {\n const guild = createMockGuild();\n const logger = createMockLogger();\n const mgr = new ChannelManager({ guild: guild as any, logger: logger as any });\n\n // Create a project channel first\n const channel = await mgr.createProjectChannel('/home/user/project');\n const channelId = channel.id;\n\n // Archive it\n await mgr.archiveChannel(channelId);\n\n // The channel should have been edit()-ed with the archive category as parent\n const archived = guild._channels.get(channelId)!;\n // Archive category was created as the 3rd channel (chan-3): category(chan-1), text(chan-2), archive(chan-3)\n assert.equal(archived.parentId, 'chan-3');\n\n // Verify archive log\n const archiveLog = logger.entries.find((e) => e.msg === 'channel archived');\n assert.ok(archiveLog, 'should log channel archived');\n assert.equal(archiveLog!.data.channelId, channelId);\n });\n\n it('archiveChannel warns when channel not found', async () => {\n const guild = createMockGuild();\n const logger = createMockLogger();\n const mgr = new ChannelManager({ guild: guild as any, logger: logger as any });\n\n await mgr.archiveChannel('nonexistent-id');\n const warnLog = logger.entries.find((e) => e.msg === 'archive target not found');\n assert.ok(warnLog, 'should warn about missing channel');\n });\n\n it('uses custom category name when provided', async () => {\n const guild = createMockGuild();\n const logger = createMockLogger();\n const mgr = new ChannelManager({\n guild: guild as any,\n logger: logger as any,\n categoryName: 'Custom Category',\n });\n\n const cat = await mgr.resolveCategory();\n assert.equal(cat.name, 'Custom Category');\n });\n});\n\n// ---------- buildCommands ----------\n\ndescribe('buildCommands', () => {\n it('returns array with correct command names', () => {\n const commands = buildCommands();\n assert.equal(commands.length, 4);\n const names = commands.map((c) => c.name);\n assert.ok(names.includes('gsd-status'), 'should include gsd-status');\n assert.ok(names.includes('gsd-start'), 'should include gsd-start');\n assert.ok(names.includes('gsd-stop'), 'should include gsd-stop');\n assert.ok(names.includes('gsd-verbose'), 'should include gsd-verbose');\n });\n\n it('each command has a description', () => {\n const commands = buildCommands();\n for (const cmd of commands) {\n assert.ok(cmd.description, `command ${cmd.name} should have a description`);\n assert.ok(cmd.description.length > 0, `command ${cmd.name} description should be non-empty`);\n }\n });\n});\n\n// ---------- formatSessionStatus ----------\n\ndescribe('formatSessionStatus', () => {\n function mockSession(overrides: Partial<ManagedSession> = {}): ManagedSession {\n return {\n sessionId: 'sess-1',\n projectDir: '/home/user/project',\n projectName: 'project',\n status: 'running',\n client: {} as any,\n events: [],\n pendingBlocker: null,\n cost: { totalCost: 0.1234, tokens: { input: 100, output: 50, cacheRead: 0, cacheWrite: 0 } },\n startTime: Date.now() - 120_000, // 2 minutes ago\n ...overrides,\n };\n }\n\n it('returns \"No active sessions.\" for empty array', () => {\n assert.equal(formatSessionStatus([]), 'No active sessions.');\n });\n\n it('formats single session with project name and status', () => {\n const result = formatSessionStatus([mockSession()]);\n assert.ok(result.includes('project'), 'should contain project name');\n assert.ok(result.includes('running'), 'should contain status');\n assert.ok(result.includes('$'), 'should contain cost');\n });\n\n it('formats multiple sessions on separate lines', () => {\n const sessions = [\n mockSession({ projectName: 'alpha', status: 'running' }),\n mockSession({ projectName: 'beta', status: 'blocked' }),\n ];\n const result = formatSessionStatus(sessions);\n assert.ok(result.includes('alpha'), 'should contain first project');\n assert.ok(result.includes('beta'), 'should contain second project');\n const lines = result.split('\\n');\n assert.equal(lines.length, 2, 'should have one line per session');\n });\n\n it('formats 5 sessions correctly', () => {\n const sessions = Array.from({ length: 5 }, (_, i) =>\n mockSession({ projectName: `proj-${i}`, status: i % 2 === 0 ? 'running' : 'completed' }),\n );\n const result = formatSessionStatus(sessions);\n const lines = result.split('\\n');\n assert.equal(lines.length, 5);\n for (let i = 0; i < 5; i++) {\n assert.ok(lines[i].includes(`proj-${i}`));\n }\n });\n});\n\n// ---------- Command dispatch (mock interaction) ----------\n\ndescribe('command dispatch', () => {\n // Minimal mock of a ChatInputCommandInteraction\n function mockInteraction(commandName: string, userId: string = 'owner-1') {\n let replied = false;\n let replyContent = '';\n\n return {\n user: { id: userId },\n type: 2, // InteractionType.ApplicationCommand\n isChatInputCommand: () => true,\n commandName,\n reply: async (opts: { content: string; ephemeral?: boolean }) => {\n replied = true;\n replyContent = opts.content;\n },\n _getReplied: () => replied,\n _getReplyContent: () => replyContent,\n };\n }\n\n // Minimal mock of a non-command interaction\n function mockNonCommandInteraction(userId: string = 'owner-1') {\n let replied = false;\n return {\n user: { id: userId },\n type: 3, // InteractionType.MessageComponent\n isChatInputCommand: () => false,\n _getReplied: () => replied,\n };\n }\n\n // We can't easily test through DiscordBot.handleInteraction since it's private.\n // Instead, test the pure functions that the handler calls, and test auth guard\n // behavior via the mock interaction flow.\n // The command routing logic is tested indirectly through integration of the\n // pure helpers (buildCommands, formatSessionStatus, isAuthorized).\n\n it('gsd-status with no sessions produces empty message', () => {\n // Tests the formatSessionStatus path that /gsd-status calls\n const result = formatSessionStatus([]);\n assert.equal(result, 'No active sessions.');\n });\n\n it('unknown command name is not in buildCommands list', () => {\n const commands = buildCommands();\n const names = commands.map((c) => c.name);\n assert.ok(!names.includes('gsd-unknown'), 'unknown should not be in command list');\n });\n\n it('auth guard rejects non-owner on interaction', () => {\n // Simulates the first check in handleInteraction\n const authorized = isAuthorized('intruder-999', 'owner-1');\n assert.equal(authorized, false);\n });\n\n it('auth guard accepts owner on interaction', () => {\n const authorized = isAuthorized('owner-1', 'owner-1');\n assert.equal(authorized, true);\n });\n});\n\n// ---------- Config validation: new fields ----------\n\ndescribe('validateConfig — control_channel_id and orchestrator', () => {\n it('parses control_channel_id from discord block', () => {\n const config = validateConfig({\n discord: {\n token: 'tok',\n guild_id: 'g1',\n owner_id: 'o1',\n control_channel_id: 'ch-123',\n },\n });\n assert.equal(config.discord?.control_channel_id, 'ch-123');\n });\n\n it('omits control_channel_id when not present', () => {\n const config = validateConfig({\n discord: {\n token: 'tok',\n guild_id: 'g1',\n owner_id: 'o1',\n },\n });\n assert.equal(config.discord?.control_channel_id, undefined);\n });\n\n it('parses orchestrator model and max_tokens', () => {\n const config = validateConfig({\n discord: {\n token: 'tok',\n guild_id: 'g1',\n owner_id: 'o1',\n orchestrator: { model: 'claude-opus-2025', max_tokens: 2048 },\n },\n });\n assert.equal(config.discord?.orchestrator?.model, 'claude-opus-2025');\n assert.equal(config.discord?.orchestrator?.max_tokens, 2048);\n });\n\n it('missing orchestrator block results in undefined', () => {\n const config = validateConfig({\n discord: {\n token: 'tok',\n guild_id: 'g1',\n owner_id: 'o1',\n },\n });\n assert.equal(config.discord?.orchestrator, undefined);\n });\n\n it('empty orchestrator block has no model or max_tokens', () => {\n const config = validateConfig({\n discord: {\n token: 'tok',\n guild_id: 'g1',\n owner_id: 'o1',\n orchestrator: {},\n },\n });\n // orchestrator object should exist but with no values set\n assert.ok(config.discord?.orchestrator !== undefined);\n assert.equal(config.discord?.orchestrator?.model, undefined);\n assert.equal(config.discord?.orchestrator?.max_tokens, undefined);\n });\n\n it('ignores non-numeric max_tokens', () => {\n const config = validateConfig({\n discord: {\n token: 'tok',\n guild_id: 'g1',\n owner_id: 'o1',\n orchestrator: { max_tokens: 'not a number' },\n },\n });\n assert.equal(config.discord?.orchestrator?.max_tokens, undefined);\n });\n\n it('ignores non-string model', () => {\n const config = validateConfig({\n discord: {\n token: 'tok',\n guild_id: 'g1',\n owner_id: 'o1',\n orchestrator: { model: 42 },\n },\n });\n assert.equal(config.discord?.orchestrator?.model, undefined);\n });\n});\n\n// ---------- Daemon wiring: orchestrator ----------\n\ndescribe('Daemon orchestrator wiring', () => {\n it('orchestrator is undefined when control_channel_id is not set', async () => {\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'no-orchestrator.log');\n\n const config: DaemonConfig = {\n discord: undefined,\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'debug', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'debug' });\n const daemon = new Daemon(config, logger);\n\n await daemon.start();\n assert.equal(daemon.getOrchestrator(), undefined);\n\n const origExit = process.exit;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => {};\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n });\n\n it('orchestrator is undefined when discord has no control_channel_id', async () => {\n // Even with a discord block that fails login, orchestrator should not be created\n // because there's no control_channel_id\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'no-ctl-chan.log');\n\n const config: DaemonConfig = {\n discord: {\n token: 'bad-token',\n guild_id: 'g1',\n owner_id: 'o1',\n // control_channel_id intentionally omitted\n },\n projects: { scan_roots: [] },\n log: { file: logPath, level: 'debug', max_size_mb: 50 },\n };\n\n const logger = new Logger({ filePath: logPath, level: 'debug' });\n const daemon = new Daemon(config, logger);\n\n await daemon.start();\n // Login fails, so orchestrator can't be wired regardless. But the code path\n // that checks control_channel_id comes after successful login/eventBridge wiring.\n // Since login fails, orchestrator is undefined.\n assert.equal(daemon.getOrchestrator(), undefined);\n\n const origExit = process.exit;\n // @ts-expect-error — overriding process.exit for test\n process.exit = () => {};\n try {\n await daemon.shutdown();\n } finally {\n process.exit = origExit;\n }\n });\n});\n\n// ---------- /gsd-start and /gsd-stop logic paths ----------\n\ndescribe('/gsd-start and /gsd-stop logic', () => {\n // These test the observable logic paths exercised by the handlers.\n // Since handleGsdStart/handleGsdStop are private, we test the data layer\n // they depend on — project scanning, session listing, and edge cases.\n\n it('/gsd-start: scanForProjects returning 0 projects', async () => {\n // Simulates the \"no projects\" path\n const { scanForProjects } = await import('./project-scanner.js');\n // With no scan roots, should return empty\n const projects = await scanForProjects([]);\n assert.equal(projects.length, 0);\n });\n\n it('/gsd-stop: getAllSessions returns empty when no sessions active', async () => {\n const { SessionManager } = await import('./session-manager.js');\n const dir = tmpDir();\n cleanupDirs.push(dir);\n const logPath = join(dir, 'sm-test.log');\n const logger = new Logger({ filePath: logPath, level: 'debug' });\n const sm = new SessionManager(logger);\n const sessions = sm.getAllSessions();\n assert.equal(sessions.length, 0);\n await logger.close();\n });\n\n it('/gsd-stop: filters to active sessions only', () => {\n // Simulate the filter logic used in handleGsdStop\n const allSessions: Partial<ManagedSession>[] = [\n { sessionId: 's1', status: 'running', projectName: 'alpha' },\n { sessionId: 's2', status: 'completed', projectName: 'beta' },\n { sessionId: 's3', status: 'blocked', projectName: 'gamma' },\n { sessionId: 's4', status: 'error', projectName: 'delta' },\n { sessionId: 's5', status: 'starting', projectName: 'epsilon' },\n { sessionId: 's6', status: 'cancelled', projectName: 'zeta' },\n ];\n const active = allSessions.filter(\n (s) => s.status === 'running' || s.status === 'blocked' || s.status === 'starting',\n );\n assert.equal(active.length, 3);\n assert.deepEqual(active.map((s) => s.projectName), ['alpha', 'gamma', 'epsilon']);\n });\n\n it('/gsd-start: >25 projects are truncated for select menu', () => {\n // Simulate the truncation logic\n const projects = Array.from({ length: 30 }, (_, i) => ({\n name: `project-${i}`,\n path: `/home/user/project-${i}`,\n markers: [] as string[],\n lastModified: Date.now(),\n }));\n const truncated = projects.slice(0, 25);\n assert.equal(truncated.length, 25);\n assert.equal(truncated[24].name, 'project-24');\n });\n});\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* event-bridge.ts — Orchestrator wiring SessionManager events through
|
|
3
|
+
* formatter → batcher → Discord channels.
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* - Session lifecycle → Discord channel creation and cleanup
|
|
7
|
+
* - Event streaming → format + verbosity filter + batcher
|
|
8
|
+
* - Blocker resolution → interactive buttons + text relay
|
|
9
|
+
* - Conversation relay → Discord messages forwarded to GSD sessions
|
|
10
|
+
* - DM backup → owner gets DM on blocker when dm_on_blocker configured
|
|
11
|
+
*/
|
|
12
|
+
import type { Message } from 'discord.js';
|
|
13
|
+
import type { Logger } from './logger.js';
|
|
14
|
+
import type { DaemonConfig } from './types.js';
|
|
15
|
+
import type { SessionManager } from './session-manager.js';
|
|
16
|
+
import type { ChannelManager } from './channel-manager.js';
|
|
17
|
+
import { VerbosityManager } from './verbosity.js';
|
|
18
|
+
/** Minimal interface for a Discord client — extracted for testability. */
|
|
19
|
+
export interface BridgeClient {
|
|
20
|
+
on(event: 'messageCreate', listener: (message: Message) => void): void;
|
|
21
|
+
off(event: 'messageCreate', listener: (message: Message) => void): void;
|
|
22
|
+
users: {
|
|
23
|
+
fetch(id: string): Promise<{
|
|
24
|
+
send(opts: unknown): Promise<unknown>;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Options for creating an EventBridge. */
|
|
29
|
+
export interface EventBridgeOptions {
|
|
30
|
+
sessionManager: SessionManager;
|
|
31
|
+
channelManager: ChannelManager;
|
|
32
|
+
client: BridgeClient;
|
|
33
|
+
config: DaemonConfig;
|
|
34
|
+
logger: Logger;
|
|
35
|
+
ownerId: string;
|
|
36
|
+
}
|
|
37
|
+
export declare class EventBridge {
|
|
38
|
+
private readonly sessionManager;
|
|
39
|
+
private readonly channelManager;
|
|
40
|
+
private readonly client;
|
|
41
|
+
private readonly config;
|
|
42
|
+
private readonly logger;
|
|
43
|
+
private readonly ownerId;
|
|
44
|
+
/** sessionId → channelId */
|
|
45
|
+
private readonly sessionToChannel;
|
|
46
|
+
/** channelId → sessionId */
|
|
47
|
+
private readonly channelToSession;
|
|
48
|
+
/** sessionId → MessageBatcher */
|
|
49
|
+
private readonly batchers;
|
|
50
|
+
/** sessionId → TextChannel (cached for send operations) */
|
|
51
|
+
private readonly channels;
|
|
52
|
+
private readonly verbosity;
|
|
53
|
+
/** Bound event handlers for cleanup */
|
|
54
|
+
private boundHandlers;
|
|
55
|
+
constructor(opts: EventBridgeOptions);
|
|
56
|
+
/** Subscribe to SessionManager events and Discord messageCreate. */
|
|
57
|
+
start(): void;
|
|
58
|
+
/** Unsubscribe from all events, destroy batchers, clear mappings. */
|
|
59
|
+
stop(): Promise<void>;
|
|
60
|
+
/** Expose the verbosity manager for slash-command integration. */
|
|
61
|
+
getVerbosityManager(): VerbosityManager;
|
|
62
|
+
private onSessionStarted;
|
|
63
|
+
private onSessionEvent;
|
|
64
|
+
private onSessionBlocked;
|
|
65
|
+
private onSessionCompleted;
|
|
66
|
+
private onSessionError;
|
|
67
|
+
private createButtonCollector;
|
|
68
|
+
private sendBlockerDM;
|
|
69
|
+
private handleMessageCreate;
|
|
70
|
+
private cleanupSession;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=event-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-bridge.d.ts","sourceRoot":"","sources":["../src/event-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAU,OAAO,EAA4C,MAAM,YAAY,CAAC;AAG5F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,YAAY,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAclD,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;IACvE,GAAG,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;IACxE,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;YAAE,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;CAClF;AAED,2CAA2C;AAC3C,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAYD,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,4BAA4B;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAC9D,4BAA4B;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAC9D,iCAAiC;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAC9D,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAE3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IAEpD,uCAAuC;IACvC,OAAO,CAAC,aAAa,CAOL;gBAEJ,IAAI,EAAE,kBAAkB;IAapC,oEAAoE;IACpE,KAAK,IAAI,IAAI;IAkCb,qEAAqE;IAC/D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B3B,kEAAkE;IAClE,mBAAmB,IAAI,gBAAgB;YAQzB,gBAAgB;YAwChB,cAAc;YAgBd,gBAAgB;YAwBhB,kBAAkB;YAiBlB,cAAc;IAgB5B,OAAO,CAAC,qBAAqB;YA6Ef,aAAa;YAwBb,mBAAmB;YAuDnB,cAAc;CAc7B"}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* event-bridge.ts — Orchestrator wiring SessionManager events through
|
|
3
|
+
* formatter → batcher → Discord channels.
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* - Session lifecycle → Discord channel creation and cleanup
|
|
7
|
+
* - Event streaming → format + verbosity filter + batcher
|
|
8
|
+
* - Blocker resolution → interactive buttons + text relay
|
|
9
|
+
* - Conversation relay → Discord messages forwarded to GSD sessions
|
|
10
|
+
* - DM backup → owner gets DM on blocker when dm_on_blocker configured
|
|
11
|
+
*/
|
|
12
|
+
import { EmbedBuilder, ComponentType } from 'discord.js';
|
|
13
|
+
import { MessageBatcher } from './message-batcher.js';
|
|
14
|
+
import { VerbosityManager } from './verbosity.js';
|
|
15
|
+
import { formatEvent, formatBlocker, formatSessionStarted, formatError, formatCompletion, } from './event-formatter.js';
|
|
16
|
+
import { isAuthorized } from './discord-bot.js';
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Collector timeout
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const BLOCKER_COLLECTOR_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// EventBridge
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
export class EventBridge {
|
|
25
|
+
sessionManager;
|
|
26
|
+
channelManager;
|
|
27
|
+
client;
|
|
28
|
+
config;
|
|
29
|
+
logger;
|
|
30
|
+
ownerId;
|
|
31
|
+
/** sessionId → channelId */
|
|
32
|
+
sessionToChannel = new Map();
|
|
33
|
+
/** channelId → sessionId */
|
|
34
|
+
channelToSession = new Map();
|
|
35
|
+
/** sessionId → MessageBatcher */
|
|
36
|
+
batchers = new Map();
|
|
37
|
+
/** sessionId → TextChannel (cached for send operations) */
|
|
38
|
+
channels = new Map();
|
|
39
|
+
verbosity = new VerbosityManager();
|
|
40
|
+
/** Bound event handlers for cleanup */
|
|
41
|
+
boundHandlers = null;
|
|
42
|
+
constructor(opts) {
|
|
43
|
+
this.sessionManager = opts.sessionManager;
|
|
44
|
+
this.channelManager = opts.channelManager;
|
|
45
|
+
this.client = opts.client;
|
|
46
|
+
this.config = opts.config;
|
|
47
|
+
this.logger = opts.logger;
|
|
48
|
+
this.ownerId = opts.ownerId;
|
|
49
|
+
}
|
|
50
|
+
// -----------------------------------------------------------------------
|
|
51
|
+
// Lifecycle
|
|
52
|
+
// -----------------------------------------------------------------------
|
|
53
|
+
/** Subscribe to SessionManager events and Discord messageCreate. */
|
|
54
|
+
start() {
|
|
55
|
+
if (this.boundHandlers)
|
|
56
|
+
return; // already started
|
|
57
|
+
this.boundHandlers = {
|
|
58
|
+
started: (data) => {
|
|
59
|
+
void this.onSessionStarted(data);
|
|
60
|
+
},
|
|
61
|
+
event: (data) => {
|
|
62
|
+
void this.onSessionEvent(data);
|
|
63
|
+
},
|
|
64
|
+
blocked: (data) => {
|
|
65
|
+
void this.onSessionBlocked(data);
|
|
66
|
+
},
|
|
67
|
+
completed: (data) => {
|
|
68
|
+
void this.onSessionCompleted(data);
|
|
69
|
+
},
|
|
70
|
+
error: (data) => {
|
|
71
|
+
void this.onSessionError(data);
|
|
72
|
+
},
|
|
73
|
+
messageCreate: (msg) => {
|
|
74
|
+
void this.handleMessageCreate(msg);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
this.sessionManager.on('session:started', this.boundHandlers.started);
|
|
78
|
+
this.sessionManager.on('session:event', this.boundHandlers.event);
|
|
79
|
+
this.sessionManager.on('session:blocked', this.boundHandlers.blocked);
|
|
80
|
+
this.sessionManager.on('session:completed', this.boundHandlers.completed);
|
|
81
|
+
this.sessionManager.on('session:error', this.boundHandlers.error);
|
|
82
|
+
this.client.on('messageCreate', this.boundHandlers.messageCreate);
|
|
83
|
+
this.logger.info('event bridge started');
|
|
84
|
+
}
|
|
85
|
+
/** Unsubscribe from all events, destroy batchers, clear mappings. */
|
|
86
|
+
async stop() {
|
|
87
|
+
if (this.boundHandlers) {
|
|
88
|
+
this.sessionManager.off('session:started', this.boundHandlers.started);
|
|
89
|
+
this.sessionManager.off('session:event', this.boundHandlers.event);
|
|
90
|
+
this.sessionManager.off('session:blocked', this.boundHandlers.blocked);
|
|
91
|
+
this.sessionManager.off('session:completed', this.boundHandlers.completed);
|
|
92
|
+
this.sessionManager.off('session:error', this.boundHandlers.error);
|
|
93
|
+
this.client.off('messageCreate', this.boundHandlers.messageCreate);
|
|
94
|
+
this.boundHandlers = null;
|
|
95
|
+
}
|
|
96
|
+
// Destroy all batchers
|
|
97
|
+
const destroyPromises = [];
|
|
98
|
+
for (const batcher of this.batchers.values()) {
|
|
99
|
+
destroyPromises.push(batcher.destroy());
|
|
100
|
+
}
|
|
101
|
+
await Promise.allSettled(destroyPromises);
|
|
102
|
+
this.batchers.clear();
|
|
103
|
+
this.sessionToChannel.clear();
|
|
104
|
+
this.channelToSession.clear();
|
|
105
|
+
this.channels.clear();
|
|
106
|
+
this.logger.info('event bridge stopped');
|
|
107
|
+
}
|
|
108
|
+
/** Expose the verbosity manager for slash-command integration. */
|
|
109
|
+
getVerbosityManager() {
|
|
110
|
+
return this.verbosity;
|
|
111
|
+
}
|
|
112
|
+
// -----------------------------------------------------------------------
|
|
113
|
+
// SessionManager event handlers
|
|
114
|
+
// -----------------------------------------------------------------------
|
|
115
|
+
async onSessionStarted(data) {
|
|
116
|
+
const { sessionId, projectDir, projectName } = data;
|
|
117
|
+
try {
|
|
118
|
+
const channel = await this.channelManager.createProjectChannel(projectDir);
|
|
119
|
+
// Create batcher with channel.send as the send function
|
|
120
|
+
const batcher = new MessageBatcher(async (payload) => {
|
|
121
|
+
await channel.send(payload);
|
|
122
|
+
}, this.logger);
|
|
123
|
+
batcher.start();
|
|
124
|
+
// Register bidirectional mapping
|
|
125
|
+
this.sessionToChannel.set(sessionId, channel.id);
|
|
126
|
+
this.channelToSession.set(channel.id, sessionId);
|
|
127
|
+
this.batchers.set(sessionId, batcher);
|
|
128
|
+
this.channels.set(sessionId, channel);
|
|
129
|
+
// Post welcome embed
|
|
130
|
+
const welcome = formatSessionStarted(projectName);
|
|
131
|
+
batcher.enqueue(welcome);
|
|
132
|
+
this.logger.info('bridge: session channel created', {
|
|
133
|
+
sessionId,
|
|
134
|
+
channelId: channel.id,
|
|
135
|
+
projectName,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
// Failure mode: log error, skip streaming for this session
|
|
140
|
+
this.logger.error('bridge: channel creation failed', {
|
|
141
|
+
sessionId,
|
|
142
|
+
projectDir,
|
|
143
|
+
error: err instanceof Error ? err.message : String(err),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async onSessionEvent(data) {
|
|
148
|
+
const { sessionId, event } = data;
|
|
149
|
+
const channelId = this.sessionToChannel.get(sessionId);
|
|
150
|
+
if (!channelId)
|
|
151
|
+
return; // no channel for this session
|
|
152
|
+
// Verbosity filter
|
|
153
|
+
const eventType = event.type;
|
|
154
|
+
if (!this.verbosity.shouldShow(channelId, eventType))
|
|
155
|
+
return;
|
|
156
|
+
const formatted = formatEvent(event, this.ownerId);
|
|
157
|
+
const batcher = this.batchers.get(sessionId);
|
|
158
|
+
if (batcher) {
|
|
159
|
+
batcher.enqueue(formatted);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async onSessionBlocked(data) {
|
|
163
|
+
const { sessionId, projectName, blocker } = data;
|
|
164
|
+
const channel = this.channels.get(sessionId);
|
|
165
|
+
if (!channel)
|
|
166
|
+
return;
|
|
167
|
+
const formatted = formatBlocker(blocker, this.ownerId);
|
|
168
|
+
// Send immediately (bypasses batching for blockers)
|
|
169
|
+
const batcher = this.batchers.get(sessionId);
|
|
170
|
+
if (batcher) {
|
|
171
|
+
await batcher.enqueueImmediate(formatted);
|
|
172
|
+
}
|
|
173
|
+
// For select/confirm methods, set up button collector
|
|
174
|
+
if (blocker.method === 'select' || blocker.method === 'confirm') {
|
|
175
|
+
this.createButtonCollector(sessionId, channel, blocker);
|
|
176
|
+
}
|
|
177
|
+
// DM backup
|
|
178
|
+
if (this.config.discord?.dm_on_blocker) {
|
|
179
|
+
await this.sendBlockerDM(sessionId, projectName, blocker);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async onSessionCompleted(data) {
|
|
183
|
+
const { sessionId, projectName } = data;
|
|
184
|
+
const batcher = this.batchers.get(sessionId);
|
|
185
|
+
if (!batcher)
|
|
186
|
+
return;
|
|
187
|
+
const completion = formatCompletion({
|
|
188
|
+
type: 'execution_complete',
|
|
189
|
+
status: 'completed',
|
|
190
|
+
});
|
|
191
|
+
// Flush through batcher then cleanup
|
|
192
|
+
batcher.enqueue(completion);
|
|
193
|
+
await this.cleanupSession(sessionId);
|
|
194
|
+
this.logger.info('bridge: session completed', { sessionId, projectName });
|
|
195
|
+
}
|
|
196
|
+
async onSessionError(data) {
|
|
197
|
+
const { sessionId, projectName, error } = data;
|
|
198
|
+
const batcher = this.batchers.get(sessionId);
|
|
199
|
+
if (!batcher)
|
|
200
|
+
return;
|
|
201
|
+
const errorEmbed = formatError(sessionId, error);
|
|
202
|
+
batcher.enqueue(errorEmbed);
|
|
203
|
+
await this.cleanupSession(sessionId);
|
|
204
|
+
this.logger.info('bridge: session error', { sessionId, projectName, error });
|
|
205
|
+
}
|
|
206
|
+
// -----------------------------------------------------------------------
|
|
207
|
+
// Blocker resolution — button collector
|
|
208
|
+
// -----------------------------------------------------------------------
|
|
209
|
+
createButtonCollector(sessionId, channel, blocker) {
|
|
210
|
+
// Create a message collector on the channel for button interactions
|
|
211
|
+
// We use createMessageComponentCollector on the channel
|
|
212
|
+
try {
|
|
213
|
+
const collector = channel.createMessageComponentCollector({
|
|
214
|
+
componentType: ComponentType.Button,
|
|
215
|
+
time: BLOCKER_COLLECTOR_TIMEOUT_MS,
|
|
216
|
+
filter: (interaction) => {
|
|
217
|
+
return interaction.customId.startsWith(`blocker:${blocker.id}:`);
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
collector.on('collect', async (interaction) => {
|
|
221
|
+
// Auth guard
|
|
222
|
+
if (!isAuthorized(interaction.user.id, this.ownerId)) {
|
|
223
|
+
await interaction.reply({
|
|
224
|
+
content: '⛔ Only the project owner can respond to blockers.',
|
|
225
|
+
ephemeral: true,
|
|
226
|
+
}).catch(() => { });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Parse customId: blocker:{id}:{method}:{value}
|
|
230
|
+
const parts = interaction.customId.split(':');
|
|
231
|
+
const value = parts[3] ?? '';
|
|
232
|
+
try {
|
|
233
|
+
await this.sessionManager.resolveBlocker(sessionId, value);
|
|
234
|
+
await interaction.update({
|
|
235
|
+
content: `✅ Blocker resolved with: ${value}`,
|
|
236
|
+
components: [],
|
|
237
|
+
}).catch(() => { });
|
|
238
|
+
collector.stop('resolved');
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
242
|
+
this.logger.error('bridge: blocker resolve failed', { sessionId, error: errMsg });
|
|
243
|
+
await interaction.reply({
|
|
244
|
+
content: `❌ Failed to resolve blocker: ${errMsg}`,
|
|
245
|
+
ephemeral: true,
|
|
246
|
+
}).catch(() => { });
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
collector.on('end', (_collected, reason) => {
|
|
250
|
+
if (reason === 'time') {
|
|
251
|
+
// Timeout: edit to show expired
|
|
252
|
+
this.logger.info('bridge: blocker collector timed out', { sessionId, blockerId: blocker.id });
|
|
253
|
+
// Post a new message indicating expiry — editing original may fail
|
|
254
|
+
const batcher = this.batchers.get(sessionId);
|
|
255
|
+
if (batcher) {
|
|
256
|
+
batcher.enqueue({
|
|
257
|
+
content: `⏰ Blocker response timed out after 24h. Re-posting...`,
|
|
258
|
+
embed: new EmbedBuilder()
|
|
259
|
+
.setColor(0xf1c40f)
|
|
260
|
+
.setTitle('⏰ Blocker Expired')
|
|
261
|
+
.setDescription(blocker.message)
|
|
262
|
+
.setTimestamp(),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
this.logger.error('bridge: collector creation failed', {
|
|
270
|
+
sessionId,
|
|
271
|
+
error: err instanceof Error ? err.message : String(err),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// -----------------------------------------------------------------------
|
|
276
|
+
// DM backup
|
|
277
|
+
// -----------------------------------------------------------------------
|
|
278
|
+
async sendBlockerDM(sessionId, projectName, blocker) {
|
|
279
|
+
try {
|
|
280
|
+
const user = await this.client.users.fetch(this.ownerId);
|
|
281
|
+
await user.send({
|
|
282
|
+
content: `⚠️ **Blocker** in **${projectName}** — ${blocker.message}\n\nRespond in the project channel.`,
|
|
283
|
+
});
|
|
284
|
+
this.logger.debug('bridge: DM sent for blocker', { sessionId, blockerId: blocker.id });
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
// DM failure is non-fatal — channel message is the primary path
|
|
288
|
+
this.logger.warn('bridge: DM send failed', {
|
|
289
|
+
sessionId,
|
|
290
|
+
error: err instanceof Error ? err.message : String(err),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// -----------------------------------------------------------------------
|
|
295
|
+
// Conversation relay — Discord → GSD
|
|
296
|
+
// -----------------------------------------------------------------------
|
|
297
|
+
async handleMessageCreate(message) {
|
|
298
|
+
// Filter: bot messages
|
|
299
|
+
if (message.author.bot)
|
|
300
|
+
return;
|
|
301
|
+
// Filter: must be in a project channel
|
|
302
|
+
const sessionId = this.channelToSession.get(message.channelId);
|
|
303
|
+
if (!sessionId)
|
|
304
|
+
return;
|
|
305
|
+
// Filter: must be authorized
|
|
306
|
+
if (!isAuthorized(message.author.id, this.ownerId))
|
|
307
|
+
return;
|
|
308
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
309
|
+
if (!session)
|
|
310
|
+
return;
|
|
311
|
+
// If session has a pending blocker with input/editor method, resolve it
|
|
312
|
+
if (session.pendingBlocker && (session.pendingBlocker.method === 'input' || session.pendingBlocker.method === 'editor')) {
|
|
313
|
+
try {
|
|
314
|
+
await this.sessionManager.resolveBlocker(sessionId, message.content);
|
|
315
|
+
await message.react('✅').catch(() => { });
|
|
316
|
+
this.logger.info('bridge: blocker resolved via relay', {
|
|
317
|
+
sessionId,
|
|
318
|
+
method: session.pendingBlocker.method,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
323
|
+
this.logger.error('bridge: relay blocker resolve failed', { sessionId, error: errMsg });
|
|
324
|
+
await message.reply(`❌ Failed to resolve blocker: ${errMsg}`).catch(() => { });
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
// Otherwise, relay the message to the GSD session
|
|
329
|
+
// Use steer() when running (injects mid-turn), prompt() otherwise (starts new turn)
|
|
330
|
+
try {
|
|
331
|
+
if (session.status === 'running') {
|
|
332
|
+
await session.client.steer(message.content);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
await session.client.prompt(message.content);
|
|
336
|
+
}
|
|
337
|
+
await message.react('📨').catch(() => { });
|
|
338
|
+
this.logger.info('bridge: message relayed to session', {
|
|
339
|
+
sessionId,
|
|
340
|
+
method: session.status === 'running' ? 'steer' : 'prompt',
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
345
|
+
this.logger.error('bridge: relay failed', { sessionId, error: errMsg });
|
|
346
|
+
await message.reply(`❌ Failed to relay message: ${errMsg}`).catch(() => { });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// -----------------------------------------------------------------------
|
|
350
|
+
// Cleanup
|
|
351
|
+
// -----------------------------------------------------------------------
|
|
352
|
+
async cleanupSession(sessionId) {
|
|
353
|
+
const batcher = this.batchers.get(sessionId);
|
|
354
|
+
if (batcher) {
|
|
355
|
+
await batcher.destroy();
|
|
356
|
+
this.batchers.delete(sessionId);
|
|
357
|
+
}
|
|
358
|
+
const channelId = this.sessionToChannel.get(sessionId);
|
|
359
|
+
if (channelId) {
|
|
360
|
+
this.channelToSession.delete(channelId);
|
|
361
|
+
}
|
|
362
|
+
this.sessionToChannel.delete(sessionId);
|
|
363
|
+
this.channels.delete(sessionId);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
//# sourceMappingURL=event-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-bridge.js","sourceRoot":"","sources":["../src/event-bridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * event-bridge.ts — Orchestrator wiring SessionManager events through\n * formatter → batcher → Discord channels.\n *\n * Handles:\n * - Session lifecycle → Discord channel creation and cleanup\n * - Event streaming → format + verbosity filter + batcher\n * - Blocker resolution → interactive buttons + text relay\n * - Conversation relay → Discord messages forwarded to GSD sessions\n * - DM backup → owner gets DM on blocker when dm_on_blocker configured\n */\n\nimport type { Client, Message, TextChannel, MessageComponentInteraction } from 'discord.js';\nimport { EmbedBuilder, ComponentType } from 'discord.js';\nimport type { SdkAgentEvent } from '@opengsd/contracts';\nimport type { Logger } from './logger.js';\nimport type { DaemonConfig, PendingBlocker } from './types.js';\nimport type { SessionManager } from './session-manager.js';\nimport type { ChannelManager } from './channel-manager.js';\nimport { MessageBatcher } from './message-batcher.js';\nimport { VerbosityManager } from './verbosity.js';\nimport {\n formatEvent,\n formatBlocker,\n formatSessionStarted,\n formatError,\n formatCompletion,\n} from './event-formatter.js';\nimport { isAuthorized } from './discord-bot.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Minimal interface for a Discord client — extracted for testability. */\nexport interface BridgeClient {\n on(event: 'messageCreate', listener: (message: Message) => void): void;\n off(event: 'messageCreate', listener: (message: Message) => void): void;\n users: { fetch(id: string): Promise<{ send(opts: unknown): Promise<unknown> }> };\n}\n\n/** Options for creating an EventBridge. */\nexport interface EventBridgeOptions {\n sessionManager: SessionManager;\n channelManager: ChannelManager;\n client: BridgeClient;\n config: DaemonConfig;\n logger: Logger;\n ownerId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Collector timeout\n// ---------------------------------------------------------------------------\n\nconst BLOCKER_COLLECTOR_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24 hours\n\n// ---------------------------------------------------------------------------\n// EventBridge\n// ---------------------------------------------------------------------------\n\nexport class EventBridge {\n private readonly sessionManager: SessionManager;\n private readonly channelManager: ChannelManager;\n private readonly client: BridgeClient;\n private readonly config: DaemonConfig;\n private readonly logger: Logger;\n private readonly ownerId: string;\n\n /** sessionId → channelId */\n private readonly sessionToChannel = new Map<string, string>();\n /** channelId → sessionId */\n private readonly channelToSession = new Map<string, string>();\n /** sessionId → MessageBatcher */\n private readonly batchers = new Map<string, MessageBatcher>();\n /** sessionId → TextChannel (cached for send operations) */\n private readonly channels = new Map<string, TextChannel>();\n\n private readonly verbosity = new VerbosityManager();\n\n /** Bound event handlers for cleanup */\n private boundHandlers: {\n started: (...args: unknown[]) => void;\n event: (...args: unknown[]) => void;\n blocked: (...args: unknown[]) => void;\n completed: (...args: unknown[]) => void;\n error: (...args: unknown[]) => void;\n messageCreate: (msg: Message) => void;\n } | null = null;\n\n constructor(opts: EventBridgeOptions) {\n this.sessionManager = opts.sessionManager;\n this.channelManager = opts.channelManager;\n this.client = opts.client;\n this.config = opts.config;\n this.logger = opts.logger;\n this.ownerId = opts.ownerId;\n }\n\n // -----------------------------------------------------------------------\n // Lifecycle\n // -----------------------------------------------------------------------\n\n /** Subscribe to SessionManager events and Discord messageCreate. */\n start(): void {\n if (this.boundHandlers) return; // already started\n\n this.boundHandlers = {\n started: (data: unknown) => {\n void this.onSessionStarted(data as SessionStartedPayload);\n },\n event: (data: unknown) => {\n void this.onSessionEvent(data as SessionEventPayload);\n },\n blocked: (data: unknown) => {\n void this.onSessionBlocked(data as SessionBlockedPayload);\n },\n completed: (data: unknown) => {\n void this.onSessionCompleted(data as SessionCompletedPayload);\n },\n error: (data: unknown) => {\n void this.onSessionError(data as SessionErrorPayload);\n },\n messageCreate: (msg: Message) => {\n void this.handleMessageCreate(msg);\n },\n };\n\n this.sessionManager.on('session:started', this.boundHandlers.started);\n this.sessionManager.on('session:event', this.boundHandlers.event);\n this.sessionManager.on('session:blocked', this.boundHandlers.blocked);\n this.sessionManager.on('session:completed', this.boundHandlers.completed);\n this.sessionManager.on('session:error', this.boundHandlers.error);\n this.client.on('messageCreate', this.boundHandlers.messageCreate);\n\n this.logger.info('event bridge started');\n }\n\n /** Unsubscribe from all events, destroy batchers, clear mappings. */\n async stop(): Promise<void> {\n if (this.boundHandlers) {\n this.sessionManager.off('session:started', this.boundHandlers.started);\n this.sessionManager.off('session:event', this.boundHandlers.event);\n this.sessionManager.off('session:blocked', this.boundHandlers.blocked);\n this.sessionManager.off('session:completed', this.boundHandlers.completed);\n this.sessionManager.off('session:error', this.boundHandlers.error);\n this.client.off('messageCreate', this.boundHandlers.messageCreate);\n this.boundHandlers = null;\n }\n\n // Destroy all batchers\n const destroyPromises: Promise<void>[] = [];\n for (const batcher of this.batchers.values()) {\n destroyPromises.push(batcher.destroy());\n }\n await Promise.allSettled(destroyPromises);\n\n this.batchers.clear();\n this.sessionToChannel.clear();\n this.channelToSession.clear();\n this.channels.clear();\n\n this.logger.info('event bridge stopped');\n }\n\n /** Expose the verbosity manager for slash-command integration. */\n getVerbosityManager(): VerbosityManager {\n return this.verbosity;\n }\n\n // -----------------------------------------------------------------------\n // SessionManager event handlers\n // -----------------------------------------------------------------------\n\n private async onSessionStarted(data: SessionStartedPayload): Promise<void> {\n const { sessionId, projectDir, projectName } = data;\n\n try {\n const channel = await this.channelManager.createProjectChannel(projectDir);\n\n // Create batcher with channel.send as the send function\n const batcher = new MessageBatcher(\n async (payload) => {\n await channel.send(payload as Parameters<TextChannel['send']>[0]);\n },\n this.logger,\n );\n batcher.start();\n\n // Register bidirectional mapping\n this.sessionToChannel.set(sessionId, channel.id);\n this.channelToSession.set(channel.id, sessionId);\n this.batchers.set(sessionId, batcher);\n this.channels.set(sessionId, channel);\n\n // Post welcome embed\n const welcome = formatSessionStarted(projectName);\n batcher.enqueue(welcome);\n\n this.logger.info('bridge: session channel created', {\n sessionId,\n channelId: channel.id,\n projectName,\n });\n } catch (err) {\n // Failure mode: log error, skip streaming for this session\n this.logger.error('bridge: channel creation failed', {\n sessionId,\n projectDir,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private async onSessionEvent(data: SessionEventPayload): Promise<void> {\n const { sessionId, event } = data;\n const channelId = this.sessionToChannel.get(sessionId);\n if (!channelId) return; // no channel for this session\n\n // Verbosity filter\n const eventType = (event as Record<string, unknown>).type as string;\n if (!this.verbosity.shouldShow(channelId, eventType)) return;\n\n const formatted = formatEvent(event, this.ownerId);\n const batcher = this.batchers.get(sessionId);\n if (batcher) {\n batcher.enqueue(formatted);\n }\n }\n\n private async onSessionBlocked(data: SessionBlockedPayload): Promise<void> {\n const { sessionId, projectName, blocker } = data;\n const channel = this.channels.get(sessionId);\n if (!channel) return;\n\n const formatted = formatBlocker(blocker, this.ownerId);\n\n // Send immediately (bypasses batching for blockers)\n const batcher = this.batchers.get(sessionId);\n if (batcher) {\n await batcher.enqueueImmediate(formatted);\n }\n\n // For select/confirm methods, set up button collector\n if (blocker.method === 'select' || blocker.method === 'confirm') {\n this.createButtonCollector(sessionId, channel, blocker);\n }\n\n // DM backup\n if (this.config.discord?.dm_on_blocker) {\n await this.sendBlockerDM(sessionId, projectName, blocker);\n }\n }\n\n private async onSessionCompleted(data: SessionCompletedPayload): Promise<void> {\n const { sessionId, projectName } = data;\n const batcher = this.batchers.get(sessionId);\n if (!batcher) return;\n\n const completion = formatCompletion({\n type: 'execution_complete',\n status: 'completed',\n } as SdkAgentEvent);\n\n // Flush through batcher then cleanup\n batcher.enqueue(completion);\n await this.cleanupSession(sessionId);\n\n this.logger.info('bridge: session completed', { sessionId, projectName });\n }\n\n private async onSessionError(data: SessionErrorPayload): Promise<void> {\n const { sessionId, projectName, error } = data;\n const batcher = this.batchers.get(sessionId);\n if (!batcher) return;\n\n const errorEmbed = formatError(sessionId, error);\n batcher.enqueue(errorEmbed);\n await this.cleanupSession(sessionId);\n\n this.logger.info('bridge: session error', { sessionId, projectName, error });\n }\n\n // -----------------------------------------------------------------------\n // Blocker resolution — button collector\n // -----------------------------------------------------------------------\n\n private createButtonCollector(\n sessionId: string,\n channel: TextChannel,\n blocker: PendingBlocker,\n ): void {\n // Create a message collector on the channel for button interactions\n // We use createMessageComponentCollector on the channel\n try {\n const collector = channel.createMessageComponentCollector({\n componentType: ComponentType.Button,\n time: BLOCKER_COLLECTOR_TIMEOUT_MS,\n filter: (interaction: MessageComponentInteraction) => {\n return interaction.customId.startsWith(`blocker:${blocker.id}:`);\n },\n });\n\n collector.on('collect', async (interaction: MessageComponentInteraction) => {\n // Auth guard\n if (!isAuthorized(interaction.user.id, this.ownerId)) {\n await interaction.reply({\n content: '⛔ Only the project owner can respond to blockers.',\n ephemeral: true,\n }).catch(() => {});\n return;\n }\n\n // Parse customId: blocker:{id}:{method}:{value}\n const parts = interaction.customId.split(':');\n const value = parts[3] ?? '';\n\n try {\n await this.sessionManager.resolveBlocker(sessionId, value);\n await interaction.update({\n content: `✅ Blocker resolved with: ${value}`,\n components: [],\n }).catch(() => {});\n collector.stop('resolved');\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n this.logger.error('bridge: blocker resolve failed', { sessionId, error: errMsg });\n await interaction.reply({\n content: `❌ Failed to resolve blocker: ${errMsg}`,\n ephemeral: true,\n }).catch(() => {});\n }\n });\n\n collector.on('end', (_collected, reason) => {\n if (reason === 'time') {\n // Timeout: edit to show expired\n this.logger.info('bridge: blocker collector timed out', { sessionId, blockerId: blocker.id });\n // Post a new message indicating expiry — editing original may fail\n const batcher = this.batchers.get(sessionId);\n if (batcher) {\n batcher.enqueue({\n content: `⏰ Blocker response timed out after 24h. Re-posting...`,\n embed: new EmbedBuilder()\n .setColor(0xf1c40f)\n .setTitle('⏰ Blocker Expired')\n .setDescription(blocker.message)\n .setTimestamp(),\n });\n }\n }\n });\n } catch (err) {\n this.logger.error('bridge: collector creation failed', {\n sessionId,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // -----------------------------------------------------------------------\n // DM backup\n // -----------------------------------------------------------------------\n\n private async sendBlockerDM(\n sessionId: string,\n projectName: string,\n blocker: PendingBlocker,\n ): Promise<void> {\n try {\n const user = await this.client.users.fetch(this.ownerId);\n await user.send({\n content: `⚠️ **Blocker** in **${projectName}** — ${blocker.message}\\n\\nRespond in the project channel.`,\n });\n this.logger.debug('bridge: DM sent for blocker', { sessionId, blockerId: blocker.id });\n } catch (err) {\n // DM failure is non-fatal — channel message is the primary path\n this.logger.warn('bridge: DM send failed', {\n sessionId,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n // -----------------------------------------------------------------------\n // Conversation relay — Discord → GSD\n // -----------------------------------------------------------------------\n\n private async handleMessageCreate(message: Message): Promise<void> {\n // Filter: bot messages\n if (message.author.bot) return;\n\n // Filter: must be in a project channel\n const sessionId = this.channelToSession.get(message.channelId);\n if (!sessionId) return;\n\n // Filter: must be authorized\n if (!isAuthorized(message.author.id, this.ownerId)) return;\n\n const session = this.sessionManager.getSession(sessionId);\n if (!session) return;\n\n // If session has a pending blocker with input/editor method, resolve it\n if (session.pendingBlocker && (session.pendingBlocker.method === 'input' || session.pendingBlocker.method === 'editor')) {\n try {\n await this.sessionManager.resolveBlocker(sessionId, message.content);\n await message.react('✅').catch(() => {});\n this.logger.info('bridge: blocker resolved via relay', {\n sessionId,\n method: session.pendingBlocker.method,\n });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n this.logger.error('bridge: relay blocker resolve failed', { sessionId, error: errMsg });\n await message.reply(`❌ Failed to resolve blocker: ${errMsg}`).catch(() => {});\n }\n return;\n }\n\n // Otherwise, relay the message to the GSD session\n // Use steer() when running (injects mid-turn), prompt() otherwise (starts new turn)\n try {\n if (session.status === 'running') {\n await session.client.steer(message.content);\n } else {\n await session.client.prompt(message.content);\n }\n await message.react('📨').catch(() => {});\n this.logger.info('bridge: message relayed to session', {\n sessionId,\n method: session.status === 'running' ? 'steer' : 'prompt',\n });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n this.logger.error('bridge: relay failed', { sessionId, error: errMsg });\n await message.reply(`❌ Failed to relay message: ${errMsg}`).catch(() => {});\n }\n }\n\n // -----------------------------------------------------------------------\n // Cleanup\n // -----------------------------------------------------------------------\n\n private async cleanupSession(sessionId: string): Promise<void> {\n const batcher = this.batchers.get(sessionId);\n if (batcher) {\n await batcher.destroy();\n this.batchers.delete(sessionId);\n }\n\n const channelId = this.sessionToChannel.get(sessionId);\n if (channelId) {\n this.channelToSession.delete(channelId);\n }\n this.sessionToChannel.delete(sessionId);\n this.channels.delete(sessionId);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal event payload types (matching SessionManager emissions)\n// ---------------------------------------------------------------------------\n\ninterface SessionStartedPayload {\n sessionId: string;\n projectDir: string;\n projectName: string;\n}\n\ninterface SessionEventPayload {\n sessionId: string;\n projectDir: string;\n event: SdkAgentEvent;\n}\n\ninterface SessionBlockedPayload {\n sessionId: string;\n projectDir: string;\n projectName: string;\n blocker: PendingBlocker;\n}\n\ninterface SessionCompletedPayload {\n sessionId: string;\n projectDir: string;\n projectName: string;\n}\n\ninterface SessionErrorPayload {\n sessionId: string;\n projectDir: string;\n projectName: string;\n error: string;\n}\n"]}
|