offsite 0.0.94 → 0.0.96
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-sNE7wlGT.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-D9sGyf1Y.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-De_ijtFY.js} +7 -7
- package/apps/client/dist/assets/{image-viewer-3S4rNjFt.js → image-viewer-DuyADV75.js} +1 -1
- package/apps/client/dist/assets/index-9ox1jth4.js +72 -0
- package/apps/client/dist/assets/index-dtN3QMc8.css +2 -0
- package/apps/client/dist/assets/panel-close-button-yIEA8ujQ.js +1 -0
- package/apps/client/dist/assets/{preview-dialog-BuSSuMeQ.js → preview-dialog-CPV2cjGd.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-BoixVvhO.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-DbYvGHLh.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 +582 -150
- package/apps/server/dist/main.mjs.map +1 -1
- package/apps/server/drizzle/20261108212949_last_efforts/migration.sql +6 -0
- package/apps/server/drizzle/20261108212949_last_efforts/snapshot.json +495 -0
- 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";
|
|
@@ -22,7 +22,7 @@ import { sumBy } from "es-toolkit";
|
|
|
22
22
|
import { DatabaseSync } from "node:sqlite";
|
|
23
23
|
import { drizzle } from "drizzle-orm/node-sqlite";
|
|
24
24
|
import { migrate } from "drizzle-orm/node-sqlite/migrator";
|
|
25
|
-
import { integer, real, snakeCase, text } from "drizzle-orm/sqlite-core";
|
|
25
|
+
import { integer, primaryKey, real, snakeCase, text } from "drizzle-orm/sqlite-core";
|
|
26
26
|
import sharp from "sharp";
|
|
27
27
|
import webPush from "web-push";
|
|
28
28
|
import dns from "node:dns";
|
|
@@ -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;
|
|
@@ -1130,10 +1165,7 @@ function record$1(value) {
|
|
|
1130
1165
|
return isRecord(value) ? value : { value };
|
|
1131
1166
|
}
|
|
1132
1167
|
// src/storage/config.ts
|
|
1133
|
-
const modelSetting = z.strictObject({
|
|
1134
|
-
hidden: z.boolean(),
|
|
1135
|
-
effort: z.string().nullable()
|
|
1136
|
-
});
|
|
1168
|
+
const modelSetting = z.strictObject({ hidden: z.boolean() });
|
|
1137
1169
|
const generationModel = z.strictObject({
|
|
1138
1170
|
provider: z.enum([
|
|
1139
1171
|
"claude",
|
|
@@ -1162,27 +1194,31 @@ function accountSchema(provider) {
|
|
|
1162
1194
|
enabled: z.boolean().default(true)
|
|
1163
1195
|
});
|
|
1164
1196
|
}
|
|
1197
|
+
const modelSettings = z.record(z.string(), modelSetting).default({});
|
|
1165
1198
|
const configSchema = z.strictObject({
|
|
1166
|
-
|
|
1199
|
+
version: z.literal(2).default(2),
|
|
1200
|
+
claude: z.strictObject({
|
|
1201
|
+
enabled: z.boolean().default(true),
|
|
1202
|
+
baseDir: platformDirs,
|
|
1203
|
+
accounts: z.array(accountSchema("claude")).default([]),
|
|
1204
|
+
models: modelSettings
|
|
1205
|
+
}).prefault({}),
|
|
1206
|
+
codex: z.strictObject({
|
|
1207
|
+
enabled: z.boolean().default(true),
|
|
1208
|
+
accounts: z.array(accountSchema("codex")).default([]),
|
|
1209
|
+
models: modelSettings
|
|
1210
|
+
}).prefault({}),
|
|
1211
|
+
opencode: z.strictObject({
|
|
1212
|
+
enabled: z.boolean().default(true),
|
|
1213
|
+
models: modelSettings
|
|
1214
|
+
}).prefault({}),
|
|
1167
1215
|
workingDir: z.strictObject({
|
|
1168
1216
|
macos: workingDir(path.posix.isAbsolute),
|
|
1169
1217
|
linux: workingDir(path.posix.isAbsolute),
|
|
1170
1218
|
windows: workingDir(path.win32.isAbsolute)
|
|
1171
1219
|
}).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
1220
|
commitMessage: generationModel,
|
|
1180
1221
|
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
1222
|
delivery: z.enum(["queue", "steer"]).default("steer"),
|
|
1187
1223
|
autoSkipQuestionTimeout: z.number().positive().nullable().default(180),
|
|
1188
1224
|
autoPermissionMode: z.enum([
|
|
@@ -1216,7 +1252,7 @@ function platform() {
|
|
|
1216
1252
|
throw new Error(`unsupported platform ${process.platform}`);
|
|
1217
1253
|
}
|
|
1218
1254
|
function claudeBaseDir() {
|
|
1219
|
-
return readConfig().
|
|
1255
|
+
return readConfig().claude.baseDir[platform()] ?? path.join(os.homedir(), ".claude");
|
|
1220
1256
|
}
|
|
1221
1257
|
function workingDirectory() {
|
|
1222
1258
|
const dir = readConfig().workingDir[platform()] ?? "~";
|
|
@@ -1234,11 +1270,42 @@ function configInfo() {
|
|
|
1234
1270
|
defaultShell: defaultShell(),
|
|
1235
1271
|
defaultClaudeBaseDir: path.join(os.homedir(), ".claude"),
|
|
1236
1272
|
homeDir: os.homedir(),
|
|
1237
|
-
scratchAvailable
|
|
1273
|
+
scratchAvailable,
|
|
1274
|
+
version: "v0.0.96"
|
|
1238
1275
|
};
|
|
1239
1276
|
}
|
|
1240
1277
|
function readConfig() {
|
|
1241
|
-
return readSettings("config.json", configSchema, defaults$1);
|
|
1278
|
+
return readSettings("config.json", configSchema, defaults$1, migrate$1);
|
|
1279
|
+
}
|
|
1280
|
+
function migrate$1(value) {
|
|
1281
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return value;
|
|
1282
|
+
if (!("version" in value)) return fromVersion1(value);
|
|
1283
|
+
if (value.version === 2) return value;
|
|
1284
|
+
throw new Error(`config.json has version ${String(value.version)}, which this version of offsite cannot read. Update offsite`);
|
|
1285
|
+
}
|
|
1286
|
+
function fromVersion1(value) {
|
|
1287
|
+
const { claudeBaseDir, claudeAccounts, codexAccounts, enabledProviders, models, ...rest } = value;
|
|
1288
|
+
const enabled = enabledProviders ?? {};
|
|
1289
|
+
const providerModels = models ?? {};
|
|
1290
|
+
return {
|
|
1291
|
+
...rest,
|
|
1292
|
+
version: 2,
|
|
1293
|
+
claude: {
|
|
1294
|
+
enabled: enabled.claude,
|
|
1295
|
+
baseDir: claudeBaseDir,
|
|
1296
|
+
accounts: claudeAccounts,
|
|
1297
|
+
models: providerModels.claude
|
|
1298
|
+
},
|
|
1299
|
+
codex: {
|
|
1300
|
+
enabled: enabled.codex,
|
|
1301
|
+
accounts: codexAccounts,
|
|
1302
|
+
models: providerModels.codex
|
|
1303
|
+
},
|
|
1304
|
+
opencode: {
|
|
1305
|
+
enabled: enabled.opencode,
|
|
1306
|
+
models: providerModels.opencode
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1242
1309
|
}
|
|
1243
1310
|
function saveConfig(value) {
|
|
1244
1311
|
const result = configSchema.safeParse(value);
|
|
@@ -1266,6 +1333,7 @@ function activity() {
|
|
|
1266
1333
|
prompted: run.prompted,
|
|
1267
1334
|
compacting: run.compacting,
|
|
1268
1335
|
backgroundAgent: run.backgroundAgents > 0,
|
|
1336
|
+
holdsCompletion: !run.failed && run.agentTasks > 0,
|
|
1269
1337
|
tasks: run.tasks
|
|
1270
1338
|
};
|
|
1271
1339
|
return result;
|
|
@@ -1314,6 +1382,24 @@ function scheduleAutoResolve(sessionId, request) {
|
|
|
1314
1382
|
}
|
|
1315
1383
|
};
|
|
1316
1384
|
}
|
|
1385
|
+
// src/lib/codex/agents.ts
|
|
1386
|
+
function trackAgents(agents, method, params) {
|
|
1387
|
+
const threadId = params.threadId;
|
|
1388
|
+
if (method === "turn/started" && agents.has(threadId)) agents.set(threadId, true);
|
|
1389
|
+
if (method === "turn/completed" && agents.has(threadId)) agents.set(threadId, false);
|
|
1390
|
+
if (method !== "item/completed") return;
|
|
1391
|
+
const item = params.item;
|
|
1392
|
+
if (item.type === "subAgentActivity") {
|
|
1393
|
+
if (item.kind === "started" && !agents.has(item.agentThreadId)) agents.set(item.agentThreadId, true);
|
|
1394
|
+
if ((item.kind === "interrupted" || item.kind === "completed") && agents.has(item.agentThreadId)) agents.set(item.agentThreadId, false);
|
|
1395
|
+
}
|
|
1396
|
+
if (item.type === "collabAgentToolCall") {
|
|
1397
|
+
if (item.tool === "spawnAgent") {
|
|
1398
|
+
for (const agent of item.receiverThreadIds) if (!agents.has(agent)) agents.set(agent, true);
|
|
1399
|
+
}
|
|
1400
|
+
for (const [agent, state] of Object.entries(item.agentsStates)) if (agents.get(agent) === true) agents.set(agent, state.status === "pendingInit" || state.status === "running");
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1317
1403
|
// src/lib/codex/presets.ts
|
|
1318
1404
|
const presets = /* @__PURE__ */ new Map([
|
|
1319
1405
|
["read-only", {
|
|
@@ -1466,11 +1552,13 @@ var CodexRun = class CodexRun {
|
|
|
1466
1552
|
compacting = false;
|
|
1467
1553
|
backgroundTasks = 0;
|
|
1468
1554
|
backgroundAgents = 0;
|
|
1555
|
+
failed = false;
|
|
1469
1556
|
tasks = {};
|
|
1470
1557
|
activeAt = Date.now();
|
|
1471
1558
|
model;
|
|
1472
1559
|
close;
|
|
1473
1560
|
turnId = null;
|
|
1561
|
+
agents = /* @__PURE__ */ new Map();
|
|
1474
1562
|
idleTimer;
|
|
1475
1563
|
static async start(cwd, settings) {
|
|
1476
1564
|
const server = codexServer(settings);
|
|
@@ -1579,6 +1667,7 @@ var CodexRun = class CodexRun {
|
|
|
1579
1667
|
}
|
|
1580
1668
|
notify(method, params) {
|
|
1581
1669
|
this.activeAt = Date.now();
|
|
1670
|
+
this.notifyAgent(method, params);
|
|
1582
1671
|
switch (method) {
|
|
1583
1672
|
case "turn/started": {
|
|
1584
1673
|
const turn = params.turn;
|
|
@@ -1592,6 +1681,7 @@ var CodexRun = class CodexRun {
|
|
|
1592
1681
|
case "turn/completed": {
|
|
1593
1682
|
const turn = params.turn;
|
|
1594
1683
|
if (turn.status === "failed" && turn.error !== null) this.put(errorItem(`${turn.id}:error`, turn.error.message));
|
|
1684
|
+
this.failed = turn.status === "failed" || turn.status === "inProgress";
|
|
1595
1685
|
this.finishTurn();
|
|
1596
1686
|
return;
|
|
1597
1687
|
}
|
|
@@ -1657,6 +1747,14 @@ var CodexRun = class CodexRun {
|
|
|
1657
1747
|
case "serverRequest/resolved": this.settle(String(params.requestId));
|
|
1658
1748
|
}
|
|
1659
1749
|
}
|
|
1750
|
+
notifyAgent(method, params) {
|
|
1751
|
+
const running = this.agentTasks;
|
|
1752
|
+
trackAgents(this.agents, method, params);
|
|
1753
|
+
if (this.agentTasks !== running) broadcastActivity();
|
|
1754
|
+
}
|
|
1755
|
+
get agentTasks() {
|
|
1756
|
+
return [...this.agents.values()].filter((running) => running).length;
|
|
1757
|
+
}
|
|
1660
1758
|
request(id, method, params) {
|
|
1661
1759
|
if (method === "mcpServer/elicitation/request") {
|
|
1662
1760
|
this.server.reply(id, {
|
|
@@ -2667,6 +2765,8 @@ var OpenCodeRun = class OpenCodeRun {
|
|
|
2667
2765
|
compacting = false;
|
|
2668
2766
|
backgroundTasks = 0;
|
|
2669
2767
|
backgroundAgents = 0;
|
|
2768
|
+
agentTasks = 0;
|
|
2769
|
+
failed = false;
|
|
2670
2770
|
tasks = {};
|
|
2671
2771
|
close;
|
|
2672
2772
|
applied;
|
|
@@ -3057,6 +3157,15 @@ const lastUsedModels = snakeCase.table("last_used_models", {
|
|
|
3057
3157
|
model: text().notNull(),
|
|
3058
3158
|
usedAt: integer().notNull()
|
|
3059
3159
|
});
|
|
3160
|
+
const lastEfforts = snakeCase.table("last_efforts", {
|
|
3161
|
+
provider: text({ enum: [
|
|
3162
|
+
"claude",
|
|
3163
|
+
"codex",
|
|
3164
|
+
"opencode"
|
|
3165
|
+
] }).notNull(),
|
|
3166
|
+
model: text().notNull(),
|
|
3167
|
+
effort: text()
|
|
3168
|
+
}, (table) => [primaryKey({ columns: [table.provider, table.model] })]);
|
|
3060
3169
|
// src/storage/auth.ts
|
|
3061
3170
|
const CODE_LIFETIME = 3e5;
|
|
3062
3171
|
const DEVICE_LIFETIME = 2592e6;
|
|
@@ -3443,7 +3552,7 @@ const UNTRACKED_LIMIT = 100;
|
|
|
3443
3552
|
const NEW_FILE_LIMIT = 262144;
|
|
3444
3553
|
const REPO_DEPTH = 3;
|
|
3445
3554
|
const FETCH_TIMEOUT = 6e4;
|
|
3446
|
-
const run$
|
|
3555
|
+
const run$4 = promisify(execFile);
|
|
3447
3556
|
const PULL_REQUEST_INSTRUCTIONS = `You write GitHub pull requests as Markdown.
|
|
3448
3557
|
|
|
3449
3558
|
## TITLE RULES
|
|
@@ -3475,6 +3584,7 @@ Return one Markdown document. The title is the level-1 heading (\`# <title>\`),
|
|
|
3475
3584
|
async function gitRepos(cwd) {
|
|
3476
3585
|
return Promise.all((await projectRepos(cwd)).map(async (repo) => ({
|
|
3477
3586
|
...await gitStatus(path.join(cwd, repo)),
|
|
3587
|
+
...await lineChanges(path.join(cwd, repo)),
|
|
3478
3588
|
path: repo
|
|
3479
3589
|
})));
|
|
3480
3590
|
}
|
|
@@ -3535,7 +3645,7 @@ async function sync(cwd) {
|
|
|
3535
3645
|
async function fetchProject(cwd) {
|
|
3536
3646
|
await Promise.all((await projectRepos(cwd)).map(async (repo) => {
|
|
3537
3647
|
try {
|
|
3538
|
-
await run$
|
|
3648
|
+
await run$4("git", ["fetch"], {
|
|
3539
3649
|
cwd: path.join(cwd, repo),
|
|
3540
3650
|
env: {
|
|
3541
3651
|
...process.env,
|
|
@@ -3566,7 +3676,7 @@ async function switchBranch(cwd, branch, create) {
|
|
|
3566
3676
|
async function pullRequestBranches(cwd) {
|
|
3567
3677
|
const head = (await gitStatus(cwd)).branch;
|
|
3568
3678
|
if (head === null) throw new Error("Not on a branch");
|
|
3569
|
-
const base = (await run$
|
|
3679
|
+
const base = (await run$4("gh", [
|
|
3570
3680
|
"repo",
|
|
3571
3681
|
"view",
|
|
3572
3682
|
"--json",
|
|
@@ -3591,7 +3701,7 @@ async function preparePullRequest(cwd, base, commitChanges) {
|
|
|
3591
3701
|
if (commitChanges) await commit(cwd, "", true);
|
|
3592
3702
|
const status = await gitStatus(cwd);
|
|
3593
3703
|
if (status.upstream === null || status.ahead > 0) await push$1(cwd);
|
|
3594
|
-
const open = (await run$
|
|
3704
|
+
const open = (await run$4("gh", [
|
|
3595
3705
|
"pr",
|
|
3596
3706
|
"list",
|
|
3597
3707
|
"--head",
|
|
@@ -3638,7 +3748,7 @@ async function preparePullRequest(cwd, base, commitChanges) {
|
|
|
3638
3748
|
};
|
|
3639
3749
|
}
|
|
3640
3750
|
async function createPullRequest(cwd, request) {
|
|
3641
|
-
const url = (await run$
|
|
3751
|
+
const url = (await run$4("gh", [
|
|
3642
3752
|
"pr",
|
|
3643
3753
|
"create",
|
|
3644
3754
|
"--title",
|
|
@@ -3671,7 +3781,7 @@ async function projectDiff(cwd) {
|
|
|
3671
3781
|
return (await Promise.all(repos.map((repo) => repoDiff(path.join(cwd, repo), repo === "" ? "" : `${repo}/`)))).join("");
|
|
3672
3782
|
}
|
|
3673
3783
|
function git(cwd, args) {
|
|
3674
|
-
return run$
|
|
3784
|
+
return run$4("git", args, {
|
|
3675
3785
|
cwd,
|
|
3676
3786
|
maxBuffer: 67108864
|
|
3677
3787
|
});
|
|
@@ -3708,6 +3818,19 @@ async function gitStatus(cwd) {
|
|
|
3708
3818
|
}
|
|
3709
3819
|
return status;
|
|
3710
3820
|
}
|
|
3821
|
+
async function lineChanges(repo) {
|
|
3822
|
+
let added = 0;
|
|
3823
|
+
let removed = 0;
|
|
3824
|
+
let hunk = false;
|
|
3825
|
+
for (const line of (await repoDiff(repo, "")).split("\n")) if (line.startsWith("diff --git ")) hunk = false;
|
|
3826
|
+
else if (line.startsWith("@@")) hunk = true;
|
|
3827
|
+
else if (hunk && line.startsWith("+")) added++;
|
|
3828
|
+
else if (hunk && line.startsWith("-")) removed++;
|
|
3829
|
+
return {
|
|
3830
|
+
added,
|
|
3831
|
+
removed
|
|
3832
|
+
};
|
|
3833
|
+
}
|
|
3711
3834
|
async function rebaseOnUpstream(cwd) {
|
|
3712
3835
|
try {
|
|
3713
3836
|
await git(cwd, ["rebase", "@{upstream}"]);
|
|
@@ -4130,8 +4253,10 @@ function rebuildThreads() {
|
|
|
4130
4253
|
const config = readConfig();
|
|
4131
4254
|
const key = JSON.stringify([
|
|
4132
4255
|
projectPaths$1(),
|
|
4133
|
-
config.
|
|
4134
|
-
config.
|
|
4256
|
+
config.claude.enabled,
|
|
4257
|
+
config.codex.enabled,
|
|
4258
|
+
config.opencode.enabled,
|
|
4259
|
+
config.claude.baseDir
|
|
4135
4260
|
]);
|
|
4136
4261
|
if (key === builtFor) return;
|
|
4137
4262
|
builtFor = key;
|
|
@@ -4145,7 +4270,7 @@ function threadFailed(provider) {
|
|
|
4145
4270
|
}
|
|
4146
4271
|
function knownThreads() {
|
|
4147
4272
|
const known = new Set(projects().map((project) => project.id));
|
|
4148
|
-
return allProviders$2.filter((provider) => readConfig()
|
|
4273
|
+
return allProviders$2.filter((provider) => readConfig()[provider].enabled).flatMap((provider) => [...threads[provider].values()]).filter((item) => known.has(item.projectId));
|
|
4149
4274
|
}
|
|
4150
4275
|
async function providerThreads(provider) {
|
|
4151
4276
|
await built;
|
|
@@ -4220,7 +4345,7 @@ async function relistAll() {
|
|
|
4220
4345
|
await Promise.all(allProviders$2.map((provider) => relist(provider)));
|
|
4221
4346
|
}
|
|
4222
4347
|
async function relist(provider) {
|
|
4223
|
-
if (!readConfig()
|
|
4348
|
+
if (!readConfig()[provider].enabled) {
|
|
4224
4349
|
threads[provider] = /* @__PURE__ */ new Map();
|
|
4225
4350
|
failures.delete(provider);
|
|
4226
4351
|
broadcast({ type: "threads" });
|
|
@@ -4320,7 +4445,7 @@ function withStatus(items) {
|
|
|
4320
4445
|
});
|
|
4321
4446
|
}
|
|
4322
4447
|
function enabledProviders() {
|
|
4323
|
-
return allProviders$1.filter((provider) => readConfig()
|
|
4448
|
+
return allProviders$1.filter((provider) => readConfig()[provider].enabled);
|
|
4324
4449
|
}
|
|
4325
4450
|
function projectPaths() {
|
|
4326
4451
|
return projects().map((project) => [project.path, project.id]);
|
|
@@ -4339,18 +4464,18 @@ function providerOf(sessionId) {
|
|
|
4339
4464
|
if (provider === void 0) throw new Error("offsite does not know this session yet, reload the page");
|
|
4340
4465
|
return provider;
|
|
4341
4466
|
}
|
|
4342
|
-
function recordInteraction(id, provider) {
|
|
4467
|
+
function recordInteraction(id, provider, status) {
|
|
4343
4468
|
db.insert(sessions$1).values({
|
|
4344
4469
|
sessionId: id,
|
|
4345
4470
|
provider,
|
|
4346
4471
|
interactedAt: Date.now(),
|
|
4347
|
-
status: "active"
|
|
4472
|
+
status: status ?? "active"
|
|
4348
4473
|
}).onConflictDoUpdate({
|
|
4349
4474
|
target: sessions$1.sessionId,
|
|
4350
4475
|
set: {
|
|
4351
4476
|
provider,
|
|
4352
4477
|
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`
|
|
4478
|
+
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
4479
|
}
|
|
4355
4480
|
}).run();
|
|
4356
4481
|
}
|
|
@@ -4400,7 +4525,7 @@ async function list(provider) {
|
|
|
4400
4525
|
const paths = new Map(projectPaths());
|
|
4401
4526
|
const before = statuses[provider];
|
|
4402
4527
|
try {
|
|
4403
|
-
if (!readConfig()
|
|
4528
|
+
if (!readConfig()[provider].enabled) {
|
|
4404
4529
|
listed[provider] = [];
|
|
4405
4530
|
statuses[provider] = {
|
|
4406
4531
|
provider,
|
|
@@ -4455,7 +4580,7 @@ async function lookup(provider, sessionId, paths) {
|
|
|
4455
4580
|
function watchSessions() {
|
|
4456
4581
|
for (const watcher of watchers) watcher.close();
|
|
4457
4582
|
watchers = [["claude", path.join(claudeBaseDir(), "projects")], ["codex", path.join(codexBaseDir, "sessions")]].flatMap(([provider, dir]) => {
|
|
4458
|
-
if (!readConfig()
|
|
4583
|
+
if (!readConfig()[provider].enabled) return [];
|
|
4459
4584
|
try {
|
|
4460
4585
|
const watcher = watch(dir, { recursive: true }, (_, filename) => {
|
|
4461
4586
|
fileChanged(provider, filename);
|
|
@@ -4549,7 +4674,7 @@ async function fork(sessionId, projectId, messageId) {
|
|
|
4549
4674
|
const provider = providerOf(sessionId);
|
|
4550
4675
|
const result = await forkWith(provider, sessionId, projectPath(projectId), messageId);
|
|
4551
4676
|
providers.set(result.sessionId, provider);
|
|
4552
|
-
recordInteraction(result.sessionId, provider);
|
|
4677
|
+
recordInteraction(result.sessionId, provider, null);
|
|
4553
4678
|
markChanged(provider, result.sessionId);
|
|
4554
4679
|
refreshSessions(provider);
|
|
4555
4680
|
return result;
|
|
@@ -4695,16 +4820,16 @@ function providerName(provider) {
|
|
|
4695
4820
|
}
|
|
4696
4821
|
function storedAccounts() {
|
|
4697
4822
|
const config = readConfig();
|
|
4698
|
-
return [...config.
|
|
4823
|
+
return [...config.claude.accounts.map((item) => ({
|
|
4699
4824
|
...item,
|
|
4700
4825
|
configDir: item.configDir[platform()]
|
|
4701
|
-
})), ...config.
|
|
4826
|
+
})), ...config.codex.accounts.map((item) => ({
|
|
4702
4827
|
...item,
|
|
4703
4828
|
configDir: item.configDir[platform()]
|
|
4704
4829
|
}))];
|
|
4705
4830
|
}
|
|
4706
4831
|
function assertEnabled(provider, accountId) {
|
|
4707
|
-
if (!readConfig()
|
|
4832
|
+
if (!readConfig()[provider].enabled) throw new Error(`${providerName(provider)} is disabled in Settings`);
|
|
4708
4833
|
if (provider === "opencode") return;
|
|
4709
4834
|
if (!storedAccount(provider, accountId).enabled) throw new Error("That account is disabled in Settings");
|
|
4710
4835
|
}
|
|
@@ -4734,7 +4859,7 @@ function storedAccount(provider, accountId) {
|
|
|
4734
4859
|
}
|
|
4735
4860
|
async function accounts() {
|
|
4736
4861
|
const stored = await Promise.all(storedAccounts().map((account) => {
|
|
4737
|
-
if (!account.enabled || !readConfig()
|
|
4862
|
+
if (!account.enabled || !readConfig()[account.provider].enabled) return disabledAccount(account);
|
|
4738
4863
|
switch (account.provider) {
|
|
4739
4864
|
case "claude": return claudeAccount(account);
|
|
4740
4865
|
case "codex": return codexAccount(account);
|
|
@@ -4743,7 +4868,7 @@ async function accounts() {
|
|
|
4743
4868
|
return {
|
|
4744
4869
|
claudeAccounts: stored.filter((item) => item.provider === "claude"),
|
|
4745
4870
|
codexAccounts: stored.filter((item) => item.provider === "codex"),
|
|
4746
|
-
openCode: { available: openCodeInstalled() && readConfig().
|
|
4871
|
+
openCode: { available: openCodeInstalled() && readConfig().opencode.enabled }
|
|
4747
4872
|
};
|
|
4748
4873
|
}
|
|
4749
4874
|
function disabledAccount(account) {
|
|
@@ -4772,23 +4897,20 @@ async function publishAccounts() {
|
|
|
4772
4897
|
async function capabilities(provider, accountId, cwd) {
|
|
4773
4898
|
assertEnabled(provider, accountId);
|
|
4774
4899
|
const result = await providerCapabilities(provider, accountId, cwd);
|
|
4775
|
-
const settings = readConfig()
|
|
4900
|
+
const settings = readConfig()[provider].models;
|
|
4776
4901
|
return {
|
|
4777
4902
|
...result,
|
|
4778
4903
|
defaultModel: defaultModels[provider],
|
|
4779
4904
|
models: result.models.map((model) => {
|
|
4780
4905
|
const deprecated = deprecatedModels[provider].includes(model.value);
|
|
4781
|
-
const setting = settings[model.value] ?? {
|
|
4782
|
-
hidden: deprecated,
|
|
4783
|
-
effort: null
|
|
4784
|
-
};
|
|
4906
|
+
const setting = settings[model.value] ?? { hidden: deprecated };
|
|
4785
4907
|
const preferred = defaultEfforts[provider];
|
|
4786
4908
|
const effort = preferred === null ? null : model.efforts.includes(preferred) ? preferred : model.efforts.at(-1) ?? null;
|
|
4787
4909
|
return {
|
|
4788
4910
|
...model,
|
|
4789
4911
|
deprecated,
|
|
4790
4912
|
hidden: setting.hidden,
|
|
4791
|
-
effort
|
|
4913
|
+
effort
|
|
4792
4914
|
};
|
|
4793
4915
|
})
|
|
4794
4916
|
};
|
|
@@ -4805,51 +4927,80 @@ function providerCapabilities(provider, accountId, cwd) {
|
|
|
4805
4927
|
function addAccount(provider, name, configDir) {
|
|
4806
4928
|
const account = validAccount(name, configDir);
|
|
4807
4929
|
const config = readConfig();
|
|
4808
|
-
const key = `${provider}Accounts`;
|
|
4809
4930
|
saveConfig({
|
|
4810
4931
|
...config,
|
|
4811
|
-
[
|
|
4812
|
-
|
|
4813
|
-
provider,
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4932
|
+
[provider]: {
|
|
4933
|
+
...config[provider],
|
|
4934
|
+
accounts: [...config[provider].accounts, {
|
|
4935
|
+
id: randomUUID(),
|
|
4936
|
+
provider,
|
|
4937
|
+
name: account.name,
|
|
4938
|
+
color: null,
|
|
4939
|
+
configDir: { [platform()]: account.configDir },
|
|
4940
|
+
enabled: true
|
|
4941
|
+
}]
|
|
4942
|
+
}
|
|
4819
4943
|
});
|
|
4944
|
+
publishAccounts();
|
|
4945
|
+
return null;
|
|
4820
4946
|
}
|
|
4821
4947
|
async function updateAccount(update) {
|
|
4822
4948
|
const before = storedAccounts().find((item) => item.id === update.id);
|
|
4823
4949
|
if (before === void 0) throw new Error("That account was removed");
|
|
4824
4950
|
const account = validAccount(update.name, update.configDir);
|
|
4825
4951
|
const config = readConfig();
|
|
4826
|
-
const key = `${before.provider}Accounts`;
|
|
4827
4952
|
saveConfig({
|
|
4828
4953
|
...config,
|
|
4829
|
-
[
|
|
4830
|
-
...
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4954
|
+
[before.provider]: {
|
|
4955
|
+
...config[before.provider],
|
|
4956
|
+
accounts: config[before.provider].accounts.map((item) => item.id === update.id ? {
|
|
4957
|
+
...item,
|
|
4958
|
+
name: account.name,
|
|
4959
|
+
color: update.color,
|
|
4960
|
+
configDir: {
|
|
4961
|
+
...item.configDir,
|
|
4962
|
+
[platform()]: account.configDir
|
|
4963
|
+
},
|
|
4964
|
+
enabled: update.enabled
|
|
4965
|
+
} : item)
|
|
4966
|
+
}
|
|
4839
4967
|
});
|
|
4840
4968
|
forgetClaude(update.id);
|
|
4841
4969
|
forgetUsage(update.id);
|
|
4842
4970
|
if (before.enabled && !update.enabled) await stopAccount(before);
|
|
4971
|
+
publishAccounts();
|
|
4972
|
+
return null;
|
|
4973
|
+
}
|
|
4974
|
+
function accountLinkPlan(id) {
|
|
4975
|
+
return linkPlan(storedAccount("claude", id).configDir ?? claudeBaseDir());
|
|
4976
|
+
}
|
|
4977
|
+
async function linkAccount(id) {
|
|
4978
|
+
const account = storedAccount("claude", id);
|
|
4979
|
+
await stopAccount(account);
|
|
4980
|
+
try {
|
|
4981
|
+
await linkClaude(account.configDir ?? claudeBaseDir());
|
|
4982
|
+
} finally {
|
|
4983
|
+
publishAccounts();
|
|
4984
|
+
}
|
|
4985
|
+
return null;
|
|
4843
4986
|
}
|
|
4844
4987
|
function removeAccount(id) {
|
|
4845
4988
|
const config = readConfig();
|
|
4846
4989
|
saveConfig({
|
|
4847
4990
|
...config,
|
|
4848
|
-
|
|
4849
|
-
|
|
4991
|
+
claude: {
|
|
4992
|
+
...config.claude,
|
|
4993
|
+
accounts: config.claude.accounts.filter((item) => item.id !== id)
|
|
4994
|
+
},
|
|
4995
|
+
codex: {
|
|
4996
|
+
...config.codex,
|
|
4997
|
+
accounts: config.codex.accounts.filter((item) => item.id !== id)
|
|
4998
|
+
}
|
|
4850
4999
|
});
|
|
4851
5000
|
forgetClaude(id);
|
|
4852
5001
|
forgetUsage(id);
|
|
5002
|
+
publishAccounts();
|
|
5003
|
+
return null;
|
|
4853
5004
|
}
|
|
4854
5005
|
function validAccount(name, configDir) {
|
|
4855
5006
|
if (name.trim() === "") throw new Error("Give the account a name");
|
|
@@ -4984,6 +5135,213 @@ function targets() {
|
|
|
4984
5135
|
if (notifier === null || notifier.endpoint === null) return [];
|
|
4985
5136
|
return db.select().from(pushSubscriptions).where(and(eq(pushSubscriptions.server, server$1), eq(pushSubscriptions.endpoint, notifier.endpoint))).all();
|
|
4986
5137
|
}
|
|
5138
|
+
// src/lib/claude/links.ts
|
|
5139
|
+
const run$3 = promisify(execFile);
|
|
5140
|
+
const requiredFolders = [
|
|
5141
|
+
"projects",
|
|
5142
|
+
"file-history",
|
|
5143
|
+
"tasks",
|
|
5144
|
+
"todos",
|
|
5145
|
+
"plans",
|
|
5146
|
+
"paste-cache",
|
|
5147
|
+
"session-env",
|
|
5148
|
+
"jobs",
|
|
5149
|
+
"teams",
|
|
5150
|
+
"sessions"
|
|
5151
|
+
];
|
|
5152
|
+
const sharedFolders = [
|
|
5153
|
+
"skills",
|
|
5154
|
+
"agents",
|
|
5155
|
+
"commands",
|
|
5156
|
+
"output-styles",
|
|
5157
|
+
"plugins",
|
|
5158
|
+
"cache",
|
|
5159
|
+
"state",
|
|
5160
|
+
"shell-snapshots"
|
|
5161
|
+
];
|
|
5162
|
+
const sharedFiles = [
|
|
5163
|
+
"settings.json",
|
|
5164
|
+
"CLAUDE.md",
|
|
5165
|
+
"keybindings.json",
|
|
5166
|
+
"history.jsonl"
|
|
5167
|
+
];
|
|
5168
|
+
function isLinked(configDir) {
|
|
5169
|
+
const base = claudeBaseDir();
|
|
5170
|
+
return sameFile(configDir, base) || requiredFolders.every((name) => sameFile(path.join(configDir, name), path.join(base, name)));
|
|
5171
|
+
}
|
|
5172
|
+
function linkPlan(configDir) {
|
|
5173
|
+
const base = path.resolve(claudeBaseDir());
|
|
5174
|
+
const dir = statSync(configDir, { throwIfNoEntry: false });
|
|
5175
|
+
if (dir === void 0 || !dir.isDirectory()) throw new Error(`${configDir} does not exist`);
|
|
5176
|
+
if (sameFile(configDir, base)) throw new Error(`${configDir} is the main Claude folder`);
|
|
5177
|
+
return {
|
|
5178
|
+
configDir,
|
|
5179
|
+
baseDir: base,
|
|
5180
|
+
entries: [
|
|
5181
|
+
...requiredFolders.map((name) => entry(configDir, base, name, true, true)),
|
|
5182
|
+
...sharedFolders.map((name) => entry(configDir, base, name, false, true)),
|
|
5183
|
+
...sharedFiles.map((name) => entry(configDir, base, name, false, false))
|
|
5184
|
+
]
|
|
5185
|
+
};
|
|
5186
|
+
}
|
|
5187
|
+
async function linkClaude(configDir) {
|
|
5188
|
+
const plan = linkPlan(configDir);
|
|
5189
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replaceAll(":", "-");
|
|
5190
|
+
const steps = plan.entries.flatMap((item) => entrySteps(plan, item, stamp));
|
|
5191
|
+
if (steps.length === 0) return;
|
|
5192
|
+
for (const item of plan.entries) if (item.createInBase) mkdirSync(path.join(plan.baseDir, item.name), { recursive: true });
|
|
5193
|
+
if (process.platform === "win32" && !canSymlink()) await runElevated(steps);
|
|
5194
|
+
else for (const step of steps) runStep(step);
|
|
5195
|
+
const missing = plan.entries.filter((item) => item.action !== "skip" && !sameFile(path.join(plan.configDir, item.name), path.join(plan.baseDir, item.name)));
|
|
5196
|
+
if (missing.length > 0) throw new Error(`Could not link ${missing.map((item) => item.name).join(", ")}`);
|
|
5197
|
+
}
|
|
5198
|
+
function entry(configDir, base, name, required, folder) {
|
|
5199
|
+
const target = path.join(configDir, name);
|
|
5200
|
+
const source = path.join(base, name);
|
|
5201
|
+
const sourceExists = statSync(source, { throwIfNoEntry: false }) !== void 0;
|
|
5202
|
+
return {
|
|
5203
|
+
name,
|
|
5204
|
+
required,
|
|
5205
|
+
folder,
|
|
5206
|
+
action: entryAction(target, source, folder, sourceExists),
|
|
5207
|
+
createInBase: folder && !sourceExists
|
|
5208
|
+
};
|
|
5209
|
+
}
|
|
5210
|
+
function entryAction(target, source, folder, sourceExists) {
|
|
5211
|
+
if (!folder && !sourceExists) return "skip";
|
|
5212
|
+
const existing = lstatSync(target, { throwIfNoEntry: false });
|
|
5213
|
+
if (existing === void 0) return "link";
|
|
5214
|
+
if (sameFile(target, source)) return "linked";
|
|
5215
|
+
if (existing.isSymbolicLink()) return "relink";
|
|
5216
|
+
if (existing.isDirectory() && readdirSync(target).length === 0) return "replace";
|
|
5217
|
+
return "backup";
|
|
5218
|
+
}
|
|
5219
|
+
function entrySteps(plan, item, stamp) {
|
|
5220
|
+
const target = path.join(plan.configDir, item.name);
|
|
5221
|
+
const link = {
|
|
5222
|
+
type: "link",
|
|
5223
|
+
path: target,
|
|
5224
|
+
target: path.join(plan.baseDir, item.name),
|
|
5225
|
+
folder: item.folder
|
|
5226
|
+
};
|
|
5227
|
+
switch (item.action) {
|
|
5228
|
+
case "linked":
|
|
5229
|
+
case "skip": return [];
|
|
5230
|
+
case "link": return [link];
|
|
5231
|
+
case "relink": return [{
|
|
5232
|
+
type: "unlink",
|
|
5233
|
+
path: target
|
|
5234
|
+
}, link];
|
|
5235
|
+
case "replace": return [{
|
|
5236
|
+
type: "rmdir",
|
|
5237
|
+
path: target
|
|
5238
|
+
}, link];
|
|
5239
|
+
case "backup": {
|
|
5240
|
+
const backup = `${target}.backup-${stamp}`;
|
|
5241
|
+
if (lstatSync(backup, { throwIfNoEntry: false }) !== void 0) throw new Error(`${backup} already exists`);
|
|
5242
|
+
return [{
|
|
5243
|
+
type: "move",
|
|
5244
|
+
from: target,
|
|
5245
|
+
to: backup
|
|
5246
|
+
}, link];
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
function sameFile(first, second) {
|
|
5251
|
+
const firstStats = statSync(first, {
|
|
5252
|
+
bigint: true,
|
|
5253
|
+
throwIfNoEntry: false
|
|
5254
|
+
});
|
|
5255
|
+
const secondStats = statSync(second, {
|
|
5256
|
+
bigint: true,
|
|
5257
|
+
throwIfNoEntry: false
|
|
5258
|
+
});
|
|
5259
|
+
if (firstStats === void 0 || secondStats === void 0) return false;
|
|
5260
|
+
return firstStats.dev === secondStats.dev && firstStats.ino === secondStats.ino;
|
|
5261
|
+
}
|
|
5262
|
+
function canSymlink() {
|
|
5263
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), "offsite-"));
|
|
5264
|
+
const link = path.join(dir, "link");
|
|
5265
|
+
try {
|
|
5266
|
+
symlinkSync(dir, link, "dir");
|
|
5267
|
+
} catch (error) {
|
|
5268
|
+
rmdirSync(dir);
|
|
5269
|
+
if (error.code === "EPERM") return false;
|
|
5270
|
+
throw error;
|
|
5271
|
+
}
|
|
5272
|
+
unlinkSync(link);
|
|
5273
|
+
rmdirSync(dir);
|
|
5274
|
+
return true;
|
|
5275
|
+
}
|
|
5276
|
+
function runStep(step) {
|
|
5277
|
+
switch (step.type) {
|
|
5278
|
+
case "unlink":
|
|
5279
|
+
unlinkSync(step.path);
|
|
5280
|
+
return;
|
|
5281
|
+
case "rmdir":
|
|
5282
|
+
rmdirSync(step.path);
|
|
5283
|
+
return;
|
|
5284
|
+
case "move":
|
|
5285
|
+
renameSync(step.from, step.to);
|
|
5286
|
+
return;
|
|
5287
|
+
case "link": symlinkSync(step.target, step.path, step.folder ? "dir" : "file");
|
|
5288
|
+
}
|
|
5289
|
+
}
|
|
5290
|
+
async function runElevated(steps) {
|
|
5291
|
+
const user = await run$3("whoami", [
|
|
5292
|
+
"/user",
|
|
5293
|
+
"/fo",
|
|
5294
|
+
"csv",
|
|
5295
|
+
"/nh"
|
|
5296
|
+
]);
|
|
5297
|
+
const sid = user.stdout.trim().split(",").at(-1)?.replaceAll("\"", "");
|
|
5298
|
+
if (sid === void 0 || !sid.startsWith("S-1-")) throw new Error(`Could not read your Windows user: ${user.stdout}`);
|
|
5299
|
+
const owner = `*${sid}`;
|
|
5300
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), "offsite-"));
|
|
5301
|
+
const errorFile = path.join(dir, "error.txt");
|
|
5302
|
+
const script = `const fs = require('node:fs')
|
|
5303
|
+
const childProcess = require('node:child_process')
|
|
5304
|
+
const data = ${JSON.stringify({
|
|
5305
|
+
steps,
|
|
5306
|
+
owner,
|
|
5307
|
+
errorFile
|
|
5308
|
+
})}
|
|
5309
|
+
try {
|
|
5310
|
+
for (const step of data.steps) {
|
|
5311
|
+
if (step.type === 'unlink') fs.unlinkSync(step.path)
|
|
5312
|
+
if (step.type === 'rmdir') fs.rmdirSync(step.path)
|
|
5313
|
+
if (step.type === 'move') fs.renameSync(step.from, step.to)
|
|
5314
|
+
if (step.type === 'link') {
|
|
5315
|
+
fs.symlinkSync(step.target, step.path, step.folder ? 'dir' : 'file')
|
|
5316
|
+
childProcess.execFileSync('icacls', [step.path, '/setowner', data.owner, '/L', '/Q'])
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
} catch (error) {
|
|
5320
|
+
fs.writeFileSync(data.errorFile, error.message)
|
|
5321
|
+
process.exit(1)
|
|
5322
|
+
}`;
|
|
5323
|
+
const code = `eval(Buffer.from('${Buffer.from(script).toString("base64")}','base64').toString())`;
|
|
5324
|
+
try {
|
|
5325
|
+
await run$3("powershell.exe", [
|
|
5326
|
+
"-NoProfile",
|
|
5327
|
+
"-NonInteractive",
|
|
5328
|
+
"-Command",
|
|
5329
|
+
`$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`
|
|
5330
|
+
]);
|
|
5331
|
+
} catch (error) {
|
|
5332
|
+
if (existsSync(errorFile)) throw new Error(readFileSync(errorFile, "utf8"));
|
|
5333
|
+
if (error instanceof Error && "stderr" in error && typeof error.stderr === "string" && error.stderr.trim() !== "") throw new Error(`Windows admin permission failed: ${error.stderr.trim()}`);
|
|
5334
|
+
throw error;
|
|
5335
|
+
} finally {
|
|
5336
|
+
rmSync(dir, {
|
|
5337
|
+
recursive: true,
|
|
5338
|
+
force: true
|
|
5339
|
+
});
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
5342
|
+
function quote(value) {
|
|
5343
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
5344
|
+
}
|
|
4987
5345
|
// src/lib/claude/claude.ts
|
|
4988
5346
|
const log$8 = logger.withTag("claude");
|
|
4989
5347
|
const efforts = [
|
|
@@ -5093,7 +5451,8 @@ function runOptions(options) {
|
|
|
5093
5451
|
...settings.model === null ? {} : { model: settings.model },
|
|
5094
5452
|
...settings.effort === null ? {} : { effort: claudeEffort(settings.effort) },
|
|
5095
5453
|
allowDangerouslySkipPermissions: true,
|
|
5096
|
-
includePartialMessages: true
|
|
5454
|
+
includePartialMessages: true,
|
|
5455
|
+
perTaskStopAffordance: true
|
|
5097
5456
|
};
|
|
5098
5457
|
}
|
|
5099
5458
|
async function claudeCapabilities(account, cwd) {
|
|
@@ -5242,16 +5601,6 @@ function claudeLogin(configDir) {
|
|
|
5242
5601
|
} : null
|
|
5243
5602
|
};
|
|
5244
5603
|
}
|
|
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
5604
|
function initialization(accountId, cwd) {
|
|
5256
5605
|
const key = `${accountId}\n${cwd}`;
|
|
5257
5606
|
const existing = probes.get(key);
|
|
@@ -5812,10 +6161,18 @@ async function image(sessionId, uuid, index) {
|
|
|
5812
6161
|
data: block.data
|
|
5813
6162
|
};
|
|
5814
6163
|
}
|
|
6164
|
+
async function shownMessages(sessionId, cwd) {
|
|
6165
|
+
const [disk, session] = await Promise.all([getSessionMessages(sessionId, { dir: cwd }), loadSession(sessionId)]);
|
|
6166
|
+
const found = runs.get(sessionId);
|
|
6167
|
+
const saved = session?.entries.findLast((entry) => entry.type === "last-prompt" && entry.rewound === true);
|
|
6168
|
+
const cuts = [found?.provider === "claude" ? found.cut : null, saved?.cutUuid];
|
|
6169
|
+
const index = disk.findIndex((item) => cuts.includes(item.uuid));
|
|
6170
|
+
return index < 0 ? disk : disk.slice(0, index);
|
|
6171
|
+
}
|
|
5815
6172
|
async function history$1(sessionId, cwd) {
|
|
5816
6173
|
const found = runs.get(sessionId);
|
|
5817
6174
|
const run = found?.provider === "claude" ? found : void 0;
|
|
5818
|
-
const [disk, session] = await Promise.all([
|
|
6175
|
+
const [disk, session] = await Promise.all([shownMessages(sessionId, cwd), loadSession(sessionId)]);
|
|
5819
6176
|
const entries = session === null ? [] : session.entries;
|
|
5820
6177
|
const resumeAt = run?.resumeAt ?? null;
|
|
5821
6178
|
const kept = resumeAt === null ? disk : disk.slice(0, disk.findIndex((item) => item.uuid === resumeAt) + 1);
|
|
@@ -5858,6 +6215,28 @@ async function agentMessages(sessionId, cwd, agentId, parent) {
|
|
|
5858
6215
|
parent_tool_use_id: parent
|
|
5859
6216
|
}, null)]));
|
|
5860
6217
|
}
|
|
6218
|
+
// src/lib/claude/permission.ts
|
|
6219
|
+
function permissionResult(input, suggestions, decision, answers) {
|
|
6220
|
+
switch (decision) {
|
|
6221
|
+
case "allow": return {
|
|
6222
|
+
behavior: "allow",
|
|
6223
|
+
updatedInput: answers === null ? input : {
|
|
6224
|
+
...input,
|
|
6225
|
+
answers
|
|
6226
|
+
}
|
|
6227
|
+
};
|
|
6228
|
+
case "always": return {
|
|
6229
|
+
behavior: "allow",
|
|
6230
|
+
updatedInput: input,
|
|
6231
|
+
updatedPermissions: suggestions
|
|
6232
|
+
};
|
|
6233
|
+
case "deny": return {
|
|
6234
|
+
behavior: "deny",
|
|
6235
|
+
message: "The user rejected this action",
|
|
6236
|
+
interrupt: true
|
|
6237
|
+
};
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
5861
6240
|
// src/lib/claude/run.ts
|
|
5862
6241
|
const IDLE_LIMIT = 6e5;
|
|
5863
6242
|
var Run = class {
|
|
@@ -5870,6 +6249,7 @@ var Run = class {
|
|
|
5870
6249
|
query;
|
|
5871
6250
|
closed;
|
|
5872
6251
|
resumeAt;
|
|
6252
|
+
cut = null;
|
|
5873
6253
|
queue = [];
|
|
5874
6254
|
settings;
|
|
5875
6255
|
context = null;
|
|
@@ -5880,6 +6260,8 @@ var Run = class {
|
|
|
5880
6260
|
compacting = false;
|
|
5881
6261
|
backgroundTasks = 0;
|
|
5882
6262
|
backgroundAgents = 0;
|
|
6263
|
+
agentTasks = 0;
|
|
6264
|
+
failed = false;
|
|
5883
6265
|
tasks = {};
|
|
5884
6266
|
inbox = [];
|
|
5885
6267
|
wake = null;
|
|
@@ -5952,30 +6334,7 @@ var Run = class {
|
|
|
5952
6334
|
respond(requestId, decision, answers) {
|
|
5953
6335
|
const pending = this.pending.get(requestId);
|
|
5954
6336
|
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
|
-
}
|
|
6337
|
+
this.settle(requestId, permissionResult(pending.input, pending.suggestions, decision, answers));
|
|
5979
6338
|
}
|
|
5980
6339
|
stop() {
|
|
5981
6340
|
this.stopping = true;
|
|
@@ -6069,7 +6428,9 @@ var Run = class {
|
|
|
6069
6428
|
return;
|
|
6070
6429
|
}
|
|
6071
6430
|
case "result": {
|
|
6072
|
-
|
|
6431
|
+
const failure = resultNotice(message);
|
|
6432
|
+
if (failure !== null) this.add(failure);
|
|
6433
|
+
this.failed = turnFailed(message);
|
|
6073
6434
|
this.finishTurn();
|
|
6074
6435
|
const usage = this.model === null ? void 0 : message.modelUsage[this.model];
|
|
6075
6436
|
if (this.context !== null && usage !== void 0) this.context = {
|
|
@@ -6091,6 +6452,7 @@ var Run = class {
|
|
|
6091
6452
|
});
|
|
6092
6453
|
return;
|
|
6093
6454
|
case "background_tasks_changed": {
|
|
6455
|
+
this.agentTasks = message.tasks.filter((task) => task.task_type !== "local_bash").length;
|
|
6094
6456
|
const live = message.tasks.filter((task) => task.ambient !== true);
|
|
6095
6457
|
this.backgroundTasks = live.length;
|
|
6096
6458
|
this.backgroundAgents = live.filter((task) => task.task_type === "local_agent").length;
|
|
@@ -6237,6 +6599,30 @@ var Run = class {
|
|
|
6237
6599
|
});
|
|
6238
6600
|
}
|
|
6239
6601
|
};
|
|
6602
|
+
// src/lib/claude/rewind.ts
|
|
6603
|
+
const ATTEMPTS = 10;
|
|
6604
|
+
const answerSchema = z.object({ response: z.discriminatedUnion("rewound", [z.object({ rewound: z.literal(true) }), z.object({
|
|
6605
|
+
rewound: z.literal(false),
|
|
6606
|
+
reason: z.string(),
|
|
6607
|
+
error: z.string()
|
|
6608
|
+
})]) });
|
|
6609
|
+
async function rewindLive(run, messageId, lastSeenId) {
|
|
6610
|
+
const request = Reflect.get(run.query, "request");
|
|
6611
|
+
if (typeof request !== "function") throw new Error("This Claude Code version cannot rewind a live session");
|
|
6612
|
+
for (let attempt = 1;; attempt++) {
|
|
6613
|
+
const answer = answerSchema.parse(await Reflect.apply(request, run.query, [{
|
|
6614
|
+
subtype: "rewind_conversation",
|
|
6615
|
+
target_message_uuid: messageId,
|
|
6616
|
+
last_seen_user_message_uuid: lastSeenId
|
|
6617
|
+
}])).response;
|
|
6618
|
+
if (answer.rewound) break;
|
|
6619
|
+
if (answer.reason !== "turn_running" || attempt === ATTEMPTS) throw new Error(`Claude Code could not rewind: ${answer.error}`);
|
|
6620
|
+
await setTimeout$1(500);
|
|
6621
|
+
}
|
|
6622
|
+
const index = run.messages.findIndex((item) => item.uuid === messageId);
|
|
6623
|
+
run.messages.splice(index < 0 ? 0 : index);
|
|
6624
|
+
run.cut = messageId;
|
|
6625
|
+
}
|
|
6240
6626
|
// src/lib/proxy/sites.ts
|
|
6241
6627
|
const sites = /* @__PURE__ */ new Map();
|
|
6242
6628
|
const siteNames = /* @__PURE__ */ new Map();
|
|
@@ -6381,6 +6767,13 @@ function sessionHistory(sessionId, cwd) {
|
|
|
6381
6767
|
case "opencode": return openCodeHistory(sessionId, cwd);
|
|
6382
6768
|
}
|
|
6383
6769
|
}
|
|
6770
|
+
function agentHistory(sessionId, cwd, agentId, parent) {
|
|
6771
|
+
switch (providerOf(sessionId)) {
|
|
6772
|
+
case "claude": return agentMessages(sessionId, cwd, agentId, parent);
|
|
6773
|
+
case "codex": return codexAgent(agentId, parent);
|
|
6774
|
+
case "opencode": return openCodeAgent(agentId, parent);
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6384
6777
|
function rewind(request) {
|
|
6385
6778
|
return locked(request.sessionId, () => rewindNow(request));
|
|
6386
6779
|
}
|
|
@@ -6388,7 +6781,6 @@ async function rewindNow(request) {
|
|
|
6388
6781
|
const sessionId = request.sessionId;
|
|
6389
6782
|
const cwd = projectPath(request.projectId);
|
|
6390
6783
|
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
6784
|
const run = await runFor(sessionId, cwd, request.settings);
|
|
6393
6785
|
if (run.provider === "codex") {
|
|
6394
6786
|
if (request.code) throw new Error("Codex cannot restore files");
|
|
@@ -6418,9 +6810,16 @@ async function rewindNow(request) {
|
|
|
6418
6810
|
};
|
|
6419
6811
|
}
|
|
6420
6812
|
if (request.dryRun || !request.conversation) return result;
|
|
6421
|
-
const disk = await
|
|
6813
|
+
const disk = await shownMessages(sessionId, cwd);
|
|
6422
6814
|
const index = disk.findIndex((item) => item.uuid === request.messageId);
|
|
6423
6815
|
if (index < 1) throw new Error("Cannot rewind the conversation to that point");
|
|
6816
|
+
if (run.backgroundTasks > 0) {
|
|
6817
|
+
const lastSeen = disk.findLast((item) => item.type === "user" && item.parent_tool_use_id === null);
|
|
6818
|
+
if (lastSeen === void 0) throw new Error("Cannot rewind the conversation to that point");
|
|
6819
|
+
await rewindLive(run, request.messageId, lastSeen.uuid);
|
|
6820
|
+
publish$2(sessionId, await history$1(sessionId, cwd));
|
|
6821
|
+
return result;
|
|
6822
|
+
}
|
|
6424
6823
|
run.stop();
|
|
6425
6824
|
await run.closed;
|
|
6426
6825
|
new Run({
|
|
@@ -6433,6 +6832,24 @@ async function rewindNow(request) {
|
|
|
6433
6832
|
publish$2(sessionId, await history$1(sessionId, cwd));
|
|
6434
6833
|
return result;
|
|
6435
6834
|
}
|
|
6835
|
+
function restart(sessionId) {
|
|
6836
|
+
return locked(sessionId, async () => {
|
|
6837
|
+
const run = runs.get(sessionId);
|
|
6838
|
+
if (run === void 0) return;
|
|
6839
|
+
if (run.provider !== "claude") throw new Error("Only Claude Code threads can be restarted");
|
|
6840
|
+
if (run.running) await run.interrupt();
|
|
6841
|
+
run.stop();
|
|
6842
|
+
await run.closed;
|
|
6843
|
+
new Run({
|
|
6844
|
+
sessionId,
|
|
6845
|
+
cwd: run.cwd,
|
|
6846
|
+
resume: true,
|
|
6847
|
+
resumeAt: null,
|
|
6848
|
+
settings: run.settings
|
|
6849
|
+
});
|
|
6850
|
+
publish$2(sessionId, await history$1(sessionId, run.cwd));
|
|
6851
|
+
});
|
|
6852
|
+
}
|
|
6436
6853
|
async function open$1(client, sessionId, projectId) {
|
|
6437
6854
|
client.opening = sessionId;
|
|
6438
6855
|
const event = await sessionHistory(sessionId, projectPath(projectId));
|
|
@@ -6471,7 +6888,7 @@ async function submitFirst(client, run, request, projectId) {
|
|
|
6471
6888
|
from: request.projectId
|
|
6472
6889
|
});
|
|
6473
6890
|
await run.submit(request.prompt, request.delivery);
|
|
6474
|
-
recordInteraction(sessionId, run.provider);
|
|
6891
|
+
recordInteraction(sessionId, run.provider, request.status);
|
|
6475
6892
|
}
|
|
6476
6893
|
async function sendExisting(client, sessionId, cwd, request) {
|
|
6477
6894
|
const provider = providerOf(sessionId);
|
|
@@ -6485,7 +6902,7 @@ async function sendExisting(client, sessionId, cwd, request) {
|
|
|
6485
6902
|
if (run === existing) await run.configure(request.settings);
|
|
6486
6903
|
else await open$1(client, sessionId, request.projectId);
|
|
6487
6904
|
await run.submit(request.prompt, request.delivery);
|
|
6488
|
-
recordInteraction(sessionId, provider);
|
|
6905
|
+
recordInteraction(sessionId, provider, request.status);
|
|
6489
6906
|
if (emptied.has(sessionId) && request.prompt.text.trim() !== "") {
|
|
6490
6907
|
emptied.delete(sessionId);
|
|
6491
6908
|
await updateSession(sessionId, { title: request.prompt.text.trim() });
|
|
@@ -6535,7 +6952,7 @@ async function startRun(cwd, settings) {
|
|
|
6535
6952
|
}
|
|
6536
6953
|
async function changedElsewhere(run, cwd) {
|
|
6537
6954
|
if (run.messages.length === 0) return false;
|
|
6538
|
-
const last = (await
|
|
6955
|
+
const last = (await shownMessages(run.sessionId, cwd)).at(-1)?.uuid;
|
|
6539
6956
|
return last !== void 0 && !run.messages.some((item) => item.uuid === last);
|
|
6540
6957
|
}
|
|
6541
6958
|
// src/services/export.ts
|
|
@@ -6675,7 +7092,7 @@ async function searchMessages(query) {
|
|
|
6675
7092
|
"claude",
|
|
6676
7093
|
"codex",
|
|
6677
7094
|
"opencode"
|
|
6678
|
-
].filter((provider) => readConfig()
|
|
7095
|
+
].filter((provider) => readConfig()[provider].enabled).map(async (provider) => {
|
|
6679
7096
|
try {
|
|
6680
7097
|
return {
|
|
6681
7098
|
provider,
|
|
@@ -8064,6 +8481,15 @@ const lastUsedSchema = z.discriminatedUnion("provider", [z.strictObject({
|
|
|
8064
8481
|
accountId: z.null(),
|
|
8065
8482
|
model: z.string()
|
|
8066
8483
|
})]);
|
|
8484
|
+
const lastEffortSchema = z.strictObject({
|
|
8485
|
+
provider: z.enum([
|
|
8486
|
+
"claude",
|
|
8487
|
+
"codex",
|
|
8488
|
+
"opencode"
|
|
8489
|
+
]),
|
|
8490
|
+
model: z.string(),
|
|
8491
|
+
effort: z.string().nullable()
|
|
8492
|
+
});
|
|
8067
8493
|
function readLastUsed() {
|
|
8068
8494
|
return db.select().from(lastUsedModels).all();
|
|
8069
8495
|
}
|
|
@@ -8084,6 +8510,22 @@ function saveLastUsed(value) {
|
|
|
8084
8510
|
});
|
|
8085
8511
|
return null;
|
|
8086
8512
|
}
|
|
8513
|
+
function readLastEfforts() {
|
|
8514
|
+
return db.select().from(lastEfforts).all();
|
|
8515
|
+
}
|
|
8516
|
+
function saveLastEffort(value) {
|
|
8517
|
+
const result = lastEffortSchema.safeParse(value);
|
|
8518
|
+
if (!result.success) throw new Error(z.prettifyError(result.error));
|
|
8519
|
+
db.insert(lastEfforts).values(result.data).onConflictDoUpdate({
|
|
8520
|
+
target: [lastEfforts.provider, lastEfforts.model],
|
|
8521
|
+
set: { effort: result.data.effort }
|
|
8522
|
+
}).run();
|
|
8523
|
+
broadcast({
|
|
8524
|
+
type: "lastEfforts",
|
|
8525
|
+
lastEfforts: readLastEfforts()
|
|
8526
|
+
});
|
|
8527
|
+
return null;
|
|
8528
|
+
}
|
|
8087
8529
|
// ../core/src/app-icons.ts
|
|
8088
8530
|
const appIcons = [
|
|
8089
8531
|
{
|
|
@@ -8184,6 +8626,7 @@ const handlers = {
|
|
|
8184
8626
|
return {
|
|
8185
8627
|
...configInfo(),
|
|
8186
8628
|
lastUsed: readLastUsed(),
|
|
8629
|
+
lastEfforts: readLastEfforts(),
|
|
8187
8630
|
hostname: os.hostname(),
|
|
8188
8631
|
projects: projects(),
|
|
8189
8632
|
sessions: sessions(),
|
|
@@ -8195,6 +8638,7 @@ const handlers = {
|
|
|
8195
8638
|
},
|
|
8196
8639
|
"config.get": () => configInfo(),
|
|
8197
8640
|
"lastUsed.save": (params) => saveLastUsed(params),
|
|
8641
|
+
"lastEffort.save": (params) => saveLastEffort(params),
|
|
8198
8642
|
"appIcon.save": (params) => saveAppIcon(params),
|
|
8199
8643
|
"config.save": async (params) => {
|
|
8200
8644
|
const before = readConfig();
|
|
@@ -8203,8 +8647,8 @@ const handlers = {
|
|
|
8203
8647
|
"claude",
|
|
8204
8648
|
"codex",
|
|
8205
8649
|
"opencode"
|
|
8206
|
-
]) if (before
|
|
8207
|
-
if (JSON.stringify(saved.
|
|
8650
|
+
]) if (before[provider].enabled && !saved[provider].enabled) await stopProvider(provider);
|
|
8651
|
+
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
8652
|
watchSessions();
|
|
8209
8653
|
refreshAll();
|
|
8210
8654
|
publishAccounts();
|
|
@@ -8256,13 +8700,7 @@ const handlers = {
|
|
|
8256
8700
|
"sessions.earlier": (params) => earlier(params.sessionId, params.before),
|
|
8257
8701
|
"sessions.export": (params) => exportSession(params.sessionId, params.projectId),
|
|
8258
8702
|
"sessions.image": (params) => image(params.sessionId, params.itemId, params.index),
|
|
8259
|
-
"sessions.agent": (params) =>
|
|
8260
|
-
switch (providerOf(params.sessionId)) {
|
|
8261
|
-
case "claude": return agentMessages(params.sessionId, projectPath(params.projectId), params.agentId, params.parent);
|
|
8262
|
-
case "codex": return codexAgent(params.agentId, params.parent);
|
|
8263
|
-
case "opencode": return openCodeAgent(params.agentId, params.parent);
|
|
8264
|
-
}
|
|
8265
|
-
},
|
|
8703
|
+
"sessions.agent": (params) => agentHistory(params.sessionId, projectPath(params.projectId), params.agentId, params.parent),
|
|
8266
8704
|
"sessions.send": async (params, client) => {
|
|
8267
8705
|
await send$1(client, params);
|
|
8268
8706
|
return null;
|
|
@@ -8279,6 +8717,10 @@ const handlers = {
|
|
|
8279
8717
|
await runs.get(params.sessionId)?.interrupt();
|
|
8280
8718
|
return null;
|
|
8281
8719
|
},
|
|
8720
|
+
"sessions.restart": async (params) => {
|
|
8721
|
+
await restart(params.sessionId);
|
|
8722
|
+
return null;
|
|
8723
|
+
},
|
|
8282
8724
|
"sessions.stopTask": async (params) => {
|
|
8283
8725
|
await runs.get(params.sessionId)?.stopTask(params.taskId);
|
|
8284
8726
|
return null;
|
|
@@ -8291,7 +8733,7 @@ const handlers = {
|
|
|
8291
8733
|
const run = runs.get(params.sessionId);
|
|
8292
8734
|
if (run === void 0) return null;
|
|
8293
8735
|
await run.respond(params.requestId, params.decision, params.answers);
|
|
8294
|
-
recordInteraction(params.sessionId, run.provider);
|
|
8736
|
+
recordInteraction(params.sessionId, run.provider, null);
|
|
8295
8737
|
return null;
|
|
8296
8738
|
},
|
|
8297
8739
|
"sessions.rewind": (params) => rewind(params),
|
|
@@ -8301,21 +8743,11 @@ const handlers = {
|
|
|
8301
8743
|
return result;
|
|
8302
8744
|
},
|
|
8303
8745
|
"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
|
-
},
|
|
8746
|
+
"accounts.add": (params) => addAccount(params.provider, params.name, params.configDir),
|
|
8747
|
+
"accounts.update": (params) => updateAccount(params),
|
|
8748
|
+
"accounts.remove": (params) => removeAccount(params.id),
|
|
8749
|
+
"accounts.link": (params) => linkAccount(params.id),
|
|
8750
|
+
"accounts.linkPlan": (params) => accountLinkPlan(params.id),
|
|
8319
8751
|
"accounts.usage": (params) => readUsage(params.id, params.force),
|
|
8320
8752
|
capabilities: (params) => capabilities(params.provider, params.accountId, params.projectId === null || params.projectId === "no-project" ? os.homedir() : projectPath(params.projectId)),
|
|
8321
8753
|
"files.list": (params) => params.projectId === "no-project" ? [] : listFiles(projectPath(params.projectId)),
|
|
@@ -9700,7 +10132,7 @@ async function backfillSessionProviders() {
|
|
|
9700
10132
|
const rows = db.select().from(sessions$1).where(isNull(sessions$1.provider)).all();
|
|
9701
10133
|
if (rows.length === 0) return;
|
|
9702
10134
|
const paths = new Map(projects().map((project) => [project.path, project.id]));
|
|
9703
|
-
const enabled = allProviders.filter((provider) => readConfig()
|
|
10135
|
+
const enabled = allProviders.filter((provider) => readConfig()[provider].enabled);
|
|
9704
10136
|
for (const row of rows) {
|
|
9705
10137
|
const answers = await Promise.all(enabled.map(async (provider) => {
|
|
9706
10138
|
try {
|