gsd-pi 2.67.0-dev.509bd95 → 2.67.0-dev.5399650

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 (125) hide show
  1. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  2. package/dist/resources/extensions/gsd/auto.js +27 -0
  3. package/dist/resources/extensions/gsd/init-wizard.js +34 -0
  4. package/dist/resources/extensions/gsd/workflow-mcp.js +38 -7
  5. package/dist/web/standalone/.next/BUILD_ID +1 -1
  6. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  7. package/dist/web/standalone/.next/build-manifest.json +2 -2
  8. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  9. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  10. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  18. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/index.html +1 -1
  26. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  33. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  34. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  35. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  36. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  37. package/package.json +4 -2
  38. package/packages/mcp-server/dist/cli.d.ts +9 -0
  39. package/packages/mcp-server/dist/cli.d.ts.map +1 -0
  40. package/packages/mcp-server/dist/cli.js +58 -0
  41. package/packages/mcp-server/dist/cli.js.map +1 -0
  42. package/packages/mcp-server/dist/index.d.ts +20 -0
  43. package/packages/mcp-server/dist/index.d.ts.map +1 -0
  44. package/packages/mcp-server/dist/index.js +14 -0
  45. package/packages/mcp-server/dist/index.js.map +1 -0
  46. package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
  47. package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
  48. package/packages/mcp-server/dist/readers/captures.js +67 -0
  49. package/packages/mcp-server/dist/readers/captures.js.map +1 -0
  50. package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
  51. package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
  52. package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
  53. package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
  54. package/packages/mcp-server/dist/readers/index.d.ts +14 -0
  55. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
  56. package/packages/mcp-server/dist/readers/index.js +10 -0
  57. package/packages/mcp-server/dist/readers/index.js.map +1 -0
  58. package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
  59. package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
  60. package/packages/mcp-server/dist/readers/knowledge.js +82 -0
  61. package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
  62. package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
  63. package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
  64. package/packages/mcp-server/dist/readers/metrics.js +74 -0
  65. package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
  66. package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
  67. package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
  68. package/packages/mcp-server/dist/readers/paths.js +199 -0
  69. package/packages/mcp-server/dist/readers/paths.js.map +1 -0
  70. package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
  71. package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
  72. package/packages/mcp-server/dist/readers/roadmap.js +194 -0
  73. package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
  74. package/packages/mcp-server/dist/readers/state.d.ts +43 -0
  75. package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
  76. package/packages/mcp-server/dist/readers/state.js +184 -0
  77. package/packages/mcp-server/dist/readers/state.js.map +1 -0
  78. package/packages/mcp-server/dist/server.d.ts +28 -0
  79. package/packages/mcp-server/dist/server.d.ts.map +1 -0
  80. package/packages/mcp-server/dist/server.js +319 -0
  81. package/packages/mcp-server/dist/server.js.map +1 -0
  82. package/packages/mcp-server/dist/session-manager.d.ts +54 -0
  83. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
  84. package/packages/mcp-server/dist/session-manager.js +284 -0
  85. package/packages/mcp-server/dist/session-manager.js.map +1 -0
  86. package/packages/mcp-server/dist/types.d.ts +61 -0
  87. package/packages/mcp-server/dist/types.d.ts.map +1 -0
  88. package/packages/mcp-server/dist/types.js +11 -0
  89. package/packages/mcp-server/dist/types.js.map +1 -0
  90. package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
  91. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
  92. package/packages/mcp-server/dist/workflow-tools.js +526 -0
  93. package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
  94. package/packages/mcp-server/tsconfig.json +1 -1
  95. package/packages/rpc-client/dist/index.d.ts +10 -0
  96. package/packages/rpc-client/dist/index.d.ts.map +1 -0
  97. package/packages/rpc-client/dist/index.js +9 -0
  98. package/packages/rpc-client/dist/index.js.map +1 -0
  99. package/packages/rpc-client/dist/jsonl.d.ts +17 -0
  100. package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
  101. package/packages/rpc-client/dist/jsonl.js +54 -0
  102. package/packages/rpc-client/dist/jsonl.js.map +1 -0
  103. package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
  104. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
  105. package/packages/rpc-client/dist/rpc-client.js +541 -0
  106. package/packages/rpc-client/dist/rpc-client.js.map +1 -0
  107. package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
  108. package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
  109. package/packages/rpc-client/dist/rpc-client.test.js +477 -0
  110. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
  111. package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
  112. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
  113. package/packages/rpc-client/dist/rpc-types.js +12 -0
  114. package/packages/rpc-client/dist/rpc-types.js.map +1 -0
  115. package/scripts/ensure-workspace-builds.cjs +2 -0
  116. package/scripts/link-workspace-packages.cjs +21 -14
  117. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  118. package/src/resources/extensions/gsd/auto.ts +29 -1
  119. package/src/resources/extensions/gsd/init-wizard.ts +34 -0
  120. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
  121. package/src/resources/extensions/gsd/tests/init-bootstrap-completeness.test.ts +121 -0
  122. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +39 -1
  123. package/src/resources/extensions/gsd/workflow-mcp.ts +41 -7
  124. /package/dist/web/standalone/.next/static/{mHJZ3Z8yGRzZ32BmQs-I7 → 6_QPFhgX0DQnDhhquheRc}/_buildManifest.js +0 -0
  125. /package/dist/web/standalone/.next/static/{mHJZ3Z8yGRzZ32BmQs-I7 → 6_QPFhgX0DQnDhhquheRc}/_ssgManifest.js +0 -0
@@ -0,0 +1,284 @@
1
+ /**
2
+ * SessionManager — manages RpcClient lifecycle for background GSD execution.
3
+ *
4
+ * One active session per projectDir. Tracks events in a ring buffer,
5
+ * detects blockers, tracks terminal state, and accumulates cost using
6
+ * the cumulative-max pattern (K004).
7
+ */
8
+ import { execSync } from 'node:child_process';
9
+ import { resolve } from 'node:path';
10
+ import { RpcClient } from '@gsd-build/rpc-client';
11
+ import { MAX_EVENTS, INIT_TIMEOUT_MS } from './types.js';
12
+ // ---------------------------------------------------------------------------
13
+ // Inlined detection logic (from headless-events.ts — no internal package imports)
14
+ // ---------------------------------------------------------------------------
15
+ const FIRE_AND_FORGET_METHODS = new Set([
16
+ 'notify', 'setStatus', 'setWidget', 'setTitle', 'set_editor_text',
17
+ ]);
18
+ const TERMINAL_PREFIXES = ['auto-mode stopped', 'step-mode stopped'];
19
+ function isTerminalNotification(event) {
20
+ if (event.type !== 'extension_ui_request' || event.method !== 'notify')
21
+ return false;
22
+ const message = String(event.message ?? '').toLowerCase();
23
+ return TERMINAL_PREFIXES.some((prefix) => message.startsWith(prefix));
24
+ }
25
+ function isBlockedNotification(event) {
26
+ if (event.type !== 'extension_ui_request' || event.method !== 'notify')
27
+ return false;
28
+ const message = String(event.message ?? '').toLowerCase();
29
+ return message.includes('blocked:');
30
+ }
31
+ function isBlockingUIRequest(event) {
32
+ if (event.type !== 'extension_ui_request')
33
+ return false;
34
+ const method = String(event.method ?? '');
35
+ return !FIRE_AND_FORGET_METHODS.has(method);
36
+ }
37
+ // ---------------------------------------------------------------------------
38
+ // SessionManager
39
+ // ---------------------------------------------------------------------------
40
+ export class SessionManager {
41
+ /** Sessions keyed by projectDir for duplicate-start prevention */
42
+ sessions = new Map();
43
+ /**
44
+ * Start a new GSD auto-mode session for the given project directory.
45
+ *
46
+ * Rejects if a session already exists for this projectDir.
47
+ * Creates an RpcClient, starts the process, performs the v2 init handshake,
48
+ * wires event tracking, and sends '/gsd auto' to begin execution.
49
+ */
50
+ async startSession(projectDir, options = {}) {
51
+ if (!projectDir || projectDir.trim() === '') {
52
+ throw new Error('projectDir is required and cannot be empty');
53
+ }
54
+ const resolvedDir = resolve(projectDir);
55
+ const existing = this.sessions.get(resolvedDir);
56
+ if (existing) {
57
+ throw new Error(`Session already active for ${resolvedDir} (sessionId: ${existing.sessionId}, status: ${existing.status})`);
58
+ }
59
+ const cliPath = options.cliPath ?? SessionManager.resolveCLIPath();
60
+ const args = ['--mode', 'rpc'];
61
+ if (options.model)
62
+ args.push('--model', options.model);
63
+ if (options.bare)
64
+ args.push('--bare');
65
+ const client = new RpcClient({
66
+ cliPath,
67
+ cwd: resolvedDir,
68
+ args,
69
+ });
70
+ // Build the session shell before async operations so we can track state
71
+ const session = {
72
+ sessionId: '', // filled after init
73
+ projectDir: resolvedDir,
74
+ status: 'starting',
75
+ client,
76
+ events: [],
77
+ pendingBlocker: null,
78
+ cost: { totalCost: 0, tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } },
79
+ startTime: Date.now(),
80
+ };
81
+ // Insert into map early (keyed by dir) so concurrent starts are rejected
82
+ this.sessions.set(resolvedDir, session);
83
+ try {
84
+ // Start the process with timeout
85
+ await Promise.race([
86
+ client.start(),
87
+ timeout(INIT_TIMEOUT_MS, `RpcClient.start() timed out after ${INIT_TIMEOUT_MS}ms`),
88
+ ]);
89
+ // Perform v2 init handshake
90
+ const initResult = await Promise.race([
91
+ client.init(),
92
+ timeout(INIT_TIMEOUT_MS, `RpcClient.init() timed out after ${INIT_TIMEOUT_MS}ms`),
93
+ ]);
94
+ session.sessionId = initResult.sessionId;
95
+ session.status = 'running';
96
+ // Wire event tracking
97
+ session.unsubscribe = client.onEvent((event) => {
98
+ this.handleEvent(session, event);
99
+ });
100
+ // Kick off auto-mode
101
+ const command = options.command ?? '/gsd auto';
102
+ await client.prompt(command);
103
+ return session.sessionId;
104
+ }
105
+ catch (err) {
106
+ session.status = 'error';
107
+ session.error = err instanceof Error ? err.message : String(err);
108
+ // Attempt cleanup
109
+ try {
110
+ await client.stop();
111
+ }
112
+ catch { /* swallow cleanup errors */ }
113
+ // Keep session in map so callers can inspect the error
114
+ throw new Error(`Failed to start session for ${resolvedDir}: ${session.error}`);
115
+ }
116
+ }
117
+ /**
118
+ * Look up a session by sessionId.
119
+ * Linear scan is fine — we expect <10 concurrent sessions.
120
+ */
121
+ getSession(sessionId) {
122
+ for (const session of this.sessions.values()) {
123
+ if (session.sessionId === sessionId)
124
+ return session;
125
+ }
126
+ return undefined;
127
+ }
128
+ /**
129
+ * Look up a session by project directory (direct map lookup).
130
+ */
131
+ getSessionByDir(projectDir) {
132
+ return this.sessions.get(resolve(projectDir));
133
+ }
134
+ /**
135
+ * Resolve a pending blocker by sending a UI response.
136
+ */
137
+ async resolveBlocker(sessionId, response) {
138
+ const session = this.getSession(sessionId);
139
+ if (!session)
140
+ throw new Error(`Session not found: ${sessionId}`);
141
+ if (!session.pendingBlocker)
142
+ throw new Error(`No pending blocker for session ${sessionId}`);
143
+ const blocker = session.pendingBlocker;
144
+ session.client.sendUIResponse(blocker.id, { value: response });
145
+ session.pendingBlocker = null;
146
+ if (session.status === 'blocked') {
147
+ session.status = 'running';
148
+ }
149
+ }
150
+ /**
151
+ * Cancel a running session — abort current operation then stop the process.
152
+ */
153
+ async cancelSession(sessionId) {
154
+ const session = this.getSession(sessionId);
155
+ if (!session)
156
+ throw new Error(`Session not found: ${sessionId}`);
157
+ try {
158
+ await session.client.abort();
159
+ }
160
+ catch { /* may already be stopped */ }
161
+ try {
162
+ await session.client.stop();
163
+ }
164
+ catch { /* swallow */ }
165
+ session.status = 'cancelled';
166
+ session.unsubscribe?.();
167
+ }
168
+ /**
169
+ * Build a HeadlessJsonResult-shaped object from accumulated session state.
170
+ */
171
+ getResult(sessionId) {
172
+ const session = this.getSession(sessionId);
173
+ if (!session)
174
+ throw new Error(`Session not found: ${sessionId}`);
175
+ const durationMs = Date.now() - session.startTime;
176
+ return {
177
+ sessionId: session.sessionId,
178
+ projectDir: session.projectDir,
179
+ status: session.status,
180
+ durationMs,
181
+ cost: session.cost,
182
+ recentEvents: session.events.slice(-10),
183
+ pendingBlocker: session.pendingBlocker
184
+ ? { id: session.pendingBlocker.id, method: session.pendingBlocker.method, message: session.pendingBlocker.message }
185
+ : null,
186
+ error: session.error ?? null,
187
+ };
188
+ }
189
+ /**
190
+ * Stop all active sessions and clean up resources.
191
+ */
192
+ async cleanup() {
193
+ const stopPromises = [];
194
+ for (const session of this.sessions.values()) {
195
+ session.unsubscribe?.();
196
+ if (session.status === 'running' || session.status === 'starting' || session.status === 'blocked') {
197
+ stopPromises.push(session.client.stop().catch(() => { }));
198
+ session.status = 'cancelled';
199
+ }
200
+ }
201
+ await Promise.allSettled(stopPromises);
202
+ }
203
+ /**
204
+ * Resolve the GSD CLI path.
205
+ *
206
+ * 1. GSD_CLI_PATH env var (highest priority)
207
+ * 2. `which gsd` → resolve to the actual dist/cli.js
208
+ */
209
+ static resolveCLIPath() {
210
+ // Check env var first
211
+ const envPath = process.env['GSD_CLI_PATH'];
212
+ if (envPath)
213
+ return resolve(envPath);
214
+ // Fallback: locate `gsd` via which
215
+ try {
216
+ const gsdBin = execSync('which gsd', { encoding: 'utf-8' }).trim();
217
+ if (gsdBin) {
218
+ // gsd bin is typically a symlink to dist/loader.js — return the resolved path
219
+ return resolve(gsdBin);
220
+ }
221
+ }
222
+ catch {
223
+ // which failed
224
+ }
225
+ throw new Error('Cannot find GSD CLI. Set GSD_CLI_PATH environment variable or ensure `gsd` is in PATH.');
226
+ }
227
+ // ---------------------------------------------------------------------------
228
+ // Private: Event Handling
229
+ // ---------------------------------------------------------------------------
230
+ handleEvent(session, event) {
231
+ // Ring buffer: push and trim
232
+ session.events.push(event);
233
+ if (session.events.length > MAX_EVENTS) {
234
+ session.events.splice(0, session.events.length - MAX_EVENTS);
235
+ }
236
+ // Cost tracking (K004 — cumulative-max)
237
+ if (event.type === 'cost_update') {
238
+ const costEvent = event;
239
+ session.cost.totalCost = Math.max(session.cost.totalCost, costEvent.cumulativeCost ?? 0);
240
+ if (costEvent.tokens) {
241
+ session.cost.tokens.input = Math.max(session.cost.tokens.input, costEvent.tokens.input ?? 0);
242
+ session.cost.tokens.output = Math.max(session.cost.tokens.output, costEvent.tokens.output ?? 0);
243
+ session.cost.tokens.cacheRead = Math.max(session.cost.tokens.cacheRead, costEvent.tokens.cacheRead ?? 0);
244
+ session.cost.tokens.cacheWrite = Math.max(session.cost.tokens.cacheWrite, costEvent.tokens.cacheWrite ?? 0);
245
+ }
246
+ }
247
+ // Terminal detection — auto-mode/step-mode stopped
248
+ if (isTerminalNotification(event)) {
249
+ // Check if it's a blocked stop (not truly terminal — it's a blocker)
250
+ if (isBlockedNotification(event)) {
251
+ session.status = 'blocked';
252
+ session.pendingBlocker = extractBlocker(event);
253
+ }
254
+ else {
255
+ session.status = 'completed';
256
+ session.unsubscribe?.();
257
+ }
258
+ return;
259
+ }
260
+ // Blocker detection — non-fire-and-forget extension_ui_request
261
+ if (isBlockingUIRequest(event)) {
262
+ session.status = 'blocked';
263
+ session.pendingBlocker = extractBlocker(event);
264
+ }
265
+ }
266
+ }
267
+ // ---------------------------------------------------------------------------
268
+ // Helpers
269
+ // ---------------------------------------------------------------------------
270
+ function timeout(ms, message) {
271
+ return new Promise((_, reject) => {
272
+ setTimeout(() => reject(new Error(message)), ms);
273
+ });
274
+ }
275
+ function extractBlocker(event) {
276
+ const uiEvent = event;
277
+ return {
278
+ id: String(uiEvent.id ?? ''),
279
+ method: String(uiEvent.method ?? ''),
280
+ message: String(uiEvent.title ?? uiEvent.message ?? ''),
281
+ event: uiEvent,
282
+ };
283
+ }
284
+ //# sourceMappingURL=session-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SessionManager — manages RpcClient lifecycle for background GSD execution.\n *\n * One active session per projectDir. Tracks events in a ring buffer,\n * detects blockers, tracks terminal state, and accumulates cost using\n * the cumulative-max pattern (K004).\n */\n\nimport { execSync } from 'node:child_process';\nimport { resolve } from 'node:path';\nimport { RpcClient } from '@gsd-build/rpc-client';\nimport type { SdkAgentEvent, RpcInitResult, RpcCostUpdateEvent, RpcExtensionUIRequest } from '@gsd-build/rpc-client';\nimport type {\n ManagedSession,\n ExecuteOptions,\n PendingBlocker,\n CostAccumulator,\n SessionStatus,\n} from './types.js';\nimport { MAX_EVENTS, INIT_TIMEOUT_MS } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Inlined detection logic (from headless-events.ts — no internal package imports)\n// ---------------------------------------------------------------------------\n\nconst FIRE_AND_FORGET_METHODS = new Set([\n 'notify', 'setStatus', 'setWidget', 'setTitle', 'set_editor_text',\n]);\n\nconst TERMINAL_PREFIXES = ['auto-mode stopped', 'step-mode stopped'];\n\nfunction isTerminalNotification(event: Record<string, unknown>): boolean {\n if (event.type !== 'extension_ui_request' || event.method !== 'notify') return false;\n const message = String(event.message ?? '').toLowerCase();\n return TERMINAL_PREFIXES.some((prefix) => message.startsWith(prefix));\n}\n\nfunction isBlockedNotification(event: Record<string, unknown>): boolean {\n if (event.type !== 'extension_ui_request' || event.method !== 'notify') return false;\n const message = String(event.message ?? '').toLowerCase();\n return message.includes('blocked:');\n}\n\nfunction isBlockingUIRequest(event: Record<string, unknown>): boolean {\n if (event.type !== 'extension_ui_request') return false;\n const method = String(event.method ?? '');\n return !FIRE_AND_FORGET_METHODS.has(method);\n}\n\n// ---------------------------------------------------------------------------\n// SessionManager\n// ---------------------------------------------------------------------------\n\nexport class SessionManager {\n /** Sessions keyed by projectDir for duplicate-start prevention */\n private sessions = new Map<string, ManagedSession>();\n\n /**\n * Start a new GSD auto-mode session for the given project directory.\n *\n * Rejects if a session already exists for this projectDir.\n * Creates an RpcClient, starts the process, performs the v2 init handshake,\n * wires event tracking, and sends '/gsd auto' to begin execution.\n */\n async startSession(projectDir: string, options: ExecuteOptions = {}): Promise<string> {\n if (!projectDir || projectDir.trim() === '') {\n throw new Error('projectDir is required and cannot be empty');\n }\n\n const resolvedDir = resolve(projectDir);\n\n const existing = this.sessions.get(resolvedDir);\n if (existing) {\n throw new Error(\n `Session already active for ${resolvedDir} (sessionId: ${existing.sessionId}, status: ${existing.status})`\n );\n }\n\n const cliPath = options.cliPath ?? SessionManager.resolveCLIPath();\n\n const args: string[] = ['--mode', 'rpc'];\n if (options.model) args.push('--model', options.model);\n if (options.bare) args.push('--bare');\n\n const client = new RpcClient({\n cliPath,\n cwd: resolvedDir,\n args,\n });\n\n // Build the session shell before async operations so we can track state\n const session: ManagedSession = {\n sessionId: '', // filled after init\n projectDir: resolvedDir,\n status: 'starting',\n client,\n events: [],\n pendingBlocker: null,\n cost: { totalCost: 0, tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } },\n startTime: Date.now(),\n };\n\n // Insert into map early (keyed by dir) so concurrent starts are rejected\n this.sessions.set(resolvedDir, session);\n\n try {\n // Start the process with timeout\n await Promise.race([\n client.start(),\n timeout(INIT_TIMEOUT_MS, `RpcClient.start() timed out after ${INIT_TIMEOUT_MS}ms`),\n ]);\n\n // Perform v2 init handshake\n const initResult: RpcInitResult = await Promise.race([\n client.init(),\n timeout(INIT_TIMEOUT_MS, `RpcClient.init() timed out after ${INIT_TIMEOUT_MS}ms`),\n ]) as RpcInitResult;\n\n session.sessionId = initResult.sessionId;\n session.status = 'running';\n\n // Wire event tracking\n session.unsubscribe = client.onEvent((event: SdkAgentEvent) => {\n this.handleEvent(session, event);\n });\n\n // Kick off auto-mode\n const command = options.command ?? '/gsd auto';\n await client.prompt(command);\n\n return session.sessionId;\n } catch (err) {\n session.status = 'error';\n session.error = err instanceof Error ? err.message : String(err);\n\n // Attempt cleanup\n try { await client.stop(); } catch { /* swallow cleanup errors */ }\n\n // Keep session in map so callers can inspect the error\n throw new Error(`Failed to start session for ${resolvedDir}: ${session.error}`);\n }\n }\n\n /**\n * Look up a session by sessionId.\n * Linear scan is fine — we expect <10 concurrent sessions.\n */\n getSession(sessionId: string): ManagedSession | undefined {\n for (const session of this.sessions.values()) {\n if (session.sessionId === sessionId) return session;\n }\n return undefined;\n }\n\n /**\n * Look up a session by project directory (direct map lookup).\n */\n getSessionByDir(projectDir: string): ManagedSession | undefined {\n return this.sessions.get(resolve(projectDir));\n }\n\n /**\n * Resolve a pending blocker by sending a UI response.\n */\n async resolveBlocker(sessionId: string, response: string): Promise<void> {\n const session = this.getSession(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n if (!session.pendingBlocker) throw new Error(`No pending blocker for session ${sessionId}`);\n\n const blocker = session.pendingBlocker;\n session.client.sendUIResponse(blocker.id, { value: response });\n session.pendingBlocker = null;\n if (session.status === 'blocked') {\n session.status = 'running';\n }\n }\n\n /**\n * Cancel a running session — abort current operation then stop the process.\n */\n async cancelSession(sessionId: string): Promise<void> {\n const session = this.getSession(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n try {\n await session.client.abort();\n } catch { /* may already be stopped */ }\n\n try {\n await session.client.stop();\n } catch { /* swallow */ }\n\n session.status = 'cancelled';\n session.unsubscribe?.();\n }\n\n /**\n * Build a HeadlessJsonResult-shaped object from accumulated session state.\n */\n getResult(sessionId: string): Record<string, unknown> {\n const session = this.getSession(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n const durationMs = Date.now() - session.startTime;\n\n return {\n sessionId: session.sessionId,\n projectDir: session.projectDir,\n status: session.status,\n durationMs,\n cost: session.cost,\n recentEvents: session.events.slice(-10),\n pendingBlocker: session.pendingBlocker\n ? { id: session.pendingBlocker.id, method: session.pendingBlocker.method, message: session.pendingBlocker.message }\n : null,\n error: session.error ?? null,\n };\n }\n\n /**\n * Stop all active sessions and clean up resources.\n */\n async cleanup(): Promise<void> {\n const stopPromises: Promise<void>[] = [];\n\n for (const session of this.sessions.values()) {\n session.unsubscribe?.();\n if (session.status === 'running' || session.status === 'starting' || session.status === 'blocked') {\n stopPromises.push(\n session.client.stop().catch(() => { /* swallow */ })\n );\n session.status = 'cancelled';\n }\n }\n\n await Promise.allSettled(stopPromises);\n }\n\n /**\n * Resolve the GSD CLI path.\n *\n * 1. GSD_CLI_PATH env var (highest priority)\n * 2. `which gsd` → resolve to the actual dist/cli.js\n */\n static resolveCLIPath(): string {\n // Check env var first\n const envPath = process.env['GSD_CLI_PATH'];\n if (envPath) return resolve(envPath);\n\n // Fallback: locate `gsd` via which\n try {\n const gsdBin = execSync('which gsd', { encoding: 'utf-8' }).trim();\n if (gsdBin) {\n // gsd bin is typically a symlink to dist/loader.js — return the resolved path\n return resolve(gsdBin);\n }\n } catch {\n // which failed\n }\n\n throw new Error(\n 'Cannot find GSD CLI. Set GSD_CLI_PATH environment variable or ensure `gsd` is in PATH.'\n );\n }\n\n // ---------------------------------------------------------------------------\n // Private: Event Handling\n // ---------------------------------------------------------------------------\n\n private handleEvent(session: ManagedSession, event: SdkAgentEvent): void {\n // Ring buffer: push and trim\n session.events.push(event);\n if (session.events.length > MAX_EVENTS) {\n session.events.splice(0, session.events.length - MAX_EVENTS);\n }\n\n // Cost tracking (K004 — cumulative-max)\n if (event.type === 'cost_update') {\n const costEvent = event as unknown as RpcCostUpdateEvent;\n session.cost.totalCost = Math.max(session.cost.totalCost, costEvent.cumulativeCost ?? 0);\n if (costEvent.tokens) {\n session.cost.tokens.input = Math.max(session.cost.tokens.input, costEvent.tokens.input ?? 0);\n session.cost.tokens.output = Math.max(session.cost.tokens.output, costEvent.tokens.output ?? 0);\n session.cost.tokens.cacheRead = Math.max(session.cost.tokens.cacheRead, costEvent.tokens.cacheRead ?? 0);\n session.cost.tokens.cacheWrite = Math.max(session.cost.tokens.cacheWrite, costEvent.tokens.cacheWrite ?? 0);\n }\n }\n\n // Terminal detection — auto-mode/step-mode stopped\n if (isTerminalNotification(event as Record<string, unknown>)) {\n // Check if it's a blocked stop (not truly terminal — it's a blocker)\n if (isBlockedNotification(event as Record<string, unknown>)) {\n session.status = 'blocked';\n session.pendingBlocker = extractBlocker(event);\n } else {\n session.status = 'completed';\n session.unsubscribe?.();\n }\n return;\n }\n\n // Blocker detection — non-fire-and-forget extension_ui_request\n if (isBlockingUIRequest(event as Record<string, unknown>)) {\n session.status = 'blocked';\n session.pendingBlocker = extractBlocker(event);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction timeout(ms: number, message: string): Promise<never> {\n return new Promise((_, reject) => {\n setTimeout(() => reject(new Error(message)), ms);\n });\n}\n\nfunction extractBlocker(event: SdkAgentEvent): PendingBlocker {\n const uiEvent = event as unknown as RpcExtensionUIRequest;\n return {\n id: String(uiEvent.id ?? ''),\n method: String(uiEvent.method ?? ''),\n message: String((uiEvent as Record<string, unknown>).title ?? (uiEvent as Record<string, unknown>).message ?? ''),\n event: uiEvent,\n };\n}\n"]}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * MCP Server types — session lifecycle and orchestration.
3
+ */
4
+ import type { RpcClient, SdkAgentEvent, RpcExtensionUIRequest } from '@gsd-build/rpc-client';
5
+ export type SessionStatus = 'starting' | 'running' | 'blocked' | 'completed' | 'error' | 'cancelled';
6
+ export interface ManagedSession {
7
+ /** Unique session ID returned from RpcClient.init() */
8
+ sessionId: string;
9
+ /** Absolute path to the project directory */
10
+ projectDir: string;
11
+ /** Current lifecycle status */
12
+ status: SessionStatus;
13
+ /** The RpcClient instance managing the agent process */
14
+ client: RpcClient;
15
+ /** Ring buffer of recent events (capped at MAX_EVENTS) */
16
+ events: SdkAgentEvent[];
17
+ /** Pending blocker requiring user response, if any */
18
+ pendingBlocker: PendingBlocker | null;
19
+ /** Cumulative cost tracking (max pattern per K004) */
20
+ cost: CostAccumulator;
21
+ /** Session start timestamp */
22
+ startTime: number;
23
+ /** Error message if status is 'error' */
24
+ error?: string;
25
+ /** Cleanup function to unsubscribe from events */
26
+ unsubscribe?: () => void;
27
+ }
28
+ export interface PendingBlocker {
29
+ /** The extension_ui_request id */
30
+ id: string;
31
+ /** The request method (e.g. 'select', 'confirm', 'input') */
32
+ method: string;
33
+ /** Human-readable message or title */
34
+ message: string;
35
+ /** Full event payload for inspection */
36
+ event: RpcExtensionUIRequest;
37
+ }
38
+ export interface CostAccumulator {
39
+ totalCost: number;
40
+ tokens: {
41
+ input: number;
42
+ output: number;
43
+ cacheRead: number;
44
+ cacheWrite: number;
45
+ };
46
+ }
47
+ export interface ExecuteOptions {
48
+ /** Command to send after '/gsd auto' (default: none) */
49
+ command?: string;
50
+ /** Model ID override */
51
+ model?: string;
52
+ /** Run in bare mode (skip user config) */
53
+ bare?: boolean;
54
+ /** Path to CLI binary (overrides GSD_CLI_PATH and which resolution) */
55
+ cliPath?: string;
56
+ }
57
+ /** Maximum number of events kept in the ring buffer */
58
+ export declare const MAX_EVENTS = 50;
59
+ /** Timeout for RpcClient initialization (ms) */
60
+ export declare const INIT_TIMEOUT_MS = 30000;
61
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAsB,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAMjH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,WAAW,CAAC;AAMrG,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAElB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IAEnB,+BAA+B;IAC/B,MAAM,EAAE,aAAa,CAAC;IAEtB,wDAAwD;IACxD,MAAM,EAAE,SAAS,CAAC;IAElB,0DAA0D;IAC1D,MAAM,EAAE,aAAa,EAAE,CAAC;IAExB,sDAAsD;IACtD,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IAEtC,sDAAsD;IACtD,IAAI,EAAE,eAAe,CAAC;IAEtB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAElB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAMD,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,EAAE,EAAE,MAAM,CAAC;IAEX,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IAEf,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAEhB,wCAAwC;IACxC,KAAK,EAAE,qBAAqB,CAAC;CAC9B;AAMD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAMD,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,0CAA0C;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD,uDAAuD;AACvD,eAAO,MAAM,UAAU,KAAK,CAAC;AAE7B,gDAAgD;AAChD,eAAO,MAAM,eAAe,QAAS,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MCP Server types — session lifecycle and orchestration.
3
+ */
4
+ // ---------------------------------------------------------------------------
5
+ // Constants
6
+ // ---------------------------------------------------------------------------
7
+ /** Maximum number of events kept in the ring buffer */
8
+ export const MAX_EVENTS = 50;
9
+ /** Timeout for RpcClient initialization (ms) */
10
+ export const INIT_TIMEOUT_MS = 30_000;
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgGH,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,uDAAuD;AACvD,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,CAAC;AAE7B,gDAAgD;AAChD,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC","sourcesContent":["/**\n * MCP Server types — session lifecycle and orchestration.\n */\n\nimport type { RpcClient, SdkAgentEvent, RpcCostUpdateEvent, RpcExtensionUIRequest } from '@gsd-build/rpc-client';\n\n// ---------------------------------------------------------------------------\n// Session Status\n// ---------------------------------------------------------------------------\n\nexport type SessionStatus = 'starting' | 'running' | 'blocked' | 'completed' | 'error' | 'cancelled';\n\n// ---------------------------------------------------------------------------\n// Managed Session\n// ---------------------------------------------------------------------------\n\nexport interface ManagedSession {\n /** Unique session ID returned from RpcClient.init() */\n sessionId: string;\n\n /** Absolute path to the project directory */\n projectDir: string;\n\n /** Current lifecycle status */\n status: SessionStatus;\n\n /** The RpcClient instance managing the agent process */\n client: RpcClient;\n\n /** Ring buffer of recent events (capped at MAX_EVENTS) */\n events: SdkAgentEvent[];\n\n /** Pending blocker requiring user response, if any */\n pendingBlocker: PendingBlocker | null;\n\n /** Cumulative cost tracking (max pattern per K004) */\n cost: CostAccumulator;\n\n /** Session start timestamp */\n startTime: number;\n\n /** Error message if status is 'error' */\n error?: string;\n\n /** Cleanup function to unsubscribe from events */\n unsubscribe?: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Pending Blocker\n// ---------------------------------------------------------------------------\n\nexport interface PendingBlocker {\n /** The extension_ui_request id */\n id: string;\n\n /** The request method (e.g. 'select', 'confirm', 'input') */\n method: string;\n\n /** Human-readable message or title */\n message: string;\n\n /** Full event payload for inspection */\n event: RpcExtensionUIRequest;\n}\n\n// ---------------------------------------------------------------------------\n// Cost Accumulator (K004 — cumulative-max)\n// ---------------------------------------------------------------------------\n\nexport interface CostAccumulator {\n totalCost: number;\n tokens: {\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Execute Options\n// ---------------------------------------------------------------------------\n\nexport interface ExecuteOptions {\n /** Command to send after '/gsd auto' (default: none) */\n command?: string;\n\n /** Model ID override */\n model?: string;\n\n /** Run in bare mode (skip user config) */\n bare?: boolean;\n\n /** Path to CLI binary (overrides GSD_CLI_PATH and which resolution) */\n cliPath?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Maximum number of events kept in the ring buffer */\nexport const MAX_EVENTS = 50;\n\n/** Timeout for RpcClient initialization (ms) */\nexport const INIT_TIMEOUT_MS = 30_000;\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Workflow MCP tools — exposes the core GSD mutation/read handlers over MCP.
3
+ */
4
+ interface McpToolServer {
5
+ tool(name: string, description: string, params: Record<string, unknown>, handler: (args: Record<string, unknown>) => Promise<unknown>): unknown;
6
+ }
7
+ export declare function registerWorkflowTools(server: McpToolServer): void;
8
+ export {};
9
+ //# sourceMappingURL=workflow-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-tools.d.ts","sourceRoot":"","sources":["../src/workflow-tools.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgZH,UAAU,aAAa;IACrB,IAAI,CACF,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GAC3D,OAAO,CAAC;CACZ;AAiYD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CA+LjE"}