@tstax/coding-tab 0.1.2 → 0.2.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 +60 -7
- package/dist/browser.js +81 -39
- package/dist/browser.js.map +1 -1
- package/dist/server.cjs +642 -125
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +150 -22
- package/dist/server.d.ts +150 -22
- package/dist/server.js +641 -123
- package/dist/server.js.map +1 -1
- package/dist/style.css +310 -17
- package/package.json +2 -2
package/dist/server.cjs
CHANGED
|
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/server/index.ts
|
|
31
31
|
var server_exports = {};
|
|
32
32
|
__export(server_exports, {
|
|
33
|
+
FileChatStorage: () => FileChatStorage,
|
|
34
|
+
createDefaultStorage: () => createDefaultStorage,
|
|
33
35
|
mountCodingTab: () => mountCodingTab
|
|
34
36
|
});
|
|
35
37
|
module.exports = __toCommonJS(server_exports);
|
|
@@ -39,7 +41,7 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
|
|
|
39
41
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
40
42
|
|
|
41
43
|
// src/server/index.ts
|
|
42
|
-
var
|
|
44
|
+
var import_express6 = __toESM(require("express"), 1);
|
|
43
45
|
|
|
44
46
|
// src/server/authRoutes.ts
|
|
45
47
|
var import_express = require("express");
|
|
@@ -203,6 +205,7 @@ function makeAuthRouter(opts) {
|
|
|
203
205
|
// src/server/agentRoutes.ts
|
|
204
206
|
var import_express2 = require("express");
|
|
205
207
|
var import_sdk2 = require("@cursor/sdk");
|
|
208
|
+
var import_node_crypto3 = require("crypto");
|
|
206
209
|
|
|
207
210
|
// src/server/models.ts
|
|
208
211
|
var import_sdk = require("@cursor/sdk");
|
|
@@ -243,7 +246,6 @@ async function resolveModel(apiKey, choice) {
|
|
|
243
246
|
}
|
|
244
247
|
|
|
245
248
|
// src/server/sessions.ts
|
|
246
|
-
var import_node_crypto2 = require("crypto");
|
|
247
249
|
var SESSION_IDLE_TTL_MS = 30 * 60 * 1e3;
|
|
248
250
|
var sessions = /* @__PURE__ */ new Map();
|
|
249
251
|
var sweeperStarted = false;
|
|
@@ -254,35 +256,41 @@ function startSweeper() {
|
|
|
254
256
|
const now = Date.now();
|
|
255
257
|
for (const [id, s] of sessions) {
|
|
256
258
|
if (now - s.lastUsedAt > SESSION_IDLE_TTL_MS) {
|
|
257
|
-
|
|
259
|
+
disposeSessionsForChat(id).catch(
|
|
260
|
+
(e) => console.error("[coding-tab] session sweep dispose failed", e)
|
|
261
|
+
);
|
|
258
262
|
}
|
|
259
263
|
}
|
|
260
264
|
}, 5 * 60 * 1e3).unref?.();
|
|
261
265
|
}
|
|
262
|
-
function
|
|
266
|
+
function registerLiveSession(opts) {
|
|
263
267
|
startSweeper();
|
|
264
|
-
const
|
|
268
|
+
const prev = sessions.get(opts.chatId);
|
|
269
|
+
if (prev) {
|
|
270
|
+
sessions.delete(opts.chatId);
|
|
271
|
+
prev.agent[Symbol.asyncDispose]().catch(() => {
|
|
272
|
+
});
|
|
273
|
+
}
|
|
265
274
|
const session = {
|
|
266
|
-
|
|
275
|
+
chatId: opts.chatId,
|
|
267
276
|
githubLogin: opts.githubLogin,
|
|
268
277
|
agent: opts.agent,
|
|
269
|
-
|
|
270
|
-
startingRef: opts.startingRef,
|
|
278
|
+
agentId: opts.agentId,
|
|
271
279
|
createdAt: Date.now(),
|
|
272
280
|
lastUsedAt: Date.now()
|
|
273
281
|
};
|
|
274
|
-
sessions.set(
|
|
282
|
+
sessions.set(opts.chatId, session);
|
|
275
283
|
return session;
|
|
276
284
|
}
|
|
277
|
-
function
|
|
278
|
-
const s = sessions.get(
|
|
285
|
+
function getLiveSession(chatId) {
|
|
286
|
+
const s = sessions.get(chatId);
|
|
279
287
|
if (s) s.lastUsedAt = Date.now();
|
|
280
288
|
return s;
|
|
281
289
|
}
|
|
282
|
-
async function
|
|
283
|
-
const s = sessions.get(
|
|
290
|
+
async function disposeSessionsForChat(chatId) {
|
|
291
|
+
const s = sessions.get(chatId);
|
|
284
292
|
if (!s) return;
|
|
285
|
-
sessions.delete(
|
|
293
|
+
sessions.delete(chatId);
|
|
286
294
|
try {
|
|
287
295
|
await s.agent[Symbol.asyncDispose]();
|
|
288
296
|
} catch (err) {
|
|
@@ -290,6 +298,235 @@ async function disposeSession(id) {
|
|
|
290
298
|
}
|
|
291
299
|
}
|
|
292
300
|
|
|
301
|
+
// src/server/storage.ts
|
|
302
|
+
var import_promises = require("fs/promises");
|
|
303
|
+
var import_node_path = require("path");
|
|
304
|
+
var import_node_crypto2 = require("crypto");
|
|
305
|
+
function safeId(s) {
|
|
306
|
+
return s.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
307
|
+
}
|
|
308
|
+
async function ensureDir(p) {
|
|
309
|
+
await (0, import_promises.mkdir)(p, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
async function readJson(path) {
|
|
312
|
+
try {
|
|
313
|
+
const raw = await (0, import_promises.readFile)(path, "utf8");
|
|
314
|
+
return JSON.parse(raw);
|
|
315
|
+
} catch (err) {
|
|
316
|
+
if (err.code === "ENOENT") return null;
|
|
317
|
+
throw err;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function writeJsonAtomic(path, data) {
|
|
321
|
+
await ensureDir((0, import_node_path.dirname)(path));
|
|
322
|
+
const tmp = `${path}.${(0, import_node_crypto2.randomUUID)()}.tmp`;
|
|
323
|
+
await (0, import_promises.writeFile)(tmp, JSON.stringify(data, null, 2), "utf8");
|
|
324
|
+
await (0, import_promises.rename)(tmp, path);
|
|
325
|
+
}
|
|
326
|
+
var MAX_BLOB_BYTES = 32 * 1024;
|
|
327
|
+
function truncateBlob(value) {
|
|
328
|
+
if (value === void 0 || value === null) return value;
|
|
329
|
+
try {
|
|
330
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
331
|
+
if (str.length <= MAX_BLOB_BYTES) return value;
|
|
332
|
+
const head = str.slice(0, MAX_BLOB_BYTES);
|
|
333
|
+
return `${head}
|
|
334
|
+
|
|
335
|
+
\u2026[truncated ${str.length - MAX_BLOB_BYTES} chars]`;
|
|
336
|
+
} catch {
|
|
337
|
+
return "[unserializable]";
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function sanitizeEvent(evt) {
|
|
341
|
+
if (evt.kind === "tool") {
|
|
342
|
+
return {
|
|
343
|
+
...evt,
|
|
344
|
+
args: truncateBlob(evt.args),
|
|
345
|
+
result: truncateBlob(evt.result)
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return evt;
|
|
349
|
+
}
|
|
350
|
+
var FileChatStorage = class {
|
|
351
|
+
dataDir;
|
|
352
|
+
/** Per-chat write mutex chain to keep concurrent appends consistent. */
|
|
353
|
+
chains = /* @__PURE__ */ new Map();
|
|
354
|
+
constructor(opts) {
|
|
355
|
+
this.dataDir = (0, import_node_path.resolve)(opts.dataDir);
|
|
356
|
+
}
|
|
357
|
+
chatPath(id) {
|
|
358
|
+
return (0, import_node_path.join)(this.dataDir, "chats", `${safeId(id)}.json`);
|
|
359
|
+
}
|
|
360
|
+
indexPath(login) {
|
|
361
|
+
return (0, import_node_path.join)(this.dataDir, "index", `${safeId(login.toLowerCase())}.json`);
|
|
362
|
+
}
|
|
363
|
+
/** Serialize all reads/writes for a single chat through a chained promise. */
|
|
364
|
+
withChat(id, fn) {
|
|
365
|
+
const prev = this.chains.get(id) ?? Promise.resolve();
|
|
366
|
+
const next = prev.then(fn, fn);
|
|
367
|
+
this.chains.set(
|
|
368
|
+
id,
|
|
369
|
+
next.finally(() => {
|
|
370
|
+
if (this.chains.get(id) === next) this.chains.delete(id);
|
|
371
|
+
})
|
|
372
|
+
);
|
|
373
|
+
return next;
|
|
374
|
+
}
|
|
375
|
+
async readChat(id) {
|
|
376
|
+
return readJson(this.chatPath(id));
|
|
377
|
+
}
|
|
378
|
+
async writeChat(file) {
|
|
379
|
+
await writeJsonAtomic(this.chatPath(file.chat.id), file);
|
|
380
|
+
}
|
|
381
|
+
async readIndex(login) {
|
|
382
|
+
const list = await readJson(this.indexPath(login));
|
|
383
|
+
return list ?? [];
|
|
384
|
+
}
|
|
385
|
+
async writeIndex(login, items) {
|
|
386
|
+
items.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
387
|
+
await writeJsonAtomic(this.indexPath(login), items);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Rebuild the index for a user by scanning every chat file. Used as a
|
|
391
|
+
* self-heal path when the index gets out of sync (e.g. crash mid-write).
|
|
392
|
+
*/
|
|
393
|
+
async rebuildIndex(login) {
|
|
394
|
+
const dir = (0, import_node_path.join)(this.dataDir, "chats");
|
|
395
|
+
let entries = [];
|
|
396
|
+
try {
|
|
397
|
+
entries = await (0, import_promises.readdir)(dir);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
if (err.code !== "ENOENT") throw err;
|
|
400
|
+
}
|
|
401
|
+
const items = [];
|
|
402
|
+
const target = login.toLowerCase();
|
|
403
|
+
for (const name of entries) {
|
|
404
|
+
if (!name.endsWith(".json")) continue;
|
|
405
|
+
const file = await readJson((0, import_node_path.join)(dir, name));
|
|
406
|
+
if (!file?.chat) continue;
|
|
407
|
+
if (file.chat.githubLogin.toLowerCase() !== target) continue;
|
|
408
|
+
items.push(this.toListItem(file.chat));
|
|
409
|
+
}
|
|
410
|
+
await this.writeIndex(login, items);
|
|
411
|
+
return items;
|
|
412
|
+
}
|
|
413
|
+
toListItem(c) {
|
|
414
|
+
return {
|
|
415
|
+
id: c.id,
|
|
416
|
+
title: c.title,
|
|
417
|
+
mode: c.mode,
|
|
418
|
+
model: c.model,
|
|
419
|
+
createdAt: c.createdAt,
|
|
420
|
+
updatedAt: c.updatedAt
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
async upsertIndex(chat) {
|
|
424
|
+
const items = await this.readIndex(chat.githubLogin);
|
|
425
|
+
const idx = items.findIndex((c) => c.id === chat.id);
|
|
426
|
+
const item = this.toListItem(chat);
|
|
427
|
+
if (idx >= 0) items[idx] = item;
|
|
428
|
+
else items.push(item);
|
|
429
|
+
await this.writeIndex(chat.githubLogin, items);
|
|
430
|
+
}
|
|
431
|
+
async removeFromIndex(login, chatId) {
|
|
432
|
+
const items = await this.readIndex(login);
|
|
433
|
+
const filtered = items.filter((c) => c.id !== chatId);
|
|
434
|
+
if (filtered.length !== items.length) {
|
|
435
|
+
await this.writeIndex(login, filtered);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
async listChats(login) {
|
|
439
|
+
const items = await this.readIndex(login);
|
|
440
|
+
if (items.length > 0) return items;
|
|
441
|
+
return this.rebuildIndex(login);
|
|
442
|
+
}
|
|
443
|
+
async loadChat(id, login) {
|
|
444
|
+
return this.withChat(id, async () => {
|
|
445
|
+
const file = await this.readChat(id);
|
|
446
|
+
if (!file) return null;
|
|
447
|
+
if (file.chat.githubLogin.toLowerCase() !== login.toLowerCase()) {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
const turns = [...file.turns].sort((a, b) => a.seq - b.seq);
|
|
451
|
+
return { ...file.chat, turns };
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
async createChat(chat) {
|
|
455
|
+
await this.withChat(chat.id, async () => {
|
|
456
|
+
await this.writeChat({ chat, turns: [] });
|
|
457
|
+
});
|
|
458
|
+
await this.upsertIndex(chat);
|
|
459
|
+
}
|
|
460
|
+
async patchChat(id, login, patch) {
|
|
461
|
+
const updated = await this.withChat(id, async () => {
|
|
462
|
+
const file = await this.readChat(id);
|
|
463
|
+
if (!file) return null;
|
|
464
|
+
if (file.chat.githubLogin.toLowerCase() !== login.toLowerCase()) {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
const next = {
|
|
468
|
+
...file.chat,
|
|
469
|
+
...patch,
|
|
470
|
+
updatedAt: Date.now()
|
|
471
|
+
};
|
|
472
|
+
await this.writeChat({ chat: next, turns: file.turns });
|
|
473
|
+
return next;
|
|
474
|
+
});
|
|
475
|
+
if (updated) await this.upsertIndex(updated);
|
|
476
|
+
return updated;
|
|
477
|
+
}
|
|
478
|
+
async appendTurn(turn) {
|
|
479
|
+
const sanitized = {
|
|
480
|
+
...turn,
|
|
481
|
+
events: turn.events.map(sanitizeEvent)
|
|
482
|
+
};
|
|
483
|
+
let chat = null;
|
|
484
|
+
await this.withChat(turn.chatId, async () => {
|
|
485
|
+
const file = await this.readChat(turn.chatId);
|
|
486
|
+
if (!file) throw new Error(`chat_not_found:${turn.chatId}`);
|
|
487
|
+
file.turns = file.turns.filter((t) => t.id !== sanitized.id);
|
|
488
|
+
file.turns.push(sanitized);
|
|
489
|
+
file.chat = { ...file.chat, updatedAt: Date.now() };
|
|
490
|
+
chat = file.chat;
|
|
491
|
+
await this.writeChat(file);
|
|
492
|
+
});
|
|
493
|
+
if (chat) await this.upsertIndex(chat);
|
|
494
|
+
}
|
|
495
|
+
async patchTurn(chatId, turnId, patch) {
|
|
496
|
+
let chat = null;
|
|
497
|
+
await this.withChat(chatId, async () => {
|
|
498
|
+
const file = await this.readChat(chatId);
|
|
499
|
+
if (!file) return;
|
|
500
|
+
const idx = file.turns.findIndex((t) => t.id === turnId);
|
|
501
|
+
if (idx < 0) return;
|
|
502
|
+
const current = file.turns[idx];
|
|
503
|
+
const events = patch.events ? patch.events.map(sanitizeEvent) : current.events;
|
|
504
|
+
file.turns[idx] = { ...current, ...patch, events };
|
|
505
|
+
file.chat = { ...file.chat, updatedAt: Date.now() };
|
|
506
|
+
chat = file.chat;
|
|
507
|
+
await this.writeChat(file);
|
|
508
|
+
});
|
|
509
|
+
if (chat) await this.upsertIndex(chat);
|
|
510
|
+
}
|
|
511
|
+
async deleteChat(id, login) {
|
|
512
|
+
const removed = await this.withChat(id, async () => {
|
|
513
|
+
const file = await this.readChat(id);
|
|
514
|
+
if (!file) return false;
|
|
515
|
+
if (file.chat.githubLogin.toLowerCase() !== login.toLowerCase()) {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
await (0, import_promises.rm)(this.chatPath(id), { force: true });
|
|
519
|
+
return true;
|
|
520
|
+
});
|
|
521
|
+
if (removed) await this.removeFromIndex(login, id);
|
|
522
|
+
return removed;
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
function createDefaultStorage(dataDir) {
|
|
526
|
+
const dir = dataDir ?? (0, import_node_path.join)(process.cwd(), ".coding-tab-data");
|
|
527
|
+
return new FileChatStorage({ dataDir: dir });
|
|
528
|
+
}
|
|
529
|
+
|
|
293
530
|
// src/server/agentRoutes.ts
|
|
294
531
|
var PLAN_INSTRUCTION = `You are operating in PLAN MODE.
|
|
295
532
|
|
|
@@ -331,39 +568,221 @@ function setupSseHeaders(res) {
|
|
|
331
568
|
});
|
|
332
569
|
res.flushHeaders?.();
|
|
333
570
|
}
|
|
334
|
-
|
|
571
|
+
function deriveTitle(prompt) {
|
|
572
|
+
const trimmed = prompt.trim().replace(/\s+/g, " ");
|
|
573
|
+
if (trimmed.length <= 60) return trimmed || "New chat";
|
|
574
|
+
return `${trimmed.slice(0, 57)}\u2026`;
|
|
575
|
+
}
|
|
576
|
+
var TurnBuffer = class {
|
|
577
|
+
events = [];
|
|
578
|
+
lastWasText = false;
|
|
579
|
+
pushText(text) {
|
|
580
|
+
if (this.lastWasText) {
|
|
581
|
+
const last = this.events[this.events.length - 1];
|
|
582
|
+
if (last.kind === "text") {
|
|
583
|
+
last.text += text;
|
|
584
|
+
return last;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const evt = { kind: "text", id: (0, import_node_crypto3.randomUUID)(), text };
|
|
588
|
+
this.events.push(evt);
|
|
589
|
+
this.lastWasText = true;
|
|
590
|
+
return evt;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Mark the next text event as starting a new block (called when the SDK
|
|
594
|
+
* emits a non-streaming-delta boundary, e.g. a tool call or a new assistant
|
|
595
|
+
* message). The next `pushText` will start a fresh paragraph.
|
|
596
|
+
*/
|
|
597
|
+
endTextBlock() {
|
|
598
|
+
this.lastWasText = false;
|
|
599
|
+
}
|
|
600
|
+
upsertTool(args) {
|
|
601
|
+
this.endTextBlock();
|
|
602
|
+
const existing = this.events.find(
|
|
603
|
+
(e) => e.kind === "tool" && e.callId === args.callId
|
|
604
|
+
);
|
|
605
|
+
if (existing) {
|
|
606
|
+
existing.status = args.status;
|
|
607
|
+
if (args.args !== void 0) existing.args = args.args;
|
|
608
|
+
if (args.result !== void 0) existing.result = args.result;
|
|
609
|
+
return existing;
|
|
610
|
+
}
|
|
611
|
+
const evt = {
|
|
612
|
+
kind: "tool",
|
|
613
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
614
|
+
callId: args.callId,
|
|
615
|
+
name: args.name,
|
|
616
|
+
status: args.status,
|
|
617
|
+
args: args.args,
|
|
618
|
+
result: args.result
|
|
619
|
+
};
|
|
620
|
+
this.events.push(evt);
|
|
621
|
+
return evt;
|
|
622
|
+
}
|
|
623
|
+
snapshot() {
|
|
624
|
+
return this.events.map(sanitizeEvent);
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
async function streamRun(res, run, ctx) {
|
|
628
|
+
const flushDebounceMs = 750;
|
|
629
|
+
let flushPending = false;
|
|
630
|
+
let lastFlush = 0;
|
|
631
|
+
const persist = async (status, pr) => {
|
|
632
|
+
try {
|
|
633
|
+
await ctx.storage.patchTurn(ctx.chat.id, ctx.turn.id, {
|
|
634
|
+
events: ctx.buffer.snapshot(),
|
|
635
|
+
...status ? { status } : {},
|
|
636
|
+
...pr ? { pr } : {}
|
|
637
|
+
});
|
|
638
|
+
lastFlush = Date.now();
|
|
639
|
+
} catch (err) {
|
|
640
|
+
console.error("[coding-tab] patchTurn failed", err);
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
const scheduleFlush = () => {
|
|
644
|
+
if (flushPending) return;
|
|
645
|
+
flushPending = true;
|
|
646
|
+
setTimeout(async () => {
|
|
647
|
+
flushPending = false;
|
|
648
|
+
const elapsed = Date.now() - lastFlush;
|
|
649
|
+
if (elapsed < flushDebounceMs) return;
|
|
650
|
+
await persist();
|
|
651
|
+
}, flushDebounceMs).unref?.();
|
|
652
|
+
};
|
|
653
|
+
let finalStatus = "finished";
|
|
654
|
+
let finalPr;
|
|
335
655
|
try {
|
|
336
656
|
for await (const message of run.stream()) {
|
|
337
657
|
if (res.writableEnded) break;
|
|
338
658
|
if (message.type === "assistant") {
|
|
659
|
+
ctx.buffer.endTextBlock();
|
|
339
660
|
for (const block of message.message.content) {
|
|
340
|
-
if (block.type === "text" && block.text)
|
|
341
|
-
|
|
661
|
+
if (block.type === "text" && block.text) {
|
|
662
|
+
ctx.buffer.pushText(block.text);
|
|
663
|
+
ctx.buffer.endTextBlock();
|
|
664
|
+
sse(res, { kind: "text", text: block.text });
|
|
665
|
+
} else if (block.type === "tool_use") {
|
|
666
|
+
ctx.buffer.upsertTool({
|
|
667
|
+
callId: block.id,
|
|
668
|
+
name: block.name,
|
|
669
|
+
status: "running",
|
|
670
|
+
args: block.input
|
|
671
|
+
});
|
|
672
|
+
sse(res, {
|
|
673
|
+
kind: "tool",
|
|
674
|
+
name: block.name,
|
|
675
|
+
status: "running",
|
|
676
|
+
callId: block.id,
|
|
677
|
+
args: block.input
|
|
678
|
+
});
|
|
679
|
+
}
|
|
342
680
|
}
|
|
343
681
|
} else if (message.type === "thinking") {
|
|
344
682
|
sse(res, { kind: "thinking", text: message.text });
|
|
345
683
|
} else if (message.type === "tool_call") {
|
|
346
|
-
|
|
684
|
+
ctx.buffer.upsertTool({
|
|
685
|
+
callId: message.call_id,
|
|
686
|
+
name: message.name,
|
|
687
|
+
status: message.status,
|
|
688
|
+
args: message.args,
|
|
689
|
+
result: message.result
|
|
690
|
+
});
|
|
691
|
+
sse(res, {
|
|
692
|
+
kind: "tool",
|
|
693
|
+
name: message.name,
|
|
694
|
+
status: message.status,
|
|
695
|
+
callId: message.call_id,
|
|
696
|
+
args: message.args,
|
|
697
|
+
result: message.result
|
|
698
|
+
});
|
|
347
699
|
} else if (message.type === "status") {
|
|
348
700
|
sse(res, { kind: "status", status: message.status, message: message.message });
|
|
349
701
|
}
|
|
702
|
+
scheduleFlush();
|
|
350
703
|
}
|
|
351
704
|
const result = await run.wait();
|
|
352
705
|
const prUrl = result.git?.branches?.find((b) => b.prUrl)?.prUrl;
|
|
706
|
+
finalPr = parsePrUrl(prUrl);
|
|
707
|
+
finalStatus = result.status ?? "finished";
|
|
353
708
|
sse(res, {
|
|
354
709
|
kind: "result",
|
|
355
710
|
status: result.status,
|
|
356
|
-
pr:
|
|
711
|
+
pr: finalPr,
|
|
357
712
|
durationMs: result.durationMs
|
|
358
713
|
});
|
|
359
714
|
} catch (err) {
|
|
715
|
+
finalStatus = "error";
|
|
360
716
|
const message = err instanceof Error ? err.message : String(err);
|
|
361
717
|
const retryable = err instanceof import_sdk2.CursorAgentError ? Boolean(err.isRetryable) : false;
|
|
362
718
|
sse(res, { kind: "error", message, retryable });
|
|
363
719
|
} finally {
|
|
720
|
+
await persist(finalStatus, finalPr);
|
|
364
721
|
if (!res.writableEnded) res.end();
|
|
365
722
|
}
|
|
366
723
|
}
|
|
724
|
+
async function ensureAgentForChat(storage, chat, user, opts, modelChoice) {
|
|
725
|
+
const existing = getLiveSession(chat.id);
|
|
726
|
+
if (existing && existing.githubLogin.toLowerCase() === user.githubLogin.toLowerCase()) {
|
|
727
|
+
return { agent: existing.agent, agentId: existing.agentId, resumed: true };
|
|
728
|
+
}
|
|
729
|
+
const resolved = await resolveModel(opts.cursorApiKey, modelChoice);
|
|
730
|
+
const cloud = {
|
|
731
|
+
repos: [
|
|
732
|
+
{
|
|
733
|
+
url: chat.repoUrl,
|
|
734
|
+
startingRef: chat.startingRef
|
|
735
|
+
}
|
|
736
|
+
],
|
|
737
|
+
autoCreatePR: chat.mode === "agent",
|
|
738
|
+
skipReviewerRequest: opts.skipReviewerRequest ?? true,
|
|
739
|
+
envVars: { GITHUB_TOKEN: user.accessToken },
|
|
740
|
+
...opts.envName ? { env: { type: "cloud", name: opts.envName } } : {}
|
|
741
|
+
};
|
|
742
|
+
if (chat.agentId) {
|
|
743
|
+
try {
|
|
744
|
+
const resumed = await import_sdk2.Agent.resume(chat.agentId, {
|
|
745
|
+
apiKey: opts.cursorApiKey,
|
|
746
|
+
model: { id: resolved.cursorModelId },
|
|
747
|
+
cloud
|
|
748
|
+
});
|
|
749
|
+
registerLiveSession({
|
|
750
|
+
chatId: chat.id,
|
|
751
|
+
githubLogin: user.githubLogin,
|
|
752
|
+
agent: resumed,
|
|
753
|
+
agentId: resumed.agentId
|
|
754
|
+
});
|
|
755
|
+
if (resumed.agentId !== chat.agentId) {
|
|
756
|
+
await storage.patchChat(chat.id, user.githubLogin, { agentId: resumed.agentId });
|
|
757
|
+
}
|
|
758
|
+
return { agent: resumed, agentId: resumed.agentId, resumed: true };
|
|
759
|
+
} catch (err) {
|
|
760
|
+
console.warn(
|
|
761
|
+
`[coding-tab] resume failed for chat=${chat.id} agentId=${chat.agentId}; creating fresh agent`,
|
|
762
|
+
err instanceof Error ? err.message : err
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
const fresh = await import_sdk2.Agent.create({
|
|
767
|
+
apiKey: opts.cursorApiKey,
|
|
768
|
+
model: { id: resolved.cursorModelId },
|
|
769
|
+
cloud
|
|
770
|
+
});
|
|
771
|
+
registerLiveSession({
|
|
772
|
+
chatId: chat.id,
|
|
773
|
+
githubLogin: user.githubLogin,
|
|
774
|
+
agent: fresh,
|
|
775
|
+
agentId: fresh.agentId
|
|
776
|
+
});
|
|
777
|
+
await storage.patchChat(chat.id, user.githubLogin, { agentId: fresh.agentId });
|
|
778
|
+
return { agent: fresh, agentId: fresh.agentId, resumed: false };
|
|
779
|
+
}
|
|
780
|
+
async function nextSeq(storage, chatId, login) {
|
|
781
|
+
const full = await storage.loadChat(chatId, login);
|
|
782
|
+
if (!full) throw Object.assign(new Error("chat_not_found"), { status: 404 });
|
|
783
|
+
const max = full.turns.reduce((acc, t) => Math.max(acc, t.seq), -1);
|
|
784
|
+
return max + 1;
|
|
785
|
+
}
|
|
367
786
|
function makeAgentRouter(opts) {
|
|
368
787
|
const router = (0, import_express2.Router)();
|
|
369
788
|
router.get("/models", async (_req, res) => {
|
|
@@ -375,144 +794,236 @@ function makeAgentRouter(opts) {
|
|
|
375
794
|
res.status(500).json({ error: err instanceof Error ? err.message : "models_failed" });
|
|
376
795
|
}
|
|
377
796
|
});
|
|
378
|
-
|
|
797
|
+
const handleSend = async (req, res, isExecute) => {
|
|
379
798
|
const user = req.user;
|
|
380
|
-
const
|
|
381
|
-
if (!
|
|
799
|
+
const body = req.body ?? {};
|
|
800
|
+
if (!body.chatId) {
|
|
801
|
+
res.status(400).json({ error: "missing_chatId" });
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (!isExecute && (!body.prompt || body.mode !== "plan" && body.mode !== "agent")) {
|
|
382
805
|
res.status(400).json({ error: "invalid_request" });
|
|
383
806
|
return;
|
|
384
807
|
}
|
|
808
|
+
const chat = await opts.storage.loadChat(body.chatId, user.githubLogin);
|
|
809
|
+
if (!chat) {
|
|
810
|
+
res.status(404).json({ error: "chat_not_found" });
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
385
813
|
setupSseHeaders(res);
|
|
386
|
-
let
|
|
814
|
+
let assistantTurn = null;
|
|
387
815
|
try {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
816
|
+
if (!isExecute && body.mode && body.mode !== chat.mode) {
|
|
817
|
+
chat.mode = body.mode;
|
|
818
|
+
await opts.storage.patchChat(chat.id, user.githubLogin, { mode: body.mode });
|
|
819
|
+
}
|
|
820
|
+
let seq = await nextSeq(opts.storage, chat.id, user.githubLogin);
|
|
821
|
+
if (!isExecute) {
|
|
822
|
+
const userTurn = {
|
|
823
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
824
|
+
chatId: chat.id,
|
|
825
|
+
seq: seq++,
|
|
826
|
+
role: "user",
|
|
827
|
+
isPlan: chat.mode === "plan",
|
|
828
|
+
status: "finished",
|
|
829
|
+
events: [{ kind: "text", id: (0, import_node_crypto3.randomUUID)(), text: body.prompt }],
|
|
830
|
+
prompt: body.prompt,
|
|
831
|
+
createdAt: Date.now()
|
|
832
|
+
};
|
|
833
|
+
await opts.storage.appendTurn(userTurn);
|
|
834
|
+
if (chat.title === "New chat") {
|
|
835
|
+
const title = deriveTitle(body.prompt);
|
|
836
|
+
chat.title = title;
|
|
837
|
+
await opts.storage.patchChat(chat.id, user.githubLogin, { title });
|
|
403
838
|
}
|
|
839
|
+
}
|
|
840
|
+
assistantTurn = {
|
|
841
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
842
|
+
chatId: chat.id,
|
|
843
|
+
seq,
|
|
844
|
+
role: "assistant",
|
|
845
|
+
isPlan: !isExecute && chat.mode === "plan",
|
|
846
|
+
status: "running",
|
|
847
|
+
events: [],
|
|
848
|
+
createdAt: Date.now()
|
|
849
|
+
};
|
|
850
|
+
await opts.storage.appendTurn(assistantTurn);
|
|
851
|
+
const { agent } = await ensureAgentForChat(opts.storage, chat, user, opts, chat.model);
|
|
852
|
+
const sendText = isExecute ? EXECUTE_INSTRUCTION : modeInstructionPrefix(body.mode, body.prompt);
|
|
853
|
+
const run = await agent.send(sendText);
|
|
854
|
+
sse(res, {
|
|
855
|
+
kind: "ready",
|
|
856
|
+
chatId: chat.id,
|
|
857
|
+
turnId: assistantTurn.id,
|
|
858
|
+
agentId: agent.agentId,
|
|
859
|
+
runId: run.id
|
|
404
860
|
});
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
861
|
+
console.log(
|
|
862
|
+
`[coding-tab] ${isExecute ? "execute" : "send"} agent=${agent.agentId} run=${run.id} chat=${chat.id} login=${user.githubLogin}`
|
|
863
|
+
);
|
|
864
|
+
const buffer = new TurnBuffer();
|
|
865
|
+
await streamRun(res, run, {
|
|
866
|
+
storage: opts.storage,
|
|
867
|
+
chat,
|
|
868
|
+
turn: assistantTurn,
|
|
869
|
+
buffer
|
|
410
870
|
});
|
|
411
|
-
const run = await agent.send(modeInstructionPrefix(mode, prompt));
|
|
412
|
-
sse(res, { kind: "ready", sessionId: session.id, agentId: agent.agentId, runId: run.id });
|
|
413
|
-
console.log(`[coding-tab] start agent=${agent.agentId} run=${run.id} session=${session.id} login=${user.githubLogin}`);
|
|
414
|
-
await streamRun(res, run);
|
|
415
871
|
} catch (err) {
|
|
416
872
|
const message = err instanceof Error ? err.message : String(err);
|
|
417
873
|
const retryable = err instanceof import_sdk2.CursorAgentError ? Boolean(err.isRetryable) : false;
|
|
418
|
-
console.error(
|
|
874
|
+
console.error(`[coding-tab] /agent/${isExecute ? "execute" : "send"} failed`, err);
|
|
419
875
|
sse(res, { kind: "error", message, retryable });
|
|
876
|
+
if (assistantTurn) {
|
|
877
|
+
await opts.storage.patchTurn(assistantTurn.chatId, assistantTurn.id, {
|
|
878
|
+
status: "error",
|
|
879
|
+
events: [
|
|
880
|
+
...assistantTurn.events,
|
|
881
|
+
{ kind: "text", id: (0, import_node_crypto3.randomUUID)(), text: `[error] ${message}` }
|
|
882
|
+
]
|
|
883
|
+
}).catch(() => {
|
|
884
|
+
});
|
|
885
|
+
}
|
|
420
886
|
if (!res.writableEnded) res.end();
|
|
421
|
-
if (agent) await agent[Symbol.asyncDispose]().catch(() => {
|
|
422
|
-
});
|
|
423
887
|
}
|
|
424
888
|
req.on("close", () => {
|
|
425
889
|
if (!res.writableEnded) res.end();
|
|
426
890
|
});
|
|
427
|
-
}
|
|
428
|
-
router.post("/agent/
|
|
429
|
-
|
|
430
|
-
|
|
891
|
+
};
|
|
892
|
+
router.post("/agent/start", (req, res) => handleSend(req, res, false));
|
|
893
|
+
router.post("/agent/send", (req, res) => handleSend(req, res, false));
|
|
894
|
+
router.post("/agent/execute", (req, res) => handleSend(req, res, true));
|
|
895
|
+
router.post("/agent/cancel", async (req, res) => {
|
|
896
|
+
const { chatId, runId } = req.body ?? {};
|
|
897
|
+
if (!chatId || !runId) {
|
|
431
898
|
res.status(400).json({ error: "invalid_request" });
|
|
432
899
|
return;
|
|
433
900
|
}
|
|
434
|
-
const
|
|
435
|
-
if (!
|
|
901
|
+
const live = getLiveSession(chatId);
|
|
902
|
+
if (!live) {
|
|
436
903
|
res.status(404).json({ error: "session_not_found" });
|
|
437
904
|
return;
|
|
438
905
|
}
|
|
439
|
-
setupSseHeaders(res);
|
|
440
906
|
try {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
907
|
+
await import_sdk2.Agent.cancelRun(runId, {
|
|
908
|
+
runtime: "cloud",
|
|
909
|
+
agentId: live.agentId,
|
|
910
|
+
apiKey: opts.cursorApiKey
|
|
911
|
+
});
|
|
912
|
+
res.json({ ok: true });
|
|
445
913
|
} catch (err) {
|
|
446
914
|
const message = err instanceof Error ? err.message : String(err);
|
|
447
|
-
console.error("[coding-tab] /agent/
|
|
448
|
-
|
|
449
|
-
if (!res.writableEnded) res.end();
|
|
915
|
+
console.error("[coding-tab] /agent/cancel failed", err);
|
|
916
|
+
res.status(500).json({ error: message });
|
|
450
917
|
}
|
|
451
|
-
req.on("close", () => {
|
|
452
|
-
if (!res.writableEnded) res.end();
|
|
453
|
-
});
|
|
454
918
|
});
|
|
455
|
-
router.post("/agent/
|
|
456
|
-
const {
|
|
457
|
-
if (!
|
|
919
|
+
router.post("/agent/dispose", async (req, res) => {
|
|
920
|
+
const { chatId } = req.body ?? {};
|
|
921
|
+
if (!chatId) {
|
|
458
922
|
res.status(400).json({ error: "invalid_request" });
|
|
459
923
|
return;
|
|
460
924
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
925
|
+
await disposeSessionsForChat(chatId);
|
|
926
|
+
res.json({ ok: true });
|
|
927
|
+
});
|
|
928
|
+
return router;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/server/chatRoutes.ts
|
|
932
|
+
var import_express3 = require("express");
|
|
933
|
+
var import_node_crypto4 = require("crypto");
|
|
934
|
+
var VALID_MODES = ["plan", "agent"];
|
|
935
|
+
var VALID_MODELS = ["sonnet", "opus"];
|
|
936
|
+
function makeChatRouter(opts) {
|
|
937
|
+
const router = (0, import_express3.Router)();
|
|
938
|
+
router.get("/chats", async (req, res) => {
|
|
939
|
+
const user = req.user;
|
|
467
940
|
try {
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
console.log(`[coding-tab] execute agent=${session.agent.agentId} run=${run.id} session=${session.id}`);
|
|
471
|
-
await streamRun(res, run);
|
|
941
|
+
const items = await opts.storage.listChats(user.githubLogin);
|
|
942
|
+
res.json({ chats: items });
|
|
472
943
|
} catch (err) {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
sse(res, { kind: "error", message });
|
|
476
|
-
if (!res.writableEnded) res.end();
|
|
944
|
+
console.error("[coding-tab] /chats list failed", err);
|
|
945
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "list_failed" });
|
|
477
946
|
}
|
|
478
|
-
req.on("close", () => {
|
|
479
|
-
if (!res.writableEnded) res.end();
|
|
480
|
-
});
|
|
481
947
|
});
|
|
482
|
-
router.post("/
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
948
|
+
router.post("/chats", async (req, res) => {
|
|
949
|
+
const user = req.user;
|
|
950
|
+
const body = req.body ?? {};
|
|
951
|
+
const mode = VALID_MODES.includes(body.mode) ? body.mode : "plan";
|
|
952
|
+
const model = VALID_MODELS.includes(body.model) ? body.model : "sonnet";
|
|
953
|
+
const now = Date.now();
|
|
954
|
+
const chat = {
|
|
955
|
+
id: (0, import_node_crypto4.randomUUID)(),
|
|
956
|
+
githubLogin: user.githubLogin,
|
|
957
|
+
title: (body.title ?? "").trim() || "New chat",
|
|
958
|
+
mode,
|
|
959
|
+
model,
|
|
960
|
+
repoUrl: body.repoUrl?.trim() || opts.defaultRepo.url,
|
|
961
|
+
startingRef: body.startingRef ?? opts.defaultRepo.ref,
|
|
962
|
+
createdAt: now,
|
|
963
|
+
updatedAt: now
|
|
964
|
+
};
|
|
965
|
+
try {
|
|
966
|
+
await opts.storage.createChat(chat);
|
|
967
|
+
res.status(201).json({ chat });
|
|
968
|
+
} catch (err) {
|
|
969
|
+
console.error("[coding-tab] /chats create failed", err);
|
|
970
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "create_failed" });
|
|
487
971
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
972
|
+
});
|
|
973
|
+
router.get("/chats/:id", async (req, res) => {
|
|
974
|
+
const user = req.user;
|
|
975
|
+
try {
|
|
976
|
+
const full = await opts.storage.loadChat(req.params.id, user.githubLogin);
|
|
977
|
+
if (!full) {
|
|
978
|
+
res.status(404).json({ error: "chat_not_found" });
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
res.json({ chat: full });
|
|
982
|
+
} catch (err) {
|
|
983
|
+
console.error("[coding-tab] /chats/:id load failed", err);
|
|
984
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "load_failed" });
|
|
492
985
|
}
|
|
986
|
+
});
|
|
987
|
+
router.patch("/chats/:id", async (req, res) => {
|
|
988
|
+
const user = req.user;
|
|
989
|
+
const body = req.body ?? {};
|
|
990
|
+
const patch = {};
|
|
991
|
+
if (typeof body.title === "string") patch.title = body.title.trim() || "Untitled";
|
|
992
|
+
if (body.mode && VALID_MODES.includes(body.mode)) patch.mode = body.mode;
|
|
993
|
+
if (body.model && VALID_MODELS.includes(body.model)) patch.model = body.model;
|
|
493
994
|
try {
|
|
494
|
-
await
|
|
495
|
-
|
|
995
|
+
const updated = await opts.storage.patchChat(req.params.id, user.githubLogin, patch);
|
|
996
|
+
if (!updated) {
|
|
997
|
+
res.status(404).json({ error: "chat_not_found" });
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
res.json({ chat: updated });
|
|
496
1001
|
} catch (err) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
res.status(500).json({ error: message });
|
|
1002
|
+
console.error("[coding-tab] /chats/:id patch failed", err);
|
|
1003
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "patch_failed" });
|
|
500
1004
|
}
|
|
501
1005
|
});
|
|
502
|
-
router.
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
1006
|
+
router.delete("/chats/:id", async (req, res) => {
|
|
1007
|
+
const user = req.user;
|
|
1008
|
+
try {
|
|
1009
|
+
const removed = await opts.storage.deleteChat(req.params.id, user.githubLogin);
|
|
1010
|
+
if (!removed) {
|
|
1011
|
+
res.status(404).json({ error: "chat_not_found" });
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
await disposeSessionsForChat(req.params.id).catch(() => {
|
|
1015
|
+
});
|
|
1016
|
+
res.json({ ok: true });
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
console.error("[coding-tab] /chats/:id delete failed", err);
|
|
1019
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "delete_failed" });
|
|
507
1020
|
}
|
|
508
|
-
await disposeSession(sessionId);
|
|
509
|
-
res.json({ ok: true });
|
|
510
1021
|
});
|
|
511
1022
|
return router;
|
|
512
1023
|
}
|
|
513
1024
|
|
|
514
1025
|
// src/server/githubRoutes.ts
|
|
515
|
-
var
|
|
1026
|
+
var import_express4 = require("express");
|
|
516
1027
|
var import_rest = require("@octokit/rest");
|
|
517
1028
|
function parsePrUrl2(prUrl) {
|
|
518
1029
|
const match = prUrl.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
@@ -520,7 +1031,7 @@ function parsePrUrl2(prUrl) {
|
|
|
520
1031
|
return { owner: match[1], repo: match[2], number: Number(match[3]) };
|
|
521
1032
|
}
|
|
522
1033
|
function makeGitHubRouter() {
|
|
523
|
-
const router = (0,
|
|
1034
|
+
const router = (0, import_express4.Router)();
|
|
524
1035
|
router.get("/pr/status", async (req, res) => {
|
|
525
1036
|
const prUrl = typeof req.query.prUrl === "string" ? req.query.prUrl : null;
|
|
526
1037
|
if (!prUrl) {
|
|
@@ -599,26 +1110,26 @@ function makeGitHubRouter() {
|
|
|
599
1110
|
}
|
|
600
1111
|
|
|
601
1112
|
// src/server/staticAssets.ts
|
|
602
|
-
var
|
|
603
|
-
var
|
|
604
|
-
var
|
|
1113
|
+
var import_express5 = require("express");
|
|
1114
|
+
var import_promises2 = require("fs/promises");
|
|
1115
|
+
var import_node_path2 = require("path");
|
|
605
1116
|
var import_node_url = require("url");
|
|
606
|
-
var here = (0,
|
|
1117
|
+
var here = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
607
1118
|
var ASSET_CANDIDATES = [
|
|
608
|
-
(0,
|
|
609
|
-
(0,
|
|
610
|
-
(0,
|
|
1119
|
+
(0, import_node_path2.resolve)(here, "browser.js"),
|
|
1120
|
+
(0, import_node_path2.resolve)(here, "..", "dist", "browser.js"),
|
|
1121
|
+
(0, import_node_path2.resolve)(here, "..", "browser.js")
|
|
611
1122
|
];
|
|
612
1123
|
var STYLE_CANDIDATES = [
|
|
613
|
-
(0,
|
|
614
|
-
(0,
|
|
615
|
-
(0,
|
|
1124
|
+
(0, import_node_path2.resolve)(here, "style.css"),
|
|
1125
|
+
(0, import_node_path2.resolve)(here, "..", "dist", "style.css"),
|
|
1126
|
+
(0, import_node_path2.resolve)(here, "..", "style.css")
|
|
616
1127
|
];
|
|
617
1128
|
async function readFirst(paths) {
|
|
618
1129
|
let lastErr;
|
|
619
1130
|
for (const p of paths) {
|
|
620
1131
|
try {
|
|
621
|
-
const data = await (0,
|
|
1132
|
+
const data = await (0, import_promises2.readFile)(p);
|
|
622
1133
|
return { path: p, data };
|
|
623
1134
|
} catch (err) {
|
|
624
1135
|
lastErr = err;
|
|
@@ -627,7 +1138,7 @@ async function readFirst(paths) {
|
|
|
627
1138
|
throw lastErr instanceof Error ? lastErr : new Error("asset not found");
|
|
628
1139
|
}
|
|
629
1140
|
function makeAssetRouter(basePath) {
|
|
630
|
-
const router = (0,
|
|
1141
|
+
const router = (0, import_express5.Router)();
|
|
631
1142
|
router.get("/browser.js", async (_req, res) => {
|
|
632
1143
|
try {
|
|
633
1144
|
const { data } = await readFirst(ASSET_CANDIDATES);
|
|
@@ -694,8 +1205,9 @@ function mountCodingTab(app, options) {
|
|
|
694
1205
|
if (!options.githubOAuth.allowedLogins || options.githubOAuth.allowedLogins.length === 0) {
|
|
695
1206
|
console.warn("[coding-tab] WARNING: allowedLogins is empty \u2014 anyone with a GitHub account can sign in.");
|
|
696
1207
|
}
|
|
697
|
-
const
|
|
698
|
-
router.
|
|
1208
|
+
const storage = options.storage ?? createDefaultStorage(options.dataDir);
|
|
1209
|
+
const router = import_express6.default.Router();
|
|
1210
|
+
router.use(import_express6.default.json({ limit: "1mb" }));
|
|
699
1211
|
const assetRouter = makeAssetRouter(basePath);
|
|
700
1212
|
router.use(assetRouter);
|
|
701
1213
|
const authRouter = makeAuthRouter({
|
|
@@ -712,11 +1224,14 @@ function mountCodingTab(app, options) {
|
|
|
712
1224
|
secure,
|
|
713
1225
|
allowedLogins: options.githubOAuth.allowedLogins
|
|
714
1226
|
});
|
|
1227
|
+
const chatRouter = makeChatRouter({ storage, defaultRepo: options.defaultRepo });
|
|
1228
|
+
router.use(requireAuth, chatRouter);
|
|
715
1229
|
const agentRouter = makeAgentRouter({
|
|
716
1230
|
cursorApiKey: options.cursorApiKey,
|
|
717
1231
|
defaultRepo: options.defaultRepo,
|
|
718
1232
|
envName: options.envName,
|
|
719
|
-
skipReviewerRequest: options.skipReviewerRequest
|
|
1233
|
+
skipReviewerRequest: options.skipReviewerRequest,
|
|
1234
|
+
storage
|
|
720
1235
|
});
|
|
721
1236
|
router.use(requireAuth, agentRouter);
|
|
722
1237
|
const githubRouter = makeGitHubRouter();
|
|
@@ -727,6 +1242,8 @@ function mountCodingTab(app, options) {
|
|
|
727
1242
|
}
|
|
728
1243
|
// Annotate the CommonJS export names for ESM import in node:
|
|
729
1244
|
0 && (module.exports = {
|
|
1245
|
+
FileChatStorage,
|
|
1246
|
+
createDefaultStorage,
|
|
730
1247
|
mountCodingTab
|
|
731
1248
|
});
|
|
732
1249
|
//# sourceMappingURL=server.cjs.map
|