codekanban 0.29.0 → 0.31.0

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.
@@ -1,17 +1,21 @@
1
- import { readFile } from 'node:fs/promises';
1
+ import { readFile } from "node:fs/promises";
2
2
 
3
- import { buildAgentLaunchSpec } from './command-builder.js';
4
- import { CodeKanbanConfigError, CodeKanbanHttpError, CodeKanbanValidationError } from './errors.js';
5
- import { TerminalConnection } from './terminal-connection.js';
6
- import { WebSessionCommandChannel } from './web-session-command-channel.js';
7
- import { WebSessionEventStream } from './web-session-event-stream.js';
3
+ import { buildAgentLaunchSpec } from "./command-builder.js";
4
+ import {
5
+ CodeKanbanConfigError,
6
+ CodeKanbanHttpError,
7
+ CodeKanbanValidationError,
8
+ } from "./errors.js";
9
+ import { TerminalConnection } from "./terminal-connection.js";
10
+ import { WebSessionCommandChannel } from "./web-session-command-channel.js";
11
+ import { WebSessionEventStream } from "./web-session-event-stream.js";
8
12
  import {
9
13
  WEB_SESSION_COMMAND_WS_PATH,
10
14
  WEB_SESSION_EVENTS_WS_PATH,
11
15
  analyzeWebSession,
12
16
  ensureImageMimeType,
13
17
  normalizeWebSessionAttachment,
14
- } from './web-session-shared.js';
18
+ } from "./web-session-shared.js";
15
19
  import {
16
20
  ensureOptionalString,
17
21
  ensureString,
@@ -21,7 +25,7 @@ import {
21
25
  pathBasename,
22
26
  sleep,
23
27
  toWsUrl,
24
- } from './utils.js';
28
+ } from "./utils.js";
25
29
 
26
30
  const DEFAULT_REQUEST_RETRY = Object.freeze({
27
31
  attempts: 2,
@@ -29,30 +33,36 @@ const DEFAULT_REQUEST_RETRY = Object.freeze({
29
33
  maxDelayMs: 1000,
30
34
  });
31
35
 
32
- const RETRYABLE_HTTP_STATUS_CODES = new Set([408, 425, 429, 500, 502, 503, 504]);
36
+ const RETRYABLE_HTTP_STATUS_CODES = new Set([
37
+ 408, 425, 429, 500, 502, 503, 504,
38
+ ]);
33
39
 
34
- const DEFAULT_WEB_SESSION_AUTO_RETRY_SCOPE = 'network_only';
35
- const DEFAULT_WEB_SESSION_AUTO_RETRY_PRESET = 'gentle_stop';
40
+ const DEFAULT_WEB_SESSION_AUTO_RETRY_SCOPE = "network_only";
41
+ const DEFAULT_WEB_SESSION_AUTO_RETRY_PRESET = "gentle_stop";
36
42
 
37
43
  function defaultWebSessionModel(agent) {
38
- return agent === 'claude' ? 'opus' : 'gpt-5.4';
44
+ return agent === "claude" ? "opus" : "gpt-5.4";
39
45
  }
40
46
 
41
47
  function defaultWebSessionReasoningEffort(agent) {
42
- return agent === 'claude' ? 'default' : 'xhigh';
48
+ return agent === "claude" ? "default" : "xhigh";
43
49
  }
44
50
 
45
51
  function defaultWebSessionWorkflowMode(permissionMode) {
46
- return permissionMode === 'plan' ? 'plan' : 'default';
52
+ return permissionMode === "plan" ? "plan" : "default";
47
53
  }
48
54
 
49
55
  function defaultWebSessionPermissionLevel(permissionMode) {
50
- return permissionMode === 'yolo' ? 'yolo' : 'elevated';
56
+ return permissionMode === "yolo" ? "yolo" : "elevated";
51
57
  }
52
58
 
53
59
  function normalizeWebSessionAutoRetryScope(scope) {
54
60
  const normalized = ensureOptionalString(scope);
55
- if (['network_only', 'network_and_rate_limit', 'all_failures'].includes(normalized)) {
61
+ if (
62
+ ["network_only", "network_and_rate_limit", "all_failures"].includes(
63
+ normalized,
64
+ )
65
+ ) {
56
66
  return normalized;
57
67
  }
58
68
  return DEFAULT_WEB_SESSION_AUTO_RETRY_SCOPE;
@@ -60,14 +70,16 @@ function normalizeWebSessionAutoRetryScope(scope) {
60
70
 
61
71
  function normalizeWebSessionAutoRetryPreset(preset) {
62
72
  const normalized = ensureOptionalString(preset);
63
- if (['gentle_stop', 'aggressive_stop', 'sustain_60s'].includes(normalized)) {
73
+ if (["gentle_stop", "aggressive_stop", "sustain_60s"].includes(normalized)) {
64
74
  return normalized;
65
75
  }
66
76
  return DEFAULT_WEB_SESSION_AUTO_RETRY_PRESET;
67
77
  }
68
78
 
69
79
  function normalizeRequestRetryConfig(value = {}) {
70
- const attempts = Number.isFinite(value.attempts) ? Math.max(1, Math.trunc(value.attempts)) : DEFAULT_REQUEST_RETRY.attempts;
80
+ const attempts = Number.isFinite(value.attempts)
81
+ ? Math.max(1, Math.trunc(value.attempts))
82
+ : DEFAULT_REQUEST_RETRY.attempts;
71
83
  const baseDelayMs = Number.isFinite(value.baseDelayMs)
72
84
  ? Math.max(1, Math.trunc(value.baseDelayMs))
73
85
  : DEFAULT_REQUEST_RETRY.baseDelayMs;
@@ -86,12 +98,12 @@ function resolveRequestRetryConfig(method, defaults, override) {
86
98
  return null;
87
99
  }
88
100
 
89
- const normalizedMethod = String(method || 'GET').toUpperCase();
90
- const source = override && typeof override === 'object' ? override : {};
101
+ const normalizedMethod = String(method || "GET").toUpperCase();
102
+ const source = override && typeof override === "object" ? override : {};
91
103
  const enabled =
92
- typeof source.enabled === 'boolean'
104
+ typeof source.enabled === "boolean"
93
105
  ? source.enabled
94
- : ['GET', 'HEAD', 'OPTIONS'].includes(normalizedMethod);
106
+ : ["GET", "HEAD", "OPTIONS"].includes(normalizedMethod);
95
107
  if (!enabled) {
96
108
  return null;
97
109
  }
@@ -106,13 +118,16 @@ function isRetryableRequestError(error) {
106
118
  if (error instanceof CodeKanbanHttpError) {
107
119
  return RETRYABLE_HTTP_STATUS_CODES.has(error.status);
108
120
  }
109
- if (error instanceof CodeKanbanValidationError || error instanceof CodeKanbanConfigError) {
121
+ if (
122
+ error instanceof CodeKanbanValidationError ||
123
+ error instanceof CodeKanbanConfigError
124
+ ) {
110
125
  return false;
111
126
  }
112
127
  if (error instanceof SyntaxError) {
113
128
  return false;
114
129
  }
115
- if (error?.name === 'AbortError') {
130
+ if (error?.name === "AbortError") {
116
131
  return true;
117
132
  }
118
133
  if (error instanceof TypeError) {
@@ -122,7 +137,10 @@ function isRetryableRequestError(error) {
122
137
  }
123
138
 
124
139
  function getRetryDelayMs(retryConfig, attemptIndex) {
125
- return Math.min(retryConfig.maxDelayMs, retryConfig.baseDelayMs * 2 ** attemptIndex);
140
+ return Math.min(
141
+ retryConfig.maxDelayMs,
142
+ retryConfig.baseDelayMs * 2 ** attemptIndex,
143
+ );
126
144
  }
127
145
 
128
146
  export class CodeKanbanClient {
@@ -133,41 +151,58 @@ export class CodeKanbanClient {
133
151
  this.WebSocketImpl = options.WebSocketImpl || globalThis.WebSocket;
134
152
  this.requestRetry = normalizeRequestRetryConfig(options.requestRetry);
135
153
  if (!this.fetchImpl) {
136
- throw new CodeKanbanConfigError('fetch implementation is unavailable');
154
+ throw new CodeKanbanConfigError("fetch implementation is unavailable");
137
155
  }
138
156
  }
139
157
 
140
158
  async requestJson(path, options = {}) {
141
- const method = String(options.method || 'GET').toUpperCase();
142
- const headers = { Accept: 'application/json', ...this.headers, ...(options.headers || {}) };
159
+ const method = String(options.method || "GET").toUpperCase();
160
+ const headers = {
161
+ Accept: "application/json",
162
+ ...this.headers,
163
+ ...(options.headers || {}),
164
+ };
143
165
  const request = {
144
166
  method,
145
167
  headers,
146
168
  };
147
169
  if (options.body !== undefined) {
148
170
  request.body = JSON.stringify(options.body);
149
- request.headers['Content-Type'] = 'application/json';
171
+ request.headers["Content-Type"] = "application/json";
150
172
  }
151
173
 
152
- const retryConfig = resolveRequestRetryConfig(method, this.requestRetry, options.retry);
174
+ const retryConfig = resolveRequestRetryConfig(
175
+ method,
176
+ this.requestRetry,
177
+ options.retry,
178
+ );
153
179
  const maxAttempts = retryConfig?.attempts ?? 1;
154
180
 
155
181
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
156
182
  try {
157
- const response = await this.fetchImpl(new URL(path, this.baseURL), request);
183
+ const response = await this.fetchImpl(
184
+ new URL(path, this.baseURL),
185
+ request,
186
+ );
158
187
  const text = await response.text();
159
188
  const body = text ? JSON.parse(text) : null;
160
189
  if (!response.ok) {
161
- throw new CodeKanbanHttpError(`request failed with ${response.status}`, {
162
- status: response.status,
163
- method,
164
- path,
165
- body,
166
- });
190
+ throw new CodeKanbanHttpError(
191
+ `request failed with ${response.status}`,
192
+ {
193
+ status: response.status,
194
+ method,
195
+ path,
196
+ body,
197
+ },
198
+ );
167
199
  }
168
200
  return body;
169
201
  } catch (error) {
170
- const canRetry = retryConfig && attempt + 1 < maxAttempts && isRetryableRequestError(error);
202
+ const canRetry =
203
+ retryConfig &&
204
+ attempt + 1 < maxAttempts &&
205
+ isRetryableRequestError(error);
171
206
  if (!canRetry) {
172
207
  throw error;
173
208
  }
@@ -175,7 +210,9 @@ export class CodeKanbanClient {
175
210
  }
176
211
  }
177
212
 
178
- throw new CodeKanbanValidationError(`request retry loop exited unexpectedly for ${method} ${path}`);
213
+ throw new CodeKanbanValidationError(
214
+ `request retry loop exited unexpectedly for ${method} ${path}`,
215
+ );
179
216
  }
180
217
 
181
218
  async resolveProjectReference({ projectId, path, ensureProject = true }) {
@@ -197,25 +234,31 @@ export class CodeKanbanClient {
197
234
  path,
198
235
  ensureProject,
199
236
  });
200
- return ensureString(project?.id, 'projectId');
237
+ return ensureString(project?.id, "projectId");
201
238
  }
202
239
 
203
240
  async listProjects() {
204
- const response = await this.requestJson('/api/v1/projects');
241
+ const response = await this.requestJson("/api/v1/projects");
205
242
  return response?.items || [];
206
243
  }
207
244
 
208
245
  async getProject(projectId) {
209
- ensureString(projectId, 'projectId');
246
+ ensureString(projectId, "projectId");
210
247
  const response = await this.requestJson(`/api/v1/projects/${projectId}`);
211
248
  return response?.item;
212
249
  }
213
250
 
214
- async createProject({ path, name, description = '', worktreeBasePath, hidePath }) {
215
- ensureString(path, 'path');
216
- ensureString(name, 'name');
217
- const response = await this.requestJson('/api/v1/projects/create', {
218
- method: 'POST',
251
+ async createProject({
252
+ path,
253
+ name,
254
+ description = "",
255
+ worktreeBasePath,
256
+ hidePath,
257
+ }) {
258
+ ensureString(path, "path");
259
+ ensureString(name, "name");
260
+ const response = await this.requestJson("/api/v1/projects/create", {
261
+ method: "POST",
219
262
  body: {
220
263
  path,
221
264
  name,
@@ -228,27 +271,33 @@ export class CodeKanbanClient {
228
271
  }
229
272
 
230
273
  async listWorktrees(projectId) {
231
- ensureString(projectId, 'projectId');
232
- const response = await this.requestJson(`/api/v1/projects/${projectId}/worktrees`);
274
+ ensureString(projectId, "projectId");
275
+ const response = await this.requestJson(
276
+ `/api/v1/projects/${projectId}/worktrees`,
277
+ );
233
278
  return response?.items || [];
234
279
  }
235
280
 
236
281
  async listTerminalSessions(projectId) {
237
- ensureString(projectId, 'projectId');
238
- const response = await this.requestJson(`/api/v1/projects/${projectId}/terminals`);
282
+ ensureString(projectId, "projectId");
283
+ const response = await this.requestJson(
284
+ `/api/v1/projects/${projectId}/terminals`,
285
+ );
239
286
  return response?.items || [];
240
287
  }
241
288
 
242
289
  async listAISessionsByProject(projectId) {
243
- ensureString(projectId, 'projectId');
244
- const response = await this.requestJson(`/api/v1/projects/${projectId}/ai-sessions`);
290
+ ensureString(projectId, "projectId");
291
+ const response = await this.requestJson(
292
+ `/api/v1/projects/${projectId}/ai-sessions`,
293
+ );
245
294
  return response?.item || null;
246
295
  }
247
296
 
248
297
  async listAISessionsByPath(projectPath) {
249
- ensureString(projectPath, 'path');
250
- const response = await this.requestJson('/api/v1/ai-sessions/by-path', {
251
- method: 'POST',
298
+ ensureString(projectPath, "path");
299
+ const response = await this.requestJson("/api/v1/ai-sessions/by-path", {
300
+ method: "POST",
252
301
  body: { path: projectPath },
253
302
  });
254
303
  return response?.item || null;
@@ -258,28 +307,36 @@ export class CodeKanbanClient {
258
307
  const dbId = ensureOptionalString(id);
259
308
  const rawSessionId = ensureOptionalString(sessionId);
260
309
  if (!dbId && !rawSessionId) {
261
- throw new CodeKanbanValidationError('id or sessionId is required');
310
+ throw new CodeKanbanValidationError("id or sessionId is required");
262
311
  }
263
312
  if (refresh && !dbId) {
264
- throw new CodeKanbanValidationError('refresh currently requires a database id');
313
+ throw new CodeKanbanValidationError(
314
+ "refresh currently requires a database id",
315
+ );
265
316
  }
266
317
 
267
318
  if (dbId) {
268
- const path = refresh ? `/api/v1/ai-sessions/${dbId}/refresh` : `/api/v1/ai-sessions/${dbId}/conversation`;
269
- const response = await this.requestJson(path, { method: refresh ? 'POST' : 'GET' });
319
+ const path = refresh
320
+ ? `/api/v1/ai-sessions/${dbId}/refresh`
321
+ : `/api/v1/ai-sessions/${dbId}/conversation`;
322
+ const response = await this.requestJson(path, {
323
+ method: refresh ? "POST" : "GET",
324
+ });
270
325
  return response?.item || null;
271
326
  }
272
327
 
273
- const response = await this.requestJson(`/api/v1/ai-sessions/by-session-id/${rawSessionId}/conversation`);
328
+ const response = await this.requestJson(
329
+ `/api/v1/ai-sessions/by-session-id/${rawSessionId}/conversation`,
330
+ );
274
331
  return response?.item || null;
275
332
  }
276
333
 
277
334
  async getAISessionToolResult({ id, sessionId, toolUseId }) {
278
335
  const dbId = ensureOptionalString(id);
279
336
  const rawSessionId = ensureOptionalString(sessionId);
280
- const resolvedToolUseId = ensureString(toolUseId, 'toolUseId');
337
+ const resolvedToolUseId = ensureString(toolUseId, "toolUseId");
281
338
  if (!dbId && !rawSessionId) {
282
- throw new CodeKanbanValidationError('id or sessionId is required');
339
+ throw new CodeKanbanValidationError("id or sessionId is required");
283
340
  }
284
341
  const path = dbId
285
342
  ? `/api/v1/ai-sessions/${dbId}/conversation/tool-results/${resolvedToolUseId}`
@@ -288,18 +345,28 @@ export class CodeKanbanClient {
288
345
  return response?.item || null;
289
346
  }
290
347
 
291
- async createTerminalSession({ projectId, worktreeId, workingDir = '', title = '', rows = 0, cols = 0 }) {
292
- ensureString(projectId, 'projectId');
293
- ensureString(worktreeId, 'worktreeId');
294
- const response = await this.requestJson(`/api/v1/projects/${projectId}/worktrees/${worktreeId}/terminals`, {
295
- method: 'POST',
296
- body: {
297
- workingDir,
298
- title,
299
- rows,
300
- cols,
348
+ async createTerminalSession({
349
+ projectId,
350
+ worktreeId,
351
+ workingDir = "",
352
+ title = "",
353
+ rows = 0,
354
+ cols = 0,
355
+ }) {
356
+ ensureString(projectId, "projectId");
357
+ ensureString(worktreeId, "worktreeId");
358
+ const response = await this.requestJson(
359
+ `/api/v1/projects/${projectId}/worktrees/${worktreeId}/terminals`,
360
+ {
361
+ method: "POST",
362
+ body: {
363
+ workingDir,
364
+ title,
365
+ rows,
366
+ cols,
367
+ },
301
368
  },
302
- });
369
+ );
303
370
  return response?.item;
304
371
  }
305
372
 
@@ -311,61 +378,74 @@ export class CodeKanbanClient {
311
378
  const project = await this.getProject(resolvedProjectId);
312
379
  return {
313
380
  project,
314
- matchedBy: 'projectId',
381
+ matchedBy: "projectId",
315
382
  };
316
383
  }
317
384
 
318
385
  if (!resolvedPath) {
319
- throw new CodeKanbanValidationError('projectId or path is required');
386
+ throw new CodeKanbanValidationError("projectId or path is required");
320
387
  }
321
388
 
322
389
  const target = normalizeFsPath(resolvedPath);
323
390
  const projects = await this.listProjects();
324
- const project = projects.find(item => normalizeFsPath(item.path) === target);
391
+ const project = projects.find(
392
+ (item) => normalizeFsPath(item.path) === target,
393
+ );
325
394
  if (project) {
326
395
  return {
327
396
  project,
328
- matchedBy: 'path',
397
+ matchedBy: "path",
329
398
  };
330
399
  }
331
400
 
332
401
  if (!ensureProject) {
333
- throw new CodeKanbanValidationError(`no CodeKanban project is registered for path: ${resolvedPath}`);
402
+ throw new CodeKanbanValidationError(
403
+ `no CodeKanban project is registered for path: ${resolvedPath}`,
404
+ );
334
405
  }
335
406
 
336
407
  const created = await this.createProject({
337
408
  path: resolvedPath,
338
409
  name: pathBasename(resolvedPath),
339
- description: '',
410
+ description: "",
340
411
  });
341
412
  return {
342
413
  project: created,
343
- matchedBy: 'created',
414
+ matchedBy: "created",
344
415
  };
345
416
  }
346
417
 
347
418
  async resolveWorktree({ projectId, worktreeId }) {
348
- const project = ensureString(projectId, 'projectId');
419
+ const project = ensureString(projectId, "projectId");
349
420
  const preferredWorktreeId = ensureOptionalString(worktreeId);
350
421
  const worktrees = await this.listWorktrees(project);
351
422
  if (worktrees.length === 0) {
352
- throw new CodeKanbanValidationError(`no worktrees are available for project ${project}`);
423
+ throw new CodeKanbanValidationError(
424
+ `no worktrees are available for project ${project}`,
425
+ );
353
426
  }
354
427
  if (preferredWorktreeId) {
355
- const direct = worktrees.find(item => item.id === preferredWorktreeId);
428
+ const direct = worktrees.find((item) => item.id === preferredWorktreeId);
356
429
  if (!direct) {
357
- throw new CodeKanbanValidationError(`worktree ${preferredWorktreeId} was not found in project ${project}`);
430
+ throw new CodeKanbanValidationError(
431
+ `worktree ${preferredWorktreeId} was not found in project ${project}`,
432
+ );
358
433
  }
359
434
  return direct;
360
435
  }
361
- return worktrees.find(item => item.isMain) || worktrees[0];
436
+ return worktrees.find((item) => item.isMain) || worktrees[0];
362
437
  }
363
438
 
364
439
  connectTerminal({ sessionId, wsPath, wsUrl }) {
365
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
440
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
366
441
  const resolvedPath =
367
- ensureOptionalString(wsUrl) || ensureOptionalString(wsPath) || `/api/v1/terminal/ws?sessionId=${resolvedSessionId}`;
368
- const url = resolvedPath.startsWith('ws://') || resolvedPath.startsWith('wss://') ? resolvedPath : toWsUrl(this.baseURL, resolvedPath);
442
+ ensureOptionalString(wsUrl) ||
443
+ ensureOptionalString(wsPath) ||
444
+ `/api/v1/terminal/ws?sessionId=${resolvedSessionId}`;
445
+ const url =
446
+ resolvedPath.startsWith("ws://") || resolvedPath.startsWith("wss://")
447
+ ? resolvedPath
448
+ : toWsUrl(this.baseURL, resolvedPath);
369
449
 
370
450
  return new TerminalConnection({
371
451
  sessionId: resolvedSessionId,
@@ -399,11 +479,23 @@ export class CodeKanbanClient {
399
479
  }
400
480
  }
401
481
 
402
- async listSessions({ projectId, path, includeTerminal = true, includeAI = true, ensureProject = true }) {
403
- const { project, matchedBy } = await this.resolveProject({ projectId, path, ensureProject });
482
+ async listSessions({
483
+ projectId,
484
+ path,
485
+ includeTerminal = true,
486
+ includeAI = true,
487
+ ensureProject = true,
488
+ }) {
489
+ const { project, matchedBy } = await this.resolveProject({
490
+ projectId,
491
+ path,
492
+ ensureProject,
493
+ });
404
494
 
405
495
  const [terminalSessions, aiSessions] = await Promise.all([
406
- includeTerminal ? this.listTerminalSessions(project.id) : Promise.resolve([]),
496
+ includeTerminal
497
+ ? this.listTerminalSessions(project.id)
498
+ : Promise.resolve([]),
407
499
  includeAI
408
500
  ? this.listAISessionsByProject(project.id)
409
501
  : Promise.resolve({
@@ -423,8 +515,14 @@ export class CodeKanbanClient {
423
515
  }
424
516
 
425
517
  async listWebSessions({ projectId, path, ensureProject = true } = {}) {
426
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject });
427
- const response = await this.requestJson(`/api/v1/projects/${resolvedProjectId}/web-sessions`);
518
+ const resolvedProjectId = await this.resolveProjectId({
519
+ projectId,
520
+ path,
521
+ ensureProject,
522
+ });
523
+ const response = await this.requestJson(
524
+ `/api/v1/projects/${resolvedProjectId}/web-sessions`,
525
+ );
428
526
  return response?.items || [];
429
527
  }
430
528
 
@@ -435,142 +533,231 @@ export class CodeKanbanClient {
435
533
  ensureProject: true,
436
534
  });
437
535
 
438
- const agent = ensureString(input.agent, 'agent');
439
- const permissionMode = ensureOptionalString(input.permissionMode).toLowerCase();
536
+ const agent = ensureString(input.agent, "agent");
537
+ const permissionMode = ensureOptionalString(
538
+ input.permissionMode,
539
+ ).toLowerCase();
440
540
  const worktree = await this.resolveWorktree({
441
541
  projectId,
442
542
  worktreeId: input.worktreeId,
443
543
  });
444
544
 
445
- const response = await this.requestJson(`/api/v1/projects/${projectId}/web-sessions`, {
446
- method: 'POST',
447
- body: {
448
- worktreeId: ensureString(worktree?.id, 'worktreeId'),
449
- agent,
450
- model: ensureOptionalString(input.model) || defaultWebSessionModel(agent),
451
- reasoningEffort:
452
- ensureOptionalString(input.reasoningEffort) || defaultWebSessionReasoningEffort(agent),
453
- workflowMode: ensureOptionalString(input.workflowMode) || defaultWebSessionWorkflowMode(permissionMode),
454
- permissionLevel:
455
- ensureOptionalString(input.permissionLevel) || defaultWebSessionPermissionLevel(permissionMode),
456
- autoRetryEnabled: input.autoRetryEnabled === true,
457
- autoRetryScope: normalizeWebSessionAutoRetryScope(input.autoRetryScope),
458
- autoRetryPreset: normalizeWebSessionAutoRetryPreset(input.autoRetryPreset),
459
- permissionMode,
460
- title: ensureOptionalString(input.title),
545
+ const response = await this.requestJson(
546
+ `/api/v1/projects/${projectId}/web-sessions`,
547
+ {
548
+ method: "POST",
549
+ body: {
550
+ worktreeId: ensureString(worktree?.id, "worktreeId"),
551
+ agent,
552
+ model:
553
+ ensureOptionalString(input.model) || defaultWebSessionModel(agent),
554
+ reasoningEffort:
555
+ ensureOptionalString(input.reasoningEffort) ||
556
+ defaultWebSessionReasoningEffort(agent),
557
+ workflowMode:
558
+ ensureOptionalString(input.workflowMode) ||
559
+ defaultWebSessionWorkflowMode(permissionMode),
560
+ permissionLevel:
561
+ ensureOptionalString(input.permissionLevel) ||
562
+ defaultWebSessionPermissionLevel(permissionMode),
563
+ autoRetryEnabled: input.autoRetryEnabled === true,
564
+ autoRetryScope: normalizeWebSessionAutoRetryScope(
565
+ input.autoRetryScope,
566
+ ),
567
+ autoRetryPreset: normalizeWebSessionAutoRetryPreset(
568
+ input.autoRetryPreset,
569
+ ),
570
+ permissionMode,
571
+ title: ensureOptionalString(input.title),
572
+ },
461
573
  },
462
- });
574
+ );
463
575
  return response?.item || null;
464
576
  }
465
577
 
466
578
  async getWebSessionSnapshot({ projectId, path, sessionId, limit = 80 }) {
467
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject: true });
468
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
469
- const normalizedLimit = Number.isFinite(limit) ? Math.max(1, Math.trunc(limit)) : 80;
579
+ const resolvedProjectId = await this.resolveProjectId({
580
+ projectId,
581
+ path,
582
+ ensureProject: true,
583
+ });
584
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
585
+ const normalizedLimit = Number.isFinite(limit)
586
+ ? Math.max(1, Math.trunc(limit))
587
+ : 80;
470
588
  const response = await this.requestJson(
471
589
  `/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/snapshot?limit=${normalizedLimit}`,
472
590
  );
473
591
  return response?.item || null;
474
592
  }
475
593
 
476
- async getWebSessionHistory({ projectId, path, sessionId, beforeCursor, limit = 80 }) {
477
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject: true });
478
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
594
+ async getWebSessionHistory({
595
+ projectId,
596
+ path,
597
+ sessionId,
598
+ beforeCursor,
599
+ limit = 80,
600
+ }) {
601
+ const resolvedProjectId = await this.resolveProjectId({
602
+ projectId,
603
+ path,
604
+ ensureProject: true,
605
+ });
606
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
479
607
  const params = new URLSearchParams();
480
608
  if (ensureOptionalString(beforeCursor)) {
481
- params.set('beforeCursor', ensureOptionalString(beforeCursor));
609
+ params.set("beforeCursor", ensureOptionalString(beforeCursor));
482
610
  }
483
611
  if (Number.isFinite(limit)) {
484
- params.set('limit', String(Math.max(1, Math.trunc(limit))));
612
+ params.set("limit", String(Math.max(1, Math.trunc(limit))));
485
613
  }
486
614
  const suffix = params.toString();
487
615
  const response = await this.requestJson(
488
- `/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/history${suffix ? `?${suffix}` : ''}`,
616
+ `/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/history${suffix ? `?${suffix}` : ""}`,
489
617
  );
490
618
  return response?.item || null;
491
619
  }
492
620
 
493
- async syncWebSession({ projectId, path, sessionId, mode, clearExisting = false }) {
494
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject: true });
495
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
496
- const response = await this.requestJson(`/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/sync`, {
497
- method: 'POST',
498
- body: {
499
- ...(ensureOptionalString(mode) ? { mode: ensureOptionalString(mode) } : {}),
500
- clearExisting: clearExisting === true,
501
- },
621
+ async syncWebSession({
622
+ projectId,
623
+ path,
624
+ sessionId,
625
+ mode,
626
+ clearExisting = false,
627
+ }) {
628
+ const resolvedProjectId = await this.resolveProjectId({
629
+ projectId,
630
+ path,
631
+ ensureProject: true,
502
632
  });
633
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
634
+ const response = await this.requestJson(
635
+ `/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/sync`,
636
+ {
637
+ method: "POST",
638
+ body: {
639
+ ...(ensureOptionalString(mode)
640
+ ? { mode: ensureOptionalString(mode) }
641
+ : {}),
642
+ clearExisting: clearExisting === true,
643
+ },
644
+ },
645
+ );
503
646
  return response?.item || null;
504
647
  }
505
648
 
506
649
  async archiveWebSession({ projectId, path, sessionId }) {
507
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject: true });
508
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
509
- const response = await this.requestJson(`/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/archive`, {
510
- method: 'POST',
650
+ const resolvedProjectId = await this.resolveProjectId({
651
+ projectId,
652
+ path,
653
+ ensureProject: true,
511
654
  });
655
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
656
+ const response = await this.requestJson(
657
+ `/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/archive`,
658
+ {
659
+ method: "POST",
660
+ },
661
+ );
512
662
  return response?.item || null;
513
663
  }
514
664
 
515
665
  async unarchiveWebSession({ projectId, path, sessionId }) {
516
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject: true });
517
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
518
- const response = await this.requestJson(`/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/unarchive`, {
519
- method: 'POST',
666
+ const resolvedProjectId = await this.resolveProjectId({
667
+ projectId,
668
+ path,
669
+ ensureProject: true,
520
670
  });
671
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
672
+ const response = await this.requestJson(
673
+ `/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/unarchive`,
674
+ {
675
+ method: "POST",
676
+ },
677
+ );
521
678
  return response?.item || null;
522
679
  }
523
680
 
524
681
  async renameWebSession({ projectId, path, sessionId, title }) {
525
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject: true });
526
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
527
- const response = await this.requestJson(`/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/rename`, {
528
- method: 'POST',
529
- body: {
530
- title: ensureString(title, 'title'),
531
- },
682
+ const resolvedProjectId = await this.resolveProjectId({
683
+ projectId,
684
+ path,
685
+ ensureProject: true,
532
686
  });
687
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
688
+ const response = await this.requestJson(
689
+ `/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/rename`,
690
+ {
691
+ method: "POST",
692
+ body: {
693
+ title: ensureString(title, "title"),
694
+ },
695
+ },
696
+ );
533
697
  return response?.item || null;
534
698
  }
535
699
 
536
700
  async closeWebSession({ projectId, path, sessionId }) {
537
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject: true });
538
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
539
- const response = await this.requestJson(`/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/close`, {
540
- method: 'POST',
701
+ const resolvedProjectId = await this.resolveProjectId({
702
+ projectId,
703
+ path,
704
+ ensureProject: true,
541
705
  });
706
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
707
+ const response = await this.requestJson(
708
+ `/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/close`,
709
+ {
710
+ method: "POST",
711
+ },
712
+ );
542
713
  return {
543
- message: response?.message || 'session aborted',
714
+ message: response?.message || "session aborted",
544
715
  };
545
716
  }
546
717
 
547
718
  async deleteWebSession({ projectId, path, sessionId }) {
548
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject: true });
549
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
550
- const response = await this.requestJson(`/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}`, {
551
- method: 'DELETE',
719
+ const resolvedProjectId = await this.resolveProjectId({
720
+ projectId,
721
+ path,
722
+ ensureProject: true,
552
723
  });
724
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
725
+ const response = await this.requestJson(
726
+ `/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}`,
727
+ {
728
+ method: "DELETE",
729
+ },
730
+ );
553
731
  return {
554
- message: response?.message || 'session deleted',
732
+ message: response?.message || "session deleted",
555
733
  };
556
734
  }
557
735
 
558
736
  async queryArchivedWebSessions({ projectIds, offset = 0, limit = 20 }) {
559
- const response = await this.requestJson('/api/v1/web-sessions/archived/query', {
560
- method: 'POST',
561
- body: {
562
- projectIds: Array.isArray(projectIds) ? projectIds : [],
563
- offset: Number.isFinite(offset) ? Math.max(0, Math.trunc(offset)) : 0,
564
- limit: Number.isFinite(limit) ? Math.max(1, Math.trunc(limit)) : 20,
737
+ const response = await this.requestJson(
738
+ "/api/v1/web-sessions/archived/query",
739
+ {
740
+ method: "POST",
741
+ body: {
742
+ projectIds: Array.isArray(projectIds) ? projectIds : [],
743
+ offset: Number.isFinite(offset) ? Math.max(0, Math.trunc(offset)) : 0,
744
+ limit: Number.isFinite(limit) ? Math.max(1, Math.trunc(limit)) : 20,
745
+ },
565
746
  },
566
- });
567
- return response?.item || { items: [], total: 0, hasMore: false, nextOffset: 0 };
747
+ );
748
+ return (
749
+ response?.item || { items: [], total: 0, hasMore: false, nextOffset: 0 }
750
+ );
568
751
  }
569
752
 
570
753
  async getWebSessionCommandGroup({ projectId, path, sessionId, groupId }) {
571
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject: true });
572
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
573
- const resolvedGroupId = ensureString(groupId, 'groupId');
754
+ const resolvedProjectId = await this.resolveProjectId({
755
+ projectId,
756
+ path,
757
+ ensureProject: true,
758
+ });
759
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
760
+ const resolvedGroupId = ensureString(groupId, "groupId");
574
761
  const response = await this.requestJson(
575
762
  `/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/command-groups/${resolvedGroupId}`,
576
763
  );
@@ -578,29 +765,48 @@ export class CodeKanbanClient {
578
765
  }
579
766
 
580
767
  async getWebSessionRuntimeConfig() {
581
- const response = await this.requestJson('/api/v1/web-sessions/runtime-config');
768
+ const response = await this.requestJson(
769
+ "/api/v1/web-sessions/runtime-config",
770
+ );
582
771
  return response?.item || null;
583
772
  }
584
773
 
585
- async uploadWebSessionAttachment({ projectId, path, filePath, fileName, mimeType }) {
586
- const resolvedProjectId = await this.resolveProjectId({ projectId, path, ensureProject: true });
587
- const resolvedFilePath = ensureString(filePath, 'filePath');
588
- const resolvedFileName = ensureOptionalString(fileName) || pathBasename(resolvedFilePath);
774
+ async uploadWebSessionAttachment({
775
+ projectId,
776
+ path,
777
+ filePath,
778
+ fileName,
779
+ mimeType,
780
+ }) {
781
+ const resolvedProjectId = await this.resolveProjectId({
782
+ projectId,
783
+ path,
784
+ ensureProject: true,
785
+ });
786
+ const resolvedFilePath = ensureString(filePath, "filePath");
787
+ const resolvedFileName =
788
+ ensureOptionalString(fileName) || pathBasename(resolvedFilePath);
589
789
  const resolvedMimeType = ensureImageMimeType(mimeType, resolvedFileName);
590
790
  const fileBuffer = await readFile(resolvedFilePath);
591
791
  const formData = new FormData();
592
- formData.append('file', new File([fileBuffer], resolvedFileName, { type: resolvedMimeType }));
792
+ formData.append(
793
+ "file",
794
+ new File([fileBuffer], resolvedFileName, { type: resolvedMimeType }),
795
+ );
593
796
 
594
797
  const headers = {
595
- Accept: 'application/json',
798
+ Accept: "application/json",
596
799
  ...this.headers,
597
800
  };
598
- delete headers['Content-Type'];
801
+ delete headers["Content-Type"];
599
802
 
600
803
  const response = await this.fetchImpl(
601
- new URL(`/api/v1/projects/${resolvedProjectId}/web-sessions/attachments`, this.baseURL),
804
+ new URL(
805
+ `/api/v1/projects/${resolvedProjectId}/web-sessions/attachments`,
806
+ this.baseURL,
807
+ ),
602
808
  {
603
- method: 'POST',
809
+ method: "POST",
604
810
  headers,
605
811
  body: formData,
606
812
  },
@@ -610,7 +816,7 @@ export class CodeKanbanClient {
610
816
  if (!response.ok) {
611
817
  throw new CodeKanbanHttpError(`request failed with ${response.status}`, {
612
818
  status: response.status,
613
- method: 'POST',
819
+ method: "POST",
614
820
  path: `/api/v1/projects/${resolvedProjectId}/web-sessions/attachments`,
615
821
  body,
616
822
  });
@@ -632,17 +838,26 @@ export class CodeKanbanClient {
632
838
  return this.analyzeWebSession(snapshot);
633
839
  }
634
840
 
635
- async sendWebSessionMessage({ sessionId, text, attachmentIds = [] }) {
636
- return await this.withWebSessionCommandChannel(channel =>
841
+ async sendWebSessionMessage({ sessionId, text, attachmentIds = [], mode }) {
842
+ return await this.withWebSessionCommandChannel((channel) =>
637
843
  channel.sendMessage(sessionId, {
638
844
  text,
639
845
  attachmentIds,
846
+ mode: ensureOptionalString(mode),
847
+ }),
848
+ );
849
+ }
850
+
851
+ async removeWebSessionPendingInput({ sessionId, pendingId }) {
852
+ return await this.withWebSessionCommandChannel((channel) =>
853
+ channel.removePendingInput(sessionId, {
854
+ pendingId,
640
855
  }),
641
856
  );
642
857
  }
643
858
 
644
859
  async updateWebSessionWorkflowMode({ sessionId, workflowMode }) {
645
- return await this.withWebSessionCommandChannel(channel =>
860
+ return await this.withWebSessionCommandChannel((channel) =>
646
861
  channel.updateWorkflowMode(sessionId, {
647
862
  workflowMode,
648
863
  }),
@@ -650,7 +865,7 @@ export class CodeKanbanClient {
650
865
  }
651
866
 
652
867
  async answerWebSessionUserInput({ sessionId, itemId, answers }) {
653
- return await this.withWebSessionCommandChannel(channel =>
868
+ return await this.withWebSessionCommandChannel((channel) =>
654
869
  channel.answerUserInput(sessionId, {
655
870
  itemId,
656
871
  answers,
@@ -659,14 +874,24 @@ export class CodeKanbanClient {
659
874
  }
660
875
 
661
876
  async approveWebSession({ sessionId }) {
662
- return await this.withWebSessionCommandChannel(channel => channel.approve(sessionId));
877
+ return await this.withWebSessionCommandChannel((channel) =>
878
+ channel.approve(sessionId),
879
+ );
663
880
  }
664
881
 
665
882
  async rejectWebSession({ sessionId }) {
666
- return await this.withWebSessionCommandChannel(channel => channel.reject(sessionId));
883
+ return await this.withWebSessionCommandChannel((channel) =>
884
+ channel.reject(sessionId),
885
+ );
667
886
  }
668
887
 
669
- async answerPendingUserInput({ projectId, path, sessionId, answers, limit = 120 }) {
888
+ async answerPendingUserInput({
889
+ projectId,
890
+ path,
891
+ sessionId,
892
+ answers,
893
+ limit = 120,
894
+ }) {
670
895
  const state = await this.getWebSessionState({
671
896
  projectId,
672
897
  path,
@@ -674,7 +899,9 @@ export class CodeKanbanClient {
674
899
  limit,
675
900
  });
676
901
  if (!state.pendingUserInput?.itemId) {
677
- throw new CodeKanbanValidationError(`web session ${sessionId} has no pending user input`);
902
+ throw new CodeKanbanValidationError(
903
+ `web session ${sessionId} has no pending user input`,
904
+ );
678
905
  }
679
906
  const ack = await this.answerWebSessionUserInput({
680
907
  sessionId,
@@ -698,7 +925,9 @@ export class CodeKanbanClient {
698
925
  limit,
699
926
  });
700
927
  if (!state.pendingApproval) {
701
- throw new CodeKanbanValidationError(`web session ${sessionId} has no pending approval`);
928
+ throw new CodeKanbanValidationError(
929
+ `web session ${sessionId} has no pending approval`,
930
+ );
702
931
  }
703
932
  const ack = await this.approveWebSession({ sessionId });
704
933
  return {
@@ -717,7 +946,9 @@ export class CodeKanbanClient {
717
946
  limit,
718
947
  });
719
948
  if (!state.pendingApproval) {
720
- throw new CodeKanbanValidationError(`web session ${sessionId} has no pending approval`);
949
+ throw new CodeKanbanValidationError(
950
+ `web session ${sessionId} has no pending approval`,
951
+ );
721
952
  }
722
953
  const ack = await this.rejectWebSession({ sessionId });
723
954
  return {
@@ -728,7 +959,13 @@ export class CodeKanbanClient {
728
959
  };
729
960
  }
730
961
 
731
- async executeLatestPlan({ projectId, path, sessionId, prompt = 'Implement the plan.', limit = 120 }) {
962
+ async executeLatestPlan({
963
+ projectId,
964
+ path,
965
+ sessionId,
966
+ prompt = "Implement the plan.",
967
+ limit = 120,
968
+ }) {
732
969
  const state = await this.getWebSessionState({
733
970
  projectId,
734
971
  path,
@@ -736,16 +973,20 @@ export class CodeKanbanClient {
736
973
  limit,
737
974
  });
738
975
  if (!state.latestPlan) {
739
- throw new CodeKanbanValidationError(`web session ${sessionId} has no latest plan to execute`);
976
+ throw new CodeKanbanValidationError(
977
+ `web session ${sessionId} has no latest plan to execute`,
978
+ );
740
979
  }
741
- if (!state.canSend && state.nextAction?.type !== 'execute_plan') {
742
- throw new CodeKanbanValidationError(`web session ${sessionId} is not ready to execute the latest plan`);
980
+ if (!state.canSend && state.nextAction?.type !== "execute_plan") {
981
+ throw new CodeKanbanValidationError(
982
+ `web session ${sessionId} is not ready to execute the latest plan`,
983
+ );
743
984
  }
744
985
 
745
- return await this.withWebSessionCommandChannel(async channel => {
746
- if (state.session?.workflowMode === 'plan') {
986
+ return await this.withWebSessionCommandChannel(async (channel) => {
987
+ if (state.session?.workflowMode === "plan") {
747
988
  await channel.updateWorkflowMode(sessionId, {
748
- workflowMode: 'default',
989
+ workflowMode: "default",
749
990
  });
750
991
  }
751
992
 
@@ -758,12 +999,14 @@ export class CodeKanbanClient {
758
999
  const ack = await channel.answerUserInput(sessionId, {
759
1000
  itemId: state.pendingUserInput.itemId,
760
1001
  answers: {
761
- [state.pendingUserInput.questionId]: [state.pendingUserInput.executeOptionLabel],
1002
+ [state.pendingUserInput.questionId]: [
1003
+ state.pendingUserInput.executeOptionLabel,
1004
+ ],
762
1005
  },
763
1006
  });
764
1007
  return {
765
1008
  sessionId,
766
- mode: 'plan_choice',
1009
+ mode: "plan_choice",
767
1010
  latestPlan: state.latestPlan,
768
1011
  ack,
769
1012
  state,
@@ -776,7 +1019,7 @@ export class CodeKanbanClient {
776
1019
  });
777
1020
  return {
778
1021
  sessionId,
779
- mode: 'followup_message',
1022
+ mode: "followup_message",
780
1023
  prompt,
781
1024
  latestPlan: state.latestPlan,
782
1025
  ack,
@@ -795,15 +1038,15 @@ export class CodeKanbanClient {
795
1038
  limit = 120,
796
1039
  }) {
797
1040
  if (!until) {
798
- throw new CodeKanbanValidationError('until is required');
1041
+ throw new CodeKanbanValidationError("until is required");
799
1042
  }
800
1043
 
801
1044
  const matches =
802
- typeof until === 'function'
1045
+ typeof until === "function"
803
1046
  ? until
804
1047
  : Array.isArray(until)
805
- ? state => until.includes(state.phase)
806
- : state => state.phase === until;
1048
+ ? (state) => until.includes(state.phase)
1049
+ : (state) => state.phase === until;
807
1050
 
808
1051
  const startedAt = Date.now();
809
1052
  let lastRetryableError = null;
@@ -836,7 +1079,9 @@ export class CodeKanbanClient {
836
1079
  );
837
1080
  }
838
1081
 
839
- throw new CodeKanbanValidationError(`web session ${sessionId} did not reach the requested state within ${timeoutMs}ms`);
1082
+ throw new CodeKanbanValidationError(
1083
+ `web session ${sessionId} did not reach the requested state within ${timeoutMs}ms`,
1084
+ );
840
1085
  }
841
1086
 
842
1087
  async startWorkflow(input = {}) {
@@ -855,7 +1100,10 @@ export class CodeKanbanClient {
855
1100
  projectId: project.id,
856
1101
  worktreeId: worktree.id,
857
1102
  workingDir: ensureOptionalString(input.workingDir) || worktree.path,
858
- title: ensureOptionalString(input.title) || ensureOptionalString(input.prompt) || 'AI workflow',
1103
+ title:
1104
+ ensureOptionalString(input.title) ||
1105
+ ensureOptionalString(input.prompt) ||
1106
+ "AI workflow",
859
1107
  rows: Number.isFinite(input.rows) ? input.rows : 0,
860
1108
  cols: Number.isFinite(input.cols) ? input.cols : 0,
861
1109
  });
@@ -888,16 +1136,22 @@ export class CodeKanbanClient {
888
1136
  }
889
1137
 
890
1138
  async continueTerminalSession({ projectId, path, sessionId, prompt }) {
891
- const resolvedSessionId = ensureString(sessionId, 'sessionId');
892
- const resolvedPrompt = ensureString(prompt, 'prompt');
1139
+ const resolvedSessionId = ensureString(sessionId, "sessionId");
1140
+ const resolvedPrompt = ensureString(prompt, "prompt");
893
1141
 
894
1142
  let project;
895
1143
  if (projectId || path) {
896
- ({ project } = await this.resolveProject({ projectId, path, ensureProject: true }));
1144
+ ({ project } = await this.resolveProject({
1145
+ projectId,
1146
+ path,
1147
+ ensureProject: true,
1148
+ }));
897
1149
  const sessions = await this.listTerminalSessions(project.id);
898
- const exists = sessions.some(item => item.id === resolvedSessionId);
1150
+ const exists = sessions.some((item) => item.id === resolvedSessionId);
899
1151
  if (!exists) {
900
- throw new CodeKanbanValidationError(`terminal session ${resolvedSessionId} does not belong to project ${project.id}`);
1152
+ throw new CodeKanbanValidationError(
1153
+ `terminal session ${resolvedSessionId} does not belong to project ${project.id}`,
1154
+ );
901
1155
  }
902
1156
  }
903
1157