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.
Files changed (28) hide show
  1. package/apps/client/dist/assets/{XIcon-CDJz0EhT.js → XIcon-Tc8nkaoT.js} +4 -4
  2. package/apps/client/dist/assets/{audio-player-CEvt4CVB.js → audio-player-DosgEDBw.js} +1 -1
  3. package/apps/client/dist/assets/{button-CWFREsRn.js → button-BnzxfmO3.js} +1 -1
  4. package/apps/client/dist/assets/diff-panel-DTqI0r9W.js +260 -0
  5. package/apps/client/dist/assets/{dist-C_Y10kCd.js → dist-DNx_j6Py.js} +1 -1
  6. package/apps/client/dist/assets/{editor-dialog-CEMtEtdH.js → editor-dialog-Se2DP1E5.js} +36 -36
  7. package/apps/client/dist/assets/{image-viewer-3S4rNjFt.js → image-viewer-qKLCNUVK.js} +1 -1
  8. package/apps/client/dist/assets/index-B6yjBLio.js +72 -0
  9. package/apps/client/dist/assets/index-BFkBeASv.css +2 -0
  10. package/apps/client/dist/assets/panel-close-button-ByrJIu34.js +1 -0
  11. package/apps/client/dist/assets/{preview-dialog-BuSSuMeQ.js → preview-dialog-CnvAwgMU.js} +2 -2
  12. package/apps/client/dist/assets/{react-BgpkckXP.js → react-aDzERmfM.js} +1 -1
  13. package/apps/client/dist/assets/{tooltip-CKPboHje.js → tooltip-CU6Gh-yj.js} +1 -1
  14. package/apps/client/dist/assets/{use-editor-shortcut-auobREOP.js → use-editor-shortcut-CGHT7fnL.js} +26 -26
  15. package/apps/client/dist/assets/{utils-U6QfaJBO.js → utils-Cih_T9uV.js} +1 -1
  16. package/apps/client/dist/assets/{video-viewer-gcKzQe3N.js → video-viewer-Ctc_XHXE.js} +1 -1
  17. package/apps/client/dist/assets/zoomable-image-D0X1OvmT.js +1 -0
  18. package/apps/client/dist/assets/{zoomable-image-BD-kkiyn.js → zoomable-image-D6TeXYQ9.js} +1 -1
  19. package/apps/client/dist/index.html +10 -10
  20. package/apps/client/dist/sw.js +1 -1
  21. package/apps/server/dist/main.mjs +512 -133
  22. package/apps/server/dist/main.mjs.map +1 -1
  23. package/package.json +1 -1
  24. package/apps/client/dist/assets/diff-panel-CeJqpNi8.js +0 -260
  25. package/apps/client/dist/assets/index-B7o5cqzG.js +0 -72
  26. package/apps/client/dist/assets/index-BwL3keyv.css +0 -2
  27. package/apps/client/dist/assets/panel-close-button-Bf-0TdmN.js +0 -1
  28. 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 value;
487
+ let saved;
465
488
  try {
466
- value = readJson(name);
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 (value === null) return structuredClone(defaults);
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) return result.data;
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?.notify(message.method, params);
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
- claudeBaseDir: platformDirs,
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().claudeBaseDir[platform()] ?? path.join(os.homedir(), ".claude");
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$3 = promisify(execFile);
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$3("git", ["fetch"], {
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$3("gh", [
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$3("gh", [
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$3("gh", [
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$3("git", args, {
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.enabledProviders,
4134
- config.claudeBaseDir
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().enabledProviders[provider]).flatMap((provider) => [...threads[provider].values()]).filter((item) => known.has(item.projectId));
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().enabledProviders[provider]) {
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().enabledProviders[provider]);
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().enabledProviders[provider]) {
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().enabledProviders[provider]) return [];
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.claudeAccounts.map((item) => ({
4817
+ return [...config.claude.accounts.map((item) => ({
4699
4818
  ...item,
4700
4819
  configDir: item.configDir[platform()]
4701
- })), ...config.codexAccounts.map((item) => ({
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().enabledProviders[provider]) throw new Error(`${providerName(provider)} is disabled in Settings`);
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().enabledProviders[account.provider]) return disabledAccount(account);
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().enabledProviders.opencode }
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().models[provider];
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
- [key]: [...config[key], {
4812
- id: randomUUID(),
4813
- provider,
4814
- name: account.name,
4815
- color: null,
4816
- configDir: { [platform()]: account.configDir },
4817
- enabled: true
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
- [key]: config[key].map((item) => item.id === update.id ? {
4830
- ...item,
4831
- name: account.name,
4832
- color: update.color,
4833
- configDir: {
4834
- ...item.configDir,
4835
- [platform()]: account.configDir
4836
- },
4837
- enabled: update.enabled
4838
- } : item)
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
- claudeAccounts: config.claudeAccounts.filter((item) => item.id !== id),
4849
- codexAccounts: config.codexAccounts.filter((item) => item.id !== id)
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([getSessionMessages(sessionId, { dir: cwd }), loadSession(sessionId)]);
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
- const input = pending.input;
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
- if (message.subtype !== "success" && message.terminal_reason !== "aborted_streaming" && message.terminal_reason !== "aborted_tools") this.add(notice(message.errors.filter((error) => !error.startsWith("[ede_diagnostic]")).join("\n") || message.subtype));
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 getSessionMessages(sessionId, { dir: cwd });
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 getSessionMessages(run.sessionId, { dir: cwd })).at(-1)?.uuid;
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().enabledProviders[provider]).map(async (provider) => {
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.enabledProviders[provider] && !saved.enabledProviders[provider]) await stopProvider(provider);
8207
- if (JSON.stringify(saved.claudeBaseDir) === JSON.stringify(before.claudeBaseDir) && JSON.stringify(saved.enabledProviders) === JSON.stringify(before.enabledProviders)) return 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
- addAccount(params.provider, params.name, params.configDir);
8306
- publishAccounts();
8307
- return null;
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().enabledProviders[provider]);
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 {