codekanban 0.30.0 → 0.32.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.
- package/README.md +22 -0
- package/README.zh-CN.md +15 -0
- package/package.json +6 -6
- package/packages/codekanban-cli/README.md +118 -0
- package/packages/codekanban-cli/package.json +48 -0
- package/packages/codekanban-cli/release/install-cli-unix.sh +9 -0
- package/packages/codekanban-cli/release/install-skills-unix.sh +12 -0
- package/packages/codekanban-cli/release/install-unix.sh +16 -0
- package/packages/codekanban-cli/release/install-windows.cmd +19 -0
- package/packages/codekanban-cli/skills/README.md +18 -0
- package/packages/codekanban-cli/skills/codekanban-cli/agents/openai.yaml +4 -0
- package/packages/codekanban-cli/src/core.js +1019 -0
- package/packages/codekanban-cli/src/index.js +13 -0
- package/packages/{node-sdk/src/cli.js → codekanban-cli/src/runtime.js} +353 -6
- package/packages/node-sdk/README.md +57 -33
- package/packages/node-sdk/package.json +2 -7
- package/packages/node-sdk/src/client.js +810 -56
- package/packages/node-sdk/src/terminal-connection.js +2 -2
- package/packages/node-sdk/src/utils.js +25 -3
- package/packages/node-sdk/src/web-session-command-channel.js +2 -2
- package/packages/node-sdk/src/web-session-event-stream.js +1 -1
- package/packages/node-sdk/src/web-session-shared.js +44 -1
- package/skills/codekanban-project-session/agents/openai.yaml +0 -4
- package/skills/codekanban-project-session/scripts/run-codekanban-session.mjs +0 -7
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
normalizeWebSessionAttachment,
|
|
18
18
|
} from "./web-session-shared.js";
|
|
19
19
|
import {
|
|
20
|
+
ensureArrayOfStrings,
|
|
20
21
|
ensureOptionalString,
|
|
21
22
|
ensureString,
|
|
22
23
|
normalizeBaseUrl,
|
|
@@ -143,12 +144,274 @@ function getRetryDelayMs(retryConfig, attemptIndex) {
|
|
|
143
144
|
);
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
function normalizeWebSessionQuestionChoice(value) {
|
|
148
|
+
return String(value || "").trim();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildAutoWebSessionAnswers(
|
|
152
|
+
questions = [],
|
|
153
|
+
strategy = "prefer-second-or-text",
|
|
154
|
+
) {
|
|
155
|
+
const normalizedStrategy = String(
|
|
156
|
+
strategy || "prefer-second-or-text",
|
|
157
|
+
).trim().toLowerCase();
|
|
158
|
+
if (
|
|
159
|
+
!["prefer-second-or-text", "prefer-second-or-first"].includes(
|
|
160
|
+
normalizedStrategy,
|
|
161
|
+
)
|
|
162
|
+
) {
|
|
163
|
+
throw new CodeKanbanValidationError(
|
|
164
|
+
`unsupported answer strategy: ${strategy}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const answers = {};
|
|
169
|
+
for (const question of Array.isArray(questions) ? questions : []) {
|
|
170
|
+
const questionId = normalizeWebSessionQuestionChoice(question?.id);
|
|
171
|
+
if (!questionId) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const options = Array.isArray(question?.options)
|
|
175
|
+
? question.options
|
|
176
|
+
.map((option) => normalizeWebSessionQuestionChoice(option?.label))
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
: [];
|
|
179
|
+
if (options[1]) {
|
|
180
|
+
answers[questionId] = [options[1]];
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (normalizedStrategy === "prefer-second-or-first" && options[0]) {
|
|
184
|
+
answers[questionId] = [options[0]];
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
answers[questionId] = [question?.isSecret ? "redacted" : "continue"];
|
|
188
|
+
}
|
|
189
|
+
return answers;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function normalizeWebSessionStateMatcher(until) {
|
|
193
|
+
if (!until) {
|
|
194
|
+
throw new CodeKanbanValidationError("until is required");
|
|
195
|
+
}
|
|
196
|
+
return typeof until === "function"
|
|
197
|
+
? until
|
|
198
|
+
: Array.isArray(until)
|
|
199
|
+
? (state) => until.includes(state.phase)
|
|
200
|
+
: (state) => state.phase === until;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function getWebSessionPauseReason(state, untilMatcher) {
|
|
204
|
+
if (!state) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
if (state.phase === "done") {
|
|
208
|
+
return "done";
|
|
209
|
+
}
|
|
210
|
+
if (state.phase === "error") {
|
|
211
|
+
return "error";
|
|
212
|
+
}
|
|
213
|
+
if (typeof untilMatcher === "function" && untilMatcher(state)) {
|
|
214
|
+
return "until";
|
|
215
|
+
}
|
|
216
|
+
if (state.nextAction?.type === "approval") {
|
|
217
|
+
return "approval";
|
|
218
|
+
}
|
|
219
|
+
if (state.nextAction?.type === "answer_user_input") {
|
|
220
|
+
return "user_input";
|
|
221
|
+
}
|
|
222
|
+
if (
|
|
223
|
+
state.nextAction?.type === "execute_plan" ||
|
|
224
|
+
(state.latestPlan?.awaitingExecution && state.canSend)
|
|
225
|
+
) {
|
|
226
|
+
return "execute_plan";
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isDebouncedPauseReason(reason) {
|
|
232
|
+
return reason === "done" || reason === "error" || reason === "until";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function hasOwnField(value, fieldName) {
|
|
236
|
+
return Boolean(
|
|
237
|
+
value &&
|
|
238
|
+
typeof value === "object" &&
|
|
239
|
+
Object.prototype.hasOwnProperty.call(value, fieldName),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function extractPayloadItem(payload) {
|
|
244
|
+
if (hasOwnField(payload?.body, "item")) {
|
|
245
|
+
return payload.body.item;
|
|
246
|
+
}
|
|
247
|
+
if (hasOwnField(payload, "item")) {
|
|
248
|
+
return payload.item;
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function extractPayloadItems(payload) {
|
|
254
|
+
if (Array.isArray(payload?.body?.items)) {
|
|
255
|
+
return payload.body.items;
|
|
256
|
+
}
|
|
257
|
+
if (Array.isArray(payload?.items)) {
|
|
258
|
+
return payload.items;
|
|
259
|
+
}
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function extractPayloadMessage(payload) {
|
|
264
|
+
if (typeof payload?.body?.message === "string") {
|
|
265
|
+
return payload.body.message;
|
|
266
|
+
}
|
|
267
|
+
if (typeof payload?.message === "string") {
|
|
268
|
+
return payload.message;
|
|
269
|
+
}
|
|
270
|
+
return "";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function normalizeProjectSearchToken(value) {
|
|
274
|
+
return ensureOptionalString(value).toLowerCase();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function uniqueProjectsById(projects) {
|
|
278
|
+
const seen = new Set();
|
|
279
|
+
return projects.filter((project) => {
|
|
280
|
+
const id = ensureOptionalString(project?.id);
|
|
281
|
+
if (!id || seen.has(id)) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
seen.add(id);
|
|
285
|
+
return true;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function projectPathBaseName(project) {
|
|
290
|
+
const projectPath = ensureOptionalString(project?.path);
|
|
291
|
+
return projectPath ? normalizeProjectSearchToken(pathBasename(projectPath)) : "";
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function normalizeProjectIndex(projectIndex) {
|
|
295
|
+
if (projectIndex == null || projectIndex === "") {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const normalized = Number(projectIndex);
|
|
299
|
+
if (!Number.isInteger(normalized) || normalized < 1) {
|
|
300
|
+
throw new CodeKanbanValidationError(
|
|
301
|
+
"projectIndex must be a positive integer",
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
return normalized;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function selectProjectCandidate(
|
|
308
|
+
projectName,
|
|
309
|
+
candidates,
|
|
310
|
+
projectIndex,
|
|
311
|
+
reason,
|
|
312
|
+
matchedBy,
|
|
313
|
+
) {
|
|
314
|
+
const normalizedProjectIndex = normalizeProjectIndex(projectIndex);
|
|
315
|
+
if (normalizedProjectIndex != null) {
|
|
316
|
+
const selected = candidates[normalizedProjectIndex - 1];
|
|
317
|
+
if (!selected) {
|
|
318
|
+
throw new CodeKanbanValidationError(
|
|
319
|
+
`projectIndex ${normalizedProjectIndex} is out of range for projectName ${projectName}`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
project: selected,
|
|
324
|
+
matchedBy,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
throw new CodeKanbanValidationError(
|
|
328
|
+
`projectName ${projectName} ${reason}; provide projectIndex or projectId`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function resolveProjectByName(projects, projectName, projectIndex) {
|
|
333
|
+
const needle = normalizeProjectSearchToken(projectName);
|
|
334
|
+
if (!needle) {
|
|
335
|
+
throw new CodeKanbanValidationError("projectName is required");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const exactName = uniqueProjectsById(
|
|
339
|
+
projects.filter(
|
|
340
|
+
(project) => normalizeProjectSearchToken(project?.name) === needle,
|
|
341
|
+
),
|
|
342
|
+
);
|
|
343
|
+
if (exactName.length === 1) {
|
|
344
|
+
return {
|
|
345
|
+
project: exactName[0],
|
|
346
|
+
matchedBy: "projectName",
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (exactName.length > 1) {
|
|
350
|
+
return selectProjectCandidate(
|
|
351
|
+
projectName,
|
|
352
|
+
exactName,
|
|
353
|
+
projectIndex,
|
|
354
|
+
"matches multiple projects",
|
|
355
|
+
"projectName",
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const exactBaseName = uniqueProjectsById(
|
|
360
|
+
projects.filter((project) => projectPathBaseName(project) === needle),
|
|
361
|
+
);
|
|
362
|
+
if (exactBaseName.length === 1) {
|
|
363
|
+
return {
|
|
364
|
+
project: exactBaseName[0],
|
|
365
|
+
matchedBy: "projectPathBaseName",
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (exactBaseName.length > 1) {
|
|
369
|
+
return selectProjectCandidate(
|
|
370
|
+
projectName,
|
|
371
|
+
exactBaseName,
|
|
372
|
+
projectIndex,
|
|
373
|
+
"matches multiple project paths",
|
|
374
|
+
"projectPathBaseName",
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const fuzzy = uniqueProjectsById(
|
|
379
|
+
projects.filter((project) => {
|
|
380
|
+
const normalizedName = normalizeProjectSearchToken(project?.name);
|
|
381
|
+
const normalizedBaseName = projectPathBaseName(project);
|
|
382
|
+
return (
|
|
383
|
+
normalizedName.includes(needle) || normalizedBaseName.includes(needle)
|
|
384
|
+
);
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
387
|
+
if (fuzzy.length === 1) {
|
|
388
|
+
return {
|
|
389
|
+
project: fuzzy[0],
|
|
390
|
+
matchedBy: "projectNameFuzzy",
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
if (fuzzy.length > 1) {
|
|
394
|
+
return selectProjectCandidate(
|
|
395
|
+
projectName,
|
|
396
|
+
fuzzy,
|
|
397
|
+
projectIndex,
|
|
398
|
+
"is ambiguous",
|
|
399
|
+
"projectNameFuzzy",
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
throw new CodeKanbanValidationError(
|
|
404
|
+
`no CodeKanban project is registered for projectName: ${projectName}`,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
146
408
|
export class CodeKanbanClient {
|
|
147
409
|
constructor(options = {}) {
|
|
148
410
|
this.baseURL = normalizeBaseUrl(options.baseURL);
|
|
149
411
|
this.headers = { ...(options.headers || {}) };
|
|
150
412
|
this.fetchImpl = options.fetchImpl || globalThis.fetch;
|
|
151
413
|
this.WebSocketImpl = options.WebSocketImpl || globalThis.WebSocket;
|
|
414
|
+
this.webSocketOptions = options.webSocketOptions || null;
|
|
152
415
|
this.requestRetry = normalizeRequestRetryConfig(options.requestRetry);
|
|
153
416
|
if (!this.fetchImpl) {
|
|
154
417
|
throw new CodeKanbanConfigError("fetch implementation is unavailable");
|
|
@@ -215,22 +478,104 @@ export class CodeKanbanClient {
|
|
|
215
478
|
);
|
|
216
479
|
}
|
|
217
480
|
|
|
218
|
-
async
|
|
481
|
+
async requestText(path, options = {}) {
|
|
482
|
+
const method = String(options.method || "GET").toUpperCase();
|
|
483
|
+
const headers = {
|
|
484
|
+
...this.headers,
|
|
485
|
+
...(options.headers || {}),
|
|
486
|
+
};
|
|
487
|
+
const request = {
|
|
488
|
+
method,
|
|
489
|
+
headers,
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const retryConfig = resolveRequestRetryConfig(
|
|
493
|
+
method,
|
|
494
|
+
this.requestRetry,
|
|
495
|
+
options.retry,
|
|
496
|
+
);
|
|
497
|
+
const maxAttempts = retryConfig?.attempts ?? 1;
|
|
498
|
+
|
|
499
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
500
|
+
try {
|
|
501
|
+
const response = await this.fetchImpl(
|
|
502
|
+
new URL(path, this.baseURL),
|
|
503
|
+
request,
|
|
504
|
+
);
|
|
505
|
+
const text = await response.text();
|
|
506
|
+
if (!response.ok) {
|
|
507
|
+
let body = null;
|
|
508
|
+
try {
|
|
509
|
+
body = text ? JSON.parse(text) : null;
|
|
510
|
+
} catch {
|
|
511
|
+
body = text || null;
|
|
512
|
+
}
|
|
513
|
+
throw new CodeKanbanHttpError(
|
|
514
|
+
`request failed with ${response.status}`,
|
|
515
|
+
{
|
|
516
|
+
status: response.status,
|
|
517
|
+
method,
|
|
518
|
+
path,
|
|
519
|
+
body,
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
text,
|
|
525
|
+
contentType:
|
|
526
|
+
typeof response.headers?.get === "function"
|
|
527
|
+
? response.headers.get("content-type") || ""
|
|
528
|
+
: "",
|
|
529
|
+
};
|
|
530
|
+
} catch (error) {
|
|
531
|
+
const canRetry =
|
|
532
|
+
retryConfig &&
|
|
533
|
+
attempt + 1 < maxAttempts &&
|
|
534
|
+
isRetryableRequestError(error);
|
|
535
|
+
if (!canRetry) {
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
await sleep(getRetryDelayMs(retryConfig, attempt));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
throw new CodeKanbanValidationError(
|
|
543
|
+
`request retry loop exited unexpectedly for ${method} ${path}`,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async resolveProjectReference({
|
|
548
|
+
projectId,
|
|
549
|
+
projectName,
|
|
550
|
+
projectIndex,
|
|
551
|
+
path,
|
|
552
|
+
ensureProject = true,
|
|
553
|
+
}) {
|
|
219
554
|
const { project } = await this.resolveProject({
|
|
220
555
|
projectId,
|
|
556
|
+
projectName,
|
|
557
|
+
projectIndex,
|
|
221
558
|
path,
|
|
222
559
|
ensureProject,
|
|
223
560
|
});
|
|
224
561
|
return project;
|
|
225
562
|
}
|
|
226
563
|
|
|
227
|
-
async resolveProjectId({
|
|
564
|
+
async resolveProjectId({
|
|
565
|
+
projectId,
|
|
566
|
+
projectName,
|
|
567
|
+
projectIndex,
|
|
568
|
+
path,
|
|
569
|
+
ensureProject = true,
|
|
570
|
+
}) {
|
|
228
571
|
const resolvedProjectId = ensureOptionalString(projectId);
|
|
229
572
|
if (resolvedProjectId) {
|
|
230
573
|
return resolvedProjectId;
|
|
231
574
|
}
|
|
232
575
|
const { project } = await this.resolveProject({
|
|
233
576
|
projectId,
|
|
577
|
+
projectName,
|
|
578
|
+
projectIndex,
|
|
234
579
|
path,
|
|
235
580
|
ensureProject,
|
|
236
581
|
});
|
|
@@ -239,13 +584,13 @@ export class CodeKanbanClient {
|
|
|
239
584
|
|
|
240
585
|
async listProjects() {
|
|
241
586
|
const response = await this.requestJson("/api/v1/projects");
|
|
242
|
-
return response
|
|
587
|
+
return extractPayloadItems(response);
|
|
243
588
|
}
|
|
244
589
|
|
|
245
590
|
async getProject(projectId) {
|
|
246
591
|
ensureString(projectId, "projectId");
|
|
247
592
|
const response = await this.requestJson(`/api/v1/projects/${projectId}`);
|
|
248
|
-
return response
|
|
593
|
+
return extractPayloadItem(response);
|
|
249
594
|
}
|
|
250
595
|
|
|
251
596
|
async createProject({
|
|
@@ -267,7 +612,7 @@ export class CodeKanbanClient {
|
|
|
267
612
|
hidePath,
|
|
268
613
|
},
|
|
269
614
|
});
|
|
270
|
-
return response
|
|
615
|
+
return extractPayloadItem(response);
|
|
271
616
|
}
|
|
272
617
|
|
|
273
618
|
async listWorktrees(projectId) {
|
|
@@ -275,7 +620,7 @@ export class CodeKanbanClient {
|
|
|
275
620
|
const response = await this.requestJson(
|
|
276
621
|
`/api/v1/projects/${projectId}/worktrees`,
|
|
277
622
|
);
|
|
278
|
-
return response
|
|
623
|
+
return extractPayloadItems(response);
|
|
279
624
|
}
|
|
280
625
|
|
|
281
626
|
async listTerminalSessions(projectId) {
|
|
@@ -283,7 +628,7 @@ export class CodeKanbanClient {
|
|
|
283
628
|
const response = await this.requestJson(
|
|
284
629
|
`/api/v1/projects/${projectId}/terminals`,
|
|
285
630
|
);
|
|
286
|
-
return response
|
|
631
|
+
return extractPayloadItems(response);
|
|
287
632
|
}
|
|
288
633
|
|
|
289
634
|
async listAISessionsByProject(projectId) {
|
|
@@ -291,7 +636,7 @@ export class CodeKanbanClient {
|
|
|
291
636
|
const response = await this.requestJson(
|
|
292
637
|
`/api/v1/projects/${projectId}/ai-sessions`,
|
|
293
638
|
);
|
|
294
|
-
return response
|
|
639
|
+
return extractPayloadItem(response);
|
|
295
640
|
}
|
|
296
641
|
|
|
297
642
|
async listAISessionsByPath(projectPath) {
|
|
@@ -300,7 +645,7 @@ export class CodeKanbanClient {
|
|
|
300
645
|
method: "POST",
|
|
301
646
|
body: { path: projectPath },
|
|
302
647
|
});
|
|
303
|
-
return response
|
|
648
|
+
return extractPayloadItem(response);
|
|
304
649
|
}
|
|
305
650
|
|
|
306
651
|
async getAISessionConversation({ id, sessionId, refresh = false }) {
|
|
@@ -322,13 +667,13 @@ export class CodeKanbanClient {
|
|
|
322
667
|
const response = await this.requestJson(path, {
|
|
323
668
|
method: refresh ? "POST" : "GET",
|
|
324
669
|
});
|
|
325
|
-
return response
|
|
670
|
+
return extractPayloadItem(response);
|
|
326
671
|
}
|
|
327
672
|
|
|
328
673
|
const response = await this.requestJson(
|
|
329
674
|
`/api/v1/ai-sessions/by-session-id/${rawSessionId}/conversation`,
|
|
330
675
|
);
|
|
331
|
-
return response
|
|
676
|
+
return extractPayloadItem(response);
|
|
332
677
|
}
|
|
333
678
|
|
|
334
679
|
async getAISessionToolResult({ id, sessionId, toolUseId }) {
|
|
@@ -342,7 +687,7 @@ export class CodeKanbanClient {
|
|
|
342
687
|
? `/api/v1/ai-sessions/${dbId}/conversation/tool-results/${resolvedToolUseId}`
|
|
343
688
|
: `/api/v1/ai-sessions/by-session-id/${rawSessionId}/conversation/tool-results/${resolvedToolUseId}`;
|
|
344
689
|
const response = await this.requestJson(path);
|
|
345
|
-
return response
|
|
690
|
+
return extractPayloadItem(response);
|
|
346
691
|
}
|
|
347
692
|
|
|
348
693
|
async createTerminalSession({
|
|
@@ -367,11 +712,18 @@ export class CodeKanbanClient {
|
|
|
367
712
|
},
|
|
368
713
|
},
|
|
369
714
|
);
|
|
370
|
-
return response
|
|
715
|
+
return extractPayloadItem(response);
|
|
371
716
|
}
|
|
372
717
|
|
|
373
|
-
async resolveProject({
|
|
718
|
+
async resolveProject({
|
|
719
|
+
projectId,
|
|
720
|
+
projectName,
|
|
721
|
+
projectIndex,
|
|
722
|
+
path,
|
|
723
|
+
ensureProject = true,
|
|
724
|
+
}) {
|
|
374
725
|
const resolvedProjectId = ensureOptionalString(projectId);
|
|
726
|
+
const resolvedProjectName = ensureOptionalString(projectName);
|
|
375
727
|
const resolvedPath = ensureOptionalString(path);
|
|
376
728
|
|
|
377
729
|
if (resolvedProjectId) {
|
|
@@ -382,12 +734,20 @@ export class CodeKanbanClient {
|
|
|
382
734
|
};
|
|
383
735
|
}
|
|
384
736
|
|
|
737
|
+
const projects =
|
|
738
|
+
resolvedProjectName || resolvedPath ? await this.listProjects() : [];
|
|
739
|
+
|
|
740
|
+
if (resolvedProjectName) {
|
|
741
|
+
return resolveProjectByName(projects, resolvedProjectName, projectIndex);
|
|
742
|
+
}
|
|
743
|
+
|
|
385
744
|
if (!resolvedPath) {
|
|
386
|
-
throw new CodeKanbanValidationError(
|
|
745
|
+
throw new CodeKanbanValidationError(
|
|
746
|
+
"projectId, projectName, or path is required",
|
|
747
|
+
);
|
|
387
748
|
}
|
|
388
749
|
|
|
389
750
|
const target = normalizeFsPath(resolvedPath);
|
|
390
|
-
const projects = await this.listProjects();
|
|
391
751
|
const project = projects.find(
|
|
392
752
|
(item) => normalizeFsPath(item.path) === target,
|
|
393
753
|
);
|
|
@@ -451,6 +811,7 @@ export class CodeKanbanClient {
|
|
|
451
811
|
sessionId: resolvedSessionId,
|
|
452
812
|
url,
|
|
453
813
|
WebSocketImpl: this.WebSocketImpl,
|
|
814
|
+
webSocketOptions: this.webSocketOptions,
|
|
454
815
|
});
|
|
455
816
|
}
|
|
456
817
|
|
|
@@ -458,6 +819,7 @@ export class CodeKanbanClient {
|
|
|
458
819
|
return new WebSessionCommandChannel({
|
|
459
820
|
url: toWsUrl(this.baseURL, WEB_SESSION_COMMAND_WS_PATH),
|
|
460
821
|
WebSocketImpl: this.WebSocketImpl,
|
|
822
|
+
webSocketOptions: this.webSocketOptions,
|
|
461
823
|
});
|
|
462
824
|
}
|
|
463
825
|
|
|
@@ -466,6 +828,7 @@ export class CodeKanbanClient {
|
|
|
466
828
|
url: toWsUrl(this.baseURL, WEB_SESSION_EVENTS_WS_PATH),
|
|
467
829
|
sessionId: ensureOptionalString(options.sessionId),
|
|
468
830
|
WebSocketImpl: this.WebSocketImpl,
|
|
831
|
+
webSocketOptions: this.webSocketOptions,
|
|
469
832
|
});
|
|
470
833
|
}
|
|
471
834
|
|
|
@@ -481,6 +844,8 @@ export class CodeKanbanClient {
|
|
|
481
844
|
|
|
482
845
|
async listSessions({
|
|
483
846
|
projectId,
|
|
847
|
+
projectName,
|
|
848
|
+
projectIndex,
|
|
484
849
|
path,
|
|
485
850
|
includeTerminal = true,
|
|
486
851
|
includeAI = true,
|
|
@@ -488,6 +853,8 @@ export class CodeKanbanClient {
|
|
|
488
853
|
}) {
|
|
489
854
|
const { project, matchedBy } = await this.resolveProject({
|
|
490
855
|
projectId,
|
|
856
|
+
projectName,
|
|
857
|
+
projectIndex,
|
|
491
858
|
path,
|
|
492
859
|
ensureProject,
|
|
493
860
|
});
|
|
@@ -514,21 +881,108 @@ export class CodeKanbanClient {
|
|
|
514
881
|
};
|
|
515
882
|
}
|
|
516
883
|
|
|
517
|
-
async
|
|
884
|
+
async listProjectFileScopes({ projectId, projectName, projectIndex, path, ensureProject = true } = {}) {
|
|
518
885
|
const resolvedProjectId = await this.resolveProjectId({
|
|
519
886
|
projectId,
|
|
887
|
+
projectName,
|
|
888
|
+
projectIndex,
|
|
889
|
+
path,
|
|
890
|
+
ensureProject,
|
|
891
|
+
});
|
|
892
|
+
const response = await this.requestJson(
|
|
893
|
+
`/api/v1/projects/${resolvedProjectId}/files/scopes`,
|
|
894
|
+
);
|
|
895
|
+
return extractPayloadItems(response);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
async readProjectFileText({
|
|
899
|
+
projectId,
|
|
900
|
+
projectName,
|
|
901
|
+
projectIndex,
|
|
902
|
+
path,
|
|
903
|
+
scopeId,
|
|
904
|
+
filePath,
|
|
905
|
+
ensureProject = true,
|
|
906
|
+
} = {}) {
|
|
907
|
+
const resolvedProjectId = await this.resolveProjectId({
|
|
908
|
+
projectId,
|
|
909
|
+
projectName,
|
|
910
|
+
projectIndex,
|
|
911
|
+
path,
|
|
912
|
+
ensureProject,
|
|
913
|
+
});
|
|
914
|
+
const params = new URLSearchParams();
|
|
915
|
+
if (ensureOptionalString(scopeId)) {
|
|
916
|
+
params.set("scopeId", ensureOptionalString(scopeId));
|
|
917
|
+
}
|
|
918
|
+
params.set("path", ensureString(filePath, "filePath"));
|
|
919
|
+
params.set("disposition", "inline");
|
|
920
|
+
const { text, contentType } = await this.requestText(
|
|
921
|
+
`/api/v1/projects/${resolvedProjectId}/files/content?${params.toString()}`,
|
|
922
|
+
);
|
|
923
|
+
return {
|
|
924
|
+
projectId: resolvedProjectId,
|
|
925
|
+
scopeId: ensureOptionalString(scopeId) || null,
|
|
926
|
+
path: ensureString(filePath, "filePath"),
|
|
927
|
+
contentType,
|
|
928
|
+
text,
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
async deleteProjectFiles({
|
|
933
|
+
projectId,
|
|
934
|
+
projectName,
|
|
935
|
+
projectIndex,
|
|
936
|
+
path,
|
|
937
|
+
scopeId,
|
|
938
|
+
paths,
|
|
939
|
+
ensureProject = true,
|
|
940
|
+
} = {}) {
|
|
941
|
+
const resolvedProjectId = await this.resolveProjectId({
|
|
942
|
+
projectId,
|
|
943
|
+
projectName,
|
|
944
|
+
projectIndex,
|
|
945
|
+
path,
|
|
946
|
+
ensureProject,
|
|
947
|
+
});
|
|
948
|
+
const normalizedPaths = ensureArrayOfStrings(paths, "paths");
|
|
949
|
+
if (normalizedPaths.length === 0) {
|
|
950
|
+
throw new CodeKanbanValidationError("paths is required");
|
|
951
|
+
}
|
|
952
|
+
const response = await this.requestJson(
|
|
953
|
+
`/api/v1/projects/${resolvedProjectId}/files/delete`,
|
|
954
|
+
{
|
|
955
|
+
method: "POST",
|
|
956
|
+
body: {
|
|
957
|
+
...(ensureOptionalString(scopeId)
|
|
958
|
+
? { scopeId: ensureOptionalString(scopeId) }
|
|
959
|
+
: {}),
|
|
960
|
+
paths: normalizedPaths,
|
|
961
|
+
},
|
|
962
|
+
},
|
|
963
|
+
);
|
|
964
|
+
return extractPayloadItem(response);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
async listWebSessions({ projectId, projectName, projectIndex, path, ensureProject = true } = {}) {
|
|
968
|
+
const resolvedProjectId = await this.resolveProjectId({
|
|
969
|
+
projectId,
|
|
970
|
+
projectName,
|
|
971
|
+
projectIndex,
|
|
520
972
|
path,
|
|
521
973
|
ensureProject,
|
|
522
974
|
});
|
|
523
975
|
const response = await this.requestJson(
|
|
524
976
|
`/api/v1/projects/${resolvedProjectId}/web-sessions`,
|
|
525
977
|
);
|
|
526
|
-
return response
|
|
978
|
+
return extractPayloadItems(response);
|
|
527
979
|
}
|
|
528
980
|
|
|
529
981
|
async createWebSession(input = {}) {
|
|
530
982
|
const projectId = await this.resolveProjectId({
|
|
531
983
|
projectId: input.projectId,
|
|
984
|
+
projectName: input.projectName,
|
|
985
|
+
projectIndex: input.projectIndex,
|
|
532
986
|
path: input.path,
|
|
533
987
|
ensureProject: true,
|
|
534
988
|
});
|
|
@@ -572,12 +1026,14 @@ export class CodeKanbanClient {
|
|
|
572
1026
|
},
|
|
573
1027
|
},
|
|
574
1028
|
);
|
|
575
|
-
return response
|
|
1029
|
+
return extractPayloadItem(response);
|
|
576
1030
|
}
|
|
577
1031
|
|
|
578
|
-
async getWebSessionSnapshot({ projectId, path, sessionId, limit = 80 }) {
|
|
1032
|
+
async getWebSessionSnapshot({ projectId, projectName, projectIndex, path, sessionId, limit = 80 }) {
|
|
579
1033
|
const resolvedProjectId = await this.resolveProjectId({
|
|
580
1034
|
projectId,
|
|
1035
|
+
projectName,
|
|
1036
|
+
projectIndex,
|
|
581
1037
|
path,
|
|
582
1038
|
ensureProject: true,
|
|
583
1039
|
});
|
|
@@ -588,11 +1044,13 @@ export class CodeKanbanClient {
|
|
|
588
1044
|
const response = await this.requestJson(
|
|
589
1045
|
`/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/snapshot?limit=${normalizedLimit}`,
|
|
590
1046
|
);
|
|
591
|
-
return response
|
|
1047
|
+
return extractPayloadItem(response);
|
|
592
1048
|
}
|
|
593
1049
|
|
|
594
1050
|
async getWebSessionHistory({
|
|
595
1051
|
projectId,
|
|
1052
|
+
projectName,
|
|
1053
|
+
projectIndex,
|
|
596
1054
|
path,
|
|
597
1055
|
sessionId,
|
|
598
1056
|
beforeCursor,
|
|
@@ -600,6 +1058,8 @@ export class CodeKanbanClient {
|
|
|
600
1058
|
}) {
|
|
601
1059
|
const resolvedProjectId = await this.resolveProjectId({
|
|
602
1060
|
projectId,
|
|
1061
|
+
projectName,
|
|
1062
|
+
projectIndex,
|
|
603
1063
|
path,
|
|
604
1064
|
ensureProject: true,
|
|
605
1065
|
});
|
|
@@ -615,11 +1075,13 @@ export class CodeKanbanClient {
|
|
|
615
1075
|
const response = await this.requestJson(
|
|
616
1076
|
`/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/history${suffix ? `?${suffix}` : ""}`,
|
|
617
1077
|
);
|
|
618
|
-
return response
|
|
1078
|
+
return extractPayloadItem(response);
|
|
619
1079
|
}
|
|
620
1080
|
|
|
621
1081
|
async syncWebSession({
|
|
622
1082
|
projectId,
|
|
1083
|
+
projectName,
|
|
1084
|
+
projectIndex,
|
|
623
1085
|
path,
|
|
624
1086
|
sessionId,
|
|
625
1087
|
mode,
|
|
@@ -627,6 +1089,8 @@ export class CodeKanbanClient {
|
|
|
627
1089
|
}) {
|
|
628
1090
|
const resolvedProjectId = await this.resolveProjectId({
|
|
629
1091
|
projectId,
|
|
1092
|
+
projectName,
|
|
1093
|
+
projectIndex,
|
|
630
1094
|
path,
|
|
631
1095
|
ensureProject: true,
|
|
632
1096
|
});
|
|
@@ -643,12 +1107,14 @@ export class CodeKanbanClient {
|
|
|
643
1107
|
},
|
|
644
1108
|
},
|
|
645
1109
|
);
|
|
646
|
-
return response
|
|
1110
|
+
return extractPayloadItem(response);
|
|
647
1111
|
}
|
|
648
1112
|
|
|
649
|
-
async archiveWebSession({ projectId, path, sessionId }) {
|
|
1113
|
+
async archiveWebSession({ projectId, projectName, projectIndex, path, sessionId }) {
|
|
650
1114
|
const resolvedProjectId = await this.resolveProjectId({
|
|
651
1115
|
projectId,
|
|
1116
|
+
projectName,
|
|
1117
|
+
projectIndex,
|
|
652
1118
|
path,
|
|
653
1119
|
ensureProject: true,
|
|
654
1120
|
});
|
|
@@ -659,12 +1125,14 @@ export class CodeKanbanClient {
|
|
|
659
1125
|
method: "POST",
|
|
660
1126
|
},
|
|
661
1127
|
);
|
|
662
|
-
return response
|
|
1128
|
+
return extractPayloadItem(response);
|
|
663
1129
|
}
|
|
664
1130
|
|
|
665
|
-
async unarchiveWebSession({ projectId, path, sessionId }) {
|
|
1131
|
+
async unarchiveWebSession({ projectId, projectName, projectIndex, path, sessionId }) {
|
|
666
1132
|
const resolvedProjectId = await this.resolveProjectId({
|
|
667
1133
|
projectId,
|
|
1134
|
+
projectName,
|
|
1135
|
+
projectIndex,
|
|
668
1136
|
path,
|
|
669
1137
|
ensureProject: true,
|
|
670
1138
|
});
|
|
@@ -675,12 +1143,14 @@ export class CodeKanbanClient {
|
|
|
675
1143
|
method: "POST",
|
|
676
1144
|
},
|
|
677
1145
|
);
|
|
678
|
-
return response
|
|
1146
|
+
return extractPayloadItem(response);
|
|
679
1147
|
}
|
|
680
1148
|
|
|
681
|
-
async renameWebSession({ projectId, path, sessionId, title }) {
|
|
1149
|
+
async renameWebSession({ projectId, projectName, projectIndex, path, sessionId, title }) {
|
|
682
1150
|
const resolvedProjectId = await this.resolveProjectId({
|
|
683
1151
|
projectId,
|
|
1152
|
+
projectName,
|
|
1153
|
+
projectIndex,
|
|
684
1154
|
path,
|
|
685
1155
|
ensureProject: true,
|
|
686
1156
|
});
|
|
@@ -694,12 +1164,14 @@ export class CodeKanbanClient {
|
|
|
694
1164
|
},
|
|
695
1165
|
},
|
|
696
1166
|
);
|
|
697
|
-
return response
|
|
1167
|
+
return extractPayloadItem(response);
|
|
698
1168
|
}
|
|
699
1169
|
|
|
700
|
-
async closeWebSession({ projectId, path, sessionId }) {
|
|
1170
|
+
async closeWebSession({ projectId, projectName, projectIndex, path, sessionId }) {
|
|
701
1171
|
const resolvedProjectId = await this.resolveProjectId({
|
|
702
1172
|
projectId,
|
|
1173
|
+
projectName,
|
|
1174
|
+
projectIndex,
|
|
703
1175
|
path,
|
|
704
1176
|
ensureProject: true,
|
|
705
1177
|
});
|
|
@@ -711,13 +1183,15 @@ export class CodeKanbanClient {
|
|
|
711
1183
|
},
|
|
712
1184
|
);
|
|
713
1185
|
return {
|
|
714
|
-
message: response
|
|
1186
|
+
message: extractPayloadMessage(response) || "session aborted",
|
|
715
1187
|
};
|
|
716
1188
|
}
|
|
717
1189
|
|
|
718
|
-
async deleteWebSession({ projectId, path, sessionId }) {
|
|
1190
|
+
async deleteWebSession({ projectId, projectName, projectIndex, path, sessionId }) {
|
|
719
1191
|
const resolvedProjectId = await this.resolveProjectId({
|
|
720
1192
|
projectId,
|
|
1193
|
+
projectName,
|
|
1194
|
+
projectIndex,
|
|
721
1195
|
path,
|
|
722
1196
|
ensureProject: true,
|
|
723
1197
|
});
|
|
@@ -729,7 +1203,7 @@ export class CodeKanbanClient {
|
|
|
729
1203
|
},
|
|
730
1204
|
);
|
|
731
1205
|
return {
|
|
732
|
-
message: response
|
|
1206
|
+
message: extractPayloadMessage(response) || "session deleted",
|
|
733
1207
|
};
|
|
734
1208
|
}
|
|
735
1209
|
|
|
@@ -746,13 +1220,15 @@ export class CodeKanbanClient {
|
|
|
746
1220
|
},
|
|
747
1221
|
);
|
|
748
1222
|
return (
|
|
749
|
-
response
|
|
1223
|
+
extractPayloadItem(response) || { items: [], total: 0, hasMore: false, nextOffset: 0 }
|
|
750
1224
|
);
|
|
751
1225
|
}
|
|
752
1226
|
|
|
753
|
-
async getWebSessionCommandGroup({ projectId, path, sessionId, groupId }) {
|
|
1227
|
+
async getWebSessionCommandGroup({ projectId, projectName, projectIndex, path, sessionId, groupId }) {
|
|
754
1228
|
const resolvedProjectId = await this.resolveProjectId({
|
|
755
1229
|
projectId,
|
|
1230
|
+
projectName,
|
|
1231
|
+
projectIndex,
|
|
756
1232
|
path,
|
|
757
1233
|
ensureProject: true,
|
|
758
1234
|
});
|
|
@@ -761,18 +1237,20 @@ export class CodeKanbanClient {
|
|
|
761
1237
|
const response = await this.requestJson(
|
|
762
1238
|
`/api/v1/projects/${resolvedProjectId}/web-sessions/${resolvedSessionId}/command-groups/${resolvedGroupId}`,
|
|
763
1239
|
);
|
|
764
|
-
return response
|
|
1240
|
+
return extractPayloadItem(response);
|
|
765
1241
|
}
|
|
766
1242
|
|
|
767
1243
|
async getWebSessionRuntimeConfig() {
|
|
768
1244
|
const response = await this.requestJson(
|
|
769
1245
|
"/api/v1/web-sessions/runtime-config",
|
|
770
1246
|
);
|
|
771
|
-
return response
|
|
1247
|
+
return extractPayloadItem(response);
|
|
772
1248
|
}
|
|
773
1249
|
|
|
774
1250
|
async uploadWebSessionAttachment({
|
|
775
1251
|
projectId,
|
|
1252
|
+
projectName,
|
|
1253
|
+
projectIndex,
|
|
776
1254
|
path,
|
|
777
1255
|
filePath,
|
|
778
1256
|
fileName,
|
|
@@ -780,6 +1258,8 @@ export class CodeKanbanClient {
|
|
|
780
1258
|
}) {
|
|
781
1259
|
const resolvedProjectId = await this.resolveProjectId({
|
|
782
1260
|
projectId,
|
|
1261
|
+
projectName,
|
|
1262
|
+
projectIndex,
|
|
783
1263
|
path,
|
|
784
1264
|
ensureProject: true,
|
|
785
1265
|
});
|
|
@@ -821,16 +1301,18 @@ export class CodeKanbanClient {
|
|
|
821
1301
|
body,
|
|
822
1302
|
});
|
|
823
1303
|
}
|
|
824
|
-
return normalizeWebSessionAttachment(body
|
|
1304
|
+
return normalizeWebSessionAttachment(extractPayloadItem(body));
|
|
825
1305
|
}
|
|
826
1306
|
|
|
827
1307
|
analyzeWebSession(snapshot) {
|
|
828
1308
|
return analyzeWebSession(snapshot);
|
|
829
1309
|
}
|
|
830
1310
|
|
|
831
|
-
async getWebSessionState({ projectId, path, sessionId, limit = 120 }) {
|
|
1311
|
+
async getWebSessionState({ projectId, projectName, projectIndex, path, sessionId, limit = 120 }) {
|
|
832
1312
|
const snapshot = await this.getWebSessionSnapshot({
|
|
833
1313
|
projectId,
|
|
1314
|
+
projectName,
|
|
1315
|
+
projectIndex,
|
|
834
1316
|
path,
|
|
835
1317
|
sessionId,
|
|
836
1318
|
limit,
|
|
@@ -887,6 +1369,8 @@ export class CodeKanbanClient {
|
|
|
887
1369
|
|
|
888
1370
|
async answerPendingUserInput({
|
|
889
1371
|
projectId,
|
|
1372
|
+
projectName,
|
|
1373
|
+
projectIndex,
|
|
890
1374
|
path,
|
|
891
1375
|
sessionId,
|
|
892
1376
|
answers,
|
|
@@ -894,6 +1378,8 @@ export class CodeKanbanClient {
|
|
|
894
1378
|
}) {
|
|
895
1379
|
const state = await this.getWebSessionState({
|
|
896
1380
|
projectId,
|
|
1381
|
+
projectName,
|
|
1382
|
+
projectIndex,
|
|
897
1383
|
path,
|
|
898
1384
|
sessionId,
|
|
899
1385
|
limit,
|
|
@@ -917,9 +1403,11 @@ export class CodeKanbanClient {
|
|
|
917
1403
|
};
|
|
918
1404
|
}
|
|
919
1405
|
|
|
920
|
-
async approvePending({ projectId, path, sessionId, limit = 120 }) {
|
|
1406
|
+
async approvePending({ projectId, projectName, projectIndex, path, sessionId, limit = 120 }) {
|
|
921
1407
|
const state = await this.getWebSessionState({
|
|
922
1408
|
projectId,
|
|
1409
|
+
projectName,
|
|
1410
|
+
projectIndex,
|
|
923
1411
|
path,
|
|
924
1412
|
sessionId,
|
|
925
1413
|
limit,
|
|
@@ -938,9 +1426,11 @@ export class CodeKanbanClient {
|
|
|
938
1426
|
};
|
|
939
1427
|
}
|
|
940
1428
|
|
|
941
|
-
async rejectPending({ projectId, path, sessionId, limit = 120 }) {
|
|
1429
|
+
async rejectPending({ projectId, projectName, projectIndex, path, sessionId, limit = 120 }) {
|
|
942
1430
|
const state = await this.getWebSessionState({
|
|
943
1431
|
projectId,
|
|
1432
|
+
projectName,
|
|
1433
|
+
projectIndex,
|
|
944
1434
|
path,
|
|
945
1435
|
sessionId,
|
|
946
1436
|
limit,
|
|
@@ -961,6 +1451,8 @@ export class CodeKanbanClient {
|
|
|
961
1451
|
|
|
962
1452
|
async executeLatestPlan({
|
|
963
1453
|
projectId,
|
|
1454
|
+
projectName,
|
|
1455
|
+
projectIndex,
|
|
964
1456
|
path,
|
|
965
1457
|
sessionId,
|
|
966
1458
|
prompt = "Implement the plan.",
|
|
@@ -968,6 +1460,8 @@ export class CodeKanbanClient {
|
|
|
968
1460
|
}) {
|
|
969
1461
|
const state = await this.getWebSessionState({
|
|
970
1462
|
projectId,
|
|
1463
|
+
projectName,
|
|
1464
|
+
projectIndex,
|
|
971
1465
|
path,
|
|
972
1466
|
sessionId,
|
|
973
1467
|
limit,
|
|
@@ -1030,64 +1524,322 @@ export class CodeKanbanClient {
|
|
|
1030
1524
|
|
|
1031
1525
|
async waitForWebSessionState({
|
|
1032
1526
|
projectId,
|
|
1527
|
+
projectName,
|
|
1528
|
+
projectIndex,
|
|
1033
1529
|
path,
|
|
1034
1530
|
sessionId,
|
|
1035
1531
|
until,
|
|
1036
1532
|
intervalMs = 5000,
|
|
1037
1533
|
timeoutMs = 60000,
|
|
1038
1534
|
limit = 120,
|
|
1535
|
+
settleMs = 0,
|
|
1039
1536
|
}) {
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
typeof until === "function"
|
|
1046
|
-
? until
|
|
1047
|
-
: Array.isArray(until)
|
|
1048
|
-
? (state) => until.includes(state.phase)
|
|
1049
|
-
: (state) => state.phase === until;
|
|
1537
|
+
const matches = normalizeWebSessionStateMatcher(until);
|
|
1538
|
+
const normalizedIntervalMs = Math.max(1, Math.trunc(intervalMs));
|
|
1539
|
+
const normalizedSettleMs = Number.isFinite(settleMs)
|
|
1540
|
+
? Math.max(0, Math.trunc(settleMs))
|
|
1541
|
+
: 0;
|
|
1050
1542
|
|
|
1051
1543
|
const startedAt = Date.now();
|
|
1052
1544
|
let lastRetryableError = null;
|
|
1545
|
+
let matchedAt = null;
|
|
1053
1546
|
while (Date.now() - startedAt <= timeoutMs) {
|
|
1054
1547
|
try {
|
|
1055
1548
|
const state = await this.getWebSessionState({
|
|
1056
1549
|
projectId,
|
|
1550
|
+
projectName,
|
|
1551
|
+
projectIndex,
|
|
1057
1552
|
path,
|
|
1058
1553
|
sessionId,
|
|
1059
1554
|
limit,
|
|
1060
1555
|
});
|
|
1061
1556
|
lastRetryableError = null;
|
|
1062
1557
|
if (matches(state)) {
|
|
1063
|
-
|
|
1558
|
+
if (normalizedSettleMs <= 0) {
|
|
1559
|
+
return state;
|
|
1560
|
+
}
|
|
1561
|
+
if (matchedAt == null) {
|
|
1562
|
+
matchedAt = Date.now();
|
|
1563
|
+
}
|
|
1564
|
+
if (Date.now() - matchedAt >= normalizedSettleMs) {
|
|
1565
|
+
return state;
|
|
1566
|
+
}
|
|
1567
|
+
} else {
|
|
1568
|
+
matchedAt = null;
|
|
1064
1569
|
}
|
|
1065
1570
|
} catch (error) {
|
|
1066
1571
|
if (!isRetryableRequestError(error)) {
|
|
1067
1572
|
throw error;
|
|
1068
1573
|
}
|
|
1069
1574
|
lastRetryableError = error;
|
|
1575
|
+
matchedAt = null;
|
|
1070
1576
|
}
|
|
1071
1577
|
|
|
1072
|
-
|
|
1578
|
+
const remainingSettleMs =
|
|
1579
|
+
matchedAt == null || normalizedSettleMs <= 0
|
|
1580
|
+
? normalizedIntervalMs
|
|
1581
|
+
: Math.max(1, normalizedSettleMs - (Date.now() - matchedAt));
|
|
1582
|
+
await sleep(Math.min(normalizedIntervalMs, remainingSettleMs));
|
|
1073
1583
|
}
|
|
1074
1584
|
|
|
1075
1585
|
if (lastRetryableError) {
|
|
1076
1586
|
throw new CodeKanbanValidationError(
|
|
1077
1587
|
`web session ${sessionId} did not reach the requested state within ${timeoutMs}ms (last transient error: ${lastRetryableError.message})`,
|
|
1078
|
-
{ cause: lastRetryableError },
|
|
1588
|
+
{ cause: lastRetryableError, reason: "timeout" },
|
|
1079
1589
|
);
|
|
1080
1590
|
}
|
|
1081
1591
|
|
|
1082
1592
|
throw new CodeKanbanValidationError(
|
|
1083
1593
|
`web session ${sessionId} did not reach the requested state within ${timeoutMs}ms`,
|
|
1594
|
+
{ reason: "timeout" },
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
async waitForWebSessionPause({
|
|
1599
|
+
projectId,
|
|
1600
|
+
projectName,
|
|
1601
|
+
projectIndex,
|
|
1602
|
+
path,
|
|
1603
|
+
sessionId,
|
|
1604
|
+
until,
|
|
1605
|
+
intervalMs = 5000,
|
|
1606
|
+
timeoutMs = 60000,
|
|
1607
|
+
limit = 120,
|
|
1608
|
+
settleMs = 0,
|
|
1609
|
+
}) {
|
|
1610
|
+
const untilMatcher = until ? normalizeWebSessionStateMatcher(until) : null;
|
|
1611
|
+
const normalizedIntervalMs = Math.max(1, Math.trunc(intervalMs));
|
|
1612
|
+
const normalizedSettleMs = Number.isFinite(settleMs)
|
|
1613
|
+
? Math.max(0, Math.trunc(settleMs))
|
|
1614
|
+
: 0;
|
|
1615
|
+
|
|
1616
|
+
const startedAt = Date.now();
|
|
1617
|
+
let lastRetryableError = null;
|
|
1618
|
+
let settledReason = null;
|
|
1619
|
+
let settledAt = null;
|
|
1620
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
1621
|
+
try {
|
|
1622
|
+
const state = await this.getWebSessionState({
|
|
1623
|
+
projectId,
|
|
1624
|
+
projectName,
|
|
1625
|
+
projectIndex,
|
|
1626
|
+
path,
|
|
1627
|
+
sessionId,
|
|
1628
|
+
limit,
|
|
1629
|
+
});
|
|
1630
|
+
lastRetryableError = null;
|
|
1631
|
+
const reason = getWebSessionPauseReason(state, untilMatcher);
|
|
1632
|
+
if (reason) {
|
|
1633
|
+
if (
|
|
1634
|
+
normalizedSettleMs > 0 &&
|
|
1635
|
+
isDebouncedPauseReason(reason)
|
|
1636
|
+
) {
|
|
1637
|
+
if (settledReason !== reason) {
|
|
1638
|
+
settledReason = reason;
|
|
1639
|
+
settledAt = Date.now();
|
|
1640
|
+
}
|
|
1641
|
+
if (Date.now() - settledAt >= normalizedSettleMs) {
|
|
1642
|
+
return { reason, state };
|
|
1643
|
+
}
|
|
1644
|
+
} else {
|
|
1645
|
+
return { reason, state };
|
|
1646
|
+
}
|
|
1647
|
+
} else {
|
|
1648
|
+
settledReason = null;
|
|
1649
|
+
settledAt = null;
|
|
1650
|
+
}
|
|
1651
|
+
} catch (error) {
|
|
1652
|
+
if (!isRetryableRequestError(error)) {
|
|
1653
|
+
throw error;
|
|
1654
|
+
}
|
|
1655
|
+
lastRetryableError = error;
|
|
1656
|
+
settledReason = null;
|
|
1657
|
+
settledAt = null;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
const remainingSettleMs =
|
|
1661
|
+
settledAt == null || normalizedSettleMs <= 0
|
|
1662
|
+
? normalizedIntervalMs
|
|
1663
|
+
: Math.max(1, normalizedSettleMs - (Date.now() - settledAt));
|
|
1664
|
+
await sleep(Math.min(normalizedIntervalMs, remainingSettleMs));
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
if (lastRetryableError) {
|
|
1668
|
+
throw new CodeKanbanValidationError(
|
|
1669
|
+
`web session ${sessionId} did not reach a pause state within ${timeoutMs}ms (last transient error: ${lastRetryableError.message})`,
|
|
1670
|
+
{ cause: lastRetryableError, reason: "timeout" },
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
throw new CodeKanbanValidationError(
|
|
1675
|
+
`web session ${sessionId} did not reach a pause state within ${timeoutMs}ms`,
|
|
1676
|
+
{ reason: "timeout" },
|
|
1084
1677
|
);
|
|
1085
1678
|
}
|
|
1086
1679
|
|
|
1680
|
+
async runWebSessionUntilDone({
|
|
1681
|
+
projectId,
|
|
1682
|
+
projectName,
|
|
1683
|
+
projectIndex,
|
|
1684
|
+
path,
|
|
1685
|
+
sessionId,
|
|
1686
|
+
until,
|
|
1687
|
+
intervalMs = 2000,
|
|
1688
|
+
timeoutMs = 120000,
|
|
1689
|
+
limit = 120,
|
|
1690
|
+
settleMs = 2000,
|
|
1691
|
+
terminalDebounceMs,
|
|
1692
|
+
answerStrategy = "prefer-second-or-text",
|
|
1693
|
+
autoExecutePlan = true,
|
|
1694
|
+
executePlanPrompt = "Implement the plan.",
|
|
1695
|
+
}) {
|
|
1696
|
+
const normalizedTerminalDebounceMs = Number.isFinite(terminalDebounceMs)
|
|
1697
|
+
? Math.max(0, Math.trunc(terminalDebounceMs))
|
|
1698
|
+
: Number.isFinite(settleMs)
|
|
1699
|
+
? Math.max(0, Math.trunc(settleMs))
|
|
1700
|
+
: 2000;
|
|
1701
|
+
const actions = [];
|
|
1702
|
+
const answeredItemIds = new Set();
|
|
1703
|
+
const executedPlanIds = new Set();
|
|
1704
|
+
let lastExecuteMode = null;
|
|
1705
|
+
let lastState = null;
|
|
1706
|
+
const startedAt = Date.now();
|
|
1707
|
+
|
|
1708
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
1709
|
+
const remainingTimeoutMs = Math.max(1, timeoutMs - (Date.now() - startedAt));
|
|
1710
|
+
let pause = null;
|
|
1711
|
+
try {
|
|
1712
|
+
pause = await this.waitForWebSessionPause({
|
|
1713
|
+
projectId,
|
|
1714
|
+
projectName,
|
|
1715
|
+
projectIndex,
|
|
1716
|
+
path,
|
|
1717
|
+
sessionId,
|
|
1718
|
+
until,
|
|
1719
|
+
intervalMs,
|
|
1720
|
+
timeoutMs: remainingTimeoutMs,
|
|
1721
|
+
limit,
|
|
1722
|
+
settleMs: normalizedTerminalDebounceMs,
|
|
1723
|
+
});
|
|
1724
|
+
} catch (error) {
|
|
1725
|
+
if (error?.reason !== "timeout") {
|
|
1726
|
+
throw error;
|
|
1727
|
+
}
|
|
1728
|
+
try {
|
|
1729
|
+
lastState = await this.getWebSessionState({
|
|
1730
|
+
projectId,
|
|
1731
|
+
projectName,
|
|
1732
|
+
projectIndex,
|
|
1733
|
+
path,
|
|
1734
|
+
sessionId,
|
|
1735
|
+
limit,
|
|
1736
|
+
});
|
|
1737
|
+
} catch {
|
|
1738
|
+
// Ignore a best-effort state refresh on timeout.
|
|
1739
|
+
}
|
|
1740
|
+
return {
|
|
1741
|
+
stopReason: "timeout",
|
|
1742
|
+
finalState: lastState,
|
|
1743
|
+
actions,
|
|
1744
|
+
lastExecuteMode,
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
lastState = pause.state;
|
|
1749
|
+
if (pause.reason === "done" || pause.reason === "error" || pause.reason === "until") {
|
|
1750
|
+
return {
|
|
1751
|
+
stopReason: pause.reason,
|
|
1752
|
+
finalState: pause.state,
|
|
1753
|
+
actions,
|
|
1754
|
+
lastExecuteMode,
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
if (pause.reason === "approval") {
|
|
1758
|
+
return {
|
|
1759
|
+
stopReason: "needs_approval",
|
|
1760
|
+
finalState: pause.state,
|
|
1761
|
+
actions,
|
|
1762
|
+
lastExecuteMode,
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
if (pause.reason === "user_input") {
|
|
1766
|
+
const itemId = pause.state.pendingUserInput?.itemId || null;
|
|
1767
|
+
if (!itemId || answeredItemIds.has(itemId) || !answerStrategy) {
|
|
1768
|
+
return {
|
|
1769
|
+
stopReason: "needs_user_input",
|
|
1770
|
+
finalState: pause.state,
|
|
1771
|
+
actions,
|
|
1772
|
+
lastExecuteMode,
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
const answers = buildAutoWebSessionAnswers(
|
|
1776
|
+
pause.state.pendingUserInput?.questions,
|
|
1777
|
+
answerStrategy,
|
|
1778
|
+
);
|
|
1779
|
+
await this.answerPendingUserInput({
|
|
1780
|
+
projectId,
|
|
1781
|
+
projectName,
|
|
1782
|
+
projectIndex,
|
|
1783
|
+
path,
|
|
1784
|
+
sessionId,
|
|
1785
|
+
answers,
|
|
1786
|
+
limit,
|
|
1787
|
+
});
|
|
1788
|
+
answeredItemIds.add(itemId);
|
|
1789
|
+
actions.push({
|
|
1790
|
+
type: "answer_user_input",
|
|
1791
|
+
at: new Date().toISOString(),
|
|
1792
|
+
itemId,
|
|
1793
|
+
answers,
|
|
1794
|
+
});
|
|
1795
|
+
continue;
|
|
1796
|
+
}
|
|
1797
|
+
if (pause.reason === "execute_plan") {
|
|
1798
|
+
const planId = pause.state.latestPlan?.itemId || pause.state.latestPlan?.id || null;
|
|
1799
|
+
if (!autoExecutePlan || (planId && executedPlanIds.has(planId))) {
|
|
1800
|
+
return {
|
|
1801
|
+
stopReason: "needs_execute_plan",
|
|
1802
|
+
finalState: pause.state,
|
|
1803
|
+
actions,
|
|
1804
|
+
lastExecuteMode,
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
const result = await this.executeLatestPlan({
|
|
1808
|
+
projectId,
|
|
1809
|
+
projectName,
|
|
1810
|
+
projectIndex,
|
|
1811
|
+
path,
|
|
1812
|
+
sessionId,
|
|
1813
|
+
prompt: executePlanPrompt,
|
|
1814
|
+
limit,
|
|
1815
|
+
});
|
|
1816
|
+
if (planId) {
|
|
1817
|
+
executedPlanIds.add(planId);
|
|
1818
|
+
}
|
|
1819
|
+
lastExecuteMode = result.mode;
|
|
1820
|
+
actions.push({
|
|
1821
|
+
type: "execute_plan",
|
|
1822
|
+
at: new Date().toISOString(),
|
|
1823
|
+
itemId: planId,
|
|
1824
|
+
mode: result.mode,
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
return {
|
|
1830
|
+
stopReason: "timeout",
|
|
1831
|
+
finalState: lastState,
|
|
1832
|
+
actions,
|
|
1833
|
+
lastExecuteMode,
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1087
1837
|
async startWorkflow(input = {}) {
|
|
1088
1838
|
const launch = buildAgentLaunchSpec(input);
|
|
1089
1839
|
const { project, matchedBy } = await this.resolveProject({
|
|
1090
1840
|
projectId: input.projectId,
|
|
1841
|
+
projectName: input.projectName,
|
|
1842
|
+
projectIndex: input.projectIndex,
|
|
1091
1843
|
path: input.path,
|
|
1092
1844
|
ensureProject: true,
|
|
1093
1845
|
});
|
|
@@ -1135,14 +1887,16 @@ export class CodeKanbanClient {
|
|
|
1135
1887
|
};
|
|
1136
1888
|
}
|
|
1137
1889
|
|
|
1138
|
-
async continueTerminalSession({ projectId, path, sessionId, prompt }) {
|
|
1890
|
+
async continueTerminalSession({ projectId, projectName, projectIndex, path, sessionId, prompt }) {
|
|
1139
1891
|
const resolvedSessionId = ensureString(sessionId, "sessionId");
|
|
1140
1892
|
const resolvedPrompt = ensureString(prompt, "prompt");
|
|
1141
1893
|
|
|
1142
1894
|
let project;
|
|
1143
|
-
if (projectId || path) {
|
|
1895
|
+
if (projectId || projectName || path) {
|
|
1144
1896
|
({ project } = await this.resolveProject({
|
|
1145
1897
|
projectId,
|
|
1898
|
+
projectName,
|
|
1899
|
+
projectIndex,
|
|
1146
1900
|
path,
|
|
1147
1901
|
ensureProject: true,
|
|
1148
1902
|
}));
|