offsite 0.0.94 → 0.0.95
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/apps/client/dist/assets/{XIcon-CDJz0EhT.js → XIcon-Tc8nkaoT.js} +4 -4
- package/apps/client/dist/assets/{audio-player-CEvt4CVB.js → audio-player-DosgEDBw.js} +1 -1
- package/apps/client/dist/assets/{button-CWFREsRn.js → button-BnzxfmO3.js} +1 -1
- package/apps/client/dist/assets/diff-panel-DTqI0r9W.js +260 -0
- package/apps/client/dist/assets/{dist-C_Y10kCd.js → dist-DNx_j6Py.js} +1 -1
- package/apps/client/dist/assets/{editor-dialog-CEMtEtdH.js → editor-dialog-Se2DP1E5.js} +36 -36
- package/apps/client/dist/assets/{image-viewer-3S4rNjFt.js → image-viewer-qKLCNUVK.js} +1 -1
- package/apps/client/dist/assets/index-B6yjBLio.js +72 -0
- package/apps/client/dist/assets/index-BFkBeASv.css +2 -0
- package/apps/client/dist/assets/panel-close-button-ByrJIu34.js +1 -0
- package/apps/client/dist/assets/{preview-dialog-BuSSuMeQ.js → preview-dialog-CnvAwgMU.js} +2 -2
- package/apps/client/dist/assets/{react-BgpkckXP.js → react-aDzERmfM.js} +1 -1
- package/apps/client/dist/assets/{tooltip-CKPboHje.js → tooltip-CU6Gh-yj.js} +1 -1
- package/apps/client/dist/assets/{use-editor-shortcut-auobREOP.js → use-editor-shortcut-CGHT7fnL.js} +26 -26
- package/apps/client/dist/assets/{utils-U6QfaJBO.js → utils-Cih_T9uV.js} +1 -1
- package/apps/client/dist/assets/{video-viewer-gcKzQe3N.js → video-viewer-Ctc_XHXE.js} +1 -1
- package/apps/client/dist/assets/zoomable-image-D0X1OvmT.js +1 -0
- package/apps/client/dist/assets/{zoomable-image-BD-kkiyn.js → zoomable-image-D6TeXYQ9.js} +1 -1
- package/apps/client/dist/index.html +10 -10
- package/apps/client/dist/sw.js +1 -1
- package/apps/server/dist/main.mjs +512 -133
- package/apps/server/dist/main.mjs.map +1 -1
- package/package.json +1 -1
- package/apps/client/dist/assets/diff-panel-CeJqpNi8.js +0 -260
- package/apps/client/dist/assets/index-B7o5cqzG.js +0 -72
- package/apps/client/dist/assets/index-BwL3keyv.css +0 -2
- package/apps/client/dist/assets/panel-close-button-Bf-0TdmN.js +0 -1
- package/apps/client/dist/assets/zoomable-image-_HGrW8wW.js +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
-
import { accessSync, appendFileSync, chmodSync, closeSync, constants, createReadStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync, writeSync } from "node:fs";
|
|
2
|
+
import { accessSync, appendFileSync, chmodSync, closeSync, constants, createReadStream, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, symlinkSync, unlinkSync, watch, writeFileSync, writeSync } from "node:fs";
|
|
3
3
|
import http, { createServer } from "node:http";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import zlib, { createGzip } from "node:zlib";
|
|
@@ -302,6 +302,29 @@ function notice(text) {
|
|
|
302
302
|
isError: true
|
|
303
303
|
};
|
|
304
304
|
}
|
|
305
|
+
function resultNotice(message) {
|
|
306
|
+
if (message.subtype === "success" || message.terminal_reason === "aborted_streaming" || message.terminal_reason === "aborted_tools") return null;
|
|
307
|
+
return notice(message.errors.filter((error) => !error.startsWith("[ede_diagnostic]")).join("\n") || message.subtype);
|
|
308
|
+
}
|
|
309
|
+
const failingReasons = [
|
|
310
|
+
"api_error",
|
|
311
|
+
"malformed_tool_use_exhausted",
|
|
312
|
+
"budget_exhausted",
|
|
313
|
+
"structured_output_retry_exhausted",
|
|
314
|
+
"tool_deferred_unavailable",
|
|
315
|
+
"turn_setup_failed",
|
|
316
|
+
"blocking_limit",
|
|
317
|
+
"rapid_refill_breaker",
|
|
318
|
+
"prompt_too_long",
|
|
319
|
+
"image_error",
|
|
320
|
+
"model_error"
|
|
321
|
+
];
|
|
322
|
+
function turnFailed(message) {
|
|
323
|
+
if (message.terminal_reason === "aborted_tools" || message.terminal_reason === "aborted_streaming") return false;
|
|
324
|
+
if (message.subtype === "success") return message.api_error_status === 429 || message.api_error_status === 529 || message.is_error && message.api_error_status === 403 || failingReasons.includes(message.terminal_reason);
|
|
325
|
+
const errors = message.errors.join("\n").toLowerCase();
|
|
326
|
+
return !errors.includes("interrupt") && !errors.includes("cancel");
|
|
327
|
+
}
|
|
305
328
|
function contextTokens(message) {
|
|
306
329
|
if (!isRecord(message) || !isRecord(message.usage)) return null;
|
|
307
330
|
const usage = message.usage;
|
|
@@ -459,18 +482,26 @@ function readJson(name) {
|
|
|
459
482
|
throw error;
|
|
460
483
|
}
|
|
461
484
|
}
|
|
462
|
-
function readSettings(name, schema, defaults) {
|
|
485
|
+
function readSettings(name, schema, defaults, migrate = (value) => value) {
|
|
463
486
|
const file = path.join(home, name);
|
|
464
|
-
let
|
|
487
|
+
let saved;
|
|
465
488
|
try {
|
|
466
|
-
|
|
489
|
+
saved = readJson(name);
|
|
467
490
|
} catch (error) {
|
|
468
491
|
log$19.warn(`invalid ${file}, using the defaults:`, error);
|
|
469
492
|
return structuredClone(defaults);
|
|
470
493
|
}
|
|
471
|
-
if (
|
|
494
|
+
if (saved === null) return structuredClone(defaults);
|
|
495
|
+
const value = migrate(saved);
|
|
496
|
+
if (value !== saved) {
|
|
497
|
+
writeJson(`${name}.bak`, saved);
|
|
498
|
+
log$19.info(`migrated ${file}, the old file is in ${file}.bak`);
|
|
499
|
+
}
|
|
472
500
|
let result = schema.safeParse(value);
|
|
473
|
-
if (result.success)
|
|
501
|
+
if (result.success) {
|
|
502
|
+
if (value !== saved) writeJson(name, result.data);
|
|
503
|
+
return result.data;
|
|
504
|
+
}
|
|
474
505
|
while (!result.success) {
|
|
475
506
|
const issue = result.error.issues[0];
|
|
476
507
|
const targets = issue.code === "unrecognized_keys" ? issue.keys.map((key) => [...issue.path, key]) : [issue.path];
|
|
@@ -525,11 +556,11 @@ function installOpen(host, port) {
|
|
|
525
556
|
"wslview"
|
|
526
557
|
]) {
|
|
527
558
|
const wrapper = path.join(folder, name);
|
|
528
|
-
writeFileSync(wrapper, `#!/bin/sh\nexec ${quote(process.execPath)} ${quote(script)} ${name} "$@"\n`);
|
|
559
|
+
writeFileSync(wrapper, `#!/bin/sh\nexec ${quote$1(process.execPath)} ${quote$1(script)} ${name} "$@"\n`);
|
|
529
560
|
chmodSync(wrapper, 493);
|
|
530
561
|
}
|
|
531
562
|
const startup = path.join(folder, "env.sh");
|
|
532
|
-
writeFileSync(startup, `export PATH=${quote(folder)}:"$PATH"\n`);
|
|
563
|
+
writeFileSync(startup, `export PATH=${quote$1(folder)}:"$PATH"\n`);
|
|
533
564
|
env = {
|
|
534
565
|
PATH: process.env.PATH === void 0 ? folder : `${folder}${path.delimiter}${process.env.PATH}`,
|
|
535
566
|
BROWSER: path.join(folder, process.platform === "darwin" ? "open" : "xdg-open"),
|
|
@@ -544,7 +575,7 @@ function loopback(host) {
|
|
|
544
575
|
if (host.includes(":")) return `[${host}]`;
|
|
545
576
|
return host;
|
|
546
577
|
}
|
|
547
|
-
function quote(value) {
|
|
578
|
+
function quote$1(value) {
|
|
548
579
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
549
580
|
}
|
|
550
581
|
// src/lib/codex/app-server.ts
|
|
@@ -722,7 +753,11 @@ var AppServer = class {
|
|
|
722
753
|
publishCodexLimits(this.home, params.rateLimits);
|
|
723
754
|
return;
|
|
724
755
|
}
|
|
725
|
-
listener
|
|
756
|
+
if (listener === void 0) {
|
|
757
|
+
owner?.notifyAgent?.(message.method, params);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
listener.notify(message.method, params);
|
|
726
761
|
}
|
|
727
762
|
rootOf(threadId) {
|
|
728
763
|
if (typeof threadId !== "string") return;
|
|
@@ -1162,27 +1197,31 @@ function accountSchema(provider) {
|
|
|
1162
1197
|
enabled: z.boolean().default(true)
|
|
1163
1198
|
});
|
|
1164
1199
|
}
|
|
1200
|
+
const modelSettings = z.record(z.string(), modelSetting).default({});
|
|
1165
1201
|
const configSchema = z.strictObject({
|
|
1166
|
-
|
|
1202
|
+
version: z.literal(2).default(2),
|
|
1203
|
+
claude: z.strictObject({
|
|
1204
|
+
enabled: z.boolean().default(true),
|
|
1205
|
+
baseDir: platformDirs,
|
|
1206
|
+
accounts: z.array(accountSchema("claude")).default([]),
|
|
1207
|
+
models: modelSettings
|
|
1208
|
+
}).prefault({}),
|
|
1209
|
+
codex: z.strictObject({
|
|
1210
|
+
enabled: z.boolean().default(true),
|
|
1211
|
+
accounts: z.array(accountSchema("codex")).default([]),
|
|
1212
|
+
models: modelSettings
|
|
1213
|
+
}).prefault({}),
|
|
1214
|
+
opencode: z.strictObject({
|
|
1215
|
+
enabled: z.boolean().default(true),
|
|
1216
|
+
models: modelSettings
|
|
1217
|
+
}).prefault({}),
|
|
1167
1218
|
workingDir: z.strictObject({
|
|
1168
1219
|
macos: workingDir(path.posix.isAbsolute),
|
|
1169
1220
|
linux: workingDir(path.posix.isAbsolute),
|
|
1170
1221
|
windows: workingDir(path.win32.isAbsolute)
|
|
1171
1222
|
}).prefault({}),
|
|
1172
|
-
claudeAccounts: z.array(accountSchema("claude")).default([]),
|
|
1173
|
-
codexAccounts: z.array(accountSchema("codex")).default([]),
|
|
1174
|
-
enabledProviders: z.strictObject({
|
|
1175
|
-
claude: z.boolean().default(true),
|
|
1176
|
-
codex: z.boolean().default(true),
|
|
1177
|
-
opencode: z.boolean().default(true)
|
|
1178
|
-
}).prefault({}),
|
|
1179
1223
|
commitMessage: generationModel,
|
|
1180
1224
|
titleGeneration: generationModel,
|
|
1181
|
-
models: z.strictObject({
|
|
1182
|
-
claude: z.record(z.string(), modelSetting).default({}),
|
|
1183
|
-
codex: z.record(z.string(), modelSetting).default({}),
|
|
1184
|
-
opencode: z.record(z.string(), modelSetting).default({})
|
|
1185
|
-
}).prefault({}),
|
|
1186
1225
|
delivery: z.enum(["queue", "steer"]).default("steer"),
|
|
1187
1226
|
autoSkipQuestionTimeout: z.number().positive().nullable().default(180),
|
|
1188
1227
|
autoPermissionMode: z.enum([
|
|
@@ -1216,7 +1255,7 @@ function platform() {
|
|
|
1216
1255
|
throw new Error(`unsupported platform ${process.platform}`);
|
|
1217
1256
|
}
|
|
1218
1257
|
function claudeBaseDir() {
|
|
1219
|
-
return readConfig().
|
|
1258
|
+
return readConfig().claude.baseDir[platform()] ?? path.join(os.homedir(), ".claude");
|
|
1220
1259
|
}
|
|
1221
1260
|
function workingDirectory() {
|
|
1222
1261
|
const dir = readConfig().workingDir[platform()] ?? "~";
|
|
@@ -1234,11 +1273,42 @@ function configInfo() {
|
|
|
1234
1273
|
defaultShell: defaultShell(),
|
|
1235
1274
|
defaultClaudeBaseDir: path.join(os.homedir(), ".claude"),
|
|
1236
1275
|
homeDir: os.homedir(),
|
|
1237
|
-
scratchAvailable
|
|
1276
|
+
scratchAvailable,
|
|
1277
|
+
version: "v0.0.95"
|
|
1238
1278
|
};
|
|
1239
1279
|
}
|
|
1240
1280
|
function readConfig() {
|
|
1241
|
-
return readSettings("config.json", configSchema, defaults$1);
|
|
1281
|
+
return readSettings("config.json", configSchema, defaults$1, migrate$1);
|
|
1282
|
+
}
|
|
1283
|
+
function migrate$1(value) {
|
|
1284
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
|
|
1285
|
+
if (!("version" in value)) return fromVersion1(value);
|
|
1286
|
+
if (value.version === 2) return value;
|
|
1287
|
+
throw new Error(`config.json has version ${String(value.version)}, which this version of offsite cannot read. Update offsite`);
|
|
1288
|
+
}
|
|
1289
|
+
function fromVersion1(value) {
|
|
1290
|
+
const { claudeBaseDir, claudeAccounts, codexAccounts, enabledProviders, models, ...rest } = value;
|
|
1291
|
+
const enabled = enabledProviders ?? {};
|
|
1292
|
+
const providerModels = models ?? {};
|
|
1293
|
+
return {
|
|
1294
|
+
...rest,
|
|
1295
|
+
version: 2,
|
|
1296
|
+
claude: {
|
|
1297
|
+
enabled: enabled.claude,
|
|
1298
|
+
baseDir: claudeBaseDir,
|
|
1299
|
+
accounts: claudeAccounts,
|
|
1300
|
+
models: providerModels.claude
|
|
1301
|
+
},
|
|
1302
|
+
codex: {
|
|
1303
|
+
enabled: enabled.codex,
|
|
1304
|
+
accounts: codexAccounts,
|
|
1305
|
+
models: providerModels.codex
|
|
1306
|
+
},
|
|
1307
|
+
opencode: {
|
|
1308
|
+
enabled: enabled.opencode,
|
|
1309
|
+
models: providerModels.opencode
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1242
1312
|
}
|
|
1243
1313
|
function saveConfig(value) {
|
|
1244
1314
|
const result = configSchema.safeParse(value);
|
|
@@ -1266,6 +1336,7 @@ function activity() {
|
|
|
1266
1336
|
prompted: run.prompted,
|
|
1267
1337
|
compacting: run.compacting,
|
|
1268
1338
|
backgroundAgent: run.backgroundAgents > 0,
|
|
1339
|
+
holdsCompletion: !run.failed && run.agentTasks > 0,
|
|
1269
1340
|
tasks: run.tasks
|
|
1270
1341
|
};
|
|
1271
1342
|
return result;
|
|
@@ -1314,6 +1385,24 @@ function scheduleAutoResolve(sessionId, request) {
|
|
|
1314
1385
|
}
|
|
1315
1386
|
};
|
|
1316
1387
|
}
|
|
1388
|
+
// src/lib/codex/agents.ts
|
|
1389
|
+
function trackAgents(agents, method, params) {
|
|
1390
|
+
const threadId = params.threadId;
|
|
1391
|
+
if (method === "turn/started" && agents.has(threadId)) agents.set(threadId, true);
|
|
1392
|
+
if (method === "turn/completed" && agents.has(threadId)) agents.set(threadId, false);
|
|
1393
|
+
if (method !== "item/completed") return;
|
|
1394
|
+
const item = params.item;
|
|
1395
|
+
if (item.type === "subAgentActivity") {
|
|
1396
|
+
if (item.kind === "started" && !agents.has(item.agentThreadId)) agents.set(item.agentThreadId, true);
|
|
1397
|
+
if ((item.kind === "interrupted" || item.kind === "completed") && agents.has(item.agentThreadId)) agents.set(item.agentThreadId, false);
|
|
1398
|
+
}
|
|
1399
|
+
if (item.type === "collabAgentToolCall") {
|
|
1400
|
+
if (item.tool === "spawnAgent") {
|
|
1401
|
+
for (const agent of item.receiverThreadIds) if (!agents.has(agent)) agents.set(agent, true);
|
|
1402
|
+
}
|
|
1403
|
+
for (const [agent, state] of Object.entries(item.agentsStates)) if (agents.get(agent) === true) agents.set(agent, state.status === "pendingInit" || state.status === "running");
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1317
1406
|
// src/lib/codex/presets.ts
|
|
1318
1407
|
const presets = /* @__PURE__ */ new Map([
|
|
1319
1408
|
["read-only", {
|
|
@@ -1466,11 +1555,13 @@ var CodexRun = class CodexRun {
|
|
|
1466
1555
|
compacting = false;
|
|
1467
1556
|
backgroundTasks = 0;
|
|
1468
1557
|
backgroundAgents = 0;
|
|
1558
|
+
failed = false;
|
|
1469
1559
|
tasks = {};
|
|
1470
1560
|
activeAt = Date.now();
|
|
1471
1561
|
model;
|
|
1472
1562
|
close;
|
|
1473
1563
|
turnId = null;
|
|
1564
|
+
agents = /* @__PURE__ */ new Map();
|
|
1474
1565
|
idleTimer;
|
|
1475
1566
|
static async start(cwd, settings) {
|
|
1476
1567
|
const server = codexServer(settings);
|
|
@@ -1579,6 +1670,7 @@ var CodexRun = class CodexRun {
|
|
|
1579
1670
|
}
|
|
1580
1671
|
notify(method, params) {
|
|
1581
1672
|
this.activeAt = Date.now();
|
|
1673
|
+
this.notifyAgent(method, params);
|
|
1582
1674
|
switch (method) {
|
|
1583
1675
|
case "turn/started": {
|
|
1584
1676
|
const turn = params.turn;
|
|
@@ -1592,6 +1684,7 @@ var CodexRun = class CodexRun {
|
|
|
1592
1684
|
case "turn/completed": {
|
|
1593
1685
|
const turn = params.turn;
|
|
1594
1686
|
if (turn.status === "failed" && turn.error !== null) this.put(errorItem(`${turn.id}:error`, turn.error.message));
|
|
1687
|
+
this.failed = turn.status === "failed" || turn.status === "inProgress";
|
|
1595
1688
|
this.finishTurn();
|
|
1596
1689
|
return;
|
|
1597
1690
|
}
|
|
@@ -1657,6 +1750,14 @@ var CodexRun = class CodexRun {
|
|
|
1657
1750
|
case "serverRequest/resolved": this.settle(String(params.requestId));
|
|
1658
1751
|
}
|
|
1659
1752
|
}
|
|
1753
|
+
notifyAgent(method, params) {
|
|
1754
|
+
const running = this.agentTasks;
|
|
1755
|
+
trackAgents(this.agents, method, params);
|
|
1756
|
+
if (this.agentTasks !== running) broadcastActivity();
|
|
1757
|
+
}
|
|
1758
|
+
get agentTasks() {
|
|
1759
|
+
return [...this.agents.values()].filter((running) => running).length;
|
|
1760
|
+
}
|
|
1660
1761
|
request(id, method, params) {
|
|
1661
1762
|
if (method === "mcpServer/elicitation/request") {
|
|
1662
1763
|
this.server.reply(id, {
|
|
@@ -2667,6 +2768,8 @@ var OpenCodeRun = class OpenCodeRun {
|
|
|
2667
2768
|
compacting = false;
|
|
2668
2769
|
backgroundTasks = 0;
|
|
2669
2770
|
backgroundAgents = 0;
|
|
2771
|
+
agentTasks = 0;
|
|
2772
|
+
failed = false;
|
|
2670
2773
|
tasks = {};
|
|
2671
2774
|
close;
|
|
2672
2775
|
applied;
|
|
@@ -3443,7 +3546,7 @@ const UNTRACKED_LIMIT = 100;
|
|
|
3443
3546
|
const NEW_FILE_LIMIT = 262144;
|
|
3444
3547
|
const REPO_DEPTH = 3;
|
|
3445
3548
|
const FETCH_TIMEOUT = 6e4;
|
|
3446
|
-
const run$
|
|
3549
|
+
const run$4 = promisify(execFile);
|
|
3447
3550
|
const PULL_REQUEST_INSTRUCTIONS = `You write GitHub pull requests as Markdown.
|
|
3448
3551
|
|
|
3449
3552
|
## TITLE RULES
|
|
@@ -3475,6 +3578,7 @@ Return one Markdown document. The title is the level-1 heading (\`# <title>\`),
|
|
|
3475
3578
|
async function gitRepos(cwd) {
|
|
3476
3579
|
return Promise.all((await projectRepos(cwd)).map(async (repo) => ({
|
|
3477
3580
|
...await gitStatus(path.join(cwd, repo)),
|
|
3581
|
+
...await lineChanges(path.join(cwd, repo)),
|
|
3478
3582
|
path: repo
|
|
3479
3583
|
})));
|
|
3480
3584
|
}
|
|
@@ -3535,7 +3639,7 @@ async function sync(cwd) {
|
|
|
3535
3639
|
async function fetchProject(cwd) {
|
|
3536
3640
|
await Promise.all((await projectRepos(cwd)).map(async (repo) => {
|
|
3537
3641
|
try {
|
|
3538
|
-
await run$
|
|
3642
|
+
await run$4("git", ["fetch"], {
|
|
3539
3643
|
cwd: path.join(cwd, repo),
|
|
3540
3644
|
env: {
|
|
3541
3645
|
...process.env,
|
|
@@ -3566,7 +3670,7 @@ async function switchBranch(cwd, branch, create) {
|
|
|
3566
3670
|
async function pullRequestBranches(cwd) {
|
|
3567
3671
|
const head = (await gitStatus(cwd)).branch;
|
|
3568
3672
|
if (head === null) throw new Error("Not on a branch");
|
|
3569
|
-
const base = (await run$
|
|
3673
|
+
const base = (await run$4("gh", [
|
|
3570
3674
|
"repo",
|
|
3571
3675
|
"view",
|
|
3572
3676
|
"--json",
|
|
@@ -3591,7 +3695,7 @@ async function preparePullRequest(cwd, base, commitChanges) {
|
|
|
3591
3695
|
if (commitChanges) await commit(cwd, "", true);
|
|
3592
3696
|
const status = await gitStatus(cwd);
|
|
3593
3697
|
if (status.upstream === null || status.ahead > 0) await push$1(cwd);
|
|
3594
|
-
const open = (await run$
|
|
3698
|
+
const open = (await run$4("gh", [
|
|
3595
3699
|
"pr",
|
|
3596
3700
|
"list",
|
|
3597
3701
|
"--head",
|
|
@@ -3638,7 +3742,7 @@ async function preparePullRequest(cwd, base, commitChanges) {
|
|
|
3638
3742
|
};
|
|
3639
3743
|
}
|
|
3640
3744
|
async function createPullRequest(cwd, request) {
|
|
3641
|
-
const url = (await run$
|
|
3745
|
+
const url = (await run$4("gh", [
|
|
3642
3746
|
"pr",
|
|
3643
3747
|
"create",
|
|
3644
3748
|
"--title",
|
|
@@ -3671,7 +3775,7 @@ async function projectDiff(cwd) {
|
|
|
3671
3775
|
return (await Promise.all(repos.map((repo) => repoDiff(path.join(cwd, repo), repo === "" ? "" : `${repo}/`)))).join("");
|
|
3672
3776
|
}
|
|
3673
3777
|
function git(cwd, args) {
|
|
3674
|
-
return run$
|
|
3778
|
+
return run$4("git", args, {
|
|
3675
3779
|
cwd,
|
|
3676
3780
|
maxBuffer: 67108864
|
|
3677
3781
|
});
|
|
@@ -3708,6 +3812,19 @@ async function gitStatus(cwd) {
|
|
|
3708
3812
|
}
|
|
3709
3813
|
return status;
|
|
3710
3814
|
}
|
|
3815
|
+
async function lineChanges(repo) {
|
|
3816
|
+
let added = 0;
|
|
3817
|
+
let removed = 0;
|
|
3818
|
+
let hunk = false;
|
|
3819
|
+
for (const line of (await repoDiff(repo, "")).split("\n")) if (line.startsWith("diff --git ")) hunk = false;
|
|
3820
|
+
else if (line.startsWith("@@")) hunk = true;
|
|
3821
|
+
else if (hunk && line.startsWith("+")) added++;
|
|
3822
|
+
else if (hunk && line.startsWith("-")) removed++;
|
|
3823
|
+
return {
|
|
3824
|
+
added,
|
|
3825
|
+
removed
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3711
3828
|
async function rebaseOnUpstream(cwd) {
|
|
3712
3829
|
try {
|
|
3713
3830
|
await git(cwd, ["rebase", "@{upstream}"]);
|
|
@@ -4130,8 +4247,10 @@ function rebuildThreads() {
|
|
|
4130
4247
|
const config = readConfig();
|
|
4131
4248
|
const key = JSON.stringify([
|
|
4132
4249
|
projectPaths$1(),
|
|
4133
|
-
config.
|
|
4134
|
-
config.
|
|
4250
|
+
config.claude.enabled,
|
|
4251
|
+
config.codex.enabled,
|
|
4252
|
+
config.opencode.enabled,
|
|
4253
|
+
config.claude.baseDir
|
|
4135
4254
|
]);
|
|
4136
4255
|
if (key === builtFor) return;
|
|
4137
4256
|
builtFor = key;
|
|
@@ -4145,7 +4264,7 @@ function threadFailed(provider) {
|
|
|
4145
4264
|
}
|
|
4146
4265
|
function knownThreads() {
|
|
4147
4266
|
const known = new Set(projects().map((project) => project.id));
|
|
4148
|
-
return allProviders$2.filter((provider) => readConfig()
|
|
4267
|
+
return allProviders$2.filter((provider) => readConfig()[provider].enabled).flatMap((provider) => [...threads[provider].values()]).filter((item) => known.has(item.projectId));
|
|
4149
4268
|
}
|
|
4150
4269
|
async function providerThreads(provider) {
|
|
4151
4270
|
await built;
|
|
@@ -4220,7 +4339,7 @@ async function relistAll() {
|
|
|
4220
4339
|
await Promise.all(allProviders$2.map((provider) => relist(provider)));
|
|
4221
4340
|
}
|
|
4222
4341
|
async function relist(provider) {
|
|
4223
|
-
if (!readConfig()
|
|
4342
|
+
if (!readConfig()[provider].enabled) {
|
|
4224
4343
|
threads[provider] = /* @__PURE__ */ new Map();
|
|
4225
4344
|
failures.delete(provider);
|
|
4226
4345
|
broadcast({ type: "threads" });
|
|
@@ -4320,7 +4439,7 @@ function withStatus(items) {
|
|
|
4320
4439
|
});
|
|
4321
4440
|
}
|
|
4322
4441
|
function enabledProviders() {
|
|
4323
|
-
return allProviders$1.filter((provider) => readConfig()
|
|
4442
|
+
return allProviders$1.filter((provider) => readConfig()[provider].enabled);
|
|
4324
4443
|
}
|
|
4325
4444
|
function projectPaths() {
|
|
4326
4445
|
return projects().map((project) => [project.path, project.id]);
|
|
@@ -4339,18 +4458,18 @@ function providerOf(sessionId) {
|
|
|
4339
4458
|
if (provider === void 0) throw new Error("offsite does not know this session yet, reload the page");
|
|
4340
4459
|
return provider;
|
|
4341
4460
|
}
|
|
4342
|
-
function recordInteraction(id, provider) {
|
|
4461
|
+
function recordInteraction(id, provider, status) {
|
|
4343
4462
|
db.insert(sessions$1).values({
|
|
4344
4463
|
sessionId: id,
|
|
4345
4464
|
provider,
|
|
4346
4465
|
interactedAt: Date.now(),
|
|
4347
|
-
status: "active"
|
|
4466
|
+
status: status ?? "active"
|
|
4348
4467
|
}).onConflictDoUpdate({
|
|
4349
4468
|
target: sessions$1.sessionId,
|
|
4350
4469
|
set: {
|
|
4351
4470
|
provider,
|
|
4352
4471
|
interactedAt: Date.now(),
|
|
4353
|
-
status: sql`CASE ${sessions$1.status} WHEN 'pinned' THEN 'pinned' WHEN 'snoozed' THEN 'snoozed' WHEN 'minimized' THEN 'minimized' ELSE 'active' END`
|
|
4472
|
+
status: status === null ? sql`CASE ${sessions$1.status} WHEN 'pinned' THEN 'pinned' WHEN 'snoozed' THEN 'snoozed' WHEN 'minimized' THEN 'minimized' ELSE 'active' END` : sql`CASE ${sessions$1.status} WHEN 'pinned' THEN 'pinned' ELSE ${status} END`
|
|
4354
4473
|
}
|
|
4355
4474
|
}).run();
|
|
4356
4475
|
}
|
|
@@ -4400,7 +4519,7 @@ async function list(provider) {
|
|
|
4400
4519
|
const paths = new Map(projectPaths());
|
|
4401
4520
|
const before = statuses[provider];
|
|
4402
4521
|
try {
|
|
4403
|
-
if (!readConfig()
|
|
4522
|
+
if (!readConfig()[provider].enabled) {
|
|
4404
4523
|
listed[provider] = [];
|
|
4405
4524
|
statuses[provider] = {
|
|
4406
4525
|
provider,
|
|
@@ -4455,7 +4574,7 @@ async function lookup(provider, sessionId, paths) {
|
|
|
4455
4574
|
function watchSessions() {
|
|
4456
4575
|
for (const watcher of watchers) watcher.close();
|
|
4457
4576
|
watchers = [["claude", path.join(claudeBaseDir(), "projects")], ["codex", path.join(codexBaseDir, "sessions")]].flatMap(([provider, dir]) => {
|
|
4458
|
-
if (!readConfig()
|
|
4577
|
+
if (!readConfig()[provider].enabled) return [];
|
|
4459
4578
|
try {
|
|
4460
4579
|
const watcher = watch(dir, { recursive: true }, (_, filename) => {
|
|
4461
4580
|
fileChanged(provider, filename);
|
|
@@ -4549,7 +4668,7 @@ async function fork(sessionId, projectId, messageId) {
|
|
|
4549
4668
|
const provider = providerOf(sessionId);
|
|
4550
4669
|
const result = await forkWith(provider, sessionId, projectPath(projectId), messageId);
|
|
4551
4670
|
providers.set(result.sessionId, provider);
|
|
4552
|
-
recordInteraction(result.sessionId, provider);
|
|
4671
|
+
recordInteraction(result.sessionId, provider, null);
|
|
4553
4672
|
markChanged(provider, result.sessionId);
|
|
4554
4673
|
refreshSessions(provider);
|
|
4555
4674
|
return result;
|
|
@@ -4695,16 +4814,16 @@ function providerName(provider) {
|
|
|
4695
4814
|
}
|
|
4696
4815
|
function storedAccounts() {
|
|
4697
4816
|
const config = readConfig();
|
|
4698
|
-
return [...config.
|
|
4817
|
+
return [...config.claude.accounts.map((item) => ({
|
|
4699
4818
|
...item,
|
|
4700
4819
|
configDir: item.configDir[platform()]
|
|
4701
|
-
})), ...config.
|
|
4820
|
+
})), ...config.codex.accounts.map((item) => ({
|
|
4702
4821
|
...item,
|
|
4703
4822
|
configDir: item.configDir[platform()]
|
|
4704
4823
|
}))];
|
|
4705
4824
|
}
|
|
4706
4825
|
function assertEnabled(provider, accountId) {
|
|
4707
|
-
if (!readConfig()
|
|
4826
|
+
if (!readConfig()[provider].enabled) throw new Error(`${providerName(provider)} is disabled in Settings`);
|
|
4708
4827
|
if (provider === "opencode") return;
|
|
4709
4828
|
if (!storedAccount(provider, accountId).enabled) throw new Error("That account is disabled in Settings");
|
|
4710
4829
|
}
|
|
@@ -4734,7 +4853,7 @@ function storedAccount(provider, accountId) {
|
|
|
4734
4853
|
}
|
|
4735
4854
|
async function accounts() {
|
|
4736
4855
|
const stored = await Promise.all(storedAccounts().map((account) => {
|
|
4737
|
-
if (!account.enabled || !readConfig()
|
|
4856
|
+
if (!account.enabled || !readConfig()[account.provider].enabled) return disabledAccount(account);
|
|
4738
4857
|
switch (account.provider) {
|
|
4739
4858
|
case "claude": return claudeAccount(account);
|
|
4740
4859
|
case "codex": return codexAccount(account);
|
|
@@ -4743,7 +4862,7 @@ async function accounts() {
|
|
|
4743
4862
|
return {
|
|
4744
4863
|
claudeAccounts: stored.filter((item) => item.provider === "claude"),
|
|
4745
4864
|
codexAccounts: stored.filter((item) => item.provider === "codex"),
|
|
4746
|
-
openCode: { available: openCodeInstalled() && readConfig().
|
|
4865
|
+
openCode: { available: openCodeInstalled() && readConfig().opencode.enabled }
|
|
4747
4866
|
};
|
|
4748
4867
|
}
|
|
4749
4868
|
function disabledAccount(account) {
|
|
@@ -4772,7 +4891,7 @@ async function publishAccounts() {
|
|
|
4772
4891
|
async function capabilities(provider, accountId, cwd) {
|
|
4773
4892
|
assertEnabled(provider, accountId);
|
|
4774
4893
|
const result = await providerCapabilities(provider, accountId, cwd);
|
|
4775
|
-
const settings = readConfig()
|
|
4894
|
+
const settings = readConfig()[provider].models;
|
|
4776
4895
|
return {
|
|
4777
4896
|
...result,
|
|
4778
4897
|
defaultModel: defaultModels[provider],
|
|
@@ -4805,51 +4924,80 @@ function providerCapabilities(provider, accountId, cwd) {
|
|
|
4805
4924
|
function addAccount(provider, name, configDir) {
|
|
4806
4925
|
const account = validAccount(name, configDir);
|
|
4807
4926
|
const config = readConfig();
|
|
4808
|
-
const key = `${provider}Accounts`;
|
|
4809
4927
|
saveConfig({
|
|
4810
4928
|
...config,
|
|
4811
|
-
[
|
|
4812
|
-
|
|
4813
|
-
provider,
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4929
|
+
[provider]: {
|
|
4930
|
+
...config[provider],
|
|
4931
|
+
accounts: [...config[provider].accounts, {
|
|
4932
|
+
id: randomUUID(),
|
|
4933
|
+
provider,
|
|
4934
|
+
name: account.name,
|
|
4935
|
+
color: null,
|
|
4936
|
+
configDir: { [platform()]: account.configDir },
|
|
4937
|
+
enabled: true
|
|
4938
|
+
}]
|
|
4939
|
+
}
|
|
4819
4940
|
});
|
|
4941
|
+
publishAccounts();
|
|
4942
|
+
return null;
|
|
4820
4943
|
}
|
|
4821
4944
|
async function updateAccount(update) {
|
|
4822
4945
|
const before = storedAccounts().find((item) => item.id === update.id);
|
|
4823
4946
|
if (before === void 0) throw new Error("That account was removed");
|
|
4824
4947
|
const account = validAccount(update.name, update.configDir);
|
|
4825
4948
|
const config = readConfig();
|
|
4826
|
-
const key = `${before.provider}Accounts`;
|
|
4827
4949
|
saveConfig({
|
|
4828
4950
|
...config,
|
|
4829
|
-
[
|
|
4830
|
-
...
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4951
|
+
[before.provider]: {
|
|
4952
|
+
...config[before.provider],
|
|
4953
|
+
accounts: config[before.provider].accounts.map((item) => item.id === update.id ? {
|
|
4954
|
+
...item,
|
|
4955
|
+
name: account.name,
|
|
4956
|
+
color: update.color,
|
|
4957
|
+
configDir: {
|
|
4958
|
+
...item.configDir,
|
|
4959
|
+
[platform()]: account.configDir
|
|
4960
|
+
},
|
|
4961
|
+
enabled: update.enabled
|
|
4962
|
+
} : item)
|
|
4963
|
+
}
|
|
4839
4964
|
});
|
|
4840
4965
|
forgetClaude(update.id);
|
|
4841
4966
|
forgetUsage(update.id);
|
|
4842
4967
|
if (before.enabled && !update.enabled) await stopAccount(before);
|
|
4968
|
+
publishAccounts();
|
|
4969
|
+
return null;
|
|
4970
|
+
}
|
|
4971
|
+
function accountLinkPlan(id) {
|
|
4972
|
+
return linkPlan(storedAccount("claude", id).configDir ?? claudeBaseDir());
|
|
4973
|
+
}
|
|
4974
|
+
async function linkAccount(id) {
|
|
4975
|
+
const account = storedAccount("claude", id);
|
|
4976
|
+
await stopAccount(account);
|
|
4977
|
+
try {
|
|
4978
|
+
await linkClaude(account.configDir ?? claudeBaseDir());
|
|
4979
|
+
} finally {
|
|
4980
|
+
publishAccounts();
|
|
4981
|
+
}
|
|
4982
|
+
return null;
|
|
4843
4983
|
}
|
|
4844
4984
|
function removeAccount(id) {
|
|
4845
4985
|
const config = readConfig();
|
|
4846
4986
|
saveConfig({
|
|
4847
4987
|
...config,
|
|
4848
|
-
|
|
4849
|
-
|
|
4988
|
+
claude: {
|
|
4989
|
+
...config.claude,
|
|
4990
|
+
accounts: config.claude.accounts.filter((item) => item.id !== id)
|
|
4991
|
+
},
|
|
4992
|
+
codex: {
|
|
4993
|
+
...config.codex,
|
|
4994
|
+
accounts: config.codex.accounts.filter((item) => item.id !== id)
|
|
4995
|
+
}
|
|
4850
4996
|
});
|
|
4851
4997
|
forgetClaude(id);
|
|
4852
4998
|
forgetUsage(id);
|
|
4999
|
+
publishAccounts();
|
|
5000
|
+
return null;
|
|
4853
5001
|
}
|
|
4854
5002
|
function validAccount(name, configDir) {
|
|
4855
5003
|
if (name.trim() === "") throw new Error("Give the account a name");
|
|
@@ -4984,6 +5132,213 @@ function targets() {
|
|
|
4984
5132
|
if (notifier === null || notifier.endpoint === null) return [];
|
|
4985
5133
|
return db.select().from(pushSubscriptions).where(and(eq(pushSubscriptions.server, server$1), eq(pushSubscriptions.endpoint, notifier.endpoint))).all();
|
|
4986
5134
|
}
|
|
5135
|
+
// src/lib/claude/links.ts
|
|
5136
|
+
const run$3 = promisify(execFile);
|
|
5137
|
+
const requiredFolders = [
|
|
5138
|
+
"projects",
|
|
5139
|
+
"file-history",
|
|
5140
|
+
"tasks",
|
|
5141
|
+
"todos",
|
|
5142
|
+
"plans",
|
|
5143
|
+
"paste-cache",
|
|
5144
|
+
"session-env",
|
|
5145
|
+
"jobs",
|
|
5146
|
+
"teams",
|
|
5147
|
+
"sessions"
|
|
5148
|
+
];
|
|
5149
|
+
const sharedFolders = [
|
|
5150
|
+
"skills",
|
|
5151
|
+
"agents",
|
|
5152
|
+
"commands",
|
|
5153
|
+
"output-styles",
|
|
5154
|
+
"plugins",
|
|
5155
|
+
"cache",
|
|
5156
|
+
"state",
|
|
5157
|
+
"shell-snapshots"
|
|
5158
|
+
];
|
|
5159
|
+
const sharedFiles = [
|
|
5160
|
+
"settings.json",
|
|
5161
|
+
"CLAUDE.md",
|
|
5162
|
+
"keybindings.json",
|
|
5163
|
+
"history.jsonl"
|
|
5164
|
+
];
|
|
5165
|
+
function isLinked(configDir) {
|
|
5166
|
+
const base = claudeBaseDir();
|
|
5167
|
+
return sameFile(configDir, base) || requiredFolders.every((name) => sameFile(path.join(configDir, name), path.join(base, name)));
|
|
5168
|
+
}
|
|
5169
|
+
function linkPlan(configDir) {
|
|
5170
|
+
const base = path.resolve(claudeBaseDir());
|
|
5171
|
+
const dir = statSync(configDir, { throwIfNoEntry: false });
|
|
5172
|
+
if (dir === void 0 || !dir.isDirectory()) throw new Error(`${configDir} does not exist`);
|
|
5173
|
+
if (sameFile(configDir, base)) throw new Error(`${configDir} is the main Claude folder`);
|
|
5174
|
+
return {
|
|
5175
|
+
configDir,
|
|
5176
|
+
baseDir: base,
|
|
5177
|
+
entries: [
|
|
5178
|
+
...requiredFolders.map((name) => entry(configDir, base, name, true, true)),
|
|
5179
|
+
...sharedFolders.map((name) => entry(configDir, base, name, false, true)),
|
|
5180
|
+
...sharedFiles.map((name) => entry(configDir, base, name, false, false))
|
|
5181
|
+
]
|
|
5182
|
+
};
|
|
5183
|
+
}
|
|
5184
|
+
async function linkClaude(configDir) {
|
|
5185
|
+
const plan = linkPlan(configDir);
|
|
5186
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replaceAll(":", "-");
|
|
5187
|
+
const steps = plan.entries.flatMap((item) => entrySteps(plan, item, stamp));
|
|
5188
|
+
if (steps.length === 0) return;
|
|
5189
|
+
for (const item of plan.entries) if (item.createInBase) mkdirSync(path.join(plan.baseDir, item.name), { recursive: true });
|
|
5190
|
+
if (process.platform === "win32" && !canSymlink()) await runElevated(steps);
|
|
5191
|
+
else for (const step of steps) runStep(step);
|
|
5192
|
+
const missing = plan.entries.filter((item) => item.action !== "skip" && !sameFile(path.join(plan.configDir, item.name), path.join(plan.baseDir, item.name)));
|
|
5193
|
+
if (missing.length > 0) throw new Error(`Could not link ${missing.map((item) => item.name).join(", ")}`);
|
|
5194
|
+
}
|
|
5195
|
+
function entry(configDir, base, name, required, folder) {
|
|
5196
|
+
const target = path.join(configDir, name);
|
|
5197
|
+
const source = path.join(base, name);
|
|
5198
|
+
const sourceExists = statSync(source, { throwIfNoEntry: false }) !== void 0;
|
|
5199
|
+
return {
|
|
5200
|
+
name,
|
|
5201
|
+
required,
|
|
5202
|
+
folder,
|
|
5203
|
+
action: entryAction(target, source, folder, sourceExists),
|
|
5204
|
+
createInBase: folder && !sourceExists
|
|
5205
|
+
};
|
|
5206
|
+
}
|
|
5207
|
+
function entryAction(target, source, folder, sourceExists) {
|
|
5208
|
+
if (!folder && !sourceExists) return "skip";
|
|
5209
|
+
const existing = lstatSync(target, { throwIfNoEntry: false });
|
|
5210
|
+
if (existing === void 0) return "link";
|
|
5211
|
+
if (sameFile(target, source)) return "linked";
|
|
5212
|
+
if (existing.isSymbolicLink()) return "relink";
|
|
5213
|
+
if (existing.isDirectory() && readdirSync(target).length === 0) return "replace";
|
|
5214
|
+
return "backup";
|
|
5215
|
+
}
|
|
5216
|
+
function entrySteps(plan, item, stamp) {
|
|
5217
|
+
const target = path.join(plan.configDir, item.name);
|
|
5218
|
+
const link = {
|
|
5219
|
+
type: "link",
|
|
5220
|
+
path: target,
|
|
5221
|
+
target: path.join(plan.baseDir, item.name),
|
|
5222
|
+
folder: item.folder
|
|
5223
|
+
};
|
|
5224
|
+
switch (item.action) {
|
|
5225
|
+
case "linked":
|
|
5226
|
+
case "skip": return [];
|
|
5227
|
+
case "link": return [link];
|
|
5228
|
+
case "relink": return [{
|
|
5229
|
+
type: "unlink",
|
|
5230
|
+
path: target
|
|
5231
|
+
}, link];
|
|
5232
|
+
case "replace": return [{
|
|
5233
|
+
type: "rmdir",
|
|
5234
|
+
path: target
|
|
5235
|
+
}, link];
|
|
5236
|
+
case "backup": {
|
|
5237
|
+
const backup = `${target}.backup-${stamp}`;
|
|
5238
|
+
if (lstatSync(backup, { throwIfNoEntry: false }) !== void 0) throw new Error(`${backup} already exists`);
|
|
5239
|
+
return [{
|
|
5240
|
+
type: "move",
|
|
5241
|
+
from: target,
|
|
5242
|
+
to: backup
|
|
5243
|
+
}, link];
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
}
|
|
5247
|
+
function sameFile(first, second) {
|
|
5248
|
+
const firstStats = statSync(first, {
|
|
5249
|
+
bigint: true,
|
|
5250
|
+
throwIfNoEntry: false
|
|
5251
|
+
});
|
|
5252
|
+
const secondStats = statSync(second, {
|
|
5253
|
+
bigint: true,
|
|
5254
|
+
throwIfNoEntry: false
|
|
5255
|
+
});
|
|
5256
|
+
if (firstStats === void 0 || secondStats === void 0) return false;
|
|
5257
|
+
return firstStats.dev === secondStats.dev && firstStats.ino === secondStats.ino;
|
|
5258
|
+
}
|
|
5259
|
+
function canSymlink() {
|
|
5260
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), "offsite-"));
|
|
5261
|
+
const link = path.join(dir, "link");
|
|
5262
|
+
try {
|
|
5263
|
+
symlinkSync(dir, link, "dir");
|
|
5264
|
+
} catch (error) {
|
|
5265
|
+
rmdirSync(dir);
|
|
5266
|
+
if (error.code === "EPERM") return false;
|
|
5267
|
+
throw error;
|
|
5268
|
+
}
|
|
5269
|
+
unlinkSync(link);
|
|
5270
|
+
rmdirSync(dir);
|
|
5271
|
+
return true;
|
|
5272
|
+
}
|
|
5273
|
+
function runStep(step) {
|
|
5274
|
+
switch (step.type) {
|
|
5275
|
+
case "unlink":
|
|
5276
|
+
unlinkSync(step.path);
|
|
5277
|
+
return;
|
|
5278
|
+
case "rmdir":
|
|
5279
|
+
rmdirSync(step.path);
|
|
5280
|
+
return;
|
|
5281
|
+
case "move":
|
|
5282
|
+
renameSync(step.from, step.to);
|
|
5283
|
+
return;
|
|
5284
|
+
case "link": symlinkSync(step.target, step.path, step.folder ? "dir" : "file");
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5287
|
+
async function runElevated(steps) {
|
|
5288
|
+
const user = await run$3("whoami", [
|
|
5289
|
+
"/user",
|
|
5290
|
+
"/fo",
|
|
5291
|
+
"csv",
|
|
5292
|
+
"/nh"
|
|
5293
|
+
]);
|
|
5294
|
+
const sid = user.stdout.trim().split(",").at(-1)?.replaceAll("\"", "");
|
|
5295
|
+
if (sid === void 0 || !sid.startsWith("S-1-")) throw new Error(`Could not read your Windows user: ${user.stdout}`);
|
|
5296
|
+
const owner = `*${sid}`;
|
|
5297
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), "offsite-"));
|
|
5298
|
+
const errorFile = path.join(dir, "error.txt");
|
|
5299
|
+
const script = `const fs = require('node:fs')
|
|
5300
|
+
const childProcess = require('node:child_process')
|
|
5301
|
+
const data = ${JSON.stringify({
|
|
5302
|
+
steps,
|
|
5303
|
+
owner,
|
|
5304
|
+
errorFile
|
|
5305
|
+
})}
|
|
5306
|
+
try {
|
|
5307
|
+
for (const step of data.steps) {
|
|
5308
|
+
if (step.type === 'unlink') fs.unlinkSync(step.path)
|
|
5309
|
+
if (step.type === 'rmdir') fs.rmdirSync(step.path)
|
|
5310
|
+
if (step.type === 'move') fs.renameSync(step.from, step.to)
|
|
5311
|
+
if (step.type === 'link') {
|
|
5312
|
+
fs.symlinkSync(step.target, step.path, step.folder ? 'dir' : 'file')
|
|
5313
|
+
childProcess.execFileSync('icacls', [step.path, '/setowner', data.owner, '/L', '/Q'])
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5316
|
+
} catch (error) {
|
|
5317
|
+
fs.writeFileSync(data.errorFile, error.message)
|
|
5318
|
+
process.exit(1)
|
|
5319
|
+
}`;
|
|
5320
|
+
const code = `eval(Buffer.from('${Buffer.from(script).toString("base64")}','base64').toString())`;
|
|
5321
|
+
try {
|
|
5322
|
+
await run$3("powershell.exe", [
|
|
5323
|
+
"-NoProfile",
|
|
5324
|
+
"-NonInteractive",
|
|
5325
|
+
"-Command",
|
|
5326
|
+
`$ProgressPreference = 'SilentlyContinue'; try { $process = Start-Process -FilePath ${quote(process.execPath)} -ArgumentList '-e',${quote(code)} -Verb RunAs -WindowStyle Hidden -Wait -PassThru -ErrorAction Stop } catch { [Console]::Error.WriteLine($_.Exception.Message); exit 1 }; exit $process.ExitCode`
|
|
5327
|
+
]);
|
|
5328
|
+
} catch (error) {
|
|
5329
|
+
if (existsSync(errorFile)) throw new Error(readFileSync(errorFile, "utf8"));
|
|
5330
|
+
if (error instanceof Error && "stderr" in error && typeof error.stderr === "string" && error.stderr.trim() !== "") throw new Error(`Windows admin permission failed: ${error.stderr.trim()}`);
|
|
5331
|
+
throw error;
|
|
5332
|
+
} finally {
|
|
5333
|
+
rmSync(dir, {
|
|
5334
|
+
recursive: true,
|
|
5335
|
+
force: true
|
|
5336
|
+
});
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
function quote(value) {
|
|
5340
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
5341
|
+
}
|
|
4987
5342
|
// src/lib/claude/claude.ts
|
|
4988
5343
|
const log$8 = logger.withTag("claude");
|
|
4989
5344
|
const efforts = [
|
|
@@ -5093,7 +5448,8 @@ function runOptions(options) {
|
|
|
5093
5448
|
...settings.model === null ? {} : { model: settings.model },
|
|
5094
5449
|
...settings.effort === null ? {} : { effort: claudeEffort(settings.effort) },
|
|
5095
5450
|
allowDangerouslySkipPermissions: true,
|
|
5096
|
-
includePartialMessages: true
|
|
5451
|
+
includePartialMessages: true,
|
|
5452
|
+
perTaskStopAffordance: true
|
|
5097
5453
|
};
|
|
5098
5454
|
}
|
|
5099
5455
|
async function claudeCapabilities(account, cwd) {
|
|
@@ -5242,16 +5598,6 @@ function claudeLogin(configDir) {
|
|
|
5242
5598
|
} : null
|
|
5243
5599
|
};
|
|
5244
5600
|
}
|
|
5245
|
-
function isLinked(configDir) {
|
|
5246
|
-
const base = claudeBaseDir();
|
|
5247
|
-
try {
|
|
5248
|
-
if (realpathSync(configDir) === realpathSync(base)) return true;
|
|
5249
|
-
return realpathSync(path.join(configDir, "projects")) === realpathSync(path.join(base, "projects"));
|
|
5250
|
-
} catch (error) {
|
|
5251
|
-
if (error.code === "ENOENT") return false;
|
|
5252
|
-
throw error;
|
|
5253
|
-
}
|
|
5254
|
-
}
|
|
5255
5601
|
function initialization(accountId, cwd) {
|
|
5256
5602
|
const key = `${accountId}\n${cwd}`;
|
|
5257
5603
|
const existing = probes.get(key);
|
|
@@ -5812,10 +6158,18 @@ async function image(sessionId, uuid, index) {
|
|
|
5812
6158
|
data: block.data
|
|
5813
6159
|
};
|
|
5814
6160
|
}
|
|
6161
|
+
async function shownMessages(sessionId, cwd) {
|
|
6162
|
+
const [disk, session] = await Promise.all([getSessionMessages(sessionId, { dir: cwd }), loadSession(sessionId)]);
|
|
6163
|
+
const found = runs.get(sessionId);
|
|
6164
|
+
const saved = session?.entries.findLast((entry) => entry.type === "last-prompt" && entry.rewound === true);
|
|
6165
|
+
const cuts = [found?.provider === "claude" ? found.cut : null, saved?.cutUuid];
|
|
6166
|
+
const index = disk.findIndex((item) => cuts.includes(item.uuid));
|
|
6167
|
+
return index < 0 ? disk : disk.slice(0, index);
|
|
6168
|
+
}
|
|
5815
6169
|
async function history$1(sessionId, cwd) {
|
|
5816
6170
|
const found = runs.get(sessionId);
|
|
5817
6171
|
const run = found?.provider === "claude" ? found : void 0;
|
|
5818
|
-
const [disk, session] = await Promise.all([
|
|
6172
|
+
const [disk, session] = await Promise.all([shownMessages(sessionId, cwd), loadSession(sessionId)]);
|
|
5819
6173
|
const entries = session === null ? [] : session.entries;
|
|
5820
6174
|
const resumeAt = run?.resumeAt ?? null;
|
|
5821
6175
|
const kept = resumeAt === null ? disk : disk.slice(0, disk.findIndex((item) => item.uuid === resumeAt) + 1);
|
|
@@ -5858,6 +6212,28 @@ async function agentMessages(sessionId, cwd, agentId, parent) {
|
|
|
5858
6212
|
parent_tool_use_id: parent
|
|
5859
6213
|
}, null)]));
|
|
5860
6214
|
}
|
|
6215
|
+
// src/lib/claude/permission.ts
|
|
6216
|
+
function permissionResult(input, suggestions, decision, answers) {
|
|
6217
|
+
switch (decision) {
|
|
6218
|
+
case "allow": return {
|
|
6219
|
+
behavior: "allow",
|
|
6220
|
+
updatedInput: answers === null ? input : {
|
|
6221
|
+
...input,
|
|
6222
|
+
answers
|
|
6223
|
+
}
|
|
6224
|
+
};
|
|
6225
|
+
case "always": return {
|
|
6226
|
+
behavior: "allow",
|
|
6227
|
+
updatedInput: input,
|
|
6228
|
+
updatedPermissions: suggestions
|
|
6229
|
+
};
|
|
6230
|
+
case "deny": return {
|
|
6231
|
+
behavior: "deny",
|
|
6232
|
+
message: "The user rejected this action",
|
|
6233
|
+
interrupt: true
|
|
6234
|
+
};
|
|
6235
|
+
}
|
|
6236
|
+
}
|
|
5861
6237
|
// src/lib/claude/run.ts
|
|
5862
6238
|
const IDLE_LIMIT = 6e5;
|
|
5863
6239
|
var Run = class {
|
|
@@ -5870,6 +6246,7 @@ var Run = class {
|
|
|
5870
6246
|
query;
|
|
5871
6247
|
closed;
|
|
5872
6248
|
resumeAt;
|
|
6249
|
+
cut = null;
|
|
5873
6250
|
queue = [];
|
|
5874
6251
|
settings;
|
|
5875
6252
|
context = null;
|
|
@@ -5880,6 +6257,8 @@ var Run = class {
|
|
|
5880
6257
|
compacting = false;
|
|
5881
6258
|
backgroundTasks = 0;
|
|
5882
6259
|
backgroundAgents = 0;
|
|
6260
|
+
agentTasks = 0;
|
|
6261
|
+
failed = false;
|
|
5883
6262
|
tasks = {};
|
|
5884
6263
|
inbox = [];
|
|
5885
6264
|
wake = null;
|
|
@@ -5952,30 +6331,7 @@ var Run = class {
|
|
|
5952
6331
|
respond(requestId, decision, answers) {
|
|
5953
6332
|
const pending = this.pending.get(requestId);
|
|
5954
6333
|
if (pending === void 0) return;
|
|
5955
|
-
|
|
5956
|
-
switch (decision) {
|
|
5957
|
-
case "allow":
|
|
5958
|
-
this.settle(requestId, {
|
|
5959
|
-
behavior: "allow",
|
|
5960
|
-
updatedInput: answers === null ? input : {
|
|
5961
|
-
...input,
|
|
5962
|
-
answers
|
|
5963
|
-
}
|
|
5964
|
-
});
|
|
5965
|
-
return;
|
|
5966
|
-
case "always":
|
|
5967
|
-
this.settle(requestId, {
|
|
5968
|
-
behavior: "allow",
|
|
5969
|
-
updatedInput: input,
|
|
5970
|
-
updatedPermissions: pending.suggestions
|
|
5971
|
-
});
|
|
5972
|
-
return;
|
|
5973
|
-
case "deny": this.settle(requestId, {
|
|
5974
|
-
behavior: "deny",
|
|
5975
|
-
message: "The user rejected this action",
|
|
5976
|
-
interrupt: true
|
|
5977
|
-
});
|
|
5978
|
-
}
|
|
6334
|
+
this.settle(requestId, permissionResult(pending.input, pending.suggestions, decision, answers));
|
|
5979
6335
|
}
|
|
5980
6336
|
stop() {
|
|
5981
6337
|
this.stopping = true;
|
|
@@ -6069,7 +6425,9 @@ var Run = class {
|
|
|
6069
6425
|
return;
|
|
6070
6426
|
}
|
|
6071
6427
|
case "result": {
|
|
6072
|
-
|
|
6428
|
+
const failure = resultNotice(message);
|
|
6429
|
+
if (failure !== null) this.add(failure);
|
|
6430
|
+
this.failed = turnFailed(message);
|
|
6073
6431
|
this.finishTurn();
|
|
6074
6432
|
const usage = this.model === null ? void 0 : message.modelUsage[this.model];
|
|
6075
6433
|
if (this.context !== null && usage !== void 0) this.context = {
|
|
@@ -6091,6 +6449,7 @@ var Run = class {
|
|
|
6091
6449
|
});
|
|
6092
6450
|
return;
|
|
6093
6451
|
case "background_tasks_changed": {
|
|
6452
|
+
this.agentTasks = message.tasks.filter((task) => task.task_type !== "local_bash").length;
|
|
6094
6453
|
const live = message.tasks.filter((task) => task.ambient !== true);
|
|
6095
6454
|
this.backgroundTasks = live.length;
|
|
6096
6455
|
this.backgroundAgents = live.filter((task) => task.task_type === "local_agent").length;
|
|
@@ -6237,6 +6596,30 @@ var Run = class {
|
|
|
6237
6596
|
});
|
|
6238
6597
|
}
|
|
6239
6598
|
};
|
|
6599
|
+
// src/lib/claude/rewind.ts
|
|
6600
|
+
const ATTEMPTS = 10;
|
|
6601
|
+
const answerSchema = z.object({ response: z.discriminatedUnion("rewound", [z.object({ rewound: z.literal(true) }), z.object({
|
|
6602
|
+
rewound: z.literal(false),
|
|
6603
|
+
reason: z.string(),
|
|
6604
|
+
error: z.string()
|
|
6605
|
+
})]) });
|
|
6606
|
+
async function rewindLive(run, messageId, lastSeenId) {
|
|
6607
|
+
const request = Reflect.get(run.query, "request");
|
|
6608
|
+
if (typeof request !== "function") throw new Error("This Claude Code version cannot rewind a live session");
|
|
6609
|
+
for (let attempt = 1;; attempt++) {
|
|
6610
|
+
const answer = answerSchema.parse(await Reflect.apply(request, run.query, [{
|
|
6611
|
+
subtype: "rewind_conversation",
|
|
6612
|
+
target_message_uuid: messageId,
|
|
6613
|
+
last_seen_user_message_uuid: lastSeenId
|
|
6614
|
+
}])).response;
|
|
6615
|
+
if (answer.rewound) break;
|
|
6616
|
+
if (answer.reason !== "turn_running" || attempt === ATTEMPTS) throw new Error(`Claude Code could not rewind: ${answer.error}`);
|
|
6617
|
+
await setTimeout$1(500);
|
|
6618
|
+
}
|
|
6619
|
+
const index = run.messages.findIndex((item) => item.uuid === messageId);
|
|
6620
|
+
run.messages.splice(index < 0 ? 0 : index);
|
|
6621
|
+
run.cut = messageId;
|
|
6622
|
+
}
|
|
6240
6623
|
// src/lib/proxy/sites.ts
|
|
6241
6624
|
const sites = /* @__PURE__ */ new Map();
|
|
6242
6625
|
const siteNames = /* @__PURE__ */ new Map();
|
|
@@ -6388,7 +6771,6 @@ async function rewindNow(request) {
|
|
|
6388
6771
|
const sessionId = request.sessionId;
|
|
6389
6772
|
const cwd = projectPath(request.projectId);
|
|
6390
6773
|
if (runs.get(sessionId)?.running === true) throw new Error(`Stop ${providerName(providerOf(sessionId))} before rewinding`);
|
|
6391
|
-
if ((runs.get(sessionId)?.backgroundTasks ?? 0) > 0) throw new Error("Wait for background tasks to finish before rewinding");
|
|
6392
6774
|
const run = await runFor(sessionId, cwd, request.settings);
|
|
6393
6775
|
if (run.provider === "codex") {
|
|
6394
6776
|
if (request.code) throw new Error("Codex cannot restore files");
|
|
@@ -6418,9 +6800,16 @@ async function rewindNow(request) {
|
|
|
6418
6800
|
};
|
|
6419
6801
|
}
|
|
6420
6802
|
if (request.dryRun || !request.conversation) return result;
|
|
6421
|
-
const disk = await
|
|
6803
|
+
const disk = await shownMessages(sessionId, cwd);
|
|
6422
6804
|
const index = disk.findIndex((item) => item.uuid === request.messageId);
|
|
6423
6805
|
if (index < 1) throw new Error("Cannot rewind the conversation to that point");
|
|
6806
|
+
if (run.backgroundTasks > 0) {
|
|
6807
|
+
const lastSeen = disk.findLast((item) => item.type === "user" && item.parent_tool_use_id === null);
|
|
6808
|
+
if (lastSeen === void 0) throw new Error("Cannot rewind the conversation to that point");
|
|
6809
|
+
await rewindLive(run, request.messageId, lastSeen.uuid);
|
|
6810
|
+
publish$2(sessionId, await history$1(sessionId, cwd));
|
|
6811
|
+
return result;
|
|
6812
|
+
}
|
|
6424
6813
|
run.stop();
|
|
6425
6814
|
await run.closed;
|
|
6426
6815
|
new Run({
|
|
@@ -6471,7 +6860,7 @@ async function submitFirst(client, run, request, projectId) {
|
|
|
6471
6860
|
from: request.projectId
|
|
6472
6861
|
});
|
|
6473
6862
|
await run.submit(request.prompt, request.delivery);
|
|
6474
|
-
recordInteraction(sessionId, run.provider);
|
|
6863
|
+
recordInteraction(sessionId, run.provider, request.status);
|
|
6475
6864
|
}
|
|
6476
6865
|
async function sendExisting(client, sessionId, cwd, request) {
|
|
6477
6866
|
const provider = providerOf(sessionId);
|
|
@@ -6485,7 +6874,7 @@ async function sendExisting(client, sessionId, cwd, request) {
|
|
|
6485
6874
|
if (run === existing) await run.configure(request.settings);
|
|
6486
6875
|
else await open$1(client, sessionId, request.projectId);
|
|
6487
6876
|
await run.submit(request.prompt, request.delivery);
|
|
6488
|
-
recordInteraction(sessionId, provider);
|
|
6877
|
+
recordInteraction(sessionId, provider, request.status);
|
|
6489
6878
|
if (emptied.has(sessionId) && request.prompt.text.trim() !== "") {
|
|
6490
6879
|
emptied.delete(sessionId);
|
|
6491
6880
|
await updateSession(sessionId, { title: request.prompt.text.trim() });
|
|
@@ -6535,7 +6924,7 @@ async function startRun(cwd, settings) {
|
|
|
6535
6924
|
}
|
|
6536
6925
|
async function changedElsewhere(run, cwd) {
|
|
6537
6926
|
if (run.messages.length === 0) return false;
|
|
6538
|
-
const last = (await
|
|
6927
|
+
const last = (await shownMessages(run.sessionId, cwd)).at(-1)?.uuid;
|
|
6539
6928
|
return last !== void 0 && !run.messages.some((item) => item.uuid === last);
|
|
6540
6929
|
}
|
|
6541
6930
|
// src/services/export.ts
|
|
@@ -6675,7 +7064,7 @@ async function searchMessages(query) {
|
|
|
6675
7064
|
"claude",
|
|
6676
7065
|
"codex",
|
|
6677
7066
|
"opencode"
|
|
6678
|
-
].filter((provider) => readConfig()
|
|
7067
|
+
].filter((provider) => readConfig()[provider].enabled).map(async (provider) => {
|
|
6679
7068
|
try {
|
|
6680
7069
|
return {
|
|
6681
7070
|
provider,
|
|
@@ -8203,8 +8592,8 @@ const handlers = {
|
|
|
8203
8592
|
"claude",
|
|
8204
8593
|
"codex",
|
|
8205
8594
|
"opencode"
|
|
8206
|
-
]) if (before
|
|
8207
|
-
if (JSON.stringify(saved.
|
|
8595
|
+
]) if (before[provider].enabled && !saved[provider].enabled) await stopProvider(provider);
|
|
8596
|
+
if (JSON.stringify(saved.claude.baseDir) === JSON.stringify(before.claude.baseDir) && saved.claude.enabled === before.claude.enabled && saved.codex.enabled === before.codex.enabled && saved.opencode.enabled === before.opencode.enabled) return saved;
|
|
8208
8597
|
watchSessions();
|
|
8209
8598
|
refreshAll();
|
|
8210
8599
|
publishAccounts();
|
|
@@ -8291,7 +8680,7 @@ const handlers = {
|
|
|
8291
8680
|
const run = runs.get(params.sessionId);
|
|
8292
8681
|
if (run === void 0) return null;
|
|
8293
8682
|
await run.respond(params.requestId, params.decision, params.answers);
|
|
8294
|
-
recordInteraction(params.sessionId, run.provider);
|
|
8683
|
+
recordInteraction(params.sessionId, run.provider, null);
|
|
8295
8684
|
return null;
|
|
8296
8685
|
},
|
|
8297
8686
|
"sessions.rewind": (params) => rewind(params),
|
|
@@ -8301,21 +8690,11 @@ const handlers = {
|
|
|
8301
8690
|
return result;
|
|
8302
8691
|
},
|
|
8303
8692
|
"accounts.list": () => accounts(),
|
|
8304
|
-
"accounts.add": (params) =>
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
"accounts.update": async (params) => {
|
|
8310
|
-
await updateAccount(params);
|
|
8311
|
-
publishAccounts();
|
|
8312
|
-
return null;
|
|
8313
|
-
},
|
|
8314
|
-
"accounts.remove": (params) => {
|
|
8315
|
-
removeAccount(params.id);
|
|
8316
|
-
publishAccounts();
|
|
8317
|
-
return null;
|
|
8318
|
-
},
|
|
8693
|
+
"accounts.add": (params) => addAccount(params.provider, params.name, params.configDir),
|
|
8694
|
+
"accounts.update": (params) => updateAccount(params),
|
|
8695
|
+
"accounts.remove": (params) => removeAccount(params.id),
|
|
8696
|
+
"accounts.link": (params) => linkAccount(params.id),
|
|
8697
|
+
"accounts.linkPlan": (params) => accountLinkPlan(params.id),
|
|
8319
8698
|
"accounts.usage": (params) => readUsage(params.id, params.force),
|
|
8320
8699
|
capabilities: (params) => capabilities(params.provider, params.accountId, params.projectId === null || params.projectId === "no-project" ? os.homedir() : projectPath(params.projectId)),
|
|
8321
8700
|
"files.list": (params) => params.projectId === "no-project" ? [] : listFiles(projectPath(params.projectId)),
|
|
@@ -9700,7 +10079,7 @@ async function backfillSessionProviders() {
|
|
|
9700
10079
|
const rows = db.select().from(sessions$1).where(isNull(sessions$1.provider)).all();
|
|
9701
10080
|
if (rows.length === 0) return;
|
|
9702
10081
|
const paths = new Map(projects().map((project) => [project.path, project.id]));
|
|
9703
|
-
const enabled = allProviders.filter((provider) => readConfig()
|
|
10082
|
+
const enabled = allProviders.filter((provider) => readConfig()[provider].enabled);
|
|
9704
10083
|
for (const row of rows) {
|
|
9705
10084
|
const answers = await Promise.all(enabled.map(async (provider) => {
|
|
9706
10085
|
try {
|