@threadbase-sh/streamer 1.23.1 → 1.24.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/dist/cli.cjs +1170 -350
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +1152 -374
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -28
- package/dist/index.d.ts +89 -28
- package/dist/index.js +1022 -249
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -26621,7 +26621,7 @@ var require_transport = __commonJS({
|
|
|
26621
26621
|
"node_modules/pino/lib/transport.js"(exports2, module2) {
|
|
26622
26622
|
"use strict";
|
|
26623
26623
|
var { createRequire } = require("module");
|
|
26624
|
-
var { existsSync:
|
|
26624
|
+
var { existsSync: existsSync12 } = require("fs");
|
|
26625
26625
|
var getCallers = require_caller();
|
|
26626
26626
|
var { join: join26, isAbsolute: isAbsolute3, sep: sep2 } = require("path");
|
|
26627
26627
|
var { fileURLToPath: fileURLToPath3 } = require("url");
|
|
@@ -26695,7 +26695,7 @@ var require_transport = __commonJS({
|
|
|
26695
26695
|
return false;
|
|
26696
26696
|
}
|
|
26697
26697
|
}
|
|
26698
|
-
return isAbsolute3(path2) && !
|
|
26698
|
+
return isAbsolute3(path2) && !existsSync12(path2);
|
|
26699
26699
|
}
|
|
26700
26700
|
function stripQuotes(value) {
|
|
26701
26701
|
const first = value[0];
|
|
@@ -100885,8 +100885,8 @@ var require_pattern = __commonJS({
|
|
|
100885
100885
|
}
|
|
100886
100886
|
exports2.endsWithSlashGlobStar = endsWithSlashGlobStar;
|
|
100887
100887
|
function isAffectDepthOfReadingPattern(pattern) {
|
|
100888
|
-
const
|
|
100889
|
-
return endsWithSlashGlobStar(pattern) || isStaticPattern(
|
|
100888
|
+
const basename11 = path2.basename(pattern);
|
|
100889
|
+
return endsWithSlashGlobStar(pattern) || isStaticPattern(basename11);
|
|
100890
100890
|
}
|
|
100891
100891
|
exports2.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
|
|
100892
100892
|
function expandPatternsWithBraceExpansion(patterns2) {
|
|
@@ -124663,6 +124663,50 @@ var init_platform = __esm({
|
|
|
124663
124663
|
}
|
|
124664
124664
|
});
|
|
124665
124665
|
|
|
124666
|
+
// src/db/check-sqlite-abi.ts
|
|
124667
|
+
var check_sqlite_abi_exports = {};
|
|
124668
|
+
__export(check_sqlite_abi_exports, {
|
|
124669
|
+
SqliteAbiError: () => SqliteAbiError,
|
|
124670
|
+
checkSqliteAbi: () => checkSqliteAbi,
|
|
124671
|
+
isAbiMismatch: () => isAbiMismatch
|
|
124672
|
+
});
|
|
124673
|
+
function isAbiMismatch(message) {
|
|
124674
|
+
return ABI_MARKERS.some((m2) => message.includes(m2));
|
|
124675
|
+
}
|
|
124676
|
+
function checkSqliteAbi() {
|
|
124677
|
+
try {
|
|
124678
|
+
const Database3 = require("better-sqlite3");
|
|
124679
|
+
const db = new Database3(":memory:");
|
|
124680
|
+
db.close();
|
|
124681
|
+
} catch (err) {
|
|
124682
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
124683
|
+
if (isAbiMismatch(message)) {
|
|
124684
|
+
throw new SqliteAbiError(message);
|
|
124685
|
+
}
|
|
124686
|
+
throw err;
|
|
124687
|
+
}
|
|
124688
|
+
}
|
|
124689
|
+
var REBUILD_HINT, SqliteAbiError, ABI_MARKERS;
|
|
124690
|
+
var init_check_sqlite_abi = __esm({
|
|
124691
|
+
"src/db/check-sqlite-abi.ts"() {
|
|
124692
|
+
"use strict";
|
|
124693
|
+
REBUILD_HINT = "npm rebuild better-sqlite3";
|
|
124694
|
+
SqliteAbiError = class extends Error {
|
|
124695
|
+
constructor(cause) {
|
|
124696
|
+
super(
|
|
124697
|
+
`better-sqlite3 native module failed to load \u2014 likely a Node ABI mismatch (node_modules was built against a different Node version).
|
|
124698
|
+
Running Node: ${process.version} (NODE_MODULE_VERSION ${process.versions.modules})
|
|
124699
|
+
Fix: ${REBUILD_HINT}
|
|
124700
|
+
(or 'npm ci' after any dependency-affecting pull)
|
|
124701
|
+
Underlying error: ${cause}`
|
|
124702
|
+
);
|
|
124703
|
+
this.name = "SqliteAbiError";
|
|
124704
|
+
}
|
|
124705
|
+
};
|
|
124706
|
+
ABI_MARKERS = ["NODE_MODULE_VERSION", "was compiled against a different Node.js version"];
|
|
124707
|
+
}
|
|
124708
|
+
});
|
|
124709
|
+
|
|
124666
124710
|
// src/lifecycle/prefs.ts
|
|
124667
124711
|
function readPrefs() {
|
|
124668
124712
|
const path2 = prefsPath();
|
|
@@ -129594,10 +129638,10 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
129594
129638
|
}
|
|
129595
129639
|
async _formatEntry(dirent, path2) {
|
|
129596
129640
|
let entry;
|
|
129597
|
-
const
|
|
129641
|
+
const basename11 = this._isDirent ? dirent.name : dirent;
|
|
129598
129642
|
try {
|
|
129599
|
-
const fullPath = (0, import_node_path5.resolve)((0, import_node_path5.join)(path2,
|
|
129600
|
-
entry = { path: (0, import_node_path5.relative)(this._root, fullPath), fullPath, basename:
|
|
129643
|
+
const fullPath = (0, import_node_path5.resolve)((0, import_node_path5.join)(path2, basename11));
|
|
129644
|
+
entry = { path: (0, import_node_path5.relative)(this._root, fullPath), fullPath, basename: basename11 };
|
|
129601
129645
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
129602
129646
|
} catch (err) {
|
|
129603
129647
|
this._onError(err);
|
|
@@ -130136,9 +130180,9 @@ var NodeFsHandler = class {
|
|
|
130136
130180
|
_watchWithNodeFs(path2, listener) {
|
|
130137
130181
|
const opts = this.fsw.options;
|
|
130138
130182
|
const directory = sysPath.dirname(path2);
|
|
130139
|
-
const
|
|
130183
|
+
const basename11 = sysPath.basename(path2);
|
|
130140
130184
|
const parent = this.fsw._getWatchedDir(directory);
|
|
130141
|
-
parent.add(
|
|
130185
|
+
parent.add(basename11);
|
|
130142
130186
|
const absolutePath = sysPath.resolve(path2);
|
|
130143
130187
|
const options = {
|
|
130144
130188
|
persistent: opts.persistent
|
|
@@ -130148,7 +130192,7 @@ var NodeFsHandler = class {
|
|
|
130148
130192
|
let closer;
|
|
130149
130193
|
if (opts.usePolling) {
|
|
130150
130194
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
130151
|
-
options.interval = enableBin && isBinaryPath(
|
|
130195
|
+
options.interval = enableBin && isBinaryPath(basename11) ? opts.binaryInterval : opts.interval;
|
|
130152
130196
|
closer = setFsWatchFileListener(path2, absolutePath, options, {
|
|
130153
130197
|
listener,
|
|
130154
130198
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -130171,10 +130215,10 @@ var NodeFsHandler = class {
|
|
|
130171
130215
|
return;
|
|
130172
130216
|
}
|
|
130173
130217
|
const dirname17 = sysPath.dirname(file2);
|
|
130174
|
-
const
|
|
130218
|
+
const basename11 = sysPath.basename(file2);
|
|
130175
130219
|
const parent = this.fsw._getWatchedDir(dirname17);
|
|
130176
130220
|
let prevStats = stats;
|
|
130177
|
-
if (parent.has(
|
|
130221
|
+
if (parent.has(basename11))
|
|
130178
130222
|
return;
|
|
130179
130223
|
const listener = async (path2, newStats) => {
|
|
130180
130224
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file2, 5))
|
|
@@ -130199,9 +130243,9 @@ var NodeFsHandler = class {
|
|
|
130199
130243
|
prevStats = newStats2;
|
|
130200
130244
|
}
|
|
130201
130245
|
} catch (error51) {
|
|
130202
|
-
this.fsw._remove(dirname17,
|
|
130246
|
+
this.fsw._remove(dirname17, basename11);
|
|
130203
130247
|
}
|
|
130204
|
-
} else if (parent.has(
|
|
130248
|
+
} else if (parent.has(basename11)) {
|
|
130205
130249
|
const at2 = newStats.atimeMs;
|
|
130206
130250
|
const mt2 = newStats.mtimeMs;
|
|
130207
130251
|
if (!at2 || at2 <= mt2 || mt2 !== prevStats.mtimeMs) {
|
|
@@ -133973,11 +134017,11 @@ async function search(query, options, scanner) {
|
|
|
133973
134017
|
|
|
133974
134018
|
// src/server.ts
|
|
133975
134019
|
var import_events3 = require("events");
|
|
133976
|
-
var
|
|
134020
|
+
var import_fs24 = require("fs");
|
|
133977
134021
|
var import_promises14 = require("fs/promises");
|
|
133978
134022
|
var import_http = require("http");
|
|
133979
134023
|
var import_os10 = require("os");
|
|
133980
|
-
var
|
|
134024
|
+
var import_path21 = require("path");
|
|
133981
134025
|
var import_readline4 = require("readline");
|
|
133982
134026
|
|
|
133983
134027
|
// src/agent/agent-client.ts
|
|
@@ -137104,8 +137148,10 @@ function runSqliteMigrations(db, migrationsDir) {
|
|
|
137104
137148
|
// src/providers.ts
|
|
137105
137149
|
var CLAUDE_CODE_PROVIDER2 = "claude-code";
|
|
137106
137150
|
var CODEX_CLI_PROVIDER2 = "codex-cli";
|
|
137107
|
-
function
|
|
137108
|
-
|
|
137151
|
+
function isProviderName(value) {
|
|
137152
|
+
return value === CLAUDE_CODE_PROVIDER2 || value === CODEX_CLI_PROVIDER2;
|
|
137153
|
+
}
|
|
137154
|
+
function isProviderResumable(_provider, availabilityResumable) {
|
|
137109
137155
|
return availabilityResumable;
|
|
137110
137156
|
}
|
|
137111
137157
|
|
|
@@ -138182,67 +138228,15 @@ function handleListProjects(url2, res) {
|
|
|
138182
138228
|
res.end(JSON.stringify({ projects: page, total }));
|
|
138183
138229
|
}
|
|
138184
138230
|
|
|
138185
|
-
// src/
|
|
138186
|
-
|
|
138231
|
+
// src/live-session-manager.ts
|
|
138232
|
+
var import_path18 = require("path");
|
|
138187
138233
|
|
|
138188
|
-
// src/
|
|
138234
|
+
// src/codex-pty-runner.ts
|
|
138235
|
+
var import_headless = __toESM(require_xterm_headless(), 1);
|
|
138189
138236
|
var import_crypto5 = require("crypto");
|
|
138190
|
-
var
|
|
138191
|
-
var SWEEP_INTERVAL_MS = 6e4;
|
|
138192
|
-
var PairTokenStore = class {
|
|
138193
|
-
current = null;
|
|
138194
|
-
ttlMs;
|
|
138195
|
-
sweepTimer = null;
|
|
138196
|
-
constructor(opts = {}) {
|
|
138197
|
-
this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
|
|
138198
|
-
if (opts.autoSweep !== false) {
|
|
138199
|
-
this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
|
|
138200
|
-
this.sweepTimer.unref?.();
|
|
138201
|
-
}
|
|
138202
|
-
}
|
|
138203
|
-
mint() {
|
|
138204
|
-
const token = `pt_${(0, import_crypto5.randomBytes)(16).toString("hex")}`;
|
|
138205
|
-
const expiresAt = Date.now() + this.ttlMs;
|
|
138206
|
-
this.current = { token, expiresAt, used: false };
|
|
138207
|
-
return {
|
|
138208
|
-
token,
|
|
138209
|
-
expiresAt,
|
|
138210
|
-
expiresInSeconds: Math.floor(this.ttlMs / 1e3)
|
|
138211
|
-
};
|
|
138212
|
-
}
|
|
138213
|
-
consume(token) {
|
|
138214
|
-
const record2 = this.current;
|
|
138215
|
-
if (!record2 || record2.token !== token) return { ok: false, reason: "unknown" };
|
|
138216
|
-
if (Date.now() > record2.expiresAt) {
|
|
138217
|
-
this.current = null;
|
|
138218
|
-
return { ok: false, reason: "expired" };
|
|
138219
|
-
}
|
|
138220
|
-
if (record2.used) return { ok: false, reason: "used" };
|
|
138221
|
-
record2.used = true;
|
|
138222
|
-
return { ok: true };
|
|
138223
|
-
}
|
|
138224
|
-
peek() {
|
|
138225
|
-
return this.current;
|
|
138226
|
-
}
|
|
138227
|
-
clear() {
|
|
138228
|
-
this.current = null;
|
|
138229
|
-
}
|
|
138230
|
-
sweep() {
|
|
138231
|
-
if (this.current && Date.now() > this.current.expiresAt) {
|
|
138232
|
-
this.current = null;
|
|
138233
|
-
}
|
|
138234
|
-
}
|
|
138235
|
-
dispose() {
|
|
138236
|
-
if (this.sweepTimer) clearInterval(this.sweepTimer);
|
|
138237
|
-
this.sweepTimer = null;
|
|
138238
|
-
this.current = null;
|
|
138239
|
-
}
|
|
138240
|
-
};
|
|
138241
|
-
|
|
138242
|
-
// src/process-discovery.ts
|
|
138243
|
-
var import_child_process2 = require("child_process");
|
|
138244
|
-
var import_os9 = require("os");
|
|
138237
|
+
var import_fs20 = require("fs");
|
|
138245
138238
|
var import_path16 = require("path");
|
|
138239
|
+
init_logger();
|
|
138246
138240
|
|
|
138247
138241
|
// src/platform.ts
|
|
138248
138242
|
var import_child_process = require("child_process");
|
|
@@ -138308,151 +138302,523 @@ function resolveClaudeExe() {
|
|
|
138308
138302
|
_claudeExe = "claude";
|
|
138309
138303
|
return _claudeExe;
|
|
138310
138304
|
}
|
|
138311
|
-
|
|
138312
|
-
|
|
138313
|
-
|
|
138314
|
-
if (
|
|
138315
|
-
|
|
138316
|
-
|
|
138317
|
-
|
|
138318
|
-
|
|
138319
|
-
|
|
138320
|
-
|
|
138321
|
-
|
|
138322
|
-
|
|
138323
|
-
|
|
138324
|
-
getProcessArgsUnix(pid),
|
|
138325
|
-
getProcessStartTimeUnix(pid)
|
|
138326
|
-
]);
|
|
138327
|
-
const conversationId = extractResumeId(args);
|
|
138328
|
-
return {
|
|
138329
|
-
pid,
|
|
138330
|
-
projectPath: cwd,
|
|
138331
|
-
projectName: (0, import_path16.basename)(cwd),
|
|
138332
|
-
branch: await readGitBranch2(cwd),
|
|
138333
|
-
conversationId,
|
|
138334
|
-
startedAt
|
|
138335
|
-
};
|
|
138336
|
-
} catch {
|
|
138337
|
-
return null;
|
|
138305
|
+
var _codexExe;
|
|
138306
|
+
function resolveCodexExe() {
|
|
138307
|
+
if (_codexExe !== void 0) return _codexExe;
|
|
138308
|
+
if (isWindows2) {
|
|
138309
|
+
try {
|
|
138310
|
+
const found = (0, import_child_process.execFileSync)("where.exe", ["codex"], {
|
|
138311
|
+
encoding: "utf-8",
|
|
138312
|
+
windowsHide: true,
|
|
138313
|
+
timeout: 3e3
|
|
138314
|
+
}).trim().split("\n")[0].trim();
|
|
138315
|
+
if (found) {
|
|
138316
|
+
_codexExe = found;
|
|
138317
|
+
return _codexExe;
|
|
138338
138318
|
}
|
|
138339
|
-
}
|
|
138340
|
-
|
|
138341
|
-
|
|
138342
|
-
|
|
138343
|
-
|
|
138344
|
-
|
|
138345
|
-
|
|
138346
|
-
|
|
138347
|
-
|
|
138348
|
-
|
|
138349
|
-
|
|
138350
|
-
|
|
138351
|
-
|
|
138352
|
-
|
|
138353
|
-
|
|
138354
|
-
branch: await readGitBranch2(info.cwd),
|
|
138355
|
-
conversationId: extractResumeId(info.args),
|
|
138356
|
-
startedAt: info.startedAt
|
|
138357
|
-
};
|
|
138358
|
-
} catch {
|
|
138359
|
-
return null;
|
|
138319
|
+
} catch {
|
|
138320
|
+
}
|
|
138321
|
+
const candidates = [
|
|
138322
|
+
(0, import_path15.join)((0, import_os8.homedir)(), ".local", "bin", "codex.exe"),
|
|
138323
|
+
(0, import_path15.join)(
|
|
138324
|
+
process.env.LOCALAPPDATA ?? (0, import_path15.join)((0, import_os8.homedir)(), "AppData", "Local"),
|
|
138325
|
+
"Microsoft",
|
|
138326
|
+
"WindowsApps",
|
|
138327
|
+
"codex.exe"
|
|
138328
|
+
)
|
|
138329
|
+
];
|
|
138330
|
+
for (const p2 of candidates) {
|
|
138331
|
+
if ((0, import_fs19.existsSync)(p2)) {
|
|
138332
|
+
_codexExe = p2;
|
|
138333
|
+
return _codexExe;
|
|
138360
138334
|
}
|
|
138361
|
-
}
|
|
138362
|
-
|
|
138363
|
-
|
|
138364
|
-
|
|
138365
|
-
|
|
138366
|
-
|
|
138367
|
-
|
|
138368
|
-
|
|
138369
|
-
|
|
138370
|
-
|
|
138371
|
-
(err, stdout2) => {
|
|
138372
|
-
if (err) reject(err);
|
|
138373
|
-
else resolve6(stdout2);
|
|
138335
|
+
}
|
|
138336
|
+
} else {
|
|
138337
|
+
try {
|
|
138338
|
+
const found = (0, import_child_process.execFileSync)("/usr/bin/which", ["codex"], {
|
|
138339
|
+
encoding: "utf-8",
|
|
138340
|
+
timeout: 3e3
|
|
138341
|
+
}).trim().split("\n")[0].trim();
|
|
138342
|
+
if (found && (0, import_fs19.existsSync)(found)) {
|
|
138343
|
+
_codexExe = found;
|
|
138344
|
+
return _codexExe;
|
|
138374
138345
|
}
|
|
138375
|
-
|
|
138376
|
-
|
|
138377
|
-
|
|
138378
|
-
|
|
138379
|
-
|
|
138380
|
-
|
|
138381
|
-
|
|
138382
|
-
|
|
138383
|
-
|
|
138346
|
+
} catch {
|
|
138347
|
+
}
|
|
138348
|
+
const candidates = [
|
|
138349
|
+
"/opt/homebrew/bin/codex",
|
|
138350
|
+
"/usr/local/bin/codex",
|
|
138351
|
+
(0, import_path15.join)((0, import_os8.homedir)(), ".local", "bin", "codex")
|
|
138352
|
+
];
|
|
138353
|
+
for (const p2 of candidates) {
|
|
138354
|
+
if ((0, import_fs19.existsSync)(p2)) {
|
|
138355
|
+
_codexExe = p2;
|
|
138356
|
+
return _codexExe;
|
|
138357
|
+
}
|
|
138358
|
+
}
|
|
138384
138359
|
}
|
|
138360
|
+
_codexExe = "codex";
|
|
138361
|
+
return _codexExe;
|
|
138385
138362
|
}
|
|
138386
|
-
|
|
138387
|
-
|
|
138388
|
-
|
|
138389
|
-
|
|
138390
|
-
|
|
138391
|
-
|
|
138392
|
-
|
|
138393
|
-
|
|
138394
|
-
|
|
138395
|
-
|
|
138396
|
-
|
|
138397
|
-
|
|
138398
|
-
|
|
138399
|
-
|
|
138400
|
-
try {
|
|
138401
|
-
const output = await run("tasklist", ["/FI", "IMAGENAME eq claude.exe", "/FO", "CSV", "/NH"]);
|
|
138402
|
-
return output.trim().split("\n").filter(Boolean).map((line) => {
|
|
138403
|
-
const parts = line.split(",");
|
|
138404
|
-
return Number.parseInt(parts[1]?.replace(/"/g, "") ?? "0", 10);
|
|
138405
|
-
}).filter((pid) => pid > 0);
|
|
138406
|
-
} catch {
|
|
138407
|
-
return [];
|
|
138408
|
-
}
|
|
138363
|
+
|
|
138364
|
+
// src/codex-pty-runner.ts
|
|
138365
|
+
var OUTPUT_BUFFER_MAX = 65536;
|
|
138366
|
+
var PTY_COLS = 120;
|
|
138367
|
+
var PTY_ROWS = 40;
|
|
138368
|
+
var SCREEN_SCROLLBACK = 1e3;
|
|
138369
|
+
var CODEX_PROMPT_READY_TEXT = "Ready";
|
|
138370
|
+
var CODEX_TRUST_GATE_REGEX = /trust the contents/i;
|
|
138371
|
+
var SUBMIT_BYTES = "\r";
|
|
138372
|
+
var CODEX_SUBMIT_DELAY_MS = 16;
|
|
138373
|
+
function digestBytes(s3) {
|
|
138374
|
+
const escaped = s3.replace(new RegExp(String.fromCharCode(27), "g"), "\\x1b").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
|
|
138375
|
+
if (escaped.length <= 200) return escaped;
|
|
138376
|
+
return `${escaped.slice(0, 100)}\u2026[${escaped.length - 200}B omitted]\u2026${escaped.slice(-100)}`;
|
|
138409
138377
|
}
|
|
138410
|
-
|
|
138378
|
+
var pty = null;
|
|
138379
|
+
async function loadPty() {
|
|
138380
|
+
if (pty) return pty;
|
|
138411
138381
|
try {
|
|
138412
|
-
|
|
138413
|
-
|
|
138414
|
-
|
|
138415
|
-
|
|
138416
|
-
|
|
138417
|
-
|
|
138418
|
-
|
|
138419
|
-
]);
|
|
138420
|
-
const lines = output.trim().split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
138421
|
-
if (lines.length < 2) return null;
|
|
138422
|
-
const parts = lines[1].split(",");
|
|
138423
|
-
const args = parts[1] ?? "";
|
|
138424
|
-
const creationDate = parts[2] ?? "";
|
|
138425
|
-
const year = creationDate.slice(0, 4);
|
|
138426
|
-
const month = creationDate.slice(4, 6);
|
|
138427
|
-
const day = creationDate.slice(6, 8);
|
|
138428
|
-
const hour = creationDate.slice(8, 10);
|
|
138429
|
-
const min = creationDate.slice(10, 12);
|
|
138430
|
-
const sec = creationDate.slice(12, 14);
|
|
138431
|
-
const startedAt = /* @__PURE__ */ new Date(`${year}-${month}-${day}T${hour}:${min}:${sec}`);
|
|
138432
|
-
if (Number.isNaN(startedAt.getTime())) return null;
|
|
138433
|
-
const exePath = parts[3] ?? "";
|
|
138434
|
-
const cwd = exePath ? (0, import_path16.dirname)(exePath) : "";
|
|
138435
|
-
return { cwd, args, startedAt };
|
|
138436
|
-
} catch {
|
|
138437
|
-
return null;
|
|
138382
|
+
pty = await import("node-pty");
|
|
138383
|
+
return pty;
|
|
138384
|
+
} catch (err) {
|
|
138385
|
+
throw new Error(
|
|
138386
|
+
`node-pty is required for PTY management but failed to load. Ensure it is installed: npm install node-pty
|
|
138387
|
+
Original error: ${err}`
|
|
138388
|
+
);
|
|
138438
138389
|
}
|
|
138439
138390
|
}
|
|
138440
|
-
function
|
|
138441
|
-
|
|
138442
|
-
|
|
138391
|
+
function createScreen() {
|
|
138392
|
+
return new import_headless.Terminal({
|
|
138393
|
+
cols: PTY_COLS,
|
|
138394
|
+
rows: PTY_ROWS,
|
|
138395
|
+
scrollback: SCREEN_SCROLLBACK,
|
|
138396
|
+
allowProposedApi: true
|
|
138397
|
+
});
|
|
138443
138398
|
}
|
|
138444
|
-
|
|
138445
|
-
|
|
138446
|
-
|
|
138447
|
-
|
|
138448
|
-
|
|
138399
|
+
var CodexPtyRunner = class {
|
|
138400
|
+
sessions = /* @__PURE__ */ new Map();
|
|
138401
|
+
onOutput;
|
|
138402
|
+
onStatusChange;
|
|
138403
|
+
onReady;
|
|
138404
|
+
// Accepted for shape-compatibility with PTYManagerOptions; Codex has no
|
|
138405
|
+
// detected equivalent yet (Phase 0) — never invoked.
|
|
138406
|
+
onPermissionChange;
|
|
138407
|
+
onLiveQuestion;
|
|
138408
|
+
onLiveQuestionGone;
|
|
138409
|
+
log;
|
|
138410
|
+
// Tracks sessions whose PTY has spawned but Codex hasn't yet reached its
|
|
138411
|
+
// "Ready" status bar — i.e. onReady hasn't fired.
|
|
138412
|
+
pendingReady = /* @__PURE__ */ new Set();
|
|
138413
|
+
// Inputs received via sendInput() while the session was still pendingReady.
|
|
138414
|
+
// Flushed in arrival order once Codex reaches Ready.
|
|
138415
|
+
queuedInputs = /* @__PURE__ */ new Map();
|
|
138416
|
+
// Per-session debounce so the directory-trust gate's \r is only written once.
|
|
138417
|
+
trustGateAnswered = /* @__PURE__ */ new Set();
|
|
138418
|
+
// In-flight start()/startFresh() calls keyed by sessionId. A second
|
|
138419
|
+
// concurrent resume for the same session (double-tap, client retry) awaits
|
|
138420
|
+
// the first call's promise instead of spawning a duplicate PTY (CRITICAL #3).
|
|
138421
|
+
startPromises = /* @__PURE__ */ new Map();
|
|
138422
|
+
constructor(options = {}) {
|
|
138423
|
+
this.onOutput = options.onOutput;
|
|
138424
|
+
this.onStatusChange = options.onStatusChange;
|
|
138425
|
+
this.onReady = options.onReady;
|
|
138426
|
+
this.onPermissionChange = options.onPermissionChange;
|
|
138427
|
+
this.onLiveQuestion = options.onLiveQuestion;
|
|
138428
|
+
this.onLiveQuestionGone = options.onLiveQuestionGone;
|
|
138429
|
+
this.log = options.logger ?? getLogger("codex-pty");
|
|
138430
|
+
}
|
|
138431
|
+
// Resume an existing Codex session. sessionId is the Codex-persisted
|
|
138432
|
+
// session_meta.payload.id (Phase 0, Section 8) — Codex has no fresh-session
|
|
138433
|
+
// equivalent of --session-id, so start() always means "resume".
|
|
138434
|
+
async start(sessionId, options) {
|
|
138435
|
+
const existing = this.sessions.get(sessionId);
|
|
138436
|
+
if (existing) return toPublicSession(existing);
|
|
138437
|
+
const inFlight = this.startPromises.get(sessionId);
|
|
138438
|
+
if (inFlight) return inFlight;
|
|
138439
|
+
const promise2 = this.doStart(sessionId, options).finally(() => {
|
|
138440
|
+
this.startPromises.delete(sessionId);
|
|
138441
|
+
});
|
|
138442
|
+
this.startPromises.set(sessionId, promise2);
|
|
138443
|
+
return promise2;
|
|
138444
|
+
}
|
|
138445
|
+
async doStart(sessionId, options) {
|
|
138446
|
+
const nodePty = await loadPty();
|
|
138447
|
+
const projectName = options.projectName ?? (0, import_path16.basename)(options.projectPath);
|
|
138448
|
+
const proc = nodePty.spawn(
|
|
138449
|
+
resolveCodexExe(),
|
|
138450
|
+
["resume", sessionId, "--cd", options.projectPath, "--no-alt-screen"],
|
|
138451
|
+
{
|
|
138452
|
+
name: "xterm-256color",
|
|
138453
|
+
cols: PTY_COLS,
|
|
138454
|
+
rows: PTY_ROWS,
|
|
138455
|
+
cwd: options.projectPath,
|
|
138456
|
+
env: process.env
|
|
138457
|
+
}
|
|
138458
|
+
);
|
|
138459
|
+
const session = {
|
|
138460
|
+
id: sessionId,
|
|
138461
|
+
provider: CODEX_CLI_PROVIDER2,
|
|
138462
|
+
projectPath: options.projectPath,
|
|
138463
|
+
projectName,
|
|
138464
|
+
branch: options.branch ?? "",
|
|
138465
|
+
status: "running",
|
|
138466
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
138467
|
+
completedAt: null,
|
|
138468
|
+
promptCount: 0,
|
|
138469
|
+
lastOutput: "",
|
|
138470
|
+
process: proc,
|
|
138471
|
+
outputBuffer: Buffer.alloc(0),
|
|
138472
|
+
screen: createScreen()
|
|
138473
|
+
};
|
|
138474
|
+
this.sessions.set(sessionId, session);
|
|
138475
|
+
this.pendingReady.add(sessionId);
|
|
138476
|
+
proc.onData((data) => {
|
|
138477
|
+
this.handleOutput(sessionId, data);
|
|
138478
|
+
});
|
|
138479
|
+
proc.onExit(({ exitCode }) => {
|
|
138480
|
+
this.pendingReady.delete(sessionId);
|
|
138481
|
+
this.handleExit(sessionId, exitCode);
|
|
138482
|
+
});
|
|
138483
|
+
return toPublicSession(session);
|
|
138484
|
+
}
|
|
138485
|
+
// Start a brand-new Codex session. Codex has no --session-id equivalent for
|
|
138486
|
+
// a fresh launch — it assigns its own id, discovered later (Task 3's
|
|
138487
|
+
// binding logic). This runner generates a local placeholder id for the
|
|
138488
|
+
// ManagedSession handle only.
|
|
138489
|
+
async startFresh(options) {
|
|
138490
|
+
const nodePty = await loadPty();
|
|
138491
|
+
const sessionId = (0, import_crypto5.randomUUID)();
|
|
138492
|
+
const projectName = options.projectName ?? (0, import_path16.basename)(options.projectPath);
|
|
138493
|
+
const args = ["--cd", options.projectPath, "--no-alt-screen"];
|
|
138494
|
+
if (options.systemPrompt) {
|
|
138495
|
+
args.push(options.systemPrompt);
|
|
138496
|
+
}
|
|
138497
|
+
const proc = nodePty.spawn(resolveCodexExe(), args, {
|
|
138498
|
+
name: "xterm-256color",
|
|
138499
|
+
cols: PTY_COLS,
|
|
138500
|
+
rows: PTY_ROWS,
|
|
138501
|
+
cwd: options.projectPath,
|
|
138502
|
+
env: process.env
|
|
138503
|
+
});
|
|
138504
|
+
const session = {
|
|
138505
|
+
id: sessionId,
|
|
138506
|
+
provider: CODEX_CLI_PROVIDER2,
|
|
138507
|
+
projectPath: options.projectPath,
|
|
138508
|
+
projectName,
|
|
138509
|
+
branch: "",
|
|
138510
|
+
status: "running",
|
|
138511
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
138512
|
+
completedAt: null,
|
|
138513
|
+
promptCount: 0,
|
|
138514
|
+
lastOutput: "",
|
|
138515
|
+
process: proc,
|
|
138516
|
+
outputBuffer: Buffer.alloc(0),
|
|
138517
|
+
screen: createScreen()
|
|
138518
|
+
};
|
|
138519
|
+
this.sessions.set(sessionId, session);
|
|
138520
|
+
this.pendingReady.add(sessionId);
|
|
138521
|
+
proc.onData((data) => {
|
|
138522
|
+
this.handleOutput(sessionId, data);
|
|
138523
|
+
});
|
|
138524
|
+
proc.onExit(({ exitCode }) => {
|
|
138525
|
+
this.pendingReady.delete(sessionId);
|
|
138526
|
+
this.handleExit(sessionId, exitCode);
|
|
138527
|
+
});
|
|
138528
|
+
return toPublicSession(session);
|
|
138529
|
+
}
|
|
138530
|
+
// Write raw key bytes directly to the PTY, same as PTYManager.sendKeys.
|
|
138531
|
+
sendKeys(sessionId, keys) {
|
|
138532
|
+
const session = this.sessions.get(sessionId);
|
|
138533
|
+
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
138534
|
+
if (session.status === "idle") {
|
|
138535
|
+
throw new Error(`Session is idle (no active PTY): ${sessionId}`);
|
|
138536
|
+
}
|
|
138537
|
+
if (session.status === "waiting_input") {
|
|
138538
|
+
session.status = "running";
|
|
138539
|
+
this.onStatusChange?.(toPublicSession(session));
|
|
138540
|
+
}
|
|
138541
|
+
this.log.info(
|
|
138542
|
+
`[codex.keys.write] ${sessionId.slice(0, 8)} bytes=${keys.length} digest=${digestBytes(keys)}`,
|
|
138543
|
+
{ event: "codex.keys_write", sessionId, byteLen: keys.length }
|
|
138544
|
+
);
|
|
138545
|
+
session.process.write(keys);
|
|
138546
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
138547
|
+
}
|
|
138548
|
+
sendInput(sessionId, input) {
|
|
138549
|
+
const session = this.sessions.get(sessionId);
|
|
138550
|
+
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
138551
|
+
if (session.status === "idle") {
|
|
138552
|
+
throw new Error(`Session is idle (no active PTY): ${sessionId}`);
|
|
138553
|
+
}
|
|
138554
|
+
if (this.pendingReady.has(sessionId)) {
|
|
138555
|
+
const queue = this.queuedInputs.get(sessionId) ?? [];
|
|
138556
|
+
queue.push(input);
|
|
138557
|
+
this.queuedInputs.set(sessionId, queue);
|
|
138558
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
138559
|
+
session.promptCount++;
|
|
138560
|
+
this.log.warn(
|
|
138561
|
+
`[codex.input.queued] ${sessionId.slice(0, 8)} promptCount=${session.promptCount} queueLen=${queue.length}`,
|
|
138562
|
+
{
|
|
138563
|
+
event: "codex.input_queued",
|
|
138564
|
+
sessionId,
|
|
138565
|
+
promptCount: session.promptCount,
|
|
138566
|
+
queueLen: queue.length,
|
|
138567
|
+
inputLen: input.length
|
|
138568
|
+
}
|
|
138569
|
+
);
|
|
138570
|
+
return session.promptCount;
|
|
138571
|
+
}
|
|
138572
|
+
if (session.status === "waiting_input") {
|
|
138573
|
+
session.status = "running";
|
|
138574
|
+
this.onStatusChange?.(toPublicSession(session));
|
|
138575
|
+
}
|
|
138576
|
+
this.writeSubmit(sessionId, session, input, "direct", session.promptCount + 1);
|
|
138577
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
138578
|
+
session.promptCount++;
|
|
138579
|
+
return session.promptCount;
|
|
138580
|
+
}
|
|
138581
|
+
// Write the input as plain bytes (no bracketed-paste wrap — Phase 0
|
|
138582
|
+
// confirmed Codex accepts plain keystrokes), then submit \r after a short
|
|
138583
|
+
// delay so Codex's TUI gets an event-loop tick to process the input first.
|
|
138584
|
+
writeSubmit(sessionId, session, input, path2, promptCount) {
|
|
138585
|
+
this.log.info(
|
|
138586
|
+
`[codex.input.write] ${sessionId.slice(0, 8)} promptCount=${promptCount} bytes=${input.length} digest=${digestBytes(input)}`,
|
|
138587
|
+
{
|
|
138588
|
+
event: "codex.input_write",
|
|
138589
|
+
sessionId,
|
|
138590
|
+
promptCount,
|
|
138591
|
+
byteLen: input.length,
|
|
138592
|
+
digest: digestBytes(input),
|
|
138593
|
+
path: path2,
|
|
138594
|
+
phase: "input"
|
|
138595
|
+
}
|
|
138596
|
+
);
|
|
138597
|
+
session.process.write(input);
|
|
138598
|
+
setTimeout(() => {
|
|
138599
|
+
const current = this.sessions.get(sessionId);
|
|
138600
|
+
if (!current || current !== session) return;
|
|
138601
|
+
this.log.info(
|
|
138602
|
+
`[codex.input.submit] ${sessionId.slice(0, 8)} promptCount=${promptCount} digest=\\r`,
|
|
138603
|
+
{
|
|
138604
|
+
event: "codex.input_write",
|
|
138605
|
+
sessionId,
|
|
138606
|
+
promptCount,
|
|
138607
|
+
byteLen: SUBMIT_BYTES.length,
|
|
138608
|
+
digest: "\\r",
|
|
138609
|
+
path: path2,
|
|
138610
|
+
phase: "submit"
|
|
138611
|
+
}
|
|
138612
|
+
);
|
|
138613
|
+
current.process.write(SUBMIT_BYTES);
|
|
138614
|
+
}, CODEX_SUBMIT_DELAY_MS);
|
|
138615
|
+
}
|
|
138616
|
+
// Drain any inputs sent while the session was still pendingReady, writing
|
|
138617
|
+
// them in arrival order now that Codex is Ready.
|
|
138618
|
+
flushQueuedInputs(sessionId) {
|
|
138619
|
+
const queue = this.queuedInputs.get(sessionId);
|
|
138620
|
+
if (!queue || queue.length === 0) return;
|
|
138621
|
+
this.queuedInputs.delete(sessionId);
|
|
138622
|
+
const session = this.sessions.get(sessionId);
|
|
138623
|
+
if (!session) return;
|
|
138624
|
+
this.log.info(
|
|
138625
|
+
`[codex.flush] ${sessionId.slice(0, 8)} flushing ${queue.length} queued input(s)`,
|
|
138626
|
+
{
|
|
138627
|
+
event: "codex.flush_queued",
|
|
138628
|
+
sessionId,
|
|
138629
|
+
queueLen: queue.length
|
|
138630
|
+
}
|
|
138631
|
+
);
|
|
138632
|
+
queue.forEach((input, i) => {
|
|
138633
|
+
const writeAt = i * CODEX_SUBMIT_DELAY_MS * 2;
|
|
138634
|
+
if (writeAt === 0) {
|
|
138635
|
+
this.writeSubmit(sessionId, session, input, "flush", session.promptCount);
|
|
138636
|
+
} else {
|
|
138637
|
+
setTimeout(() => {
|
|
138638
|
+
const current = this.sessions.get(sessionId);
|
|
138639
|
+
if (!current || current !== session) return;
|
|
138640
|
+
this.writeSubmit(sessionId, session, input, "flush", session.promptCount);
|
|
138641
|
+
}, writeAt);
|
|
138642
|
+
}
|
|
138643
|
+
});
|
|
138644
|
+
}
|
|
138645
|
+
// SIGINT produces a clean exitCode=0 exit (Phase 0 — confirmed).
|
|
138646
|
+
cancel(sessionId) {
|
|
138647
|
+
const session = this.sessions.get(sessionId);
|
|
138648
|
+
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
138649
|
+
session.process.kill("SIGINT");
|
|
138650
|
+
}
|
|
138651
|
+
killPid(pid) {
|
|
138652
|
+
try {
|
|
138653
|
+
process.kill(pid, "SIGTERM");
|
|
138654
|
+
} catch {
|
|
138655
|
+
}
|
|
138656
|
+
}
|
|
138657
|
+
// Kill the PTY and mark the session idle. Mirrors PTYManager.putOnHold.
|
|
138658
|
+
putOnHold(sessionId) {
|
|
138659
|
+
const session = this.sessions.get(sessionId);
|
|
138660
|
+
if (!session) return;
|
|
138661
|
+
this.pendingReady.delete(sessionId);
|
|
138662
|
+
this.queuedInputs.delete(sessionId);
|
|
138663
|
+
this.trustGateAnswered.delete(sessionId);
|
|
138664
|
+
try {
|
|
138665
|
+
session.process.kill("SIGINT");
|
|
138666
|
+
} catch {
|
|
138667
|
+
}
|
|
138668
|
+
session.status = "idle";
|
|
138669
|
+
session.completedAt = /* @__PURE__ */ new Date();
|
|
138670
|
+
session.screen.dispose();
|
|
138671
|
+
this.sessions.delete(sessionId);
|
|
138672
|
+
this.onStatusChange?.(toPublicSession(session));
|
|
138673
|
+
}
|
|
138674
|
+
getOutput(sessionId) {
|
|
138675
|
+
const session = this.sessions.get(sessionId);
|
|
138676
|
+
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
138677
|
+
return session.outputBuffer.toString("utf-8");
|
|
138449
138678
|
}
|
|
138679
|
+
// Render the last `maxLines` rows of the session's screen in true on-screen
|
|
138680
|
+
// order — same flush-then-read technique as PTYManager.getOutputLines.
|
|
138681
|
+
async getOutputLines(sessionId, maxLines) {
|
|
138682
|
+
const session = this.sessions.get(sessionId);
|
|
138683
|
+
if (!session) throw new Error(`Session not found: ${sessionId}`);
|
|
138684
|
+
await new Promise((resolve6) => session.screen.write("", () => resolve6()));
|
|
138685
|
+
const buf = session.screen.buffer.active;
|
|
138686
|
+
const lines = [];
|
|
138687
|
+
for (let y2 = 0; y2 < buf.length; y2++) {
|
|
138688
|
+
lines.push(buf.getLine(y2)?.translateToString(true) ?? "");
|
|
138689
|
+
}
|
|
138690
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
138691
|
+
lines.pop();
|
|
138692
|
+
}
|
|
138693
|
+
return lines.slice(-maxLines);
|
|
138694
|
+
}
|
|
138695
|
+
getSession(sessionId) {
|
|
138696
|
+
const session = this.sessions.get(sessionId);
|
|
138697
|
+
return session ? toPublicSession(session) : null;
|
|
138698
|
+
}
|
|
138699
|
+
hasSession(sessionId) {
|
|
138700
|
+
return this.sessions.has(sessionId);
|
|
138701
|
+
}
|
|
138702
|
+
listSessions() {
|
|
138703
|
+
return Array.from(this.sessions.values()).map(toPublicSession);
|
|
138704
|
+
}
|
|
138705
|
+
dispose() {
|
|
138706
|
+
for (const session of this.sessions.values()) {
|
|
138707
|
+
try {
|
|
138708
|
+
session.process.kill();
|
|
138709
|
+
} catch {
|
|
138710
|
+
}
|
|
138711
|
+
session.screen.dispose();
|
|
138712
|
+
}
|
|
138713
|
+
this.sessions.clear();
|
|
138714
|
+
this.pendingReady.clear();
|
|
138715
|
+
this.queuedInputs.clear();
|
|
138716
|
+
this.trustGateAnswered.clear();
|
|
138717
|
+
}
|
|
138718
|
+
handleOutput(sessionId, data) {
|
|
138719
|
+
const session = this.sessions.get(sessionId);
|
|
138720
|
+
if (!session) return;
|
|
138721
|
+
const chunk = Buffer.from(data, "utf-8");
|
|
138722
|
+
session.outputBuffer = Buffer.concat([session.outputBuffer, chunk]);
|
|
138723
|
+
if (session.outputBuffer.length > OUTPUT_BUFFER_MAX) {
|
|
138724
|
+
session.outputBuffer = session.outputBuffer.subarray(
|
|
138725
|
+
session.outputBuffer.length - OUTPUT_BUFFER_MAX
|
|
138726
|
+
);
|
|
138727
|
+
}
|
|
138728
|
+
session.screen.write(data);
|
|
138729
|
+
session.lastOutput = stripAnsi(data);
|
|
138730
|
+
this.onOutput?.(sessionId, data);
|
|
138731
|
+
this.detectReady(sessionId, session).catch((err) => {
|
|
138732
|
+
this.log.warn("[codex.ready_detect] failed", {
|
|
138733
|
+
event: "codex.ready_detect_failed",
|
|
138734
|
+
sessionId,
|
|
138735
|
+
err
|
|
138736
|
+
});
|
|
138737
|
+
});
|
|
138738
|
+
}
|
|
138739
|
+
// Renders the session's headless screen and checks for the directory-trust
|
|
138740
|
+
// gate (answered once, debounced) and the "Ready" status-bar text. Only
|
|
138741
|
+
// transitions to waiting_input / fires onReady when the rendered status
|
|
138742
|
+
// line literally contains "Ready" — `›` alone (visible during "Starting")
|
|
138743
|
+
// is NOT a valid readiness signal (Phase 0).
|
|
138744
|
+
async detectReady(sessionId, session) {
|
|
138745
|
+
if (session.status !== "running" || !this.pendingReady.has(sessionId)) return;
|
|
138746
|
+
const lines = await this.getOutputLines(sessionId, PTY_ROWS);
|
|
138747
|
+
const screenText = lines.join("\n");
|
|
138748
|
+
if (CODEX_TRUST_GATE_REGEX.test(screenText)) {
|
|
138749
|
+
if (!this.trustGateAnswered.has(sessionId)) {
|
|
138750
|
+
this.trustGateAnswered.add(sessionId);
|
|
138751
|
+
this.log.info(`[codex.trust_gate] ${sessionId.slice(0, 8)} auto-answering`, {
|
|
138752
|
+
event: "codex.trust_gate",
|
|
138753
|
+
sessionId
|
|
138754
|
+
});
|
|
138755
|
+
session.process.write("\r");
|
|
138756
|
+
}
|
|
138757
|
+
return;
|
|
138758
|
+
}
|
|
138759
|
+
const lastNonBlank2 = [...lines].reverse().find((l) => l.trim() !== "") ?? "";
|
|
138760
|
+
if (!lastNonBlank2.includes(CODEX_PROMPT_READY_TEXT)) return;
|
|
138761
|
+
this.markReady(sessionId, session);
|
|
138762
|
+
}
|
|
138763
|
+
markReady(sessionId, session) {
|
|
138764
|
+
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
138765
|
+
session.status = "waiting_input";
|
|
138766
|
+
this.log.info(`[codex.ready] ${sessionId.slice(0, 8)}`, {
|
|
138767
|
+
event: "codex.ready",
|
|
138768
|
+
sessionId
|
|
138769
|
+
});
|
|
138770
|
+
this.onStatusChange?.(toPublicSession(session));
|
|
138771
|
+
if (this.pendingReady.has(sessionId)) {
|
|
138772
|
+
this.pendingReady.delete(sessionId);
|
|
138773
|
+
this.flushQueuedInputs(sessionId);
|
|
138774
|
+
this.onReady?.(toPublicSession(session));
|
|
138775
|
+
}
|
|
138776
|
+
}
|
|
138777
|
+
handleExit(sessionId, exitCode) {
|
|
138778
|
+
const session = this.sessions.get(sessionId);
|
|
138779
|
+
if (!session) return;
|
|
138780
|
+
session.completedAt = /* @__PURE__ */ new Date();
|
|
138781
|
+
session.status = "idle";
|
|
138782
|
+
const elapsedMs = session.completedAt.getTime() - session.startedAt.getTime();
|
|
138783
|
+
if (exitCode !== 0 && elapsedMs < 2e3 && session.lastOutput === "") {
|
|
138784
|
+
if (!(0, import_fs20.existsSync)(session.projectPath)) {
|
|
138785
|
+
session.failureReason = `Project directory not found: ${session.projectPath}`;
|
|
138786
|
+
} else {
|
|
138787
|
+
session.failureReason = `Codex process exited immediately (code ${exitCode}).`;
|
|
138788
|
+
}
|
|
138789
|
+
}
|
|
138790
|
+
this.onStatusChange?.(toPublicSession(session));
|
|
138791
|
+
session.screen.dispose();
|
|
138792
|
+
this.sessions.delete(sessionId);
|
|
138793
|
+
this.queuedInputs.delete(sessionId);
|
|
138794
|
+
this.trustGateAnswered.delete(sessionId);
|
|
138795
|
+
}
|
|
138796
|
+
};
|
|
138797
|
+
function toPublicSession(s3) {
|
|
138798
|
+
return {
|
|
138799
|
+
id: s3.id,
|
|
138800
|
+
provider: s3.provider ?? CODEX_CLI_PROVIDER2,
|
|
138801
|
+
projectPath: s3.projectPath,
|
|
138802
|
+
projectName: s3.projectName,
|
|
138803
|
+
branch: s3.branch,
|
|
138804
|
+
status: s3.status,
|
|
138805
|
+
startedAt: s3.startedAt,
|
|
138806
|
+
completedAt: s3.completedAt,
|
|
138807
|
+
promptCount: s3.promptCount,
|
|
138808
|
+
lastOutput: s3.lastOutput,
|
|
138809
|
+
...s3.failureReason != null && { failureReason: s3.failureReason },
|
|
138810
|
+
...s3.lastActivityAt != null && { lastActivityAt: s3.lastActivityAt },
|
|
138811
|
+
...s3.filePath != null && { filePath: s3.filePath }
|
|
138812
|
+
};
|
|
138813
|
+
}
|
|
138814
|
+
function stripAnsi(str) {
|
|
138815
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
|
|
138450
138816
|
}
|
|
138451
138817
|
|
|
138452
138818
|
// src/pty-manager.ts
|
|
138453
|
-
var
|
|
138819
|
+
var import_headless2 = __toESM(require_xterm_headless(), 1);
|
|
138454
138820
|
var import_crypto6 = require("crypto");
|
|
138455
|
-
var
|
|
138821
|
+
var import_fs21 = require("fs");
|
|
138456
138822
|
var import_path17 = require("path");
|
|
138457
138823
|
init_logger();
|
|
138458
138824
|
|
|
@@ -138613,28 +138979,28 @@ function detectShellPrompt(lines) {
|
|
|
138613
138979
|
}
|
|
138614
138980
|
|
|
138615
138981
|
// src/pty-manager.ts
|
|
138616
|
-
var
|
|
138617
|
-
var
|
|
138618
|
-
var
|
|
138619
|
-
var
|
|
138982
|
+
var OUTPUT_BUFFER_MAX2 = 65536;
|
|
138983
|
+
var PTY_COLS2 = 120;
|
|
138984
|
+
var PTY_ROWS2 = 40;
|
|
138985
|
+
var SCREEN_SCROLLBACK2 = 1e3;
|
|
138620
138986
|
var CLAUDE_PROMPT_MARKERS = ["\u256D", "\u276F"];
|
|
138621
138987
|
var PROMPT_MARKER_FALLBACK_MS = 1e4;
|
|
138622
138988
|
function buildPasteBytes(input) {
|
|
138623
138989
|
return `\x1B[200~${input}\x1B[201~`;
|
|
138624
138990
|
}
|
|
138625
|
-
var
|
|
138991
|
+
var SUBMIT_BYTES2 = "\r";
|
|
138626
138992
|
var SUBMIT_DELAY_MS = 16;
|
|
138627
|
-
function
|
|
138993
|
+
function digestBytes2(s3) {
|
|
138628
138994
|
const escaped = s3.replace(new RegExp(String.fromCharCode(27), "g"), "\\x1b").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
|
|
138629
138995
|
if (escaped.length <= 200) return escaped;
|
|
138630
138996
|
return `${escaped.slice(0, 100)}\u2026[${escaped.length - 200}B omitted]\u2026${escaped.slice(-100)}`;
|
|
138631
138997
|
}
|
|
138632
|
-
var
|
|
138633
|
-
async function
|
|
138634
|
-
if (
|
|
138998
|
+
var pty2 = null;
|
|
138999
|
+
async function loadPty2() {
|
|
139000
|
+
if (pty2) return pty2;
|
|
138635
139001
|
try {
|
|
138636
|
-
|
|
138637
|
-
return
|
|
139002
|
+
pty2 = await import("node-pty");
|
|
139003
|
+
return pty2;
|
|
138638
139004
|
} catch (err) {
|
|
138639
139005
|
throw new Error(
|
|
138640
139006
|
`node-pty is required for PTY management but failed to load. Ensure it is installed: npm install node-pty
|
|
@@ -138642,11 +139008,11 @@ Original error: ${err}`
|
|
|
138642
139008
|
);
|
|
138643
139009
|
}
|
|
138644
139010
|
}
|
|
138645
|
-
function
|
|
138646
|
-
return new
|
|
138647
|
-
cols:
|
|
138648
|
-
rows:
|
|
138649
|
-
scrollback:
|
|
139011
|
+
function createScreen2() {
|
|
139012
|
+
return new import_headless2.Terminal({
|
|
139013
|
+
cols: PTY_COLS2,
|
|
139014
|
+
rows: PTY_ROWS2,
|
|
139015
|
+
scrollback: SCREEN_SCROLLBACK2,
|
|
138650
139016
|
allowProposedApi: true
|
|
138651
139017
|
});
|
|
138652
139018
|
}
|
|
@@ -138695,6 +139061,10 @@ var PTYManager = class {
|
|
|
138695
139061
|
// to a given input or fell silent. Reset on dispose().
|
|
138696
139062
|
chunkIndex = /* @__PURE__ */ new Map();
|
|
138697
139063
|
lastChunkAt = /* @__PURE__ */ new Map();
|
|
139064
|
+
// In-flight start()/startFresh() calls keyed by sessionId. A second
|
|
139065
|
+
// concurrent resume for the same session (double-tap, client retry) awaits
|
|
139066
|
+
// the first call's promise instead of spawning a duplicate PTY (CRITICAL #3).
|
|
139067
|
+
startPromises = /* @__PURE__ */ new Map();
|
|
138698
139068
|
constructor(options = {}) {
|
|
138699
139069
|
this.onOutput = options.onOutput;
|
|
138700
139070
|
this.onStatusChange = options.onStatusChange;
|
|
@@ -138717,7 +139087,18 @@ var PTYManager = class {
|
|
|
138717
139087
|
// custom-API-key — are cleared by the seeded ~/.claude.json in
|
|
138718
139088
|
// docker/entrypoint.sh.) startFresh() uses the same flag for the same reason.
|
|
138719
139089
|
async start(sessionId, options) {
|
|
138720
|
-
const
|
|
139090
|
+
const existing = this.sessions.get(sessionId);
|
|
139091
|
+
if (existing) return toPublicSession2(existing);
|
|
139092
|
+
const inFlight = this.startPromises.get(sessionId);
|
|
139093
|
+
if (inFlight) return inFlight;
|
|
139094
|
+
const promise2 = this.doStart(sessionId, options).finally(() => {
|
|
139095
|
+
this.startPromises.delete(sessionId);
|
|
139096
|
+
});
|
|
139097
|
+
this.startPromises.set(sessionId, promise2);
|
|
139098
|
+
return promise2;
|
|
139099
|
+
}
|
|
139100
|
+
async doStart(sessionId, options) {
|
|
139101
|
+
const nodePty = await loadPty2();
|
|
138721
139102
|
const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
|
|
138722
139103
|
const proc = nodePty.spawn(
|
|
138723
139104
|
resolveClaudeExe(),
|
|
@@ -138739,6 +139120,7 @@ var PTYManager = class {
|
|
|
138739
139120
|
);
|
|
138740
139121
|
const session = {
|
|
138741
139122
|
id: sessionId,
|
|
139123
|
+
provider: CLAUDE_CODE_PROVIDER2,
|
|
138742
139124
|
projectPath: options.projectPath,
|
|
138743
139125
|
projectName,
|
|
138744
139126
|
branch: options.branch ?? "",
|
|
@@ -138749,7 +139131,7 @@ var PTYManager = class {
|
|
|
138749
139131
|
lastOutput: "",
|
|
138750
139132
|
process: proc,
|
|
138751
139133
|
outputBuffer: Buffer.alloc(0),
|
|
138752
|
-
screen:
|
|
139134
|
+
screen: createScreen2()
|
|
138753
139135
|
};
|
|
138754
139136
|
this.sessions.set(sessionId, session);
|
|
138755
139137
|
this.pendingReady.add(sessionId);
|
|
@@ -138760,13 +139142,13 @@ var PTYManager = class {
|
|
|
138760
139142
|
this.pendingReady.delete(sessionId);
|
|
138761
139143
|
this.handleExit(sessionId, exitCode);
|
|
138762
139144
|
});
|
|
138763
|
-
return
|
|
139145
|
+
return toPublicSession2(session);
|
|
138764
139146
|
}
|
|
138765
139147
|
// Start a brand-new Claude session. A stable UUID is generated here and passed
|
|
138766
139148
|
// to Claude via --session-id so the JSONL filename matches from the start.
|
|
138767
139149
|
// onReady fires once Claude reaches its first prompt (waiting_input).
|
|
138768
139150
|
async startFresh(options) {
|
|
138769
|
-
const nodePty = await
|
|
139151
|
+
const nodePty = await loadPty2();
|
|
138770
139152
|
const sessionId = (0, import_crypto6.randomUUID)();
|
|
138771
139153
|
const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
|
|
138772
139154
|
const args = [
|
|
@@ -138789,6 +139171,7 @@ var PTYManager = class {
|
|
|
138789
139171
|
});
|
|
138790
139172
|
const session = {
|
|
138791
139173
|
id: sessionId,
|
|
139174
|
+
provider: CLAUDE_CODE_PROVIDER2,
|
|
138792
139175
|
projectPath: options.projectPath,
|
|
138793
139176
|
projectName,
|
|
138794
139177
|
branch: "",
|
|
@@ -138799,7 +139182,7 @@ var PTYManager = class {
|
|
|
138799
139182
|
lastOutput: "",
|
|
138800
139183
|
process: proc,
|
|
138801
139184
|
outputBuffer: Buffer.alloc(0),
|
|
138802
|
-
screen:
|
|
139185
|
+
screen: createScreen2()
|
|
138803
139186
|
};
|
|
138804
139187
|
this.sessions.set(sessionId, session);
|
|
138805
139188
|
this.pendingReady.add(sessionId);
|
|
@@ -138810,7 +139193,7 @@ var PTYManager = class {
|
|
|
138810
139193
|
this.pendingReady.delete(sessionId);
|
|
138811
139194
|
this.handleExit(sessionId, exitCode);
|
|
138812
139195
|
});
|
|
138813
|
-
return
|
|
139196
|
+
return toPublicSession2(session);
|
|
138814
139197
|
}
|
|
138815
139198
|
// Write raw key bytes directly to the PTY without bracketed-paste wrapping.
|
|
138816
139199
|
// Use for control sequences (arrow keys, Enter) that must not be quoted.
|
|
@@ -138822,10 +139205,10 @@ var PTYManager = class {
|
|
|
138822
139205
|
}
|
|
138823
139206
|
if (session.status === "waiting_input") {
|
|
138824
139207
|
session.status = "running";
|
|
138825
|
-
this.onStatusChange?.(
|
|
139208
|
+
this.onStatusChange?.(toPublicSession2(session));
|
|
138826
139209
|
}
|
|
138827
139210
|
this.log.info(
|
|
138828
|
-
`[pty.keys.write] ${sessionId.slice(0, 8)} bytes=${keys.length} digest=${
|
|
139211
|
+
`[pty.keys.write] ${sessionId.slice(0, 8)} bytes=${keys.length} digest=${digestBytes2(keys)}`,
|
|
138829
139212
|
{ event: "pty.keys_write", sessionId, byteLen: keys.length }
|
|
138830
139213
|
);
|
|
138831
139214
|
session.process.write(keys);
|
|
@@ -138857,7 +139240,7 @@ var PTYManager = class {
|
|
|
138857
139240
|
}
|
|
138858
139241
|
if (session.status === "waiting_input") {
|
|
138859
139242
|
session.status = "running";
|
|
138860
|
-
this.onStatusChange?.(
|
|
139243
|
+
this.onStatusChange?.(toPublicSession2(session));
|
|
138861
139244
|
}
|
|
138862
139245
|
this.writeSubmit(sessionId, session, input, "direct", session.promptCount + 1);
|
|
138863
139246
|
session.lastActivityAt = /* @__PURE__ */ new Date();
|
|
@@ -138870,13 +139253,13 @@ var PTYManager = class {
|
|
|
138870
139253
|
writeSubmit(sessionId, session, input, path2, promptCount) {
|
|
138871
139254
|
const pasteBytes = buildPasteBytes(input);
|
|
138872
139255
|
this.log.info(
|
|
138873
|
-
`[pty.input.write] ${sessionId.slice(0, 8)} promptCount=${promptCount} bytes=${pasteBytes.length} digest=${
|
|
139256
|
+
`[pty.input.write] ${sessionId.slice(0, 8)} promptCount=${promptCount} bytes=${pasteBytes.length} digest=${digestBytes2(pasteBytes)}`,
|
|
138874
139257
|
{
|
|
138875
139258
|
event: "pty.input_write",
|
|
138876
139259
|
sessionId,
|
|
138877
139260
|
promptCount,
|
|
138878
139261
|
byteLen: pasteBytes.length,
|
|
138879
|
-
digest:
|
|
139262
|
+
digest: digestBytes2(pasteBytes),
|
|
138880
139263
|
path: path2,
|
|
138881
139264
|
phase: "paste"
|
|
138882
139265
|
}
|
|
@@ -138891,13 +139274,13 @@ var PTYManager = class {
|
|
|
138891
139274
|
event: "pty.input_write",
|
|
138892
139275
|
sessionId,
|
|
138893
139276
|
promptCount,
|
|
138894
|
-
byteLen:
|
|
139277
|
+
byteLen: SUBMIT_BYTES2.length,
|
|
138895
139278
|
digest: "\\r",
|
|
138896
139279
|
path: path2,
|
|
138897
139280
|
phase: "submit"
|
|
138898
139281
|
}
|
|
138899
139282
|
);
|
|
138900
|
-
current.process.write(
|
|
139283
|
+
current.process.write(SUBMIT_BYTES2);
|
|
138901
139284
|
}, SUBMIT_DELAY_MS);
|
|
138902
139285
|
}
|
|
138903
139286
|
// Drain any inputs that were sent while the session was still pendingReady,
|
|
@@ -138955,7 +139338,7 @@ var PTYManager = class {
|
|
|
138955
139338
|
session.completedAt = /* @__PURE__ */ new Date();
|
|
138956
139339
|
session.screen.dispose();
|
|
138957
139340
|
this.sessions.delete(sessionId);
|
|
138958
|
-
this.onStatusChange?.(
|
|
139341
|
+
this.onStatusChange?.(toPublicSession2(session));
|
|
138959
139342
|
}
|
|
138960
139343
|
getOutput(sessionId) {
|
|
138961
139344
|
const session = this.sessions.get(sessionId);
|
|
@@ -138986,13 +139369,13 @@ var PTYManager = class {
|
|
|
138986
139369
|
}
|
|
138987
139370
|
getSession(sessionId) {
|
|
138988
139371
|
const session = this.sessions.get(sessionId);
|
|
138989
|
-
return session ?
|
|
139372
|
+
return session ? toPublicSession2(session) : null;
|
|
138990
139373
|
}
|
|
138991
139374
|
hasSession(sessionId) {
|
|
138992
139375
|
return this.sessions.has(sessionId);
|
|
138993
139376
|
}
|
|
138994
139377
|
listSessions() {
|
|
138995
|
-
return Array.from(this.sessions.values()).map(
|
|
139378
|
+
return Array.from(this.sessions.values()).map(toPublicSession2);
|
|
138996
139379
|
}
|
|
138997
139380
|
dispose() {
|
|
138998
139381
|
for (const session of this.sessions.values()) {
|
|
@@ -139024,7 +139407,7 @@ var PTYManager = class {
|
|
|
139024
139407
|
this.lastChunkAt.set(sessionId, now);
|
|
139025
139408
|
const gapMs = last == null ? 0 : now - last;
|
|
139026
139409
|
this.log.info(
|
|
139027
|
-
`[pty.chunk] ${sessionId.slice(0, 8)} #${idx} +${chunk.length}B gap=${gapMs}ms status=${session.status} digest=${
|
|
139410
|
+
`[pty.chunk] ${sessionId.slice(0, 8)} #${idx} +${chunk.length}B gap=${gapMs}ms status=${session.status} digest=${digestBytes2(data)}`,
|
|
139028
139411
|
{
|
|
139029
139412
|
event: "pty.chunk",
|
|
139030
139413
|
sessionId,
|
|
@@ -139033,17 +139416,17 @@ var PTYManager = class {
|
|
|
139033
139416
|
gapMs,
|
|
139034
139417
|
status: session.status,
|
|
139035
139418
|
pendingReady: this.pendingReady.has(sessionId),
|
|
139036
|
-
digest:
|
|
139419
|
+
digest: digestBytes2(data)
|
|
139037
139420
|
}
|
|
139038
139421
|
);
|
|
139039
139422
|
session.outputBuffer = Buffer.concat([session.outputBuffer, chunk]);
|
|
139040
|
-
if (session.outputBuffer.length >
|
|
139423
|
+
if (session.outputBuffer.length > OUTPUT_BUFFER_MAX2) {
|
|
139041
139424
|
session.outputBuffer = session.outputBuffer.subarray(
|
|
139042
|
-
session.outputBuffer.length -
|
|
139425
|
+
session.outputBuffer.length - OUTPUT_BUFFER_MAX2
|
|
139043
139426
|
);
|
|
139044
139427
|
}
|
|
139045
139428
|
session.screen.write(data);
|
|
139046
|
-
const stripped =
|
|
139429
|
+
const stripped = stripAnsi2(data);
|
|
139047
139430
|
session.lastOutput = stripped;
|
|
139048
139431
|
const matchedMarker = CLAUDE_PROMPT_MARKERS.find((m2) => stripped.includes(m2));
|
|
139049
139432
|
if (session.status === "running" && matchedMarker) {
|
|
@@ -139146,11 +139529,11 @@ var PTYManager = class {
|
|
|
139146
139529
|
reason,
|
|
139147
139530
|
elapsedMs
|
|
139148
139531
|
});
|
|
139149
|
-
this.onStatusChange?.(
|
|
139532
|
+
this.onStatusChange?.(toPublicSession2(session));
|
|
139150
139533
|
if (this.pendingReady.has(sessionId)) {
|
|
139151
139534
|
this.pendingReady.delete(sessionId);
|
|
139152
139535
|
this.flushQueuedInputs(sessionId);
|
|
139153
|
-
this.onReady?.(
|
|
139536
|
+
this.onReady?.(toPublicSession2(session));
|
|
139154
139537
|
}
|
|
139155
139538
|
}
|
|
139156
139539
|
handleExit(sessionId, exitCode) {
|
|
@@ -139160,13 +139543,13 @@ var PTYManager = class {
|
|
|
139160
139543
|
session.status = "idle";
|
|
139161
139544
|
const elapsedMs = session.completedAt.getTime() - session.startedAt.getTime();
|
|
139162
139545
|
if (exitCode !== 0 && elapsedMs < 2e3 && session.lastOutput === "") {
|
|
139163
|
-
if (!(0,
|
|
139546
|
+
if (!(0, import_fs21.existsSync)(session.projectPath)) {
|
|
139164
139547
|
session.failureReason = `Project directory not found: ${session.projectPath}`;
|
|
139165
139548
|
} else {
|
|
139166
139549
|
session.failureReason = `Process exited immediately (code ${exitCode}). Check that the Claude binary is installed and accessible.`;
|
|
139167
139550
|
}
|
|
139168
139551
|
}
|
|
139169
|
-
this.onStatusChange?.(
|
|
139552
|
+
this.onStatusChange?.(toPublicSession2(session));
|
|
139170
139553
|
session.screen.dispose();
|
|
139171
139554
|
this.sessions.delete(sessionId);
|
|
139172
139555
|
this.queuedInputs.delete(sessionId);
|
|
@@ -139176,9 +139559,10 @@ var PTYManager = class {
|
|
|
139176
139559
|
this.shellPromptOpen.delete(sessionId);
|
|
139177
139560
|
}
|
|
139178
139561
|
};
|
|
139179
|
-
function
|
|
139562
|
+
function toPublicSession2(s3) {
|
|
139180
139563
|
return {
|
|
139181
139564
|
id: s3.id,
|
|
139565
|
+
provider: s3.provider ?? CLAUDE_CODE_PROVIDER2,
|
|
139182
139566
|
projectPath: s3.projectPath,
|
|
139183
139567
|
projectName: s3.projectName,
|
|
139184
139568
|
branch: s3.branch,
|
|
@@ -139192,10 +139576,306 @@ function toPublicSession(s3) {
|
|
|
139192
139576
|
...s3.filePath != null && { filePath: s3.filePath }
|
|
139193
139577
|
};
|
|
139194
139578
|
}
|
|
139195
|
-
function
|
|
139579
|
+
function stripAnsi2(str) {
|
|
139196
139580
|
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
|
|
139197
139581
|
}
|
|
139198
139582
|
|
|
139583
|
+
// src/live-session-manager.ts
|
|
139584
|
+
var LiveSessionManager = class {
|
|
139585
|
+
runners;
|
|
139586
|
+
constructor(options = {}) {
|
|
139587
|
+
this.runners = /* @__PURE__ */ new Map([
|
|
139588
|
+
[CLAUDE_CODE_PROVIDER2, new PTYManager(options)],
|
|
139589
|
+
[CODEX_CLI_PROVIDER2, new CodexPtyRunner(options)]
|
|
139590
|
+
]);
|
|
139591
|
+
}
|
|
139592
|
+
async start(sessionId, options) {
|
|
139593
|
+
const provider = options.provider ?? CLAUDE_CODE_PROVIDER2;
|
|
139594
|
+
const runner = this.assertSupportedProvider(provider, options.projectPath);
|
|
139595
|
+
return runner.start(sessionId, options);
|
|
139596
|
+
}
|
|
139597
|
+
async startFresh(options) {
|
|
139598
|
+
const provider = options.provider ?? CLAUDE_CODE_PROVIDER2;
|
|
139599
|
+
const runner = this.assertSupportedProvider(provider, options.projectPath);
|
|
139600
|
+
return runner.startFresh(options);
|
|
139601
|
+
}
|
|
139602
|
+
sendInput(sessionId, input) {
|
|
139603
|
+
return this.runnerFor(sessionId).sendInput(sessionId, input);
|
|
139604
|
+
}
|
|
139605
|
+
sendKeys(sessionId, keys) {
|
|
139606
|
+
this.runnerFor(sessionId).sendKeys(sessionId, keys);
|
|
139607
|
+
}
|
|
139608
|
+
cancel(sessionId) {
|
|
139609
|
+
this.runnerFor(sessionId).cancel(sessionId);
|
|
139610
|
+
}
|
|
139611
|
+
killPid(pid) {
|
|
139612
|
+
for (const runner of this.runners.values()) {
|
|
139613
|
+
runner.killPid(pid);
|
|
139614
|
+
}
|
|
139615
|
+
}
|
|
139616
|
+
// putOnHold tolerates an unknown sessionId (PTYManager.putOnHold is a no-op
|
|
139617
|
+
// when the session isn't in its map), so — unlike the other session-keyed
|
|
139618
|
+
// methods — route to the owning runner when found, otherwise broadcast to
|
|
139619
|
+
// every runner rather than throwing; this matches the pre-extraction
|
|
139620
|
+
// behavior of delegating straight through with no existence check.
|
|
139621
|
+
putOnHold(sessionId) {
|
|
139622
|
+
for (const runner of this.runners.values()) {
|
|
139623
|
+
if (runner.hasSession(sessionId) || runner.getSession(sessionId)) {
|
|
139624
|
+
runner.putOnHold(sessionId);
|
|
139625
|
+
return;
|
|
139626
|
+
}
|
|
139627
|
+
}
|
|
139628
|
+
for (const runner of this.runners.values()) {
|
|
139629
|
+
runner.putOnHold(sessionId);
|
|
139630
|
+
}
|
|
139631
|
+
}
|
|
139632
|
+
getOutput(sessionId) {
|
|
139633
|
+
return this.runnerFor(sessionId).getOutput(sessionId);
|
|
139634
|
+
}
|
|
139635
|
+
getOutputLines(sessionId, maxLines) {
|
|
139636
|
+
return this.runnerFor(sessionId).getOutputLines(sessionId, maxLines);
|
|
139637
|
+
}
|
|
139638
|
+
getSession(sessionId) {
|
|
139639
|
+
for (const runner of this.runners.values()) {
|
|
139640
|
+
const session = runner.getSession(sessionId);
|
|
139641
|
+
if (session) return session;
|
|
139642
|
+
}
|
|
139643
|
+
return null;
|
|
139644
|
+
}
|
|
139645
|
+
hasSession(sessionId) {
|
|
139646
|
+
for (const runner of this.runners.values()) {
|
|
139647
|
+
if (runner.hasSession(sessionId)) return true;
|
|
139648
|
+
}
|
|
139649
|
+
return false;
|
|
139650
|
+
}
|
|
139651
|
+
listSessions() {
|
|
139652
|
+
return Array.from(this.runners.values()).flatMap((runner) => runner.listSessions());
|
|
139653
|
+
}
|
|
139654
|
+
dispose() {
|
|
139655
|
+
for (const runner of this.runners.values()) {
|
|
139656
|
+
runner.dispose();
|
|
139657
|
+
}
|
|
139658
|
+
}
|
|
139659
|
+
// Look up which runner owns a session. Only one runner exists today, so
|
|
139660
|
+
// this is a linear scan across hasSession()/getSession() rather than a
|
|
139661
|
+
// separate session→provider index — see task-1-brief.md.
|
|
139662
|
+
runnerFor(sessionId) {
|
|
139663
|
+
for (const runner of this.runners.values()) {
|
|
139664
|
+
if (runner.hasSession(sessionId) || runner.getSession(sessionId)) return runner;
|
|
139665
|
+
}
|
|
139666
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
139667
|
+
}
|
|
139668
|
+
assertSupportedProvider(provider, projectPath) {
|
|
139669
|
+
const runner = this.runners.get(provider);
|
|
139670
|
+
if (runner) return runner;
|
|
139671
|
+
const err = new Error(
|
|
139672
|
+
`Live ${provider} sessions are not implemented yet for ${(0, import_path18.basename)(projectPath)}`
|
|
139673
|
+
);
|
|
139674
|
+
err.statusCode = 501;
|
|
139675
|
+
throw err;
|
|
139676
|
+
}
|
|
139677
|
+
};
|
|
139678
|
+
|
|
139679
|
+
// src/server.ts
|
|
139680
|
+
init_logger();
|
|
139681
|
+
|
|
139682
|
+
// src/pair-store.ts
|
|
139683
|
+
var import_crypto7 = require("crypto");
|
|
139684
|
+
var DEFAULT_TTL_SECONDS = 180;
|
|
139685
|
+
var SWEEP_INTERVAL_MS = 6e4;
|
|
139686
|
+
var PairTokenStore = class {
|
|
139687
|
+
current = null;
|
|
139688
|
+
ttlMs;
|
|
139689
|
+
sweepTimer = null;
|
|
139690
|
+
constructor(opts = {}) {
|
|
139691
|
+
this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
|
|
139692
|
+
if (opts.autoSweep !== false) {
|
|
139693
|
+
this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
|
|
139694
|
+
this.sweepTimer.unref?.();
|
|
139695
|
+
}
|
|
139696
|
+
}
|
|
139697
|
+
mint() {
|
|
139698
|
+
const token = `pt_${(0, import_crypto7.randomBytes)(16).toString("hex")}`;
|
|
139699
|
+
const expiresAt = Date.now() + this.ttlMs;
|
|
139700
|
+
this.current = { token, expiresAt, used: false };
|
|
139701
|
+
return {
|
|
139702
|
+
token,
|
|
139703
|
+
expiresAt,
|
|
139704
|
+
expiresInSeconds: Math.floor(this.ttlMs / 1e3)
|
|
139705
|
+
};
|
|
139706
|
+
}
|
|
139707
|
+
consume(token) {
|
|
139708
|
+
const record2 = this.current;
|
|
139709
|
+
if (!record2 || record2.token !== token) return { ok: false, reason: "unknown" };
|
|
139710
|
+
if (Date.now() > record2.expiresAt) {
|
|
139711
|
+
this.current = null;
|
|
139712
|
+
return { ok: false, reason: "expired" };
|
|
139713
|
+
}
|
|
139714
|
+
if (record2.used) return { ok: false, reason: "used" };
|
|
139715
|
+
record2.used = true;
|
|
139716
|
+
return { ok: true };
|
|
139717
|
+
}
|
|
139718
|
+
peek() {
|
|
139719
|
+
return this.current;
|
|
139720
|
+
}
|
|
139721
|
+
clear() {
|
|
139722
|
+
this.current = null;
|
|
139723
|
+
}
|
|
139724
|
+
sweep() {
|
|
139725
|
+
if (this.current && Date.now() > this.current.expiresAt) {
|
|
139726
|
+
this.current = null;
|
|
139727
|
+
}
|
|
139728
|
+
}
|
|
139729
|
+
dispose() {
|
|
139730
|
+
if (this.sweepTimer) clearInterval(this.sweepTimer);
|
|
139731
|
+
this.sweepTimer = null;
|
|
139732
|
+
this.current = null;
|
|
139733
|
+
}
|
|
139734
|
+
};
|
|
139735
|
+
|
|
139736
|
+
// src/process-discovery.ts
|
|
139737
|
+
var import_child_process2 = require("child_process");
|
|
139738
|
+
var import_os9 = require("os");
|
|
139739
|
+
var import_path19 = require("path");
|
|
139740
|
+
async function discoverClaudeProcesses() {
|
|
139741
|
+
if ((0, import_os9.platform)() === "win32") return discoverWindows();
|
|
139742
|
+
return discoverUnix();
|
|
139743
|
+
}
|
|
139744
|
+
async function discoverUnix() {
|
|
139745
|
+
const pids = await getPidsUnix();
|
|
139746
|
+
const results = await Promise.all(
|
|
139747
|
+
pids.map(async (pid) => {
|
|
139748
|
+
try {
|
|
139749
|
+
const [cwd, args, startedAt] = await Promise.all([
|
|
139750
|
+
getProcessCwdUnix(pid),
|
|
139751
|
+
getProcessArgsUnix(pid),
|
|
139752
|
+
getProcessStartTimeUnix(pid)
|
|
139753
|
+
]);
|
|
139754
|
+
const conversationId = extractResumeId(args);
|
|
139755
|
+
return {
|
|
139756
|
+
pid,
|
|
139757
|
+
projectPath: cwd,
|
|
139758
|
+
projectName: (0, import_path19.basename)(cwd),
|
|
139759
|
+
branch: await readGitBranch2(cwd),
|
|
139760
|
+
conversationId,
|
|
139761
|
+
startedAt
|
|
139762
|
+
};
|
|
139763
|
+
} catch {
|
|
139764
|
+
return null;
|
|
139765
|
+
}
|
|
139766
|
+
})
|
|
139767
|
+
);
|
|
139768
|
+
return results.filter((r) => r !== null);
|
|
139769
|
+
}
|
|
139770
|
+
async function discoverWindows() {
|
|
139771
|
+
const pids = await getPidsWindows();
|
|
139772
|
+
const results = await Promise.all(
|
|
139773
|
+
pids.map(async (pid) => {
|
|
139774
|
+
try {
|
|
139775
|
+
const info = await getProcessInfoWindows(pid);
|
|
139776
|
+
if (!info) return null;
|
|
139777
|
+
return {
|
|
139778
|
+
pid,
|
|
139779
|
+
projectPath: info.cwd,
|
|
139780
|
+
projectName: (0, import_path19.basename)(info.cwd),
|
|
139781
|
+
branch: await readGitBranch2(info.cwd),
|
|
139782
|
+
conversationId: extractResumeId(info.args),
|
|
139783
|
+
startedAt: info.startedAt
|
|
139784
|
+
};
|
|
139785
|
+
} catch {
|
|
139786
|
+
return null;
|
|
139787
|
+
}
|
|
139788
|
+
})
|
|
139789
|
+
);
|
|
139790
|
+
return results.filter((r) => r !== null);
|
|
139791
|
+
}
|
|
139792
|
+
function run(cmd, args, opts = {}) {
|
|
139793
|
+
return new Promise((resolve6, reject) => {
|
|
139794
|
+
(0, import_child_process2.execFile)(
|
|
139795
|
+
cmd,
|
|
139796
|
+
args,
|
|
139797
|
+
{ windowsHide: isWindows2, encoding: "utf-8", timeout: opts.timeout ?? 5e3, cwd: opts.cwd },
|
|
139798
|
+
(err, stdout2) => {
|
|
139799
|
+
if (err) reject(err);
|
|
139800
|
+
else resolve6(stdout2);
|
|
139801
|
+
}
|
|
139802
|
+
);
|
|
139803
|
+
});
|
|
139804
|
+
}
|
|
139805
|
+
async function getPidsUnix() {
|
|
139806
|
+
try {
|
|
139807
|
+
const output = await run("pgrep", ["-x", "claude"]);
|
|
139808
|
+
return output.trim().split("\n").filter(Boolean).map((s3) => Number.parseInt(s3, 10));
|
|
139809
|
+
} catch {
|
|
139810
|
+
return [];
|
|
139811
|
+
}
|
|
139812
|
+
}
|
|
139813
|
+
async function getProcessCwdUnix(pid) {
|
|
139814
|
+
const output = await run("lsof", ["-p", String(pid), "-a", "-d", "cwd", "-Fn"]);
|
|
139815
|
+
const match2 = output.match(/n(.+)/);
|
|
139816
|
+
return match2?.[1] ?? "";
|
|
139817
|
+
}
|
|
139818
|
+
async function getProcessArgsUnix(pid) {
|
|
139819
|
+
return (await run("ps", ["-p", String(pid), "-o", "args="])).trim();
|
|
139820
|
+
}
|
|
139821
|
+
async function getProcessStartTimeUnix(pid) {
|
|
139822
|
+
const raw2 = (await run("ps", ["-p", String(pid), "-o", "lstart="])).trim();
|
|
139823
|
+
const d = new Date(raw2);
|
|
139824
|
+
return Number.isNaN(d.getTime()) ? /* @__PURE__ */ new Date() : d;
|
|
139825
|
+
}
|
|
139826
|
+
async function getPidsWindows() {
|
|
139827
|
+
try {
|
|
139828
|
+
const output = await run("tasklist", ["/FI", "IMAGENAME eq claude.exe", "/FO", "CSV", "/NH"]);
|
|
139829
|
+
return output.trim().split("\n").filter(Boolean).map((line) => {
|
|
139830
|
+
const parts = line.split(",");
|
|
139831
|
+
return Number.parseInt(parts[1]?.replace(/"/g, "") ?? "0", 10);
|
|
139832
|
+
}).filter((pid) => pid > 0);
|
|
139833
|
+
} catch {
|
|
139834
|
+
return [];
|
|
139835
|
+
}
|
|
139836
|
+
}
|
|
139837
|
+
async function getProcessInfoWindows(pid) {
|
|
139838
|
+
try {
|
|
139839
|
+
const output = await run("wmic", [
|
|
139840
|
+
"process",
|
|
139841
|
+
"where",
|
|
139842
|
+
`ProcessId=${pid}`,
|
|
139843
|
+
"get",
|
|
139844
|
+
"CommandLine,CreationDate,ExecutablePath",
|
|
139845
|
+
"/FORMAT:CSV"
|
|
139846
|
+
]);
|
|
139847
|
+
const lines = output.trim().split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
139848
|
+
if (lines.length < 2) return null;
|
|
139849
|
+
const parts = lines[1].split(",");
|
|
139850
|
+
const args = parts[1] ?? "";
|
|
139851
|
+
const creationDate = parts[2] ?? "";
|
|
139852
|
+
const year = creationDate.slice(0, 4);
|
|
139853
|
+
const month = creationDate.slice(4, 6);
|
|
139854
|
+
const day = creationDate.slice(6, 8);
|
|
139855
|
+
const hour = creationDate.slice(8, 10);
|
|
139856
|
+
const min = creationDate.slice(10, 12);
|
|
139857
|
+
const sec = creationDate.slice(12, 14);
|
|
139858
|
+
const startedAt = /* @__PURE__ */ new Date(`${year}-${month}-${day}T${hour}:${min}:${sec}`);
|
|
139859
|
+
if (Number.isNaN(startedAt.getTime())) return null;
|
|
139860
|
+
const exePath = parts[3] ?? "";
|
|
139861
|
+
const cwd = exePath ? (0, import_path19.dirname)(exePath) : "";
|
|
139862
|
+
return { cwd, args, startedAt };
|
|
139863
|
+
} catch {
|
|
139864
|
+
return null;
|
|
139865
|
+
}
|
|
139866
|
+
}
|
|
139867
|
+
function extractResumeId(args) {
|
|
139868
|
+
const match2 = args.match(/--resume\s+(\S+)/);
|
|
139869
|
+
return match2?.[1] ?? null;
|
|
139870
|
+
}
|
|
139871
|
+
async function readGitBranch2(dir) {
|
|
139872
|
+
try {
|
|
139873
|
+
return (await run("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: dir, timeout: 3e3 })).trim();
|
|
139874
|
+
} catch {
|
|
139875
|
+
return "";
|
|
139876
|
+
}
|
|
139877
|
+
}
|
|
139878
|
+
|
|
139199
139879
|
// src/seal.ts
|
|
139200
139880
|
var import_tweetnacl = __toESM(require_nacl_fast(), 1);
|
|
139201
139881
|
var import_tweetnacl_util = __toESM(require_nacl_util(), 1);
|
|
@@ -139384,10 +140064,10 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
|
|
|
139384
140064
|
}
|
|
139385
140065
|
async _formatEntry(dirent, path2) {
|
|
139386
140066
|
let entry;
|
|
139387
|
-
const
|
|
140067
|
+
const basename11 = this._isDirent ? dirent.name : dirent;
|
|
139388
140068
|
try {
|
|
139389
|
-
const fullPath = (0, import_node_path9.resolve)((0, import_node_path9.join)(path2,
|
|
139390
|
-
entry = { path: (0, import_node_path9.relative)(this._root, fullPath), fullPath, basename:
|
|
140069
|
+
const fullPath = (0, import_node_path9.resolve)((0, import_node_path9.join)(path2, basename11));
|
|
140070
|
+
entry = { path: (0, import_node_path9.relative)(this._root, fullPath), fullPath, basename: basename11 };
|
|
139391
140071
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
139392
140072
|
} catch (err) {
|
|
139393
140073
|
this._onError(err);
|
|
@@ -139928,9 +140608,9 @@ var NodeFsHandler2 = class {
|
|
|
139928
140608
|
_watchWithNodeFs(path2, listener) {
|
|
139929
140609
|
const opts = this.fsw.options;
|
|
139930
140610
|
const directory = sp.dirname(path2);
|
|
139931
|
-
const
|
|
140611
|
+
const basename11 = sp.basename(path2);
|
|
139932
140612
|
const parent = this.fsw._getWatchedDir(directory);
|
|
139933
|
-
parent.add(
|
|
140613
|
+
parent.add(basename11);
|
|
139934
140614
|
const absolutePath = sp.resolve(path2);
|
|
139935
140615
|
const options = {
|
|
139936
140616
|
persistent: opts.persistent
|
|
@@ -139940,7 +140620,7 @@ var NodeFsHandler2 = class {
|
|
|
139940
140620
|
let closer;
|
|
139941
140621
|
if (opts.usePolling) {
|
|
139942
140622
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
139943
|
-
options.interval = enableBin && isBinaryPath2(
|
|
140623
|
+
options.interval = enableBin && isBinaryPath2(basename11) ? opts.binaryInterval : opts.interval;
|
|
139944
140624
|
closer = setFsWatchFileListener2(path2, absolutePath, options, {
|
|
139945
140625
|
listener,
|
|
139946
140626
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -139963,10 +140643,10 @@ var NodeFsHandler2 = class {
|
|
|
139963
140643
|
return;
|
|
139964
140644
|
}
|
|
139965
140645
|
const dirname17 = sp.dirname(file2);
|
|
139966
|
-
const
|
|
140646
|
+
const basename11 = sp.basename(file2);
|
|
139967
140647
|
const parent = this.fsw._getWatchedDir(dirname17);
|
|
139968
140648
|
let prevStats = stats;
|
|
139969
|
-
if (parent.has(
|
|
140649
|
+
if (parent.has(basename11))
|
|
139970
140650
|
return;
|
|
139971
140651
|
const listener = async (path2, newStats) => {
|
|
139972
140652
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH2, file2, 5))
|
|
@@ -139991,9 +140671,9 @@ var NodeFsHandler2 = class {
|
|
|
139991
140671
|
prevStats = newStats2;
|
|
139992
140672
|
}
|
|
139993
140673
|
} catch (error51) {
|
|
139994
|
-
this.fsw._remove(dirname17,
|
|
140674
|
+
this.fsw._remove(dirname17, basename11);
|
|
139995
140675
|
}
|
|
139996
|
-
} else if (parent.has(
|
|
140676
|
+
} else if (parent.has(basename11)) {
|
|
139997
140677
|
const at2 = newStats.atimeMs;
|
|
139998
140678
|
const mt2 = newStats.mtimeMs;
|
|
139999
140679
|
if (!at2 || at2 <= mt2 || mt2 !== prevStats.mtimeMs) {
|
|
@@ -140950,7 +141630,7 @@ function watch2(paths, options = {}) {
|
|
|
140950
141630
|
var chokidar_default = { watch: watch2, FSWatcher: FSWatcher2 };
|
|
140951
141631
|
|
|
140952
141632
|
// src/services/conversations/conversationWatcher.ts
|
|
140953
|
-
var
|
|
141633
|
+
var import_fs22 = require("fs");
|
|
140954
141634
|
var import_promises12 = require("fs/promises");
|
|
140955
141635
|
var ConversationWatcher = class {
|
|
140956
141636
|
files = /* @__PURE__ */ new Map();
|
|
@@ -140971,7 +141651,7 @@ var ConversationWatcher = class {
|
|
|
140971
141651
|
if (this.files.has(filePath)) return;
|
|
140972
141652
|
let offset;
|
|
140973
141653
|
try {
|
|
140974
|
-
offset = (0,
|
|
141654
|
+
offset = (0, import_fs22.statSync)(filePath).size;
|
|
140975
141655
|
} catch {
|
|
140976
141656
|
offset = 0;
|
|
140977
141657
|
}
|
|
@@ -141080,14 +141760,14 @@ var ConversationWatcher = class {
|
|
|
141080
141760
|
};
|
|
141081
141761
|
|
|
141082
141762
|
// src/services/conversations/pruneAgentConversations.ts
|
|
141083
|
-
var
|
|
141763
|
+
var import_fs23 = require("fs");
|
|
141084
141764
|
function pruneAgentConversations(cache) {
|
|
141085
141765
|
const db = cache.getDatabase();
|
|
141086
141766
|
const rows = db.prepare("SELECT id, file_path FROM conversation_meta").all();
|
|
141087
141767
|
let pruned = 0;
|
|
141088
141768
|
let missing = 0;
|
|
141089
141769
|
for (const row of rows) {
|
|
141090
|
-
if (!(0,
|
|
141770
|
+
if (!(0, import_fs23.existsSync)(row.file_path)) {
|
|
141091
141771
|
missing += 1;
|
|
141092
141772
|
continue;
|
|
141093
141773
|
}
|
|
@@ -141396,6 +142076,7 @@ function managedToResponse(s3, ptyAttached) {
|
|
|
141396
142076
|
return {
|
|
141397
142077
|
id: s3.id,
|
|
141398
142078
|
conversationId: s3.id,
|
|
142079
|
+
provider: s3.provider ?? CLAUDE_CODE_PROVIDER2,
|
|
141399
142080
|
status: s3.status,
|
|
141400
142081
|
projectPath: s3.projectPath,
|
|
141401
142082
|
projectName: s3.projectName,
|
|
@@ -141421,13 +142102,15 @@ function managedToResponse(s3, ptyAttached) {
|
|
|
141421
142102
|
...s3.failureReason != null && { failureReason: s3.failureReason },
|
|
141422
142103
|
...s3.resumedFromConversationId != null && {
|
|
141423
142104
|
resumedFromConversationId: s3.resumedFromConversationId
|
|
141424
|
-
}
|
|
142105
|
+
},
|
|
142106
|
+
...s3.boundConversationId != null && { boundConversationId: s3.boundConversationId }
|
|
141425
142107
|
};
|
|
141426
142108
|
}
|
|
141427
142109
|
function discoveredToResponse(d, conversationId) {
|
|
141428
142110
|
return {
|
|
141429
142111
|
id: conversationId,
|
|
141430
142112
|
conversationId,
|
|
142113
|
+
provider: CLAUDE_CODE_PROVIDER2,
|
|
141431
142114
|
status: "idle",
|
|
141432
142115
|
projectPath: d.projectPath,
|
|
141433
142116
|
projectName: d.projectName,
|
|
@@ -141443,10 +142126,10 @@ function discoveredToResponse(d, conversationId) {
|
|
|
141443
142126
|
}
|
|
141444
142127
|
|
|
141445
142128
|
// src/uploads.ts
|
|
141446
|
-
var
|
|
142129
|
+
var import_crypto8 = require("crypto");
|
|
141447
142130
|
var import_promises13 = require("fs/promises");
|
|
141448
142131
|
var import_heic_convert = __toESM(require_heic_convert(), 1);
|
|
141449
|
-
var
|
|
142132
|
+
var import_path20 = require("path");
|
|
141450
142133
|
var UPLOAD_DIR_NAME = ".threadbase-uploads";
|
|
141451
142134
|
var MAX_BYTES = 25 * 1024 * 1024;
|
|
141452
142135
|
var HEIC_MIMES = /* @__PURE__ */ new Set(["image/heic", "image/heif"]);
|
|
@@ -141477,11 +142160,11 @@ async function saveUploadFile(input) {
|
|
|
141477
142160
|
mimeType = "image/jpeg";
|
|
141478
142161
|
originalName = originalName.replace(/\.(heic|heif)$/i, ".jpg");
|
|
141479
142162
|
}
|
|
141480
|
-
const id = `up_${(0,
|
|
142163
|
+
const id = `up_${(0, import_crypto8.randomBytes)(8).toString("hex")}`;
|
|
141481
142164
|
const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
|
|
141482
|
-
const dir = (0,
|
|
142165
|
+
const dir = (0, import_path20.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
|
|
141483
142166
|
await (0, import_promises13.mkdir)(dir, { recursive: true });
|
|
141484
|
-
const filePath = (0,
|
|
142167
|
+
const filePath = (0, import_path20.join)(dir, `${Date.now()}-${id}-${safeName}`);
|
|
141485
142168
|
await (0, import_promises13.writeFile)(filePath, buffer);
|
|
141486
142169
|
return {
|
|
141487
142170
|
id,
|
|
@@ -141994,10 +142677,10 @@ var StreamerServer = class {
|
|
|
141994
142677
|
this.verbose = config2.verbose ?? false;
|
|
141995
142678
|
this.disableDb = config2.disableDb ?? false;
|
|
141996
142679
|
this.scanProfiles = config2.scanProfiles;
|
|
141997
|
-
this.codexRoots = config2.codexRoots ?? [(0,
|
|
142680
|
+
this.codexRoots = config2.codexRoots ?? [(0, import_path21.join)((0, import_os10.homedir)(), ".codex", "sessions")];
|
|
141998
142681
|
this.ptyGracePeriodMs = config2.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
|
|
141999
142682
|
this.defaultSystemPrompt = config2.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
142000
|
-
this.cacheDir = config2.cacheDir ?? loadCacheDir() ?? (0,
|
|
142683
|
+
this.cacheDir = config2.cacheDir ?? loadCacheDir() ?? (0, import_path21.join)((0, import_os10.homedir)(), ".threadbase", "cache");
|
|
142001
142684
|
this.tailSize = config2.tailSize ?? loadTailSize() ?? 10;
|
|
142002
142685
|
this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config2.directoryScanDebounceMs ?? 1e3;
|
|
142003
142686
|
this.markScannerStaleDebounced = debounce(() => {
|
|
@@ -142081,7 +142764,7 @@ var StreamerServer = class {
|
|
|
142081
142764
|
});
|
|
142082
142765
|
}
|
|
142083
142766
|
});
|
|
142084
|
-
this.ptyManager = new
|
|
142767
|
+
this.ptyManager = new LiveSessionManager({
|
|
142085
142768
|
logger: getLogger("pty"),
|
|
142086
142769
|
onOutput: (sessionId, data) => {
|
|
142087
142770
|
this.wsHub.broadcast({ type: "terminal_output", sessionId, data });
|
|
@@ -142162,7 +142845,7 @@ var StreamerServer = class {
|
|
|
142162
142845
|
temporalClient,
|
|
142163
142846
|
taskQueue: agentConfig.temporal.taskQueue
|
|
142164
142847
|
});
|
|
142165
|
-
const conversationsBaseDir = agentConfig.conversationsDir || (0,
|
|
142848
|
+
const conversationsBaseDir = agentConfig.conversationsDir || (0, import_path21.join)((0, import_path21.dirname)(this.cacheDir), "conversations");
|
|
142166
142849
|
conversationWriter = createConversationWriter({
|
|
142167
142850
|
baseDir: conversationsBaseDir
|
|
142168
142851
|
});
|
|
@@ -142384,7 +143067,7 @@ var StreamerServer = class {
|
|
|
142384
143067
|
});
|
|
142385
143068
|
try {
|
|
142386
143069
|
this.cache = ConversationCache.open(
|
|
142387
|
-
(0,
|
|
143070
|
+
(0, import_path21.join)(this.cacheDir, "cache.db"),
|
|
142388
143071
|
this.tailSize,
|
|
142389
143072
|
void 0,
|
|
142390
143073
|
{
|
|
@@ -142410,18 +143093,19 @@ var StreamerServer = class {
|
|
|
142410
143093
|
if (this.scanProfiles && this.scanProfiles.length > 0) {
|
|
142411
143094
|
for (const profile of this.scanProfiles) {
|
|
142412
143095
|
if (profile.enabled) {
|
|
142413
|
-
this.fileWatcher.watchDirectory((0,
|
|
143096
|
+
this.fileWatcher.watchDirectory((0, import_path21.join)(profile.configDir, "projects"));
|
|
142414
143097
|
}
|
|
142415
143098
|
}
|
|
142416
143099
|
} else {
|
|
142417
|
-
this.fileWatcher.watchDirectory((0,
|
|
143100
|
+
this.fileWatcher.watchDirectory((0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects"));
|
|
142418
143101
|
}
|
|
142419
143102
|
} catch (err) {
|
|
142420
143103
|
const message = err instanceof Error ? err.message : String(err);
|
|
142421
|
-
|
|
142422
|
-
|
|
142423
|
-
|
|
142424
|
-
|
|
143104
|
+
const abiMismatch = message.includes("NODE_MODULE_VERSION") || message.includes("was compiled against a different Node.js version");
|
|
143105
|
+
this.log.error(
|
|
143106
|
+
`ConversationCache failed to open \u2014 running WITHOUT cache; /api/conversations, /api/conversations/count and /project-chats will 500.` + (abiMismatch ? ` Fix: npm rebuild better-sqlite3` : "") + ` (${message})`,
|
|
143107
|
+
{ error: message, abiMismatch, event: "cache.open_failed" }
|
|
143108
|
+
);
|
|
142425
143109
|
}
|
|
142426
143110
|
const warmupScanner = new ConversationScanner();
|
|
142427
143111
|
this.allScanners.add(warmupScanner);
|
|
@@ -142908,17 +143592,17 @@ var StreamerServer = class {
|
|
|
142908
143592
|
return this.getScanner();
|
|
142909
143593
|
}
|
|
142910
143594
|
findJsonlPath(uuid3) {
|
|
142911
|
-
const projectsDir = (0,
|
|
142912
|
-
if (!(0,
|
|
143595
|
+
const projectsDir = (0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects");
|
|
143596
|
+
if (!(0, import_fs24.existsSync)(projectsDir)) return null;
|
|
142913
143597
|
const filename = `${uuid3}.jsonl`;
|
|
142914
|
-
for (const dir of (0,
|
|
142915
|
-
const fp = (0,
|
|
142916
|
-
if ((0,
|
|
142917
|
-
const projectDir = (0,
|
|
143598
|
+
for (const dir of (0, import_fs24.readdirSync)(projectsDir)) {
|
|
143599
|
+
const fp = (0, import_path21.join)(projectsDir, dir, filename);
|
|
143600
|
+
if ((0, import_fs24.existsSync)(fp)) return fp;
|
|
143601
|
+
const projectDir = (0, import_path21.join)(projectsDir, dir);
|
|
142918
143602
|
try {
|
|
142919
|
-
for (const sub of (0,
|
|
142920
|
-
const subagentPath = (0,
|
|
142921
|
-
if ((0,
|
|
143603
|
+
for (const sub of (0, import_fs24.readdirSync)(projectDir)) {
|
|
143604
|
+
const subagentPath = (0, import_path21.join)(projectDir, sub, "subagents", filename);
|
|
143605
|
+
if ((0, import_fs24.existsSync)(subagentPath)) return subagentPath;
|
|
142922
143606
|
}
|
|
142923
143607
|
} catch {
|
|
142924
143608
|
}
|
|
@@ -142927,7 +143611,7 @@ var StreamerServer = class {
|
|
|
142927
143611
|
}
|
|
142928
143612
|
async readCwdFromJsonl(filePath) {
|
|
142929
143613
|
return new Promise((resolve6) => {
|
|
142930
|
-
const rl = (0, import_readline4.createInterface)({ input: (0,
|
|
143614
|
+
const rl = (0, import_readline4.createInterface)({ input: (0, import_fs24.createReadStream)(filePath), crlfDelay: Infinity });
|
|
142931
143615
|
let found = false;
|
|
142932
143616
|
rl.on("line", (line) => {
|
|
142933
143617
|
if (found) return;
|
|
@@ -142984,7 +143668,7 @@ var StreamerServer = class {
|
|
|
142984
143668
|
if (!conv.filePath) return false;
|
|
142985
143669
|
let mtimeMs = null;
|
|
142986
143670
|
try {
|
|
142987
|
-
mtimeMs = (0,
|
|
143671
|
+
mtimeMs = (0, import_fs24.statSync)(conv.filePath).mtimeMs;
|
|
142988
143672
|
} catch {
|
|
142989
143673
|
return false;
|
|
142990
143674
|
}
|
|
@@ -143227,7 +143911,7 @@ var StreamerServer = class {
|
|
|
143227
143911
|
handleGetSession(sessionId, res) {
|
|
143228
143912
|
const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());
|
|
143229
143913
|
if (session) {
|
|
143230
|
-
if (!(0,
|
|
143914
|
+
if (!(0, import_fs24.existsSync)(session.projectPath)) {
|
|
143231
143915
|
session.failureReason = `Project directory not found: ${session.projectPath}`;
|
|
143232
143916
|
}
|
|
143233
143917
|
json2(res, 200, session);
|
|
@@ -143267,7 +143951,10 @@ var StreamerServer = class {
|
|
|
143267
143951
|
json2(res, 400, { error: "Could not determine project path" });
|
|
143268
143952
|
return;
|
|
143269
143953
|
}
|
|
143954
|
+
const cachedConvMeta = this.cache?.getMetaById(sessionId);
|
|
143955
|
+
const provider = conv?.provider ?? cachedConvMeta?.provider ?? CLAUDE_CODE_PROVIDER2;
|
|
143270
143956
|
const session = await this.ptyManager.start(sessionId, {
|
|
143957
|
+
provider,
|
|
143271
143958
|
projectPath,
|
|
143272
143959
|
projectName: body.projectName,
|
|
143273
143960
|
branch: body.branch
|
|
@@ -143617,7 +144304,7 @@ var StreamerServer = class {
|
|
|
143617
144304
|
sessionStore: this.sessionStore,
|
|
143618
144305
|
// biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
|
|
143619
144306
|
agentClient: this.agentClient,
|
|
143620
|
-
conversationsDir: this.cacheDir ? (0,
|
|
144307
|
+
conversationsDir: this.cacheDir ? (0, import_path21.join)((0, import_path21.dirname)(this.cacheDir), "conversations") : "",
|
|
143621
144308
|
agentConfig: this.agentConfig
|
|
143622
144309
|
});
|
|
143623
144310
|
json2(res, result.status, result.body);
|
|
@@ -143626,6 +144313,13 @@ var StreamerServer = class {
|
|
|
143626
144313
|
}
|
|
143627
144314
|
return;
|
|
143628
144315
|
}
|
|
144316
|
+
const body = await readBody(req);
|
|
144317
|
+
const { path: relativePath, provider: requestedProvider, systemPrompt: clientPrompt } = body;
|
|
144318
|
+
if (requestedProvider !== void 0 && !isProviderName(requestedProvider)) {
|
|
144319
|
+
json2(res, 400, { error: "Invalid provider" });
|
|
144320
|
+
return;
|
|
144321
|
+
}
|
|
144322
|
+
const provider = requestedProvider ?? CLAUDE_CODE_PROVIDER2;
|
|
143629
144323
|
if (!this.browseRoot) {
|
|
143630
144324
|
json2(res, 403, {
|
|
143631
144325
|
error: "File browsing not configured. Set browseRoot on the server.",
|
|
@@ -143633,8 +144327,6 @@ var StreamerServer = class {
|
|
|
143633
144327
|
});
|
|
143634
144328
|
return;
|
|
143635
144329
|
}
|
|
143636
|
-
const body = await readBody(req);
|
|
143637
|
-
const { path: relativePath, systemPrompt: clientPrompt } = body;
|
|
143638
144330
|
if (typeof relativePath !== "string") {
|
|
143639
144331
|
json2(res, 400, { error: "Missing path field" });
|
|
143640
144332
|
return;
|
|
@@ -143655,21 +144347,27 @@ var StreamerServer = class {
|
|
|
143655
144347
|
].filter(Boolean);
|
|
143656
144348
|
try {
|
|
143657
144349
|
const session = await this.ptyManager.startFresh({
|
|
144350
|
+
provider,
|
|
143658
144351
|
projectPath: resolvedPath,
|
|
143659
144352
|
projectName: body.projectName,
|
|
143660
144353
|
systemPrompt: systemPromptParts.join("\n")
|
|
143661
144354
|
});
|
|
143662
144355
|
this.sessionStore.addManaged(session);
|
|
143663
144356
|
json2(res, 202, { id: session.id, status: "pending" });
|
|
143664
|
-
|
|
144357
|
+
if (provider === CODEX_CLI_PROVIDER2) {
|
|
144358
|
+
this.watchForCodexRollout(session.id, resolvedPath);
|
|
144359
|
+
} else {
|
|
144360
|
+
this.watchForJsonl(session.id, resolvedPath);
|
|
144361
|
+
}
|
|
143665
144362
|
this.broadcastOrUnicastSessionList(req);
|
|
143666
144363
|
} catch (err) {
|
|
143667
144364
|
const message = err instanceof Error ? err.message : "Failed to start session";
|
|
144365
|
+
const statusCode = typeof err.statusCode === "number" ? err.statusCode : 500;
|
|
143668
144366
|
this.log.error(`[start] failed to start session: ${message}`, {
|
|
143669
144367
|
event: "session.start_failed",
|
|
143670
144368
|
error: message
|
|
143671
144369
|
});
|
|
143672
|
-
json2(res,
|
|
144370
|
+
json2(res, statusCode, { error: message });
|
|
143673
144371
|
}
|
|
143674
144372
|
}
|
|
143675
144373
|
// ─── Project linking ─────────────────────────────────────────────
|
|
@@ -143722,9 +144420,9 @@ var StreamerServer = class {
|
|
|
143722
144420
|
// was passed to Claude via --session-id so the filename matches from the start.
|
|
143723
144421
|
watchForJsonl(sessionId, projectPath) {
|
|
143724
144422
|
const encoded = projectPath.replace(/[/\\:.]/g, "-");
|
|
143725
|
-
const projectsDir = (0,
|
|
144423
|
+
const projectsDir = (0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects", encoded);
|
|
143726
144424
|
const expectedFile = `${sessionId}.jsonl`;
|
|
143727
|
-
const filePath = (0,
|
|
144425
|
+
const filePath = (0, import_path21.join)(projectsDir, expectedFile);
|
|
143728
144426
|
const deadline = Date.now() + 12e4;
|
|
143729
144427
|
let watcher = null;
|
|
143730
144428
|
const cleanup = () => {
|
|
@@ -143742,12 +144440,12 @@ var StreamerServer = class {
|
|
|
143742
144440
|
cleanup();
|
|
143743
144441
|
return;
|
|
143744
144442
|
}
|
|
143745
|
-
let resolvedFilePath = (0,
|
|
143746
|
-
if (!resolvedFilePath && (0,
|
|
144443
|
+
let resolvedFilePath = (0, import_fs24.existsSync)(filePath) ? filePath : null;
|
|
144444
|
+
if (!resolvedFilePath && (0, import_fs24.existsSync)(projectsDir)) {
|
|
143747
144445
|
try {
|
|
143748
144446
|
const now = Date.now();
|
|
143749
|
-
const recent = (0,
|
|
143750
|
-
if (recent) resolvedFilePath = (0,
|
|
144447
|
+
const recent = (0, import_fs24.readdirSync)(projectsDir).filter((f2) => f2.endsWith(".jsonl")).map((f2) => ({ f: f2, mtime: (0, import_fs24.statSync)((0, import_path21.join)(projectsDir, f2)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).sort((a, b2) => b2.mtime - a.mtime)[0];
|
|
144448
|
+
if (recent) resolvedFilePath = (0, import_path21.join)(projectsDir, recent.f);
|
|
143751
144449
|
} catch {
|
|
143752
144450
|
}
|
|
143753
144451
|
}
|
|
@@ -143756,7 +144454,7 @@ var StreamerServer = class {
|
|
|
143756
144454
|
this.sessionFileMap.set(sessionId, resolvedFilePath);
|
|
143757
144455
|
this.fileWatcher.watch(resolvedFilePath);
|
|
143758
144456
|
try {
|
|
143759
|
-
const existing = (0,
|
|
144457
|
+
const existing = (0, import_fs24.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
|
|
143760
144458
|
if (existing.length > 0) {
|
|
143761
144459
|
this.wsHub.broadcast({ type: "conversation_events", sessionId, lines: existing });
|
|
143762
144460
|
for (const line of existing) {
|
|
@@ -143782,11 +144480,126 @@ var StreamerServer = class {
|
|
|
143782
144480
|
if (this.sessionFileMap.has(sessionId)) return;
|
|
143783
144481
|
try {
|
|
143784
144482
|
require("fs").mkdirSync(projectsDir, { recursive: true });
|
|
143785
|
-
watcher = (0,
|
|
144483
|
+
watcher = (0, import_fs24.watch)(projectsDir, tryWire);
|
|
143786
144484
|
watcher.on("error", cleanup);
|
|
143787
144485
|
} catch {
|
|
143788
144486
|
}
|
|
143789
144487
|
}
|
|
144488
|
+
// Codex-equivalent of watchForJsonl(). Differs because Codex has no
|
|
144489
|
+
// filename-encoded session id (it assigns its own persisted id) and its
|
|
144490
|
+
// rollout files live under a date-nested directory
|
|
144491
|
+
// (~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl) that Codex creates
|
|
144492
|
+
// itself — it may not exist yet when this function is first called, so we
|
|
144493
|
+
// poll rather than fs.watch a not-yet-existent directory. Per Phase 0
|
|
144494
|
+
// findings, the rollout file appears within ~1s of process spawn (after
|
|
144495
|
+
// any directory-trust gate is cleared), well before any user input.
|
|
144496
|
+
watchForCodexRollout(sessionId, projectPath) {
|
|
144497
|
+
const deadline = Date.now() + 12e4;
|
|
144498
|
+
const now = /* @__PURE__ */ new Date();
|
|
144499
|
+
const dateDir = (0, import_path21.join)(
|
|
144500
|
+
String(now.getFullYear()),
|
|
144501
|
+
String(now.getMonth() + 1).padStart(2, "0"),
|
|
144502
|
+
String(now.getDate()).padStart(2, "0")
|
|
144503
|
+
);
|
|
144504
|
+
const sessionStartedAtMs = (this.sessionStore.getManaged(sessionId)?.startedAt?.getTime() ?? Date.now()) - 5e3;
|
|
144505
|
+
let intervalHandle = null;
|
|
144506
|
+
const cleanup = () => {
|
|
144507
|
+
if (intervalHandle) clearInterval(intervalHandle);
|
|
144508
|
+
intervalHandle = null;
|
|
144509
|
+
};
|
|
144510
|
+
const matchesProjectPath = (candidatePath) => {
|
|
144511
|
+
try {
|
|
144512
|
+
const firstLine = (0, import_fs24.readFileSync)(candidatePath, "utf8").split("\n", 1)[0];
|
|
144513
|
+
if (!firstLine) return null;
|
|
144514
|
+
const parsed = JSON.parse(firstLine);
|
|
144515
|
+
if (parsed?.type !== "session_meta") return null;
|
|
144516
|
+
const payload = parsed.payload ?? {};
|
|
144517
|
+
if (payload.cwd !== projectPath) return null;
|
|
144518
|
+
if (typeof payload.id !== "string") return null;
|
|
144519
|
+
const createdIso = payload.timestamp ?? parsed.timestamp;
|
|
144520
|
+
const createdAtMs = typeof createdIso === "string" ? Date.parse(createdIso) : Number.NaN;
|
|
144521
|
+
if (Number.isNaN(createdAtMs) || createdAtMs < sessionStartedAtMs) return null;
|
|
144522
|
+
return { id: payload.id, createdAtMs };
|
|
144523
|
+
} catch {
|
|
144524
|
+
return null;
|
|
144525
|
+
}
|
|
144526
|
+
};
|
|
144527
|
+
const tryWire = () => {
|
|
144528
|
+
if (!this.ptyManager.hasSession(sessionId)) {
|
|
144529
|
+
cleanup();
|
|
144530
|
+
return;
|
|
144531
|
+
}
|
|
144532
|
+
if (Date.now() > deadline) {
|
|
144533
|
+
cleanup();
|
|
144534
|
+
return;
|
|
144535
|
+
}
|
|
144536
|
+
const boundElsewhere = new Set(
|
|
144537
|
+
this.sessionStore.listManaged().filter((s3) => s3.id !== sessionId && s3.boundConversationId != null).map((s3) => s3.boundConversationId)
|
|
144538
|
+
);
|
|
144539
|
+
for (const root of this.codexRoots) {
|
|
144540
|
+
const sessionsDir = (0, import_path21.join)(root, dateDir);
|
|
144541
|
+
if (!(0, import_fs24.existsSync)(sessionsDir)) continue;
|
|
144542
|
+
let candidateFiles;
|
|
144543
|
+
try {
|
|
144544
|
+
candidateFiles = (0, import_fs24.readdirSync)(sessionsDir).filter((f2) => f2.endsWith(".jsonl"));
|
|
144545
|
+
} catch {
|
|
144546
|
+
continue;
|
|
144547
|
+
}
|
|
144548
|
+
const nowMs = Date.now();
|
|
144549
|
+
const recentCandidates = candidateFiles.map((f2) => ({ f: f2, mtime: (0, import_fs24.statSync)((0, import_path21.join)(sessionsDir, f2)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b2) => b2.mtime - a.mtime);
|
|
144550
|
+
for (const { f: f2 } of recentCandidates) {
|
|
144551
|
+
const candidatePath = (0, import_path21.join)(sessionsDir, f2);
|
|
144552
|
+
const match2 = matchesProjectPath(candidatePath);
|
|
144553
|
+
if (!match2) continue;
|
|
144554
|
+
if (boundElsewhere.has(match2.id)) continue;
|
|
144555
|
+
const codexSessionId = match2.id;
|
|
144556
|
+
cleanup();
|
|
144557
|
+
this.sessionStore.updateManaged(sessionId, { boundConversationId: codexSessionId });
|
|
144558
|
+
this.sessionFileMap.set(sessionId, candidatePath);
|
|
144559
|
+
this.fileWatcher.watch(candidatePath);
|
|
144560
|
+
try {
|
|
144561
|
+
const existing = (0, import_fs24.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
|
|
144562
|
+
if (existing.length > 0) {
|
|
144563
|
+
this.wsHub.broadcast({ type: "conversation_events", sessionId, lines: existing });
|
|
144564
|
+
for (const line of existing) {
|
|
144565
|
+
this.wsHub.broadcast({ type: "conversation_event", sessionId, line });
|
|
144566
|
+
}
|
|
144567
|
+
}
|
|
144568
|
+
} catch {
|
|
144569
|
+
}
|
|
144570
|
+
if (this.scannerReady) {
|
|
144571
|
+
this.scannerStale = true;
|
|
144572
|
+
} else {
|
|
144573
|
+
this.scanner = null;
|
|
144574
|
+
}
|
|
144575
|
+
this.linkSessionToProject(sessionId, projectPath, candidatePath);
|
|
144576
|
+
this.cache?.markAsStreamer(sessionId);
|
|
144577
|
+
const resp = this.sessionStore.get(sessionId, this.ptyAttachedIds());
|
|
144578
|
+
if (resp) {
|
|
144579
|
+
this.wsHub.broadcast({ type: "session_update", session: resp });
|
|
144580
|
+
}
|
|
144581
|
+
this.log.info(
|
|
144582
|
+
`[startFresh] bound Codex rollout for ${sessionId}`,
|
|
144583
|
+
{
|
|
144584
|
+
event: "session.codex_rollout_bound",
|
|
144585
|
+
sessionId,
|
|
144586
|
+
boundConversationId: codexSessionId,
|
|
144587
|
+
filePath: candidatePath
|
|
144588
|
+
},
|
|
144589
|
+
"pino"
|
|
144590
|
+
);
|
|
144591
|
+
return;
|
|
144592
|
+
}
|
|
144593
|
+
}
|
|
144594
|
+
};
|
|
144595
|
+
tryWire();
|
|
144596
|
+
if (!intervalHandle && Date.now() <= deadline) {
|
|
144597
|
+
const alreadyBound = this.sessionStore.getManaged(sessionId)?.boundConversationId != null;
|
|
144598
|
+
if (!alreadyBound) {
|
|
144599
|
+
intervalHandle = setInterval(tryWire, 250);
|
|
144600
|
+
}
|
|
144601
|
+
}
|
|
144602
|
+
}
|
|
143790
144603
|
async handleBrowse(url2, res) {
|
|
143791
144604
|
if (!this.browseRoot) {
|
|
143792
144605
|
json2(res, 403, {
|
|
@@ -143870,7 +144683,7 @@ var StreamerServer = class {
|
|
|
143870
144683
|
};
|
|
143871
144684
|
function classifyResumability(cwd) {
|
|
143872
144685
|
if (!cwd) return { resumable: true };
|
|
143873
|
-
if ((0,
|
|
144686
|
+
if ((0, import_fs24.existsSync)(cwd)) return { resumable: true };
|
|
143874
144687
|
const ranInWorktree = /\/\.worktrees\//.test(cwd) || /\/\.claude\/worktrees\//.test(cwd);
|
|
143875
144688
|
return {
|
|
143876
144689
|
resumable: false,
|
|
@@ -144450,13 +145263,13 @@ var import_node_fs17 = require("fs");
|
|
|
144450
145263
|
|
|
144451
145264
|
// node_modules/tar/dist/esm/index.min.js
|
|
144452
145265
|
var import_events4 = __toESM(require("events"), 1);
|
|
144453
|
-
var
|
|
145266
|
+
var import_fs25 = __toESM(require("fs"), 1);
|
|
144454
145267
|
var import_node_events3 = require("events");
|
|
144455
145268
|
var import_node_stream3 = __toESM(require("stream"), 1);
|
|
144456
145269
|
var import_node_string_decoder = require("string_decoder");
|
|
144457
145270
|
var import_node_path14 = __toESM(require("path"), 1);
|
|
144458
145271
|
var import_node_fs11 = __toESM(require("fs"), 1);
|
|
144459
|
-
var
|
|
145272
|
+
var import_path22 = require("path");
|
|
144460
145273
|
var import_events5 = require("events");
|
|
144461
145274
|
var import_assert = __toESM(require("assert"), 1);
|
|
144462
145275
|
var import_buffer = require("buffer");
|
|
@@ -144464,17 +145277,17 @@ var Ps = __toESM(require("zlib"), 1);
|
|
|
144464
145277
|
var import_zlib = __toESM(require("zlib"), 1);
|
|
144465
145278
|
var import_node_path15 = require("path");
|
|
144466
145279
|
var import_node_path16 = require("path");
|
|
144467
|
-
var import_fs25 = __toESM(require("fs"), 1);
|
|
144468
145280
|
var import_fs26 = __toESM(require("fs"), 1);
|
|
144469
|
-
var
|
|
145281
|
+
var import_fs27 = __toESM(require("fs"), 1);
|
|
145282
|
+
var import_path23 = __toESM(require("path"), 1);
|
|
144470
145283
|
var import_node_path17 = require("path");
|
|
144471
|
-
var
|
|
145284
|
+
var import_path24 = __toESM(require("path"), 1);
|
|
144472
145285
|
var import_node_fs12 = __toESM(require("fs"), 1);
|
|
144473
145286
|
var import_node_assert = __toESM(require("assert"), 1);
|
|
144474
145287
|
var import_node_crypto5 = require("crypto");
|
|
144475
145288
|
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
144476
145289
|
var import_node_path18 = __toESM(require("path"), 1);
|
|
144477
|
-
var
|
|
145290
|
+
var import_fs28 = __toESM(require("fs"), 1);
|
|
144478
145291
|
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
144479
145292
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
144480
145293
|
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
@@ -144826,7 +145639,7 @@ var A = class extends import_node_events3.EventEmitter {
|
|
|
144826
145639
|
return Wr;
|
|
144827
145640
|
}
|
|
144828
145641
|
};
|
|
144829
|
-
var Jr =
|
|
145642
|
+
var Jr = import_fs25.default.writev;
|
|
144830
145643
|
var ht = /* @__PURE__ */ Symbol("_autoClose");
|
|
144831
145644
|
var H = /* @__PURE__ */ Symbol("_close");
|
|
144832
145645
|
var te = /* @__PURE__ */ Symbol("_ended");
|
|
@@ -144881,7 +145694,7 @@ var _t = class extends A {
|
|
|
144881
145694
|
throw new TypeError("this is a readable stream");
|
|
144882
145695
|
}
|
|
144883
145696
|
[at]() {
|
|
144884
|
-
|
|
145697
|
+
import_fs25.default.open(this[U2], "r", (t2, e) => this[Ht](t2, e));
|
|
144885
145698
|
}
|
|
144886
145699
|
[Ht](t2, e) {
|
|
144887
145700
|
t2 ? this[Ut](t2) : (this[u2] = e, this.emit("open", e), this[zt]());
|
|
@@ -144894,7 +145707,7 @@ var _t = class extends A {
|
|
|
144894
145707
|
this[j] = true;
|
|
144895
145708
|
let t2 = this[vi]();
|
|
144896
145709
|
if (t2.length === 0) return process.nextTick(() => this[Ii](null, 0, t2));
|
|
144897
|
-
|
|
145710
|
+
import_fs25.default.read(this[u2], t2, 0, t2.length, null, (e, i, r) => this[Ii](e, i, r));
|
|
144898
145711
|
}
|
|
144899
145712
|
}
|
|
144900
145713
|
[Ii](t2, e, i) {
|
|
@@ -144903,7 +145716,7 @@ var _t = class extends A {
|
|
|
144903
145716
|
[H]() {
|
|
144904
145717
|
if (this[ht] && typeof this[u2] == "number") {
|
|
144905
145718
|
let t2 = this[u2];
|
|
144906
|
-
this[u2] = void 0,
|
|
145719
|
+
this[u2] = void 0, import_fs25.default.close(t2, (e) => e ? this.emit("error", e) : this.emit("close"));
|
|
144907
145720
|
}
|
|
144908
145721
|
}
|
|
144909
145722
|
[Ut](t2) {
|
|
@@ -144931,7 +145744,7 @@ var Be = class extends _t {
|
|
|
144931
145744
|
[at]() {
|
|
144932
145745
|
let t2 = true;
|
|
144933
145746
|
try {
|
|
144934
|
-
this[Ht](null,
|
|
145747
|
+
this[Ht](null, import_fs25.default.openSync(this[U2], "r")), t2 = false;
|
|
144935
145748
|
} finally {
|
|
144936
145749
|
t2 && this[H]();
|
|
144937
145750
|
}
|
|
@@ -144942,7 +145755,7 @@ var Be = class extends _t {
|
|
|
144942
145755
|
if (!this[j]) {
|
|
144943
145756
|
this[j] = true;
|
|
144944
145757
|
do {
|
|
144945
|
-
let e = this[vi](), i = e.length === 0 ? 0 :
|
|
145758
|
+
let e = this[vi](), i = e.length === 0 ? 0 : import_fs25.default.readSync(this[u2], e, 0, e.length, null);
|
|
144946
145759
|
if (!this[ki](i, e)) break;
|
|
144947
145760
|
} while (true);
|
|
144948
145761
|
this[j] = false;
|
|
@@ -144955,7 +145768,7 @@ var Be = class extends _t {
|
|
|
144955
145768
|
[H]() {
|
|
144956
145769
|
if (this[ht] && typeof this[u2] == "number") {
|
|
144957
145770
|
let t2 = this[u2];
|
|
144958
|
-
this[u2] = void 0,
|
|
145771
|
+
this[u2] = void 0, import_fs25.default.closeSync(t2), this.emit("close");
|
|
144959
145772
|
}
|
|
144960
145773
|
}
|
|
144961
145774
|
};
|
|
@@ -144997,7 +145810,7 @@ var et = class extends import_events4.default {
|
|
|
144997
145810
|
this[H](), this[gt] = true, this.emit("error", t2);
|
|
144998
145811
|
}
|
|
144999
145812
|
[at]() {
|
|
145000
|
-
|
|
145813
|
+
import_fs25.default.open(this[U2], this[tt], this[ie], (t2, e) => this[Ht](t2, e));
|
|
145001
145814
|
}
|
|
145002
145815
|
[Ht](t2, e) {
|
|
145003
145816
|
this[Me] && this[tt] === "r+" && t2 && t2.code === "ENOENT" ? (this[tt] = "w", this[at]()) : t2 ? this[Ut](t2) : (this[u2] = e, this.emit("open", e), this[gt] || this[Ai]());
|
|
@@ -145009,7 +145822,7 @@ var et = class extends import_events4.default {
|
|
|
145009
145822
|
return typeof t2 == "string" && (t2 = Buffer.from(t2, e)), this[te] ? (this.emit("error", new Error("write() after end()")), false) : this[u2] === void 0 || this[gt] || this[Y2].length ? (this[Y2].push(t2), this[ke] = true, false) : (this[gt] = true, this[ve](t2), true);
|
|
145010
145823
|
}
|
|
145011
145824
|
[ve](t2) {
|
|
145012
|
-
|
|
145825
|
+
import_fs25.default.write(this[u2], t2, 0, t2.length, this[ot], (e, i) => this[Pt](e, i));
|
|
145013
145826
|
}
|
|
145014
145827
|
[Pt](t2, e) {
|
|
145015
145828
|
t2 ? this[Ut](t2) : (this[ot] !== void 0 && typeof e == "number" && (this[ot] += e), this[Y2].length ? this[Ai]() : (this[gt] = false, this[te] && !this[Ni] ? (this[Ni] = true, this[H](), this.emit("finish")) : this[ke] && (this[ke] = false, this.emit("drain"))));
|
|
@@ -145025,7 +145838,7 @@ var et = class extends import_events4.default {
|
|
|
145025
145838
|
[H]() {
|
|
145026
145839
|
if (this[ht] && typeof this[u2] == "number") {
|
|
145027
145840
|
let t2 = this[u2];
|
|
145028
|
-
this[u2] = void 0,
|
|
145841
|
+
this[u2] = void 0, import_fs25.default.close(t2, (e) => e ? this.emit("error", e) : this.emit("close"));
|
|
145029
145842
|
}
|
|
145030
145843
|
}
|
|
145031
145844
|
};
|
|
@@ -145033,24 +145846,24 @@ var Wt = class extends et {
|
|
|
145033
145846
|
[at]() {
|
|
145034
145847
|
let t2;
|
|
145035
145848
|
if (this[Me] && this[tt] === "r+") try {
|
|
145036
|
-
t2 =
|
|
145849
|
+
t2 = import_fs25.default.openSync(this[U2], this[tt], this[ie]);
|
|
145037
145850
|
} catch (e) {
|
|
145038
145851
|
if (e?.code === "ENOENT") return this[tt] = "w", this[at]();
|
|
145039
145852
|
throw e;
|
|
145040
145853
|
}
|
|
145041
|
-
else t2 =
|
|
145854
|
+
else t2 = import_fs25.default.openSync(this[U2], this[tt], this[ie]);
|
|
145042
145855
|
this[Ht](null, t2);
|
|
145043
145856
|
}
|
|
145044
145857
|
[H]() {
|
|
145045
145858
|
if (this[ht] && typeof this[u2] == "number") {
|
|
145046
145859
|
let t2 = this[u2];
|
|
145047
|
-
this[u2] = void 0,
|
|
145860
|
+
this[u2] = void 0, import_fs25.default.closeSync(t2), this.emit("close");
|
|
145048
145861
|
}
|
|
145049
145862
|
}
|
|
145050
145863
|
[ve](t2) {
|
|
145051
145864
|
let e = true;
|
|
145052
145865
|
try {
|
|
145053
|
-
this[Pt](null,
|
|
145866
|
+
this[Pt](null, import_fs25.default.writeSync(this[u2], t2, 0, t2.length, this[ot])), e = false;
|
|
145054
145867
|
} finally {
|
|
145055
145868
|
if (e) try {
|
|
145056
145869
|
this[H]();
|
|
@@ -145833,11 +146646,11 @@ var vn = (s3) => {
|
|
|
145833
146646
|
};
|
|
145834
146647
|
var Qi = (s3, t2) => {
|
|
145835
146648
|
let e = new Map(t2.map((n) => [ut(n), true])), i = s3.filter, r = (n, o = "") => {
|
|
145836
|
-
let h = o || (0,
|
|
146649
|
+
let h = o || (0, import_path22.parse)(n).root || ".", a;
|
|
145837
146650
|
if (n === h) a = false;
|
|
145838
146651
|
else {
|
|
145839
146652
|
let l = e.get(n);
|
|
145840
|
-
a = l !== void 0 ? l : r((0,
|
|
146653
|
+
a = l !== void 0 ? l : r((0, import_path22.dirname)(n), h);
|
|
145841
146654
|
}
|
|
145842
146655
|
return e.set(n, a), a;
|
|
145843
146656
|
};
|
|
@@ -145957,7 +146770,7 @@ var de = class extends A {
|
|
|
145957
146770
|
let [o, h] = ce(this.path);
|
|
145958
146771
|
o && typeof h == "string" && (this.path = h, r = o);
|
|
145959
146772
|
}
|
|
145960
|
-
this.win32 = !!i.win32 || process.platform === "win32", this.win32 && (this.path = Qs(this.path.replaceAll(/\\/g, "/")), t2 = t2.replaceAll(/\\/g, "/")), this.absolute = f(i.absolute ||
|
|
146773
|
+
this.win32 = !!i.win32 || process.platform === "win32", this.win32 && (this.path = Qs(this.path.replaceAll(/\\/g, "/")), t2 = t2.replaceAll(/\\/g, "/")), this.absolute = f(i.absolute || import_path23.default.resolve(this.cwd, t2)), this.path === "" && (this.path = "./"), r && this.warn("TAR_ENTRY_INFO", `stripping ${r} from absolute path`, { entry: this, path: r + this.path });
|
|
145961
146774
|
let n = this.statCache.get(this.absolute);
|
|
145962
146775
|
n ? this[si](n) : this[ss]();
|
|
145963
146776
|
}
|
|
@@ -145968,7 +146781,7 @@ var de = class extends A {
|
|
|
145968
146781
|
return t2 === "error" && (this.#t = true), super.emit(t2, ...e);
|
|
145969
146782
|
}
|
|
145970
146783
|
[ss]() {
|
|
145971
|
-
|
|
146784
|
+
import_fs27.default.lstat(this.absolute, (t2, e) => {
|
|
145972
146785
|
if (t2) return this.emit("error", t2);
|
|
145973
146786
|
this[si](e);
|
|
145974
146787
|
});
|
|
@@ -146006,7 +146819,7 @@ var de = class extends A {
|
|
|
146006
146819
|
this.path.slice(-1) !== "/" && (this.path += "/"), this.stat.size = 0, this[fe](), this.end();
|
|
146007
146820
|
}
|
|
146008
146821
|
[is]() {
|
|
146009
|
-
|
|
146822
|
+
import_fs27.default.readlink(this.absolute, (t2, e) => {
|
|
146010
146823
|
if (t2) return this.emit("error", t2);
|
|
146011
146824
|
this[ns](e);
|
|
146012
146825
|
});
|
|
@@ -146016,7 +146829,7 @@ var de = class extends A {
|
|
|
146016
146829
|
}
|
|
146017
146830
|
[sr](t2) {
|
|
146018
146831
|
if (!this.stat) throw new Error("cannot create link entry without stat");
|
|
146019
|
-
this.type = "Link", this.linkpath = f(
|
|
146832
|
+
this.type = "Link", this.linkpath = f(import_path23.default.relative(this.cwd, t2)), this.stat.size = 0, this[fe](), this.end();
|
|
146020
146833
|
}
|
|
146021
146834
|
[er]() {
|
|
146022
146835
|
if (!this.stat) throw new Error("cannot create file entry without stat");
|
|
@@ -146029,7 +146842,7 @@ var de = class extends A {
|
|
|
146029
146842
|
this[os]();
|
|
146030
146843
|
}
|
|
146031
146844
|
[os]() {
|
|
146032
|
-
|
|
146845
|
+
import_fs27.default.open(this.absolute, "r", (t2, e) => {
|
|
146033
146846
|
if (t2) return this.emit("error", t2);
|
|
146034
146847
|
this[hs](e);
|
|
146035
146848
|
});
|
|
@@ -146044,14 +146857,14 @@ var de = class extends A {
|
|
|
146044
146857
|
[ii]() {
|
|
146045
146858
|
let { fd: t2, buf: e, offset: i, length: r, pos: n } = this;
|
|
146046
146859
|
if (t2 === void 0 || e === void 0) throw new Error("cannot read file without first opening");
|
|
146047
|
-
|
|
146860
|
+
import_fs27.default.read(t2, e, i, r, n, (o, h) => {
|
|
146048
146861
|
if (o) return this[pt](() => this.emit("error", o));
|
|
146049
146862
|
this[rs](h);
|
|
146050
146863
|
});
|
|
146051
146864
|
}
|
|
146052
146865
|
[pt](t2 = () => {
|
|
146053
146866
|
}) {
|
|
146054
|
-
this.fd !== void 0 &&
|
|
146867
|
+
this.fd !== void 0 && import_fs27.default.close(this.fd, t2);
|
|
146055
146868
|
}
|
|
146056
146869
|
[rs](t2) {
|
|
146057
146870
|
if (t2 <= 0 && this.remain > 0) {
|
|
@@ -146086,20 +146899,20 @@ var de = class extends A {
|
|
|
146086
146899
|
var ni = class extends de {
|
|
146087
146900
|
sync = true;
|
|
146088
146901
|
[ss]() {
|
|
146089
|
-
this[si](
|
|
146902
|
+
this[si](import_fs27.default.lstatSync(this.absolute));
|
|
146090
146903
|
}
|
|
146091
146904
|
[is]() {
|
|
146092
|
-
this[ns](
|
|
146905
|
+
this[ns](import_fs27.default.readlinkSync(this.absolute));
|
|
146093
146906
|
}
|
|
146094
146907
|
[os]() {
|
|
146095
|
-
this[hs](
|
|
146908
|
+
this[hs](import_fs27.default.openSync(this.absolute, "r"));
|
|
146096
146909
|
}
|
|
146097
146910
|
[ii]() {
|
|
146098
146911
|
let t2 = true;
|
|
146099
146912
|
try {
|
|
146100
146913
|
let { fd: e, buf: i, offset: r, length: n, pos: o } = this;
|
|
146101
146914
|
if (e === void 0 || i === void 0) throw new Error("fd and buf must be set in READ method");
|
|
146102
|
-
let h =
|
|
146915
|
+
let h = import_fs27.default.readSync(e, i, r, n, o);
|
|
146103
146916
|
this[rs](h), t2 = false;
|
|
146104
146917
|
} finally {
|
|
146105
146918
|
if (t2) try {
|
|
@@ -146114,7 +146927,7 @@ var ni = class extends de {
|
|
|
146114
146927
|
}
|
|
146115
146928
|
[pt](t2 = () => {
|
|
146116
146929
|
}) {
|
|
146117
|
-
this.fd !== void 0 &&
|
|
146930
|
+
this.fd !== void 0 && import_fs27.default.closeSync(this.fd), t2();
|
|
146118
146931
|
}
|
|
146119
146932
|
};
|
|
146120
146933
|
var oi = class extends A {
|
|
@@ -146430,7 +147243,7 @@ var wt = class extends A {
|
|
|
146430
147243
|
return typeof t2 == "string" ? this[ci](t2) : this[or](t2), this.flowing;
|
|
146431
147244
|
}
|
|
146432
147245
|
[or](t2) {
|
|
146433
|
-
let e = f(
|
|
147246
|
+
let e = f(import_path24.default.resolve(this.cwd, t2.path));
|
|
146434
147247
|
if (!this.filter(t2.path, t2)) t2.resume();
|
|
146435
147248
|
else {
|
|
146436
147249
|
let i = new pi(t2.path, e);
|
|
@@ -146439,13 +147252,13 @@ var wt = class extends A {
|
|
|
146439
147252
|
this[Ft]();
|
|
146440
147253
|
}
|
|
146441
147254
|
[ci](t2) {
|
|
146442
|
-
let e = f(
|
|
147255
|
+
let e = f(import_path24.default.resolve(this.cwd, t2));
|
|
146443
147256
|
this[W].push(new pi(t2, e)), this[Ft]();
|
|
146444
147257
|
}
|
|
146445
147258
|
[ds](t2) {
|
|
146446
147259
|
t2.pending = true, this[G2] += 1;
|
|
146447
147260
|
let e = this.follow ? "stat" : "lstat";
|
|
146448
|
-
|
|
147261
|
+
import_fs26.default[e](t2.absolute, (i, r) => {
|
|
146449
147262
|
t2.pending = false, this[G2] -= 1, i ? this.emit("error", i) : this[li](t2, r);
|
|
146450
147263
|
});
|
|
146451
147264
|
}
|
|
@@ -146459,7 +147272,7 @@ var wt = class extends A {
|
|
|
146459
147272
|
this[Ft]();
|
|
146460
147273
|
}
|
|
146461
147274
|
[ms](t2) {
|
|
146462
|
-
t2.pending = true, this[G2] += 1,
|
|
147275
|
+
t2.pending = true, this[G2] += 1, import_fs26.default.readdir(t2.absolute, (e, i) => {
|
|
146463
147276
|
if (t2.pending = false, this[G2] -= 1, e) return this.emit("error", e);
|
|
146464
147277
|
this[fi](t2, i);
|
|
146465
147278
|
});
|
|
@@ -146560,10 +147373,10 @@ var kt = class extends wt {
|
|
|
146560
147373
|
}
|
|
146561
147374
|
[ds](t2) {
|
|
146562
147375
|
let e = this.follow ? "statSync" : "lstatSync";
|
|
146563
|
-
this[li](t2,
|
|
147376
|
+
this[li](t2, import_fs26.default[e](t2.absolute));
|
|
146564
147377
|
}
|
|
146565
147378
|
[ms](t2) {
|
|
146566
|
-
this[fi](t2,
|
|
147379
|
+
this[fi](t2, import_fs26.default.readdirSync(t2.absolute));
|
|
146567
147380
|
}
|
|
146568
147381
|
[di](t2) {
|
|
146569
147382
|
let e = t2.entry, i = this.zip;
|
|
@@ -146614,8 +147427,8 @@ var Qn = K2(Vn, $n, Xn, qn, (s3, t2) => {
|
|
|
146614
147427
|
});
|
|
146615
147428
|
var Jn = process.env.__FAKE_PLATFORM__ || process.platform;
|
|
146616
147429
|
var Er = Jn === "win32";
|
|
146617
|
-
var { O_CREAT: wr, O_NOFOLLOW: mr, O_TRUNC: Sr, O_WRONLY: yr } =
|
|
146618
|
-
var Rr = Number(process.env.__FAKE_FS_O_FILENAME__) ||
|
|
147430
|
+
var { O_CREAT: wr, O_NOFOLLOW: mr, O_TRUNC: Sr, O_WRONLY: yr } = import_fs28.default.constants;
|
|
147431
|
+
var Rr = Number(process.env.__FAKE_FS_O_FILENAME__) || import_fs28.default.constants.UV_FS_O_FILEMAP || 0;
|
|
146619
147432
|
var jn = Er && !!Rr;
|
|
146620
147433
|
var to = 512 * 1024;
|
|
146621
147434
|
var eo = Rr | Sr | wr | yr;
|
|
@@ -147739,6 +148552,13 @@ program2.command("serve").description("Start the streamer server").option("-p, -
|
|
|
147739
148552
|
"Run in multi-agent mode (PTY mode unreachable in this process)",
|
|
147740
148553
|
false
|
|
147741
148554
|
).action(async (opts) => {
|
|
148555
|
+
try {
|
|
148556
|
+
const { checkSqliteAbi: checkSqliteAbi2 } = await Promise.resolve().then(() => (init_check_sqlite_abi(), check_sqlite_abi_exports));
|
|
148557
|
+
checkSqliteAbi2();
|
|
148558
|
+
} catch (err) {
|
|
148559
|
+
log7.error(err instanceof Error ? err.message : String(err), void 0, "console");
|
|
148560
|
+
process.exit(1);
|
|
148561
|
+
}
|
|
147742
148562
|
if (opts.multiAgentFlow) {
|
|
147743
148563
|
process.env.MULTI_AGENT_FLOW = "true";
|
|
147744
148564
|
}
|
|
@@ -147836,12 +148656,12 @@ program2.command("cache").description("Manage the local SQLite conversation cach
|
|
|
147836
148656
|
"Cache directory (default: ~/.threadbase/cache)",
|
|
147837
148657
|
`${process.env.HOME}/.threadbase/cache`
|
|
147838
148658
|
).action((opts) => {
|
|
147839
|
-
const { rmSync: rmSync6, existsSync:
|
|
148659
|
+
const { rmSync: rmSync6, existsSync: existsSync12 } = require("fs");
|
|
147840
148660
|
const { join: join26 } = require("path");
|
|
147841
148661
|
const dbPath = join26(opts.cacheDir, "cache.db");
|
|
147842
148662
|
for (const suffix of ["", "-shm", "-wal"]) {
|
|
147843
148663
|
const f2 = dbPath + suffix;
|
|
147844
|
-
if (
|
|
148664
|
+
if (existsSync12(f2)) {
|
|
147845
148665
|
rmSync6(f2);
|
|
147846
148666
|
log7.info(`Deleted ${f2}`, { path: f2 }, "console");
|
|
147847
148667
|
}
|