remote-codex 0.11.52 → 0.11.53

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.
@@ -8595,15 +8595,23 @@ var DEFERRED_COMMAND_DETAIL_TITLE = "Command Output";
8595
8595
  var DEFERRED_TOOL_DETAIL_TITLE = "Tool Call Details";
8596
8596
  var DEFERRED_AGENT_TOOL_DETAIL_TITLE = "Agent Details";
8597
8597
  function parseUuidV7Timestamp(id) {
8598
- const normalized = id.replace(/-/g, "");
8599
- if (!/^[0-9a-f]{32}$/i.test(normalized) || normalized[12]?.toLowerCase() !== "7") {
8600
- return null;
8601
- }
8602
- const millis = Number.parseInt(normalized.slice(0, 12), 16);
8603
- if (!Number.isFinite(millis)) {
8604
- return null;
8598
+ const candidates = [
8599
+ id,
8600
+ ...id.match(
8601
+ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{32}/gi
8602
+ ) ?? []
8603
+ ];
8604
+ for (const candidate of candidates) {
8605
+ const normalized = candidate.replace(/-/g, "");
8606
+ if (!/^[0-9a-f]{32}$/i.test(normalized) || normalized[12]?.toLowerCase() !== "7") {
8607
+ continue;
8608
+ }
8609
+ const millis = Number.parseInt(normalized.slice(0, 12), 16);
8610
+ if (Number.isFinite(millis)) {
8611
+ return new Date(millis).toISOString();
8612
+ }
8605
8613
  }
8606
- return new Date(millis).toISOString();
8614
+ return null;
8607
8615
  }
8608
8616
  function isRecord4(value) {
8609
8617
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -8911,6 +8919,31 @@ function formatToolCallHistoryItem(item, deferredDetails, kind = "toolCall") {
8911
8919
  }
8912
8920
  return historyItem;
8913
8921
  }
8922
+ function deferLargeHistoryItemDetails(turn, deferredDetails) {
8923
+ return {
8924
+ ...turn,
8925
+ items: turn.items.map(
8926
+ (item) => item.kind === "commandExecution" ? deferCommandHistoryItem(
8927
+ item,
8928
+ deferredDetails
8929
+ ) : item.kind === "toolCall" || item.kind === "agentToolCall" || item.kind === "skillToolCall" ? deferToolCallHistoryItem(
8930
+ item,
8931
+ deferredDetails
8932
+ ) : item
8933
+ )
8934
+ };
8935
+ }
8936
+ function visibleRuntimeTurnItems(items) {
8937
+ const hasFinalAgentMessage = items.some(
8938
+ (item) => item.kind === "agentMessage" && !isTransientAgentHistoryItem(item)
8939
+ );
8940
+ if (!hasFinalAgentMessage) {
8941
+ return items;
8942
+ }
8943
+ return items.filter(
8944
+ (item) => !(item.kind === "agentMessage" && isTransientAgentHistoryItem(item))
8945
+ );
8946
+ }
8914
8947
  function extractWebSearchQueries(item) {
8915
8948
  const action = isRecord4(item.action) ? item.action : null;
8916
8949
  const result = isRecord4(item.result) ? item.result : null;
@@ -9360,6 +9393,18 @@ function liveCodexItemToHistoryItem(item, phase) {
9360
9393
  status: historyItem.status ?? (phase === "started" ? "running" : "completed")
9361
9394
  };
9362
9395
  }
9396
+ function agentTurnToThreadTurnDto(turn, deferredDetails) {
9397
+ const baseTurn = {
9398
+ id: turn.providerTurnId,
9399
+ startedAt: turn.startedAt ?? parseUuidV7Timestamp(turn.providerTurnId),
9400
+ status: turn.status,
9401
+ error: turn.error?.message ?? null,
9402
+ items: visibleRuntimeTurnItems(turn.items).map(
9403
+ (item, transcriptIndex) => item.transcriptOrder === transcriptIndex ? item : { ...item, transcriptOrder: transcriptIndex }
9404
+ )
9405
+ };
9406
+ return deferredDetails ? deferLargeHistoryItemDetails(baseTurn, deferredDetails) : baseTurn;
9407
+ }
9363
9408
  function codexTurnToAgentTurn(turn) {
9364
9409
  return {
9365
9410
  providerTurnId: turn.id,
@@ -9374,6 +9419,10 @@ function codexTurnToAgentTurn(turn) {
9374
9419
  }
9375
9420
 
9376
9421
  // ../../packages/codex/src/local-session-store.ts
9422
+ import {
9423
+ unwatchFile,
9424
+ watchFile
9425
+ } from "fs";
9377
9426
  import fs6 from "fs/promises";
9378
9427
  import path7 from "path";
9379
9428
  import Database2 from "better-sqlite3";
@@ -9391,6 +9440,76 @@ function summarizeTitleFromTurns(turns) {
9391
9440
  function createHistoryItemId(turnId, prefix, index) {
9392
9441
  return `${turnId}-${prefix}-${index}`;
9393
9442
  }
9443
+ function transcriptMessageText(payload) {
9444
+ if (!Array.isArray(payload?.content)) {
9445
+ return null;
9446
+ }
9447
+ const text2 = payload.content.filter(
9448
+ (content) => (content?.type === "input_text" || content?.type === "output_text") && typeof content.text === "string" && content.text.trim()
9449
+ ).map((content) => content.text).join("\n\n");
9450
+ return text2 || null;
9451
+ }
9452
+ function camelCaseKey(key) {
9453
+ return key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
9454
+ }
9455
+ function camelCaseValue(value) {
9456
+ if (Array.isArray(value)) {
9457
+ return value.map(camelCaseValue);
9458
+ }
9459
+ if (!value || typeof value !== "object") {
9460
+ return value;
9461
+ }
9462
+ return Object.fromEntries(
9463
+ Object.entries(value).map(([key, entry]) => [
9464
+ camelCaseKey(key),
9465
+ camelCaseValue(entry)
9466
+ ])
9467
+ );
9468
+ }
9469
+ function completedRolloutHistoryItem(payload, timestamp) {
9470
+ if (!payload?.item || typeof payload.item !== "object") {
9471
+ return null;
9472
+ }
9473
+ const normalized = camelCaseValue(payload.item);
9474
+ if (typeof normalized.id !== "string" || typeof normalized.type !== "string") {
9475
+ return null;
9476
+ }
9477
+ const normalizedType = normalized.type.charAt(0).toLowerCase() + normalized.type.slice(1);
9478
+ if (normalizedType === "userMessage" || normalizedType === "agentMessage") {
9479
+ return null;
9480
+ }
9481
+ if (normalizedType === "reasoning") {
9482
+ const summaryText = normalized.summaryText;
9483
+ normalized.summary = Array.isArray(summaryText) ? summaryText.filter((entry) => typeof entry === "string") : typeof summaryText === "string" && summaryText.trim() ? [summaryText] : [];
9484
+ const rawContent = normalized.rawContent;
9485
+ normalized.text = Array.isArray(rawContent) ? rawContent.map(
9486
+ (entry) => typeof entry === "string" ? entry : entry && typeof entry === "object" && typeof entry.text === "string" ? entry.text : ""
9487
+ ).filter(Boolean).join("\n") : "";
9488
+ }
9489
+ const createdAtCandidate = typeof payload.started_at_ms === "number" ? payload.started_at_ms : timestamp ?? null;
9490
+ const agentTurn = codexTurnToAgentTurn({
9491
+ id: typeof payload.turn_id === "string" ? payload.turn_id : "rollout-turn",
9492
+ status: "inProgress",
9493
+ error: null,
9494
+ items: [
9495
+ {
9496
+ ...normalized,
9497
+ id: normalized.id,
9498
+ type: normalizedType,
9499
+ createdAt: createdAtCandidate
9500
+ }
9501
+ ]
9502
+ });
9503
+ return agentTurn.items[0] ?? null;
9504
+ }
9505
+ function appendUniqueTurnItem(turn, item) {
9506
+ const existingIndex = turn.items.findIndex((entry) => entry.id === item.id);
9507
+ if (existingIndex >= 0) {
9508
+ turn.items[existingIndex] = item;
9509
+ return;
9510
+ }
9511
+ turn.items.push(item);
9512
+ }
9394
9513
  function finalizeTurn(turn, turns) {
9395
9514
  if (!turn || turn.items.length === 0) {
9396
9515
  return;
@@ -9403,7 +9522,98 @@ function finalizeTurn(turn, turns) {
9403
9522
  items: turn.items
9404
9523
  });
9405
9524
  }
9525
+ function isoTimestampFromEpochSeconds(value) {
9526
+ return value === null || !Number.isFinite(value) ? null : new Date(value * 1e3).toISOString();
9527
+ }
9528
+ function normalizePaginatedTurnStatus(status) {
9529
+ switch (status) {
9530
+ case "completed":
9531
+ case "interrupted":
9532
+ case "failed":
9533
+ case "inProgress":
9534
+ return status;
9535
+ default:
9536
+ return "inProgress";
9537
+ }
9538
+ }
9539
+ function parsePaginatedTurnError(value) {
9540
+ if (!value) {
9541
+ return null;
9542
+ }
9543
+ try {
9544
+ const parsed = JSON.parse(value);
9545
+ if (parsed && typeof parsed === "object" && typeof parsed.message === "string") {
9546
+ return parsed;
9547
+ }
9548
+ return { message: value };
9549
+ } catch {
9550
+ return { message: value };
9551
+ }
9552
+ }
9553
+ function mergeSessionTurns(paginatedTurns, transcriptTurns) {
9554
+ if (paginatedTurns.length === 0) {
9555
+ return transcriptTurns;
9556
+ }
9557
+ const transcriptById = new Map(
9558
+ transcriptTurns.map((turn) => [turn.id, turn])
9559
+ );
9560
+ const merged = paginatedTurns.map((turn) => {
9561
+ const transcriptTurn = transcriptById.get(turn.id);
9562
+ if (!transcriptTurn) {
9563
+ return turn;
9564
+ }
9565
+ transcriptById.delete(turn.id);
9566
+ const paginatedItemIds = new Set(turn.items.map((item) => item.id));
9567
+ const missingTranscriptItems = transcriptTurn.items.filter(
9568
+ (item) => !paginatedItemIds.has(item.id)
9569
+ );
9570
+ const items = [...turn.items, ...missingTranscriptItems].map((item, index) => ({ item, index })).sort((left, right) => {
9571
+ const leftMillis = Date.parse(left.item.createdAt ?? "");
9572
+ const rightMillis = Date.parse(right.item.createdAt ?? "");
9573
+ if (Number.isFinite(leftMillis) && Number.isFinite(rightMillis)) {
9574
+ const delta = leftMillis - rightMillis;
9575
+ return delta === 0 ? left.index - right.index : delta;
9576
+ }
9577
+ if (Number.isFinite(leftMillis)) return -1;
9578
+ if (Number.isFinite(rightMillis)) return 1;
9579
+ return left.index - right.index;
9580
+ }).map((entry, transcriptOrder) => ({
9581
+ ...entry.item,
9582
+ transcriptOrder
9583
+ }));
9584
+ return {
9585
+ ...turn,
9586
+ startedAt: turn.startedAt ?? transcriptTurn.startedAt,
9587
+ status: transcriptTurn.status,
9588
+ error: transcriptTurn.error ?? turn.error,
9589
+ items
9590
+ };
9591
+ });
9592
+ merged.push(...transcriptById.values());
9593
+ return merged.sort(
9594
+ (left, right) => (left.startedAt ?? "").localeCompare(right.startedAt ?? "")
9595
+ );
9596
+ }
9406
9597
  function parseTranscript(contents) {
9598
+ const entries = contents.split("\n").filter((line) => line.trim()).flatMap((line) => {
9599
+ try {
9600
+ return [JSON.parse(line)];
9601
+ } catch {
9602
+ return [];
9603
+ }
9604
+ });
9605
+ let transcriptSegmentIndex = -1;
9606
+ const indexedEntries = entries.map((entry) => {
9607
+ if (entry.type === "event_msg" && entry.payload?.type === "task_started") {
9608
+ transcriptSegmentIndex += 1;
9609
+ }
9610
+ return { entry, segmentIndex: transcriptSegmentIndex };
9611
+ });
9612
+ const legacyMessageSegments = new Set(
9613
+ indexedEntries.filter(
9614
+ ({ entry }) => entry.type === "event_msg" && (entry.payload?.type === "user_message" || entry.payload?.type === "agent_message")
9615
+ ).map(({ segmentIndex }) => segmentIndex)
9616
+ );
9407
9617
  const turns = [];
9408
9618
  let cwd = null;
9409
9619
  let currentTurn = null;
@@ -9426,16 +9636,7 @@ function parseTranscript(contents) {
9426
9636
  userItemCount = 0;
9427
9637
  return currentTurn;
9428
9638
  };
9429
- for (const line of contents.split("\n")) {
9430
- if (!line.trim()) {
9431
- continue;
9432
- }
9433
- let entry;
9434
- try {
9435
- entry = JSON.parse(line);
9436
- } catch {
9437
- continue;
9438
- }
9639
+ for (const { entry, segmentIndex } of indexedEntries) {
9439
9640
  if (entry.type === "session_meta") {
9440
9641
  const payload2 = entry.payload ?? {};
9441
9642
  if (typeof payload2.cwd === "string" && payload2.cwd.trim()) {
@@ -9443,6 +9644,33 @@ function parseTranscript(contents) {
9443
9644
  }
9444
9645
  continue;
9445
9646
  }
9647
+ if (!legacyMessageSegments.has(segmentIndex) && entry.type === "response_item" && entry.payload?.type === "message") {
9648
+ const payload2 = entry.payload;
9649
+ const text2 = transcriptMessageText(payload2);
9650
+ if (!text2 || payload2.role !== "user" && payload2.role !== "assistant") {
9651
+ continue;
9652
+ }
9653
+ const turn = ensureCurrentTurn(entry.timestamp);
9654
+ if (payload2.role === "user") {
9655
+ userItemCount += 1;
9656
+ turn.items.push({
9657
+ id: typeof payload2.id === "string" && payload2.id.trim() ? payload2.id : createHistoryItemId(turn.id, "user", userItemCount),
9658
+ kind: "userMessage",
9659
+ text: text2,
9660
+ createdAt: entry.timestamp ?? null
9661
+ });
9662
+ } else {
9663
+ agentItemCount += 1;
9664
+ turn.items.push({
9665
+ id: typeof payload2.id === "string" && payload2.id.trim() ? payload2.id : createHistoryItemId(turn.id, "agent", agentItemCount),
9666
+ kind: "agentMessage",
9667
+ text: text2,
9668
+ status: typeof payload2.phase === "string" ? payload2.phase : null,
9669
+ createdAt: entry.timestamp ?? null
9670
+ });
9671
+ }
9672
+ continue;
9673
+ }
9446
9674
  if (entry.type !== "event_msg") {
9447
9675
  continue;
9448
9676
  }
@@ -9467,7 +9695,8 @@ function parseTranscript(contents) {
9467
9695
  turn.items.push({
9468
9696
  id: createHistoryItemId(turn.id, "user", userItemCount),
9469
9697
  kind: "userMessage",
9470
- text: payload.message
9698
+ text: payload.message,
9699
+ createdAt: entry.timestamp ?? null
9471
9700
  });
9472
9701
  continue;
9473
9702
  }
@@ -9478,10 +9707,21 @@ function parseTranscript(contents) {
9478
9707
  id: createHistoryItemId(turn.id, "agent", agentItemCount),
9479
9708
  kind: "agentMessage",
9480
9709
  text: payload.message,
9481
- status: typeof payload.phase === "string" ? payload.phase : null
9710
+ status: typeof payload.phase === "string" ? payload.phase : null,
9711
+ createdAt: entry.timestamp ?? null
9482
9712
  });
9483
9713
  continue;
9484
9714
  }
9715
+ if (payloadType === "item_completed") {
9716
+ const item = completedRolloutHistoryItem(
9717
+ payload,
9718
+ typeof entry.timestamp === "string" ? entry.timestamp : null
9719
+ );
9720
+ if (item) {
9721
+ appendUniqueTurnItem(ensureCurrentTurn(entry.timestamp), item);
9722
+ }
9723
+ continue;
9724
+ }
9485
9725
  if (payloadType === "task_complete") {
9486
9726
  const turn = ensureCurrentTurn(entry.timestamp);
9487
9727
  turn.status = turn.error ? "failed" : "completed";
@@ -9511,10 +9751,14 @@ async function fileExists(filePath) {
9511
9751
  }
9512
9752
  }
9513
9753
  var LocalCodexSessionStore = class {
9514
- constructor(codexHome) {
9754
+ constructor(codexHome, options = {}) {
9515
9755
  this.codexHome = codexHome;
9756
+ this.watchIntervalMs = options.watchIntervalMs ?? 500;
9757
+ this.watchThrottleMs = options.watchThrottleMs ?? 1500;
9516
9758
  }
9517
9759
  codexHome;
9760
+ watchIntervalMs;
9761
+ watchThrottleMs;
9518
9762
  async findSession(sessionId) {
9519
9763
  const stateRecord = await this.findSessionInStateDatabases(sessionId);
9520
9764
  const transcriptPath = await this.resolveTranscriptPath(
@@ -9522,6 +9766,7 @@ var LocalCodexSessionStore = class {
9522
9766
  sessionId
9523
9767
  );
9524
9768
  const transcript = transcriptPath ? parseTranscript(await fs6.readFile(transcriptPath, "utf8")) : null;
9769
+ const paginatedTurns = await this.findPaginatedTurns(sessionId);
9525
9770
  const cwd = stateRecord?.cwd ?? transcript?.cwd ?? null;
9526
9771
  if (!cwd) {
9527
9772
  return null;
@@ -9532,7 +9777,57 @@ var LocalCodexSessionStore = class {
9532
9777
  title: stateRecord?.title?.trim() || transcript?.title?.trim() || basenameFromPath(cwd),
9533
9778
  model: stateRecord?.model ?? null,
9534
9779
  rolloutPath: transcriptPath,
9535
- turns: transcript?.turns ?? []
9780
+ turns: mergeSessionTurns(paginatedTurns, transcript?.turns ?? [])
9781
+ };
9782
+ }
9783
+ async watchSession(sessionId, onChange) {
9784
+ const stateRecord = await this.findSessionInStateDatabases(sessionId);
9785
+ const transcriptPath = await this.resolveTranscriptPath(
9786
+ stateRecord?.rolloutPath ?? null,
9787
+ sessionId
9788
+ );
9789
+ if (!transcriptPath) {
9790
+ return () => {
9791
+ };
9792
+ }
9793
+ let stopped = false;
9794
+ let lastEmittedAt = 0;
9795
+ let pending = null;
9796
+ const emit = () => {
9797
+ pending = null;
9798
+ if (stopped) {
9799
+ return;
9800
+ }
9801
+ lastEmittedAt = Date.now();
9802
+ onChange();
9803
+ };
9804
+ const schedule = () => {
9805
+ if (stopped || pending) {
9806
+ return;
9807
+ }
9808
+ const delay = Math.max(
9809
+ 0,
9810
+ this.watchThrottleMs - (Date.now() - lastEmittedAt)
9811
+ );
9812
+ pending = setTimeout(emit, delay);
9813
+ };
9814
+ const listener = (current, previous) => {
9815
+ if (current.size !== previous.size || current.mtimeMs !== previous.mtimeMs) {
9816
+ schedule();
9817
+ }
9818
+ };
9819
+ watchFile(
9820
+ transcriptPath,
9821
+ { persistent: false, interval: this.watchIntervalMs },
9822
+ listener
9823
+ );
9824
+ return () => {
9825
+ stopped = true;
9826
+ if (pending) {
9827
+ clearTimeout(pending);
9828
+ pending = null;
9829
+ }
9830
+ unwatchFile(transcriptPath, listener);
9536
9831
  };
9537
9832
  }
9538
9833
  async findImportSession(sessionId, input) {
@@ -9557,29 +9852,36 @@ var LocalCodexSessionStore = class {
9557
9852
  };
9558
9853
  }
9559
9854
  async findSessionInStateDatabases(sessionId) {
9560
- let entries;
9561
- try {
9562
- entries = await fs6.readdir(this.codexHome);
9563
- } catch {
9564
- return null;
9565
- }
9566
- const stateFiles = await Promise.all(
9567
- entries.filter((entry) => /^state_\d+\.sqlite$/i.test(entry)).map(async (entry) => {
9568
- const absPath = path7.join(this.codexHome, entry);
9569
- const stats = await fs6.stat(absPath);
9570
- return {
9571
- absPath,
9572
- mtimeMs: stats.mtimeMs
9573
- };
9574
- })
9575
- );
9855
+ const stateFiles = (await Promise.all(
9856
+ [this.codexHome, path7.join(this.codexHome, "sqlite")].map(
9857
+ async (directory) => {
9858
+ let entries;
9859
+ try {
9860
+ entries = await fs6.readdir(directory);
9861
+ } catch {
9862
+ return [];
9863
+ }
9864
+ return Promise.all(
9865
+ entries.filter((entry) => /^state_\d+\.sqlite$/i.test(entry)).map(async (entry) => {
9866
+ const absPath = path7.join(directory, entry);
9867
+ const stats = await fs6.stat(absPath);
9868
+ return {
9869
+ absPath,
9870
+ mtimeMs: stats.mtimeMs
9871
+ };
9872
+ })
9873
+ );
9874
+ }
9875
+ )
9876
+ )).flat();
9576
9877
  stateFiles.sort((left, right) => right.mtimeMs - left.mtimeMs);
9577
9878
  for (const stateFile of stateFiles) {
9578
- const sqlite = new Database2(stateFile.absPath, {
9579
- readonly: true,
9580
- fileMustExist: true
9581
- });
9879
+ let sqlite = null;
9582
9880
  try {
9881
+ sqlite = new Database2(stateFile.absPath, {
9882
+ readonly: true,
9883
+ fileMustExist: true
9884
+ });
9583
9885
  const row = sqlite.prepare(
9584
9886
  `
9585
9887
  SELECT
@@ -9598,11 +9900,91 @@ var LocalCodexSessionStore = class {
9598
9900
  }
9599
9901
  } catch {
9600
9902
  } finally {
9601
- sqlite.close();
9903
+ sqlite?.close();
9602
9904
  }
9603
9905
  }
9604
9906
  return null;
9605
9907
  }
9908
+ async findPaginatedTurns(sessionId) {
9909
+ const databasePath = path7.join(this.codexHome, "thread_history_1.sqlite");
9910
+ let sqlite = null;
9911
+ try {
9912
+ sqlite = new Database2(databasePath, {
9913
+ readonly: true,
9914
+ fileMustExist: true
9915
+ });
9916
+ const turnRows = sqlite.prepare(
9917
+ `
9918
+ SELECT
9919
+ turn_id AS turnId,
9920
+ rollout_ordinal AS rolloutOrdinal,
9921
+ status,
9922
+ error_json AS errorJson,
9923
+ started_at AS startedAt
9924
+ FROM thread_turns
9925
+ WHERE thread_id = ?
9926
+ ORDER BY rollout_ordinal ASC
9927
+ `
9928
+ ).all(sessionId);
9929
+ if (turnRows.length === 0) {
9930
+ return [];
9931
+ }
9932
+ const itemRows = sqlite.prepare(
9933
+ `
9934
+ SELECT
9935
+ turn_id AS turnId,
9936
+ rollout_ordinal AS rolloutOrdinal,
9937
+ created_at_ms AS createdAtMs,
9938
+ item_type AS itemType,
9939
+ item_json AS itemJson
9940
+ FROM thread_items
9941
+ WHERE thread_id = ?
9942
+ ORDER BY rollout_ordinal ASC
9943
+ `
9944
+ ).all(sessionId);
9945
+ const itemsByTurnId = /* @__PURE__ */ new Map();
9946
+ for (const row of itemRows) {
9947
+ let parsed;
9948
+ try {
9949
+ parsed = JSON.parse(row.itemJson);
9950
+ } catch {
9951
+ continue;
9952
+ }
9953
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9954
+ continue;
9955
+ }
9956
+ const parsedRecord = parsed;
9957
+ if (typeof parsedRecord.id !== "string" || !parsedRecord.id.trim()) {
9958
+ continue;
9959
+ }
9960
+ const item = {
9961
+ ...parsedRecord,
9962
+ id: parsedRecord.id,
9963
+ type: typeof parsedRecord.type === "string" ? parsedRecord.type : row.itemType,
9964
+ createdAt: row.createdAtMs
9965
+ };
9966
+ const turnItems = itemsByTurnId.get(row.turnId) ?? [];
9967
+ turnItems.push(item);
9968
+ itemsByTurnId.set(row.turnId, turnItems);
9969
+ }
9970
+ return turnRows.map((row) => {
9971
+ const agentTurn = codexTurnToAgentTurn({
9972
+ id: row.turnId,
9973
+ status: normalizePaginatedTurnStatus(row.status),
9974
+ error: parsePaginatedTurnError(row.errorJson),
9975
+ items: itemsByTurnId.get(row.turnId) ?? []
9976
+ });
9977
+ return agentTurnToThreadTurnDto({
9978
+ ...agentTurn,
9979
+ startedAt: isoTimestampFromEpochSeconds(row.startedAt)
9980
+ });
9981
+ });
9982
+ } catch {
9983
+ return [];
9984
+ } finally {
9985
+ sqlite?.close();
9986
+ }
9987
+ }
9606
9988
  async resolveTranscriptPath(rolloutPath, sessionId) {
9607
9989
  if (rolloutPath?.trim()) {
9608
9990
  const absolutePath = path7.isAbsolute(rolloutPath) ? rolloutPath : path7.resolve(this.codexHome, rolloutPath);
@@ -25206,24 +25588,30 @@ var DEFERRED_FILE_READ_DETAIL_TITLE = "File Read Details";
25206
25588
  var DEFERRED_WEB_SEARCH_DETAIL_TITLE = "Web Search Details";
25207
25589
  var DEFERRED_HOOK_DETAIL_TITLE = "Hook Details";
25208
25590
  function parseUuidV7Timestamp2(id) {
25209
- const normalized = id.replace(/-/g, "");
25210
- if (!/^[0-9a-f]{32}$/i.test(normalized) || normalized[12]?.toLowerCase() !== "7") {
25211
- return null;
25212
- }
25213
- const millis = Number.parseInt(normalized.slice(0, 12), 16);
25214
- if (!Number.isFinite(millis)) {
25215
- return null;
25591
+ const candidates = [
25592
+ id,
25593
+ ...id.match(
25594
+ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{32}/gi
25595
+ ) ?? []
25596
+ ];
25597
+ for (const candidate of candidates) {
25598
+ const normalized = candidate.replace(/-/g, "");
25599
+ if (!/^[0-9a-f]{32}$/i.test(normalized) || normalized[12]?.toLowerCase() !== "7") {
25600
+ continue;
25601
+ }
25602
+ const millis = Number.parseInt(normalized.slice(0, 12), 16);
25603
+ if (Number.isFinite(millis)) {
25604
+ return new Date(millis).toISOString();
25605
+ }
25216
25606
  }
25217
- return new Date(millis).toISOString();
25607
+ return null;
25218
25608
  }
25219
- function normalizeHistoryItemCreatedAt(item, fallback) {
25609
+ function normalizeHistoryItemCreatedAt(item) {
25220
25610
  if (item.createdAt) {
25221
25611
  return item;
25222
25612
  }
25223
- return {
25224
- ...item,
25225
- createdAt: parseUuidV7Timestamp2(item.id) ?? fallback
25226
- };
25613
+ const inferredCreatedAt = parseUuidV7Timestamp2(item.id);
25614
+ return inferredCreatedAt ? { ...item, createdAt: inferredCreatedAt } : item;
25227
25615
  }
25228
25616
  function summarizeText(text2, fallback) {
25229
25617
  const lines = text2.replace(/\r\n/g, "\n").split("\n");
@@ -25362,7 +25750,7 @@ function deferHistoryItemDetail(item, deferredDetails) {
25362
25750
  function deferHistoryItemDetailForTransport(item) {
25363
25751
  return deferHistoryItemDetail(item, /* @__PURE__ */ new Map());
25364
25752
  }
25365
- function deferLargeHistoryItemDetails(turn, deferredDetails) {
25753
+ function deferLargeHistoryItemDetails2(turn, deferredDetails) {
25366
25754
  return {
25367
25755
  ...turn,
25368
25756
  items: turn.items.map((item) => deferHistoryItemDetail(item, deferredDetails))
@@ -25380,7 +25768,7 @@ function shouldPersistRuntimeFinalHistoryItem(item) {
25380
25768
  }
25381
25769
  return shouldPersistFinalHistoryItem(item);
25382
25770
  }
25383
- function visibleRuntimeTurnItems(items) {
25771
+ function visibleRuntimeTurnItems2(items) {
25384
25772
  const hasFinalAgentMessage = items.some(
25385
25773
  (item) => item.kind === "agentMessage" && !isTransientAgentHistoryItem(item)
25386
25774
  );
@@ -25413,7 +25801,7 @@ function historyItemTranscriptOrder(item) {
25413
25801
  }
25414
25802
  function copyPersistedOrderingHints(item, persistedItem, turnStartedAt) {
25415
25803
  let nextItem = item;
25416
- if (persistedItem.createdAt && (!nextItem.createdAt || nextItem.kind === "agentMessage" && nextItem.createdAt === turnStartedAt)) {
25804
+ if (persistedItem.createdAt && (!nextItem.createdAt || nextItem.createdAt === turnStartedAt)) {
25417
25805
  nextItem = { ...nextItem, createdAt: persistedItem.createdAt };
25418
25806
  }
25419
25807
  if (hasHistoryItemSequence(persistedItem)) {
@@ -25587,21 +25975,20 @@ function mergePersistedHistoryItemsIntoTurns(turns, persistedItemsByTurnId, defe
25587
25975
  };
25588
25976
  });
25589
25977
  }
25590
- function agentTurnToThreadTurnDto(turn, deferredDetails) {
25978
+ function agentTurnToThreadTurnDto2(turn, deferredDetails) {
25591
25979
  const startedAt = turn.startedAt ?? parseUuidV7Timestamp2(turn.providerTurnId);
25592
25980
  const baseTurn = {
25593
25981
  id: turn.providerTurnId,
25594
25982
  startedAt,
25595
25983
  status: turn.status,
25596
25984
  error: turn.error?.message ?? null,
25597
- items: visibleRuntimeTurnItems(turn.items).map(
25985
+ items: visibleRuntimeTurnItems2(turn.items).map(
25598
25986
  (item, transcriptIndex) => normalizeHistoryItemCreatedAt(
25599
- item.transcriptOrder === transcriptIndex ? item : { ...item, transcriptOrder: transcriptIndex },
25600
- startedAt
25987
+ item.transcriptOrder === transcriptIndex ? item : { ...item, transcriptOrder: transcriptIndex }
25601
25988
  )
25602
25989
  )
25603
25990
  };
25604
- return deferredDetails ? deferLargeHistoryItemDetails(baseTurn, deferredDetails) : baseTurn;
25991
+ return deferredDetails ? deferLargeHistoryItemDetails2(baseTurn, deferredDetails) : baseTurn;
25605
25992
  }
25606
25993
  function applyRecordedTurnItemOrder(turn, turnItemOrder) {
25607
25994
  const itemOrder = turnItemOrder.get(turn.id);
@@ -27245,10 +27632,7 @@ var ThreadDetailAssembler = class {
27245
27632
  if (cached) {
27246
27633
  return cached;
27247
27634
  }
27248
- let remoteSession = await this.input.callbacks.readRemoteSession(
27249
- input.record,
27250
- options
27251
- );
27635
+ let remoteSession = input.record.source === "local_codex_import" && input.record.isConnected === false ? null : await this.input.callbacks.readRemoteSession(input.record, options);
27252
27636
  if (!remoteSession) {
27253
27637
  return this.buildLocalFallbackEntry({
27254
27638
  ...input,
@@ -27317,7 +27701,7 @@ var ThreadDetailAssembler = class {
27317
27701
  input.localThreadId,
27318
27702
  remoteSession.turns
27319
27703
  );
27320
- const visibleTurns = this.input.liveState.visibleRemoteTurns(input.localThreadId, remoteSession.turns).map((turn) => agentTurnToThreadTurnDto(turn, deferredDetails));
27704
+ const visibleTurns = this.input.liveState.visibleRemoteTurns(input.localThreadId, remoteSession.turns).map((turn) => agentTurnToThreadTurnDto2(turn, deferredDetails));
27321
27705
  const visibleTurnsWithActiveLiveTurn = appendActiveLiveTurnIfMissing(
27322
27706
  visibleTurns,
27323
27707
  input.localThreadId,
@@ -27404,7 +27788,7 @@ var ThreadDetailAssembler = class {
27404
27788
  deferredDetails
27405
27789
  ).map(
27406
27790
  (turn) => buildTurnDto(
27407
- deferLargeHistoryItemDetails(turn, deferredDetails),
27791
+ deferLargeHistoryItemDetails2(turn, deferredDetails),
27408
27792
  input.turnMetadataById.get(turn.id) ?? fallbackMetadata
27409
27793
  )
27410
27794
  );
@@ -31040,6 +31424,7 @@ var ThreadService = class {
31040
31424
  constructor(db, agentRuntimes, eventBus, localSessionStore, workspaceRoot, providerManagement, pluginService, config) {
31041
31425
  this.db = db;
31042
31426
  this.eventBus = eventBus;
31427
+ this.localSessionStore = localSessionStore;
31043
31428
  this.workspaceRoot = workspaceRoot;
31044
31429
  this.pluginService = pluginService;
31045
31430
  this.config = config;
@@ -31260,6 +31645,7 @@ var ThreadService = class {
31260
31645
  }
31261
31646
  db;
31262
31647
  eventBus;
31648
+ localSessionStore;
31263
31649
  workspaceRoot;
31264
31650
  pluginService;
31265
31651
  config;
@@ -31283,6 +31669,8 @@ var ThreadService = class {
31283
31669
  forkCoordinator;
31284
31670
  attachmentCoordinator;
31285
31671
  importCoordinator;
31672
+ localImportWatcherStops = /* @__PURE__ */ new Map();
31673
+ localImportWatcherStarts = /* @__PURE__ */ new Map();
31286
31674
  normalizeProvider(provider2) {
31287
31675
  return this.providerRuntime.normalizeProvider(provider2);
31288
31676
  }
@@ -31330,7 +31718,7 @@ var ThreadService = class {
31330
31718
  if (displayTurnId === turn.providerTurnId) {
31331
31719
  continue;
31332
31720
  }
31333
- const dto = agentTurnToThreadTurnDto(
31721
+ const dto = agentTurnToThreadTurnDto2(
31334
31722
  turn,
31335
31723
  /* @__PURE__ */ new Map()
31336
31724
  );
@@ -31458,6 +31846,11 @@ var ThreadService = class {
31458
31846
  async getThreadDetail(localThreadId, options = {}) {
31459
31847
  const record2 = this.requireThreadRecord(localThreadId);
31460
31848
  const workspace = this.requireWorkspaceForThread(record2);
31849
+ if (record2.source === "local_codex_import" && record2.isConnected === false) {
31850
+ await this.ensureLocalImportWatcher(record2);
31851
+ } else {
31852
+ this.stopLocalImportWatcher(record2.id);
31853
+ }
31461
31854
  this.requireProviderSessionId(record2);
31462
31855
  const loadedIds = await this.listLoadedProviderSessionIds(record2.provider);
31463
31856
  const workspacePathStatus = await pathExists3(workspace.absPath) ? "present" : "missing";
@@ -32081,6 +32474,52 @@ var ThreadService = class {
32081
32474
  async handleRuntimeEvent(event) {
32082
32475
  await this.runtimeEventProjector.handleRuntimeEvent(event);
32083
32476
  }
32477
+ close() {
32478
+ for (const stop of this.localImportWatcherStops.values()) {
32479
+ stop();
32480
+ }
32481
+ this.localImportWatcherStops.clear();
32482
+ this.localImportWatcherStarts.clear();
32483
+ }
32484
+ async ensureLocalImportWatcher(record2) {
32485
+ if (!record2.providerSessionId || !this.localSessionStore.watchSession || this.localImportWatcherStops.has(record2.id)) {
32486
+ return;
32487
+ }
32488
+ const existingStart = this.localImportWatcherStarts.get(record2.id);
32489
+ if (existingStart) {
32490
+ await existingStart;
32491
+ return;
32492
+ }
32493
+ const start = this.localSessionStore.watchSession(
32494
+ record2.providerSessionId,
32495
+ () => {
32496
+ const current = getThreadRecordById(this.db, record2.id);
32497
+ if (!current || current.source !== "local_codex_import" || current.isConnected !== false) {
32498
+ this.stopLocalImportWatcher(record2.id);
32499
+ return;
32500
+ }
32501
+ this.invalidateThreadDetailCache(record2.id);
32502
+ this.emitThreadEvent("thread.updated", record2.id, {
32503
+ reason: "external_session_updated"
32504
+ });
32505
+ }
32506
+ ).then((stop) => {
32507
+ const current = getThreadRecordById(this.db, record2.id);
32508
+ if (!current || current.source !== "local_codex_import" || current.isConnected !== false) {
32509
+ stop();
32510
+ return;
32511
+ }
32512
+ this.localImportWatcherStops.set(record2.id, stop);
32513
+ }).finally(() => {
32514
+ this.localImportWatcherStarts.delete(record2.id);
32515
+ });
32516
+ this.localImportWatcherStarts.set(record2.id, start);
32517
+ await start;
32518
+ }
32519
+ stopLocalImportWatcher(localThreadId) {
32520
+ this.localImportWatcherStops.get(localThreadId)?.();
32521
+ this.localImportWatcherStops.delete(localThreadId);
32522
+ }
32084
32523
  shouldPreserveCompletedPendingSteer(localThreadId) {
32085
32524
  const record2 = getThreadRecordById(this.db, localThreadId);
32086
32525
  if (!record2) {
@@ -38147,6 +38586,7 @@ function buildApp(options = {}) {
38147
38586
  });
38148
38587
  app2.addHook("onClose", async () => {
38149
38588
  cleanupRelayActivity?.();
38589
+ threadService.close();
38150
38590
  await shellService.stop();
38151
38591
  relayTunnelClient?.stop();
38152
38592
  await Promise.all(agentRuntimes.all().map((runtime) => runtime.stop()));