replicas-engine 0.1.869 → 0.1.871

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.
package/dist/src/index.js CHANGED
@@ -91,7 +91,7 @@ import {
91
91
  evaluateCommandProtection,
92
92
  extractToolCommand,
93
93
  reportCommandProtectionBlock
94
- } from "./chunk-RMSEJMO4.js";
94
+ } from "./chunk-6YEU4TRD.js";
95
95
  import {
96
96
  ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
97
97
  AGENT_MESSAGE_DELTA_METHOD,
@@ -132,20 +132,20 @@ import {
132
132
  recordCredentialFallback,
133
133
  recordExhaustedCredential,
134
134
  restartCodexAspHost
135
- } from "./chunk-COJG7NIH.js";
135
+ } from "./chunk-TGECCL6C.js";
136
136
  import {
137
137
  ENGINE_ENV,
138
138
  IS_WARMING_MODE,
139
139
  monolithRequest,
140
140
  monolithService,
141
141
  setAgentCredentialSnapshot
142
- } from "./chunk-AZVITHZV.js";
142
+ } from "./chunk-VXF4AHJU.js";
143
143
  import {
144
144
  AspClient,
145
145
  SUBPROCESS_MAX_BUFFER,
146
146
  execAsync,
147
147
  execFileAsync
148
- } from "./chunk-TZGLNBWY.js";
148
+ } from "./chunk-GN43A3FI.js";
149
149
  import {
150
150
  isRecord as isRecord2
151
151
  } from "./chunk-UZSNFLDQ.js";
@@ -180,12 +180,13 @@ import {
180
180
  CLAUDE_SONNET_5_BEDROCK_MODEL,
181
181
  CLAUDE_SONNET_5_MODEL,
182
182
  CODEX_ASP_ITEM_ID_PAYLOAD_KEY,
183
- CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE,
183
+ CODEX_HISTORY_FORMAT_EVENT_TYPE,
184
184
  CODEX_QUOTA_STATUS_EVENT_TYPE,
185
185
  COMPACTION_STATUS_EVENT_TYPE,
186
186
  CONTEXT_USAGE_EVENT_TYPE,
187
187
  CREDENTIAL_METHOD,
188
188
  CURSOR_CONNECTION_STATUS_EVENT_TYPE,
189
+ CodexHistoryEventFolder,
189
190
  DEFAULT_ASTER_MODEL,
190
191
  DEFAULT_CHAT_TITLES,
191
192
  DEFAULT_CLAUDE_MODEL,
@@ -233,7 +234,6 @@ import {
233
234
  USER_MESSAGE_ID_PAYLOAD_KEY,
234
235
  VALID_AGENT_PROVIDERS,
235
236
  agentQuotaExhaustedPayloadSchema,
236
- applyCodexAspTranscriptDelta,
237
237
  areSameUserMessageEvents,
238
238
  areUserMessagesWithinMatchWindow,
239
239
  autoCompactThresholdField,
@@ -248,7 +248,12 @@ import {
248
248
  clampWarmHookTimeoutMs,
249
249
  classifyCanvasFilename,
250
250
  claudeAuthEnvFromResponse,
251
+ codexHistoryFormatEvent,
252
+ codexItemEvent,
251
253
  codexReasoningEffortForThinkingLevel,
254
+ codexTranscriptToEvents,
255
+ codexTurnEvent,
256
+ codexTurnFromEvent,
252
257
  coerceBackgroundTaskPayload,
253
258
  coerceChatForkInfo,
254
259
  coerceChatGoalPayload,
@@ -256,7 +261,6 @@ import {
256
261
  createAcceptedUserMessageEvent,
257
262
  createChatHistoryCursor,
258
263
  createChatRequestSchema,
259
- createChatTranscriptPages,
260
264
  createErrorResult,
261
265
  createProviderSlashCommand,
262
266
  createSuccessResult,
@@ -276,12 +280,9 @@ import {
276
280
  formatChatForkHandoffMessage,
277
281
  getChatExecutionProvider,
278
282
  getChatHistoryPageSenders,
279
- getChatHistoryPageWindow,
280
- getChatTranscriptHistorySize,
281
283
  getClaudeModelContextWindow,
282
- getClaudePartialMessageStreamId,
283
- getCodexAspTurnResponse,
284
284
  getDefaultAgentModel,
285
+ getEventStreamId,
285
286
  getEventTimestampMs,
286
287
  getGitHubAccessToken,
287
288
  getGoalCommand,
@@ -295,17 +296,15 @@ import {
295
296
  githubCredentialLines,
296
297
  hasChatStarted,
297
298
  imageContentToUserMessageImages,
298
- isAgentBackendEvent,
299
299
  isAgentChatMcpActivityRecord,
300
300
  isAgentChatSkillActivityRecord,
301
301
  isAgentChatTurnActivityRecord,
302
302
  isAuthenticationErrorText,
303
303
  isCanvasTextKind,
304
+ isChatHistoryEvent,
304
305
  isChatMessageSender,
305
306
  isClaudeAuthErrorText,
306
307
  isClaudeResultError,
307
- isCodexAspTranscript,
308
- isCodexAspTranscriptDelta,
309
308
  isCodexAuthError,
310
309
  isDefaultChat,
311
310
  isExcludedRepoFilePath,
@@ -330,20 +329,18 @@ import {
330
329
  normalizeContentBlocks,
331
330
  paginateChatHistory,
332
331
  paginateEngineLogContent,
333
- parseAgentEventJsonlWithCodexAspTranscript,
332
+ parseAgentEventJsonlLine,
334
333
  parseAgentEvents,
335
334
  parseChatHistoryCursor,
336
335
  parseChatTranscriptArtifact,
337
336
  parseDisplayMessages,
338
337
  parseFrontmatter,
339
338
  parseGitRemote,
340
- parseLatestCodexAspTranscriptTurn,
341
339
  parseMcpToolName,
342
340
  parseReplicasConfigString,
343
341
  percentage,
344
342
  raceWithTimeout,
345
343
  readJsonlPage,
346
- renumberCodexAspTranscriptItems,
347
344
  resolveWarmHookConfig,
348
345
  safeJsonParse,
349
346
  sanitizeCanvasFilename,
@@ -358,7 +355,7 @@ import {
358
355
  waitRelaySubagentRequestSchema,
359
356
  withTimeout,
360
357
  workspaceCredentialProviderSchema
361
- } from "./chunk-IT5R5GJT.js";
358
+ } from "./chunk-XHX6IRSG.js";
362
359
  import {
363
360
  __commonJS,
364
361
  __export,
@@ -10701,8 +10698,8 @@ async function registerDesktopPreview() {
10701
10698
  }
10702
10699
 
10703
10700
  // src/services/chat/chat-service.ts
10704
- import { appendFile as appendFile5, copyFile as copyFile2, mkdir as mkdir21, readFile as readFile24, rename as rename4, rm as rm4 } from "fs/promises";
10705
- import { join as join37 } from "path";
10701
+ import { appendFile as appendFile5, copyFile as copyFile2, mkdir as mkdir21, readFile as readFile23, rename as rename5, rm as rm4 } from "fs/promises";
10702
+ import { join as join36 } from "path";
10706
10703
  import { randomUUID as randomUUID13 } from "crypto";
10707
10704
 
10708
10705
  // ../shared/src/workspace-chat.ts
@@ -10740,7 +10737,7 @@ function workspaceChatEventSignals(event) {
10740
10737
  eventJsonCache.set(event, json);
10741
10738
  }
10742
10739
  return {
10743
- streamId: getClaudePartialMessageStreamId(event),
10740
+ streamId: getEventStreamId(event),
10744
10741
  json,
10745
10742
  userMessage: getComparableUserMessage(event),
10746
10743
  userMessageId: event.type === QUEUED_MESSAGE_REMOVED_EVENT_TYPE ? null : getUserMessageId(event)
@@ -10850,7 +10847,7 @@ function mergeWorkspaceChatHistoryEvents(primary, supplemental, removedMessageId
10850
10847
  list[position] = null;
10851
10848
  unindexEvent(listIndex, previous, position);
10852
10849
  addEvent(event);
10853
- } else if (getClaudePartialMessageStreamId(event) !== null || messageId !== null && messageId === getUserMessageId(previous)) {
10850
+ } else if (getEventStreamId(event) !== null || messageId !== null && messageId === getUserMessageId(previous)) {
10854
10851
  list[position] = event;
10855
10852
  unindexEvent(listIndex, previous, position);
10856
10853
  indexMergedEvent(listIndex, event, position);
@@ -10880,7 +10877,7 @@ import {
10880
10877
  } from "@anthropic-ai/claude-agent-sdk";
10881
10878
  import { randomUUID as randomUUID5 } from "crypto";
10882
10879
  import { dirname as dirname4, join as join16 } from "path";
10883
- import { mkdir as mkdir10, readdir as readdir4, readFile as readFile11 } from "fs/promises";
10880
+ import { mkdir as mkdir10, readdir as readdir4, readFile as readFile10 } from "fs/promises";
10884
10881
  import { homedir as homedir11 } from "os";
10885
10882
 
10886
10883
  // src/utils/linear-converter.ts
@@ -12444,7 +12441,13 @@ function isNotFoundError(error) {
12444
12441
  }
12445
12442
 
12446
12443
  // src/managers/codex-asp/codex-history-file.ts
12447
- import { appendFile as appendFile2, open, readFile as readFile10 } from "fs/promises";
12444
+ import { createReadStream } from "fs";
12445
+ import { appendFile as appendFile2, open, rename as rename2 } from "fs/promises";
12446
+ import { createInterface } from "readline";
12447
+ var MIGRATION_FLUSH_CHARS = 1 << 20;
12448
+ function isMissingFile(error) {
12449
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
12450
+ }
12448
12451
  var CodexHistoryFile = class {
12449
12452
  constructor(filePath) {
12450
12453
  this.filePath = filePath;
@@ -12464,40 +12467,79 @@ var CodexHistoryFile = class {
12464
12467
  async flush() {
12465
12468
  await this.writeLock.drain();
12466
12469
  }
12467
- async load() {
12470
+ /**
12471
+ * Rewrites a file that still holds legacy Codex transcript records as plain
12472
+ * item events, once. The file is read line by line: a long-lived chat can
12473
+ * exceed the largest string V8 will hold. The rewritten file starts with a
12474
+ * format marker so later starts only read that first line.
12475
+ */
12476
+ async ensureItemEventFormat() {
12477
+ const firstLine = await this.readFirstLine();
12478
+ if (firstLine === null) {
12479
+ await this.appendDurably(codexHistoryFormatEvent());
12480
+ return;
12481
+ }
12482
+ if (parseAgentEventJsonlLine(firstLine)?.type === CODEX_HISTORY_FORMAT_EVENT_TYPE) return;
12483
+ await this.writeLock.run(async () => {
12484
+ const events = await this.loadAll();
12485
+ const rewritten = `${this.filePath}.migrating`;
12486
+ const handle = await open(rewritten, "w");
12487
+ try {
12488
+ let pending = "";
12489
+ for (const event of [codexHistoryFormatEvent(), ...events]) {
12490
+ pending += `${JSON.stringify(event)}
12491
+ `;
12492
+ if (pending.length >= MIGRATION_FLUSH_CHARS) {
12493
+ await handle.write(pending);
12494
+ pending = "";
12495
+ }
12496
+ }
12497
+ if (pending) await handle.write(pending);
12498
+ } finally {
12499
+ await handle.close();
12500
+ }
12501
+ await rename2(rewritten, this.filePath);
12502
+ });
12503
+ }
12504
+ /**
12505
+ * Newest event of `type`, scanning the whole file so a value older than the
12506
+ * most recent page is still found. Streams line by line and retains only the
12507
+ * match, so history size does not bound memory.
12508
+ */
12509
+ async findLastEventOfType(type) {
12510
+ let found = null;
12468
12511
  try {
12469
- const content = await readFile10(this.filePath, "utf-8");
12470
- return parseAgentEventJsonlWithCodexAspTranscript(content);
12512
+ for await (const line of createInterface({ input: createReadStream(this.filePath), crlfDelay: Infinity })) {
12513
+ const event = parseAgentEventJsonlLine(line);
12514
+ if (event?.type === type) found = event;
12515
+ }
12471
12516
  } catch (error) {
12472
- if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) {
12473
- console.error("[CodexHistoryFile] Failed to load history file:", error);
12517
+ if (!isMissingFile(error)) throw error;
12518
+ }
12519
+ return found;
12520
+ }
12521
+ async loadAll() {
12522
+ const folder = new CodexHistoryEventFolder();
12523
+ try {
12524
+ for await (const line of createInterface({ input: createReadStream(this.filePath), crlfDelay: Infinity })) {
12525
+ const event = parseAgentEventJsonlLine(line);
12526
+ if (event) folder.push(event);
12474
12527
  }
12475
- return { events: [], transcript: null, transcriptsByThreadId: /* @__PURE__ */ new Map() };
12528
+ } catch (error) {
12529
+ if (!isMissingFile(error)) throw error;
12476
12530
  }
12531
+ return folder.finish();
12477
12532
  }
12478
12533
  async loadEventsPage(page2) {
12479
- if (page2.limit === void 0) {
12480
- const history = await this.load();
12481
- return { events: history.events };
12482
- }
12534
+ if (page2.limit === void 0) return { events: await this.loadAll() };
12483
12535
  const cursor = parseChatHistoryCursor(page2.beforeCursor);
12484
12536
  if (page2.beforeCursor !== void 0 && cursor?.mode !== "range") throw new RangeError("Invalid chat history cursor");
12485
- if (cursor?.events === null) {
12486
- return {
12487
- events: [],
12488
- beforeCursor: createChatHistoryCursor({
12489
- events: null,
12490
- turns: cursor.turns
12491
- })
12492
- };
12493
- }
12537
+ if (cursor?.events === null) return { events: [], beforeCursor: null };
12494
12538
  let file;
12495
12539
  try {
12496
12540
  file = await open(this.filePath, "r");
12497
12541
  } catch (error) {
12498
- if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
12499
- return { events: [], beforeCursor: null };
12500
- }
12542
+ if (isMissingFile(error)) return { events: [], beforeCursor: null };
12501
12543
  throw error;
12502
12544
  }
12503
12545
  try {
@@ -12510,19 +12552,35 @@ var CodexHistoryFile = class {
12510
12552
  const { bytesRead } = await file.read(bytes, 0, bytes.length, startByte);
12511
12553
  return bytes.subarray(0, bytesRead);
12512
12554
  },
12513
- parse: (value) => isAgentBackendEvent(value) && value.type !== CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE ? value : void 0
12555
+ parse: (value) => isChatHistoryEvent(value) ? value : void 0
12514
12556
  });
12515
12557
  return {
12516
12558
  events: result.items,
12517
- beforeCursor: createChatHistoryCursor({
12518
- events: result.before,
12519
- turns: cursor?.turns ?? null
12520
- })
12559
+ beforeCursor: createChatHistoryCursor({ events: result.before, turns: null })
12521
12560
  };
12522
12561
  } finally {
12523
12562
  await file.close();
12524
12563
  }
12525
12564
  }
12565
+ async readFirstLine() {
12566
+ let file;
12567
+ try {
12568
+ file = await open(this.filePath, "r");
12569
+ } catch (error) {
12570
+ if (isMissingFile(error)) return null;
12571
+ throw error;
12572
+ }
12573
+ try {
12574
+ const bytes = Buffer.allocUnsafe(4096);
12575
+ const { bytesRead } = await file.read(bytes, 0, bytes.length, 0);
12576
+ if (bytesRead === 0) return null;
12577
+ const text = bytes.subarray(0, bytesRead).toString("utf-8");
12578
+ const newline = text.indexOf("\n");
12579
+ return newline === -1 ? text : text.slice(0, newline);
12580
+ } finally {
12581
+ await file.close();
12582
+ }
12583
+ }
12526
12584
  };
12527
12585
 
12528
12586
  // src/managers/claude-activity.ts
@@ -13100,7 +13158,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
13100
13158
  const agentId = /^agent-(.+)\.jsonl$/.exec(file)?.[1];
13101
13159
  if (!agentId) continue;
13102
13160
  const offset = workflow.offsets.get(file) ?? 0;
13103
- const buffer = await readFile11(join16(workflow.dir, file)).catch(() => Buffer.alloc(0));
13161
+ const buffer = await readFile10(join16(workflow.dir, file)).catch(() => Buffer.alloc(0));
13104
13162
  const end = buffer.lastIndexOf(10);
13105
13163
  if (end < offset) continue;
13106
13164
  workflow.offsets.set(file, end + 1);
@@ -14077,10 +14135,6 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
14077
14135
  }
14078
14136
  };
14079
14137
 
14080
- // src/managers/codex-asp/codex-asp-manager.ts
14081
- import { readdir as readdir5 } from "fs/promises";
14082
- import { join as join18 } from "path";
14083
-
14084
14138
  // src/utils/codex-quota.ts
14085
14139
  function buildCodexRateLimitsSnapshot(fields) {
14086
14140
  if (fields.authMethod !== "oauth") return null;
@@ -14508,128 +14562,7 @@ function turnsToAspTranscript(threadId, updatedAt, sourceTurns) {
14508
14562
  items
14509
14563
  };
14510
14564
  });
14511
- return renumberCodexAspTranscriptItems({
14512
- threadId,
14513
- updatedAt,
14514
- turns
14515
- });
14516
- }
14517
- function latestIsoTimestamp(a, b) {
14518
- return Date.parse(a) >= Date.parse(b) ? a : b;
14519
- }
14520
- function nextTranscriptSequence(transcript) {
14521
- let max = -1;
14522
- for (const turn of transcript.turns) {
14523
- for (const item of turn.items) {
14524
- if (typeof item.sequence === "number" && item.sequence > max) {
14525
- max = item.sequence;
14526
- }
14527
- }
14528
- }
14529
- return max + 1;
14530
- }
14531
- function transcriptItemRank(item) {
14532
- return item.type === "userMessage" ? 0 : 1;
14533
- }
14534
- function transcriptItemSignature(item) {
14535
- if (item.type === "userMessage") return `user:${item.content.trim()}`;
14536
- if (item.type === "agentMessage" && item.text.trim()) return `agent:${item.text.trim()}`;
14537
- if (item.type === "plan" && item.text.trim()) return `plan:${item.text.trim()}`;
14538
- if (item.type === "error" && item.message.trim()) return `error:${item.message.trim()}`;
14539
- return null;
14540
- }
14541
- function areEquivalentTranscriptItems(current, candidate) {
14542
- if (current.id === candidate.id) return true;
14543
- if (current.type !== candidate.type) return false;
14544
- const signature = transcriptItemSignature(current);
14545
- return signature !== null && signature === transcriptItemSignature(candidate);
14546
- }
14547
- function findEquivalentCodexAspTranscriptItemIndex(items, candidate) {
14548
- return items.findIndex((current) => areEquivalentTranscriptItems(current, candidate));
14549
- }
14550
- function sortTranscriptItems(items) {
14551
- return [...items].map((item, index) => ({ item, index })).sort((a, b) => {
14552
- const aSequence = a.item.sequence ?? Number.MAX_SAFE_INTEGER;
14553
- const bSequence = b.item.sequence ?? Number.MAX_SAFE_INTEGER;
14554
- return transcriptItemRank(a.item) - transcriptItemRank(b.item) || aSequence - bSequence || Date.parse(a.item.timestamp) - Date.parse(b.item.timestamp) || a.index - b.index;
14555
- }).map(({ item }) => item);
14556
- }
14557
- function dedupeTranscriptItems(items) {
14558
- const deduped = [];
14559
- for (const item of items) {
14560
- const existingIndex = findEquivalentCodexAspTranscriptItemIndex(deduped, item);
14561
- if (existingIndex === -1) {
14562
- deduped.push(item);
14563
- continue;
14564
- }
14565
- deduped[existingIndex] = mergeCodexAspTranscriptItem(deduped[existingIndex], item);
14566
- }
14567
- return deduped;
14568
- }
14569
- function normalizeCodexAspTranscriptSequences(transcript) {
14570
- const turns = [...transcript.turns].map((turn, index) => ({ turn, index })).sort((a, b) => Date.parse(a.turn.startedAt) - Date.parse(b.turn.startedAt) || a.index - b.index).map(({ turn }) => ({
14571
- ...turn,
14572
- items: sortTranscriptItems(dedupeTranscriptItems(turn.items))
14573
- }));
14574
- return renumberCodexAspTranscriptItems({
14575
- ...transcript,
14576
- turns
14577
- });
14578
- }
14579
- function mergeCodexAspTranscriptItem(current, candidate) {
14580
- if (current.type === "agentMessage" && candidate.type === "agentMessage" && candidate.text.length === 0) {
14581
- return {
14582
- ...current,
14583
- sequence: current.sequence ?? candidate.sequence
14584
- };
14585
- }
14586
- return {
14587
- ...current,
14588
- ...candidate,
14589
- id: current.id,
14590
- ...current.type === "userMessage" && candidate.type === "userMessage" && !candidate.images && current.images ? { images: current.images } : {},
14591
- timestamp: current.timestamp,
14592
- sequence: current.sequence ?? candidate.sequence
14593
- };
14594
- }
14595
- function mergeCodexAspTranscripts(primary, supplemental) {
14596
- if (!primary) return supplemental ? normalizeCodexAspTranscriptSequences(supplemental) : null;
14597
- if (!supplemental) return normalizeCodexAspTranscriptSequences(primary);
14598
- if (primary.threadId !== supplemental.threadId) return normalizeCodexAspTranscriptSequences(supplemental);
14599
- let sequence = nextTranscriptSequence(primary);
14600
- const turns = primary.turns.map((turn) => ({
14601
- ...turn,
14602
- items: [...turn.items]
14603
- }));
14604
- for (const supplementalTurn of supplemental.turns) {
14605
- const existingTurn = turns.find((turn) => turn.id === supplementalTurn.id);
14606
- if (!existingTurn) {
14607
- turns.push({
14608
- ...supplementalTurn,
14609
- items: supplementalTurn.items.map((item) => typeof item.sequence === "number" ? item : { ...item, sequence: sequence++ })
14610
- });
14611
- continue;
14612
- }
14613
- existingTurn.status = supplementalTurn.status;
14614
- existingTurn.completedAt = supplementalTurn.completedAt ?? existingTurn.completedAt;
14615
- existingTurn.startedAt = Date.parse(existingTurn.startedAt) <= Date.parse(supplementalTurn.startedAt) ? existingTurn.startedAt : supplementalTurn.startedAt;
14616
- for (const item of supplementalTurn.items) {
14617
- const existingIndex = findEquivalentCodexAspTranscriptItemIndex(existingTurn.items, item);
14618
- if (existingIndex === -1) {
14619
- existingTurn.items.push(
14620
- typeof item.sequence === "number" ? item : { ...item, sequence: sequence++ }
14621
- );
14622
- continue;
14623
- }
14624
- existingTurn.items[existingIndex] = mergeCodexAspTranscriptItem(existingTurn.items[existingIndex], item);
14625
- }
14626
- existingTurn.items = sortTranscriptItems(existingTurn.items);
14627
- }
14628
- return normalizeCodexAspTranscriptSequences({
14629
- threadId: primary.threadId,
14630
- updatedAt: latestIsoTimestamp(primary.updatedAt, supplemental.updatedAt),
14631
- turns: turns.sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt))
14632
- });
14565
+ return { threadId, updatedAt, turns };
14633
14566
  }
14634
14567
  async function buildThreadStartParams(workingDirectory, request, developerInstructions, serviceTier, relayParentChatId) {
14635
14568
  const additionalDirectories = await getAgentAdditionalDirectories();
@@ -14716,96 +14649,42 @@ async function buildTurnStartParams(threadId, request, serviceTier) {
14716
14649
  };
14717
14650
  }
14718
14651
 
14719
- // src/managers/codex-asp/transcript-update-coalescer.ts
14720
- var TRANSCRIPT_UPDATE_COALESCE_MS = 1e3;
14721
- var TranscriptUpdateCoalescer = class {
14722
- constructor(flush, intervalMs = TRANSCRIPT_UPDATE_COALESCE_MS) {
14652
+ // src/managers/codex-asp/item-update-coalescer.ts
14653
+ var ITEM_UPDATE_COALESCE_MS = 250;
14654
+ var ItemUpdateCoalescer = class {
14655
+ constructor(flush, intervalMs = ITEM_UPDATE_COALESCE_MS) {
14723
14656
  this.flush = flush;
14724
14657
  this.intervalMs = intervalMs;
14725
14658
  }
14726
14659
  flush;
14727
14660
  intervalMs;
14728
- timer = null;
14729
- pendingThreadId = null;
14730
- lastFlushAt = 0;
14731
- schedule(threadId, options = {}) {
14732
- this.pendingThreadId = threadId;
14733
- if (options.immediate) {
14734
- this.flushNow(threadId);
14735
- return;
14736
- }
14737
- const now = Date.now();
14738
- const elapsed = now - this.lastFlushAt;
14739
- if (elapsed >= this.intervalMs) {
14740
- this.flushNow(threadId);
14741
- return;
14742
- }
14743
- if (this.timer) {
14661
+ timers = /* @__PURE__ */ new Map();
14662
+ lastFlushAt = /* @__PURE__ */ new Map();
14663
+ schedule(key, options = {}) {
14664
+ if (this.timers.has(key)) return;
14665
+ const elapsed = Date.now() - (this.lastFlushAt.get(key) ?? 0);
14666
+ if (options.immediate || elapsed >= this.intervalMs) {
14667
+ this.flushNow(key);
14744
14668
  return;
14745
14669
  }
14746
- this.timer = setTimeout(() => {
14747
- const pendingThreadId = this.pendingThreadId;
14748
- if (pendingThreadId) {
14749
- this.flushNow(pendingThreadId);
14750
- }
14751
- }, this.intervalMs - elapsed);
14670
+ this.timers.set(key, setTimeout(() => this.flushNow(key), this.intervalMs - elapsed));
14752
14671
  }
14753
14672
  flushPending() {
14754
- const pendingThreadId = this.pendingThreadId;
14755
- if (pendingThreadId) {
14756
- this.flushNow(pendingThreadId);
14757
- return;
14758
- }
14759
- this.clearTimer();
14673
+ for (const key of [...this.timers.keys()]) this.flushNow(key);
14760
14674
  }
14761
14675
  dispose() {
14762
- this.pendingThreadId = null;
14763
- this.clearTimer();
14676
+ for (const timer of this.timers.values()) clearTimeout(timer);
14677
+ this.timers.clear();
14764
14678
  }
14765
- flushNow(threadId) {
14766
- this.clearTimer();
14767
- this.pendingThreadId = null;
14768
- this.lastFlushAt = Date.now();
14769
- this.flush(threadId);
14770
- }
14771
- clearTimer() {
14772
- if (!this.timer) return;
14773
- clearTimeout(this.timer);
14774
- this.timer = null;
14679
+ flushNow(key) {
14680
+ const timer = this.timers.get(key);
14681
+ if (timer) clearTimeout(timer);
14682
+ this.timers.delete(key);
14683
+ this.lastFlushAt.set(key, Date.now());
14684
+ this.flush(key);
14775
14685
  }
14776
14686
  };
14777
14687
 
14778
- // src/services/chat/history-paths.ts
14779
- import { homedir as homedir12 } from "os";
14780
- import { join as join17 } from "path";
14781
- var ENGINE_DIR2 = join17(homedir12(), ".replicas", "engine");
14782
- var CHATS_FILE = join17(ENGINE_DIR2, "chats.json");
14783
- var CLAUDE_HISTORY_DIR = join17(ENGINE_DIR2, "claude-histories");
14784
- var RELAY_HISTORY_DIR = join17(ENGINE_DIR2, "relay-histories");
14785
- var CODEX_HISTORY_DIR = join17(ENGINE_DIR2, "codex-histories");
14786
- var CURSOR_HISTORY_DIR = join17(ENGINE_DIR2, "cursor-histories");
14787
- var OPENCODE_HISTORY_DIR = join17(ENGINE_DIR2, "opencode-histories");
14788
- var PI_HISTORY_DIR = join17(ENGINE_DIR2, "pi-histories");
14789
- var DEEPSEEK_HISTORY_DIR = join17(ENGINE_DIR2, "deepseek-histories");
14790
- var FX_HISTORY_DIR = join17(ENGINE_DIR2, "fx-histories");
14791
- var GEMINI_HISTORY_DIR = join17(ENGINE_DIR2, "gemini-histories");
14792
- var KIMI_HISTORY_DIR = join17(ENGINE_DIR2, "kimi-histories");
14793
- var MUSE_HISTORY_DIR = join17(ENGINE_DIR2, "muse-histories");
14794
- var FORKS_DIR = join17(ENGINE_DIR2, "forks");
14795
- var HISTORY_DIR_BY_PROVIDER = {
14796
- claude: CLAUDE_HISTORY_DIR,
14797
- relay: RELAY_HISTORY_DIR,
14798
- codex: CODEX_HISTORY_DIR,
14799
- cursor: CURSOR_HISTORY_DIR,
14800
- deepseek: DEEPSEEK_HISTORY_DIR,
14801
- fx: FX_HISTORY_DIR,
14802
- gemini: GEMINI_HISTORY_DIR,
14803
- kimi: KIMI_HISTORY_DIR,
14804
- muse: MUSE_HISTORY_DIR,
14805
- opencode: OPENCODE_HISTORY_DIR,
14806
- pi: PI_HISTORY_DIR
14807
- };
14808
-
14809
14688
  // src/services/chat/errors.ts
14810
14689
  var ChatNotFoundError = class extends Error {
14811
14690
  constructor(chatId) {
@@ -14853,44 +14732,17 @@ function formatBlockedCommandReason(reason) {
14853
14732
  return reason?.includes("Blocked by organization policy") ? reason : `Blocked by organization policy: ${reason || "agents may not merge pull requests."}`;
14854
14733
  }
14855
14734
  async function readCodexAspThreadHistory(threadId) {
14856
- let entries;
14857
- try {
14858
- entries = await readdir5(CODEX_HISTORY_DIR, { withFileTypes: true });
14859
- } catch (error) {
14860
- if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
14861
- entries = [];
14862
- } else {
14863
- throw error;
14864
- }
14865
- }
14866
- for (const entry of entries) {
14867
- if (!entry.isFile() || !entry.name.endsWith(".jsonl") || entry.name.endsWith(".pages.jsonl")) continue;
14868
- const history = await new CodexHistoryFile(join18(CODEX_HISTORY_DIR, entry.name)).load();
14869
- const transcript = history.transcriptsByThreadId.get(threadId);
14870
- if (transcript) {
14871
- return {
14872
- thread_id: threadId,
14873
- events: [],
14874
- codexAspTranscript: transcript,
14875
- goal: null
14876
- };
14877
- }
14878
- }
14735
+ let thread;
14879
14736
  try {
14880
14737
  const host = await getCodexAspHost();
14881
- const response = await host.client.request(
14738
+ ({ thread } = await host.client.request(
14882
14739
  THREAD_READ_METHOD,
14883
14740
  { threadId, includeTurns: true }
14884
- );
14885
- return {
14886
- thread_id: threadId,
14887
- events: [],
14888
- codexAspTranscript: threadToAspTranscript(response.thread),
14889
- goal: null
14890
- };
14741
+ ));
14891
14742
  } catch {
14743
+ throw new CodexThreadNotFoundError(threadId);
14892
14744
  }
14893
- throw new CodexThreadNotFoundError(threadId);
14745
+ return { thread_id: threadId, events: codexTranscriptToEvents(threadToAspTranscript(thread)), goal: null };
14894
14746
  }
14895
14747
  function skillToSlashCommand(skill) {
14896
14748
  if (!skill.enabled) return null;
@@ -14911,19 +14763,18 @@ function isCompactCommand(message) {
14911
14763
  var CodexAspManager = class extends CodingAgentManager {
14912
14764
  currentThreadId = null;
14913
14765
  activeTurnId = null;
14766
+ lastTurnFailure = null;
14914
14767
  attachedClient = null;
14915
14768
  attachedModel = null;
14916
14769
  attachedDeveloperInstructions = null;
14917
14770
  historyFile;
14918
- codexAspTranscript = null;
14919
- lastEmittedTranscript = null;
14920
- dirtyTranscriptTurns = /* @__PURE__ */ new Map();
14921
- codexAspSequence = 0;
14771
+ liveTurn = null;
14922
14772
  quotaStatus = new CodexQuotaStatusTracker();
14923
14773
  currentGoal = null;
14924
14774
  goalHistoryKnown = false;
14925
- transcriptUpdateCoalescer = new TranscriptUpdateCoalescer((threadId) => {
14926
- this.flushTranscriptUpdated(threadId);
14775
+ itemUpdateCoalescer = new ItemUpdateCoalescer((itemId) => {
14776
+ const item = this.liveTurn?.items.get(itemId);
14777
+ if (this.liveTurn && item) this.onEvent(codexItemEvent(this.liveTurn.id, item, true));
14927
14778
  });
14928
14779
  skillRegistriesApplied = false;
14929
14780
  modelServiceTierCache = null;
@@ -14946,16 +14797,13 @@ var CodexAspManager = class extends CodingAgentManager {
14946
14797
  this.initializeManager(this.processMessageInternal.bind(this));
14947
14798
  }
14948
14799
  async initialize() {
14949
- const replayed = await this.historyFile?.load();
14950
- if (replayed?.transcript && (!this.initialSessionId || replayed.transcript.threadId === this.initialSessionId)) {
14951
- this.mergeTranscriptSnapshot(replayed.transcript);
14952
- }
14953
- const goalEvent = replayed?.events.findLast((event) => event.type === CHAT_GOAL_EVENT_TYPE);
14800
+ await this.historyFile?.ensureItemEventFormat();
14801
+ this.currentThreadId = this.initialSessionId;
14802
+ const goalEvent = await this.historyFile?.findLastEventOfType(CHAT_GOAL_EVENT_TYPE);
14954
14803
  if (goalEvent) {
14955
14804
  this.currentGoal = coerceChatGoalPayload(goalEvent.payload);
14956
14805
  this.goalHistoryKnown = true;
14957
14806
  }
14958
- this.currentThreadId = this.initialSessionId ?? replayed?.transcript?.threadId ?? null;
14959
14807
  }
14960
14808
  getHistorySink() {
14961
14809
  return {
@@ -15046,61 +14894,25 @@ var CodexAspManager = class extends CodingAgentManager {
15046
14894
  }
15047
14895
  async getHistory(page2 = {}) {
15048
14896
  await this.initialized;
15049
- this.transcriptUpdateCoalescer.flushPending();
15050
14897
  await this.historyFile?.flush();
15051
- const transcript = await this.ensureTranscriptHistory();
15052
- const events = await this.historyFile?.loadEventsPage(page2) ?? { events: [], beforeCursor: null };
15053
- if (page2.limit === void 0) {
15054
- return {
15055
- thread_id: this.currentThreadId,
15056
- events: events.events,
15057
- codexAspTranscript: transcript,
15058
- goal: this.currentGoal
15059
- };
15060
- }
15061
- if (!this.currentThreadId) return { thread_id: null, ...events, goal: this.currentGoal };
15062
- const cursor = parseChatHistoryCursor(page2.beforeCursor);
15063
- if (page2.beforeCursor !== void 0 && cursor?.mode !== "range") throw new RangeError("Invalid chat history cursor");
15064
- if (page2.beforeCursor === void 0 && !this.goalHistoryKnown) {
14898
+ const latestPage = isLatestChatHistoryPage(page2);
14899
+ if (latestPage && !this.goalHistoryKnown && this.currentThreadId) {
15065
14900
  try {
15066
14901
  await this.refreshThreadGoal(await getCodexAspHost(), this.currentThreadId);
15067
14902
  } catch (error) {
15068
14903
  console.warn("[CodexAspManager] Failed to connect while refreshing thread goal:", error);
15069
14904
  }
15070
14905
  }
15071
- let transcriptPage = null;
15072
- let turnsBefore = null;
15073
- if (cursor?.turns !== null && transcript) {
15074
- const before = cursor?.turns === void 0 ? transcript.turns.length : /^\d+$/.test(cursor.turns) ? Number(cursor.turns) : Number.NaN;
15075
- if (!Number.isSafeInteger(before) || before < 0) throw new RangeError("Invalid chat history cursor");
15076
- const { startIndex, endIndex } = getChatHistoryPageWindow(transcript.turns.length, page2.limit, before);
15077
- transcriptPage = {
15078
- ...transcript,
15079
- turns: transcript.turns.slice(startIndex, endIndex)
15080
- };
15081
- turnsBefore = startIndex > 0 ? String(startIndex) : null;
15082
- }
15083
- const eventCursor = parseChatHistoryCursor(events.beforeCursor ?? void 0);
14906
+ const history = await this.historyFile?.loadEventsPage(page2) ?? { events: [] };
14907
+ const liveTurn = latestPage ? this.liveTurn : null;
14908
+ const liveEvents = liveTurn ? [...liveTurn.items.values()].map((item) => codexItemEvent(liveTurn.id, item, true)) : [];
15084
14909
  return {
15085
14910
  thread_id: this.currentThreadId,
15086
- ...events,
15087
- codexAspTranscript: transcriptPage,
15088
- goal: this.currentGoal,
15089
- beforeCursor: createChatHistoryCursor({
15090
- events: eventCursor?.events ?? null,
15091
- turns: turnsBefore
15092
- })
14911
+ ...history,
14912
+ events: [...history.events, ...liveEvents],
14913
+ goal: this.currentGoal
15093
14914
  };
15094
14915
  }
15095
- async ensureTranscriptHistory() {
15096
- if (this.codexAspTranscript || !this.currentThreadId) return this.codexAspTranscript;
15097
- const host = await getCodexAspHost();
15098
- const response = await host.client.request(
15099
- THREAD_READ_METHOD,
15100
- { threadId: this.currentThreadId, includeTurns: true }
15101
- );
15102
- return this.mergeTranscriptSnapshot(threadToAspTranscript(response.thread));
15103
- }
15104
14916
  getGoal() {
15105
14917
  return this.currentGoal;
15106
14918
  }
@@ -15257,16 +15069,16 @@ var CodexAspManager = class extends CodingAgentManager {
15257
15069
  }
15258
15070
  }
15259
15071
  const message = terminalError instanceof Error ? terminalError.message : String(terminalError);
15260
- const transcriptError = this.codexAspTranscript?.turns.find((turn) => turn.id === this.activeTurnId)?.items.find((item) => item.type === "error" && item.message === message);
15072
+ const failedTurnId = this.lastTurnFailure?.message === message ? this.lastTurnFailure.turnId : null;
15261
15073
  const event = this.recordCodexHistoryEvent("codex-asp-error", {
15262
15074
  message,
15263
- ...transcriptError ? { [CODEX_ASP_ITEM_ID_PAYLOAD_KEY]: transcriptError.id } : {}
15075
+ ...failedTurnId ? { [CODEX_ASP_ITEM_ID_PAYLOAD_KEY]: failedTurnId } : {}
15264
15076
  });
15265
15077
  this.onEvent(event);
15266
15078
  throw terminalError;
15267
15079
  } finally {
15268
- this.transcriptUpdateCoalescer.flushPending();
15269
- this.transcriptUpdateCoalescer.dispose();
15080
+ this.itemUpdateCoalescer.flushPending();
15081
+ this.itemUpdateCoalescer.dispose();
15270
15082
  this.activeTurnId = null;
15271
15083
  await removeTempImageFiles(this.steeredTempImagePaths.splice(0));
15272
15084
  await this.historyFile?.flush();
@@ -15339,7 +15151,6 @@ var CodexAspManager = class extends CodingAgentManager {
15339
15151
  this.attachedClient = host.client;
15340
15152
  this.attachedModel = requestedModel;
15341
15153
  this.attachedDeveloperInstructions = developerInstructions ?? null;
15342
- this.seedHistoryFromThread(response.thread);
15343
15154
  await this.onSaveSessionId(this.currentThreadId);
15344
15155
  }
15345
15156
  return this.currentThreadId ?? existingThreadId;
@@ -15443,7 +15254,6 @@ var CodexAspManager = class extends CodingAgentManager {
15443
15254
  const observedTurnIds = /* @__PURE__ */ new Set();
15444
15255
  let lastCompletedTurn = null;
15445
15256
  let completedResolved = false;
15446
- let userMessageAttached = false;
15447
15257
  let goalContinuationTimer = null;
15448
15258
  const completedItems = [];
15449
15259
  const agentMessageDeltas = /* @__PURE__ */ new Map();
@@ -15472,11 +15282,6 @@ var CodexAspManager = class extends CodingAgentManager {
15472
15282
  resolveCompleted(lastCompletedTurn);
15473
15283
  }, GOAL_TURN_CONTINUATION_GRACE_MS);
15474
15284
  };
15475
- const attachUserMessage = (turn) => {
15476
- if (userMessageAttached) return;
15477
- userMessageAttached = true;
15478
- this.ensureTranscriptTurnUserMessage(threadId, turn, request);
15479
- };
15480
15285
  const handlers = {
15481
15286
  [ACCOUNT_RATE_LIMITS_UPDATED_METHOD]: (notification) => {
15482
15287
  this.handleRateLimits(notification.params.rateLimits);
@@ -15518,14 +15323,11 @@ var CodexAspManager = class extends CodingAgentManager {
15518
15323
  lastCompletedTurn = null;
15519
15324
  completedItems.length = 0;
15520
15325
  agentMessageDeltas.clear();
15521
- attachUserMessage(notification.params.turn);
15522
- this.mergeTranscriptTurn(notification.params.threadId, notification.params.turn);
15523
- this.emitTranscriptUpdated(notification.params.threadId, { immediate: true });
15326
+ this.startTurn(notification.params.turn);
15524
15327
  linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
15525
15328
  },
15526
15329
  [TURN_PLAN_UPDATED_METHOD]: (notification) => {
15527
15330
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
15528
- this.emitTranscriptUpdated(notification.params.threadId);
15529
15331
  linearForwarder.sendPlan(extractPlanFromCodexAspNotification(notification));
15530
15332
  },
15531
15333
  [ITEM_STARTED_METHOD]: (notification) => {
@@ -15533,15 +15335,10 @@ var CodexAspManager = class extends CodingAgentManager {
15533
15335
  if (notification.params.item.type === "contextCompaction") {
15534
15336
  this.setCompacting(true);
15535
15337
  }
15536
- this.upsertTranscriptItem(
15537
- notification.params.threadId,
15338
+ this.startItem(
15538
15339
  notification.params.turnId,
15539
- notification.params.item,
15540
- timestampFromMilliseconds(notification.params.startedAtMs),
15541
- "in_progress",
15542
- "started"
15340
+ itemToTranscriptItem(notification.params.item, timestampFromMilliseconds(notification.params.startedAtMs), "in_progress")
15543
15341
  );
15544
- this.emitTranscriptUpdated(notification.params.threadId, { immediate: true });
15545
15342
  linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
15546
15343
  },
15547
15344
  [ITEM_COMPLETED_METHOD]: (notification) => {
@@ -15551,15 +15348,14 @@ var CodexAspManager = class extends CodingAgentManager {
15551
15348
  this.setCompacting(false);
15552
15349
  }
15553
15350
  const itemFailed = notification.params.item.type === "commandExecution" && typeof notification.params.item.exitCode === "number" && notification.params.item.exitCode !== 0;
15554
- this.upsertTranscriptItem(
15555
- notification.params.threadId,
15351
+ this.completeItem(
15556
15352
  notification.params.turnId,
15557
- notification.params.item,
15558
- timestampFromMilliseconds(notification.params.completedAtMs),
15559
- itemFailed ? "failed" : "completed",
15560
- "completed"
15353
+ itemToTranscriptItem(
15354
+ notification.params.item,
15355
+ timestampFromMilliseconds(notification.params.completedAtMs),
15356
+ itemFailed ? "failed" : "completed"
15357
+ )
15561
15358
  );
15562
- this.emitTranscriptUpdated(notification.params.threadId, { immediate: true });
15563
15359
  linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
15564
15360
  const completedItem = notification.params.item;
15565
15361
  if (completedItem.type === "commandExecution" && completedItem.exitCode === 0 && shouldRefreshPullRequests("commandExecution", { command: completedItem.command }) || completedItem.type === "mcpToolCall" && completedItem.status === "completed" && shouldRefreshPullRequests(completedItem.tool, completedItem.arguments) || completedItem.type === "dynamicToolCall" && completedItem.success !== false && shouldRefreshPullRequests(completedItem.tool, completedItem.arguments)) {
@@ -15571,53 +15367,27 @@ var CodexAspManager = class extends CodingAgentManager {
15571
15367
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
15572
15368
  const currentText = agentMessageDeltas.get(notification.params.itemId) ?? "";
15573
15369
  agentMessageDeltas.set(notification.params.itemId, currentText + notification.params.delta);
15574
- this.appendAgentMessageDelta(
15575
- notification.params.threadId,
15576
- notification.params.turnId,
15577
- notification.params.itemId,
15578
- notification.params.delta
15579
- );
15580
- this.emitTranscriptUpdated(notification.params.threadId);
15370
+ this.appendAgentMessageDelta(notification.params.turnId, notification.params.itemId, notification.params.delta);
15581
15371
  },
15582
15372
  [REASONING_SUMMARY_TEXT_DELTA_METHOD]: (notification) => {
15583
15373
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
15584
- this.appendReasoningDelta(
15585
- notification.params.threadId,
15586
- notification.params.turnId,
15587
- notification.params.itemId,
15588
- notification.params.delta
15589
- );
15590
- this.emitTranscriptUpdated(notification.params.threadId);
15374
+ this.appendReasoningDelta(notification.params.turnId, notification.params.itemId, notification.params.delta);
15591
15375
  },
15592
15376
  [REASONING_TEXT_DELTA_METHOD]: (notification) => {
15593
15377
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
15594
- this.appendReasoningDelta(
15595
- notification.params.threadId,
15596
- notification.params.turnId,
15597
- notification.params.itemId,
15598
- notification.params.delta
15599
- );
15600
- this.emitTranscriptUpdated(notification.params.threadId);
15378
+ this.appendReasoningDelta(notification.params.turnId, notification.params.itemId, notification.params.delta);
15601
15379
  },
15602
15380
  [REASONING_SUMMARY_PART_ADDED_METHOD]: (notification) => {
15603
15381
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
15604
- this.appendReasoningDelta(
15605
- notification.params.threadId,
15606
- notification.params.turnId,
15607
- notification.params.itemId,
15608
- "\n\n"
15609
- );
15610
- this.emitTranscriptUpdated(notification.params.threadId);
15382
+ this.appendReasoningDelta(notification.params.turnId, notification.params.itemId, "\n\n");
15611
15383
  },
15612
15384
  [COMMAND_EXECUTION_OUTPUT_DELTA_METHOD]: (notification) => {
15613
15385
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
15614
- this.appendTranscriptOutput(notification.params.turnId, notification.params.itemId, notification.params.delta);
15615
- this.emitTranscriptUpdated(notification.params.threadId);
15386
+ this.appendOutputDelta(notification.params.turnId, notification.params.itemId, notification.params.delta);
15616
15387
  },
15617
15388
  [FILE_CHANGE_OUTPUT_DELTA_METHOD]: (notification) => {
15618
15389
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
15619
- this.appendTranscriptOutput(notification.params.turnId, notification.params.itemId, notification.params.delta);
15620
- this.emitTranscriptUpdated(notification.params.threadId);
15390
+ this.appendOutputDelta(notification.params.turnId, notification.params.itemId, notification.params.delta);
15621
15391
  },
15622
15392
  [TURN_COMPLETED_METHOD]: (notification) => {
15623
15393
  if (notification.params.threadId !== threadId) return;
@@ -15626,8 +15396,7 @@ var CodexAspManager = class extends CodingAgentManager {
15626
15396
  observedTurnId = notification.params.turn.id;
15627
15397
  this.onCodexTurnCompleted?.(threadId, notification.params.turn.id);
15628
15398
  const completedTurn = recoverCompletedTurn(notification.params.turn, completedItems, agentMessageDeltas);
15629
- this.mergeTranscriptTurn(notification.params.threadId, completedTurn);
15630
- this.emitTranscriptUpdated(notification.params.threadId, { immediate: true });
15399
+ this.completeTurn(completedTurn);
15631
15400
  lastCompletedTurn = completedTurn;
15632
15401
  resolveIfGoalIdle();
15633
15402
  if (options.waitForActiveGoal && this.currentGoal?.status === "active") {
@@ -15668,11 +15437,7 @@ var CodexAspManager = class extends CodingAgentManager {
15668
15437
  observedTurnIds.add(started.turn.id);
15669
15438
  this.activeTurnId = started.turn.id;
15670
15439
  if (!alreadyObserved) this.onCodexTurnStarted?.(threadId, started.turn.id);
15671
- if (!alreadyObserved) {
15672
- attachUserMessage(started.turn);
15673
- this.mergeTranscriptTurn(threadId, started.turn);
15674
- this.emitTranscriptUpdated(threadId, { immediate: true });
15675
- }
15440
+ if (!alreadyObserved) this.startTurn(started.turn);
15676
15441
  }
15677
15442
  const turn = await Promise.race([completed, disposed]);
15678
15443
  linearForwarder.flushThoughtAsResponse();
@@ -15684,7 +15449,7 @@ var CodexAspManager = class extends CodingAgentManager {
15684
15449
  if (goalContinuationTimer) {
15685
15450
  clearTimeout(goalContinuationTimer);
15686
15451
  }
15687
- this.transcriptUpdateCoalescer.flushPending();
15452
+ this.itemUpdateCoalescer.flushPending();
15688
15453
  await removeTempImageFiles(tempImagePaths);
15689
15454
  if (host.client.isDisposed) {
15690
15455
  this.attachedClient = null;
@@ -15711,7 +15476,6 @@ var CodexAspManager = class extends CodingAgentManager {
15711
15476
  if (!result.allowed) {
15712
15477
  console.warn(`[CodexAspManager] blocked command approval: ${result.reason ?? "unknown reason"}`);
15713
15478
  this.recordBlockedCommand(
15714
- serverRequest.params.threadId,
15715
15479
  serverRequest.params.turnId,
15716
15480
  serverRequest.params.itemId,
15717
15481
  serverRequest.params.command ?? "",
@@ -15752,7 +15516,6 @@ var CodexAspManager = class extends CodingAgentManager {
15752
15516
  if (!result.allowed) {
15753
15517
  console.warn(`[CodexAspManager] blocked legacy command approval: ${result.reason ?? "unknown reason"}`);
15754
15518
  this.recordBlockedCommand(
15755
- this.currentThreadId ?? serverRequest.params.conversationId,
15756
15519
  this.activeTurnId ?? `legacy-${requestId}`,
15757
15520
  serverRequest.params.callId,
15758
15521
  command,
@@ -15842,12 +15605,7 @@ var CodexAspManager = class extends CodingAgentManager {
15842
15605
  console.warn("[CodexAspManager] Failed to inject blocked command reason:", error);
15843
15606
  }
15844
15607
  }
15845
- nextTranscriptSequence() {
15846
- return this.codexAspSequence++;
15847
- }
15848
- recordBlockedCommand(threadId, turnId, itemId, command, reason, startedAtMs, cwd, commandActions) {
15849
- const output = formatBlockedCommandReason(reason);
15850
- const timestamp = timestampFromMilliseconds(startedAtMs);
15608
+ recordBlockedCommand(turnId, itemId, command, reason, startedAtMs, cwd, commandActions) {
15851
15609
  const blockedItem = {
15852
15610
  type: "commandExecution",
15853
15611
  id: itemId,
@@ -15856,231 +15614,80 @@ var CodexAspManager = class extends CodingAgentManager {
15856
15614
  processId: null,
15857
15615
  source: "agent",
15858
15616
  commandActions: commandActions ?? [{ type: "unknown", command }],
15859
- aggregatedOutput: output,
15617
+ aggregatedOutput: formatBlockedCommandReason(reason),
15860
15618
  exitCode: null,
15861
15619
  status: "declined",
15862
15620
  durationMs: null
15863
15621
  };
15864
- this.upsertTranscriptItem(threadId, turnId, blockedItem, timestamp, "failed", "completed");
15865
- this.emitTranscriptUpdated(threadId, { immediate: true });
15866
- }
15867
- syncTranscriptSequence(transcript) {
15868
- if (!transcript) return;
15869
- let next = 0;
15870
- for (const turn of transcript.turns) {
15871
- for (const item of turn.items) {
15872
- if (typeof item.sequence === "number" && item.sequence >= next) {
15873
- next = item.sequence + 1;
15874
- }
15875
- }
15876
- }
15877
- this.codexAspSequence = next;
15878
- }
15879
- mergeTranscriptSnapshot(snapshot) {
15880
- if (snapshot?.threadId && this.codexAspTranscript?.threadId !== snapshot.threadId) {
15881
- this.lastEmittedTranscript = null;
15882
- this.dirtyTranscriptTurns.clear();
15883
- }
15884
- this.codexAspTranscript = mergeCodexAspTranscripts(this.codexAspTranscript, snapshot);
15885
- for (const turn of this.codexAspTranscript?.turns ?? []) {
15886
- this.dirtyTranscriptTurns.set(turn.id, turn);
15887
- }
15888
- this.syncTranscriptSequence(this.codexAspTranscript);
15889
- return this.codexAspTranscript;
15890
- }
15891
- ensureTranscript(threadId) {
15892
- if (this.codexAspTranscript?.threadId === threadId) {
15893
- return this.codexAspTranscript;
15894
- }
15895
- this.lastEmittedTranscript = null;
15896
- this.dirtyTranscriptTurns.clear();
15897
- this.codexAspTranscript = {
15898
- threadId,
15899
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15900
- turns: []
15901
- };
15902
- this.codexAspSequence = 0;
15903
- return this.codexAspTranscript;
15904
- }
15905
- ensureTranscriptTurn(threadId, turnId, startedAt) {
15906
- const transcript = this.ensureTranscript(threadId);
15907
- let turn = transcript.turns.find((candidate) => candidate.id === turnId);
15908
- if (!turn) {
15909
- turn = {
15910
- id: turnId,
15911
- status: "inProgress",
15912
- startedAt,
15913
- completedAt: null,
15914
- items: []
15915
- };
15916
- transcript.turns.push(turn);
15917
- transcript.turns.sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt));
15918
- }
15919
- this.dirtyTranscriptTurns.set(turnId, turn);
15920
- if (Date.parse(startedAt) < Date.parse(turn.startedAt)) {
15921
- turn.startedAt = startedAt;
15922
- }
15923
- return turn;
15622
+ this.completeItem(turnId, itemToTranscriptItem(blockedItem, timestampFromMilliseconds(startedAtMs), "failed"));
15924
15623
  }
15925
- ensureTranscriptTurnUserMessage(threadId, turn, request) {
15926
- const timestamp = timestampFromSeconds(turn.startedAt);
15927
- const transcriptTurn = this.ensureTranscriptTurn(threadId, turn.id, timestamp);
15928
- const images = imageContentToUserMessageImages(request.images);
15929
- const userMessage = {
15930
- type: "userMessage",
15931
- id: request.messageId ?? `user-${turn.id}`,
15932
- content: request.message,
15933
- ...images ? { images } : {},
15934
- timestamp,
15935
- sequence: this.nextTranscriptSequence()
15936
- };
15937
- const existingIndex = findEquivalentCodexAspTranscriptItemIndex(transcriptTurn.items, userMessage);
15938
- if (existingIndex === -1) {
15939
- transcriptTurn.items.push(userMessage);
15940
- } else {
15941
- transcriptTurn.items[existingIndex] = mergeCodexAspTranscriptItem(
15942
- transcriptTurn.items[existingIndex],
15943
- userMessage
15944
- );
15624
+ ensureLiveTurn(turnId) {
15625
+ if (this.liveTurn?.id !== turnId) {
15626
+ this.liveTurn = { id: turnId, items: /* @__PURE__ */ new Map(), persistedItemIds: /* @__PURE__ */ new Set() };
15945
15627
  }
15628
+ return this.liveTurn;
15946
15629
  }
15947
- mergeTranscriptTurn(threadId, turn) {
15630
+ startTurn(turn) {
15631
+ this.ensureLiveTurn(turn.id);
15948
15632
  const startedAt = timestampFromSeconds(turn.startedAt);
15949
- const completedAt = turn.completedAt === null ? null : timestampFromSeconds(turn.completedAt);
15950
- const itemTimestamp = completedAt ?? startedAt;
15951
- const transcriptTurn = this.ensureTranscriptTurn(threadId, turn.id, startedAt);
15952
- transcriptTurn.status = turn.status;
15953
- transcriptTurn.completedAt = completedAt;
15954
- const itemStatus = turn.status === "failed" ? "failed" : turn.status === "completed" ? "completed" : "in_progress";
15955
15633
  for (const item of turn.items) {
15956
- this.upsertTranscriptItem(
15957
- threadId,
15958
- turn.id,
15959
- item,
15960
- item.type === "userMessage" ? startedAt : itemTimestamp,
15961
- itemStatus,
15962
- "completed"
15963
- );
15964
- }
15965
- if (turn.status === "completed" || turn.status === "failed") {
15966
- transcriptTurn.items = transcriptTurn.items.map((item) => item.type === "reasoning" && item.status === "in_progress" ? { ...item, status: "completed" } : item);
15967
- }
15968
- if (turn.error) {
15969
- const existingIndex = transcriptTurn.items.findIndex((item) => item.id === `${turn.id}-error`);
15970
- const errorItem = {
15971
- type: "error",
15972
- id: `${turn.id}-error`,
15973
- message: formatTurnFailure(turn),
15974
- timestamp: itemTimestamp,
15975
- sequence: existingIndex === -1 ? this.nextTranscriptSequence() : transcriptTurn.items[existingIndex].sequence
15976
- };
15977
- if (existingIndex === -1) {
15978
- transcriptTurn.items.push(errorItem);
15979
- } else {
15980
- transcriptTurn.items[existingIndex] = errorItem;
15981
- }
15982
- }
15983
- if (this.codexAspTranscript) {
15984
- this.codexAspTranscript.updatedAt = itemTimestamp;
15985
- }
15986
- }
15987
- upsertTranscriptItem(threadId, turnId, item, timestamp, status, lifecycle) {
15988
- const turn = this.ensureTranscriptTurn(threadId, turnId, timestamp);
15989
- const candidate = itemToTranscriptItem(item, timestamp, status);
15990
- if (!candidate) return;
15991
- const existingIndex = findEquivalentCodexAspTranscriptItemIndex(turn.items, candidate);
15992
- const existing = existingIndex === -1 ? null : turn.items[existingIndex];
15993
- const sequence = existing?.sequence ?? this.nextTranscriptSequence();
15994
- const itemTimestamp = existing && lifecycle === "completed" ? existing.timestamp : timestamp;
15995
- const transcriptItem = itemToTranscriptItem(item, itemTimestamp, status);
15996
- if (!transcriptItem) return;
15997
- const sequencedItem = { ...transcriptItem, sequence };
15998
- if (existingIndex === -1) {
15999
- turn.items.push(sequencedItem);
16000
- } else {
16001
- const existingItem = turn.items[existingIndex];
16002
- const mergedItem = mergeCodexAspTranscriptItem(existingItem, {
16003
- ...sequencedItem,
16004
- timestamp: itemTimestamp,
16005
- sequence
16006
- });
16007
- turn.items[existingIndex] = existingItem.type === "agentMessage" && mergedItem.type === "agentMessage" && lifecycle === "started" && mergedItem.text.length === 0 ? { ...mergedItem, text: existingItem.text } : mergedItem;
16008
- }
16009
- if (this.codexAspTranscript) {
16010
- this.codexAspTranscript.updatedAt = timestamp;
16011
- }
16012
- }
16013
- appendAgentMessageDelta(threadId, turnId, itemId, delta) {
16014
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
16015
- const turn = this.ensureTranscriptTurn(threadId, turnId, timestamp);
16016
- const existingIndex = turn.items.findIndex((item) => item.id === itemId);
16017
- if (existingIndex === -1) {
16018
- turn.items.push({
16019
- type: "agentMessage",
16020
- id: itemId,
16021
- text: delta,
16022
- timestamp,
16023
- sequence: this.nextTranscriptSequence()
16024
- });
16025
- } else {
16026
- const existing = turn.items[existingIndex];
16027
- if (existing.type === "agentMessage") {
16028
- turn.items[existingIndex] = {
16029
- ...existing,
16030
- text: `${existing.text}${delta}`
16031
- };
16032
- }
16033
- }
16034
- if (this.codexAspTranscript) {
16035
- this.codexAspTranscript.updatedAt = timestamp;
16036
- }
15634
+ this.startItem(turn.id, itemToTranscriptItem(item, startedAt, "in_progress"));
15635
+ }
15636
+ }
15637
+ startItem(turnId, item) {
15638
+ if (!item) return;
15639
+ const turn = this.ensureLiveTurn(turnId);
15640
+ const existing = turn.items.get(item.id);
15641
+ const started = existing?.type === "agentMessage" && item.type === "agentMessage" && !item.text ? { ...item, text: existing.text } : item;
15642
+ turn.items.set(item.id, { ...started, timestamp: existing?.timestamp ?? item.timestamp });
15643
+ this.itemUpdateCoalescer.schedule(item.id, { immediate: true });
15644
+ }
15645
+ completeItem(turnId, item) {
15646
+ if (!item) return;
15647
+ const turn = this.ensureLiveTurn(turnId);
15648
+ const live = turn.items.get(item.id);
15649
+ turn.items.delete(item.id);
15650
+ turn.persistedItemIds.add(item.id);
15651
+ const event = codexItemEvent(turnId, live ? { ...item, timestamp: live.timestamp } : item);
15652
+ this.historyFile?.append(event);
15653
+ this.onEvent(event);
16037
15654
  }
16038
- appendReasoningDelta(threadId, turnId, itemId, delta) {
16039
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
16040
- const turn = this.ensureTranscriptTurn(threadId, turnId, timestamp);
16041
- const existingIndex = turn.items.findIndex((item) => item.id === itemId);
16042
- if (existingIndex === -1) {
16043
- if (!delta.trim()) return;
16044
- turn.items.push({
16045
- type: "reasoning",
16046
- id: itemId,
16047
- text: delta,
16048
- timestamp,
16049
- status: "in_progress",
16050
- sequence: this.nextTranscriptSequence()
16051
- });
16052
- } else {
16053
- const existing = turn.items[existingIndex];
16054
- if (existing.type === "reasoning") {
16055
- if (!delta.trim() && !existing.text.trim()) return;
16056
- turn.items[existingIndex] = {
16057
- ...existing,
16058
- text: `${existing.text}${delta}`
16059
- };
16060
- }
15655
+ completeTurn(turn) {
15656
+ const live = this.ensureLiveTurn(turn.id);
15657
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
15658
+ const status = turn.status === "failed" ? "failed" : "completed";
15659
+ for (const item of turn.items) {
15660
+ if (!live.persistedItemIds.has(item.id)) this.completeItem(turn.id, itemToTranscriptItem(item, completedAt, status));
16061
15661
  }
16062
- if (this.codexAspTranscript) {
16063
- this.codexAspTranscript.updatedAt = timestamp;
15662
+ for (const item of live.items.values()) {
15663
+ this.completeItem(turn.id, item.type === "reasoning" ? { ...item, status } : item);
16064
15664
  }
16065
- }
16066
- appendTranscriptOutput(turnId, itemId, delta) {
16067
- if (!this.codexAspTranscript) return;
16068
- const turn = this.codexAspTranscript.turns.find((candidate) => candidate.id === turnId);
16069
- if (!turn) return;
16070
- const itemIndex = turn.items.findIndex((item2) => item2.id === itemId);
16071
- if (itemIndex === -1) return;
16072
- const item = turn.items[itemIndex];
16073
- if (item.type !== "commandExecution" && item.type !== "fileChange") return;
16074
- this.dirtyTranscriptTurns.set(turnId, turn);
16075
- turn.items[itemIndex] = {
16076
- ...item,
16077
- output: appendCodexAspTranscriptOutput(item.output, delta),
16078
- status: "in_progress"
16079
- };
16080
- this.codexAspTranscript.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
16081
- }
16082
- seedHistoryFromThread(thread) {
16083
- this.mergeTranscriptSnapshot(threadToAspTranscript(thread));
15665
+ const failure = turn.error ? formatTurnFailure(turn) : void 0;
15666
+ this.lastTurnFailure = failure === void 0 ? null : { turnId: turn.id, message: failure };
15667
+ const event = codexTurnEvent(turn.id, turn.status, completedAt, failure);
15668
+ this.historyFile?.append(event);
15669
+ this.onEvent(event);
15670
+ this.liveTurn = null;
15671
+ }
15672
+ appendAgentMessageDelta(turnId, itemId, delta) {
15673
+ const turn = this.ensureLiveTurn(turnId);
15674
+ const existing = turn.items.get(itemId);
15675
+ turn.items.set(itemId, existing?.type === "agentMessage" ? { ...existing, text: `${existing.text}${delta}` } : { type: "agentMessage", id: itemId, text: delta, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
15676
+ this.itemUpdateCoalescer.schedule(itemId);
15677
+ }
15678
+ appendReasoningDelta(turnId, itemId, delta) {
15679
+ const turn = this.ensureLiveTurn(turnId);
15680
+ const existing = turn.items.get(itemId);
15681
+ if (!existing && !delta.trim()) return;
15682
+ turn.items.set(itemId, existing?.type === "reasoning" ? { ...existing, text: `${existing.text}${delta}` } : { type: "reasoning", id: itemId, text: delta, timestamp: (/* @__PURE__ */ new Date()).toISOString(), status: "in_progress" });
15683
+ this.itemUpdateCoalescer.schedule(itemId);
15684
+ }
15685
+ appendOutputDelta(turnId, itemId, delta) {
15686
+ const turn = this.ensureLiveTurn(turnId);
15687
+ const item = turn.items.get(itemId);
15688
+ if (item?.type !== "commandExecution" && item?.type !== "fileChange") return;
15689
+ turn.items.set(itemId, { ...item, output: appendCodexAspTranscriptOutput(item.output, delta), status: "in_progress" });
15690
+ this.itemUpdateCoalescer.schedule(itemId);
16084
15691
  }
16085
15692
  async refreshThreadGoal(host, threadId) {
16086
15693
  try {
@@ -16119,109 +15726,6 @@ var CodexAspManager = class extends CodingAgentManager {
16119
15726
  trackHistoryEvent(event) {
16120
15727
  this.historyFile?.append(event);
16121
15728
  }
16122
- emitTranscriptUpdated(threadId, options = {}) {
16123
- this.transcriptUpdateCoalescer.schedule(threadId, options);
16124
- }
16125
- buildTranscriptItemDelta(previous, current) {
16126
- if (previous === current) return null;
16127
- if (!previous || previous.type !== current.type) {
16128
- return { id: current.id, item: structuredClone(current) };
16129
- }
16130
- if (current.type === "agentMessage" && previous.type === "agentMessage" && current.text.startsWith(previous.text) && current.text.length > previous.text.length) {
16131
- return {
16132
- id: current.id,
16133
- appendText: current.text.slice(previous.text.length)
16134
- };
16135
- }
16136
- if ((current.type === "commandExecution" || current.type === "fileChange") && previous.type === current.type && current.output && current.output.startsWith(previous.output ?? "") && current.output.length > (previous.output ?? "").length) {
16137
- const { output: _output, ...itemWithoutOutput } = current;
16138
- const item = {
16139
- ...itemWithoutOutput,
16140
- output: previous.output
16141
- };
16142
- return {
16143
- id: current.id,
16144
- item,
16145
- appendOutput: current.output.slice((previous.output ?? "").length)
16146
- };
16147
- }
16148
- if (JSON.stringify(previous) === JSON.stringify(current)) {
16149
- return null;
16150
- }
16151
- return { id: current.id, item: structuredClone(current) };
16152
- }
16153
- buildTranscriptDelta(threadId, current) {
16154
- const previous = this.lastEmittedTranscript;
16155
- const updatedAt = current?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
16156
- if (!current || !previous || previous.threadId !== current.threadId) {
16157
- return {
16158
- threadId,
16159
- updatedAt,
16160
- reset: current ? structuredClone(current) : null,
16161
- turns: []
16162
- };
16163
- }
16164
- const turnDeltas = [];
16165
- for (const currentTurn of this.dirtyTranscriptTurns.values()) {
16166
- const previousTurn = previous.turns.get(currentTurn.id);
16167
- if (!previousTurn) {
16168
- turnDeltas.push({
16169
- id: currentTurn.id,
16170
- status: currentTurn.status,
16171
- startedAt: currentTurn.startedAt,
16172
- completedAt: currentTurn.completedAt,
16173
- items: currentTurn.items.map((item) => ({ id: item.id, item: structuredClone(item) }))
16174
- });
16175
- continue;
16176
- }
16177
- const previousItems = new Map(previousTurn.items.map((item) => [item.id, item]));
16178
- const itemDeltas = currentTurn.items.map((item) => this.buildTranscriptItemDelta(
16179
- previousItems.get(item.id),
16180
- item
16181
- )).filter((item) => item !== null);
16182
- if (itemDeltas.length > 0 || currentTurn.status !== previousTurn.status || currentTurn.startedAt !== previousTurn.startedAt || currentTurn.completedAt !== previousTurn.completedAt) {
16183
- turnDeltas.push({
16184
- id: currentTurn.id,
16185
- status: currentTurn.status,
16186
- startedAt: currentTurn.startedAt,
16187
- completedAt: currentTurn.completedAt,
16188
- items: itemDeltas
16189
- });
16190
- }
16191
- }
16192
- return {
16193
- threadId: current.threadId,
16194
- updatedAt,
16195
- turns: turnDeltas
16196
- };
16197
- }
16198
- flushTranscriptUpdated(threadId) {
16199
- const updatedAt = this.codexAspTranscript?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
16200
- const transcriptDelta = this.buildTranscriptDelta(threadId, this.codexAspTranscript);
16201
- const updatePayload = {
16202
- updatedAt,
16203
- threadId,
16204
- transcript: transcriptDelta.reset ?? null,
16205
- transcriptDelta
16206
- };
16207
- if (this.codexAspTranscript) {
16208
- const snapshot = this.lastEmittedTranscript ?? { threadId, turns: /* @__PURE__ */ new Map() };
16209
- for (const turn of this.dirtyTranscriptTurns.values()) {
16210
- snapshot.turns.set(turn.id, { ...turn, items: [...turn.items] });
16211
- }
16212
- this.lastEmittedTranscript = snapshot;
16213
- } else {
16214
- this.lastEmittedTranscript = null;
16215
- }
16216
- this.dirtyTranscriptTurns.clear();
16217
- const event = {
16218
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
16219
- type: CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE,
16220
- payload: updatePayload
16221
- };
16222
- this.historyFile?.append(event);
16223
- this.onEvent(event);
16224
- }
16225
15729
  handleRateLimits(rateLimits) {
16226
15730
  const snapshot = extractRateLimitsSnapshot(rateLimits);
16227
15731
  if (snapshot) {
@@ -16282,23 +15786,23 @@ var CodexAspManager = class extends CodingAgentManager {
16282
15786
  };
16283
15787
 
16284
15788
  // src/managers/cursor-manager.ts
16285
- import { mkdir as mkdir12, readFile as readFile13, readdir as readdir6 } from "fs/promises";
16286
- import { basename as basename2, dirname as dirname6, extname, join as join20 } from "path";
15789
+ import { mkdir as mkdir12, readFile as readFile12, readdir as readdir5 } from "fs/promises";
15790
+ import { basename as basename2, dirname as dirname6, extname, join as join18 } from "path";
16287
15791
  import { Agent as CursorAgent2 } from "@cursor/sdk";
16288
15792
 
16289
15793
  // src/services/native-command-protection-hooks.ts
16290
15794
  import { existsSync as existsSync8 } from "fs";
16291
- import { mkdir as mkdir11, readFile as readFile12, writeFile as writeFile6 } from "fs/promises";
16292
- import { basename, dirname as dirname5, join as join19 } from "path";
15795
+ import { mkdir as mkdir11, readFile as readFile11, writeFile as writeFile6 } from "fs/promises";
15796
+ import { basename, dirname as dirname5, join as join17 } from "path";
16293
15797
  import { fileURLToPath } from "url";
16294
15798
  var moduleDirectory = dirname5(fileURLToPath(import.meta.url));
16295
15799
  var hookPath = [
16296
- join19(moduleDirectory, "command-protection-hook.js"),
16297
- join19(moduleDirectory, "..", "command-protection-hook.ts")
15800
+ join17(moduleDirectory, "command-protection-hook.js"),
15801
+ join17(moduleDirectory, "..", "command-protection-hook.ts")
16298
15802
  ].find(existsSync8);
16299
15803
  var postToolHookPath = [
16300
- join19(moduleDirectory, "post-tool-pr-hook.js"),
16301
- join19(moduleDirectory, "..", "post-tool-pr-hook.ts")
15804
+ join17(moduleDirectory, "post-tool-pr-hook.js"),
15805
+ join17(moduleDirectory, "..", "post-tool-pr-hook.ts")
16302
15806
  ].find(existsSync8);
16303
15807
  function shellCommand(path7, provider) {
16304
15808
  if (!path7) throw new Error("Replicas hook executable is missing.");
@@ -16319,10 +15823,10 @@ function getPostToolPrHookPath() {
16319
15823
  return postToolHookPath;
16320
15824
  }
16321
15825
  async function ensureCursorCommandProtectionHook() {
16322
- const path7 = join19(ENGINE_ENV.HOME_DIR, ".cursor", "hooks.json");
15826
+ const path7 = join17(ENGINE_ENV.HOME_DIR, ".cursor", "hooks.json");
16323
15827
  let config = {};
16324
15828
  try {
16325
- const parsed = JSON.parse(await readFile12(path7, "utf8"));
15829
+ const parsed = JSON.parse(await readFile11(path7, "utf8"));
16326
15830
  if (!isRecord2(parsed)) throw new Error("Invalid Cursor hooks configuration.");
16327
15831
  config = parsed;
16328
15832
  } catch (error) {
@@ -16344,10 +15848,10 @@ async function ensureCursorCommandProtectionHook() {
16344
15848
  `, "utf8");
16345
15849
  }
16346
15850
  async function ensureGeminiCommandProtectionHook() {
16347
- const path7 = join19(ENGINE_ENV.HOME_DIR, ".gemini", "settings.json");
15851
+ const path7 = join17(ENGINE_ENV.HOME_DIR, ".gemini", "settings.json");
16348
15852
  let config = {};
16349
15853
  try {
16350
- const parsed = JSON.parse(await readFile12(path7, "utf8"));
15854
+ const parsed = JSON.parse(await readFile11(path7, "utf8"));
16351
15855
  if (!isRecord2(parsed)) throw new Error("Invalid Gemini hooks configuration.");
16352
15856
  config = parsed;
16353
15857
  } catch (error) {
@@ -16372,12 +15876,12 @@ async function ensureGeminiCommandProtectionHook() {
16372
15876
  `, "utf8");
16373
15877
  }
16374
15878
  async function ensureKimiCommandProtectionHook() {
16375
- const path7 = join19(ENGINE_ENV.HOME_DIR, ".kimi-code", "config.toml");
15879
+ const path7 = join17(ENGINE_ENV.HOME_DIR, ".kimi-code", "config.toml");
16376
15880
  const start = "# Replicas command protection hook";
16377
15881
  const end = "# End Replicas command protection hook";
16378
15882
  let config = "";
16379
15883
  try {
16380
- config = await readFile12(path7, "utf8");
15884
+ config = await readFile11(path7, "utf8");
16381
15885
  } catch (error) {
16382
15886
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) throw error;
16383
15887
  }
@@ -16464,7 +15968,7 @@ function extractCursorCommandDescription(content) {
16464
15968
  async function listCursorCommandsInDirectory(directory) {
16465
15969
  let entries;
16466
15970
  try {
16467
- entries = await readdir6(directory, { withFileTypes: true });
15971
+ entries = await readdir5(directory, { withFileTypes: true });
16468
15972
  } catch (error) {
16469
15973
  if (isRecord2(error) && error.code === "ENOENT") return [];
16470
15974
  console.warn("[CursorManager] Failed to read slash command directory:", error);
@@ -16474,7 +15978,7 @@ async function listCursorCommandsInDirectory(directory) {
16474
15978
  const name = basename2(entry.name, ".md");
16475
15979
  let description;
16476
15980
  try {
16477
- description = extractCursorCommandDescription(await readFile13(join20(directory, entry.name), "utf8"));
15981
+ description = extractCursorCommandDescription(await readFile12(join18(directory, entry.name), "utf8"));
16478
15982
  } catch (error) {
16479
15983
  console.warn("[CursorManager] Failed to read slash command file:", error);
16480
15984
  }
@@ -16496,7 +16000,7 @@ var CursorManager = class extends CodingAgentManager {
16496
16000
  slashCommandsRequest = null;
16497
16001
  constructor(options) {
16498
16002
  super(options);
16499
- this.historyFilePath = options.historyFilePath ?? join20(ENGINE_ENV.HOME_DIR, ".replicas", "cursor", "history.jsonl");
16003
+ this.historyFilePath = options.historyFilePath ?? join18(ENGINE_ENV.HOME_DIR, ".replicas", "cursor", "history.jsonl");
16500
16004
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
16501
16005
  this.initializeManager(this.processMessageInternal.bind(this));
16502
16006
  }
@@ -16571,7 +16075,7 @@ var CursorManager = class extends CodingAgentManager {
16571
16075
  this.slashCommandsRequest ??= (async () => {
16572
16076
  try {
16573
16077
  const repoDirectories = await getAgentAdditionalDirectories();
16574
- const commandDirectories = [this.workingDirectory, ...repoDirectories, ENGINE_ENV.HOME_DIR].map((directory) => join20(directory, ".cursor", "commands"));
16078
+ const commandDirectories = [this.workingDirectory, ...repoDirectories, ENGINE_ENV.HOME_DIR].map((directory) => join18(directory, ".cursor", "commands"));
16575
16079
  const commands = mergeSlashCommands(
16576
16080
  ...await Promise.all(commandDirectories.map(listCursorCommandsInDirectory))
16577
16081
  );
@@ -16852,10 +16356,10 @@ ${instructions}
16852
16356
  import { spawn as spawn3 } from "child_process";
16853
16357
  import { createHash as createHash2, randomUUID as randomUUID6 } from "crypto";
16854
16358
  import { createRequire } from "module";
16855
- import { basename as basename3, dirname as dirname7, join as join21 } from "path";
16359
+ import { basename as basename3, dirname as dirname7, join as join19 } from "path";
16856
16360
  import { fileURLToPath as fileURLToPath2 } from "url";
16857
16361
  import { existsSync as existsSync9 } from "fs";
16858
- import { mkdir as mkdir13, readFile as readFile14, rename as rename2, writeFile as writeFile7 } from "fs/promises";
16362
+ import { mkdir as mkdir13, readFile as readFile13, rename as rename3, writeFile as writeFile7 } from "fs/promises";
16859
16363
  import { z as z2 } from "zod";
16860
16364
  import WebSocket from "ws";
16861
16365
  import { AbstractApiClient } from "@deepseek-ai/dsh-host-apiproxy/client";
@@ -16867,17 +16371,17 @@ var questionSelectionSchema = z2.record(z2.string(), z2.object({
16867
16371
  custom: z2.string().optional()
16868
16372
  }));
16869
16373
  var require2 = createRequire(import.meta.url);
16870
- var DSH_BIN = join21(dirname7(require2.resolve("@deepseek-ai/dsh/package.json")), "lib", "bin.js");
16374
+ var DSH_BIN = join19(dirname7(require2.resolve("@deepseek-ai/dsh/package.json")), "lib", "bin.js");
16871
16375
  var MANAGER_DIR = dirname7(fileURLToPath2(import.meta.url));
16872
16376
  var DSH_PATCH = [
16873
- join21(MANAGER_DIR, "..", "..", "scripts", "deepseek", "replicas.cordis.patch.yml"),
16874
- join21(MANAGER_DIR, "..", "..", "..", "scripts", "deepseek", "replicas.cordis.patch.yml")
16377
+ join19(MANAGER_DIR, "..", "..", "scripts", "deepseek", "replicas.cordis.patch.yml"),
16378
+ join19(MANAGER_DIR, "..", "..", "..", "scripts", "deepseek", "replicas.cordis.patch.yml")
16875
16379
  ].find(existsSync9) ?? "";
16876
16380
  var DSH_COMMAND_PROTECTION_PLUGIN = [
16877
- join21(MANAGER_DIR, "deepseek-command-protection-plugin.js"),
16878
- join21(MANAGER_DIR, "..", "deepseek-command-protection-plugin.ts")
16381
+ join19(MANAGER_DIR, "deepseek-command-protection-plugin.js"),
16382
+ join19(MANAGER_DIR, "..", "deepseek-command-protection-plugin.ts")
16879
16383
  ].find(existsSync9) ?? "";
16880
- var DEEPSEEK_HOME = join21(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek");
16384
+ var DEEPSEEK_HOME = join19(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek");
16881
16385
  function relayPresetContent(content, instructions, mcp) {
16882
16386
  const personaMarker = " text: >-\n";
16883
16387
  const personaStart = content.indexOf(personaMarker);
@@ -17022,8 +16526,8 @@ var DeepseekManager = class extends CodingAgentManager {
17022
16526
  constructor(options) {
17023
16527
  super(options);
17024
16528
  this.sessionId = options.initialSessionId ? sessionIdSchema.parse(options.initialSessionId) : null;
17025
- this.historyFilePath = options.historyFilePath ?? join21(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek", "history.jsonl");
17026
- this.deepseekHome = this.isRelay ? join21(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek-relay", basename3(this.historyFilePath, ".jsonl")) : DEEPSEEK_HOME;
16529
+ this.historyFilePath = options.historyFilePath ?? join19(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek", "history.jsonl");
16530
+ this.deepseekHome = this.isRelay ? join19(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek-relay", basename3(this.historyFilePath, ".jsonl")) : DEEPSEEK_HOME;
17027
16531
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
17028
16532
  this.initializeManager(this.processMessageInternal.bind(this));
17029
16533
  }
@@ -17102,9 +16606,9 @@ var DeepseekManager = class extends CodingAgentManager {
17102
16606
  async ensureClient() {
17103
16607
  if (this.client) return this.client;
17104
16608
  if (!DSH_PATCH || !DSH_COMMAND_PROTECTION_PLUGIN) throw new Error("DeepSeek Harness configuration is missing from the engine package.");
17105
- const runtimePatch = join21(this.deepseekHome, "replicas.cordis.patch.yml");
16609
+ const runtimePatch = join19(this.deepseekHome, "replicas.cordis.patch.yml");
17106
16610
  await mkdir13(dirname7(runtimePatch), { recursive: true });
17107
- await writeFile7(runtimePatch, (await readFile14(DSH_PATCH, "utf8")).replace(
16611
+ await writeFile7(runtimePatch, (await readFile13(DSH_PATCH, "utf8")).replace(
17108
16612
  "__REPLICAS_DSH_COMMAND_PROTECTION_PLUGIN__",
17109
16613
  JSON.stringify(DSH_COMMAND_PROTECTION_PLUGIN)
17110
16614
  ));
@@ -17177,10 +16681,10 @@ var DeepseekManager = class extends CodingAgentManager {
17177
16681
  if (!roster.some((preset) => preset.id === agentPreset)) {
17178
16682
  const stage = `relay-stage-${randomUUID6().replaceAll("-", "")}`;
17179
16683
  unwrap(await client2.agentPresets.copy({ from: "standard", agentPreset: stage, name: "Relay" }));
17180
- const stageDirectory = join21(this.deepseekHome, ".agent-presets", stage);
16684
+ const stageDirectory = join19(this.deepseekHome, ".agent-presets", stage);
17181
16685
  try {
17182
- await writeFile7(join21(stageDirectory, "agent.cordis.yml"), content, "utf8");
17183
- await rename2(stageDirectory, join21(this.deepseekHome, ".agent-presets", agentPreset));
16686
+ await writeFile7(join19(stageDirectory, "agent.cordis.yml"), content, "utf8");
16687
+ await rename3(stageDirectory, join19(this.deepseekHome, ".agent-presets", agentPreset));
17184
16688
  } catch (error) {
17185
16689
  unwrap(await client2.agentPresets.remove({ agentPreset: stage }));
17186
16690
  if (!unwrap(await client2.agentPresets.list({})).presets.some((preset) => preset.id === agentPreset)) throw error;
@@ -17354,13 +16858,13 @@ ${request.message}` : request.message;
17354
16858
  };
17355
16859
 
17356
16860
  // src/managers/fx-manager.ts
17357
- import { readFile as readFile17 } from "fs/promises";
16861
+ import { readFile as readFile16 } from "fs/promises";
17358
16862
  import { spawn as spawn5 } from "child_process";
17359
16863
 
17360
16864
  // src/managers/acp-manager.ts
17361
16865
  import { spawn as spawn4 } from "child_process";
17362
16866
  import { randomUUID as randomUUID7 } from "crypto";
17363
- import { mkdir as mkdir14, readFile as readFile16, writeFile as writeFile8 } from "fs/promises";
16867
+ import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile8 } from "fs/promises";
17364
16868
  import { dirname as dirname8 } from "path";
17365
16869
  import { Writable } from "stream";
17366
16870
  import {
@@ -17371,24 +16875,24 @@ import {
17371
16875
  } from "@agentclientprotocol/sdk";
17372
16876
 
17373
16877
  // src/services/installed-skill-service.ts
17374
- import { readFile as readFile15, readdir as readdir7 } from "fs/promises";
17375
- import { join as join22 } from "path";
16878
+ import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
16879
+ import { join as join20 } from "path";
17376
16880
  var installedSkillsByHome = /* @__PURE__ */ new Map();
17377
16881
  function isNotFoundError2(error) {
17378
16882
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
17379
16883
  }
17380
16884
  async function loadInstalledSkills(homeDir) {
17381
- const skillsDir = join22(homeDir, ".agents/skills");
16885
+ const skillsDir = join20(homeDir, ".agents/skills");
17382
16886
  let entries;
17383
16887
  try {
17384
- entries = await readdir7(skillsDir, { withFileTypes: true });
16888
+ entries = await readdir6(skillsDir, { withFileTypes: true });
17385
16889
  } catch (error) {
17386
16890
  if (!isNotFoundError2(error)) console.warn("[InstalledSkills] Failed to list installed skills:", error);
17387
16891
  return [];
17388
16892
  }
17389
16893
  const skills = await Promise.all(entries.filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map(async (entry) => {
17390
16894
  try {
17391
- const metadata = parseFrontmatter(await readFile15(join22(skillsDir, entry.name, "SKILL.md"), "utf8"));
16895
+ const metadata = parseFrontmatter(await readFile14(join20(skillsDir, entry.name, "SKILL.md"), "utf8"));
17392
16896
  return metadata.name && metadata.description ? metadata : null;
17393
16897
  } catch (error) {
17394
16898
  if (!isNotFoundError2(error)) console.warn(`[InstalledSkills] Failed to read ${entry.name}:`, error);
@@ -17473,7 +16977,7 @@ var AcpManager = class extends CodingAgentManager {
17473
16977
  });
17474
16978
  const app2 = client({ name: "Replicas" }).onNotification(methods.client.session.update, ({ params }) => this.recordUpdate(params)).onRequest(methods.client.fs.readTextFile, async ({ params }) => {
17475
16979
  this.assertSession(params.sessionId);
17476
- const content = await readFile16(params.path, "utf8");
16980
+ const content = await readFile15(params.path, "utf8");
17477
16981
  if (params.line === void 0 && params.limit === void 0) return { content };
17478
16982
  const start = Math.max(0, (params.line ?? 1) - 1);
17479
16983
  const end = params.limit === null || params.limit === void 0 ? void 0 : start + params.limit;
@@ -17864,7 +17368,7 @@ ${request.message}` : request.message
17864
17368
 
17865
17369
  // src/managers/fx-manager.ts
17866
17370
  async function ensureResolvableDnsConfig() {
17867
- const contents = await readFile17("/etc/resolv.conf");
17371
+ const contents = await readFile16("/etc/resolv.conf");
17868
17372
  if (contents.length === 0 || contents.at(-1) === 10) return;
17869
17373
  await new Promise((resolve7, reject) => {
17870
17374
  const child = spawn5("sudo", ["tee", "-a", "/etc/resolv.conf"], { stdio: ["pipe", "ignore", "pipe"] });
@@ -18474,8 +17978,8 @@ ${pending}
18474
17978
 
18475
17979
  // src/managers/opencode-manager.ts
18476
17980
  import { existsSync as existsSync10 } from "fs";
18477
- import { copyFile, mkdir as mkdir16, readFile as readFile18, rm as rm2 } from "fs/promises";
18478
- import { delimiter, dirname as dirname10, join as join23 } from "path";
17981
+ import { copyFile, mkdir as mkdir16, readFile as readFile17, rm as rm2 } from "fs/promises";
17982
+ import { delimiter, dirname as dirname10, join as join21 } from "path";
18479
17983
  import { randomBytes as randomBytes3 } from "crypto";
18480
17984
  import { fileURLToPath as fileURLToPath3 } from "url";
18481
17985
  import { Agent } from "undici";
@@ -18504,10 +18008,10 @@ async function getAllowedOpenRouterModels() {
18504
18008
 
18505
18009
  // src/managers/opencode-manager.ts
18506
18010
  var OPENCODE_SHIM_DIR = dirname10(fileURLToPath3(new URL("../../scripts/opencode", import.meta.url)));
18507
- var OPENCODE_COMMAND_PROTECTION_PLUGIN_SOURCE = join23(OPENCODE_SHIM_DIR, "opencode-command-protection-plugin.ts");
18508
- var OPENCODE_COMMAND_PROTECTION_PLUGIN_PATH = join23(ENGINE_ENV.HOME_DIR, ".config", "opencode", "plugins", "replicas-command-protection.ts");
18509
- var OPENCODE_LEGACY_COMMAND_PROTECTION_PLUGIN_PATH = join23(ENGINE_ENV.HOME_DIR, ".config", "opencode", "plugins", "replicas-command-protection.mjs");
18510
- var OPENCODE_CONFIG_PATH = join23(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
18011
+ var OPENCODE_COMMAND_PROTECTION_PLUGIN_SOURCE = join21(OPENCODE_SHIM_DIR, "opencode-command-protection-plugin.ts");
18012
+ var OPENCODE_COMMAND_PROTECTION_PLUGIN_PATH = join21(ENGINE_ENV.HOME_DIR, ".config", "opencode", "plugins", "replicas-command-protection.ts");
18013
+ var OPENCODE_LEGACY_COMMAND_PROTECTION_PLUGIN_PATH = join21(ENGINE_ENV.HOME_DIR, ".config", "opencode", "plugins", "replicas-command-protection.mjs");
18014
+ var OPENCODE_CONFIG_PATH = join21(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
18511
18015
  var OPENCODE_FETCH_DISPATCHER = new Agent({ headersTimeout: 0, bodyTimeout: 0 });
18512
18016
  var OPENCODE_SERVER_STARTUP_TIMEOUT_MS = 3e4;
18513
18017
  var OPENCODE_WORKSPACE_PERMISSION = "allow";
@@ -18528,7 +18032,7 @@ var opencodeAuthSchema = z3.record(z3.string(), z3.object({
18528
18032
  async function hasOpencodeCredentials(provider) {
18529
18033
  if (!existsSync10(OPENCODE_AUTH_PATH)) return false;
18530
18034
  try {
18531
- const auth2 = opencodeAuthSchema.safeParse(JSON.parse(await readFile18(OPENCODE_AUTH_PATH, "utf8")));
18035
+ const auth2 = opencodeAuthSchema.safeParse(JSON.parse(await readFile17(OPENCODE_AUTH_PATH, "utf8")));
18532
18036
  return auth2.success && auth2.data[provider]?.type === "api" && Boolean(auth2.data[provider]?.key);
18533
18037
  } catch {
18534
18038
  return false;
@@ -18633,7 +18137,7 @@ function isOpencodeMcpEntry(value) {
18633
18137
  async function readProvisionedOpencodeMcpConfig() {
18634
18138
  let raw;
18635
18139
  try {
18636
- raw = await readFile18(OPENCODE_CONFIG_PATH, "utf8");
18140
+ raw = await readFile17(OPENCODE_CONFIG_PATH, "utf8");
18637
18141
  } catch (error) {
18638
18142
  if (isRecord2(error) && error.code === "ENOENT") return void 0;
18639
18143
  console.error("[OpencodeManager] Failed to read Opencode config:", error);
@@ -18719,7 +18223,7 @@ var OpencodeManager = class extends CodingAgentManager {
18719
18223
  constructor(options) {
18720
18224
  super({ ...options, provider: "opencode" });
18721
18225
  this.sessionId = options.initialSessionId;
18722
- this.historyFilePath = options.historyFilePath ?? join23(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
18226
+ this.historyFilePath = options.historyFilePath ?? join21(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
18723
18227
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
18724
18228
  this.initializeManager(this.processMessageInternal.bind(this));
18725
18229
  }
@@ -19139,8 +18643,8 @@ var OpencodeManager = class extends CodingAgentManager {
19139
18643
  };
19140
18644
 
19141
18645
  // src/managers/pi-manager.ts
19142
- import { mkdir as mkdir18, readFile as readFile19 } from "fs/promises";
19143
- import { dirname as dirname18, join as join30 } from "path";
18646
+ import { mkdir as mkdir18, readFile as readFile18 } from "fs/promises";
18647
+ import { dirname as dirname18, join as join29 } from "path";
19144
18648
  import {
19145
18649
  createAgentSession,
19146
18650
  ModelRegistry,
@@ -19149,6 +18653,37 @@ import {
19149
18653
  DefaultResourceLoader
19150
18654
  } from "@earendil-works/pi-coding-agent";
19151
18655
 
18656
+ // src/services/chat/history-paths.ts
18657
+ import { homedir as homedir12 } from "os";
18658
+ import { join as join22 } from "path";
18659
+ var ENGINE_DIR2 = join22(homedir12(), ".replicas", "engine");
18660
+ var CHATS_FILE = join22(ENGINE_DIR2, "chats.json");
18661
+ var CLAUDE_HISTORY_DIR = join22(ENGINE_DIR2, "claude-histories");
18662
+ var RELAY_HISTORY_DIR = join22(ENGINE_DIR2, "relay-histories");
18663
+ var CODEX_HISTORY_DIR = join22(ENGINE_DIR2, "codex-histories");
18664
+ var CURSOR_HISTORY_DIR = join22(ENGINE_DIR2, "cursor-histories");
18665
+ var OPENCODE_HISTORY_DIR = join22(ENGINE_DIR2, "opencode-histories");
18666
+ var PI_HISTORY_DIR = join22(ENGINE_DIR2, "pi-histories");
18667
+ var DEEPSEEK_HISTORY_DIR = join22(ENGINE_DIR2, "deepseek-histories");
18668
+ var FX_HISTORY_DIR = join22(ENGINE_DIR2, "fx-histories");
18669
+ var GEMINI_HISTORY_DIR = join22(ENGINE_DIR2, "gemini-histories");
18670
+ var KIMI_HISTORY_DIR = join22(ENGINE_DIR2, "kimi-histories");
18671
+ var MUSE_HISTORY_DIR = join22(ENGINE_DIR2, "muse-histories");
18672
+ var FORKS_DIR = join22(ENGINE_DIR2, "forks");
18673
+ var HISTORY_DIR_BY_PROVIDER = {
18674
+ claude: CLAUDE_HISTORY_DIR,
18675
+ relay: RELAY_HISTORY_DIR,
18676
+ codex: CODEX_HISTORY_DIR,
18677
+ cursor: CURSOR_HISTORY_DIR,
18678
+ deepseek: DEEPSEEK_HISTORY_DIR,
18679
+ fx: FX_HISTORY_DIR,
18680
+ gemini: GEMINI_HISTORY_DIR,
18681
+ kimi: KIMI_HISTORY_DIR,
18682
+ muse: MUSE_HISTORY_DIR,
18683
+ opencode: OPENCODE_HISTORY_DIR,
18684
+ pi: PI_HISTORY_DIR
18685
+ };
18686
+
19152
18687
  // ../node_modules/.bun/typebox@1.3.7/node_modules/typebox/build/system/memory/memory.mjs
19153
18688
  var memory_exports = {};
19154
18689
  __export(memory_exports, {
@@ -41679,7 +41214,7 @@ async function probeMcpEndpoint(url3) {
41679
41214
 
41680
41215
  // ../node_modules/.bun/pi-mcp-adapter@2.32.1+04b0d7b0bc944965/node_modules/pi-mcp-adapter/npx-resolver.ts
41681
41216
  import { existsSync as existsSync11, readFileSync as readFileSync4, realpathSync, readdirSync, statSync, writeFileSync, renameSync, mkdirSync, openSync, readSync, closeSync, unlinkSync } from "fs";
41682
- import { join as join24, dirname as dirname11, extname as extname2, resolve as resolve2, sep } from "path";
41217
+ import { join as join23, dirname as dirname11, extname as extname2, resolve as resolve2, sep } from "path";
41683
41218
 
41684
41219
  // ../node_modules/.bun/pi-mcp-adapter@2.32.1+04b0d7b0bc944965/node_modules/pi-mcp-adapter/abort.ts
41685
41220
  function throwIfAborted(signal) {
@@ -41836,7 +41371,7 @@ function resolveFromNpmCache(packageSpec, binName) {
41836
41371
  const { packageName, exactVersion } = parsedSpec;
41837
41372
  const packageDir = findCachedPackageDir(cacheDir, packageName, exactVersion);
41838
41373
  if (!packageDir) return null;
41839
- const packageJsonPath = join24(packageDir, "package.json");
41374
+ const packageJsonPath = join23(packageDir, "package.json");
41840
41375
  if (!existsSync11(packageJsonPath)) return null;
41841
41376
  let pkg = null;
41842
41377
  try {
@@ -41870,7 +41405,7 @@ function resolveFromNpmCache(packageSpec, binName) {
41870
41405
  }
41871
41406
  if (!binRel) return null;
41872
41407
  const nodeModulesDir = findNodeModulesDir(packageDir);
41873
- const binLink = chosenBinName ? join24(nodeModulesDir, ".bin", chosenBinName) : null;
41408
+ const binLink = chosenBinName ? join23(nodeModulesDir, ".bin", chosenBinName) : null;
41874
41409
  let resolvedBin = binLink && existsSync11(binLink) ? safeRealpath(binLink) : "";
41875
41410
  if (!resolvedBin) {
41876
41411
  resolvedBin = resolve2(packageDir, binRel);
@@ -41972,17 +41507,17 @@ function defaultBinName(packageName) {
41972
41507
  return packageName;
41973
41508
  }
41974
41509
  function findCachedPackageDir(cacheDir, packageName, exactVersion) {
41975
- const npxDir = join24(cacheDir, "_npx");
41510
+ const npxDir = join23(cacheDir, "_npx");
41976
41511
  if (!existsSync11(npxDir)) return null;
41977
41512
  const packagePathParts = packageName.startsWith("@") ? packageName.split("/") : [packageName];
41978
41513
  const candidates = readdirSync(npxDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
41979
- const full = join24(npxDir, entry.name);
41514
+ const full = join23(npxDir, entry.name);
41980
41515
  const mtime = safeStatMtime(full);
41981
41516
  return { name: entry.name, mtime };
41982
41517
  }).sort((a, b) => b.mtime - a.mtime);
41983
41518
  for (const entry of candidates) {
41984
- const pkgDir = join24(npxDir, entry.name, "node_modules", ...packagePathParts);
41985
- const packageJsonPath = join24(pkgDir, "package.json");
41519
+ const pkgDir = join23(npxDir, entry.name, "node_modules", ...packagePathParts);
41520
+ const packageJsonPath = join23(pkgDir, "package.json");
41986
41521
  if (!existsSync11(packageJsonPath)) continue;
41987
41522
  if (exactVersion) {
41988
41523
  try {
@@ -42002,7 +41537,7 @@ function findNodeModulesDir(packageDir) {
42002
41537
  if (idx >= 0) {
42003
41538
  return parts.slice(0, idx + 1).join(sep);
42004
41539
  }
42005
- return join24(packageDir, "..");
41540
+ return join23(packageDir, "..");
42006
41541
  }
42007
41542
  function detectJsBinary(binPath) {
42008
41543
  const ext = extname2(binPath).toLowerCase();
@@ -42194,7 +41729,7 @@ import { spawnSync } from "child_process";
42194
41729
  import { createHash as createHash3 } from "crypto";
42195
41730
  import { createRequire as createRequire2 } from "module";
42196
41731
  import { readFileSync as readFileSync5, existsSync as existsSync12, rmSync } from "fs";
42197
- import { dirname as dirname12, join as join25 } from "path";
41732
+ import { dirname as dirname12, join as join24 } from "path";
42198
41733
  import { fileURLToPath as fileURLToPath4 } from "url";
42199
41734
  var require3 = createRequire2(import.meta.url);
42200
41735
  var AUTH_SECRET_SERVICE = "pi-mcp-adapter.oauth";
@@ -42350,7 +41885,7 @@ function loadKeyringNativeBindingFallback(keyringRequire, platform3, arch2) {
42350
41885
  for (const target of targets) {
42351
41886
  try {
42352
41887
  const packageJsonPath = keyringRequire.resolve(`${target.packageName}/package.json`);
42353
- return keyringRequire(join25(dirname12(packageJsonPath), target.bindingFile));
41888
+ return keyringRequire(join24(dirname12(packageJsonPath), target.bindingFile));
42354
41889
  } catch (error) {
42355
41890
  lastError = error;
42356
41891
  }
@@ -42455,7 +41990,7 @@ function getServerDir(serverName, options) {
42455
41990
  throw new Error(`Invalid MCP server name: ${JSON.stringify(serverName)}`);
42456
41991
  }
42457
41992
  const storageKey = getAuthEntryAccount(serverName);
42458
- return join25(getAuthBaseDir(options), storageKey);
41993
+ return join24(getAuthBaseDir(options), storageKey);
42459
41994
  }
42460
41995
  function getAuthEntryAccount(serverName) {
42461
41996
  if (typeof serverName !== "string") {
@@ -42464,7 +41999,7 @@ function getAuthEntryAccount(serverName) {
42464
41999
  return `sha256-${createHash3("sha256").update(serverName, "utf8").digest("hex")}`;
42465
42000
  }
42466
42001
  function getAuthEntryFilePath(serverName, options) {
42467
- return join25(getServerDir(serverName, options), "tokens.json");
42002
+ return join24(getServerDir(serverName, options), "tokens.json");
42468
42003
  }
42469
42004
  function parseJsonPayload(serverName, payload, source) {
42470
42005
  try {
@@ -45067,7 +44602,7 @@ async function shutdownOAuth(runtime = legacyRuntime) {
45067
44602
  // ../node_modules/.bun/pi-mcp-adapter@2.32.1+04b0d7b0bc944965/node_modules/pi-mcp-adapter/mcp-bearer-store.ts
45068
44603
  import { createHash as createHash4 } from "crypto";
45069
44604
  import { createRequire as createRequire3 } from "module";
45070
- import { dirname as dirname13, join as join26 } from "path";
44605
+ import { dirname as dirname13, join as join25 } from "path";
45071
44606
  var require4 = createRequire3(import.meta.url);
45072
44607
  var BEARER_SECRET_SERVICE = "pi-mcp-adapter.bearer";
45073
44608
  var TEST_AUTH_STORE_ENV2 = "PI_MCP_ADAPTER_TEST_AUTH_STORE";
@@ -45169,7 +44704,7 @@ function loadKeyringNativeBindingFallback2(keyringRequire, platform3, arch2) {
45169
44704
  for (const target of targets) {
45170
44705
  try {
45171
44706
  const packageJsonPath = keyringRequire.resolve(`${target.packageName}/package.json`);
45172
- return keyringRequire(join26(dirname13(packageJsonPath), target.bindingFile));
44707
+ return keyringRequire(join25(dirname13(packageJsonPath), target.bindingFile));
45173
44708
  } catch (error) {
45174
44709
  lastError = error;
45175
44710
  }
@@ -49636,7 +49171,7 @@ async function openMcpAuthPanel(state, pi, ctx, configOverridePath) {
49636
49171
  // ../node_modules/.bun/pi-mcp-adapter@2.32.1+04b0d7b0bc944965/node_modules/pi-mcp-adapter/tool-registrar.ts
49637
49172
  import { mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
49638
49173
  import { tmpdir } from "os";
49639
- import { join as join27 } from "path";
49174
+ import { join as join26 } from "path";
49640
49175
  var MAX_BINARY_RESOURCE_BYTES = 10 * 1024 * 1024;
49641
49176
  var MAX_SESSION_RESOURCE_BYTES = 100 * 1024 * 1024;
49642
49177
  var MAX_SESSION_RESOURCE_FILES = 1e4;
@@ -49743,11 +49278,11 @@ function materializeBinaryResource(resource, scope) {
49743
49278
  return omitBinaryResource(resource, "session resource limit reached");
49744
49279
  }
49745
49280
  try {
49746
- session.directory ??= mkdtempSync(join27(tmpdir(), "pi-mcp-resource-"));
49281
+ session.directory ??= mkdtempSync(join26(tmpdir(), "pi-mcp-resource-"));
49747
49282
  } catch {
49748
49283
  return omitBinaryResource(resource, "could not be saved");
49749
49284
  }
49750
- const filePath = join27(session.directory, `resource-${++session.sequence}.bin`);
49285
+ const filePath = join26(session.directory, `resource-${++session.sequence}.bin`);
49751
49286
  session.bytes += decodedBytes;
49752
49287
  session.files += 1;
49753
49288
  try {
@@ -49840,7 +49375,7 @@ function stringifyStructuredContent(value) {
49840
49375
  import { randomBytes as randomBytes4 } from "crypto";
49841
49376
  import { mkdtemp, rm as rm3, writeFile as writeFile10 } from "fs/promises";
49842
49377
  import { tmpdir as tmpdir2 } from "os";
49843
- import { dirname as dirname16, join as join28 } from "path";
49378
+ import { dirname as dirname16, join as join27 } from "path";
49844
49379
  var DEFAULT_MCP_OUTPUT_MAX_BYTES = 50 * 1024;
49845
49380
  var DEFAULT_MCP_OUTPUT_MAX_LINES = 2e3;
49846
49381
  var DEFAULT_MCP_DETAILS_MAX_BYTES = 16 * 1024;
@@ -50148,8 +49683,8 @@ function serializedObjectEntryBytes(key, value, hasPrevious) {
50148
49683
  }
50149
49684
  async function saveArtifact(kind, text) {
50150
49685
  try {
50151
- const dir = await mkdtemp(join28(tmpdir2(), "pi-mcp-output-"));
50152
- const path7 = join28(dir, `${kind}-${randomBytes4(4).toString("hex")}.txt`);
49686
+ const dir = await mkdtemp(join27(tmpdir2(), "pi-mcp-output-"));
49687
+ const path7 = join27(dir, `${kind}-${randomBytes4(4).toString("hex")}.txt`);
50153
49688
  await writeFile10(path7, text, { encoding: "utf8", mode: 384 });
50154
49689
  return { path: path7 };
50155
49690
  } catch (error) {
@@ -51796,7 +51331,7 @@ function sendText(res, status, text) {
51796
51331
  // ../node_modules/.bun/pi-mcp-adapter@2.32.1+04b0d7b0bc944965/node_modules/pi-mcp-adapter/glimpse-ui.ts
51797
51332
  import { existsSync as existsSync15 } from "fs";
51798
51333
  import { execFileSync as execFileSync2 } from "child_process";
51799
- import { join as join29, dirname as dirname17 } from "path";
51334
+ import { join as join28, dirname as dirname17 } from "path";
51800
51335
  import { platform as platform2 } from "os";
51801
51336
  import { createRequire as createRequire4 } from "module";
51802
51337
  var glimpseAvailable = null;
@@ -51818,20 +51353,20 @@ function getGlimpseBinaryPath() {
51818
51353
  try {
51819
51354
  const require7 = createRequire4(import.meta.url);
51820
51355
  const glimpseuiPath = require7.resolve("glimpseui");
51821
- const binaryPath = join29(dirname17(glimpseuiPath), "glimpse");
51356
+ const binaryPath = join28(dirname17(glimpseuiPath), "glimpse");
51822
51357
  if (existsSync15(binaryPath)) return binaryPath;
51823
51358
  } catch {
51824
51359
  }
51825
51360
  try {
51826
51361
  const globalRoot = execFileSync2("npm", ["root", "-g"], { encoding: "utf-8" }).trim();
51827
- const binaryPath = join29(globalRoot, "glimpseui", "src", "glimpse");
51362
+ const binaryPath = join28(globalRoot, "glimpseui", "src", "glimpse");
51828
51363
  if (existsSync15(binaryPath)) return binaryPath;
51829
51364
  } catch {
51830
51365
  }
51831
51366
  return null;
51832
51367
  }
51833
51368
  async function openGlimpseWindow(html, options) {
51834
- const modulePath = resolvedBinaryPath ? join29(dirname17(resolvedBinaryPath), "glimpse.mjs") : "glimpseui";
51369
+ const modulePath = resolvedBinaryPath ? join28(dirname17(resolvedBinaryPath), "glimpse.mjs") : "glimpseui";
51835
51370
  const glimpse = await import(modulePath);
51836
51371
  let active = true;
51837
51372
  const win = glimpse.open(html, {
@@ -59621,7 +59156,7 @@ var pi_mcp_adapter_default = createMcpAdapter();
59621
59156
  // src/managers/pi-manager.ts
59622
59157
  async function getManagedPiMcpServers() {
59623
59158
  try {
59624
- const parsed = JSON.parse(await readFile19(PI_MCP_CONFIG_PATH, "utf8"));
59159
+ const parsed = JSON.parse(await readFile18(PI_MCP_CONFIG_PATH, "utf8"));
59625
59160
  if (!isRecord2(parsed) || !isRecord2(parsed.mcpServers)) return {};
59626
59161
  return Object.fromEntries(Object.entries(parsed.mcpServers).filter(
59627
59162
  (entry) => isRecord2(entry[1]) && (typeof entry[1].command === "string" || typeof entry[1].url === "string")
@@ -59743,7 +59278,7 @@ var PiManager = class extends CodingAgentManager {
59743
59278
  modelRuntime = null;
59744
59279
  constructor(options) {
59745
59280
  super(options);
59746
- this.historyFilePath = options.historyFilePath ?? join30(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
59281
+ this.historyFilePath = options.historyFilePath ?? join29(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
59747
59282
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
59748
59283
  this.initializeManager(this.processMessageInternal.bind(this));
59749
59284
  }
@@ -59879,7 +59414,7 @@ var PiManager = class extends CodingAgentManager {
59879
59414
  }
59880
59415
  const resourceLoader = new DefaultResourceLoader({
59881
59416
  cwd: this.workingDirectory,
59882
- agentDir: join30(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
59417
+ agentDir: join29(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
59883
59418
  extensionFactories,
59884
59419
  appendSystemPrompt: [this.buildCombinedInstructions() ?? ""]
59885
59420
  });
@@ -59977,11 +59512,6 @@ Match verification depth to the change: use focused checks for small changes and
59977
59512
 
59978
59513
  // src/managers/relay-tools.ts
59979
59514
  function extractFinalResponse(history, provider) {
59980
- const turns = history.codexAspTranscript?.turns ?? [];
59981
- for (let index = turns.length - 1; index >= 0; index--) {
59982
- const response = getCodexAspTurnResponse(turns[index]);
59983
- if (response?.text) return response.text;
59984
- }
59985
59515
  const message = parseAgentEvents(history.events ?? [], provider).findLast((candidate) => candidate.type === "agent");
59986
59516
  return message?.type === "agent" && message.content ? message.content : null;
59987
59517
  }
@@ -60001,13 +59531,13 @@ async function uploadAnalytics(endpoint, body) {
60001
59531
  import {
60002
59532
  appendFile as appendFile4,
60003
59533
  mkdir as mkdir19,
60004
- readFile as readFile20,
60005
- readdir as readdir8,
60006
- rename as rename3,
59534
+ readFile as readFile19,
59535
+ readdir as readdir7,
59536
+ rename as rename4,
60007
59537
  unlink as unlink3
60008
59538
  } from "fs/promises";
60009
59539
  import { existsSync as existsSync16, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "fs";
60010
- import { join as join31 } from "path";
59540
+ import { join as join30 } from "path";
60011
59541
  var MAX_FAILED_RECORDS = 500;
60012
59542
  var FLUSH_DEBOUNCE_MS = 15e3;
60013
59543
  var MAX_UPLOADED_SEGMENTS = 50;
@@ -60019,7 +59549,7 @@ var AnalyticsRecordBuffer = class {
60019
59549
  options.storageName,
60020
59550
  ...options.legacyStorageNames ?? []
60021
59551
  ];
60022
- this.liveFile = join31(ENGINE_DIR2, `${options.storageName}.jsonl`);
59552
+ this.liveFile = join30(ENGINE_DIR2, `${options.storageName}.jsonl`);
60023
59553
  this.segmentFilePatterns = this.storageNames.map(
60024
59554
  (storageName) => new RegExp(`^${storageName}\\.(\\d+)\\.jsonl$`)
60025
59555
  );
@@ -60057,7 +59587,7 @@ var AnalyticsRecordBuffer = class {
60057
59587
  if (!existsSync16(ENGINE_DIR2)) return [];
60058
59588
  return readdirSync2(ENGINE_DIR2, { withFileTypes: true }).filter((entry) => entry.isFile() && this.storageNames.some(
60059
59589
  (storageName) => entry.name === `${storageName}.jsonl` || entry.name.startsWith(`${storageName}.`) && (entry.name.endsWith(".jsonl") || entry.name.endsWith(`.jsonl${UPLOADED_SUFFIX}`))
60060
- )).map(({ name }) => name).sort().flatMap((name) => this.parseRecords(readFileSync7(join31(ENGINE_DIR2, name), "utf-8"))).map((record3) => {
59590
+ )).map(({ name }) => name).sort().flatMap((name) => this.parseRecords(readFileSync7(join30(ENGINE_DIR2, name), "utf-8"))).map((record3) => {
60061
59591
  const recordId = this.options.getRecordId?.(record3);
60062
59592
  if (recordId) this.recordIds.add(recordId);
60063
59593
  return record3;
@@ -60091,19 +59621,19 @@ var AnalyticsRecordBuffer = class {
60091
59621
  await Promise.allSettled([...this.pendingAppends]);
60092
59622
  }
60093
59623
  for (const storageName of this.storageNames) {
60094
- await rename3(
60095
- join31(ENGINE_DIR2, `${storageName}.jsonl`),
60096
- join31(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
59624
+ await rename4(
59625
+ join30(ENGINE_DIR2, `${storageName}.jsonl`),
59626
+ join30(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
60097
59627
  ).catch(() => {
60098
59628
  });
60099
59629
  }
60100
- const entries = await readdir8(ENGINE_DIR2).catch(() => []);
59630
+ const entries = await readdir7(ENGINE_DIR2).catch(() => []);
60101
59631
  let flushed = 0;
60102
59632
  let failed = 0;
60103
59633
  for (const entry of entries) {
60104
59634
  if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry))) continue;
60105
59635
  try {
60106
- await this.uploadSegment(join31(ENGINE_DIR2, entry));
59636
+ await this.uploadSegment(join30(ENGINE_DIR2, entry));
60107
59637
  flushed++;
60108
59638
  } catch (error) {
60109
59639
  failed++;
@@ -60132,16 +59662,16 @@ var AnalyticsRecordBuffer = class {
60132
59662
  (storageName) => entry.startsWith(`${storageName}.`)
60133
59663
  ) && entry.endsWith(UPLOADED_SUFFIX)).sort();
60134
59664
  for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
60135
- await unlink3(join31(ENGINE_DIR2, entry)).catch(() => {
59665
+ await unlink3(join30(ENGINE_DIR2, entry)).catch(() => {
60136
59666
  });
60137
59667
  }
60138
59668
  }
60139
59669
  return { flushed, failed };
60140
59670
  }
60141
59671
  async uploadSegment(filePath) {
60142
- const records = this.parseRecords(await readFile20(filePath, "utf-8"));
59672
+ const records = this.parseRecords(await readFile19(filePath, "utf-8"));
60143
59673
  if (records.length > 0) await this.options.upload(records);
60144
- await rename3(filePath, `${filePath}${UPLOADED_SUFFIX}`).catch(() => {
59674
+ await rename4(filePath, `${filePath}${UPLOADED_SUFFIX}`).catch(() => {
60145
59675
  });
60146
59676
  }
60147
59677
  parseRecords(contents) {
@@ -60981,9 +60511,9 @@ var KeepAliveService = class _KeepAliveService {
60981
60511
  var keepAliveService = new KeepAliveService();
60982
60512
 
60983
60513
  // src/services/canvas-service.ts
60984
- import { readdir as readdir9, readFile as readFile22, stat as stat4 } from "fs/promises";
60514
+ import { readdir as readdir8, readFile as readFile21, stat as stat4 } from "fs/promises";
60985
60515
  import { homedir as homedir13 } from "os";
60986
- import { join as join32 } from "path";
60516
+ import { join as join31 } from "path";
60987
60517
 
60988
60518
  // src/utils/contained-file.ts
60989
60519
  import { realpath, stat as stat3 } from "fs/promises";
@@ -61009,9 +60539,9 @@ function fileRevision(content) {
61009
60539
  }
61010
60540
 
61011
60541
  // src/utils/revisioned-text-file.ts
61012
- import { readFile as readFile21 } from "fs/promises";
60542
+ import { readFile as readFile20 } from "fs/promises";
61013
60543
  async function writeRevisionedTextFile(filePath, content, expectedRevision) {
61014
- const current = await readFile21(filePath, "utf8");
60544
+ const current = await readFile20(filePath, "utf8");
61015
60545
  const revision = fileRevision(current);
61016
60546
  if (revision !== expectedRevision) {
61017
60547
  return {
@@ -61025,15 +60555,15 @@ async function writeRevisionedTextFile(filePath, content, expectedRevision) {
61025
60555
 
61026
60556
  // src/services/canvas-service.ts
61027
60557
  var GLOBAL_CANVAS_DIRECTORIES = [
61028
- join32(homedir13(), ".claude", "plans"),
61029
- join32(process.env.XDG_DATA_HOME ?? join32(homedir13(), ".local", "share"), "opencode", "plans"),
61030
- join32(homedir13(), ".replicas", "canvas")
60558
+ join31(homedir13(), ".claude", "plans"),
60559
+ join31(process.env.XDG_DATA_HOME ?? join31(homedir13(), ".local", "share"), "opencode", "plans"),
60560
+ join31(homedir13(), ".replicas", "canvas")
61031
60561
  ];
61032
60562
  async function canvasDirectories() {
61033
60563
  const repositories = await gitService.listRepositories().catch(() => []);
61034
60564
  return [
61035
60565
  ...GLOBAL_CANVAS_DIRECTORIES,
61036
- ...repositories.map((repository) => join32(repository.path, ".opencode", "plans"))
60566
+ ...repositories.map((repository) => join31(repository.path, ".opencode", "plans"))
61037
60567
  ];
61038
60568
  }
61039
60569
  var CanvasService = class {
@@ -61051,14 +60581,14 @@ var CanvasService = class {
61051
60581
  if (!current) continue;
61052
60582
  let entries;
61053
60583
  try {
61054
- entries = await readdir9(current.directory, { withFileTypes: true });
60584
+ entries = await readdir8(current.directory, { withFileTypes: true });
61055
60585
  } catch {
61056
60586
  continue;
61057
60587
  }
61058
60588
  for (const entry of entries) {
61059
60589
  if (entry.name.startsWith(".")) continue;
61060
60590
  const filename = current.relativePath ? `${current.relativePath}/${entry.name}` : entry.name;
61061
- const filePath = join32(current.directory, entry.name);
60591
+ const filePath = join31(current.directory, entry.name);
61062
60592
  if (entry.isDirectory()) {
61063
60593
  pending.push({ directory: filePath, relativePath: filename });
61064
60594
  continue;
@@ -61083,7 +60613,7 @@ var CanvasService = class {
61083
60613
  if (!safe) return null;
61084
60614
  const { kind, mimeType } = classifyCanvasFilename(safe);
61085
60615
  for (const directory of await this.directories()) {
61086
- const filePath = join32(directory, safe);
60616
+ const filePath = join31(directory, safe);
61087
60617
  let sizeBytes = 0;
61088
60618
  let updatedAt = "";
61089
60619
  try {
@@ -61104,7 +60634,7 @@ var CanvasService = class {
61104
60634
  };
61105
60635
  }
61106
60636
  try {
61107
- const bytes = await readFile22(filePath);
60637
+ const bytes = await readFile21(filePath);
61108
60638
  return { filename: safe, kind, sizeBytes, mimeType, updatedAt, bytes };
61109
60639
  } catch {
61110
60640
  continue;
@@ -61264,16 +60794,16 @@ async function reconcileCanvasItems(filenames) {
61264
60794
  }
61265
60795
 
61266
60796
  // src/services/upload-chat-transcripts.ts
61267
- import { createReadStream } from "fs";
60797
+ import { createReadStream as createReadStream2 } from "fs";
61268
60798
  import { createHash as createHash8 } from "crypto";
61269
- import { readFile as readFile23, readdir as readdir10, stat as stat5 } from "fs/promises";
61270
- import { basename as basename4, join as join34 } from "path";
60799
+ import { readFile as readFile22, readdir as readdir9, stat as stat5 } from "fs/promises";
60800
+ import { basename as basename4, join as join33 } from "path";
61271
60801
 
61272
60802
  // src/services/chat/chat-senders.ts
61273
- import { join as join33 } from "path";
61274
- var CHAT_SENDERS_DIR = join33(ENGINE_DIR2, "chat-senders");
60803
+ import { join as join32 } from "path";
60804
+ var CHAT_SENDERS_DIR = join32(ENGINE_DIR2, "chat-senders");
61275
60805
  function chatMessageSendersFilePath(chatId) {
61276
- return join33(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
60806
+ return join32(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
61277
60807
  }
61278
60808
  function parseChatMessageSendersJsonl(content) {
61279
60809
  return content.split("\n").flatMap((line) => {
@@ -61306,7 +60836,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
61306
60836
  for (const dir of HISTORY_DIRS) {
61307
60837
  let entries;
61308
60838
  try {
61309
- entries = await readdir10(dir);
60839
+ entries = await readdir9(dir);
61310
60840
  } catch {
61311
60841
  continue;
61312
60842
  }
@@ -61316,7 +60846,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
61316
60846
  const chat = chatsById.get(chatId);
61317
60847
  if (!shouldFlushChatTranscript(dir, chat, chatsById)) continue;
61318
60848
  tasks.push(
61319
- uploadChatTranscript(chatId, join34(dir, entry), chat, capture).then((artifact) => {
60849
+ uploadChatTranscript(chatId, join33(dir, entry), chat, capture).then((artifact) => {
61320
60850
  flushed++;
61321
60851
  if (artifact && capture) revisions.push(artifact);
61322
60852
  }).catch((err) => {
@@ -61332,7 +60862,6 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
61332
60862
  async function uploadChatTranscript(chatId, filePath, chat, capture) {
61333
60863
  const { size } = await stat5(filePath);
61334
60864
  if (size === 0) return null;
61335
- const historyPages = createChatTranscriptPages(await readFile23(filePath));
61336
60865
  const metadata = chat ? {
61337
60866
  provider: chat.provider,
61338
60867
  ...chat.relayBaseProvider ? { relayBaseProvider: chat.relayBaseProvider } : {},
@@ -61346,21 +60875,23 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
61346
60875
  } : {};
61347
60876
  if (capture) {
61348
60877
  const hash = createHash8("sha256");
61349
- for await (const chunk of createReadStream(filePath)) hash.update(chunk);
60878
+ for await (const chunk of createReadStream2(filePath)) hash.update(chunk);
61350
60879
  metadata.captureId = capture.captureId;
61351
60880
  metadata.captureReason = capture.reason;
61352
60881
  metadata.sha256 = hash.digest("hex");
61353
60882
  }
61354
60883
  try {
61355
60884
  metadata.senders = parseChatMessageSendersJsonl(
61356
- await readFile23(chatMessageSendersFilePath(chatId), "utf-8")
60885
+ await readFile22(chatMessageSendersFilePath(chatId), "utf-8")
61357
60886
  );
61358
60887
  } catch (error) {
61359
60888
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) throw error;
61360
60889
  }
61361
- if (historyPages.index.codexAspTranscript || historyPages.index.removedMessageIds?.length) {
61362
- metadata.historyIndex = historyPages.index;
61363
- }
60890
+ metadata.historyIndex = {
60891
+ version: 2,
60892
+ sourceSize: size,
60893
+ ...chat?.removedMessageIds?.length ? { removedMessageIds: chat.removedMessageIds } : {}
60894
+ };
61364
60895
  const uploadRequest = { chatId, size, metadata };
61365
60896
  const prepareResponse = await monolithRequest("/v1/engine/chat-transcripts/upload-url", {
61366
60897
  body: uploadRequest
@@ -61369,25 +60900,11 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
61369
60900
  throw new Error(`prepare failed: ${prepareResponse.status} ${await prepareResponse.text()}`);
61370
60901
  }
61371
60902
  const prepareBody = await prepareResponse.json();
61372
- if (!isRecord(prepareBody) || typeof prepareBody.uploadUrl !== "string" && prepareBody.uploadUrl !== null || prepareBody.historyPagesUploadUrl !== void 0 && typeof prepareBody.historyPagesUploadUrl !== "string") {
60903
+ if (!isRecord(prepareBody) || typeof prepareBody.uploadUrl !== "string" && prepareBody.uploadUrl !== null) {
61373
60904
  throw new Error("prepare failed: invalid response");
61374
60905
  }
61375
60906
  if (prepareBody.uploadUrl === null) return null;
61376
- const uploads = [putPresignedFile(prepareBody.uploadUrl, filePath, size, "application/x-ndjson")];
61377
- const historyPagesSize = getChatTranscriptHistorySize(historyPages.index);
61378
- if (historyPagesSize > 0) {
61379
- if (typeof prepareBody.historyPagesUploadUrl !== "string") {
61380
- throw new Error("prepare failed: missing history pages upload URL");
61381
- }
61382
- uploads.push(fetch(prepareBody.historyPagesUploadUrl, {
61383
- method: "PUT",
61384
- headers: { "content-type": "application/x-ndjson" },
61385
- body: new Uint8Array(historyPages.bytes).buffer
61386
- }).then((response) => {
61387
- if (!response.ok) throw new Error(`history pages upload failed: ${response.status}`);
61388
- }));
61389
- }
61390
- await Promise.all(uploads);
60907
+ await putPresignedFile(prepareBody.uploadUrl, filePath, size, "application/x-ndjson");
61391
60908
  const finalizeResponse = await monolithRequest("/v1/engine/chat-transcripts/finalize", {
61392
60909
  body: uploadRequest
61393
60910
  });
@@ -61403,9 +60920,9 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
61403
60920
  }
61404
60921
 
61405
60922
  // src/services/upload-engine-logs.ts
61406
- import { createReadStream as createReadStream2 } from "fs";
61407
- import { readdir as readdir11, stat as stat6 } from "fs/promises";
61408
- import { join as join35 } from "path";
60923
+ import { createReadStream as createReadStream3 } from "fs";
60924
+ import { readdir as readdir10, stat as stat6 } from "fs/promises";
60925
+ import { join as join34 } from "path";
61409
60926
  var MAX_ENGINE_LOG_FLUSH_SESSIONS = 10;
61410
60927
  var MAX_ENGINE_LOG_FLUSH_BYTES = 5 * 1024 * 1024;
61411
60928
  var ENGINE_LOG_FLUSH_TIMEOUT_MS = 2e4;
@@ -61415,7 +60932,7 @@ async function flushAllEngineLogs() {
61415
60932
  let failed = 0;
61416
60933
  const deadline = Date.now() + ENGINE_LOG_FLUSH_TIMEOUT_MS;
61417
60934
  await runBeforeDeadline((signal) => engineLogger.flush(signal), deadline);
61418
- const files = await runBeforeDeadline(() => readdir11(LOG_DIR), deadline).catch(() => []);
60935
+ const files = await runBeforeDeadline(() => readdir10(LOG_DIR), deadline).catch(() => []);
61419
60936
  const currentFilename = engineLogger.sessionId ? `${engineLogger.sessionId}.log` : null;
61420
60937
  const filenames = files.filter((filename) => {
61421
60938
  if (!filename.endsWith(".log")) return false;
@@ -61434,7 +60951,7 @@ async function flushAllEngineLogs() {
61434
60951
  const candidates = (await Promise.all(filenames.slice(0, MAX_ENGINE_LOG_FLUSH_SESSIONS).map(async (filename) => {
61435
60952
  try {
61436
60953
  const sessionId = filename.slice(0, -".log".length);
61437
- const filePath = join35(LOG_DIR, filename);
60954
+ const filePath = join34(LOG_DIR, filename);
61438
60955
  const fileStat = await runBeforeDeadline(() => stat6(filePath), deadline);
61439
60956
  if (!fileStat.isFile()) {
61440
60957
  skipped++;
@@ -61467,7 +60984,7 @@ async function flushAllEngineLogs() {
61467
60984
  const content = await runBeforeDeadline(async (signal) => {
61468
60985
  if (candidate.length === 0) return Buffer.alloc(0);
61469
60986
  const chunks = [];
61470
- for await (const chunk of createReadStream2(candidate.filePath, {
60987
+ for await (const chunk of createReadStream3(candidate.filePath, {
61471
60988
  start: candidate.offset,
61472
60989
  end: candidate.offset + candidate.length - 1,
61473
60990
  signal
@@ -61531,7 +61048,7 @@ async function uploadEngineLog(input, timeoutMs) {
61531
61048
 
61532
61049
  // src/services/chat/fork-handoff.ts
61533
61050
  import { mkdir as mkdir20, open as open3, writeFile as writeFile11 } from "fs/promises";
61534
- import { join as join36 } from "path";
61051
+ import { join as join35 } from "path";
61535
61052
  var EXCERPT_MAX_CHARS = 24e3;
61536
61053
  var MAX_SANITIZED_SOURCE_BYTES = 32 * 1024 * 1024;
61537
61054
  function unansweredRequest(blocks) {
@@ -61566,7 +61083,7 @@ function tailBlocks(blocks, maxChars) {
61566
61083
  return { excerpt, truncated: start > 0 };
61567
61084
  }
61568
61085
  async function buildForkHandoff(input) {
61569
- const rawHistoryPath = join36(HISTORY_DIR_BY_PROVIDER[input.sourceProvider], `${input.sourceChatId}.jsonl`);
61086
+ const rawHistoryPath = join35(HISTORY_DIR_BY_PROVIDER[input.sourceProvider], `${input.sourceChatId}.jsonl`);
61570
61087
  const { content, truncated: sourceTruncated } = await readHistoryTail(rawHistoryPath, MAX_SANITIZED_SOURCE_BYTES);
61571
61088
  let blocks = canonicalizeChatTranscriptBlocks(content, input.executionProvider, input.senders);
61572
61089
  if (input.turnLimit !== void 0) {
@@ -61574,9 +61091,9 @@ async function buildForkHandoff(input) {
61574
61091
  const start = blocks.findLastIndex((block) => block.startsWith("## User") && ++turns === input.turnLimit);
61575
61092
  blocks = blocks.slice(Math.max(0, start));
61576
61093
  }
61577
- const forkDir = join36(FORKS_DIR, input.targetChatId);
61094
+ const forkDir = join35(FORKS_DIR, input.targetChatId);
61578
61095
  await mkdir20(forkDir, { recursive: true });
61579
- const transcriptPath = join36(forkDir, "source-transcript.md");
61096
+ const transcriptPath = join35(forkDir, "source-transcript.md");
61580
61097
  const header = sourceTruncated ? `_This file was generated from only the most recent ${MAX_SANITIZED_SOURCE_BYTES / 1024 / 1024} MB of the source chat.${input.turnLimit === void 0 ? ` The complete raw event log is at ${rawHistoryPath}; grep it for anything earlier.` : ""}_
61581
61098
 
61582
61099
  ` : "";
@@ -61624,25 +61141,7 @@ function isSameAcceptedUserEvent(event, acceptedEvent) {
61624
61141
  { content: acceptedMessage, timestamp: acceptedEvent.timestamp }
61625
61142
  );
61626
61143
  }
61627
- function getCodexTranscriptUserMessages(transcript) {
61628
- if (!transcript) return [];
61629
- const messages = [];
61630
- for (const turn of transcript.turns) {
61631
- for (const item of turn.items) {
61632
- if (item.type === "userMessage" && item.content.trim()) {
61633
- messages.push(item.content);
61634
- }
61635
- }
61636
- }
61637
- return messages;
61638
- }
61639
- function getCodexTranscriptFromEvent(event) {
61640
- if (event.type !== CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE) return null;
61641
- if (isCodexAspTranscript(event.payload.transcript)) return event.payload.transcript;
61642
- if (!isCodexAspTranscriptDelta(event.payload.transcriptDelta)) return null;
61643
- return applyCodexAspTranscriptDelta(null, event.payload.transcriptDelta);
61644
- }
61645
- function terminalErrorsFromEvent(event, provider, codexTranscript) {
61144
+ function terminalErrorsFromEvent(event, provider) {
61646
61145
  if (event.type === "claude-result") {
61647
61146
  const payload = coerceClaudeResultPayload(event.payload);
61648
61147
  if (!isClaudeResultError(payload)) return null;
@@ -61655,21 +61154,15 @@ function terminalErrorsFromEvent(event, provider, codexTranscript) {
61655
61154
  if (event.type === "codex-asp-error") {
61656
61155
  return [typeof event.payload.message === "string" ? event.payload.message : "Codex run failed"];
61657
61156
  }
61658
- const failedCodexTurn = codexTranscript?.turns.at(-1);
61659
- if (failedCodexTurn?.status === "failed") {
61660
- const errors2 = failedCodexTurn.items.flatMap((item) => item.type === "error" ? [item.message] : []);
61661
- return errors2.length > 0 ? errors2 : ["Codex run failed"];
61157
+ const codexTurn = codexTurnFromEvent(event);
61158
+ if (codexTurn) {
61159
+ return normalizeCodexAspTranscriptStatus(codexTurn.status) === "failed" ? [codexTurn.error ?? "Codex run failed"] : void 0;
61662
61160
  }
61663
61161
  const mayContainProviderError = provider === "cursor" && event.type === "cursor-error" || provider === "opencode" && (event.type === "opencode-error" || event.type === "opencode-message.updated") || provider === "pi" && (event.type === "pi-error" || event.type === "pi-message_end");
61664
61162
  if (!mayContainProviderError) return void 0;
61665
61163
  const errors = parseAgentEvents([event], provider).flatMap((message) => message.type === "error" ? [message.message] : []);
61666
61164
  return errors.length > 0 ? errors : void 0;
61667
61165
  }
61668
- function acceptedEventInCodexTranscript(acceptedEvent, transcript) {
61669
- const message = getUserMessage(acceptedEvent);
61670
- if (!message) return false;
61671
- return getCodexTranscriptUserMessages(transcript).includes(message);
61672
- }
61673
61166
  var LAST_MESSAGE_PREVIEW_MAX = 200;
61674
61167
  var MAX_ACCEPTED_SEND_RESPONSES = 50;
61675
61168
  var MAX_REMOVED_MESSAGE_IDS = 6e3;
@@ -62060,9 +61553,13 @@ var ChatService = class {
62060
61553
  event: acceptedEvent,
62061
61554
  ...recordedSender ? { sender: recordedSender } : {}
62062
61555
  }
61556
+ }).catch((error) => {
61557
+ console.error("[ChatService] Failed to publish accepted turn:", { chatId, messageId: result.messageId, error });
62063
61558
  });
62064
61559
  if (chat.persisted.model !== previousModel) {
62065
- await this.publish({ type: "chat.updated", payload: { chat: this.toSummary(chat) } });
61560
+ await this.publish({ type: "chat.updated", payload: { chat: this.toSummary(chat) } }).catch((error) => {
61561
+ console.error("[ChatService] Failed to publish accepted chat update:", { chatId, error });
61562
+ });
62066
61563
  }
62067
61564
  }
62068
61565
  async appendSender(chatId, sender) {
@@ -62073,7 +61570,7 @@ var ChatService = class {
62073
61570
  }
62074
61571
  }
62075
61572
  async readSenders(chatId) {
62076
- return readFile24(chatMessageSendersFilePath(chatId), "utf-8").then(parseChatMessageSendersJsonl).catch((error) => {
61573
+ return readFile23(chatMessageSendersFilePath(chatId), "utf-8").then(parseChatMessageSendersJsonl).catch((error) => {
62077
61574
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return [];
62078
61575
  console.error("[ChatService] Failed to read sender records:", error);
62079
61576
  return [];
@@ -62326,17 +61823,24 @@ var ChatService = class {
62326
61823
  return descendants;
62327
61824
  }
62328
61825
  async deleteHistoryFile(persisted) {
62329
- const historyPath = join37(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`);
61826
+ const historyPath = join36(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`);
62330
61827
  await Promise.all([
62331
61828
  historyPath,
62332
61829
  `${historyPath}.pages.jsonl`,
62333
61830
  `${historyPath}.pages.index.json`
62334
61831
  ].map((path7) => rm4(path7, { force: true })));
62335
61832
  await rm4(chatMessageSendersFilePath(persisted.id), { force: true });
62336
- await rm4(join37(FORKS_DIR, persisted.id), { recursive: true, force: true });
61833
+ await rm4(join36(FORKS_DIR, persisted.id), { recursive: true, force: true });
62337
61834
  }
62338
61835
  /** Resolves once the target chat exists; the handoff and its first turn run in the background. */
62339
61836
  async forkChat(sourceChatId, request, trigger = "manual", reason) {
61837
+ const target = await this.createForkTarget(sourceChatId, request, trigger, reason);
61838
+ this.runFork(target.id, request).catch((error) => {
61839
+ console.error("[ChatService] Fork failed:", { sourceChatId, targetChatId: target.id, error });
61840
+ });
61841
+ return target;
61842
+ }
61843
+ async createForkTarget(sourceChatId, request, trigger, reason) {
62340
61844
  const source = this.requireChat(sourceChatId);
62341
61845
  const relayBaseProvider = await resolveRelayBaseProvider(request.provider, request.relayBaseProvider);
62342
61846
  const executionProvider = request.provider === "relay" ? relayBaseProvider : request.provider;
@@ -62350,7 +61854,7 @@ var ChatService = class {
62350
61854
  state: "preparing",
62351
61855
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
62352
61856
  };
62353
- const target = await this.createChat({
61857
+ return this.createChat({
62354
61858
  id: request.id,
62355
61859
  provider: request.provider,
62356
61860
  ...relayBaseProvider ? { relayBaseProvider } : {},
@@ -62358,10 +61862,6 @@ var ChatService = class {
62358
61862
  clientRequestId: request.clientRequestId,
62359
61863
  fork
62360
61864
  });
62361
- this.runFork(target.id, request).catch((error) => {
62362
- console.error("[ChatService] Fork failed:", { sourceChatId, targetChatId: target.id, error });
62363
- });
62364
- return target;
62365
61865
  }
62366
61866
  async createForkHandoffMessage(targetChatId, options) {
62367
61867
  const source = this.requireChat(options.source.chatId);
@@ -62376,10 +61876,10 @@ var ChatService = class {
62376
61876
  });
62377
61877
  return formatChatForkHandoffMessage({ ...handoff, ...options });
62378
61878
  }
62379
- async runFork(targetId, request) {
61879
+ async runFork(targetId, request, errorNotificationTarget) {
62380
61880
  const target = this.chats.get(targetId);
62381
61881
  const fork = target?.persisted.fork;
62382
- if (!target || !fork) return;
61882
+ if (!target || !fork) return false;
62383
61883
  try {
62384
61884
  if (!isAgentAvailable(getChatExecutionProvider(target.persisted))) throw new ChatProviderUnavailableError(request.provider);
62385
61885
  const message = await this.createForkHandoffMessage(targetId, {
@@ -62388,7 +61888,7 @@ var ChatService = class {
62388
61888
  reason: fork.reason,
62389
61889
  message: request.message
62390
61890
  });
62391
- if (target.persisted.deletedAt) return;
61891
+ if (target.persisted.deletedAt) return false;
62392
61892
  await this.sendMessage(targetId, {
62393
61893
  message,
62394
61894
  model: request.model,
@@ -62398,15 +61898,19 @@ var ChatService = class {
62398
61898
  senderEmail: request.senderEmail,
62399
61899
  senderDisplayName: request.senderDisplayName,
62400
61900
  senderAvatarUrl: request.senderAvatarUrl,
61901
+ errorNotificationTarget,
62401
61902
  idempotencyKey: `chat-fork:${targetId}`
62402
61903
  });
62403
- await this.updateFork(target, { state: "ready", completedAt: (/* @__PURE__ */ new Date()).toISOString() });
62404
61904
  } catch (error) {
62405
- if (target.persisted.deletedAt) return;
61905
+ if (target.persisted.deletedAt) return false;
62406
61906
  const message = error instanceof Error ? error.message : String(error);
62407
61907
  await this.updateFork(target, { state: "failed", error: message.slice(0, 500), completedAt: (/* @__PURE__ */ new Date()).toISOString() });
62408
61908
  throw error;
62409
61909
  }
61910
+ await this.updateFork(target, { state: "ready", completedAt: (/* @__PURE__ */ new Date()).toISOString() }).catch((error) => {
61911
+ console.error("[ChatService] Failed to finalize accepted fork:", { targetId, error });
61912
+ });
61913
+ return true;
62410
61914
  }
62411
61915
  async updateFork(chat, patch) {
62412
61916
  if (!chat.persisted.fork) return;
@@ -62430,11 +61934,11 @@ var ChatService = class {
62430
61934
  }
62431
61935
  async autoForkAfterQuota(chat, exhaustion) {
62432
61936
  const target = exhaustion.forkTo;
62433
- if (!target || chat.persisted.deletedAt) return;
61937
+ if (!target || chat.persisted.deletedAt) return false;
62434
61938
  const alreadyForked = [...this.chats.values()].some((other) => other.persisted.fork?.trigger === "auto" && other.persisted.fork.source.chatId === chat.persisted.id && !other.persisted.deletedAt);
62435
- if (alreadyForked) return;
61939
+ if (alreadyForked) return false;
62436
61940
  const last = chat.lastSendRequest;
62437
- await this.forkChat(chat.persisted.id, {
61941
+ const request = {
62438
61942
  provider: target.provider,
62439
61943
  relayBaseProvider: target.relayBaseProvider,
62440
61944
  model: target.model,
@@ -62444,7 +61948,13 @@ var ChatService = class {
62444
61948
  senderEmail: last?.senderEmail,
62445
61949
  senderDisplayName: last?.senderDisplayName,
62446
61950
  senderAvatarUrl: last?.senderAvatarUrl
62447
- }, "auto", exhaustion.reason);
61951
+ };
61952
+ const fallback = await this.createForkTarget(chat.persisted.id, request, "auto", exhaustion.reason);
61953
+ const accepted = await this.runFork(fallback.id, request, chat.activeErrorNotificationTarget ?? void 0);
61954
+ if (!accepted) return false;
61955
+ chat.lastTurnErrors = null;
61956
+ chat.activeErrorNotificationTarget = null;
61957
+ return true;
62448
61958
  }
62449
61959
  async getChatHistory(chatId, page2 = {}) {
62450
61960
  const chat = this.requireChat(chatId);
@@ -62458,13 +61968,13 @@ var ChatService = class {
62458
61968
  chat.persisted.removedMessageIds
62459
61969
  );
62460
61970
  for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
62461
- if (acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) || historyEvents.some((event) => isSameAcceptedUserEvent(event, acceptedEvent))) {
61971
+ if (historyEvents.some((event) => isSameAcceptedUserEvent(event, acceptedEvent))) {
62462
61972
  chat.acceptedUserEvents.delete(messageId);
62463
61973
  }
62464
61974
  }
62465
61975
  const isLatestPage = isLatestChatHistoryPage(page2);
62466
61976
  const queuedMessageIds = new Set(chat.provider.getQueue().map((message) => message.id));
62467
- const acceptedEvents = (isLatestPage ? [...chat.acceptedUserEvents.entries()] : []).filter(([messageId]) => messageId === chat.activeMessageId || !chat.hasActiveTurn && chat.pendingMessageIds[0] === messageId && !queuedMessageIds.has(messageId)).map(([, acceptedEvent]) => acceptedEvent).filter((acceptedEvent) => !acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) && !historyEvents.some((event) => isSameAcceptedUserEvent(event, acceptedEvent)));
61977
+ const acceptedEvents = (isLatestPage ? [...chat.acceptedUserEvents.entries()] : []).filter(([messageId]) => messageId === chat.activeMessageId || !chat.hasActiveTurn && chat.pendingMessageIds[0] === messageId && !queuedMessageIds.has(messageId)).map(([, acceptedEvent]) => acceptedEvent).filter((acceptedEvent) => !historyEvents.some((event) => isSameAcceptedUserEvent(event, acceptedEvent)));
62468
61978
  const events = excludeQueuedAcceptedEvents(
62469
61979
  [...historyEvents, ...acceptedEvents].sort((a, b) => getEventTimestampMs(a) - getEventTimestampMs(b)),
62470
61980
  [],
@@ -62498,7 +62008,6 @@ var ChatService = class {
62498
62008
  thread_id: history.thread_id,
62499
62009
  events,
62500
62010
  removedMessageIds: chat.persisted.removedMessageIds,
62501
- codexAspTranscript: history.codexAspTranscript ?? null,
62502
62011
  goal: history.goal ?? chat.provider.getGoal?.() ?? null,
62503
62012
  senders
62504
62013
  }, page2);
@@ -62512,14 +62021,17 @@ var ChatService = class {
62512
62021
  }
62513
62022
  return count;
62514
62023
  }
62024
+ toTranscriptSource(chat) {
62025
+ return { ...this.toSummary(chat), removedMessageIds: chat.persisted.removedMessageIds };
62026
+ }
62515
62027
  async flushAllChatTranscripts() {
62516
62028
  return flushAllChatTranscripts(new Map(
62517
- [...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
62029
+ [...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toTranscriptSource(chat)])
62518
62030
  ));
62519
62031
  }
62520
62032
  async flushAllWorkspaceArtifacts(capture) {
62521
62033
  const chatsById = new Map(
62522
- [...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
62034
+ [...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toTranscriptSource(chat)])
62523
62035
  );
62524
62036
  const [chatTranscripts, canvas, repoState, engineLogs, [chatActivity]] = await Promise.all([
62525
62037
  flushAllChatTranscripts(chatsById, capture),
@@ -62581,7 +62093,7 @@ var ChatService = class {
62581
62093
  }
62582
62094
  const baseProvider = getChatExecutionProvider(persisted);
62583
62095
  const additionalInstructions = persisted.provider === "relay" ? buildRelayInstructions(this.workingDirectory) : void 0;
62584
- const historyFilePath = join37(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`);
62096
+ const historyFilePath = join36(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`);
62585
62097
  const managerOptions = {
62586
62098
  provider: baseProvider,
62587
62099
  workingDirectory: this.workingDirectory,
@@ -62738,15 +62250,16 @@ var ChatService = class {
62738
62250
  });
62739
62251
  }
62740
62252
  this.agentTokenUsageTracker.noteAgentEvent(chatId, event);
62741
- const codexTranscript = getCodexTranscriptFromEvent(event);
62742
62253
  if (chat.hasActiveTurn) {
62743
- const displayMessages = codexTranscript ? parseLatestCodexAspTranscriptTurn(codexTranscript) : parseDisplayMessages([event], getChatExecutionProvider(chat.persisted), null, {
62744
- filter: false,
62745
- parentToolUseId: typeof event.payload.parent_tool_use_id === "string" ? event.payload.parent_tool_use_id : null
62746
- });
62747
- this.agentChatActivityTrackerService.noteSkillMcpCalls(chatId, displayMessages);
62254
+ this.agentChatActivityTrackerService.noteSkillMcpCalls(
62255
+ chatId,
62256
+ parseDisplayMessages([event], getChatExecutionProvider(chat.persisted), {
62257
+ filter: false,
62258
+ parentToolUseId: typeof event.payload.parent_tool_use_id === "string" ? event.payload.parent_tool_use_id : null
62259
+ })
62260
+ );
62748
62261
  }
62749
- const terminalErrors = terminalErrorsFromEvent(event, getChatExecutionProvider(chat.persisted), codexTranscript);
62262
+ const terminalErrors = terminalErrorsFromEvent(event, getChatExecutionProvider(chat.persisted));
62750
62263
  if (terminalErrors !== void 0) {
62751
62264
  chat.lastTurnErrors = terminalErrors;
62752
62265
  }
@@ -62772,13 +62285,6 @@ var ChatService = class {
62772
62285
  }
62773
62286
  }
62774
62287
  }
62775
- if (codexTranscript) {
62776
- for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
62777
- if (acceptedEventInCodexTranscript(acceptedEvent, codexTranscript)) {
62778
- chat.acceptedUserEvents.delete(messageId);
62779
- }
62780
- }
62781
- }
62782
62288
  if (event.type !== CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE) {
62783
62289
  this.touch(chat);
62784
62290
  this.observeCurrentBranches(chat).catch(() => {
@@ -62827,25 +62333,29 @@ var ChatService = class {
62827
62333
  this.endTrackedTurn(chatId);
62828
62334
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
62829
62335
  const processing = chat.provider.getQueue().length > 0 || (chat.provider.hasActiveBackgroundTasks?.() ?? false);
62830
- this.publishTurnCompleted(chat, completedAt, processing);
62336
+ const exhaustion = chat.quotaExhaustion;
62337
+ chat.quotaExhaustion = null;
62831
62338
  uploadChatTranscript(
62832
62339
  chatId,
62833
- join37(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
62834
- this.toSummary(chat)
62340
+ join36(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
62341
+ this.toTranscriptSource(chat)
62835
62342
  ).catch((err) => {
62836
62343
  console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
62837
62344
  });
62838
62345
  flushAllCanvasItems().catch((err) => {
62839
62346
  console.error("[ChatService] Failed to upload canvas items:", { chatId, err });
62840
62347
  });
62841
- await this.publishAgentTurnCompleteWebhook(chat, completedAt, processing);
62842
- const exhaustion = chat.quotaExhaustion;
62843
- chat.quotaExhaustion = null;
62348
+ let forked = false;
62844
62349
  if (exhaustion) {
62845
- this.autoForkAfterQuota(chat, exhaustion).catch((error) => {
62350
+ try {
62351
+ forked = await this.autoForkAfterQuota(chat, exhaustion);
62352
+ } catch (error) {
62846
62353
  console.error("[ChatService] Automatic fork after quota limit failed:", { chatId, error });
62847
- });
62354
+ }
62848
62355
  }
62356
+ const stillProcessing = processing || forked;
62357
+ this.publishTurnCompleted(chat, completedAt, stillProcessing);
62358
+ await this.publishAgentTurnCompleteWebhook(chat, completedAt, stillProcessing);
62849
62359
  this.notifyWorkspaceAgentIdle();
62850
62360
  }
62851
62361
  publishTurnCompleted(chat, completedAt, processing) {
@@ -62878,7 +62388,7 @@ var ChatService = class {
62878
62388
  }
62879
62389
  async loadChats() {
62880
62390
  try {
62881
- const content = await readFile24(CHATS_FILE, "utf-8");
62391
+ const content = await readFile23(CHATS_FILE, "utf-8");
62882
62392
  return parsePersistedChatsContent(content);
62883
62393
  } catch (error) {
62884
62394
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -62887,13 +62397,13 @@ var ChatService = class {
62887
62397
  const quarantinePath = corruptChatsFilePath();
62888
62398
  console.error(`[ChatService] Failed to load ${CHATS_FILE}; quarantining and trying backup:`, error);
62889
62399
  try {
62890
- await rename4(CHATS_FILE, quarantinePath);
62400
+ await rename5(CHATS_FILE, quarantinePath);
62891
62401
  console.error(`[ChatService] Quarantined corrupt chats file at ${quarantinePath}`);
62892
62402
  } catch (renameError) {
62893
62403
  console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
62894
62404
  }
62895
62405
  try {
62896
- const backupContent = await readFile24(CHATS_BACKUP_FILE, "utf-8");
62406
+ const backupContent = await readFile23(CHATS_BACKUP_FILE, "utf-8");
62897
62407
  return parsePersistedChatsContent(backupContent);
62898
62408
  } catch (backupError) {
62899
62409
  if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
@@ -62988,14 +62498,14 @@ var ChatService = class {
62988
62498
 
62989
62499
  // src/services/repo-file-service.ts
62990
62500
  import { execFile as execFile7 } from "child_process";
62991
- import { readFile as readFile25 } from "fs/promises";
62501
+ import { readFile as readFile24 } from "fs/promises";
62992
62502
  import { extname as extname3 } from "path";
62993
62503
 
62994
62504
  // src/services/language-server-service.ts
62995
62505
  import { spawn as spawn9 } from "child_process";
62996
62506
  import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
62997
62507
  import { createRequire as createRequire6 } from "module";
62998
- import { delimiter as delimiter2, dirname as dirname19, join as join38, relative as relative2 } from "path";
62508
+ import { delimiter as delimiter2, dirname as dirname19, join as join37, relative as relative2 } from "path";
62999
62509
  import { setTimeout as setTimeout2 } from "timers/promises";
63000
62510
  import { fileURLToPath as fileURLToPath6, pathToFileURL } from "url";
63001
62511
  import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node.js";
@@ -63114,7 +62624,7 @@ var LanguageServerService = class {
63114
62624
  if (config.package && config.bin) command = process.execPath;
63115
62625
  else if (config.command) {
63116
62626
  const commandName = config.command;
63117
- command = process.env.PATH?.split(delimiter2).map((directory) => join38(directory, commandName)).find(existsSync17);
62627
+ command = process.env.PATH?.split(delimiter2).map((directory) => join37(directory, commandName)).find(existsSync17);
63118
62628
  }
63119
62629
  const args = config.package && config.bin ? [packageBin(config.package, config.bin), ...config.args] : config.args;
63120
62630
  if (!command) return null;
@@ -63285,7 +62795,7 @@ var RepoFileService = class {
63285
62795
  sizeBytes,
63286
62796
  binary: true,
63287
62797
  tooLarge: false,
63288
- base64: (await readFile25(fullPath)).toString("base64"),
62798
+ base64: (await readFile24(fullPath)).toString("base64"),
63289
62799
  mimeType
63290
62800
  };
63291
62801
  }
@@ -63310,7 +62820,7 @@ var RepoFileService = class {
63310
62820
  tooLarge: true
63311
62821
  };
63312
62822
  }
63313
- const content = await readFile25(fullPath, "utf-8");
62823
+ const content = await readFile24(fullPath, "utf-8");
63314
62824
  return {
63315
62825
  repoName,
63316
62826
  path: filePath,
@@ -63342,7 +62852,7 @@ var RepoFileService = class {
63342
62852
  async inspectSymbol(repoName, filePath, line, character, action) {
63343
62853
  const resolved = await this.resolveFile(repoName, filePath);
63344
62854
  if (!resolved || line < 1 || character < 0 || isBinaryExtension(filePath)) return null;
63345
- const content = await readFile25(resolved.fullPath, "utf8");
62855
+ const content = await readFile24(resolved.fullPath, "utf8");
63346
62856
  const sourceLine = content.split("\n")[line - 1];
63347
62857
  if (sourceLine === void 0) return null;
63348
62858
  const symbolMatch = Array.from(sourceLine.matchAll(/[\p{L}_$][\p{L}\p{N}_$]*/gu)).find((match) => character >= match.index && character <= match.index + match[0].length);
@@ -63455,21 +62965,21 @@ var RepoFileService = class {
63455
62965
  // src/v1-routes.ts
63456
62966
  import { Hono } from "hono";
63457
62967
  import { z as z6 } from "zod";
63458
- import { readdir as readdir13, stat as stat7, readFile as readFile28 } from "fs/promises";
63459
- import { join as join41, resolve as resolve5 } from "path";
62968
+ import { readdir as readdir12, stat as stat7, readFile as readFile27 } from "fs/promises";
62969
+ import { join as join40, resolve as resolve5 } from "path";
63460
62970
 
63461
62971
  // src/services/warm-hooks-service.ts
63462
62972
  import { spawn as spawn10 } from "child_process";
63463
- import { readFile as readFile27 } from "fs/promises";
62973
+ import { readFile as readFile26 } from "fs/promises";
63464
62974
  import { existsSync as existsSync18 } from "fs";
63465
- import { join as join40 } from "path";
62975
+ import { join as join39 } from "path";
63466
62976
 
63467
62977
  // src/services/warm-hook-logs-service.ts
63468
- import { mkdir as mkdir22, readFile as readFile26, writeFile as writeFile12, readdir as readdir12, appendFile as appendFile6, unlink as unlink4 } from "fs/promises";
62978
+ import { mkdir as mkdir22, readFile as readFile25, writeFile as writeFile12, readdir as readdir11, appendFile as appendFile6, unlink as unlink4 } from "fs/promises";
63469
62979
  import { homedir as homedir14 } from "os";
63470
- import { join as join39 } from "path";
63471
- var LOGS_DIR2 = join39(homedir14(), ".replicas", "warm-hook-logs");
63472
- var CURRENT_RUN_LOG = join39(LOGS_DIR2, "current-run.log");
62980
+ import { join as join38 } from "path";
62981
+ var LOGS_DIR2 = join38(homedir14(), ".replicas", "warm-hook-logs");
62982
+ var CURRENT_RUN_LOG = join38(LOGS_DIR2, "current-run.log");
63473
62983
  var GLOBAL_FILENAME = "global.json";
63474
62984
  function withPreview2(stored) {
63475
62985
  const preview = buildHookOutputPreview(stored.output);
@@ -63486,7 +62996,7 @@ var WarmHookLogsService = class {
63486
62996
  hookName: "organization",
63487
62997
  ...entry
63488
62998
  };
63489
- await writeFile12(join39(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
62999
+ await writeFile12(join38(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
63490
63000
  `, "utf-8");
63491
63001
  }
63492
63002
  async saveEnvironmentHookLog(entry) {
@@ -63496,7 +63006,7 @@ var WarmHookLogsService = class {
63496
63006
  hookName: "environment",
63497
63007
  ...entry
63498
63008
  };
63499
- await writeFile12(join39(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
63009
+ await writeFile12(join38(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
63500
63010
  `, "utf-8");
63501
63011
  }
63502
63012
  async saveRepoHookLog(repoName, entry) {
@@ -63506,13 +63016,13 @@ var WarmHookLogsService = class {
63506
63016
  hookName: repoName,
63507
63017
  ...entry
63508
63018
  };
63509
- await writeFile12(join39(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
63019
+ await writeFile12(join38(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
63510
63020
  `, "utf-8");
63511
63021
  }
63512
63022
  async getAllLogs() {
63513
63023
  let files;
63514
63024
  try {
63515
- files = await readdir12(LOGS_DIR2);
63025
+ files = await readdir11(LOGS_DIR2);
63516
63026
  } catch (err) {
63517
63027
  if (err.code === "ENOENT") {
63518
63028
  return [];
@@ -63525,7 +63035,7 @@ var WarmHookLogsService = class {
63525
63035
  continue;
63526
63036
  }
63527
63037
  try {
63528
- const raw = await readFile26(join39(LOGS_DIR2, file), "utf-8");
63038
+ const raw = await readFile25(join38(LOGS_DIR2, file), "utf-8");
63529
63039
  const stored = JSON.parse(raw);
63530
63040
  logs.push(withPreview2(stored));
63531
63041
  } catch {
@@ -63554,7 +63064,7 @@ var WarmHookLogsService = class {
63554
63064
  }
63555
63065
  async getCurrentRunLog() {
63556
63066
  try {
63557
- return await readFile26(CURRENT_RUN_LOG, "utf-8");
63067
+ return await readFile25(CURRENT_RUN_LOG, "utf-8");
63558
63068
  } catch (err) {
63559
63069
  if (err.code === "ENOENT") return null;
63560
63070
  throw err;
@@ -63563,7 +63073,7 @@ var WarmHookLogsService = class {
63563
63073
  async getFullOutput(hookType, hookName) {
63564
63074
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
63565
63075
  try {
63566
- const raw = await readFile26(join39(LOGS_DIR2, filename), "utf-8");
63076
+ const raw = await readFile25(join38(LOGS_DIR2, filename), "utf-8");
63567
63077
  const stored = JSON.parse(raw);
63568
63078
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
63569
63079
  return null;
@@ -63582,12 +63092,12 @@ var warmHookLogsService = new WarmHookLogsService();
63582
63092
  // src/services/warm-hooks-service.ts
63583
63093
  async function readRepoWarmHook(repoPath) {
63584
63094
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
63585
- const configPath = join40(repoPath, filename);
63095
+ const configPath = join39(repoPath, filename);
63586
63096
  if (!existsSync18(configPath)) {
63587
63097
  continue;
63588
63098
  }
63589
63099
  try {
63590
- const raw = await readFile27(configPath, "utf-8");
63100
+ const raw = await readFile26(configPath, "utf-8");
63591
63101
  const config = parseReplicasConfigString(raw, filename);
63592
63102
  if (!config.warmHook) {
63593
63103
  return null;
@@ -64912,11 +64422,11 @@ data: ${JSON.stringify("Terminal session not found")}
64912
64422
  });
64913
64423
  app2.get("/logs", async (c) => {
64914
64424
  try {
64915
- const files = await readdir13(LOG_DIR).catch(() => []);
64425
+ const files = await readdir12(LOG_DIR).catch(() => []);
64916
64426
  const logFiles = files.filter((f) => f.endsWith(".log"));
64917
64427
  const sessions = await Promise.all(
64918
64428
  logFiles.map(async (filename) => {
64919
- const filePath = join41(LOG_DIR, filename);
64429
+ const filePath = join40(LOG_DIR, filename);
64920
64430
  const fileStat = await stat7(filePath);
64921
64431
  const sessionId = filename.replace(/\.log$/, "");
64922
64432
  return {
@@ -64951,7 +64461,7 @@ data: ${JSON.stringify("Terminal session not found")}
64951
64461
  }
64952
64462
  let content;
64953
64463
  try {
64954
- content = await readFile28(filePath, "utf-8");
64464
+ content = await readFile27(filePath, "utf-8");
64955
64465
  } catch {
64956
64466
  return c.json(jsonError("Log session not found"), 404);
64957
64467
  }