offsite 0.0.86 → 0.0.88

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.
@@ -16,7 +16,7 @@ import { lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, rm, rmdir, st
16
16
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
17
17
  import { OpenCode, isSessionNotFoundError } from "@opencode/client";
18
18
  import { ensure, headers } from "@opencode/client/service";
19
- import { and, eq, gt, inArray, lte, max, sql } from "drizzle-orm";
19
+ import { and, eq, gt, inArray, isNull, lte, max, sql } from "drizzle-orm";
20
20
  import { pathToFileURL } from "node:url";
21
21
  import { sumBy } from "es-toolkit";
22
22
  import { DatabaseSync } from "node:sqlite";
@@ -154,7 +154,7 @@ const browserLogger = fileLogger("browser.log");
154
154
  // src/services/usage.ts
155
155
  const freshFor = 6e4;
156
156
  const forcedFreshFor = 1e4;
157
- const log$18 = logger.withTag("usage");
157
+ const log$20 = logger.withTag("usage");
158
158
  const entries = /* @__PURE__ */ new Map();
159
159
  const fetching = /* @__PURE__ */ new Map();
160
160
  function cachedUsage(accountId) {
@@ -172,7 +172,7 @@ function recordLimits(accountId, limits) {
172
172
  limits: [...current.limits.map((limit) => limits.find((item) => item.window === limit.window) ?? limit), ...limits.filter((item) => !current.limits.some((limit) => limit.window === item.window))],
173
173
  updatedAt: Date.now()
174
174
  });
175
- log$18.debug(`account ${accountId} reported ${limits.map((limit) => limit.window).join(", ")} limits from a running session`);
175
+ log$20.debug(`account ${accountId} reported ${limits.map((limit) => limit.window).join(", ")} limits from a running session`);
176
176
  broadcast({
177
177
  type: "limits",
178
178
  accountId,
@@ -182,12 +182,12 @@ function recordLimits(accountId, limits) {
182
182
  async function readUsage(accountId, force) {
183
183
  const current = cachedUsage(accountId);
184
184
  if (current.fetchedAt !== null && Date.now() - current.fetchedAt < (force ? forcedFreshFor : freshFor)) {
185
- log$18.debug(`account ${accountId} served from cache, ${Math.round((Date.now() - current.fetchedAt) / 1e3)}s old${force ? " (forced)" : ""}`);
185
+ log$20.debug(`account ${accountId} served from cache, ${Math.round((Date.now() - current.fetchedAt) / 1e3)}s old${force ? " (forced)" : ""}`);
186
186
  return current;
187
187
  }
188
188
  const pending = fetching.get(accountId);
189
189
  if (pending !== void 0) {
190
- log$18.debug(`account ${accountId} joined the fetch in progress`);
190
+ log$20.debug(`account ${accountId} joined the fetch in progress`);
191
191
  return pending;
192
192
  }
193
193
  const next = fetchUsage(accountId);
@@ -206,10 +206,10 @@ async function fetchUsage(accountId) {
206
206
  if (account === void 0) throw new Error("That account was removed");
207
207
  let usage;
208
208
  const started = performance.now();
209
- log$18.debug(`fetching limits for ${account.name} (${account.provider})`);
209
+ log$20.debug(`fetching limits for ${account.name} (${account.provider})`);
210
210
  try {
211
211
  const limits = await providerLimits(account);
212
- log$18.debug(`fetched ${limits.length} limits for ${account.name} in ${Math.round(performance.now() - started)}ms`);
212
+ log$20.debug(`fetched ${limits.length} limits for ${account.name} in ${Math.round(performance.now() - started)}ms`);
213
213
  usage = {
214
214
  limits,
215
215
  fetchedAt: Date.now(),
@@ -217,7 +217,7 @@ async function fetchUsage(accountId) {
217
217
  error: null
218
218
  };
219
219
  } catch (error) {
220
- log$18.warn(`could not read usage limits for ${account.name}:`, error);
220
+ log$20.warn(`could not read usage limits for ${account.name}:`, error);
221
221
  usage = {
222
222
  ...cachedUsage(accountId),
223
223
  fetchedAt: Date.now(),
@@ -369,7 +369,7 @@ function isRecord(value) {
369
369
  const home = args.dir;
370
370
  const lockFile = path.join(home, "server.pid");
371
371
  const savedServer = z.object({ port: z.number() }).nullable();
372
- const log$17 = logger.withTag("storage");
372
+ const log$19 = logger.withTag("storage");
373
373
  mkdirSync(home, { recursive: true });
374
374
  const scratchAvailable = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: home }).status !== 0;
375
375
  function listenPort() {
@@ -402,7 +402,7 @@ function lock() {
402
402
  const writtenThisBoot = modified > Date.now() - os.uptime() * 1e3;
403
403
  if (validPid && writtenThisBoot && pid !== process.pid && isRunning(pid)) {
404
404
  console.error(`offsite is already running for ${home} (pid ${pid})`);
405
- log$17.error(`offsite is already running for ${home} (pid ${pid})`);
405
+ log$19.error(`offsite is already running for ${home} (pid ${pid})`);
406
406
  process.exit(1);
407
407
  }
408
408
  unlinkSync(lockFile);
@@ -435,7 +435,7 @@ function readSettings(name, schema, defaults) {
435
435
  try {
436
436
  value = readJson(name);
437
437
  } catch (error) {
438
- log$17.warn(`invalid ${file}, using the defaults:`, error);
438
+ log$19.warn(`invalid ${file}, using the defaults:`, error);
439
439
  return structuredClone(defaults);
440
440
  }
441
441
  if (value === null) return structuredClone(defaults);
@@ -447,10 +447,10 @@ function readSettings(name, schema, defaults) {
447
447
  for (const target of targets) {
448
448
  const discarded = discard(value, target);
449
449
  if (discarded === null) {
450
- log$17.warn(`invalid ${file}, using the defaults:\n${z.prettifyError(result.error)}`);
450
+ log$19.warn(`invalid ${file}, using the defaults:\n${z.prettifyError(result.error)}`);
451
451
  return structuredClone(defaults);
452
452
  }
453
- log$17.warn(`${file}: discarded ${discarded.map(String).join(".")} (${issue.message} at ${target.map(String).join(".")})`);
453
+ log$19.warn(`${file}: discarded ${discarded.map(String).join(".")} (${issue.message} at ${target.map(String).join(".")})`);
454
454
  }
455
455
  result = schema.safeParse(value);
456
456
  }
@@ -518,7 +518,7 @@ function quote(value) {
518
518
  return `'${value.replaceAll("'", "'\\''")}'`;
519
519
  }
520
520
  // src/lib/codex/app-server.ts
521
- const log$16 = logger.withTag("codex");
521
+ const log$18 = logger.withTag("codex");
522
522
  const IDLE_LIMIT$3 = 18e5;
523
523
  const START_TIMEOUT = 15e3;
524
524
  const REQUEST_TIMEOUT = 3e4;
@@ -559,7 +559,7 @@ var AppServer = class {
559
559
  const executable = findCodex();
560
560
  if (executable === null) throw new Error("Codex is not installed or not on the PATH");
561
561
  this.home = home;
562
- log$16.info(`starting the Codex app server for ${home}`);
562
+ log$18.info(`starting the Codex app server for ${home}`);
563
563
  this.child = spawn(executable, ["app-server"], {
564
564
  env: {
565
565
  ...process.env,
@@ -586,7 +586,7 @@ var AppServer = class {
586
586
  requestAttestation: false
587
587
  }
588
588
  }, START_TIMEOUT).then(() => this.write({ method: "initialized" }), (error) => {
589
- log$16.warn(`the Codex app server for ${home} did not start:`, error);
589
+ log$18.warn(`the Codex app server for ${home} did not start:`, error);
590
590
  this.child.kill();
591
591
  throw error;
592
592
  });
@@ -598,10 +598,10 @@ var AppServer = class {
598
598
  const started = performance.now();
599
599
  try {
600
600
  const result = await this.call(method, params, REQUEST_TIMEOUT);
601
- log$16.debug(`${method} took ${Math.round(performance.now() - started)}ms`);
601
+ log$18.debug(`${method} took ${Math.round(performance.now() - started)}ms`);
602
602
  return result;
603
603
  } catch (error) {
604
- log$16.debug(`${method} failed after ${Math.round(performance.now() - started)}ms: ${error}`);
604
+ log$18.debug(`${method} failed after ${Math.round(performance.now() - started)}ms: ${error}`);
605
605
  throw error;
606
606
  }
607
607
  }
@@ -621,7 +621,7 @@ var AppServer = class {
621
621
  });
622
622
  }
623
623
  stop() {
624
- log$16.info(`stopping the Codex app server for ${this.home}`);
624
+ log$18.info(`stopping the Codex app server for ${this.home}`);
625
625
  clearTimeout(this.idleTimer);
626
626
  if (servers.get(this.home) === this) servers.delete(this.home);
627
627
  this.child.stdin.end();
@@ -662,7 +662,7 @@ var AppServer = class {
662
662
  try {
663
663
  message = JSON.parse(line);
664
664
  } catch {
665
- log$16.warn(`ignoring a Codex line that is not JSON: ${line}`);
665
+ log$18.warn(`ignoring a Codex line that is not JSON: ${line}`);
666
666
  return;
667
667
  }
668
668
  if (message.method === void 0) {
@@ -712,7 +712,7 @@ var AppServer = class {
712
712
  exit(error) {
713
713
  if (this.exited !== null) return;
714
714
  this.exited = error;
715
- log$16.info(`the Codex app server for ${this.home} exited:`, error.message);
715
+ log$18.info(`the Codex app server for ${this.home} exited:`, error.message);
716
716
  clearTimeout(this.idleTimer);
717
717
  if (servers.get(this.home) === this) servers.delete(this.home);
718
718
  for (const waiter of this.waiting.values()) waiter.reject(error);
@@ -722,7 +722,7 @@ var AppServer = class {
722
722
  }
723
723
  };
724
724
  // src/lib/codex/accounts.ts
725
- const log$15 = logger.withTag("codex");
725
+ const log$17 = logger.withTag("codex");
726
726
  const privateEntries = [
727
727
  "auth.json",
728
728
  "models_cache.json",
@@ -739,6 +739,7 @@ async function codexAccount(account) {
739
739
  email: null,
740
740
  displayName: null,
741
741
  organization: null,
742
+ identity: null,
742
743
  plan: null,
743
744
  linked
744
745
  };
@@ -753,7 +754,7 @@ async function codexAccount(account) {
753
754
  case "chatgpt": return {
754
755
  ...base,
755
756
  email: result.account.email,
756
- displayName: codexDisplayName(configDir),
757
+ ...codexLogin(configDir),
757
758
  organization: result.account.workspaceName,
758
759
  plan: result.account.planType,
759
760
  error: null,
@@ -825,21 +826,40 @@ function toLimits(snapshot) {
825
826
  rejected: window.usedPercent >= 100
826
827
  }]);
827
828
  }
828
- function codexDisplayName(home) {
829
+ function codexLogin(home) {
829
830
  let raw;
830
831
  try {
831
832
  raw = JSON.parse(readFileSync(path.join(home, "auth.json"), "utf8"));
832
833
  } catch (error) {
833
- if (error.code !== "ENOENT") log$15.warn(`could not read the Codex account name:`, error);
834
- return null;
834
+ if (error.code !== "ENOENT") log$17.warn(`could not read the Codex account:`, error);
835
+ return {
836
+ displayName: null,
837
+ identity: null
838
+ };
835
839
  }
836
- if (!isRecord(raw) || !isRecord(raw.tokens) || typeof raw.tokens.id_token !== "string") return null;
840
+ if (!isRecord(raw) || !isRecord(raw.tokens) || typeof raw.tokens.id_token !== "string") return {
841
+ displayName: null,
842
+ identity: null
843
+ };
837
844
  const payload = raw.tokens.id_token.split(".").at(1);
838
- if (payload === void 0) return null;
845
+ if (payload === void 0) return {
846
+ displayName: null,
847
+ identity: null
848
+ };
839
849
  const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
840
- if (!isRecord(claims) || typeof claims.name !== "string") return null;
841
- const name = claims.name.trim();
842
- return name === "" ? null : name;
850
+ if (!isRecord(claims)) return {
851
+ displayName: null,
852
+ identity: null
853
+ };
854
+ const name = typeof claims.name === "string" ? claims.name.trim() : "";
855
+ const auth = claims["https://api.openai.com/auth"];
856
+ return {
857
+ displayName: name === "" ? null : name,
858
+ identity: isRecord(auth) && typeof auth.chatgpt_user_id === "string" && typeof auth.chatgpt_account_id === "string" ? {
859
+ accountId: auth.chatgpt_user_id,
860
+ organizationId: auth.chatgpt_account_id
861
+ } : null
862
+ };
843
863
  }
844
864
  function isMirror(dir) {
845
865
  try {
@@ -1126,19 +1146,6 @@ const configSchema = z.strictObject({
1126
1146
  codex: z.boolean().default(true),
1127
1147
  opencode: z.boolean().default(true)
1128
1148
  }).prefault({}),
1129
- newThreads: z.strictObject({
1130
- provider: z.enum([
1131
- "claude",
1132
- "codex",
1133
- "opencode"
1134
- ]).default("claude"),
1135
- accountId: z.string().nullable().default(null),
1136
- models: z.strictObject({
1137
- claude: z.string().nullable().default(null),
1138
- codex: z.string().nullable().default(null),
1139
- opencode: z.string().nullable().default(null)
1140
- }).prefault({})
1141
- }).prefault({}),
1142
1149
  commitMessage: generationModel,
1143
1150
  titleGeneration: generationModel,
1144
1151
  models: z.strictObject({
@@ -1148,7 +1155,11 @@ const configSchema = z.strictObject({
1148
1155
  }).prefault({}),
1149
1156
  delivery: z.enum(["queue", "steer"]).default("steer"),
1150
1157
  autoSkipQuestionTimeout: z.number().positive().nullable().default(180),
1151
- autoPermissionMode: z.enum(["allow", "deny"]).default("allow"),
1158
+ autoPermissionMode: z.enum([
1159
+ "bypass",
1160
+ "allow",
1161
+ "deny"
1162
+ ]).default("bypass"),
1152
1163
  autoPermissionTimeout: z.number().positive().nullable().default(15),
1153
1164
  terminal: z.strictObject({
1154
1165
  shell: z.strictObject({
@@ -1210,7 +1221,7 @@ function saveConfig(value) {
1210
1221
  return result.data;
1211
1222
  }
1212
1223
  // src/lib/live.ts
1213
- const log$14 = logger.withTag("runs");
1224
+ const log$16 = logger.withTag("runs");
1214
1225
  const runs = /* @__PURE__ */ new Map();
1215
1226
  const titleJobs = /* @__PURE__ */ new Set();
1216
1227
  let states = /* @__PURE__ */ new Map();
@@ -1237,10 +1248,10 @@ function broadcastActivity() {
1237
1248
  ...run.compacting ? ["compacting"] : []
1238
1249
  ].join(", ");
1239
1250
  next.set(run.sessionId, state);
1240
- if (!states.has(run.sessionId)) log$14.info(`${run.provider} session ${run.sessionId} opened in ${run.cwd} with account ${run.settings.accountId}`);
1241
- if (states.get(run.sessionId) !== state) log$14.debug(`session ${run.sessionId} is ${state}`);
1251
+ if (!states.has(run.sessionId)) log$16.info(`${run.provider} session ${run.sessionId} opened in ${run.cwd} with account ${run.settings.accountId}`);
1252
+ if (states.get(run.sessionId) !== state) log$16.debug(`session ${run.sessionId} is ${state}`);
1242
1253
  }
1243
- for (const sessionId of states.keys()) if (!next.has(sessionId)) log$14.info(`session ${sessionId} closed`);
1254
+ for (const sessionId of states.keys()) if (!next.has(sessionId)) log$16.info(`session ${sessionId} closed`);
1244
1255
  states = next;
1245
1256
  broadcast({
1246
1257
  type: "activity",
@@ -1250,16 +1261,18 @@ function broadcastActivity() {
1250
1261
  function scheduleAutoResolve(sessionId, request) {
1251
1262
  if (request.kind === "plan") return request;
1252
1263
  const config = readConfig();
1253
- const seconds = request.kind === "question" ? config.autoSkipQuestionTimeout : config.autoPermissionTimeout;
1264
+ const run = runs.get(sessionId);
1265
+ const immediate = config.autoPermissionMode === "bypass" && (run?.provider === "claude" && run.settings.mode === "bypassPermissions" || run?.provider === "codex" && run.settings.mode === "full-access");
1266
+ const seconds = request.kind === "question" ? config.autoSkipQuestionTimeout : immediate ? 0 : config.autoPermissionTimeout;
1254
1267
  if (seconds === null) return request;
1255
- const decision = request.kind === "question" ? "allow" : config.autoPermissionMode;
1268
+ const decision = request.kind === "question" || config.autoPermissionMode === "bypass" ? "allow" : config.autoPermissionMode;
1256
1269
  setTimeout(async () => {
1257
1270
  const run = runs.get(sessionId);
1258
1271
  if (run === void 0) return;
1259
1272
  try {
1260
1273
  await run.respond(request.id, decision, request.kind === "question" ? {} : null);
1261
1274
  } catch (error) {
1262
- log$14.warn(`could not auto-resolve ${request.id}:`, error);
1275
+ log$16.warn(`could not auto-resolve ${request.id}:`, error);
1263
1276
  }
1264
1277
  }, seconds * 1e3);
1265
1278
  return {
@@ -1400,7 +1413,7 @@ function reply(method, params, decision, answers) {
1400
1413
  throw new Error(`offsite cannot answer ${method}`);
1401
1414
  }
1402
1415
  // src/lib/codex/run.ts
1403
- const log$13 = logger.withTag("codex");
1416
+ const log$15 = logger.withTag("codex");
1404
1417
  const IDLE_LIMIT$2 = 18e5;
1405
1418
  function codexServer(settings) {
1406
1419
  return appServer(storedAccount("codex", settings.accountId).configDir ?? codexBaseDir);
@@ -1525,10 +1538,10 @@ var CodexRun = class CodexRun {
1525
1538
  if (runs.get(this.sessionId) === this) runs.delete(this.sessionId);
1526
1539
  this.server.threads.delete(this.sessionId);
1527
1540
  clearTimeout(this.idleTimer);
1528
- this.server.request("thread/unsubscribe", { threadId: this.sessionId }).catch((error) => log$13.warn(`could not unload Codex thread:`, error)).finally(() => this.end());
1541
+ this.server.request("thread/unsubscribe", { threadId: this.sessionId }).catch((error) => log$15.warn(`could not unload Codex thread:`, error)).finally(() => this.end());
1529
1542
  }
1530
1543
  exited() {
1531
- log$13.warn(`session ${this.sessionId} lost its Codex app server`);
1544
+ log$15.warn(`session ${this.sessionId} lost its Codex app server`);
1532
1545
  this.put(errorItem(randomUUID(), this.pending.size === 0 ? "Codex stopped" : "Codex stopped, its open requests expired"));
1533
1546
  if (runs.get(this.sessionId) === this) runs.delete(this.sessionId);
1534
1547
  this.end();
@@ -1732,7 +1745,7 @@ var CodexRun = class CodexRun {
1732
1745
  });
1733
1746
  }
1734
1747
  fail(error) {
1735
- log$13.warn(`session ${this.sessionId} failed:`, error);
1748
+ log$15.warn(`session ${this.sessionId} failed:`, error);
1736
1749
  this.put(errorItem(randomUUID(), error instanceof Error ? error.message : String(error)));
1737
1750
  }
1738
1751
  armIdleTimer() {
@@ -2599,7 +2612,7 @@ function sessionSettings(session, settings) {
2599
2612
  };
2600
2613
  }
2601
2614
  // src/lib/opencode/run.ts
2602
- const log$12 = logger.withTag("opencode");
2615
+ const log$14 = logger.withTag("opencode");
2603
2616
  const IDLE_LIMIT$1 = 18e5;
2604
2617
  const RECENT = 30;
2605
2618
  const replies = {
@@ -2727,7 +2740,7 @@ var OpenCodeRun = class OpenCodeRun {
2727
2740
  this.end();
2728
2741
  }
2729
2742
  exited() {
2730
- log$12.warn(`session ${this.sessionId} lost the connection to OpenCode`);
2743
+ log$14.warn(`session ${this.sessionId} lost the connection to OpenCode`);
2731
2744
  this.put([errorItem(randomUUID(), this.pending.size === 0 ? "Lost the connection to OpenCode" : "Lost the connection to OpenCode, its open requests expired")]);
2732
2745
  this.stop();
2733
2746
  }
@@ -2795,7 +2808,7 @@ var OpenCodeRun = class OpenCodeRun {
2795
2808
  }
2796
2809
  refresh(client) {
2797
2810
  clearTimeout(this.refreshTimer);
2798
- this.loading = this.loading.then(async () => this.load(client ?? await openCode())).catch((error) => log$12.warn(`could not load OpenCode messages:`, error));
2811
+ this.loading = this.loading.then(async () => this.load(client ?? await openCode())).catch((error) => log$14.warn(`could not load OpenCode messages:`, error));
2799
2812
  }
2800
2813
  async load(client) {
2801
2814
  const messages = (await client.message.list({
@@ -2912,7 +2925,7 @@ var OpenCodeRun = class OpenCodeRun {
2912
2925
  });
2913
2926
  }
2914
2927
  fail(error) {
2915
- log$12.warn(`session ${this.sessionId} failed:`, error);
2928
+ log$14.warn(`session ${this.sessionId} failed:`, error);
2916
2929
  this.put([errorItem(randomUUID(), error instanceof Error ? error.message : String(error))]);
2917
2930
  }
2918
2931
  armIdleTimer() {
@@ -2963,6 +2976,11 @@ const projects$1 = snakeCase.table("projects", {
2963
2976
  });
2964
2977
  const sessions$1 = snakeCase.table("sessions", {
2965
2978
  sessionId: text().primaryKey(),
2979
+ provider: text({ enum: [
2980
+ "claude",
2981
+ "codex",
2982
+ "opencode"
2983
+ ] }),
2966
2984
  interactedAt: integer(),
2967
2985
  status: text({ enum: [
2968
2986
  "active",
@@ -2998,6 +3016,16 @@ const pushSubscriptions = snakeCase.table("push_subscriptions", {
2998
3016
  machine: text().notNull(),
2999
3017
  server: text().notNull()
3000
3018
  });
3019
+ const lastUsedModels = snakeCase.table("last_used_models", {
3020
+ provider: text({ enum: [
3021
+ "claude",
3022
+ "codex",
3023
+ "opencode"
3024
+ ] }).primaryKey(),
3025
+ accountId: text(),
3026
+ model: text().notNull(),
3027
+ usedAt: integer().notNull()
3028
+ });
3001
3029
  // src/storage/auth.ts
3002
3030
  const CODE_LIFETIME = 3e5;
3003
3031
  const DEVICE_LIFETIME = 2592e6;
@@ -4047,11 +4075,145 @@ async function find(provider, paths) {
4047
4075
  });
4048
4076
  }
4049
4077
  }
4078
+ // src/services/threads.ts
4079
+ const log$13 = logger.withTag("threads");
4080
+ const allProviders$2 = [
4081
+ "claude",
4082
+ "codex",
4083
+ "opencode"
4084
+ ];
4085
+ const threads = {
4086
+ claude: /* @__PURE__ */ new Map(),
4087
+ codex: /* @__PURE__ */ new Map(),
4088
+ opencode: /* @__PURE__ */ new Map()
4089
+ };
4090
+ const changed = {
4091
+ claude: /* @__PURE__ */ new Set(),
4092
+ codex: /* @__PURE__ */ new Set(),
4093
+ opencode: /* @__PURE__ */ new Set()
4094
+ };
4095
+ const failures = /* @__PURE__ */ new Set();
4096
+ let built = Promise.resolve();
4097
+ let builtFor = "";
4098
+ function rebuildThreads() {
4099
+ const config = readConfig();
4100
+ const key = JSON.stringify([
4101
+ projectPaths$1(),
4102
+ config.enabledProviders,
4103
+ config.claudeBaseDir
4104
+ ]);
4105
+ if (key === builtFor) return;
4106
+ builtFor = key;
4107
+ built = relistAll();
4108
+ }
4109
+ function threadsReady() {
4110
+ return built;
4111
+ }
4112
+ function threadFailed(provider) {
4113
+ return failures.has(provider);
4114
+ }
4115
+ function knownThreads() {
4116
+ const known = new Set(projects().map((project) => project.id));
4117
+ return allProviders$2.filter((provider) => readConfig().enabledProviders[provider]).flatMap((provider) => [...threads[provider].values()]).filter((item) => known.has(item.projectId));
4118
+ }
4119
+ async function providerThreads(provider) {
4120
+ await built;
4121
+ if (failures.has(provider)) throw new Error(`could not list ${providerName(provider)} sessions`);
4122
+ return knownThreads().filter((item) => item.provider === provider);
4123
+ }
4124
+ function threadOf(provider, id) {
4125
+ return threads[provider].get(id);
4126
+ }
4127
+ function threadProvider(id) {
4128
+ return allProviders$2.find((provider) => threads[provider].has(id));
4129
+ }
4130
+ function markChanged(provider, id) {
4131
+ changed[provider].add(id);
4132
+ }
4133
+ function fileChanged(provider, filename) {
4134
+ if (filename === null) return;
4135
+ if (provider === "claude") {
4136
+ const parts = filename.split(path.sep);
4137
+ if (parts.length === 2 && parts[1].endsWith(".jsonl")) changed.claude.add(path.basename(parts[1], ".jsonl"));
4138
+ return;
4139
+ }
4140
+ const id = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/.exec(filename)?.[1];
4141
+ if (id !== void 0) changed.codex.add(id);
4142
+ }
4143
+ function forgetThread(provider, id) {
4144
+ threads[provider].delete(id);
4145
+ broadcast({ type: "threads" });
4146
+ }
4147
+ async function updateThreads(provider) {
4148
+ if (provider === "opencode") {
4149
+ await relist(provider);
4150
+ return;
4151
+ }
4152
+ const ids = [...changed[provider]];
4153
+ changed[provider].clear();
4154
+ if (ids.length === 0) return;
4155
+ await built;
4156
+ const paths = new Map(projectPaths$1());
4157
+ const results = await Promise.all(ids.map(async (id) => {
4158
+ try {
4159
+ return {
4160
+ id,
4161
+ item: await timeLimit(lookup$1(provider, id, paths), `${providerName(provider)} did not find session ${id} in time`)
4162
+ };
4163
+ } catch (error) {
4164
+ log$13.warn(`could not update ${providerName(provider)} session:`, error);
4165
+ return null;
4166
+ }
4167
+ }));
4168
+ let updated = false;
4169
+ for (const result of results) {
4170
+ if (result === null) continue;
4171
+ const before = threads[provider].get(result.id);
4172
+ if (result.item === null) {
4173
+ updated ||= before !== void 0;
4174
+ threads[provider].delete(result.id);
4175
+ continue;
4176
+ }
4177
+ updated ||= JSON.stringify(before) !== JSON.stringify(result.item);
4178
+ threads[provider].set(result.id, result.item);
4179
+ }
4180
+ if (updated) broadcast({ type: "threads" });
4181
+ }
4182
+ async function lookup$1(provider, id, paths) {
4183
+ const known = threads[provider].get(id);
4184
+ const knownPath = known === void 0 ? void 0 : [...paths].find((entry) => entry[1] === known.projectId);
4185
+ if (knownPath === void 0) return info(provider, id, paths);
4186
+ return await info(provider, id, new Map([knownPath])) ?? info(provider, id, paths);
4187
+ }
4188
+ async function relistAll() {
4189
+ await Promise.all(allProviders$2.map((provider) => relist(provider)));
4190
+ }
4191
+ async function relist(provider) {
4192
+ if (!readConfig().enabledProviders[provider]) {
4193
+ threads[provider] = /* @__PURE__ */ new Map();
4194
+ failures.delete(provider);
4195
+ broadcast({ type: "threads" });
4196
+ return;
4197
+ }
4198
+ try {
4199
+ const found = await timeLimit(find(provider, new Map(projectPaths$1())), `${providerName(provider)} did not list its sessions in time`) ?? [];
4200
+ threads[provider] = new Map(found.map((item) => [item.id, item]));
4201
+ failures.delete(provider);
4202
+ } catch (error) {
4203
+ log$13.warn(`could not list all ${providerName(provider)} sessions:`, error);
4204
+ failures.add(provider);
4205
+ builtFor = "";
4206
+ }
4207
+ broadcast({ type: "threads" });
4208
+ }
4209
+ function projectPaths$1() {
4210
+ return projects().map((project) => [project.path, project.id]);
4211
+ }
4050
4212
  // src/services/sessions.ts
4051
- const log$11 = logger.withTag("sessions");
4213
+ const log$12 = logger.withTag("sessions");
4052
4214
  const day = 864e5;
4053
4215
  const REFRESH_DELAY = 1e3;
4054
- const allProviders = [
4216
+ const allProviders$1 = [
4055
4217
  "claude",
4056
4218
  "codex",
4057
4219
  "opencode"
@@ -4084,61 +4246,35 @@ const providers = /* @__PURE__ */ new Map();
4084
4246
  let watchers = [];
4085
4247
  function sessions() {
4086
4248
  const known = new Set(projects().map((project) => project.id));
4087
- return withStatus(allProviders.flatMap((provider) => listed[provider]).filter((item) => known.has(item.projectId))).filter((session) => session.status !== "inactive");
4249
+ return withStatus(allProviders$1.flatMap((provider) => listed[provider]).filter((item) => known.has(item.projectId))).filter((session) => session.status !== "inactive");
4088
4250
  }
4089
- async function inactiveSessions(projectId) {
4090
- return (await everything(projects().filter((project) => projectId === null || project.id === projectId || projectId === "no-project" && project.scratch).map((project) => [project.path, project.id]))).filter((session) => session.status === "inactive").sort((a, b) => b.updatedAt - a.updatedAt);
4251
+ async function inactiveSessions(params) {
4252
+ await threadsReady();
4253
+ const scope = new Set(projects().filter((project) => params.projectId === null || project.id === params.projectId || params.projectId === "no-project" && project.scratch).map((project) => project.id));
4254
+ const found = withStatus(knownThreads().filter((item) => scope.has(item.projectId))).filter((session) => session.status === "inactive").sort((a, b) => b.updatedAt - a.updatedAt);
4255
+ return {
4256
+ sessions: found.slice(0, params.limit),
4257
+ total: found.length
4258
+ };
4091
4259
  }
4092
4260
  async function searchableSessions() {
4093
- const paths = projectPaths();
4094
- const lists = await Promise.all(enabledProviders().map(async (provider) => {
4095
- try {
4096
- return {
4097
- provider,
4098
- items: await timeLimit(find(provider, new Map(paths)), `${providerName(provider)} did not list its sessions in time`) ?? [],
4099
- failed: false
4100
- };
4101
- } catch (error) {
4102
- log$11.warn(`could not search ${providerName(provider)}:`, error);
4103
- return {
4104
- provider,
4105
- items: [],
4106
- failed: true
4107
- };
4108
- }
4109
- }));
4110
- const found = lists.flatMap((list) => list.items);
4111
- for (const item of found) providers.set(item.id, item.provider);
4261
+ await threadsReady();
4262
+ const found = knownThreads();
4112
4263
  const opens = new Map(db.select().from(sessionOpens).all().map((record) => [record.sessionId, record]));
4113
4264
  return {
4114
4265
  sessions: withStatus(found).map((session) => ({
4115
4266
  ...session,
4116
4267
  opens: opens.get(session.id) ?? null
4117
4268
  })),
4118
- failed: lists.filter((list) => list.failed).map((list) => list.provider)
4269
+ failed: enabledProviders().filter((provider) => threadFailed(provider))
4119
4270
  };
4120
4271
  }
4121
4272
  async function findSession(id) {
4122
- const paths = new Map(projectPaths());
4273
+ if (ownerOf(id) === void 0) await threadsReady();
4123
4274
  const owner = ownerOf(id);
4124
- if (owner !== void 0) {
4125
- const item = await info(owner, id, paths);
4126
- return item === null ? null : withStatus([item])[0];
4127
- }
4128
- for (const provider of allProviders) {
4129
- if (!readConfig().enabledProviders[provider]) continue;
4130
- const item = await info(provider, id, paths).catch(() => null);
4131
- if (item !== null) {
4132
- providers.set(id, provider);
4133
- return withStatus([item])[0];
4134
- }
4135
- }
4136
- return null;
4137
- }
4138
- async function everything(paths) {
4139
- const found = (await Promise.all(enabledProviders().map((provider) => find(provider, new Map(paths))))).flatMap((items) => items ?? []);
4140
- for (const item of found) providers.set(item.id, item.provider);
4141
- return withStatus(found);
4275
+ if (owner === void 0) return null;
4276
+ const item = await info(owner, id, new Map(projectPaths()));
4277
+ return item === null ? null : withStatus([item])[0];
4142
4278
  }
4143
4279
  function withStatus(items) {
4144
4280
  const records = new Map(db.select().from(sessions$1).all().map((record) => [record.sessionId, record]));
@@ -4153,16 +4289,16 @@ function withStatus(items) {
4153
4289
  });
4154
4290
  }
4155
4291
  function enabledProviders() {
4156
- return allProviders.filter((provider) => readConfig().enabledProviders[provider]);
4292
+ return allProviders$1.filter((provider) => readConfig().enabledProviders[provider]);
4157
4293
  }
4158
4294
  function projectPaths() {
4159
4295
  return projects().map((project) => [project.path, project.id]);
4160
4296
  }
4161
4297
  function ownerOf(sessionId) {
4162
- return runs.get(sessionId)?.provider ?? providers.get(sessionId);
4298
+ return runs.get(sessionId)?.provider ?? db.select({ provider: sessions$1.provider }).from(sessions$1).where(eq(sessions$1.sessionId, sessionId)).get()?.provider ?? providers.get(sessionId) ?? threadProvider(sessionId);
4163
4299
  }
4164
4300
  function providerStatuses() {
4165
- return allProviders.map((provider) => statuses[provider]);
4301
+ return allProviders$1.map((provider) => statuses[provider]);
4166
4302
  }
4167
4303
  function listedUpdatedAt(provider, sessionId) {
4168
4304
  return listed[provider].find((item) => item.id === sessionId)?.updatedAt ?? null;
@@ -4172,14 +4308,16 @@ function providerOf(sessionId) {
4172
4308
  if (provider === void 0) throw new Error("offsite does not know this session yet, reload the page");
4173
4309
  return provider;
4174
4310
  }
4175
- function recordInteraction(id) {
4311
+ function recordInteraction(id, provider) {
4176
4312
  db.insert(sessions$1).values({
4177
4313
  sessionId: id,
4314
+ provider,
4178
4315
  interactedAt: Date.now(),
4179
4316
  status: "active"
4180
4317
  }).onConflictDoUpdate({
4181
4318
  target: sessions$1.sessionId,
4182
4319
  set: {
4320
+ provider,
4183
4321
  interactedAt: Date.now(),
4184
4322
  status: sql`CASE ${sessions$1.status} WHEN 'pinned' THEN 'pinned' WHEN 'snoozed' THEN 'snoozed' WHEN 'minimized' THEN 'minimized' ELSE 'active' END`
4185
4323
  }
@@ -4202,13 +4340,15 @@ function publishSessions() {
4202
4340
  });
4203
4341
  }
4204
4342
  function refreshAll() {
4205
- for (const provider of allProviders) refresh(provider);
4343
+ rebuildThreads();
4344
+ for (const provider of allProviders$1) refresh(provider);
4206
4345
  }
4207
4346
  function refreshSessions(provider) {
4208
4347
  if (timers.has(provider)) return;
4209
4348
  timers.set(provider, setTimeout(() => {
4210
4349
  timers.delete(provider);
4211
4350
  refresh(provider);
4351
+ updateThreads(provider);
4212
4352
  }, REFRESH_DELAY));
4213
4353
  }
4214
4354
  async function refresh(provider) {
@@ -4257,7 +4397,7 @@ async function list(provider) {
4257
4397
  }
4258
4398
  } catch (error) {
4259
4399
  const message = error instanceof Error ? error.message : String(error);
4260
- log$11.warn(`could not list ${providerName(provider)} sessions: ${message}`);
4400
+ log$12.warn(`could not list ${providerName(provider)} sessions: ${message}`);
4261
4401
  statuses[provider] = {
4262
4402
  provider,
4263
4403
  state: "error",
@@ -4273,15 +4413,10 @@ async function list(provider) {
4273
4413
  }
4274
4414
  async function tracked(provider, paths) {
4275
4415
  if (provider === "codex" && !codexInstalled() || provider === "opencode" && !openCodeInstalled()) return null;
4276
- return (await Promise.all(db.select().from(sessions$1).all().map((record) => {
4277
- const owner = ownerOf(record.sessionId);
4278
- if (owner === provider) return lookup(provider, record.sessionId, paths);
4279
- if (owner === void 0) return lookup(provider, record.sessionId, paths).catch(() => null);
4280
- return null;
4281
- }))).filter((item) => item !== null);
4416
+ return (await Promise.all(db.select().from(sessions$1).all().filter((record) => record.provider === provider).map((record) => lookup(provider, record.sessionId, paths)))).filter((item) => item !== null);
4282
4417
  }
4283
4418
  async function lookup(provider, sessionId, paths) {
4284
- const known = listed[provider].find((item) => item.id === sessionId);
4419
+ const known = threadOf(provider, sessionId) ?? listed[provider].find((item) => item.id === sessionId);
4285
4420
  const knownPath = known === void 0 ? void 0 : [...paths].find((entry) => entry[1] === known.projectId);
4286
4421
  if (knownPath === void 0) return info(provider, sessionId, paths);
4287
4422
  return await info(provider, sessionId, new Map([knownPath])) ?? info(provider, sessionId, paths);
@@ -4291,13 +4426,16 @@ function watchSessions() {
4291
4426
  watchers = [["claude", path.join(claudeBaseDir(), "projects")], ["codex", path.join(codexBaseDir, "sessions")]].flatMap(([provider, dir]) => {
4292
4427
  if (!readConfig().enabledProviders[provider]) return [];
4293
4428
  try {
4294
- const watcher = watch(dir, { recursive: true }, () => refreshSessions(provider));
4295
- watcher.on("error", (error) => log$11.warn(`stopped watching ${dir}:`, error));
4296
- log$11.debug(`watching ${dir}`);
4429
+ const watcher = watch(dir, { recursive: true }, (_, filename) => {
4430
+ fileChanged(provider, filename);
4431
+ refreshSessions(provider);
4432
+ });
4433
+ watcher.on("error", (error) => log$12.warn(`stopped watching ${dir}:`, error));
4434
+ log$12.debug(`watching ${dir}`);
4297
4435
  return [watcher];
4298
4436
  } catch (error) {
4299
4437
  if (error.code !== "ENOENT") throw error;
4300
- log$11.debug(`not watching ${dir}, it does not exist`);
4438
+ log$12.debug(`not watching ${dir}, it does not exist`);
4301
4439
  return [];
4302
4440
  }
4303
4441
  });
@@ -4331,16 +4469,24 @@ async function updateSession(id, patch) {
4331
4469
  status: patch.status,
4332
4470
  interactedAt: Date.now()
4333
4471
  } : { status: patch.status };
4472
+ const provider = providerOf(id);
4334
4473
  db.insert(sessions$1).values({
4335
4474
  sessionId: id,
4475
+ provider,
4336
4476
  ...set
4337
4477
  }).onConflictDoUpdate({
4338
4478
  target: sessions$1.sessionId,
4339
- set
4479
+ set: {
4480
+ provider,
4481
+ ...set
4482
+ }
4340
4483
  }).run();
4341
- await refresh(providerOf(id));
4484
+ await refresh(provider);
4485
+ }
4486
+ if (patch.title !== void 0) {
4487
+ markChanged(providerOf(id), id);
4488
+ refreshSessions(providerOf(id));
4342
4489
  }
4343
- if (patch.title !== void 0) refreshSessions(providerOf(id));
4344
4490
  }
4345
4491
  async function removeSession(id, projectId) {
4346
4492
  const provider = providerOf(id);
@@ -4361,19 +4507,19 @@ async function removeSession(id, projectId) {
4361
4507
  db.delete(sessions$1).where(eq(sessions$1.sessionId, id)).run();
4362
4508
  db.delete(sessionOpens).where(eq(sessionOpens.sessionId, id)).run();
4363
4509
  listed[provider] = listed[provider].filter((item) => item.id !== id);
4510
+ forgetThread(provider, id);
4364
4511
  const project = projects().find((item) => item.id === projectId);
4365
4512
  if (project === void 0 || !project.scratch) return;
4366
- try {
4367
- if ((await everything([[project.path, project.id]])).filter((item) => item.id !== id).length === 0) removeProject(project.id);
4368
- } catch (error) {
4369
- log$11.warn(`could not check other chats in ${project.path}:`, error);
4370
- }
4513
+ await threadsReady();
4514
+ if (enabledProviders().some((owner) => threadFailed(owner))) return;
4515
+ if (!knownThreads().some((item) => item.projectId === project.id)) removeProject(project.id);
4371
4516
  }
4372
4517
  async function fork(sessionId, projectId, messageId) {
4373
4518
  const provider = providerOf(sessionId);
4374
4519
  const result = await forkWith(provider, sessionId, projectPath(projectId), messageId);
4375
4520
  providers.set(result.sessionId, provider);
4376
- recordInteraction(result.sessionId);
4521
+ recordInteraction(result.sessionId, provider);
4522
+ markChanged(provider, result.sessionId);
4377
4523
  refreshSessions(provider);
4378
4524
  return result;
4379
4525
  }
@@ -4387,7 +4533,7 @@ async function forkWith(provider, sessionId, cwd, messageId) {
4387
4533
  })).sessionId };
4388
4534
  }
4389
4535
  // src/lib/opencode/client.ts
4390
- const log$10 = logger.withTag("opencode");
4536
+ const log$11 = logger.withTag("opencode");
4391
4537
  const listChanges = /* @__PURE__ */ new Set([
4392
4538
  "session.created",
4393
4539
  "session.renamed",
@@ -4433,7 +4579,7 @@ async function start() {
4433
4579
  }
4434
4580
  async function connect() {
4435
4581
  const endpoint = await ensure();
4436
- log$10.info(`connected to OpenCode at ${endpoint.url}`);
4582
+ log$11.info(`connected to OpenCode at ${endpoint.url}`);
4437
4583
  return OpenCode.make({
4438
4584
  baseUrl: endpoint.url,
4439
4585
  headers: headers(endpoint)
@@ -4448,11 +4594,11 @@ async function listen(client) {
4448
4594
  signal: controller.signal,
4449
4595
  onActivity: () => silence.refresh()
4450
4596
  })) route(event);
4451
- log$10.warn("the OpenCode event stream ended");
4597
+ log$11.warn("the OpenCode event stream ended");
4452
4598
  } catch (error) {
4453
4599
  if (listener !== controller) return;
4454
- if (controller.signal.aborted) log$10.warn(`the OpenCode event stream was silent for ${SILENCE_LIMIT$2 / 1e3} seconds`);
4455
- else log$10.warn("lost the OpenCode event stream:", error);
4600
+ if (controller.signal.aborted) log$11.warn(`the OpenCode event stream was silent for ${SILENCE_LIMIT$2 / 1e3} seconds`);
4601
+ else log$11.warn("lost the OpenCode event stream:", error);
4456
4602
  } finally {
4457
4603
  clearTimeout(silence);
4458
4604
  }
@@ -4468,15 +4614,15 @@ async function reconnect() {
4468
4614
  try {
4469
4615
  client = await connect();
4470
4616
  } catch (error) {
4471
- log$10.debug(`reconnect attempt ${attempt + 1} to OpenCode failed:`, error);
4617
+ log$11.debug(`reconnect attempt ${attempt + 1} to OpenCode failed:`, error);
4472
4618
  failure = error;
4473
4619
  continue;
4474
4620
  }
4475
4621
  listen(client);
4476
- await catchUp(client).catch((error) => log$10.warn(`could not catch up with OpenCode:`, error));
4622
+ await catchUp(client).catch((error) => log$11.warn(`could not catch up with OpenCode:`, error));
4477
4623
  return client;
4478
4624
  }
4479
- log$10.warn(`could not reconnect to OpenCode:`, failure);
4625
+ log$11.warn(`could not reconnect to OpenCode:`, failure);
4480
4626
  connection = void 0;
4481
4627
  for (const run of openCodeRuns()) run.exited();
4482
4628
  refreshSessions("opencode");
@@ -4507,7 +4653,7 @@ function openCodeRuns() {
4507
4653
  return [...runs.values()].flatMap((run) => run.provider === "opencode" ? [run] : []);
4508
4654
  }
4509
4655
  // src/services/accounts.ts
4510
- const log$9 = logger.withTag("accounts");
4656
+ const log$10 = logger.withTag("accounts");
4511
4657
  const providerNames = {
4512
4658
  claude: "Claude",
4513
4659
  codex: "Codex",
@@ -4575,6 +4721,7 @@ function disabledAccount(account) {
4575
4721
  email: null,
4576
4722
  displayName: null,
4577
4723
  organization: null,
4724
+ identity: null,
4578
4725
  plan: null,
4579
4726
  error: null,
4580
4727
  linked: true,
@@ -4588,7 +4735,7 @@ async function publishAccounts() {
4588
4735
  ...await accounts()
4589
4736
  });
4590
4737
  } catch (error) {
4591
- log$9.warn(`could not list accounts:`, error);
4738
+ log$10.warn(`could not list accounts:`, error);
4592
4739
  }
4593
4740
  }
4594
4741
  async function capabilities(provider, accountId, cwd) {
@@ -4695,7 +4842,7 @@ function validAccount(name, configDir) {
4695
4842
  throw new Error("Use an absolute config directory or one starting with ~/");
4696
4843
  }
4697
4844
  // src/services/push.ts
4698
- const log$8 = logger.withTag("push");
4845
+ const log$9 = logger.withTag("push");
4699
4846
  const TITLE_LIMIT = 100;
4700
4847
  const BODY_LIMIT = 500;
4701
4848
  const TIME_TO_LIVE = 900;
@@ -4743,12 +4890,12 @@ function subscribe(registration, client) {
4743
4890
  set: values
4744
4891
  }).run();
4745
4892
  if (notifier !== null && notifier.client === client) notifier.endpoint = registration.subscription.endpoint;
4746
- log$8.info("a browser subscribed to push notifications");
4893
+ log$9.info("a browser subscribed to push notifications");
4747
4894
  }
4748
4895
  function unsubscribe(endpoint) {
4749
4896
  db.delete(pushSubscriptions).where(eq(pushSubscriptions.endpoint, endpoint)).run();
4750
4897
  if (notifier !== null && notifier.endpoint === endpoint) notifier.endpoint = null;
4751
- log$8.info("a browser unsubscribed from push notifications");
4898
+ log$9.info("a browser unsubscribed from push notifications");
4752
4899
  }
4753
4900
  async function sendPush(message) {
4754
4901
  const subscriptions = targets();
@@ -4790,14 +4937,14 @@ async function sendPush(message) {
4790
4937
  404,
4791
4938
  410
4792
4939
  ].includes(status)) {
4793
- log$8.info(`push subscription ${subscription.endpoint} is gone or was made for another key, removing it`);
4940
+ log$9.info(`push subscription ${subscription.endpoint} is gone or was made for another key, removing it`);
4794
4941
  gone.push(subscription.endpoint);
4795
4942
  return;
4796
4943
  }
4797
- log$8.warn(`could not send a push to ${subscription.endpoint}:`, error);
4944
+ log$9.warn(`could not send a push to ${subscription.endpoint}:`, error);
4798
4945
  }
4799
4946
  }));
4800
- log$8.debug(`sent a push to ${delivered} of ${subscriptions.length} browsers`);
4947
+ log$9.debug(`sent a push to ${delivered} of ${subscriptions.length} browsers`);
4801
4948
  if (gone.length === 0) return delivered > 0;
4802
4949
  db.delete(pushSubscriptions).where(and(eq(pushSubscriptions.server, server$1), inArray(pushSubscriptions.endpoint, gone))).run();
4803
4950
  return delivered > 0;
@@ -4807,7 +4954,7 @@ function targets() {
4807
4954
  return db.select().from(pushSubscriptions).where(and(eq(pushSubscriptions.server, server$1), eq(pushSubscriptions.endpoint, notifier.endpoint))).all();
4808
4955
  }
4809
4956
  // src/lib/claude/claude.ts
4810
- const log$7 = logger.withTag("claude");
4957
+ const log$8 = logger.withTag("claude");
4811
4958
  const efforts = [
4812
4959
  "low",
4813
4960
  "medium",
@@ -4946,7 +5093,7 @@ async function claudeAccount(account) {
4946
5093
  return {
4947
5094
  ...account,
4948
5095
  email: info.email ?? null,
4949
- displayName: claudeDisplayName(configDir),
5096
+ ...claudeLogin(configDir),
4950
5097
  organization: info.organization ?? null,
4951
5098
  plan: info.subscriptionType ?? null,
4952
5099
  error: null,
@@ -4959,6 +5106,7 @@ async function claudeAccount(account) {
4959
5106
  email: null,
4960
5107
  displayName: null,
4961
5108
  organization: null,
5109
+ identity: null,
4962
5110
  plan: null,
4963
5111
  error: error instanceof Error ? error.message : String(error),
4964
5112
  linked,
@@ -5037,18 +5185,31 @@ async function claudeLimits(account) {
5037
5185
  probe.close();
5038
5186
  }
5039
5187
  }
5040
- function claudeDisplayName(configDir) {
5188
+ function claudeLogin(configDir) {
5041
5189
  const file = path.resolve(configDir) === path.join(os.homedir(), ".claude") ? path.join(os.homedir(), ".claude.json") : path.join(configDir, ".claude.json");
5042
5190
  let raw;
5043
5191
  try {
5044
5192
  raw = JSON.parse(readFileSync(file, "utf8"));
5045
5193
  } catch (error) {
5046
- if (error.code !== "ENOENT") log$7.warn(`could not read the Claude account name:`, error);
5047
- return null;
5194
+ if (error.code !== "ENOENT") log$8.warn(`could not read the Claude account:`, error);
5195
+ return {
5196
+ displayName: null,
5197
+ identity: null
5198
+ };
5048
5199
  }
5049
- if (!isRecord(raw) || !isRecord(raw.oauthAccount) || typeof raw.oauthAccount.displayName !== "string") return null;
5050
- const name = raw.oauthAccount.displayName.trim();
5051
- return name === "" ? null : name;
5200
+ if (!isRecord(raw) || !isRecord(raw.oauthAccount)) return {
5201
+ displayName: null,
5202
+ identity: null
5203
+ };
5204
+ const login = raw.oauthAccount;
5205
+ const name = typeof login.displayName === "string" ? login.displayName.trim() : "";
5206
+ return {
5207
+ displayName: name === "" ? null : name,
5208
+ identity: typeof login.accountUuid === "string" && typeof login.organizationUuid === "string" ? {
5209
+ accountId: login.accountUuid,
5210
+ organizationId: login.organizationUuid
5211
+ } : null
5212
+ };
5052
5213
  }
5053
5214
  function isLinked(configDir) {
5054
5215
  const base = claudeBaseDir();
@@ -6258,7 +6419,7 @@ async function submitFirst(client, run, request, projectId) {
6258
6419
  from: request.projectId
6259
6420
  });
6260
6421
  await run.submit(request.prompt, request.delivery);
6261
- recordInteraction(sessionId);
6422
+ recordInteraction(sessionId, run.provider);
6262
6423
  }
6263
6424
  async function sendExisting(client, sessionId, cwd, request) {
6264
6425
  const provider = providerOf(sessionId);
@@ -6272,7 +6433,7 @@ async function sendExisting(client, sessionId, cwd, request) {
6272
6433
  if (run === existing) await run.configure(request.settings);
6273
6434
  else await open$1(client, sessionId, request.projectId);
6274
6435
  await run.submit(request.prompt, request.delivery);
6275
- recordInteraction(sessionId);
6436
+ recordInteraction(sessionId, provider);
6276
6437
  if (emptied.has(sessionId) && request.prompt.text.trim() !== "") {
6277
6438
  emptied.delete(sessionId);
6278
6439
  await updateSession(sessionId, { title: request.prompt.text.trim() });
@@ -6445,7 +6606,7 @@ function fenced(language, text) {
6445
6606
  return `${fence}${language}\n${text}\n${fence}`;
6446
6607
  }
6447
6608
  // src/services/message-search.ts
6448
- const log$6 = logger.withTag("search");
6609
+ const log$7 = logger.withTag("search");
6449
6610
  const READ_LIMIT = 30;
6450
6611
  const CODEX_READ_LIMIT = 10;
6451
6612
  const RG_TIME_LIMIT = 5e3;
@@ -6456,7 +6617,6 @@ async function searchMessages(query) {
6456
6617
  matches: [],
6457
6618
  failed: []
6458
6619
  };
6459
- const paths = new Map(projects().map((project) => [project.path, project.id]));
6460
6620
  const folders = new Map(projects().map((project) => [project.id, project.path]));
6461
6621
  const results = await Promise.all([
6462
6622
  "claude",
@@ -6466,11 +6626,11 @@ async function searchMessages(query) {
6466
6626
  try {
6467
6627
  return {
6468
6628
  provider,
6469
- matches: await timeLimit(search(provider, query, paths, folders), `${providerName(provider)} did not search in time`),
6629
+ matches: await timeLimit(search(provider, query, folders), `${providerName(provider)} did not search in time`),
6470
6630
  failed: false
6471
6631
  };
6472
6632
  } catch (error) {
6473
- log$6.warn(`could not search ${providerName(provider)} messages:`, error);
6633
+ log$7.warn(`could not search ${providerName(provider)} messages:`, error);
6474
6634
  return {
6475
6635
  provider,
6476
6636
  matches: [],
@@ -6483,10 +6643,8 @@ async function searchMessages(query) {
6483
6643
  failed: results.filter((result) => result.failed).map((result) => result.provider)
6484
6644
  };
6485
6645
  }
6486
- async function search(provider, query, paths, folders) {
6487
- const found = await find(provider, paths);
6488
- if (found === null) return [];
6489
- const sessions = newestFirst(found);
6646
+ async function search(provider, query, folders) {
6647
+ const sessions = newestFirst(await providerThreads(provider));
6490
6648
  switch (provider) {
6491
6649
  case "claude": return searchClaude(query, sessions, folders);
6492
6650
  case "codex": return searchCodex(query, sessions);
@@ -6520,7 +6678,7 @@ async function claudeCandidates(query) {
6520
6678
  const lists = await Promise.all(words.map((word) => sessionsWith(word)));
6521
6679
  return new Set(lists[0].filter((id) => lists.every((list) => list.includes(id))));
6522
6680
  } catch (error) {
6523
- log$6.warn(`could not narrow Claude sessions with rg:`, error);
6681
+ log$7.warn(`could not narrow Claude sessions with rg:`, error);
6524
6682
  return null;
6525
6683
  }
6526
6684
  }
@@ -7082,7 +7240,7 @@ function shellIntegrationIssue(command, platform, defaultShell) {
7082
7240
  return "Available for bash, zsh and fish";
7083
7241
  }
7084
7242
  // src/services/terminal/shell-integration.ts
7085
- const log$5 = logger.withTag("terminal");
7243
+ const log$6 = logger.withTag("terminal");
7086
7244
  const scripts = path.join(serverDir, "src/services/terminal/shell-integration");
7087
7245
  const folder = path.join(home, "shell-integration");
7088
7246
  function prepare(env, command, shell) {
@@ -7125,7 +7283,7 @@ function zshGlobalsSupported() {
7125
7283
  contents = readFileSync(global, "utf8");
7126
7284
  } catch (error) {
7127
7285
  if (error.code === "ENOENT") continue;
7128
- log$5.warn(`could not read ${global}, zsh starts without shell integration:`, error);
7286
+ log$6.warn(`could not read ${global}, zsh starts without shell integration:`, error);
7129
7287
  return false;
7130
7288
  }
7131
7289
  for (const rawLine of contents.split("\n")) {
@@ -7170,12 +7328,12 @@ function writeScript(target, source) {
7170
7328
  renameSync(`${target}.tmp`, target);
7171
7329
  return true;
7172
7330
  } catch (error) {
7173
- log$5.warn(`could not write ${target}, the shell starts without shell integration:`, error);
7331
+ log$6.warn(`could not write ${target}, the shell starts without shell integration:`, error);
7174
7332
  return false;
7175
7333
  }
7176
7334
  }
7177
7335
  // src/services/terminal/process.ts
7178
- const log$4 = logger.withTag("terminal");
7336
+ const log$5 = logger.withTag("terminal");
7179
7337
  const run = promisify(execFile);
7180
7338
  const iterm2Version = "3.6.8";
7181
7339
  const ghosttyVersion = "1.3.1";
@@ -7271,7 +7429,7 @@ async function busyProgram(pid) {
7271
7429
  try {
7272
7430
  return await programName(group);
7273
7431
  } catch (error) {
7274
- log$4.warn(`could not read the name of process ${group}:`, error);
7432
+ log$5.warn(`could not read the name of process ${group}:`, error);
7275
7433
  return `process ${group}`;
7276
7434
  }
7277
7435
  }
@@ -7513,7 +7671,7 @@ var Alerts = class {
7513
7671
  }
7514
7672
  };
7515
7673
  // src/services/terminal/terminal.ts
7516
- const log$3 = logger.withTag("terminal");
7674
+ const log$4 = logger.withTag("terminal");
7517
7675
  const HISTORY_LIMIT = 2097152;
7518
7676
  const OUTPUT_LIMIT = 1048576;
7519
7677
  const STALL_LIMIT = 1e4;
@@ -7548,7 +7706,7 @@ async function terminalRequest(client, message) {
7548
7706
  function terminalInput(client, frame) {
7549
7707
  const connection = connections.get(client);
7550
7708
  if (connection === void 0) {
7551
- log$3.warn("terminal input from a browser that is not attached");
7709
+ log$4.warn("terminal input from a browser that is not attached");
7552
7710
  return;
7553
7711
  }
7554
7712
  input(connection, frame);
@@ -7565,7 +7723,7 @@ async function busyTerminal(id) {
7565
7723
  try {
7566
7724
  return { program: await busyProgram(terminal.pty.pid) };
7567
7725
  } catch (error) {
7568
- log$3.warn(`could not tell if terminal ${id} is busy:`, error);
7726
+ log$4.warn(`could not tell if terminal ${id} is busy:`, error);
7569
7727
  return { error: String(error) };
7570
7728
  }
7571
7729
  }
@@ -7634,12 +7792,12 @@ function input(connection, bytes) {
7634
7792
  try {
7635
7793
  frame = decodeFrame(bytes);
7636
7794
  } catch (error) {
7637
- log$3.warn(`ignoring terminal input:`, error);
7795
+ log$4.warn(`ignoring terminal input:`, error);
7638
7796
  return;
7639
7797
  }
7640
7798
  const terminal = terminals.get(frame.id);
7641
7799
  if (terminal === void 0) {
7642
- log$3.warn(`input for terminal ${frame.id}, which does not exist`);
7800
+ log$4.warn(`input for terminal ${frame.id}, which does not exist`);
7643
7801
  return;
7644
7802
  }
7645
7803
  if (frame.kind === 1) {
@@ -7651,7 +7809,7 @@ function input(connection, bytes) {
7651
7809
  terminal.pty.resize(size.cols, size.rows);
7652
7810
  }
7653
7811
  } else {
7654
- log$3.warn(`ignoring terminal input of unknown kind ${frame.kind}`);
7812
+ log$4.warn(`ignoring terminal input of unknown kind ${frame.kind}`);
7655
7813
  return;
7656
7814
  }
7657
7815
  terminal.pty.write(Buffer.from(frame.data));
@@ -7659,7 +7817,7 @@ function input(connection, bytes) {
7659
7817
  async function createTab(projectId) {
7660
7818
  const cwd = projectId === null ? workingDirectory() : projectPath(projectId);
7661
7819
  if (!statSync(cwd, { throwIfNoEntry: false })?.isDirectory()) {
7662
- log$3.error(`${cwd} is not a folder, terminal not created`);
7820
+ log$4.error(`${cwd} is not a folder, terminal not created`);
7663
7821
  return;
7664
7822
  }
7665
7823
  const activeId = active.get(projectId ?? "global");
@@ -7668,7 +7826,7 @@ async function createTab(projectId) {
7668
7826
  if (current !== void 0) try {
7669
7827
  directory = await currentDirectory(current.pty.pid, current.scanner.directory);
7670
7828
  } catch (error) {
7671
- log$3.warn(`starting in the project folder, could not read the folder of the active terminal:`, error);
7829
+ log$4.warn(`starting in the project folder, could not read the folder of the active terminal:`, error);
7672
7830
  }
7673
7831
  nextTab += 1;
7674
7832
  const id = nextTab;
@@ -7676,7 +7834,7 @@ async function createTab(projectId) {
7676
7834
  try {
7677
7835
  pty = startShell(directory);
7678
7836
  } catch (error) {
7679
- log$3.error(`could not start terminal ${id}:`, error);
7837
+ log$4.error(`could not start terminal ${id}:`, error);
7680
7838
  return;
7681
7839
  }
7682
7840
  const terminal = {
@@ -7687,7 +7845,7 @@ async function createTab(projectId) {
7687
7845
  alerts: new Alerts(),
7688
7846
  scanner: new Scanner()
7689
7847
  };
7690
- log$3.info(`started terminal ${id} with pid ${pty.pid} in ${directory}`);
7848
+ log$4.info(`started terminal ${id} with pid ${pty.pid} in ${directory}`);
7691
7849
  terminals.set(id, terminal);
7692
7850
  tabs.push({
7693
7851
  id,
@@ -7701,7 +7859,7 @@ async function createTab(projectId) {
7701
7859
  held.add(pty);
7702
7860
  }
7703
7861
  pty.onExit((event) => {
7704
- log$3.info(`terminal ${id} exited with code ${event.exitCode}`);
7862
+ log$4.info(`terminal ${id} exited with code ${event.exitCode}`);
7705
7863
  terminals.delete(id);
7706
7864
  paused.delete(pty);
7707
7865
  held.delete(pty);
@@ -7711,7 +7869,7 @@ async function createTab(projectId) {
7711
7869
  function closeTab(id) {
7712
7870
  const tab = findTab(id);
7713
7871
  if (tab === void 0) return false;
7714
- log$3.debug(`closing terminal ${id}`);
7872
+ log$4.debug(`closing terminal ${id}`);
7715
7873
  release(tab);
7716
7874
  tabs.splice(tabs.indexOf(tab), 1);
7717
7875
  const terminal = terminals.get(id);
@@ -7753,7 +7911,7 @@ function drain() {
7753
7911
  last.queued = queued;
7754
7912
  progress.set(connection, last);
7755
7913
  if (now - last.at > STALL_LIMIT) {
7756
- log$3.warn("a browser is not reading fast enough, disconnecting it");
7914
+ log$4.warn("a browser is not reading fast enough, disconnecting it");
7757
7915
  connection.client.socket.terminate();
7758
7916
  continue;
7759
7917
  }
@@ -7843,6 +8001,36 @@ function send(connection, event) {
7843
8001
  event
7844
8002
  });
7845
8003
  }
8004
+ // src/storage/last-used.ts
8005
+ const lastUsedSchema = z.discriminatedUnion("provider", [z.strictObject({
8006
+ provider: z.enum(["claude", "codex"]),
8007
+ accountId: z.string(),
8008
+ model: z.string()
8009
+ }), z.strictObject({
8010
+ provider: z.literal("opencode"),
8011
+ accountId: z.null(),
8012
+ model: z.string()
8013
+ })]);
8014
+ function readLastUsed() {
8015
+ return db.select().from(lastUsedModels).all();
8016
+ }
8017
+ function saveLastUsed(value) {
8018
+ const result = lastUsedSchema.safeParse(value);
8019
+ if (!result.success) throw new Error(z.prettifyError(result.error));
8020
+ const row = {
8021
+ ...result.data,
8022
+ usedAt: Date.now()
8023
+ };
8024
+ db.insert(lastUsedModels).values(row).onConflictDoUpdate({
8025
+ target: lastUsedModels.provider,
8026
+ set: row
8027
+ }).run();
8028
+ broadcast({
8029
+ type: "lastUsed",
8030
+ lastUsed: readLastUsed()
8031
+ });
8032
+ return null;
8033
+ }
7846
8034
  // ../core/src/app-icons.ts
7847
8035
  const appIcons = [
7848
8036
  {
@@ -7913,77 +8101,14 @@ const appIcons = [
7913
8101
  }
7914
8102
  ];
7915
8103
  // src/storage/state.ts
7916
- const stateSchema = z.strictObject({
7917
- composer: z.strictObject({
7918
- provider: z.enum([
7919
- "claude",
7920
- "codex",
7921
- "opencode"
7922
- ]).default("claude"),
7923
- claude: z.strictObject({
7924
- accountId: z.string().nullable().default(null),
7925
- model: z.string().nullable().default(null),
7926
- effort: z.enum([
7927
- "low",
7928
- "medium",
7929
- "high",
7930
- "xhigh",
7931
- "max"
7932
- ]).nullable().default(null),
7933
- mode: z.enum([
7934
- "default",
7935
- "acceptEdits",
7936
- "bypassPermissions",
7937
- "plan",
7938
- "dontAsk",
7939
- "auto"
7940
- ]).nullable().default("bypassPermissions"),
7941
- plan: z.boolean().default(false)
7942
- }).prefault({}),
7943
- codex: z.strictObject({
7944
- accountId: z.string().nullable().default(null),
7945
- model: z.string().nullable().default(null),
7946
- effort: z.string().nullable().default(null),
7947
- mode: z.enum([
7948
- "read-only",
7949
- "auto",
7950
- "full-access"
7951
- ]).nullable().default("auto"),
7952
- plan: z.boolean().default(false)
7953
- }).prefault({}),
7954
- opencode: z.strictObject({
7955
- accountId: z.null().default(null),
7956
- model: z.string().nullable().default(null),
7957
- effort: z.string().nullable().default(null),
7958
- mode: z.string().nullable().default(null),
7959
- plan: z.boolean().default(false)
7960
- }).prefault({})
7961
- }).prefault({}),
7962
- appIcon: z.custom((value) => appIcons.some((icon) => icon.id === value)).default("original")
7963
- });
8104
+ const stateSchema = z.strictObject({ appIcon: z.custom((value) => appIcons.some((icon) => icon.id === value)).default("original") });
7964
8105
  const defaults = stateSchema.parse({});
7965
8106
  function readState() {
7966
8107
  return readSettings("state.json", stateSchema, defaults);
7967
8108
  }
7968
- function readComposer() {
7969
- return readState().composer;
7970
- }
7971
8109
  function readAppIcon() {
7972
8110
  return readState().appIcon;
7973
8111
  }
7974
- function saveComposer(value) {
7975
- const result = stateSchema.safeParse({
7976
- ...readState(),
7977
- composer: value
7978
- });
7979
- if (!result.success) throw new Error(z.prettifyError(result.error));
7980
- writeJson("state.json", result.data);
7981
- broadcast({
7982
- type: "composer",
7983
- composer: result.data.composer
7984
- });
7985
- return result.data.composer;
7986
- }
7987
8112
  function saveAppIcon(value) {
7988
8113
  const result = stateSchema.safeParse({
7989
8114
  ...readState(),
@@ -7998,14 +8123,14 @@ function saveAppIcon(value) {
7998
8123
  return result.data.appIcon;
7999
8124
  }
8000
8125
  // src/api/socket.ts
8001
- const log$2 = logger.withTag("socket");
8126
+ const log$3 = logger.withTag("socket");
8002
8127
  const handlers = {
8003
8128
  ping: () => null,
8004
8129
  sync: () => {
8005
8130
  refreshAll();
8006
8131
  return {
8007
8132
  ...configInfo(),
8008
- composer: readComposer(),
8133
+ lastUsed: readLastUsed(),
8009
8134
  hostname: os.hostname(),
8010
8135
  projects: projects(),
8011
8136
  sessions: sessions(),
@@ -8016,8 +8141,7 @@ const handlers = {
8016
8141
  };
8017
8142
  },
8018
8143
  "config.get": () => configInfo(),
8019
- "composer.get": () => readComposer(),
8020
- "composer.save": (params) => saveComposer(params),
8144
+ "lastUsed.save": (params) => saveLastUsed(params),
8021
8145
  "appIcon.save": (params) => saveAppIcon(params),
8022
8146
  "config.save": async (params) => {
8023
8147
  const before = readConfig();
@@ -8073,7 +8197,7 @@ const handlers = {
8073
8197
  if (params.opened && session !== null) recordOpen(params.sessionId, params.query);
8074
8198
  return session;
8075
8199
  },
8076
- "sessions.inactive": (params) => inactiveSessions(params.projectId),
8200
+ "sessions.inactive": (params) => inactiveSessions(params),
8077
8201
  "sessions.searchable": () => searchableSessions(),
8078
8202
  "sessions.searchMessages": (params) => searchMessages(params.query),
8079
8203
  "sessions.earlier": (params) => earlier(params.sessionId, params.before),
@@ -8114,7 +8238,7 @@ const handlers = {
8114
8238
  const run = runs.get(params.sessionId);
8115
8239
  if (run === void 0) return null;
8116
8240
  await run.respond(params.requestId, params.decision, params.answers);
8117
- recordInteraction(params.sessionId);
8241
+ recordInteraction(params.sessionId, run.provider);
8118
8242
  return null;
8119
8243
  },
8120
8244
  "sessions.rewind": (params) => rewind(params),
@@ -8228,7 +8352,7 @@ function connectClient(socket, device, address) {
8228
8352
  send: (message) => socket.send(JSON.stringify(message))
8229
8353
  };
8230
8354
  clients.add(client);
8231
- log$2.info(`a browser connected from ${address}`);
8355
+ log$3.info(`a browser connected from ${address}`);
8232
8356
  socket.on("message", (data, isBinary) => {
8233
8357
  client.heardAt = Date.now();
8234
8358
  if (isBinary) {
@@ -8237,9 +8361,9 @@ function connectClient(socket, device, address) {
8237
8361
  }
8238
8362
  answer(client, String(data));
8239
8363
  });
8240
- socket.on("error", (error) => log$2.warn(`a browser connection from ${address} failed:`, error));
8364
+ socket.on("error", (error) => log$3.warn(`a browser connection from ${address} failed:`, error));
8241
8365
  socket.on("close", (code) => {
8242
- log$2.info(`a browser from ${address} disconnected (code ${code})`);
8366
+ log$3.info(`a browser from ${address} disconnected (code ${code})`);
8243
8367
  clients.delete(client);
8244
8368
  detachTerminal(client);
8245
8369
  });
@@ -8249,7 +8373,7 @@ async function answer(client, text) {
8249
8373
  try {
8250
8374
  call = JSON.parse(text);
8251
8375
  } catch (error) {
8252
- log$2.warn(`ignoring a message from ${client.address} that is not JSON:`, error);
8376
+ log$3.warn(`ignoring a message from ${client.address} that is not JSON:`, error);
8253
8377
  return;
8254
8378
  }
8255
8379
  const started = performance.now();
@@ -8263,7 +8387,7 @@ async function answer(client, text) {
8263
8387
  result
8264
8388
  });
8265
8389
  } catch (error) {
8266
- log$2.warn(`${call.method} from ${client.address} failed:`, error);
8390
+ log$3.warn(`${call.method} from ${client.address} failed:`, error);
8267
8391
  client.send({
8268
8392
  type: "reply",
8269
8393
  id: call.id,
@@ -8271,7 +8395,7 @@ async function answer(client, text) {
8271
8395
  });
8272
8396
  } finally {
8273
8397
  const took = Math.round(performance.now() - started);
8274
- if (call.method !== "ping") log$2.debug(`${call.method} from ${client.address} took ${took}ms`);
8398
+ if (call.method !== "ping") log$3.debug(`${call.method} from ${client.address} took ${took}ms`);
8275
8399
  }
8276
8400
  }
8277
8401
  // src/lib/proxy/css.ts
@@ -9444,7 +9568,7 @@ function fail(response, status, message) {
9444
9568
  response.end(message);
9445
9569
  }
9446
9570
  // src/services/app-icon.ts
9447
- const log$1 = logger.withTag("app-icon");
9571
+ const log$2 = logger.withTag("app-icon");
9448
9572
  const pngs = /* @__PURE__ */ new Map([
9449
9573
  ["icon-192.png", {
9450
9574
  size: 192,
@@ -9477,7 +9601,7 @@ async function serveAppIcon(publicDir, pathname, response) {
9477
9601
  response.setHeader("cache-control", "no-cache");
9478
9602
  response.end(icon.body);
9479
9603
  } catch (error) {
9480
- log$1.warn(`could not serve the app icon:`, error);
9604
+ log$2.warn(`could not serve the app icon:`, error);
9481
9605
  response.statusCode = 500;
9482
9606
  response.end("could not render the app icon");
9483
9607
  }
@@ -9509,6 +9633,36 @@ function tile(icon, maskable) {
9509
9633
  if (icon.tile.shape === "circle") return `<circle cx="256" cy="256" r="256" fill="${icon.tile.color}"/>`;
9510
9634
  throw new Error(`unknown tile shape for ${icon.id}`);
9511
9635
  }
9636
+ // src/services/backfill-session-providers.ts
9637
+ const log$1 = logger.withTag("backfill");
9638
+ const allProviders = [
9639
+ "claude",
9640
+ "codex",
9641
+ "opencode"
9642
+ ];
9643
+ async function backfillSessionProviders() {
9644
+ const rows = db.select().from(sessions$1).where(isNull(sessions$1.provider)).all();
9645
+ if (rows.length === 0) return;
9646
+ const paths = new Map(projects().map((project) => [project.path, project.id]));
9647
+ const enabled = allProviders.filter((provider) => readConfig().enabledProviders[provider]);
9648
+ for (const row of rows) {
9649
+ const answers = await Promise.all(enabled.map(async (provider) => {
9650
+ try {
9651
+ return await timeLimit(info(provider, row.sessionId, paths), `${providerName(provider)} did not answer in time`) !== null;
9652
+ } catch (error) {
9653
+ log$1.debug(`${providerName(provider)} does not know session ${row.sessionId}:`, error);
9654
+ return false;
9655
+ }
9656
+ }));
9657
+ const owners = enabled.filter((_, index) => answers[index]);
9658
+ if (owners.length !== 1) {
9659
+ log$1.warn(`${owners.length} providers know session ${row.sessionId}, it stays without a provider until the next start`);
9660
+ continue;
9661
+ }
9662
+ db.update(sessions$1).set({ provider: owners[0] }).where(eq(sessions$1.sessionId, row.sessionId)).run();
9663
+ log$1.info(`saved ${providerName(owners[0])} as the provider of session ${row.sessionId}`);
9664
+ }
9665
+ }
9512
9666
  // src/services/browser-logs.ts
9513
9667
  const logsBody = z.array(z.object({
9514
9668
  time: z.number(),
@@ -9609,6 +9763,7 @@ lockHome();
9609
9763
  log.info(`starting offsite ${args.command} with pid ${process.pid} on ${address}, dir ${args.dir}, auth ${args.auth ? "on" : "off"}`);
9610
9764
  process.env.CLAUDE_CONFIG_DIR = claudeBaseDir();
9611
9765
  installOpen(args.host, port);
9766
+ await backfillSessionProviders();
9612
9767
  watchSessions();
9613
9768
  refreshAll();
9614
9769
  if (findClaude() === null) {