codexuse-cli 3.6.2 → 3.6.4

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.
@@ -19324,6 +19324,10 @@ const GitStatusResult = Struct({
19324
19324
  behindCount: NonNegativeInt,
19325
19325
  pr: NullOr(GitStatusPr)
19326
19326
  });
19327
+ const GitStatusUpdatedPayload = Struct({
19328
+ cwd: TrimmedNonEmptyStringSchema,
19329
+ status: GitStatusResult
19330
+ });
19327
19331
  const GitListBranchesResult = Struct({
19328
19332
  branches: Array$1(GitBranch),
19329
19333
  isRepo: Boolean$2,
@@ -19428,6 +19432,22 @@ const MAX_KEYBINDING_VALUE_LENGTH = 64;
19428
19432
  const MAX_KEYBINDING_WHEN_LENGTH = 256;
19429
19433
  const MAX_SCRIPT_ID_LENGTH = 24;
19430
19434
  const MAX_KEYBINDINGS_COUNT = 256;
19435
+ const THREAD_JUMP_KEYBINDING_COMMANDS = [
19436
+ "thread.jump.1",
19437
+ "thread.jump.2",
19438
+ "thread.jump.3",
19439
+ "thread.jump.4",
19440
+ "thread.jump.5",
19441
+ "thread.jump.6",
19442
+ "thread.jump.7",
19443
+ "thread.jump.8",
19444
+ "thread.jump.9"
19445
+ ];
19446
+ const THREAD_KEYBINDING_COMMANDS = [
19447
+ "thread.previous",
19448
+ "thread.next",
19449
+ ...THREAD_JUMP_KEYBINDING_COMMANDS
19450
+ ];
19431
19451
  const STATIC_KEYBINDING_COMMANDS = [
19432
19452
  "terminal.toggle",
19433
19453
  "terminal.split",
@@ -19436,7 +19456,8 @@ const STATIC_KEYBINDING_COMMANDS = [
19436
19456
  "diff.toggle",
19437
19457
  "chat.new",
19438
19458
  "chat.newLocal",
19439
- "editor.openFavorite"
19459
+ "editor.openFavorite",
19460
+ ...THREAD_KEYBINDING_COMMANDS
19440
19461
  ];
19441
19462
  const SCRIPT_RUN_COMMAND_PATTERN = TemplateLiteral([
19442
19463
  Literal("script."),
@@ -19527,43 +19548,43 @@ const EDITORS = [
19527
19548
  {
19528
19549
  id: "cursor",
19529
19550
  label: "Cursor",
19530
- command: "cursor",
19551
+ commands: ["cursor"],
19531
19552
  supportsGoto: true
19532
19553
  },
19533
19554
  {
19534
19555
  id: "vscode",
19535
19556
  label: "VS Code",
19536
- command: "code",
19557
+ commands: ["code"],
19537
19558
  supportsGoto: true
19538
19559
  },
19539
19560
  {
19540
19561
  id: "vscode-insiders",
19541
19562
  label: "VS Code Insiders",
19542
- command: "code-insiders",
19563
+ commands: ["code-insiders"],
19543
19564
  supportsGoto: true
19544
19565
  },
19545
19566
  {
19546
19567
  id: "vscodium",
19547
19568
  label: "VSCodium",
19548
- command: "codium",
19569
+ commands: ["codium"],
19549
19570
  supportsGoto: true
19550
19571
  },
19551
19572
  {
19552
19573
  id: "zed",
19553
19574
  label: "Zed",
19554
- command: "zed",
19575
+ commands: ["zed", "zeditor"],
19555
19576
  supportsGoto: false
19556
19577
  },
19557
19578
  {
19558
19579
  id: "antigravity",
19559
19580
  label: "Antigravity",
19560
- command: "agy",
19581
+ commands: ["agy"],
19561
19582
  supportsGoto: false
19562
19583
  },
19563
19584
  {
19564
19585
  id: "file-manager",
19565
19586
  label: "File Manager",
19566
- command: null,
19587
+ commands: null,
19567
19588
  supportsGoto: false
19568
19589
  }
19569
19590
  ];
@@ -19641,6 +19662,8 @@ const WS_METHODS = {
19641
19662
  shellOpenInEditor: "shell.openInEditor",
19642
19663
  gitPull: "git.pull",
19643
19664
  gitStatus: "git.status",
19665
+ gitWatchStatus: "git.watchStatus",
19666
+ gitUnwatchStatus: "git.unwatchStatus",
19644
19667
  gitRunStackedAction: "git.runStackedAction",
19645
19668
  gitListBranches: "git.listBranches",
19646
19669
  gitCreateWorktree: "git.createWorktree",
@@ -19713,6 +19736,7 @@ const WS_METHODS = {
19713
19736
  };
19714
19737
  const WS_CHANNELS = {
19715
19738
  gitActionProgress: "git.actionProgress",
19739
+ gitStatusUpdated: "git.statusUpdated",
19716
19740
  terminalEvent: "terminal.event",
19717
19741
  serverWelcome: "server.welcome",
19718
19742
  serverConfigUpdated: "server.configUpdated",
@@ -19737,6 +19761,8 @@ const WebSocketRequestBody = Union([
19737
19761
  tagRequestBody(WS_METHODS.shellOpenInEditor, OpenInEditorInput),
19738
19762
  tagRequestBody(WS_METHODS.gitPull, GitPullInput),
19739
19763
  tagRequestBody(WS_METHODS.gitStatus, GitStatusInput),
19764
+ tagRequestBody(WS_METHODS.gitWatchStatus, GitStatusInput),
19765
+ tagRequestBody(WS_METHODS.gitUnwatchStatus, GitStatusInput),
19740
19766
  tagRequestBody(WS_METHODS.gitRunStackedAction, GitRunStackedActionInput),
19741
19767
  tagRequestBody(WS_METHODS.gitListBranches, GitListBranchesInput),
19742
19768
  tagRequestBody(WS_METHODS.gitCreateWorktree, GitCreateWorktreeInput),
@@ -19862,6 +19888,7 @@ const WsPushServerWelcome = makeWsPushSchema(WS_CHANNELS.serverWelcome, WsWelcom
19862
19888
  const WsPushServerConfigUpdated = makeWsPushSchema(WS_CHANNELS.serverConfigUpdated, ServerConfigUpdatedPayload);
19863
19889
  const WsPushServerProvidersUpdated = makeWsPushSchema(WS_CHANNELS.serverProvidersUpdated, ServerProviderUpdatedPayload);
19864
19890
  const WsPushGitActionProgress = makeWsPushSchema(WS_CHANNELS.gitActionProgress, GitActionProgressEvent);
19891
+ const WsPushGitStatusUpdated = makeWsPushSchema(WS_CHANNELS.gitStatusUpdated, GitStatusUpdatedPayload);
19865
19892
  const WsPushTerminalEvent = makeWsPushSchema(WS_CHANNELS.terminalEvent, TerminalEvent);
19866
19893
  const WsPushProfilesSwitched = makeWsPushSchema(WS_CHANNELS.profilesSwitched, Unknown);
19867
19894
  const WsPushRateLimitsUpdated = makeWsPushSchema(WS_CHANNELS.rateLimitsUpdated, Unknown);
@@ -19870,6 +19897,7 @@ const WsPushCliStatusUpdated = makeWsPushSchema(WS_CHANNELS.cliStatusUpdated, Un
19870
19897
  const WsPushOrchestrationDomainEvent = makeWsPushSchema(ORCHESTRATION_WS_CHANNELS.domainEvent, OrchestrationEvent);
19871
19898
  const WsPushChannelSchema = Literals([
19872
19899
  WS_CHANNELS.gitActionProgress,
19900
+ WS_CHANNELS.gitStatusUpdated,
19873
19901
  WS_CHANNELS.serverWelcome,
19874
19902
  WS_CHANNELS.serverConfigUpdated,
19875
19903
  WS_CHANNELS.serverProvidersUpdated,
@@ -19885,6 +19913,7 @@ const WsPush = Union([
19885
19913
  WsPushServerConfigUpdated,
19886
19914
  WsPushServerProvidersUpdated,
19887
19915
  WsPushGitActionProgress,
19916
+ WsPushGitStatusUpdated,
19888
19917
  WsPushTerminalEvent,
19889
19918
  WsPushProfilesSwitched,
19890
19919
  WsPushRateLimitsUpdated,
@@ -19917,6 +19946,10 @@ const LINE_COLUMN_SUFFIX_PATTERN = /:\d+(?::\d+)?$/;
19917
19946
  function shouldUseGotoFlag(editor, target) {
19918
19947
  return editor.supportsGoto && LINE_COLUMN_SUFFIX_PATTERN.test(target);
19919
19948
  }
19949
+ function resolveAvailableCommand(commands, options = {}) {
19950
+ for (const command of commands) if (isCommandAvailable(command, options)) return command;
19951
+ return null;
19952
+ }
19920
19953
  function fileManagerCommandForPlatform(platform) {
19921
19954
  switch (platform) {
19922
19955
  case "darwin": return "open";
@@ -19992,26 +20025,41 @@ function isCommandAvailable(command, options = {}) {
19992
20025
  }
19993
20026
  function resolveAvailableEditors(platform = process.platform, env = process.env) {
19994
20027
  const available = [];
19995
- for (const editor of EDITORS) if (isCommandAvailable(editor.command ?? fileManagerCommandForPlatform(platform), {
19996
- platform,
19997
- env
19998
- })) available.push(editor.id);
20028
+ for (const editor of EDITORS) {
20029
+ if (editor.commands === null) {
20030
+ if (isCommandAvailable(fileManagerCommandForPlatform(platform), {
20031
+ platform,
20032
+ env
20033
+ })) available.push(editor.id);
20034
+ continue;
20035
+ }
20036
+ if (resolveAvailableCommand(editor.commands, {
20037
+ platform,
20038
+ env
20039
+ }) !== null) available.push(editor.id);
20040
+ }
19999
20041
  return available;
20000
20042
  }
20001
20043
  /**
20002
20044
  * Open - Service tag for browser/editor launch operations.
20003
20045
  */
20004
20046
  var Open = class extends Service()("t3/open") {};
20005
- const resolveEditorLaunch = fnUntraced(function* (input, platform = process.platform) {
20047
+ const resolveEditorLaunch = fnUntraced(function* (input, platform = process.platform, env = process.env) {
20006
20048
  const editorDef = EDITORS.find((editor) => editor.id === input.editor);
20007
20049
  if (!editorDef) return yield* new OpenError({ message: `Unknown editor: ${input.editor}` });
20008
- if (editorDef.command) return shouldUseGotoFlag(editorDef, input.cwd) ? {
20009
- command: editorDef.command,
20010
- args: ["--goto", input.cwd]
20011
- } : {
20012
- command: editorDef.command,
20013
- args: [input.cwd]
20014
- };
20050
+ if (editorDef.commands) {
20051
+ const command = resolveAvailableCommand(editorDef.commands, {
20052
+ platform,
20053
+ env
20054
+ }) ?? editorDef.commands[0];
20055
+ return shouldUseGotoFlag(editorDef, input.cwd) ? {
20056
+ command,
20057
+ args: ["--goto", input.cwd]
20058
+ } : {
20059
+ command,
20060
+ args: [input.cwd]
20061
+ };
20062
+ }
20015
20063
  if (editorDef.id !== "file-manager") return yield* new OpenError({ message: `Unsupported editor: ${input.editor}` });
20016
20064
  return {
20017
20065
  command: fileManagerCommandForPlatform(platform),
@@ -24278,10 +24326,13 @@ function projectEvent(model, event) {
24278
24326
  createdAt: payload.createdAt,
24279
24327
  updatedAt: payload.updatedAt
24280
24328
  }, event.type, "message");
24281
- const cappedMessages = (thread.messages.find((entry) => entry.id === message.id) ? thread.messages.map((entry) => entry.id === message.id ? {
24329
+ const existingMessage = thread.messages.find((entry) => entry.id === message.id);
24330
+ const shouldRefreshImportedMessageCreatedAt = message.id.startsWith(`${payload.threadId}:`) && !message.streaming;
24331
+ const cappedMessages = (existingMessage ? thread.messages.map((entry) => entry.id === message.id ? {
24282
24332
  ...entry,
24283
24333
  text: message.streaming ? `${entry.text}${message.text}` : message.role === "user" && message.attachments !== void 0 ? message.text : message.text.length > 0 ? message.text : entry.text,
24284
24334
  streaming: message.streaming,
24335
+ createdAt: shouldRefreshImportedMessageCreatedAt ? message.createdAt : entry.createdAt,
24285
24336
  updatedAt: message.updatedAt,
24286
24337
  turnId: message.turnId,
24287
24338
  ...message.attachments !== void 0 ? { attachments: message.attachments } : {}
@@ -28515,6 +28566,7 @@ const makeOrchestrationProjectionPipeline = gen(function* () {
28515
28566
  const existingMessage = (yield* projectionThreadMessageRepository.listByThreadId({ threadId: event.payload.threadId })).find((row) => row.messageId === event.payload.messageId);
28516
28567
  const nextText = existingMessage && event.payload.streaming ? `${existingMessage.text}${event.payload.text}` : existingMessage && event.payload.text.length === 0 ? existingMessage.text : event.payload.text;
28517
28568
  const nextAttachments = event.payload.attachments !== void 0 ? yield* materializeAttachmentsForProjection({ attachments: event.payload.attachments }) : existingMessage?.attachments;
28569
+ const shouldRefreshImportedMessageCreatedAt = event.payload.messageId.startsWith(`${event.payload.threadId}:`) && !event.payload.streaming;
28518
28570
  yield* projectionThreadMessageRepository.upsert({
28519
28571
  messageId: event.payload.messageId,
28520
28572
  threadId: event.payload.threadId,
@@ -28523,7 +28575,7 @@ const makeOrchestrationProjectionPipeline = gen(function* () {
28523
28575
  text: nextText,
28524
28576
  ...nextAttachments !== void 0 ? { attachments: [...nextAttachments] } : {},
28525
28577
  isStreaming: event.payload.streaming,
28526
- createdAt: existingMessage?.createdAt ?? event.payload.createdAt,
28578
+ createdAt: existingMessage && !shouldRefreshImportedMessageCreatedAt ? existingMessage.createdAt : event.payload.createdAt,
28527
28579
  updatedAt: event.payload.updatedAt
28528
28580
  });
28529
28581
  return;
@@ -29841,10 +29893,15 @@ var LicenseService = class {
29841
29893
  this.verificationPromise = null;
29842
29894
  }
29843
29895
  async getCachedStatus() {
29844
- return this.getStatus();
29896
+ return this.getStatus({
29897
+ allowStale: true,
29898
+ refreshInBackground: true
29899
+ });
29845
29900
  }
29846
29901
  async getStatus(options = {}) {
29847
29902
  const forceRefresh = Boolean(options.forceRefresh);
29903
+ const allowStale = Boolean(options.allowStale);
29904
+ const refreshInBackground = Boolean(options.refreshInBackground);
29848
29905
  const secret = await getLicenseSecret();
29849
29906
  const stored = await getStoredLicense();
29850
29907
  if (!stored?.licenseKey) return toLicenseStatus(null);
@@ -29870,7 +29927,11 @@ var LicenseService = class {
29870
29927
  message: "License data changed, rechecking.",
29871
29928
  error: "Untrusted license data."
29872
29929
  }) : toLicenseStatus(workingStored);
29873
- if (!(forceRefresh || graceStale || tampered || cappedNextCheckTs === null || cappedNextCheckTs <= now)) return cached;
29930
+ const shouldRefresh = forceRefresh || graceStale || tampered || cappedNextCheckTs === null || cappedNextCheckTs <= now;
29931
+ if (!shouldRefresh || allowStale) {
29932
+ if (allowStale && shouldRefresh && refreshInBackground) this.refreshStatusInBackground({ force: forceRefresh });
29933
+ return cached;
29934
+ }
29874
29935
  if (this.verificationPromise && !forceRefresh) return this.verificationPromise;
29875
29936
  const verify = async () => {
29876
29937
  try {
@@ -31692,6 +31753,8 @@ const EXTERNAL_SYNC_MARKER_VERSION = 1;
31692
31753
  const EXTERNAL_THREAD_PAGE_SIZE = 100;
31693
31754
  const EXTERNAL_SYNC_FOREGROUND_THREAD_LIMIT = 8;
31694
31755
  const EXTERNAL_SYNC_APP_SERVER_IDLE_TTL_MS = 45e3;
31756
+ const EXTERNAL_SYNC_WATCHER_SETTLE_MS = 350;
31757
+ const EXTERNAL_SYNC_WATCH_DIRECTORY_NAMES = ["sessions", "archived_sessions"];
31695
31758
  const DEFAULT_IMPORTED_MODEL = "gpt-5-codex";
31696
31759
  function toImportedModelSelection(model) {
31697
31760
  return {
@@ -31720,8 +31783,10 @@ const EXTERNAL_SYNC_APP_SERVER_CLIENT_INFO = {
31720
31783
  let sharedExternalSyncAppServer = null;
31721
31784
  let sharedExternalSyncAppServerHomePath = null;
31722
31785
  let sharedExternalSyncAppServerIdleTimer = null;
31723
- let externalCodexThreadSyncWatcher = null;
31786
+ let externalCodexThreadSyncWatchers = [];
31724
31787
  let externalCodexThreadSyncWatcherHomePath = null;
31788
+ let externalCodexThreadSyncWatcherSettleTimer = null;
31789
+ let externalCodexThreadSyncWatcherPendingTrigger = null;
31725
31790
  const externalSyncAppServerLeaseCount = /* @__PURE__ */ new Map();
31726
31791
  let externalCodexThreadSyncRefreshQueue = null;
31727
31792
  function asRecord$3(value) {
@@ -31732,6 +31797,11 @@ function clearSharedExternalSyncAppServerIdleTimer() {
31732
31797
  clearTimeout(sharedExternalSyncAppServerIdleTimer);
31733
31798
  sharedExternalSyncAppServerIdleTimer = null;
31734
31799
  }
31800
+ function clearExternalCodexThreadSyncWatcherSettleTimer() {
31801
+ if (!externalCodexThreadSyncWatcherSettleTimer) return;
31802
+ clearTimeout(externalCodexThreadSyncWatcherSettleTimer);
31803
+ externalCodexThreadSyncWatcherSettleTimer = null;
31804
+ }
31735
31805
  function incrementExternalSyncAppServerLease(appServer) {
31736
31806
  externalSyncAppServerLeaseCount.set(appServer, (externalSyncAppServerLeaseCount.get(appServer) ?? 0) + 1);
31737
31807
  }
@@ -31798,8 +31868,10 @@ async function withExternalSyncAppServer(homePath, task) {
31798
31868
  }
31799
31869
  }
31800
31870
  function closeExternalCodexThreadSyncWatcher() {
31801
- externalCodexThreadSyncWatcher?.close();
31802
- externalCodexThreadSyncWatcher = null;
31871
+ clearExternalCodexThreadSyncWatcherSettleTimer();
31872
+ externalCodexThreadSyncWatcherPendingTrigger = null;
31873
+ for (const watcher of externalCodexThreadSyncWatchers) watcher.close();
31874
+ externalCodexThreadSyncWatchers = [];
31803
31875
  externalCodexThreadSyncWatcherHomePath = null;
31804
31876
  }
31805
31877
  function setExternalCodexThreadSyncRefreshQueue(queue) {
@@ -31819,26 +31891,102 @@ async function requestExternalCodexThreadSyncRefresh(trigger) {
31819
31891
  }
31820
31892
  }
31821
31893
  function queueExternalCodexThreadSyncRefresh(trigger) {
31822
- requestExternalCodexThreadSyncRefresh(trigger);
31894
+ externalCodexThreadSyncWatcherPendingTrigger = trigger;
31895
+ if (externalCodexThreadSyncWatcherSettleTimer) return;
31896
+ externalCodexThreadSyncWatcherSettleTimer = setTimeout(() => {
31897
+ const nextTrigger = externalCodexThreadSyncWatcherPendingTrigger ?? "external-codex-thread-sync-watcher";
31898
+ externalCodexThreadSyncWatcherPendingTrigger = null;
31899
+ clearExternalCodexThreadSyncWatcherSettleTimer();
31900
+ requestExternalCodexThreadSyncRefresh(nextTrigger);
31901
+ }, EXTERNAL_SYNC_WATCHER_SETTLE_MS);
31902
+ externalCodexThreadSyncWatcherSettleTimer.unref();
31903
+ }
31904
+ function supportsRecursiveExternalCodexThreadSyncWatcher() {
31905
+ return process.platform === "darwin" || process.platform === "win32";
31906
+ }
31907
+ function collectExternalCodexThreadSyncDirectoryWatchPaths(rootPath) {
31908
+ const collectedPaths = [];
31909
+ const stack = [rootPath];
31910
+ const seenPaths = /* @__PURE__ */ new Set();
31911
+ while (stack.length > 0) {
31912
+ const currentPath = stack.pop();
31913
+ if (!currentPath) continue;
31914
+ const resolvedCurrentPath = path.resolve(currentPath);
31915
+ if (seenPaths.has(resolvedCurrentPath)) continue;
31916
+ seenPaths.add(resolvedCurrentPath);
31917
+ let stat;
31918
+ try {
31919
+ stat = fs.statSync(resolvedCurrentPath);
31920
+ } catch {
31921
+ continue;
31922
+ }
31923
+ if (!stat.isDirectory()) continue;
31924
+ collectedPaths.push(resolvedCurrentPath);
31925
+ let entries;
31926
+ try {
31927
+ entries = fs.readdirSync(resolvedCurrentPath, { withFileTypes: true });
31928
+ } catch {
31929
+ continue;
31930
+ }
31931
+ for (const entry of entries) {
31932
+ if (!entry.isDirectory()) continue;
31933
+ stack.push(path.join(resolvedCurrentPath, entry.name));
31934
+ }
31935
+ }
31936
+ return collectedPaths;
31937
+ }
31938
+ function collectExternalCodexThreadSyncWatchTargets(homePath) {
31939
+ const recursive = supportsRecursiveExternalCodexThreadSyncWatcher();
31940
+ const targets = [];
31941
+ for (const relativePath of EXTERNAL_SYNC_WATCH_DIRECTORY_NAMES) {
31942
+ const absolutePath = path.join(homePath, relativePath);
31943
+ if (!fs.existsSync(absolutePath)) continue;
31944
+ if (recursive) {
31945
+ targets.push({
31946
+ watchPath: absolutePath,
31947
+ label: relativePath,
31948
+ recursive: true
31949
+ });
31950
+ continue;
31951
+ }
31952
+ for (const watchPath of collectExternalCodexThreadSyncDirectoryWatchPaths(absolutePath)) targets.push({
31953
+ watchPath,
31954
+ label: path.relative(homePath, watchPath) || ".",
31955
+ recursive: false
31956
+ });
31957
+ }
31958
+ if (targets.length === 0) targets.push({
31959
+ watchPath: homePath,
31960
+ label: ".",
31961
+ recursive
31962
+ });
31963
+ return targets;
31823
31964
  }
31824
31965
  function startExternalCodexThreadSyncWatcher(homePath) {
31825
31966
  const resolvedHomePath = path.resolve(homePath);
31826
- if (externalCodexThreadSyncWatcher !== null && externalCodexThreadSyncWatcherHomePath === resolvedHomePath) return;
31967
+ if (externalCodexThreadSyncWatchers.length > 0 && externalCodexThreadSyncWatcherHomePath === resolvedHomePath) return;
31827
31968
  closeExternalCodexThreadSyncWatcher();
31828
31969
  try {
31829
- externalCodexThreadSyncWatcher = fs.watch(resolvedHomePath, { recursive: process.platform === "darwin" || process.platform === "win32" }, (_eventType, filename) => {
31830
- if (filename && typeof filename === "string" && !filename.trim()) return;
31831
- queueExternalCodexThreadSyncRefresh(filename ?? resolvedHomePath);
31832
- });
31833
- externalCodexThreadSyncWatcherHomePath = resolvedHomePath;
31834
- externalCodexThreadSyncWatcher.on("error", (error) => {
31835
- console.warn("[t3 external thread sync] watcher error", {
31836
- homePath: resolvedHomePath,
31837
- error
31970
+ const watchTargets = collectExternalCodexThreadSyncWatchTargets(resolvedHomePath);
31971
+ for (const target of watchTargets) {
31972
+ const watcher = fs.watch(target.watchPath, { recursive: target.recursive }, (_eventType, filename) => {
31973
+ if (filename && typeof filename === "string" && !filename.trim()) return;
31974
+ const suffix = typeof filename === "string" && filename.trim().length > 0 ? `:${filename}` : "";
31975
+ queueExternalCodexThreadSyncRefresh(`${target.label}${suffix}`);
31838
31976
  });
31839
- closeExternalCodexThreadSyncWatcher();
31840
- });
31977
+ watcher.on("error", (error) => {
31978
+ console.warn("[t3 external thread sync] watcher error", {
31979
+ homePath: resolvedHomePath,
31980
+ watchPath: target.watchPath,
31981
+ error
31982
+ });
31983
+ closeExternalCodexThreadSyncWatcher();
31984
+ });
31985
+ externalCodexThreadSyncWatchers.push(watcher);
31986
+ }
31987
+ externalCodexThreadSyncWatcherHomePath = resolvedHomePath;
31841
31988
  } catch (error) {
31989
+ closeExternalCodexThreadSyncWatcher();
31842
31990
  console.warn("[t3 external thread sync] failed to start watcher", {
31843
31991
  homePath: resolvedHomePath,
31844
31992
  error
@@ -31886,6 +32034,57 @@ function linePreview(text, fallback) {
31886
32034
  if (!normalized) return fallback;
31887
32035
  return normalized.length > 120 ? `${normalized.slice(0, 117)}...` : normalized;
31888
32036
  }
32037
+ function hasImportedVisibleUserMessageContent(payload) {
32038
+ return firstNonEmptyString(payload.message) !== null || Array.isArray(payload.text_elements) && payload.text_elements.length > 0 || Array.isArray(payload.images) && payload.images.length > 0 || Array.isArray(payload.local_images) && payload.local_images.length > 0;
32039
+ }
32040
+ async function readImportedVisibleMessageTimeline(thread) {
32041
+ const threadPath = resolveLegacyFilePath(firstNonEmptyString(thread.path) ?? "");
32042
+ if (!threadPath) return null;
32043
+ let contents;
32044
+ try {
32045
+ contents = await fs$1.readFile(threadPath, "utf8");
32046
+ } catch {
32047
+ return null;
32048
+ }
32049
+ const user = [];
32050
+ const assistant = [];
32051
+ for (const rawLine of contents.split(/\r?\n/)) {
32052
+ const line = rawLine.trim();
32053
+ if (!line) continue;
32054
+ let entry;
32055
+ try {
32056
+ entry = JSON.parse(line);
32057
+ } catch {
32058
+ continue;
32059
+ }
32060
+ const record = asRecord$3(entry);
32061
+ if (!record || asTrimmedString$1(record.type) !== "event_msg") continue;
32062
+ const payload = asRecord$3(record.payload);
32063
+ if (!payload) continue;
32064
+ const timestamp = firstNonEmptyString(record.timestamp, payload.timestamp, payload.createdAt, payload.created_at);
32065
+ if (!timestamp || !Number.isFinite(Date.parse(timestamp))) continue;
32066
+ switch (asTrimmedString$1(payload.type)) {
32067
+ case "user_message":
32068
+ if (hasImportedVisibleUserMessageContent(payload)) user.push(timestamp);
32069
+ break;
32070
+ case "agent_message":
32071
+ if (firstNonEmptyString(payload.message) !== null) assistant.push(timestamp);
32072
+ break;
32073
+ }
32074
+ }
32075
+ return user.length > 0 || assistant.length > 0 ? {
32076
+ user,
32077
+ assistant
32078
+ } : null;
32079
+ }
32080
+ function takeImportedVisibleMessageTimestamp(timeline, role) {
32081
+ if (!timeline) return null;
32082
+ const queue = timeline[role];
32083
+ const nextTimestamp = queue[0];
32084
+ if (!nextTimestamp) return null;
32085
+ queue.shift();
32086
+ return nextTimestamp;
32087
+ }
31889
32088
  function listThreadSummariesFromResponse(payload) {
31890
32089
  const root = asRecord$3(payload);
31891
32090
  const result = asRecord$3(root?.result) ?? root;
@@ -32216,6 +32415,7 @@ async function buildThreadImportArtifacts(input) {
32216
32415
  const activities = [];
32217
32416
  const proposedPlans = [];
32218
32417
  const turns = Array.isArray(thread.turns) ? thread.turns : [];
32418
+ const visibleMessageTimeline = await readImportedVisibleMessageTimeline(thread);
32219
32419
  let latestArtifactUpdatedAt = createdAt;
32220
32420
  let latestUpdatedAt = summaryUpdatedAt;
32221
32421
  for (const [turnIndex, turnEntry] of turns.entries()) {
@@ -32229,7 +32429,8 @@ async function buildThreadImportArtifacts(input) {
32229
32429
  if (!item) continue;
32230
32430
  const itemId = firstNonEmptyString(item.id) ?? `${turnIndex + 1}-${itemIndex + 1}`;
32231
32431
  const type = asTrimmedString$1(item.type);
32232
- const timestamp = nextIsoFromThreadCursor(thread, cursor, item.createdAt ?? item.updatedAt ?? turn.createdAt ?? turn.updatedAt, fallbackBaseMs);
32432
+ const visibleMessageRole = type === "userMessage" ? "user" : type === "agentMessage" ? "assistant" : null;
32433
+ const timestamp = nextIsoFromThreadCursor(thread, cursor, (visibleMessageRole !== null ? takeImportedVisibleMessageTimestamp(visibleMessageTimeline, visibleMessageRole) : null) ?? item.createdAt ?? item.updatedAt ?? item.startedAt ?? item.completedAt ?? turn.createdAt ?? turn.updatedAt ?? turn.startedAt ?? turn.completedAt, fallbackBaseMs);
32233
32434
  if (timestamp > latestArtifactUpdatedAt) latestArtifactUpdatedAt = timestamp;
32234
32435
  if (timestamp > latestUpdatedAt) latestUpdatedAt = timestamp;
32235
32436
  if (type === "userMessage") {
@@ -32294,7 +32495,7 @@ async function buildThreadImportArtifacts(input) {
32294
32495
  }
32295
32496
  const turnError = firstNonEmptyString(turn.error);
32296
32497
  if (turnError) {
32297
- const timestamp = nextIsoFromThreadCursor(thread, cursor, turn.updatedAt ?? turn.completedAt ?? thread.updatedAt, fallbackBaseMs);
32498
+ const timestamp = nextIsoFromThreadCursor(thread, cursor, turn.updatedAt ?? turn.completedAt ?? turn.startedAt ?? thread.updatedAt, fallbackBaseMs);
32298
32499
  if (timestamp > latestArtifactUpdatedAt) latestArtifactUpdatedAt = timestamp;
32299
32500
  if (timestamp > latestUpdatedAt) latestUpdatedAt = timestamp;
32300
32501
  const turnKey = turnId ? turnId : TurnId.makeUnsafe(`${thread.id}:turn:${turnIndex + 1}`);
@@ -32526,6 +32727,11 @@ function latestIsoTimestamp(left, right) {
32526
32727
  const rightMs = toEpochMs(right, 0);
32527
32728
  return new Date(Math.max(leftMs, rightMs)).toISOString();
32528
32729
  }
32730
+ function shouldRefreshImportedMessage(input) {
32731
+ const { existingMessage, nextMessage } = input;
32732
+ if (!existingMessage) return true;
32733
+ return existingMessage.createdAt !== nextMessage.createdAt || existingMessage.updatedAt !== nextMessage.updatedAt || existingMessage.text !== nextMessage.text || existingMessage.turnId !== nextMessage.turnId || existingMessage.streaming !== nextMessage.streaming || JSON.stringify(existingMessage.attachments ?? null) !== JSON.stringify(nextMessage.attachments ?? null);
32734
+ }
32529
32735
  function hasImportedThreadArtifacts(state) {
32530
32736
  return state.messageIds.size > 0 || state.activityIds.size > 0 || state.planIds.size > 0;
32531
32737
  }
@@ -32728,7 +32934,11 @@ function importExternalThreadArtifacts(input) {
32728
32934
  input.state.threadStateById.set(input.threadId, existingThreadState);
32729
32935
  let importedMessageCount = 0;
32730
32936
  for (const message of input.importArtifacts.messages) {
32731
- if (existingThreadState.messageIds.has(message.id) || shouldSkipImportedMessageForLocalContinuation({
32937
+ const existingMessage = existingThreadReadModel?.messages.find((entry) => entry.id === message.id);
32938
+ if (!shouldRefreshImportedMessage({
32939
+ existingMessage,
32940
+ nextMessage: message
32941
+ }) || shouldSkipImportedMessageForLocalContinuation({
32732
32942
  message,
32733
32943
  dedupState: localContinuationDedupState
32734
32944
  })) continue;
@@ -38074,7 +38284,19 @@ const DEFAULT_KEYBINDINGS = [
38074
38284
  {
38075
38285
  key: "mod+o",
38076
38286
  command: "editor.openFavorite"
38077
- }
38287
+ },
38288
+ {
38289
+ key: "mod+shift+[",
38290
+ command: "thread.previous"
38291
+ },
38292
+ {
38293
+ key: "mod+shift+]",
38294
+ command: "thread.next"
38295
+ },
38296
+ ...THREAD_JUMP_KEYBINDING_COMMANDS.map((command, index) => ({
38297
+ key: `mod+${index + 1}`,
38298
+ command
38299
+ }))
38078
38300
  ];
38079
38301
  function normalizeKeyToken(token) {
38080
38302
  if (token === "space") return " ";
@@ -52640,6 +52862,10 @@ function sendDesktopParentMessage$1(payload) {
52640
52862
  process.send(payload);
52641
52863
  } catch {}
52642
52864
  }
52865
+ const GIT_STATUS_WATCH_REFRESH_INTERVAL_MS = 1e4;
52866
+ function fingerprintGitStatus(status) {
52867
+ return JSON.stringify(status);
52868
+ }
52643
52869
  function appendSessionError(session, chunk) {
52644
52870
  const normalized = compactErrorText(chunk);
52645
52871
  if (!normalized) return;
@@ -52742,6 +52968,8 @@ const createServer = fn(function* () {
52742
52968
  });
52743
52969
  yield* addFinalizer$1(() => promise(() => accountPool.dispose()));
52744
52970
  const clients = yield* make$11(/* @__PURE__ */ new Set());
52971
+ const watchedGitStatusesByCwd = /* @__PURE__ */ new Map();
52972
+ const watchedGitStatusCwdsByClient = /* @__PURE__ */ new WeakMap();
52745
52973
  const logger = createLogger("ws");
52746
52974
  const readiness = yield* makeServerReadiness;
52747
52975
  function logOutgoingPush(push, recipients) {
@@ -53236,6 +53464,96 @@ const createServer = fn(function* () {
53236
53464
  cause
53237
53465
  })));
53238
53466
  const runPromise$1 = runPromiseWith(yield* services());
53467
+ const publishGitStatusToClient = (client, cwd, status) => pushBus.publishClient(client, WS_CHANNELS.gitStatusUpdated, {
53468
+ cwd,
53469
+ status
53470
+ }).pipe(asVoid);
53471
+ const stopGitStatusWatcher = (cwd, watcher) => {
53472
+ clearInterval(watcher.intervalId);
53473
+ watchedGitStatusesByCwd.delete(cwd);
53474
+ };
53475
+ const scheduleWatchedGitStatusRefresh = (cwd) => {
53476
+ const watcher = watchedGitStatusesByCwd.get(cwd);
53477
+ if (!watcher) return Promise.resolve();
53478
+ if (watcher.refreshPromise) return watcher.refreshPromise;
53479
+ const refreshPromise = runPromise$1(gitManager.status({ cwd }).pipe(catch_(() => succeed(null)), flatMap((status) => {
53480
+ if (status === null) return void_$1;
53481
+ return sync(() => {
53482
+ const currentWatcher = watchedGitStatusesByCwd.get(cwd);
53483
+ if (!currentWatcher) return null;
53484
+ const nextFingerprint = fingerprintGitStatus(status);
53485
+ const hasChanged = currentWatcher.lastFingerprint !== nextFingerprint;
53486
+ currentWatcher.lastFingerprint = nextFingerprint;
53487
+ currentWatcher.lastStatus = status;
53488
+ if (!hasChanged || currentWatcher.subscribers.size === 0) return null;
53489
+ return [...currentWatcher.subscribers];
53490
+ }).pipe(flatMap((subscribers) => subscribers === null ? void_$1 : forEach(subscribers, (client) => publishGitStatusToClient(client, cwd, status), { discard: true })));
53491
+ })));
53492
+ let trackedRefreshPromise;
53493
+ trackedRefreshPromise = refreshPromise.finally(() => {
53494
+ const currentWatcher = watchedGitStatusesByCwd.get(cwd);
53495
+ if (currentWatcher?.refreshPromise === trackedRefreshPromise) currentWatcher.refreshPromise = null;
53496
+ });
53497
+ watcher.refreshPromise = trackedRefreshPromise;
53498
+ return trackedRefreshPromise;
53499
+ };
53500
+ const getOrCreateGitStatusWatcher = (cwd) => {
53501
+ const existing = watchedGitStatusesByCwd.get(cwd);
53502
+ if (existing) return existing;
53503
+ const watcher = {
53504
+ subscribers: /* @__PURE__ */ new Set(),
53505
+ intervalId: setInterval(() => {
53506
+ scheduleWatchedGitStatusRefresh(cwd);
53507
+ }, GIT_STATUS_WATCH_REFRESH_INTERVAL_MS),
53508
+ lastFingerprint: null,
53509
+ lastStatus: null,
53510
+ refreshPromise: null
53511
+ };
53512
+ watchedGitStatusesByCwd.set(cwd, watcher);
53513
+ return watcher;
53514
+ };
53515
+ const removeGitStatusWatcherSubscriber = (client, cwd) => {
53516
+ const watcher = watchedGitStatusesByCwd.get(cwd);
53517
+ if (!watcher) return;
53518
+ watcher.subscribers.delete(client);
53519
+ if (watcher.subscribers.size === 0) stopGitStatusWatcher(cwd, watcher);
53520
+ };
53521
+ const watchGitStatusForClient = fnUntraced(function* (client, cwd) {
53522
+ let watchedCwds = watchedGitStatusCwdsByClient.get(client);
53523
+ if (!watchedCwds) {
53524
+ watchedCwds = /* @__PURE__ */ new Set();
53525
+ watchedGitStatusCwdsByClient.set(client, watchedCwds);
53526
+ }
53527
+ if (watchedCwds.has(cwd)) {
53528
+ const watcher = watchedGitStatusesByCwd.get(cwd);
53529
+ if (watcher?.lastStatus) yield* publishGitStatusToClient(client, cwd, watcher.lastStatus);
53530
+ return;
53531
+ }
53532
+ watchedCwds.add(cwd);
53533
+ const watcher = getOrCreateGitStatusWatcher(cwd);
53534
+ watcher.subscribers.add(client);
53535
+ if (watcher.lastStatus) {
53536
+ yield* publishGitStatusToClient(client, cwd, watcher.lastStatus);
53537
+ return;
53538
+ }
53539
+ yield* promise(() => scheduleWatchedGitStatusRefresh(cwd));
53540
+ });
53541
+ const unwatchGitStatusForClient = (client, cwd) => {
53542
+ const watchedCwds = watchedGitStatusCwdsByClient.get(client);
53543
+ if (!watchedCwds?.delete(cwd)) return;
53544
+ if (watchedCwds.size === 0) watchedGitStatusCwdsByClient.delete(client);
53545
+ removeGitStatusWatcherSubscriber(client, cwd);
53546
+ };
53547
+ const unwatchAllGitStatusForClient = (client) => {
53548
+ const watchedCwds = watchedGitStatusCwdsByClient.get(client);
53549
+ if (!watchedCwds) return;
53550
+ watchedGitStatusCwdsByClient.delete(client);
53551
+ for (const cwd of watchedCwds) removeGitStatusWatcherSubscriber(client, cwd);
53552
+ };
53553
+ yield* addFinalizer$1(sync(() => {
53554
+ for (const watcher of watchedGitStatusesByCwd.values()) clearInterval(watcher.intervalId);
53555
+ watchedGitStatusesByCwd.clear();
53556
+ }));
53239
53557
  const telegramBridge = createTelegramBridge({
53240
53558
  getSnapshot: () => runPromise$1(projectionReadModelQuery.getSnapshot()),
53241
53559
  getThreadSnapshot: (threadId) => runPromise$1(gen(function* () {
@@ -53410,6 +53728,18 @@ const createServer = fn(function* () {
53410
53728
  const body = stripRequestTag(request.body);
53411
53729
  return yield* gitManager.status(body);
53412
53730
  }
53731
+ case WS_METHODS.gitWatchStatus: {
53732
+ const body = stripRequestTag(request.body);
53733
+ if (!ws) return yield* new RouteRequestError({ message: "Git status watching requires a WebSocket client." });
53734
+ yield* watchGitStatusForClient(ws, yield* normalizeProjectWorkspaceRoot(body.cwd));
53735
+ return;
53736
+ }
53737
+ case WS_METHODS.gitUnwatchStatus: {
53738
+ const body = stripRequestTag(request.body);
53739
+ if (!ws) return yield* new RouteRequestError({ message: "Git status watching requires a WebSocket client." });
53740
+ unwatchGitStatusForClient(ws, yield* normalizeProjectWorkspaceRoot(body.cwd));
53741
+ return;
53742
+ }
53413
53743
  case WS_METHODS.gitPull: {
53414
53744
  const body = stripRequestTag(request.body);
53415
53745
  return yield* git.pullCurrentBranch(body.cwd);
@@ -53918,12 +54248,14 @@ const createServer = fn(function* () {
53918
54248
  runPromise$1(handleMessage(ws, raw).pipe(ignoreCause({ log: true })));
53919
54249
  });
53920
54250
  ws.on("close", () => {
54251
+ unwatchAllGitStatusForClient(ws);
53921
54252
  runPromise$1(update(clients, (clients) => {
53922
54253
  clients.delete(ws);
53923
54254
  return clients;
53924
54255
  }));
53925
54256
  });
53926
54257
  ws.on("error", () => {
54258
+ unwatchAllGitStatusForClient(ws);
53927
54259
  runPromise$1(update(clients, (clients) => {
53928
54260
  clients.delete(ws);
53929
54261
  return clients;