offsite 0.0.93 → 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 +563 -155
  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-CC5CJi_g.css +0 -2
  26. package/apps/client/dist/assets/index-CwwKJuU6.js +0 -72
  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;
@@ -366,6 +389,35 @@ function toBlocks(content, answers) {
366
389
  function isRecord(value) {
367
390
  return typeof value === "object" && value !== null && !Array.isArray(value);
368
391
  }
392
+ function userText$2(value) {
393
+ const command = /<command-name>(.*?)<\/command-name>/s.exec(value);
394
+ if (command !== null) {
395
+ const args = /<command-args>(.*?)<\/command-args>/s.exec(value);
396
+ return {
397
+ format: "command",
398
+ text: `${command[1]} ${args?.[1] ?? ""}`.trim()
399
+ };
400
+ }
401
+ const output = /^<local-command-(?:stdout|stderr)>(.*)<\/local-command-(?:stdout|stderr)>$/s.exec(value.trim());
402
+ if (output !== null) return {
403
+ format: "output",
404
+ text: output[1]
405
+ };
406
+ const bash = /^<bash-input>(.*)<\/bash-input>$/s.exec(value.trim());
407
+ if (bash !== null) return {
408
+ format: "command",
409
+ text: `$ ${bash[1]}`
410
+ };
411
+ const bashOutput = /^<bash-stdout>(.*)<\/bash-stdout><bash-stderr>(.*)<\/bash-stderr>$/s.exec(value.trim());
412
+ if (bashOutput !== null) return {
413
+ format: "output",
414
+ text: `${bashOutput[1]}${bashOutput[2]}`.trim()
415
+ };
416
+ return {
417
+ format: "text",
418
+ text: value
419
+ };
420
+ }
369
421
  // src/storage/storage.ts
370
422
  const home = args.dir;
371
423
  const lockFile = path.join(home, "server.pid");
@@ -430,18 +482,26 @@ function readJson(name) {
430
482
  throw error;
431
483
  }
432
484
  }
433
- function readSettings(name, schema, defaults) {
485
+ function readSettings(name, schema, defaults, migrate = (value) => value) {
434
486
  const file = path.join(home, name);
435
- let value;
487
+ let saved;
436
488
  try {
437
- value = readJson(name);
489
+ saved = readJson(name);
438
490
  } catch (error) {
439
491
  log$19.warn(`invalid ${file}, using the defaults:`, error);
440
492
  return structuredClone(defaults);
441
493
  }
442
- 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
+ }
443
500
  let result = schema.safeParse(value);
444
- if (result.success) return result.data;
501
+ if (result.success) {
502
+ if (value !== saved) writeJson(name, result.data);
503
+ return result.data;
504
+ }
445
505
  while (!result.success) {
446
506
  const issue = result.error.issues[0];
447
507
  const targets = issue.code === "unrecognized_keys" ? issue.keys.map((key) => [...issue.path, key]) : [issue.path];
@@ -496,11 +556,11 @@ function installOpen(host, port) {
496
556
  "wslview"
497
557
  ]) {
498
558
  const wrapper = path.join(folder, name);
499
- 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`);
500
560
  chmodSync(wrapper, 493);
501
561
  }
502
562
  const startup = path.join(folder, "env.sh");
503
- writeFileSync(startup, `export PATH=${quote(folder)}:"$PATH"\n`);
563
+ writeFileSync(startup, `export PATH=${quote$1(folder)}:"$PATH"\n`);
504
564
  env = {
505
565
  PATH: process.env.PATH === void 0 ? folder : `${folder}${path.delimiter}${process.env.PATH}`,
506
566
  BROWSER: path.join(folder, process.platform === "darwin" ? "open" : "xdg-open"),
@@ -515,7 +575,7 @@ function loopback(host) {
515
575
  if (host.includes(":")) return `[${host}]`;
516
576
  return host;
517
577
  }
518
- function quote(value) {
578
+ function quote$1(value) {
519
579
  return `'${value.replaceAll("'", "'\\''")}'`;
520
580
  }
521
581
  // src/lib/codex/app-server.ts
@@ -693,7 +753,11 @@ var AppServer = class {
693
753
  publishCodexLimits(this.home, params.rateLimits);
694
754
  return;
695
755
  }
696
- 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);
697
761
  }
698
762
  rootOf(threadId) {
699
763
  if (typeof threadId !== "string") return;
@@ -925,7 +989,7 @@ function toItems$1(turnId, item, parent, time) {
925
989
  case "userMessage": return [{
926
990
  ...base,
927
991
  kind: "user",
928
- text: item.content.flatMap(userText$2).join("\n").trim(),
992
+ text: item.content.flatMap(userText$1).join("\n").trim(),
929
993
  images: item.content.flatMap(userImages),
930
994
  format: "text"
931
995
  }];
@@ -1035,7 +1099,7 @@ function toItems$1(turnId, item, parent, time) {
1035
1099
  case "exitedReviewMode": return [];
1036
1100
  }
1037
1101
  }
1038
- function userText$2(input) {
1102
+ function userText$1(input) {
1039
1103
  switch (input.type) {
1040
1104
  case "text": return [input.text.replaceAll(/\[Image: source: [^\]]+\]/g, "")];
1041
1105
  case "skill":
@@ -1133,27 +1197,31 @@ function accountSchema(provider) {
1133
1197
  enabled: z.boolean().default(true)
1134
1198
  });
1135
1199
  }
1200
+ const modelSettings = z.record(z.string(), modelSetting).default({});
1136
1201
  const configSchema = z.strictObject({
1137
- 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({}),
1138
1218
  workingDir: z.strictObject({
1139
1219
  macos: workingDir(path.posix.isAbsolute),
1140
1220
  linux: workingDir(path.posix.isAbsolute),
1141
1221
  windows: workingDir(path.win32.isAbsolute)
1142
1222
  }).prefault({}),
1143
- claudeAccounts: z.array(accountSchema("claude")).default([]),
1144
- codexAccounts: z.array(accountSchema("codex")).default([]),
1145
- enabledProviders: z.strictObject({
1146
- claude: z.boolean().default(true),
1147
- codex: z.boolean().default(true),
1148
- opencode: z.boolean().default(true)
1149
- }).prefault({}),
1150
1223
  commitMessage: generationModel,
1151
1224
  titleGeneration: generationModel,
1152
- models: z.strictObject({
1153
- claude: z.record(z.string(), modelSetting).default({}),
1154
- codex: z.record(z.string(), modelSetting).default({}),
1155
- opencode: z.record(z.string(), modelSetting).default({})
1156
- }).prefault({}),
1157
1225
  delivery: z.enum(["queue", "steer"]).default("steer"),
1158
1226
  autoSkipQuestionTimeout: z.number().positive().nullable().default(180),
1159
1227
  autoPermissionMode: z.enum([
@@ -1187,7 +1255,7 @@ function platform() {
1187
1255
  throw new Error(`unsupported platform ${process.platform}`);
1188
1256
  }
1189
1257
  function claudeBaseDir() {
1190
- return readConfig().claudeBaseDir[platform()] ?? path.join(os.homedir(), ".claude");
1258
+ return readConfig().claude.baseDir[platform()] ?? path.join(os.homedir(), ".claude");
1191
1259
  }
1192
1260
  function workingDirectory() {
1193
1261
  const dir = readConfig().workingDir[platform()] ?? "~";
@@ -1205,11 +1273,42 @@ function configInfo() {
1205
1273
  defaultShell: defaultShell(),
1206
1274
  defaultClaudeBaseDir: path.join(os.homedir(), ".claude"),
1207
1275
  homeDir: os.homedir(),
1208
- scratchAvailable
1276
+ scratchAvailable,
1277
+ version: "v0.0.95"
1209
1278
  };
1210
1279
  }
1211
1280
  function readConfig() {
1212
- 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
+ };
1213
1312
  }
1214
1313
  function saveConfig(value) {
1215
1314
  const result = configSchema.safeParse(value);
@@ -1237,6 +1336,7 @@ function activity() {
1237
1336
  prompted: run.prompted,
1238
1337
  compacting: run.compacting,
1239
1338
  backgroundAgent: run.backgroundAgents > 0,
1339
+ holdsCompletion: !run.failed && run.agentTasks > 0,
1240
1340
  tasks: run.tasks
1241
1341
  };
1242
1342
  return result;
@@ -1285,6 +1385,24 @@ function scheduleAutoResolve(sessionId, request) {
1285
1385
  }
1286
1386
  };
1287
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
+ }
1288
1406
  // src/lib/codex/presets.ts
1289
1407
  const presets = /* @__PURE__ */ new Map([
1290
1408
  ["read-only", {
@@ -1437,11 +1555,13 @@ var CodexRun = class CodexRun {
1437
1555
  compacting = false;
1438
1556
  backgroundTasks = 0;
1439
1557
  backgroundAgents = 0;
1558
+ failed = false;
1440
1559
  tasks = {};
1441
1560
  activeAt = Date.now();
1442
1561
  model;
1443
1562
  close;
1444
1563
  turnId = null;
1564
+ agents = /* @__PURE__ */ new Map();
1445
1565
  idleTimer;
1446
1566
  static async start(cwd, settings) {
1447
1567
  const server = codexServer(settings);
@@ -1550,6 +1670,7 @@ var CodexRun = class CodexRun {
1550
1670
  }
1551
1671
  notify(method, params) {
1552
1672
  this.activeAt = Date.now();
1673
+ this.notifyAgent(method, params);
1553
1674
  switch (method) {
1554
1675
  case "turn/started": {
1555
1676
  const turn = params.turn;
@@ -1563,6 +1684,7 @@ var CodexRun = class CodexRun {
1563
1684
  case "turn/completed": {
1564
1685
  const turn = params.turn;
1565
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";
1566
1688
  this.finishTurn();
1567
1689
  return;
1568
1690
  }
@@ -1628,6 +1750,14 @@ var CodexRun = class CodexRun {
1628
1750
  case "serverRequest/resolved": this.settle(String(params.requestId));
1629
1751
  }
1630
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
+ }
1631
1761
  request(id, method, params) {
1632
1762
  if (method === "mcpServer/elicitation/request") {
1633
1763
  this.server.reply(id, {
@@ -2638,6 +2768,8 @@ var OpenCodeRun = class OpenCodeRun {
2638
2768
  compacting = false;
2639
2769
  backgroundTasks = 0;
2640
2770
  backgroundAgents = 0;
2771
+ agentTasks = 0;
2772
+ failed = false;
2641
2773
  tasks = {};
2642
2774
  close;
2643
2775
  applied;
@@ -3414,7 +3546,7 @@ const UNTRACKED_LIMIT = 100;
3414
3546
  const NEW_FILE_LIMIT = 262144;
3415
3547
  const REPO_DEPTH = 3;
3416
3548
  const FETCH_TIMEOUT = 6e4;
3417
- const run$3 = promisify(execFile);
3549
+ const run$4 = promisify(execFile);
3418
3550
  const PULL_REQUEST_INSTRUCTIONS = `You write GitHub pull requests as Markdown.
3419
3551
 
3420
3552
  ## TITLE RULES
@@ -3446,6 +3578,7 @@ Return one Markdown document. The title is the level-1 heading (\`# <title>\`),
3446
3578
  async function gitRepos(cwd) {
3447
3579
  return Promise.all((await projectRepos(cwd)).map(async (repo) => ({
3448
3580
  ...await gitStatus(path.join(cwd, repo)),
3581
+ ...await lineChanges(path.join(cwd, repo)),
3449
3582
  path: repo
3450
3583
  })));
3451
3584
  }
@@ -3506,7 +3639,7 @@ async function sync(cwd) {
3506
3639
  async function fetchProject(cwd) {
3507
3640
  await Promise.all((await projectRepos(cwd)).map(async (repo) => {
3508
3641
  try {
3509
- await run$3("git", ["fetch"], {
3642
+ await run$4("git", ["fetch"], {
3510
3643
  cwd: path.join(cwd, repo),
3511
3644
  env: {
3512
3645
  ...process.env,
@@ -3537,7 +3670,7 @@ async function switchBranch(cwd, branch, create) {
3537
3670
  async function pullRequestBranches(cwd) {
3538
3671
  const head = (await gitStatus(cwd)).branch;
3539
3672
  if (head === null) throw new Error("Not on a branch");
3540
- const base = (await run$3("gh", [
3673
+ const base = (await run$4("gh", [
3541
3674
  "repo",
3542
3675
  "view",
3543
3676
  "--json",
@@ -3562,7 +3695,7 @@ async function preparePullRequest(cwd, base, commitChanges) {
3562
3695
  if (commitChanges) await commit(cwd, "", true);
3563
3696
  const status = await gitStatus(cwd);
3564
3697
  if (status.upstream === null || status.ahead > 0) await push$1(cwd);
3565
- const open = (await run$3("gh", [
3698
+ const open = (await run$4("gh", [
3566
3699
  "pr",
3567
3700
  "list",
3568
3701
  "--head",
@@ -3609,7 +3742,7 @@ async function preparePullRequest(cwd, base, commitChanges) {
3609
3742
  };
3610
3743
  }
3611
3744
  async function createPullRequest(cwd, request) {
3612
- const url = (await run$3("gh", [
3745
+ const url = (await run$4("gh", [
3613
3746
  "pr",
3614
3747
  "create",
3615
3748
  "--title",
@@ -3642,7 +3775,7 @@ async function projectDiff(cwd) {
3642
3775
  return (await Promise.all(repos.map((repo) => repoDiff(path.join(cwd, repo), repo === "" ? "" : `${repo}/`)))).join("");
3643
3776
  }
3644
3777
  function git(cwd, args) {
3645
- return run$3("git", args, {
3778
+ return run$4("git", args, {
3646
3779
  cwd,
3647
3780
  maxBuffer: 67108864
3648
3781
  });
@@ -3679,6 +3812,19 @@ async function gitStatus(cwd) {
3679
3812
  }
3680
3813
  return status;
3681
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
+ }
3682
3828
  async function rebaseOnUpstream(cwd) {
3683
3829
  try {
3684
3830
  await git(cwd, ["rebase", "@{upstream}"]);
@@ -4101,8 +4247,10 @@ function rebuildThreads() {
4101
4247
  const config = readConfig();
4102
4248
  const key = JSON.stringify([
4103
4249
  projectPaths$1(),
4104
- config.enabledProviders,
4105
- config.claudeBaseDir
4250
+ config.claude.enabled,
4251
+ config.codex.enabled,
4252
+ config.opencode.enabled,
4253
+ config.claude.baseDir
4106
4254
  ]);
4107
4255
  if (key === builtFor) return;
4108
4256
  builtFor = key;
@@ -4116,7 +4264,7 @@ function threadFailed(provider) {
4116
4264
  }
4117
4265
  function knownThreads() {
4118
4266
  const known = new Set(projects().map((project) => project.id));
4119
- 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));
4120
4268
  }
4121
4269
  async function providerThreads(provider) {
4122
4270
  await built;
@@ -4191,7 +4339,7 @@ async function relistAll() {
4191
4339
  await Promise.all(allProviders$2.map((provider) => relist(provider)));
4192
4340
  }
4193
4341
  async function relist(provider) {
4194
- if (!readConfig().enabledProviders[provider]) {
4342
+ if (!readConfig()[provider].enabled) {
4195
4343
  threads[provider] = /* @__PURE__ */ new Map();
4196
4344
  failures.delete(provider);
4197
4345
  broadcast({ type: "threads" });
@@ -4291,7 +4439,7 @@ function withStatus(items) {
4291
4439
  });
4292
4440
  }
4293
4441
  function enabledProviders() {
4294
- return allProviders$1.filter((provider) => readConfig().enabledProviders[provider]);
4442
+ return allProviders$1.filter((provider) => readConfig()[provider].enabled);
4295
4443
  }
4296
4444
  function projectPaths() {
4297
4445
  return projects().map((project) => [project.path, project.id]);
@@ -4310,18 +4458,18 @@ function providerOf(sessionId) {
4310
4458
  if (provider === void 0) throw new Error("offsite does not know this session yet, reload the page");
4311
4459
  return provider;
4312
4460
  }
4313
- function recordInteraction(id, provider) {
4461
+ function recordInteraction(id, provider, status) {
4314
4462
  db.insert(sessions$1).values({
4315
4463
  sessionId: id,
4316
4464
  provider,
4317
4465
  interactedAt: Date.now(),
4318
- status: "active"
4466
+ status: status ?? "active"
4319
4467
  }).onConflictDoUpdate({
4320
4468
  target: sessions$1.sessionId,
4321
4469
  set: {
4322
4470
  provider,
4323
4471
  interactedAt: Date.now(),
4324
- 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`
4325
4473
  }
4326
4474
  }).run();
4327
4475
  }
@@ -4371,7 +4519,7 @@ async function list(provider) {
4371
4519
  const paths = new Map(projectPaths());
4372
4520
  const before = statuses[provider];
4373
4521
  try {
4374
- if (!readConfig().enabledProviders[provider]) {
4522
+ if (!readConfig()[provider].enabled) {
4375
4523
  listed[provider] = [];
4376
4524
  statuses[provider] = {
4377
4525
  provider,
@@ -4426,7 +4574,7 @@ async function lookup(provider, sessionId, paths) {
4426
4574
  function watchSessions() {
4427
4575
  for (const watcher of watchers) watcher.close();
4428
4576
  watchers = [["claude", path.join(claudeBaseDir(), "projects")], ["codex", path.join(codexBaseDir, "sessions")]].flatMap(([provider, dir]) => {
4429
- if (!readConfig().enabledProviders[provider]) return [];
4577
+ if (!readConfig()[provider].enabled) return [];
4430
4578
  try {
4431
4579
  const watcher = watch(dir, { recursive: true }, (_, filename) => {
4432
4580
  fileChanged(provider, filename);
@@ -4520,7 +4668,7 @@ async function fork(sessionId, projectId, messageId) {
4520
4668
  const provider = providerOf(sessionId);
4521
4669
  const result = await forkWith(provider, sessionId, projectPath(projectId), messageId);
4522
4670
  providers.set(result.sessionId, provider);
4523
- recordInteraction(result.sessionId, provider);
4671
+ recordInteraction(result.sessionId, provider, null);
4524
4672
  markChanged(provider, result.sessionId);
4525
4673
  refreshSessions(provider);
4526
4674
  return result;
@@ -4666,16 +4814,16 @@ function providerName(provider) {
4666
4814
  }
4667
4815
  function storedAccounts() {
4668
4816
  const config = readConfig();
4669
- return [...config.claudeAccounts.map((item) => ({
4817
+ return [...config.claude.accounts.map((item) => ({
4670
4818
  ...item,
4671
4819
  configDir: item.configDir[platform()]
4672
- })), ...config.codexAccounts.map((item) => ({
4820
+ })), ...config.codex.accounts.map((item) => ({
4673
4821
  ...item,
4674
4822
  configDir: item.configDir[platform()]
4675
4823
  }))];
4676
4824
  }
4677
4825
  function assertEnabled(provider, accountId) {
4678
- 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`);
4679
4827
  if (provider === "opencode") return;
4680
4828
  if (!storedAccount(provider, accountId).enabled) throw new Error("That account is disabled in Settings");
4681
4829
  }
@@ -4705,7 +4853,7 @@ function storedAccount(provider, accountId) {
4705
4853
  }
4706
4854
  async function accounts() {
4707
4855
  const stored = await Promise.all(storedAccounts().map((account) => {
4708
- if (!account.enabled || !readConfig().enabledProviders[account.provider]) return disabledAccount(account);
4856
+ if (!account.enabled || !readConfig()[account.provider].enabled) return disabledAccount(account);
4709
4857
  switch (account.provider) {
4710
4858
  case "claude": return claudeAccount(account);
4711
4859
  case "codex": return codexAccount(account);
@@ -4714,7 +4862,7 @@ async function accounts() {
4714
4862
  return {
4715
4863
  claudeAccounts: stored.filter((item) => item.provider === "claude"),
4716
4864
  codexAccounts: stored.filter((item) => item.provider === "codex"),
4717
- openCode: { available: openCodeInstalled() && readConfig().enabledProviders.opencode }
4865
+ openCode: { available: openCodeInstalled() && readConfig().opencode.enabled }
4718
4866
  };
4719
4867
  }
4720
4868
  function disabledAccount(account) {
@@ -4743,7 +4891,7 @@ async function publishAccounts() {
4743
4891
  async function capabilities(provider, accountId, cwd) {
4744
4892
  assertEnabled(provider, accountId);
4745
4893
  const result = await providerCapabilities(provider, accountId, cwd);
4746
- const settings = readConfig().models[provider];
4894
+ const settings = readConfig()[provider].models;
4747
4895
  return {
4748
4896
  ...result,
4749
4897
  defaultModel: defaultModels[provider],
@@ -4776,51 +4924,80 @@ function providerCapabilities(provider, accountId, cwd) {
4776
4924
  function addAccount(provider, name, configDir) {
4777
4925
  const account = validAccount(name, configDir);
4778
4926
  const config = readConfig();
4779
- const key = `${provider}Accounts`;
4780
4927
  saveConfig({
4781
4928
  ...config,
4782
- [key]: [...config[key], {
4783
- id: randomUUID(),
4784
- provider,
4785
- name: account.name,
4786
- color: null,
4787
- configDir: { [platform()]: account.configDir },
4788
- enabled: true
4789
- }]
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
+ }
4790
4940
  });
4941
+ publishAccounts();
4942
+ return null;
4791
4943
  }
4792
4944
  async function updateAccount(update) {
4793
4945
  const before = storedAccounts().find((item) => item.id === update.id);
4794
4946
  if (before === void 0) throw new Error("That account was removed");
4795
4947
  const account = validAccount(update.name, update.configDir);
4796
4948
  const config = readConfig();
4797
- const key = `${before.provider}Accounts`;
4798
4949
  saveConfig({
4799
4950
  ...config,
4800
- [key]: config[key].map((item) => item.id === update.id ? {
4801
- ...item,
4802
- name: account.name,
4803
- color: update.color,
4804
- configDir: {
4805
- ...item.configDir,
4806
- [platform()]: account.configDir
4807
- },
4808
- enabled: update.enabled
4809
- } : 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
+ }
4810
4964
  });
4811
4965
  forgetClaude(update.id);
4812
4966
  forgetUsage(update.id);
4813
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;
4814
4983
  }
4815
4984
  function removeAccount(id) {
4816
4985
  const config = readConfig();
4817
4986
  saveConfig({
4818
4987
  ...config,
4819
- claudeAccounts: config.claudeAccounts.filter((item) => item.id !== id),
4820
- 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
+ }
4821
4996
  });
4822
4997
  forgetClaude(id);
4823
4998
  forgetUsage(id);
4999
+ publishAccounts();
5000
+ return null;
4824
5001
  }
4825
5002
  function validAccount(name, configDir) {
4826
5003
  if (name.trim() === "") throw new Error("Give the account a name");
@@ -4955,6 +5132,213 @@ function targets() {
4955
5132
  if (notifier === null || notifier.endpoint === null) return [];
4956
5133
  return db.select().from(pushSubscriptions).where(and(eq(pushSubscriptions.server, server$1), eq(pushSubscriptions.endpoint, notifier.endpoint))).all();
4957
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
+ }
4958
5342
  // src/lib/claude/claude.ts
4959
5343
  const log$8 = logger.withTag("claude");
4960
5344
  const efforts = [
@@ -5064,7 +5448,8 @@ function runOptions(options) {
5064
5448
  ...settings.model === null ? {} : { model: settings.model },
5065
5449
  ...settings.effort === null ? {} : { effort: claudeEffort(settings.effort) },
5066
5450
  allowDangerouslySkipPermissions: true,
5067
- includePartialMessages: true
5451
+ includePartialMessages: true,
5452
+ perTaskStopAffordance: true
5068
5453
  };
5069
5454
  }
5070
5455
  async function claudeCapabilities(account, cwd) {
@@ -5213,16 +5598,6 @@ function claudeLogin(configDir) {
5213
5598
  } : null
5214
5599
  };
5215
5600
  }
5216
- function isLinked(configDir) {
5217
- const base = claudeBaseDir();
5218
- try {
5219
- if (realpathSync(configDir) === realpathSync(base)) return true;
5220
- return realpathSync(path.join(configDir, "projects")) === realpathSync(path.join(base, "projects"));
5221
- } catch (error) {
5222
- if (error.code === "ENOENT") return false;
5223
- throw error;
5224
- }
5225
- }
5226
5601
  function initialization(accountId, cwd) {
5227
5602
  const key = `${accountId}\n${cwd}`;
5228
5603
  const existing = probes.get(key);
@@ -5251,6 +5626,7 @@ async function* idle(until) {
5251
5626
  }
5252
5627
  // src/lib/claude/items.ts
5253
5628
  const notifications = /<task-notification>([\s\S]*?)<\/task-notification>/g;
5629
+ const receivedMessage = /^(?:Another Claude session sent a message:\s*)?<(agent-message|cross-session-message) from="([^"]*)">([\s\S]*?)<\/\1>/;
5254
5630
  const taskStatuses = /* @__PURE__ */ new Map([
5255
5631
  ["completed", "completed"],
5256
5632
  ["failed", "failed"],
@@ -5380,6 +5756,22 @@ function toItems(messages) {
5380
5756
  }
5381
5757
  continue;
5382
5758
  }
5759
+ const received = receivedMessage.exec(block.text);
5760
+ if (received !== null) {
5761
+ const owner = owners.get(received[2]);
5762
+ const sender = owner === void 0 ? null : items[owner];
5763
+ items.push({
5764
+ id: `${message.uuid}:received`,
5765
+ parent: message.parent,
5766
+ time: message.time,
5767
+ kind: "received_message",
5768
+ source: received[1] === "agent-message" ? "agent" : "session",
5769
+ sender: sender?.kind === "subagent" ? sender.description : received[2],
5770
+ target: sender?.id ?? null,
5771
+ text: received[3].trim()
5772
+ });
5773
+ continue;
5774
+ }
5383
5775
  if (block.text.startsWith("[Request interrupted by user")) {
5384
5776
  items.push({
5385
5777
  id: message.uuid,
@@ -5390,7 +5782,8 @@ function toItems(messages) {
5390
5782
  });
5391
5783
  continue;
5392
5784
  }
5393
- texts.push(userText$1(block.text));
5785
+ const part = userText$2(block.text);
5786
+ if (part.text !== "") texts.push(part);
5394
5787
  }
5395
5788
  if (texts.length === 0 && images.length === 0) continue;
5396
5789
  items.push({
@@ -5641,25 +6034,6 @@ function applyResult(item, result) {
5641
6034
  default: return item;
5642
6035
  }
5643
6036
  }
5644
- function userText$1(value) {
5645
- const command = /<command-name>(.*?)<\/command-name>/s.exec(value);
5646
- if (command !== null) {
5647
- const args = /<command-args>(.*?)<\/command-args>/s.exec(value);
5648
- return {
5649
- format: "command",
5650
- text: `${command[1]} ${args?.[1] ?? ""}`.trim()
5651
- };
5652
- }
5653
- const output = /^<local-command-(?:stdout|stderr)>(.*)<\/local-command-(?:stdout|stderr)>$/s.exec(value.trim());
5654
- if (output !== null) return {
5655
- format: "output",
5656
- text: output[1]
5657
- };
5658
- return {
5659
- format: "text",
5660
- text: value
5661
- };
5662
- }
5663
6037
  function text$2(value) {
5664
6038
  return typeof value === "string" ? value : "";
5665
6039
  }
@@ -5784,10 +6158,18 @@ async function image(sessionId, uuid, index) {
5784
6158
  data: block.data
5785
6159
  };
5786
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
+ }
5787
6169
  async function history$1(sessionId, cwd) {
5788
6170
  const found = runs.get(sessionId);
5789
6171
  const run = found?.provider === "claude" ? found : void 0;
5790
- const [disk, session] = await Promise.all([getSessionMessages(sessionId, { dir: cwd }), loadSession(sessionId)]);
6172
+ const [disk, session] = await Promise.all([shownMessages(sessionId, cwd), loadSession(sessionId)]);
5791
6173
  const entries = session === null ? [] : session.entries;
5792
6174
  const resumeAt = run?.resumeAt ?? null;
5793
6175
  const kept = resumeAt === null ? disk : disk.slice(0, disk.findIndex((item) => item.uuid === resumeAt) + 1);
@@ -5830,6 +6212,28 @@ async function agentMessages(sessionId, cwd, agentId, parent) {
5830
6212
  parent_tool_use_id: parent
5831
6213
  }, null)]));
5832
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
+ }
5833
6237
  // src/lib/claude/run.ts
5834
6238
  const IDLE_LIMIT = 6e5;
5835
6239
  var Run = class {
@@ -5842,6 +6246,7 @@ var Run = class {
5842
6246
  query;
5843
6247
  closed;
5844
6248
  resumeAt;
6249
+ cut = null;
5845
6250
  queue = [];
5846
6251
  settings;
5847
6252
  context = null;
@@ -5852,6 +6257,8 @@ var Run = class {
5852
6257
  compacting = false;
5853
6258
  backgroundTasks = 0;
5854
6259
  backgroundAgents = 0;
6260
+ agentTasks = 0;
6261
+ failed = false;
5855
6262
  tasks = {};
5856
6263
  inbox = [];
5857
6264
  wake = null;
@@ -5924,30 +6331,7 @@ var Run = class {
5924
6331
  respond(requestId, decision, answers) {
5925
6332
  const pending = this.pending.get(requestId);
5926
6333
  if (pending === void 0) return;
5927
- const input = pending.input;
5928
- switch (decision) {
5929
- case "allow":
5930
- this.settle(requestId, {
5931
- behavior: "allow",
5932
- updatedInput: answers === null ? input : {
5933
- ...input,
5934
- answers
5935
- }
5936
- });
5937
- return;
5938
- case "always":
5939
- this.settle(requestId, {
5940
- behavior: "allow",
5941
- updatedInput: input,
5942
- updatedPermissions: pending.suggestions
5943
- });
5944
- return;
5945
- case "deny": this.settle(requestId, {
5946
- behavior: "deny",
5947
- message: "The user rejected this action",
5948
- interrupt: true
5949
- });
5950
- }
6334
+ this.settle(requestId, permissionResult(pending.input, pending.suggestions, decision, answers));
5951
6335
  }
5952
6336
  stop() {
5953
6337
  this.stopping = true;
@@ -6041,7 +6425,9 @@ var Run = class {
6041
6425
  return;
6042
6426
  }
6043
6427
  case "result": {
6044
- 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);
6045
6431
  this.finishTurn();
6046
6432
  const usage = this.model === null ? void 0 : message.modelUsage[this.model];
6047
6433
  if (this.context !== null && usage !== void 0) this.context = {
@@ -6063,6 +6449,7 @@ var Run = class {
6063
6449
  });
6064
6450
  return;
6065
6451
  case "background_tasks_changed": {
6452
+ this.agentTasks = message.tasks.filter((task) => task.task_type !== "local_bash").length;
6066
6453
  const live = message.tasks.filter((task) => task.ambient !== true);
6067
6454
  this.backgroundTasks = live.length;
6068
6455
  this.backgroundAgents = live.filter((task) => task.task_type === "local_agent").length;
@@ -6209,6 +6596,30 @@ var Run = class {
6209
6596
  });
6210
6597
  }
6211
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
+ }
6212
6623
  // src/lib/proxy/sites.ts
6213
6624
  const sites = /* @__PURE__ */ new Map();
6214
6625
  const siteNames = /* @__PURE__ */ new Map();
@@ -6360,7 +6771,6 @@ async function rewindNow(request) {
6360
6771
  const sessionId = request.sessionId;
6361
6772
  const cwd = projectPath(request.projectId);
6362
6773
  if (runs.get(sessionId)?.running === true) throw new Error(`Stop ${providerName(providerOf(sessionId))} before rewinding`);
6363
- if ((runs.get(sessionId)?.backgroundTasks ?? 0) > 0) throw new Error("Wait for background tasks to finish before rewinding");
6364
6774
  const run = await runFor(sessionId, cwd, request.settings);
6365
6775
  if (run.provider === "codex") {
6366
6776
  if (request.code) throw new Error("Codex cannot restore files");
@@ -6390,9 +6800,16 @@ async function rewindNow(request) {
6390
6800
  };
6391
6801
  }
6392
6802
  if (request.dryRun || !request.conversation) return result;
6393
- const disk = await getSessionMessages(sessionId, { dir: cwd });
6803
+ const disk = await shownMessages(sessionId, cwd);
6394
6804
  const index = disk.findIndex((item) => item.uuid === request.messageId);
6395
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
+ }
6396
6813
  run.stop();
6397
6814
  await run.closed;
6398
6815
  new Run({
@@ -6443,7 +6860,7 @@ async function submitFirst(client, run, request, projectId) {
6443
6860
  from: request.projectId
6444
6861
  });
6445
6862
  await run.submit(request.prompt, request.delivery);
6446
- recordInteraction(sessionId, run.provider);
6863
+ recordInteraction(sessionId, run.provider, request.status);
6447
6864
  }
6448
6865
  async function sendExisting(client, sessionId, cwd, request) {
6449
6866
  const provider = providerOf(sessionId);
@@ -6457,7 +6874,7 @@ async function sendExisting(client, sessionId, cwd, request) {
6457
6874
  if (run === existing) await run.configure(request.settings);
6458
6875
  else await open$1(client, sessionId, request.projectId);
6459
6876
  await run.submit(request.prompt, request.delivery);
6460
- recordInteraction(sessionId, provider);
6877
+ recordInteraction(sessionId, provider, request.status);
6461
6878
  if (emptied.has(sessionId) && request.prompt.text.trim() !== "") {
6462
6879
  emptied.delete(sessionId);
6463
6880
  await updateSession(sessionId, { title: request.prompt.text.trim() });
@@ -6507,7 +6924,7 @@ async function startRun(cwd, settings) {
6507
6924
  }
6508
6925
  async function changedElsewhere(run, cwd) {
6509
6926
  if (run.messages.length === 0) return false;
6510
- const last = (await getSessionMessages(run.sessionId, { dir: cwd })).at(-1)?.uuid;
6927
+ const last = (await shownMessages(run.sessionId, cwd)).at(-1)?.uuid;
6511
6928
  return last !== void 0 && !run.messages.some((item) => item.uuid === last);
6512
6929
  }
6513
6930
  // src/services/export.ts
@@ -6602,6 +7019,7 @@ function itemText(item, level) {
6602
7019
  }
6603
7020
  case "subagent": return level < 1 ? null : `**Subagent (${item.agent}):** ${item.description}${statusText(item.status)}`;
6604
7021
  case "agent_message": return level < 1 ? null : `**Message${item.target === null ? "" : ` to ${item.target}`}:** ${item.summary}${statusText(item.status)}`;
7022
+ case "received_message": return level < 1 ? null : `**Message from ${item.sender}:**\n\n${item.text}`;
6605
7023
  case "notice": return level < 1 ? null : `_${item.text}_`;
6606
7024
  case "compaction":
6607
7025
  if (level < 1) return null;
@@ -6646,7 +7064,7 @@ async function searchMessages(query) {
6646
7064
  "claude",
6647
7065
  "codex",
6648
7066
  "opencode"
6649
- ].filter((provider) => readConfig().enabledProviders[provider]).map(async (provider) => {
7067
+ ].filter((provider) => readConfig()[provider].enabled).map(async (provider) => {
6650
7068
  try {
6651
7069
  return {
6652
7070
  provider,
@@ -8174,8 +8592,8 @@ const handlers = {
8174
8592
  "claude",
8175
8593
  "codex",
8176
8594
  "opencode"
8177
- ]) if (before.enabledProviders[provider] && !saved.enabledProviders[provider]) await stopProvider(provider);
8178
- 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;
8179
8597
  watchSessions();
8180
8598
  refreshAll();
8181
8599
  publishAccounts();
@@ -8262,7 +8680,7 @@ const handlers = {
8262
8680
  const run = runs.get(params.sessionId);
8263
8681
  if (run === void 0) return null;
8264
8682
  await run.respond(params.requestId, params.decision, params.answers);
8265
- recordInteraction(params.sessionId, run.provider);
8683
+ recordInteraction(params.sessionId, run.provider, null);
8266
8684
  return null;
8267
8685
  },
8268
8686
  "sessions.rewind": (params) => rewind(params),
@@ -8272,21 +8690,11 @@ const handlers = {
8272
8690
  return result;
8273
8691
  },
8274
8692
  "accounts.list": () => accounts(),
8275
- "accounts.add": (params) => {
8276
- addAccount(params.provider, params.name, params.configDir);
8277
- publishAccounts();
8278
- return null;
8279
- },
8280
- "accounts.update": async (params) => {
8281
- await updateAccount(params);
8282
- publishAccounts();
8283
- return null;
8284
- },
8285
- "accounts.remove": (params) => {
8286
- removeAccount(params.id);
8287
- publishAccounts();
8288
- return null;
8289
- },
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),
8290
8698
  "accounts.usage": (params) => readUsage(params.id, params.force),
8291
8699
  capabilities: (params) => capabilities(params.provider, params.accountId, params.projectId === null || params.projectId === "no-project" ? os.homedir() : projectPath(params.projectId)),
8292
8700
  "files.list": (params) => params.projectId === "no-project" ? [] : listFiles(projectPath(params.projectId)),
@@ -9671,7 +10079,7 @@ async function backfillSessionProviders() {
9671
10079
  const rows = db.select().from(sessions$1).where(isNull(sessions$1.provider)).all();
9672
10080
  if (rows.length === 0) return;
9673
10081
  const paths = new Map(projects().map((project) => [project.path, project.id]));
9674
- const enabled = allProviders.filter((provider) => readConfig().enabledProviders[provider]);
10082
+ const enabled = allProviders.filter((provider) => readConfig()[provider].enabled);
9675
10083
  for (const row of rows) {
9676
10084
  const answers = await Promise.all(enabled.map(async (provider) => {
9677
10085
  try {