replicas-engine 0.1.215 → 0.1.217

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/src/index.js +968 -331
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -20,6 +20,17 @@ import { readFileSync } from "fs";
20
20
  import { homedir } from "os";
21
21
  import { join } from "path";
22
22
 
23
+ // ../shared/src/agent.ts
24
+ var CODEX_REASONING_EFFORT_BY_THINKING_LEVEL = {
25
+ low: "low",
26
+ medium: "medium",
27
+ high: "high",
28
+ max: "xhigh"
29
+ };
30
+ function codexReasoningEffortForThinkingLevel(thinkingLevel) {
31
+ return thinkingLevel ? CODEX_REASONING_EFFORT_BY_THINKING_LEVEL[thinkingLevel] : void 0;
32
+ }
33
+
23
34
  // ../shared/src/event.ts
24
35
  var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
25
36
  var COMPACTION_STATUS_EVENT_TYPE = "compaction-status";
@@ -115,6 +126,10 @@ function detectLanguageByPath(filePath) {
115
126
  }
116
127
 
117
128
  // ../shared/src/context-usage.ts
129
+ var CODEX_CATEGORY_COLORS = {
130
+ input: "#66bb6a",
131
+ output: "#56b6c2"
132
+ };
118
133
  function clampPercentage(value) {
119
134
  if (!Number.isFinite(value)) return 0;
120
135
  if (value < 0) return 0;
@@ -130,6 +145,54 @@ function clampTokensToWindow(rawTokens, maxTokens) {
130
145
  if (rawTokens <= maxTokens) return { totalTokens: rawTokens };
131
146
  return { totalTokens: maxTokens, totalProcessedTokens: rawTokens };
132
147
  }
148
+ function compactCategories(categories) {
149
+ return categories.filter((category) => Boolean(category && category.tokens > 0)).map((category) => ({
150
+ ...category,
151
+ percentage: clampPercentage(category.percentage)
152
+ }));
153
+ }
154
+ function buildCodexTokenUsageContextUsagePayload(usage) {
155
+ const maxTokens = usage.modelContextWindow;
156
+ const inputTokens = usage.last.inputTokens;
157
+ const outputTokens = usage.last.outputTokens;
158
+ const lastTurnTokens = usage.last.totalTokens;
159
+ const { totalTokens } = clampTokensToWindow(lastTurnTokens, maxTokens);
160
+ const cumulativeTotalTokens = usage.total?.totalTokens ?? null;
161
+ const totalProcessedTokens = cumulativeTotalTokens !== null && cumulativeTotalTokens > totalTokens ? cumulativeTotalTokens : clampTokensToWindow(lastTurnTokens, maxTokens).totalProcessedTokens;
162
+ return {
163
+ provider: "codex",
164
+ source: "codex_token_count",
165
+ ...usage.model !== void 0 ? { model: usage.model } : {},
166
+ totalTokens,
167
+ ...totalProcessedTokens !== void 0 ? { totalProcessedTokens } : {},
168
+ maxTokens,
169
+ rawMaxTokens: maxTokens,
170
+ percentage: percentage(totalTokens, maxTokens),
171
+ compactsAutomatically: true,
172
+ categories: compactCategories([
173
+ {
174
+ name: "Input context",
175
+ tokens: inputTokens,
176
+ percentage: percentage(inputTokens, maxTokens),
177
+ color: CODEX_CATEGORY_COLORS.input
178
+ },
179
+ {
180
+ name: "Generated output",
181
+ tokens: outputTokens,
182
+ percentage: percentage(outputTokens, maxTokens),
183
+ color: CODEX_CATEGORY_COLORS.output
184
+ }
185
+ ]),
186
+ apiUsage: {
187
+ inputTokens,
188
+ outputTokens,
189
+ ...usage.last.cachedInputTokens !== null && usage.last.cachedInputTokens !== void 0 ? { cachedInputTokens: usage.last.cachedInputTokens } : {},
190
+ ...usage.last.reasoningOutputTokens !== null && usage.last.reasoningOutputTokens !== void 0 ? { reasoningOutputTokens: usage.last.reasoningOutputTokens } : {},
191
+ ...cumulativeTotalTokens !== null ? { totalTokens: cumulativeTotalTokens } : {}
192
+ },
193
+ updatedAt: usage.updatedAt
194
+ };
195
+ }
133
196
 
134
197
  // ../shared/src/pricing.ts
135
198
  var PLANS = {
@@ -1686,7 +1749,7 @@ function parseReplicasConfigString(content, filename) {
1686
1749
  }
1687
1750
 
1688
1751
  // ../shared/src/engine/environment.ts
1689
- var DAYTONA_SNAPSHOT_ID = "26-05-2026-royal-york-v2";
1752
+ var DAYTONA_SNAPSHOT_ID = "26-05-2026-royal-york-v4";
1690
1753
 
1691
1754
  // ../shared/src/engine/types.ts
1692
1755
  var DEFAULT_CHAT_TITLES = {
@@ -3548,19 +3611,19 @@ var previewService = new PreviewService();
3548
3611
 
3549
3612
  // src/services/chat/chat-service.ts
3550
3613
  import { existsSync as existsSync7 } from "fs";
3551
- import { appendFile as appendFile5, mkdir as mkdir10, readFile as readFile8, rm, writeFile as writeFile8 } from "fs/promises";
3552
- import { homedir as homedir12 } from "os";
3553
- import { join as join14 } from "path";
3614
+ import { appendFile as appendFile5, mkdir as mkdir11, readFile as readFile8, rm, writeFile as writeFile9 } from "fs/promises";
3615
+ import { homedir as homedir13 } from "os";
3616
+ import { join as join15 } from "path";
3554
3617
  import { randomUUID as randomUUID5 } from "crypto";
3555
3618
 
3556
3619
  // src/managers/claude-manager.ts
3557
3620
  import {
3558
3621
  query
3559
3622
  } from "@anthropic-ai/claude-agent-sdk";
3560
- import { randomUUID as randomUUID3 } from "crypto";
3561
- import { join as join12 } from "path";
3562
- import { mkdir as mkdir8, appendFile as appendFile4 } from "fs/promises";
3563
- import { homedir as homedir10 } from "os";
3623
+ import { randomUUID as randomUUID4 } from "crypto";
3624
+ import { join as join13 } from "path";
3625
+ import { mkdir as mkdir9, appendFile as appendFile4 } from "fs/promises";
3626
+ import { homedir as homedir11 } from "os";
3564
3627
 
3565
3628
  // src/utils/jsonl-reader.ts
3566
3629
  import { readFile as readFile6 } from "fs/promises";
@@ -3593,33 +3656,6 @@ async function readJSONL(filePath) {
3593
3656
  }
3594
3657
  }
3595
3658
 
3596
- // src/services/monolith-service.ts
3597
- var MonolithService = class {
3598
- async sendEvent(event) {
3599
- if (!ENGINE_ENV.WORKSPACE_ID) {
3600
- return;
3601
- }
3602
- try {
3603
- const response = await fetch(`${ENGINE_ENV.MONOLITH_URL}/v1/engine/webhook`, {
3604
- method: "POST",
3605
- headers: {
3606
- Authorization: `Bearer ${ENGINE_ENV.REPLICAS_ENGINE_SECRET}`,
3607
- "X-Workspace-Id": ENGINE_ENV.WORKSPACE_ID,
3608
- "Content-Type": "application/json"
3609
- },
3610
- body: JSON.stringify(event)
3611
- });
3612
- if (!response.ok) {
3613
- const errorText = await response.text();
3614
- console.error(`[MonolithService] Failed to send event: ${response.status} ${errorText}`);
3615
- }
3616
- } catch (error) {
3617
- console.error("[MonolithService] Failed to send event:", error);
3618
- }
3619
- }
3620
- };
3621
- var monolithService = new MonolithService();
3622
-
3623
3659
  // src/utils/linear-converter.ts
3624
3660
  function linearThoughtToResponse(thought) {
3625
3661
  return {
@@ -3999,8 +4035,138 @@ function extractPlanFromCodexEvent(event) {
3999
4035
  status: entry.completed ? "completed" : hasIncomplete ? "inProgress" : "pending"
4000
4036
  }));
4001
4037
  }
4038
+ function codexAspReasoningText(item) {
4039
+ return [...item.summary, ...item.content].filter(Boolean).join("\n").trim();
4040
+ }
4041
+ function codexAspChangePaths(item) {
4042
+ return item.changes.map((change) => change.path).filter(Boolean).join(", ");
4043
+ }
4044
+ function codexAspStatusResult(status) {
4045
+ return status === "completed" ? "Done" : status || "Done";
4046
+ }
4047
+ function convertCodexAspItem(item, lifecycle, linearSessionId) {
4048
+ if (item.type === "agentMessage") {
4049
+ if (!item.text) return null;
4050
+ return {
4051
+ linearSessionId,
4052
+ content: {
4053
+ type: "thought",
4054
+ body: item.text
4055
+ }
4056
+ };
4057
+ }
4058
+ if (item.type === "reasoning") {
4059
+ const text = codexAspReasoningText(item);
4060
+ if (!text) return null;
4061
+ return {
4062
+ linearSessionId,
4063
+ content: {
4064
+ type: "thought",
4065
+ body: text
4066
+ }
4067
+ };
4068
+ }
4069
+ if (item.type === "commandExecution") {
4070
+ return {
4071
+ linearSessionId,
4072
+ content: {
4073
+ type: "action",
4074
+ action: "Running command",
4075
+ parameter: item.command,
4076
+ ...lifecycle === "completed" ? { result: item.exitCode !== null ? `Exit code: ${item.exitCode}` : item.aggregatedOutput || "Done" } : {}
4077
+ }
4078
+ };
4079
+ }
4080
+ if (item.type === "fileChange") {
4081
+ return {
4082
+ linearSessionId,
4083
+ content: {
4084
+ type: "action",
4085
+ action: "File change",
4086
+ parameter: codexAspChangePaths(item),
4087
+ ...lifecycle === "completed" ? { result: codexAspStatusResult(item.status) } : {}
4088
+ }
4089
+ };
4090
+ }
4091
+ if (item.type === "mcpToolCall") {
4092
+ return {
4093
+ linearSessionId,
4094
+ content: {
4095
+ type: "action",
4096
+ action: item.tool || "MCP tool call",
4097
+ parameter: item.server,
4098
+ ...lifecycle === "completed" ? { result: item.error?.message ?? codexAspStatusResult(item.status) } : {}
4099
+ }
4100
+ };
4101
+ }
4102
+ if (item.type === "dynamicToolCall") {
4103
+ return {
4104
+ linearSessionId,
4105
+ content: {
4106
+ type: "action",
4107
+ action: item.tool,
4108
+ parameter: item.namespace ?? "",
4109
+ ...lifecycle === "completed" ? { result: item.success === false ? "Failed" : codexAspStatusResult(item.status) } : {}
4110
+ }
4111
+ };
4112
+ }
4113
+ if (item.type === "webSearch") {
4114
+ return {
4115
+ linearSessionId,
4116
+ content: {
4117
+ type: "action",
4118
+ action: "Web search",
4119
+ parameter: item.query,
4120
+ ...lifecycle === "completed" ? { result: "Done" } : {}
4121
+ }
4122
+ };
4123
+ }
4124
+ if (item.type === "plan") {
4125
+ return {
4126
+ linearSessionId,
4127
+ content: {
4128
+ type: "action",
4129
+ action: "Updating plan",
4130
+ parameter: item.text,
4131
+ ...lifecycle === "completed" ? { result: "Done" } : {}
4132
+ }
4133
+ };
4134
+ }
4135
+ return null;
4136
+ }
4137
+ function convertCodexAspNotification(notification, linearSessionId) {
4138
+ if (notification.method === "turn/started") {
4139
+ return {
4140
+ linearSessionId,
4141
+ content: {
4142
+ type: "thought",
4143
+ body: "Processing..."
4144
+ }
4145
+ };
4146
+ }
4147
+ if (notification.method === "item/started") {
4148
+ return convertCodexAspItem(notification.params.item, "started", linearSessionId);
4149
+ }
4150
+ if (notification.method === "item/completed") {
4151
+ return convertCodexAspItem(notification.params.item, "completed", linearSessionId);
4152
+ }
4153
+ return null;
4154
+ }
4155
+ function extractPlanFromCodexAspNotification(notification) {
4156
+ if (notification.method !== "turn/plan/updated" || notification.params.plan.length === 0) {
4157
+ return null;
4158
+ }
4159
+ return notification.params.plan.filter((item) => item.step.trim().length > 0).map((item) => ({
4160
+ content: item.step.trim(),
4161
+ status: item.status
4162
+ }));
4163
+ }
4002
4164
 
4003
4165
  // src/utils/image-utils.ts
4166
+ import { randomUUID as randomUUID3 } from "crypto";
4167
+ import { mkdir as mkdir8, unlink as unlink2, writeFile as writeFile7 } from "fs/promises";
4168
+ import { homedir as homedir10 } from "os";
4169
+ import { join as join12 } from "path";
4004
4170
  function isImageMediaType(value) {
4005
4171
  return IMAGE_MEDIA_TYPES.includes(value);
4006
4172
  }
@@ -4078,6 +4244,26 @@ async function normalizeImages(images) {
4078
4244
  }
4079
4245
  return normalized;
4080
4246
  }
4247
+ async function saveNormalizedImagesToTempFiles(images, tempImageDir = join12(homedir10(), ".replicas", "codex", "temp-images")) {
4248
+ await mkdir8(tempImageDir, { recursive: true });
4249
+ const tempPaths = [];
4250
+ try {
4251
+ for (const image of images) {
4252
+ const ext = image.source.media_type.split("/")[1] || "png";
4253
+ const filename = `img_${randomUUID3()}.${ext}`;
4254
+ const filepath = join12(tempImageDir, filename);
4255
+ await writeFile7(filepath, Buffer.from(image.source.data, "base64"));
4256
+ tempPaths.push(filepath);
4257
+ }
4258
+ } catch (error) {
4259
+ await removeTempImageFiles(tempPaths);
4260
+ throw error;
4261
+ }
4262
+ return tempPaths;
4263
+ }
4264
+ async function removeTempImageFiles(paths) {
4265
+ await Promise.allSettled(paths.map((path4) => unlink2(path4)));
4266
+ }
4081
4267
 
4082
4268
  // src/services/message-queue-service.ts
4083
4269
  var MessageQueueService = class {
@@ -4216,6 +4402,33 @@ var MessageQueueService = class {
4216
4402
  }
4217
4403
  };
4218
4404
 
4405
+ // src/services/monolith-service.ts
4406
+ var MonolithService = class {
4407
+ async sendEvent(event) {
4408
+ if (!ENGINE_ENV.WORKSPACE_ID) {
4409
+ return;
4410
+ }
4411
+ try {
4412
+ const response = await fetch(`${ENGINE_ENV.MONOLITH_URL}/v1/engine/webhook`, {
4413
+ method: "POST",
4414
+ headers: {
4415
+ Authorization: `Bearer ${ENGINE_ENV.REPLICAS_ENGINE_SECRET}`,
4416
+ "X-Workspace-Id": ENGINE_ENV.WORKSPACE_ID,
4417
+ "Content-Type": "application/json"
4418
+ },
4419
+ body: JSON.stringify(event)
4420
+ });
4421
+ if (!response.ok) {
4422
+ const errorText = await response.text();
4423
+ console.error(`[MonolithService] Failed to send event: ${response.status} ${errorText}`);
4424
+ }
4425
+ } catch (error) {
4426
+ console.error("[MonolithService] Failed to send event:", error);
4427
+ }
4428
+ }
4429
+ };
4430
+ var monolithService = new MonolithService();
4431
+
4219
4432
  // src/managers/coding-agent-manager.ts
4220
4433
  var MAX_INTERRUPT_QUEUE_ITEMS = 1e3;
4221
4434
  var MAX_INTERRUPT_QUEUE_CHARS = 2e5;
@@ -4425,6 +4638,49 @@ async function getAgentAdditionalDirectories() {
4425
4638
  }
4426
4639
  }
4427
4640
 
4641
+ // src/utils/linear-event-forwarder.ts
4642
+ function isLinearThoughtEvent(event) {
4643
+ return event.content.type === "thought";
4644
+ }
4645
+ var LinearEventForwarder = class {
4646
+ linearSessionId;
4647
+ latestThoughtEvent = null;
4648
+ constructor(linearSessionId) {
4649
+ this.linearSessionId = linearSessionId;
4650
+ }
4651
+ sendPlan(plan) {
4652
+ if (!this.linearSessionId || !plan) return;
4653
+ monolithService.sendEvent({
4654
+ type: "agent_plan_update",
4655
+ payload: { linearSessionId: this.linearSessionId, plan }
4656
+ }).catch(() => {
4657
+ });
4658
+ }
4659
+ sendEvent(linearEvent) {
4660
+ if (!this.linearSessionId || !linearEvent) return;
4661
+ if (this.latestThoughtEvent) {
4662
+ monolithService.sendEvent({ type: "agent_update", payload: this.latestThoughtEvent }).catch(() => {
4663
+ });
4664
+ }
4665
+ if (isLinearThoughtEvent(linearEvent)) {
4666
+ this.latestThoughtEvent = linearEvent;
4667
+ return;
4668
+ }
4669
+ this.latestThoughtEvent = null;
4670
+ monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
4671
+ });
4672
+ }
4673
+ flushThoughtAsResponse() {
4674
+ if (!this.linearSessionId || !this.latestThoughtEvent) return;
4675
+ monolithService.sendEvent({
4676
+ type: "agent_update",
4677
+ payload: linearThoughtToResponse(this.latestThoughtEvent)
4678
+ }).catch(() => {
4679
+ });
4680
+ this.latestThoughtEvent = null;
4681
+ }
4682
+ };
4683
+
4428
4684
  // src/managers/claude-manager.ts
4429
4685
  var PromptStream = class {
4430
4686
  queue = [];
@@ -4464,9 +4720,6 @@ var PromptStream = class {
4464
4720
  };
4465
4721
  }
4466
4722
  };
4467
- function isLinearThoughtEvent(event) {
4468
- return event.content.type === "thought";
4469
- }
4470
4723
  var ALWAYS_ALLOWED_TOOLS = [
4471
4724
  "Read",
4472
4725
  "Glob",
@@ -4589,7 +4842,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4589
4842
  pendingToolInputs = /* @__PURE__ */ new Map();
4590
4843
  constructor(options) {
4591
4844
  super(options);
4592
- this.historyFile = options.historyFilePath ?? join12(homedir10(), ".replicas", "claude", "history.jsonl");
4845
+ this.historyFile = options.historyFilePath ?? join13(homedir11(), ".replicas", "claude", "history.jsonl");
4593
4846
  this.systemPromptOverride = options.systemPromptOverride;
4594
4847
  this.toolsOverride = options.tools;
4595
4848
  this.mcpServersConfig = options.mcpServers;
@@ -4653,7 +4906,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4653
4906
  if (!handler) {
4654
4907
  return { behavior: "allow" };
4655
4908
  }
4656
- const requestId = randomUUID3();
4909
+ const requestId = randomUUID4();
4657
4910
  const toolUseId = options.toolUseID;
4658
4911
  const { options: requestOptions, questions: requestQuestions } = handler.getRequest(input);
4659
4912
  const result = await new Promise((resolve3) => {
@@ -4813,32 +5066,12 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4813
5066
  this.activePromptStream?.close();
4814
5067
  await this.activeQuery.interrupt();
4815
5068
  }
4816
- let latestThoughtEvent = null;
5069
+ const linearForwarder = new LinearEventForwarder(linearSessionId);
4817
5070
  for await (const msg of response) {
4818
5071
  await this.handleMessage(msg);
4819
5072
  if (linearSessionId) {
4820
- const plan = extractPlanFromClaudeEvent(msg);
4821
- if (plan) {
4822
- monolithService.sendEvent({
4823
- type: "agent_plan_update",
4824
- payload: { linearSessionId, plan }
4825
- }).catch(() => {
4826
- });
4827
- }
4828
- const linearEvent = convertClaudeEvent(msg, linearSessionId);
4829
- if (linearEvent) {
4830
- if (latestThoughtEvent) {
4831
- monolithService.sendEvent({ type: "agent_update", payload: latestThoughtEvent }).catch(() => {
4832
- });
4833
- }
4834
- if (isLinearThoughtEvent(linearEvent)) {
4835
- latestThoughtEvent = linearEvent;
4836
- } else {
4837
- latestThoughtEvent = null;
4838
- monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
4839
- });
4840
- }
4841
- }
5073
+ linearForwarder.sendPlan(extractPlanFromClaudeEvent(msg));
5074
+ linearForwarder.sendEvent(convertClaudeEvent(msg, linearSessionId));
4842
5075
  }
4843
5076
  if (msg.type === "result") {
4844
5077
  await this.recordContextUsage(response);
@@ -4846,11 +5079,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4846
5079
  break;
4847
5080
  }
4848
5081
  }
4849
- if (linearSessionId && latestThoughtEvent) {
4850
- const responseEvent = linearThoughtToResponse(latestThoughtEvent);
4851
- monolithService.sendEvent({ type: "agent_update", payload: responseEvent }).catch(() => {
4852
- });
4853
- }
5082
+ linearForwarder.flushThoughtAsResponse();
4854
5083
  } finally {
4855
5084
  this.activeQuery = null;
4856
5085
  this.activePromptStream?.close();
@@ -4927,8 +5156,8 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4927
5156
  };
4928
5157
  }
4929
5158
  async initialize() {
4930
- const historyDir = join12(homedir10(), ".replicas", "claude");
4931
- await mkdir8(historyDir, { recursive: true });
5159
+ const historyDir = join13(homedir11(), ".replicas", "claude");
5160
+ await mkdir9(historyDir, { recursive: true });
4932
5161
  if (this.initialSessionId) {
4933
5162
  this.sessionId = this.initialSessionId;
4934
5163
  console.log(`[ClaudeManager] Restored session ID from persisted state: ${this.sessionId}`);
@@ -4990,27 +5219,29 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4990
5219
 
4991
5220
  // src/managers/codex-manager.ts
4992
5221
  import { Codex } from "@openai/codex-sdk";
4993
- import { randomUUID as randomUUID4 } from "crypto";
4994
- import { readdir as readdir3, stat as stat2, writeFile as writeFile7, mkdir as mkdir9, readFile as readFile7 } from "fs/promises";
5222
+ import { readdir as readdir3, stat as stat2, writeFile as writeFile8, mkdir as mkdir10, readFile as readFile7 } from "fs/promises";
4995
5223
  import { existsSync as existsSync6 } from "fs";
4996
- import { join as join13 } from "path";
4997
- import { homedir as homedir11 } from "os";
5224
+ import { join as join14 } from "path";
5225
+ import { homedir as homedir12 } from "os";
4998
5226
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
4999
- var DEFAULT_MODEL = "gpt-5.5";
5000
- var CODEX_CONFIG_PATH = join13(homedir11(), ".codex", "config.toml");
5001
- function isLinearThoughtEvent2(event) {
5002
- return event.content.type === "thought";
5003
- }
5004
- function isJsonlEvent2(value) {
5005
- if (!isRecord3(value)) {
5006
- return false;
5227
+
5228
+ // src/utils/codex-quota.ts
5229
+ function buildCodexRateLimitsSnapshot(fields) {
5230
+ if (fields.unlimited === true) return null;
5231
+ let state = "ok";
5232
+ if (fields.hasCredits === false) {
5233
+ state = "out_of_credits";
5234
+ } else if (fields.rateLimitResetType !== null) {
5235
+ state = "rate_limited";
5007
5236
  }
5008
- return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord3(value.payload);
5009
- }
5010
- function sleep(ms) {
5011
- return new Promise((resolve3) => setTimeout(resolve3, ms));
5237
+ return {
5238
+ state,
5239
+ balance: fields.balance,
5240
+ rateLimitResetType: fields.rateLimitResetType,
5241
+ planType: fields.planType
5242
+ };
5012
5243
  }
5013
- function extractRateLimitsSnapshot(parsed) {
5244
+ function extractCodexRateLimitsSnapshotFromJsonl(parsed) {
5014
5245
  if (!isRecord3(parsed)) return null;
5015
5246
  const payload = isRecord3(parsed.payload) ? parsed.payload : parsed;
5016
5247
  const rateLimits = isRecord3(payload.rate_limits) ? payload.rate_limits : null;
@@ -5019,35 +5250,81 @@ function extractRateLimitsSnapshot(parsed) {
5019
5250
  const hasCredits = credits && typeof credits.has_credits === "boolean" ? credits.has_credits : null;
5020
5251
  const unlimited = credits && typeof credits.unlimited === "boolean" ? credits.unlimited : null;
5021
5252
  const balance = credits && typeof credits.balance === "string" ? credits.balance : null;
5022
- if (unlimited === true) return null;
5023
5253
  const rateLimitResetType = typeof rateLimits.rate_limit_reached_type === "string" && rateLimits.rate_limit_reached_type.length > 0 ? rateLimits.rate_limit_reached_type : null;
5024
5254
  const planType = typeof rateLimits.plan_type === "string" ? rateLimits.plan_type : null;
5025
- let state = "ok";
5026
- if (hasCredits === false) {
5027
- state = "out_of_credits";
5028
- } else if (rateLimitResetType !== null) {
5029
- state = "rate_limited";
5255
+ return buildCodexRateLimitsSnapshot({
5256
+ unlimited,
5257
+ hasCredits,
5258
+ balance,
5259
+ rateLimitResetType,
5260
+ planType
5261
+ });
5262
+ }
5263
+ var CodexQuotaStatusTracker = class {
5264
+ lastEmittedQuotaState = "ok";
5265
+ latestQuotaSnapshot = null;
5266
+ quotaBlocked = false;
5267
+ get blocked() {
5268
+ return this.quotaBlocked;
5269
+ }
5270
+ get latestSnapshot() {
5271
+ return this.latestQuotaSnapshot;
5272
+ }
5273
+ prime(snapshot) {
5274
+ this.latestQuotaSnapshot = snapshot;
5275
+ this.quotaBlocked = snapshot.state === "out_of_credits";
5276
+ }
5277
+ apply(snapshot, force = false) {
5278
+ const stateChanged = snapshot.state !== this.lastEmittedQuotaState;
5279
+ if (!stateChanged && !force) {
5280
+ return null;
5281
+ }
5282
+ this.lastEmittedQuotaState = snapshot.state;
5283
+ this.quotaBlocked = snapshot.state === "out_of_credits";
5284
+ this.latestQuotaSnapshot = snapshot;
5285
+ const payload = {
5286
+ state: snapshot.state,
5287
+ balance: snapshot.balance,
5288
+ rateLimitResetType: snapshot.rateLimitResetType,
5289
+ planType: snapshot.planType
5290
+ };
5291
+ const event = {
5292
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5293
+ type: CODEX_QUOTA_STATUS_EVENT_TYPE,
5294
+ payload
5295
+ };
5296
+ return event;
5030
5297
  }
5031
- return { state, balance, rateLimitResetType, planType };
5298
+ };
5299
+
5300
+ // src/utils/codex-auth.ts
5301
+ function isCodexAuthError(error) {
5302
+ const msg = error instanceof Error ? error.message : String(error);
5303
+ const lower = msg.toLowerCase();
5304
+ return lower.includes("unauthorized") || lower.includes("authentication") || lower.includes("invalid api key") || lower.includes("incorrect api key") || lower.includes("api key") && lower.includes("invalid") || lower.includes("401") || lower.includes('codexerrorinfo="unauthorized"') || lower.includes("codexerrorinfo=unauthorized");
5032
5305
  }
5033
- var CodexManager = class _CodexManager extends CodingAgentManager {
5306
+
5307
+ // src/managers/codex-manager.ts
5308
+ var DEFAULT_MODEL = "gpt-5.5";
5309
+ var CODEX_CONFIG_PATH = join14(homedir12(), ".codex", "config.toml");
5310
+ function isJsonlEvent2(value) {
5311
+ if (!isRecord3(value)) {
5312
+ return false;
5313
+ }
5314
+ return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord3(value.payload);
5315
+ }
5316
+ function sleep(ms) {
5317
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
5318
+ }
5319
+ var CodexManager = class extends CodingAgentManager {
5034
5320
  codex;
5035
5321
  currentThreadId = null;
5036
5322
  currentThread = null;
5037
- tempImageDir;
5038
5323
  activeAbortController = null;
5039
- /** Most recent quota state observed from a rollout `rate_limits` payload. */
5040
- latestQuotaState = "ok";
5041
- /** Last state actually emitted to the UI. Drives dedup so seeded historical state still emits the first time it surfaces in a turn. */
5042
- lastEmittedQuotaState = "ok";
5043
- /** Last full snapshot, retained so we can re-emit when a user retries while blocked. */
5044
- latestQuotaSnapshot = null;
5045
- /** When true, new turns short-circuit instead of running. Set by `out_of_credits`. */
5046
- quotaBlocked = false;
5324
+ quotaStatus = new CodexQuotaStatusTracker();
5047
5325
  constructor(options) {
5048
5326
  super(options);
5049
5327
  this.codex = this.createCodexClient();
5050
- this.tempImageDir = join13(homedir11(), ".replicas", "codex", "temp-images");
5051
5328
  this.initializeManager(this.processMessageInternal.bind(this));
5052
5329
  }
5053
5330
  createCodexClient() {
@@ -5067,12 +5344,6 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5067
5344
  console.log(`[CodexManager] Restored thread ID from persisted state: ${this.currentThreadId}`);
5068
5345
  }
5069
5346
  }
5070
- /**
5071
- * One-shot pass over the current session's rollout jsonl to find the most
5072
- * recent `rate_limits` entry and emit a quota state if it has changed.
5073
- * Used after `runStreamed` throws — the tail loop may not have pumped the
5074
- * fatal line before the SDK exited.
5075
- */
5076
5347
  async flushQuotaSnapshotFromCurrentSession() {
5077
5348
  if (!this.currentThreadId) return;
5078
5349
  try {
@@ -5084,7 +5355,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5084
5355
  for (const line of lines) {
5085
5356
  try {
5086
5357
  const parsed = JSON.parse(line);
5087
- const snapshot = extractRateLimitsSnapshot(parsed);
5358
+ const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
5088
5359
  if (snapshot) {
5089
5360
  latest = snapshot;
5090
5361
  }
@@ -5098,41 +5369,9 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5098
5369
  console.warn("[CodexManager] Failed to flush quota snapshot from session:", error);
5099
5370
  }
5100
5371
  }
5101
- /**
5102
- * Emit a synthetic codex-quota-status AgentEvent and update local state.
5103
- * Dedupes against `lastEmittedQuotaState` (what the UI has actually seen),
5104
- * not `latestQuotaState`, so a state primed silently by `seedSeenLines` on
5105
- * engine restart still emits the first time it surfaces in a turn. Pass
5106
- * `force` to re-emit on a retry so users see the banner reappear.
5107
- */
5108
5372
  emitQuotaStatus(snapshot, force = false) {
5109
- const stateChanged = snapshot.state !== this.lastEmittedQuotaState;
5110
- if (!stateChanged && !force) {
5111
- return;
5112
- }
5113
- this.latestQuotaState = snapshot.state;
5114
- this.lastEmittedQuotaState = snapshot.state;
5115
- this.quotaBlocked = snapshot.state === "out_of_credits";
5116
- this.latestQuotaSnapshot = snapshot;
5117
- const payload = {
5118
- state: snapshot.state,
5119
- balance: snapshot.balance,
5120
- rateLimitResetType: snapshot.rateLimitResetType,
5121
- planType: snapshot.planType
5122
- };
5123
- this.onEvent({
5124
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5125
- type: CODEX_QUOTA_STATUS_EVENT_TYPE,
5126
- payload
5127
- });
5128
- if (!stateChanged) return;
5129
- if (snapshot.state === "out_of_credits") {
5130
- console.warn("[CodexManager] Codex account out of credits \u2014 pausing turn processing.");
5131
- } else if (snapshot.state === "rate_limited") {
5132
- console.warn(`[CodexManager] Codex rate limit reached (${snapshot.rateLimitResetType}).`);
5133
- } else {
5134
- console.log("[CodexManager] Codex quota recovered.");
5135
- }
5373
+ const event = this.quotaStatus.apply(snapshot, force);
5374
+ if (event) this.onEvent(event);
5136
5375
  }
5137
5376
  async interruptActiveTurn() {
5138
5377
  if (this.activeAbortController) {
@@ -5145,8 +5384,8 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5145
5384
  */
5146
5385
  async updateCodexConfig(developerInstructions) {
5147
5386
  try {
5148
- const codexDir = join13(homedir11(), ".codex");
5149
- await mkdir9(codexDir, { recursive: true });
5387
+ const codexDir = join14(homedir12(), ".codex");
5388
+ await mkdir10(codexDir, { recursive: true });
5150
5389
  let config = {};
5151
5390
  if (existsSync6(CODEX_CONFIG_PATH)) {
5152
5391
  try {
@@ -5165,34 +5404,17 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5165
5404
  delete config.developer_instructions;
5166
5405
  }
5167
5406
  const tomlContent = stringifyToml(config);
5168
- await writeFile7(CODEX_CONFIG_PATH, tomlContent, "utf-8");
5407
+ await writeFile8(CODEX_CONFIG_PATH, tomlContent, "utf-8");
5169
5408
  console.log("[CodexManager] Updated config.toml with developer_instructions");
5170
5409
  } catch (error) {
5171
5410
  console.error("[CodexManager] Failed to update config.toml:", error);
5172
5411
  }
5173
5412
  }
5174
- /**
5175
- * Helper method to save normalized images to temp files for Codex SDK
5176
- * @returns Array of temp file paths
5177
- */
5178
- async saveImagesToTempFiles(images) {
5179
- await mkdir9(this.tempImageDir, { recursive: true });
5180
- const tempPaths = [];
5181
- for (const image of images) {
5182
- const ext = image.source.media_type.split("/")[1] || "png";
5183
- const filename = `img_${randomUUID4()}.${ext}`;
5184
- const filepath = join13(this.tempImageDir, filename);
5185
- const buffer = Buffer.from(image.source.data, "base64");
5186
- await writeFile7(filepath, buffer);
5187
- tempPaths.push(filepath);
5188
- }
5189
- return tempPaths;
5190
- }
5191
5413
  async processMessageInternal(request) {
5192
5414
  try {
5193
5415
  await this.executeCodexTurn(request);
5194
5416
  } catch (error) {
5195
- if (_CodexManager.isAuthError(error)) {
5417
+ if (isCodexAuthError(error)) {
5196
5418
  const refreshed = await codexTokenManager.fetchFreshCredentials(error instanceof Error ? error.message : String(error));
5197
5419
  if (refreshed) {
5198
5420
  this.resetCodexClient();
@@ -5204,10 +5426,10 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5204
5426
  }
5205
5427
  }
5206
5428
  async executeCodexTurn(request) {
5207
- if (this.quotaBlocked && this.latestQuotaSnapshot) {
5429
+ if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
5208
5430
  await this.flushQuotaSnapshotFromCurrentSession();
5209
- if (this.quotaBlocked && this.latestQuotaSnapshot) {
5210
- this.emitQuotaStatus(this.latestQuotaSnapshot, true);
5431
+ if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
5432
+ this.emitQuotaStatus(this.quotaStatus.latestSnapshot, true);
5211
5433
  try {
5212
5434
  await this.onTurnComplete();
5213
5435
  } catch (error) {
@@ -5231,13 +5453,13 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5231
5453
  try {
5232
5454
  if (images && images.length > 0) {
5233
5455
  const normalizedImages = await normalizeImages(images);
5234
- tempImagePaths = await this.saveImagesToTempFiles(normalizedImages);
5456
+ tempImagePaths = await saveNormalizedImagesToTempFiles(normalizedImages);
5235
5457
  }
5236
5458
  const developerInstructions = this.buildCombinedInstructions(customInstructions);
5237
5459
  await this.updateCodexConfig(developerInstructions);
5238
5460
  const sandboxMode = "danger-full-access";
5239
5461
  const webSearchMode = "live";
5240
- const codexReasoningEffort = thinkingLevel ? thinkingLevel === "max" ? "xhigh" : thinkingLevel : void 0;
5462
+ const codexReasoningEffort = codexReasoningEffortForThinkingLevel(thinkingLevel);
5241
5463
  const additionalDirectories = await getAgentAdditionalDirectories();
5242
5464
  const threadOptions = {
5243
5465
  workingDirectory: this.workingDirectory,
@@ -5281,42 +5503,17 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5281
5503
  }
5282
5504
  try {
5283
5505
  const { events } = await this.currentThread.runStreamed(input, { signal: abortController.signal });
5284
- let latestThoughtEvent = null;
5506
+ const linearForwarder = new LinearEventForwarder(linearSessionId);
5285
5507
  for await (const event of events) {
5286
5508
  if (linearSessionId) {
5287
- const plan = extractPlanFromCodexEvent(event);
5288
- if (plan) {
5289
- monolithService.sendEvent({
5290
- type: "agent_plan_update",
5291
- payload: { linearSessionId, plan }
5292
- }).catch(() => {
5293
- });
5294
- }
5295
- const linearEvent = convertCodexEvent(event, linearSessionId);
5296
- if (linearEvent) {
5297
- if (latestThoughtEvent) {
5298
- monolithService.sendEvent({ type: "agent_update", payload: latestThoughtEvent }).catch(() => {
5299
- });
5300
- }
5301
- if (isLinearThoughtEvent2(linearEvent)) {
5302
- latestThoughtEvent = linearEvent;
5303
- } else {
5304
- latestThoughtEvent = null;
5305
- monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
5306
- });
5307
- }
5308
- }
5509
+ linearForwarder.sendPlan(extractPlanFromCodexEvent(event));
5510
+ linearForwarder.sendEvent(convertCodexEvent(event, linearSessionId));
5309
5511
  }
5310
5512
  }
5311
- if (linearSessionId && latestThoughtEvent) {
5312
- const responseEvent = linearThoughtToResponse(latestThoughtEvent);
5313
- monolithService.sendEvent({ type: "agent_update", payload: responseEvent }).catch(() => {
5314
- });
5315
- }
5513
+ linearForwarder.flushThoughtAsResponse();
5316
5514
  } catch (error) {
5317
5515
  await this.flushQuotaSnapshotFromCurrentSession();
5318
- if (this.quotaBlocked) {
5319
- console.warn("[CodexManager] runStreamed failed while quota was blocked \u2014 surfacing as quota state.");
5516
+ if (this.quotaStatus.blocked) {
5320
5517
  return;
5321
5518
  }
5322
5519
  throw error;
@@ -5325,6 +5522,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5325
5522
  if (stopTail) {
5326
5523
  await stopTail();
5327
5524
  }
5525
+ await removeTempImageFiles(tempImagePaths);
5328
5526
  try {
5329
5527
  await this.onTurnComplete();
5330
5528
  } catch (error) {
@@ -5333,11 +5531,6 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5333
5531
  this.activeAbortController = null;
5334
5532
  }
5335
5533
  }
5336
- static isAuthError(error) {
5337
- const msg = error instanceof Error ? error.message : String(error);
5338
- const lower = msg.toLowerCase();
5339
- return lower.includes("unauthorized") || lower.includes("authentication") || lower.includes("invalid api key") || lower.includes("incorrect api key") || lower.includes("api key") && lower.includes("invalid") || lower.includes("login") || lower.includes("401");
5340
- }
5341
5534
  async getHistory() {
5342
5535
  if (!this.currentThreadId) {
5343
5536
  return {
@@ -5360,13 +5553,13 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5360
5553
  }
5361
5554
  // Helper methods for finding session files
5362
5555
  async findSessionFile(threadId) {
5363
- const sessionsDir = join13(homedir11(), ".codex", "sessions");
5556
+ const sessionsDir = join14(homedir12(), ".codex", "sessions");
5364
5557
  try {
5365
5558
  const now = /* @__PURE__ */ new Date();
5366
5559
  const year = now.getFullYear();
5367
5560
  const month = String(now.getMonth() + 1).padStart(2, "0");
5368
5561
  const day = String(now.getDate()).padStart(2, "0");
5369
- const todayDir = join13(sessionsDir, String(year), month, day);
5562
+ const todayDir = join14(sessionsDir, String(year), month, day);
5370
5563
  const file = await this.findFileInDirectory(todayDir, threadId);
5371
5564
  if (file) return file;
5372
5565
  for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
@@ -5375,7 +5568,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5375
5568
  const searchYear = date.getFullYear();
5376
5569
  const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
5377
5570
  const searchDay = String(date.getDate()).padStart(2, "0");
5378
- const searchDir = join13(sessionsDir, String(searchYear), searchMonth, searchDay);
5571
+ const searchDir = join14(sessionsDir, String(searchYear), searchMonth, searchDay);
5379
5572
  const file2 = await this.findFileInDirectory(searchDir, threadId);
5380
5573
  if (file2) return file2;
5381
5574
  }
@@ -5389,7 +5582,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5389
5582
  const files = await readdir3(directory);
5390
5583
  for (const file of files) {
5391
5584
  if (file.endsWith(".jsonl") && file.includes(threadId)) {
5392
- const fullPath = join13(directory, file);
5585
+ const fullPath = join14(directory, file);
5393
5586
  const stats = await stat2(fullPath);
5394
5587
  if (stats.isFile()) {
5395
5588
  return fullPath;
@@ -5446,15 +5639,13 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5446
5639
  seenLines.add(line);
5447
5640
  try {
5448
5641
  const parsed = JSON.parse(line);
5449
- const snapshot = extractRateLimitsSnapshot(parsed);
5642
+ const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
5450
5643
  if (snapshot) latest = snapshot;
5451
5644
  } catch {
5452
5645
  }
5453
5646
  }
5454
5647
  if (latest) {
5455
- this.latestQuotaState = latest.state;
5456
- this.latestQuotaSnapshot = latest;
5457
- this.quotaBlocked = latest.state === "out_of_credits";
5648
+ this.quotaStatus.prime(latest);
5458
5649
  }
5459
5650
  } catch {
5460
5651
  }
@@ -5474,7 +5665,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5474
5665
  seenLines.add(trimmed);
5475
5666
  try {
5476
5667
  const parsed = JSON.parse(trimmed);
5477
- const snapshot = extractRateLimitsSnapshot(parsed);
5668
+ const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
5478
5669
  if (snapshot) {
5479
5670
  this.emitQuotaStatus(snapshot);
5480
5671
  }
@@ -5787,6 +5978,26 @@ var AppServerProcess = class {
5787
5978
  throw error;
5788
5979
  }
5789
5980
  }
5981
+ async stop() {
5982
+ const child = this.child;
5983
+ this.shuttingDown = true;
5984
+ this.client?.dispose(new Error("ASP process stopped"));
5985
+ this.client = null;
5986
+ this.child = null;
5987
+ if (!child || child.killed) {
5988
+ return;
5989
+ }
5990
+ await new Promise((resolve3) => {
5991
+ const timer = setTimeout(() => {
5992
+ child.kill("SIGKILL");
5993
+ }, 2e3);
5994
+ child.once("exit", () => {
5995
+ clearTimeout(timer);
5996
+ resolve3();
5997
+ });
5998
+ child.kill("SIGTERM");
5999
+ });
6000
+ }
5790
6001
  async loginWithConfiguredApiKey(client) {
5791
6002
  if (ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD !== "api_key" || !ENGINE_ENV.OPENAI_API_KEY) {
5792
6003
  return;
@@ -5814,12 +6025,21 @@ var AppServerProcess = class {
5814
6025
 
5815
6026
  // src/managers/codex-asp/asp-host.ts
5816
6027
  var hostPromise = null;
6028
+ var activeProcess = null;
6029
+ var restartPromise = null;
5817
6030
  async function getCodexAspHost() {
6031
+ if (restartPromise) {
6032
+ await restartPromise;
6033
+ }
5818
6034
  hostPromise ??= (async () => {
5819
6035
  try {
5820
6036
  const process2 = new AppServerProcess();
5821
6037
  const { client } = await process2.start();
6038
+ activeProcess = process2;
5822
6039
  process2.on("exit", () => {
6040
+ if (activeProcess === process2) {
6041
+ activeProcess = null;
6042
+ }
5823
6043
  hostPromise = null;
5824
6044
  });
5825
6045
  return { client };
@@ -5830,21 +6050,67 @@ async function getCodexAspHost() {
5830
6050
  })();
5831
6051
  return hostPromise;
5832
6052
  }
6053
+ async function restartCodexAspHost() {
6054
+ if (restartPromise) {
6055
+ return restartPromise;
6056
+ }
6057
+ restartPromise = (async () => {
6058
+ const process2 = activeProcess;
6059
+ hostPromise = null;
6060
+ activeProcess = null;
6061
+ if (process2) {
6062
+ await process2.stop();
6063
+ }
6064
+ })();
6065
+ try {
6066
+ await restartPromise;
6067
+ } finally {
6068
+ restartPromise = null;
6069
+ }
6070
+ }
5833
6071
 
5834
6072
  // src/managers/codex-asp/codex-asp-manager.ts
5835
6073
  var DEFAULT_MODEL2 = "gpt-5.5";
5836
6074
  var THREAD_START_METHOD = "thread/start";
6075
+ var THREAD_RESUME_METHOD = "thread/resume";
6076
+ var THREAD_READ_METHOD = "thread/read";
5837
6077
  var TURN_START_METHOD = "turn/start";
5838
6078
  var TURN_INTERRUPT_METHOD = "turn/interrupt";
6079
+ var ACCOUNT_RATE_LIMITS_READ_METHOD = "account/rateLimits/read";
6080
+ var TURN_STARTED_METHOD = "turn/started";
5839
6081
  var TURN_COMPLETED_METHOD = "turn/completed";
6082
+ var TURN_PLAN_UPDATED_METHOD = "turn/plan/updated";
6083
+ var ITEM_STARTED_METHOD = "item/started";
5840
6084
  var ITEM_COMPLETED_METHOD = "item/completed";
5841
6085
  var AGENT_MESSAGE_DELTA_METHOD = "item/agentMessage/delta";
6086
+ var ACCOUNT_RATE_LIMITS_UPDATED_METHOD = "account/rateLimits/updated";
6087
+ var THREAD_TOKEN_USAGE_UPDATED_METHOD = "thread/tokenUsage/updated";
6088
+ var THREAD_COMPACTED_METHOD = "thread/compacted";
5842
6089
  var COMMAND_APPROVAL_METHOD = "item/commandExecution/requestApproval";
5843
6090
  var FILE_CHANGE_APPROVAL_METHOD = "item/fileChange/requestApproval";
6091
+ function toReasoningEffort(thinkingLevel) {
6092
+ return codexReasoningEffortForThinkingLevel(thinkingLevel);
6093
+ }
6094
+ function extractRateLimitsSnapshot(rateLimits) {
6095
+ const credits = rateLimits.credits;
6096
+ return buildCodexRateLimitsSnapshot({
6097
+ unlimited: credits?.unlimited ?? null,
6098
+ hasCredits: credits?.hasCredits ?? null,
6099
+ balance: credits?.balance ?? null,
6100
+ rateLimitResetType: rateLimits.rateLimitReachedType || null,
6101
+ planType: rateLimits.planType
6102
+ });
6103
+ }
6104
+ function timestampFromSeconds(value) {
6105
+ return new Date((value ?? Date.now() / 1e3) * 1e3).toISOString();
6106
+ }
5844
6107
  var CodexAspManager = class extends CodingAgentManager {
5845
6108
  currentThreadId = null;
5846
6109
  activeTurnId = null;
6110
+ threadAttached = false;
5847
6111
  historyEvents = [];
6112
+ emittedItemStages = /* @__PURE__ */ new Set();
6113
+ quotaStatus = new CodexQuotaStatusTracker();
5848
6114
  constructor(options) {
5849
6115
  super(options);
5850
6116
  this.initializeManager(this.processMessageInternal.bind(this));
@@ -5870,57 +6136,115 @@ var CodexAspManager = class extends CodingAgentManager {
5870
6136
  }
5871
6137
  }
5872
6138
  async getHistory() {
6139
+ if (!this.currentThreadId) {
6140
+ return { thread_id: null, events: [] };
6141
+ }
6142
+ try {
6143
+ const host = await getCodexAspHost();
6144
+ const response = await host.client.request(
6145
+ THREAD_READ_METHOD,
6146
+ { threadId: this.currentThreadId, includeTurns: true }
6147
+ );
6148
+ const events = this.threadToHistoryEvents(response.thread);
6149
+ if (events.length > 0) {
6150
+ const supplementalEvents = this.historyEvents.filter((event) => event.type === CODEX_QUOTA_STATUS_EVENT_TYPE || event.type === CONTEXT_USAGE_EVENT_TYPE);
6151
+ return {
6152
+ thread_id: this.currentThreadId,
6153
+ events: [...events, ...supplementalEvents].sort((a, b) => a.timestamp.localeCompare(b.timestamp))
6154
+ };
6155
+ }
6156
+ } catch {
6157
+ }
5873
6158
  return {
5874
6159
  thread_id: this.currentThreadId,
5875
6160
  events: [...this.historyEvents]
5876
6161
  };
5877
6162
  }
5878
6163
  async processMessageInternal(request) {
5879
- let host = null;
5880
- try {
5881
- host = await getCodexAspHost();
5882
- const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
6164
+ let userMessageRecorded = false;
6165
+ const recordUserMessage = () => {
6166
+ if (userMessageRecorded) return;
6167
+ userMessageRecorded = true;
5883
6168
  this.recordHistoryEvent("event_msg", {
5884
6169
  type: "user_message",
5885
6170
  message: request.message
5886
6171
  });
5887
- if (!this.currentThreadId) {
5888
- const threadStartResponse = await host.client.request(
5889
- THREAD_START_METHOD,
5890
- await this.buildThreadStartParams(request, developerInstructions)
5891
- );
5892
- this.currentThreadId = threadStartResponse.thread.id;
5893
- await this.onSaveSessionId(this.currentThreadId);
5894
- }
5895
- const threadId = this.currentThreadId;
5896
- const completedTurn = await this.runTurn(host, threadId, request, developerInstructions);
5897
- if (completedTurn.status === "failed") {
5898
- const turnError = completedTurn.error;
5899
- if (!turnError) {
5900
- throw new Error("Codex ASP turn failed without error details");
5901
- }
5902
- const parts = [`Codex ASP turn failed: ${turnError.message}`];
5903
- if (turnError.codexErrorInfo !== null) {
5904
- parts.push(`codexErrorInfo=${JSON.stringify(turnError.codexErrorInfo)}`);
5905
- }
5906
- if (turnError.additionalDetails) {
5907
- parts.push(`details=${turnError.additionalDetails}`);
6172
+ };
6173
+ try {
6174
+ await this.executeAspTurn(request, recordUserMessage);
6175
+ } catch (error) {
6176
+ if (isCodexAuthError(error)) {
6177
+ const refreshed = await codexTokenManager.fetchFreshCredentials(error instanceof Error ? error.message : String(error));
6178
+ if (refreshed) {
6179
+ await restartCodexAspHost();
6180
+ this.threadAttached = false;
6181
+ await this.executeAspTurn(request, recordUserMessage);
6182
+ return;
5908
6183
  }
5909
- throw new Error(parts.join(" "));
5910
- }
5911
- if (completedTurn.status === "completed") {
5912
- this.emitFinalAgentMessage(completedTurn);
5913
6184
  }
6185
+ throw error;
5914
6186
  } finally {
5915
6187
  this.activeTurnId = null;
5916
- if (host?.client.isDisposed) {
5917
- this.currentThreadId = null;
5918
- void this.onSaveSessionId(null).catch(() => {
5919
- });
5920
- }
5921
6188
  await this.onTurnComplete();
5922
6189
  }
5923
6190
  }
6191
+ async executeAspTurn(request, recordUserMessage) {
6192
+ const host = await getCodexAspHost();
6193
+ if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
6194
+ await this.refreshQuotaSnapshot(host);
6195
+ if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
6196
+ recordUserMessage();
6197
+ this.emitQuotaStatus(this.quotaStatus.latestSnapshot, true);
6198
+ return;
6199
+ }
6200
+ }
6201
+ const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
6202
+ const threadId = await this.ensureThread(host, request, developerInstructions);
6203
+ recordUserMessage();
6204
+ let completedTurn;
6205
+ try {
6206
+ completedTurn = await this.runTurn(host, threadId, request, developerInstructions);
6207
+ } catch (error) {
6208
+ await this.refreshQuotaSnapshot(host);
6209
+ if (this.quotaStatus.blocked) {
6210
+ return;
6211
+ }
6212
+ throw error;
6213
+ }
6214
+ if (completedTurn.status === "failed") {
6215
+ await this.refreshQuotaSnapshot(host);
6216
+ if (this.quotaStatus.blocked) {
6217
+ return;
6218
+ }
6219
+ throw new Error(this.formatTurnFailure(completedTurn));
6220
+ }
6221
+ if (completedTurn.status === "completed") {
6222
+ this.emitTurnCompletedItems(completedTurn);
6223
+ }
6224
+ }
6225
+ async ensureThread(host, request, developerInstructions) {
6226
+ if (this.currentThreadId) {
6227
+ if (!this.threadAttached) {
6228
+ const response = await host.client.request(
6229
+ THREAD_RESUME_METHOD,
6230
+ await this.buildThreadResumeParams(this.currentThreadId, request, developerInstructions)
6231
+ );
6232
+ this.currentThreadId = response.thread.id;
6233
+ this.threadAttached = true;
6234
+ this.seedHistoryFromThread(response.thread);
6235
+ await this.onSaveSessionId(this.currentThreadId);
6236
+ }
6237
+ return this.currentThreadId;
6238
+ }
6239
+ const threadStartResponse = await host.client.request(
6240
+ THREAD_START_METHOD,
6241
+ await this.buildThreadStartParams(request, developerInstructions)
6242
+ );
6243
+ this.currentThreadId = threadStartResponse.thread.id;
6244
+ this.threadAttached = true;
6245
+ await this.onSaveSessionId(this.currentThreadId);
6246
+ return this.currentThreadId;
6247
+ }
5924
6248
  async buildThreadStartParams(request, developerInstructions) {
5925
6249
  const additionalDirectories = await getAgentAdditionalDirectories();
5926
6250
  return {
@@ -5934,30 +6258,61 @@ var CodexAspManager = class extends CodingAgentManager {
5934
6258
  persistExtendedHistory: false
5935
6259
  };
5936
6260
  }
5937
- buildTurnStartParams(threadId, request, developerInstructions) {
5938
- const effort = request.thinkingLevel === "max" ? "xhigh" : request.thinkingLevel;
5939
- const model = request.model ?? DEFAULT_MODEL2;
6261
+ async buildThreadResumeParams(threadId, request, developerInstructions) {
6262
+ const additionalDirectories = await getAgentAdditionalDirectories();
5940
6263
  return {
5941
6264
  threadId,
5942
- input: [{
5943
- type: "text",
5944
- text: request.message,
5945
- text_elements: []
5946
- }],
5947
- model,
5948
- ...effort ? { effort } : {},
5949
- ...developerInstructions ? {
5950
- collaborationMode: {
5951
- mode: "default",
5952
- settings: {
5953
- model,
5954
- reasoning_effort: effort ?? null,
5955
- developer_instructions: developerInstructions
6265
+ model: request.model ?? DEFAULT_MODEL2,
6266
+ cwd: this.workingDirectory,
6267
+ runtimeWorkspaceRoots: additionalDirectories,
6268
+ sandbox: "danger-full-access",
6269
+ developerInstructions: developerInstructions ?? null,
6270
+ config: { web_search: "live" },
6271
+ excludeTurns: false,
6272
+ persistExtendedHistory: false
6273
+ };
6274
+ }
6275
+ async buildTurnStartParams(threadId, request, developerInstructions) {
6276
+ const effort = toReasoningEffort(request.thinkingLevel);
6277
+ const model = request.model ?? DEFAULT_MODEL2;
6278
+ const { input, tempImagePaths } = await this.buildTurnInput(request);
6279
+ return {
6280
+ params: {
6281
+ threadId,
6282
+ input,
6283
+ model,
6284
+ ...effort ? { effort } : {},
6285
+ ...developerInstructions ? {
6286
+ collaborationMode: {
6287
+ mode: "default",
6288
+ settings: {
6289
+ model,
6290
+ reasoning_effort: effort ?? null,
6291
+ developer_instructions: developerInstructions
6292
+ }
5956
6293
  }
5957
- }
5958
- } : {}
6294
+ } : {}
6295
+ },
6296
+ tempImagePaths
5959
6297
  };
5960
6298
  }
6299
+ async buildTurnInput(request) {
6300
+ const input = [{
6301
+ type: "text",
6302
+ text: request.message,
6303
+ text_elements: []
6304
+ }];
6305
+ if (!request.images || request.images.length === 0) {
6306
+ return { input, tempImagePaths: [] };
6307
+ }
6308
+ const normalizedImages = await normalizeImages(request.images);
6309
+ const tempImagePaths = await saveNormalizedImagesToTempFiles(normalizedImages);
6310
+ input.push(...tempImagePaths.map((path4) => ({
6311
+ type: "localImage",
6312
+ path: path4
6313
+ })));
6314
+ return { input, tempImagePaths };
6315
+ }
5961
6316
  async runTurn(host, threadId, request, developerInstructions) {
5962
6317
  let resolveCompleted;
5963
6318
  const completed = new Promise((resolve3) => {
@@ -5973,15 +6328,62 @@ var CodexAspManager = class extends CodingAgentManager {
5973
6328
  let observedTurnId = null;
5974
6329
  const completedItems = [];
5975
6330
  const agentMessageDeltas = /* @__PURE__ */ new Map();
6331
+ const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
6332
+ const model = request.model ?? DEFAULT_MODEL2;
6333
+ let tempImagePaths = [];
6334
+ const linearForwarder = new LinearEventForwarder(linearSessionId);
6335
+ const matchesTurn = (notificationThreadId, notificationTurnId) => notificationThreadId === threadId && (!observedTurnId || notificationTurnId === null || notificationTurnId === observedTurnId);
5976
6336
  const onNotification = (notification) => {
6337
+ if (notification.method === ACCOUNT_RATE_LIMITS_UPDATED_METHOD) {
6338
+ this.handleRateLimits(notification.params.rateLimits);
6339
+ return;
6340
+ }
6341
+ if (notification.method === THREAD_TOKEN_USAGE_UPDATED_METHOD) {
6342
+ if (notification.params.threadId === threadId) {
6343
+ this.emitCodexTokenUsage(notification.params.tokenUsage, model);
6344
+ }
6345
+ return;
6346
+ }
6347
+ if (notification.method === THREAD_COMPACTED_METHOD) {
6348
+ if (matchesTurn(notification.params.threadId, notification.params.turnId)) {
6349
+ this.setCompacting(false);
6350
+ }
6351
+ return;
6352
+ }
6353
+ if (notification.method === TURN_STARTED_METHOD) {
6354
+ if (notification.params.threadId !== threadId) return;
6355
+ observedTurnId = notification.params.turn.id;
6356
+ this.activeTurnId = notification.params.turn.id;
6357
+ linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
6358
+ return;
6359
+ }
6360
+ if (notification.method === TURN_PLAN_UPDATED_METHOD) {
6361
+ if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
6362
+ this.emitPlanUpdate(notification.params.plan);
6363
+ linearForwarder.sendPlan(extractPlanFromCodexAspNotification(notification));
6364
+ return;
6365
+ }
6366
+ if (notification.method === ITEM_STARTED_METHOD) {
6367
+ if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
6368
+ if (notification.params.item.type === "contextCompaction") {
6369
+ this.setCompacting(true);
6370
+ }
6371
+ this.emitThreadItemLifecycle(notification.params.item, "started");
6372
+ linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
6373
+ return;
6374
+ }
5977
6375
  if (notification.method === ITEM_COMPLETED_METHOD) {
5978
- if (notification.params.threadId === threadId && (!observedTurnId || notification.params.turnId === observedTurnId)) {
5979
- completedItems.push(notification.params.item);
6376
+ if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
6377
+ completedItems.push(notification.params.item);
6378
+ if (notification.params.item.type === "contextCompaction") {
6379
+ this.setCompacting(false);
5980
6380
  }
6381
+ this.emitThreadItemLifecycle(notification.params.item, "completed");
6382
+ linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
5981
6383
  return;
5982
6384
  }
5983
6385
  if (notification.method === AGENT_MESSAGE_DELTA_METHOD) {
5984
- if (notification.params.threadId === threadId && (!observedTurnId || notification.params.turnId === observedTurnId)) {
6386
+ if (matchesTurn(notification.params.threadId, notification.params.turnId)) {
5985
6387
  const currentText = agentMessageDeltas.get(notification.params.itemId) ?? "";
5986
6388
  agentMessageDeltas.set(notification.params.itemId, currentText + notification.params.delta);
5987
6389
  }
@@ -6023,10 +6425,8 @@ var CodexAspManager = class extends CodingAgentManager {
6023
6425
  host.client.respond(serverRequest.id, { decision: "accept" });
6024
6426
  };
6025
6427
  const onDispose = (reason) => {
6026
- this.currentThreadId = null;
6428
+ this.threadAttached = false;
6027
6429
  this.activeTurnId = null;
6028
- void this.onSaveSessionId(null).catch(() => {
6029
- });
6030
6430
  const turnLabel = observedTurnId ? ` ${observedTurnId}` : "";
6031
6431
  rejectDisposed(new Error(`Codex ASP client disposed before turn${turnLabel} completed: ${reason.message}`));
6032
6432
  };
@@ -6034,40 +6434,196 @@ var CodexAspManager = class extends CodingAgentManager {
6034
6434
  host.client.on("serverRequest", onServerRequest);
6035
6435
  host.client.on("dispose", onDispose);
6036
6436
  try {
6437
+ const { params, tempImagePaths: turnTempImagePaths } = await this.buildTurnStartParams(threadId, request, developerInstructions);
6438
+ tempImagePaths = turnTempImagePaths;
6037
6439
  const turnStartResponse = await host.client.request(
6038
6440
  TURN_START_METHOD,
6039
- this.buildTurnStartParams(threadId, request, developerInstructions)
6441
+ params
6040
6442
  );
6041
6443
  observedTurnId = turnStartResponse.turn.id;
6042
6444
  this.activeTurnId = turnStartResponse.turn.id;
6043
- return await Promise.race([completed, disposed]);
6445
+ const turn = await Promise.race([completed, disposed]);
6446
+ linearForwarder.flushThoughtAsResponse();
6447
+ return turn;
6044
6448
  } finally {
6045
6449
  host.client.off("notification", onNotification);
6046
6450
  host.client.off("serverRequest", onServerRequest);
6047
6451
  host.client.off("dispose", onDispose);
6452
+ await removeTempImageFiles(tempImagePaths);
6453
+ if (host.client.isDisposed) {
6454
+ this.threadAttached = false;
6455
+ }
6048
6456
  }
6049
6457
  }
6050
- emitFinalAgentMessage(turn) {
6051
- let text = null;
6052
- for (let index = turn.items.length - 1; index >= 0; index -= 1) {
6053
- const item = turn.items[index];
6054
- if (item?.type === "agentMessage") {
6055
- text = item.text;
6056
- break;
6458
+ emitThreadItemLifecycle(item, lifecycle) {
6459
+ const stageKey = `${lifecycle}:${item.id}`;
6460
+ if (this.emittedItemStages.has(stageKey)) {
6461
+ return;
6462
+ }
6463
+ if (lifecycle === "completed" && !this.emittedItemStages.has(`started:${item.id}`)) {
6464
+ this.emittedItemStages.add(`started:${item.id}`);
6465
+ this.recordAndEmitDrafts(this.itemToAgentEventDrafts(item, "started"));
6466
+ }
6467
+ this.emittedItemStages.add(stageKey);
6468
+ this.recordAndEmitDrafts(this.itemToAgentEventDrafts(item, lifecycle));
6469
+ }
6470
+ emitTurnCompletedItems(turn) {
6471
+ for (const item of turn.items) {
6472
+ this.emitThreadItemLifecycle(item, "completed");
6473
+ }
6474
+ }
6475
+ emitPlanUpdate(plan) {
6476
+ if (plan.length === 0) return;
6477
+ this.recordAndEmitDrafts([{
6478
+ type: "response_item",
6479
+ payload: {
6480
+ type: "function_call",
6481
+ name: "update_plan",
6482
+ call_id: `plan-${Date.now()}`,
6483
+ arguments: JSON.stringify({ plan })
6057
6484
  }
6485
+ }]);
6486
+ }
6487
+ itemToAgentEventDrafts(item, lifecycle) {
6488
+ if (item.type === "agentMessage") {
6489
+ if (lifecycle !== "completed" || !item.text) return [];
6490
+ return [{
6491
+ type: "response_item",
6492
+ payload: {
6493
+ type: "message",
6494
+ role: "assistant",
6495
+ content: [{
6496
+ type: "output_text",
6497
+ text: item.text
6498
+ }]
6499
+ }
6500
+ }];
6058
6501
  }
6059
- if (!text) {
6060
- return;
6502
+ if (item.type === "reasoning") {
6503
+ if (lifecycle !== "completed") return [];
6504
+ const text = [...item.summary, ...item.content].filter(Boolean).join("\n").trim();
6505
+ if (!text) return [];
6506
+ return [{
6507
+ type: "event_msg",
6508
+ payload: {
6509
+ type: "agent_reasoning",
6510
+ text
6511
+ }
6512
+ }];
6513
+ }
6514
+ if (item.type === "commandExecution") {
6515
+ if (lifecycle === "started") {
6516
+ return [{
6517
+ type: "response_item",
6518
+ payload: {
6519
+ type: "function_call",
6520
+ name: "exec_command",
6521
+ call_id: item.id,
6522
+ arguments: JSON.stringify({ cmd: item.command })
6523
+ }
6524
+ }];
6525
+ }
6526
+ return [{
6527
+ type: "response_item",
6528
+ payload: {
6529
+ type: "function_call_output",
6530
+ call_id: item.id,
6531
+ output: this.formatCommandOutput(item)
6532
+ }
6533
+ }];
6534
+ }
6535
+ if (item.type === "fileChange") {
6536
+ if (lifecycle === "started") {
6537
+ return [{
6538
+ type: "response_item",
6539
+ payload: {
6540
+ type: "custom_tool_call",
6541
+ name: "apply_patch",
6542
+ call_id: item.id,
6543
+ input: this.fileChangeToPatchInput(item),
6544
+ status: "in_progress"
6545
+ }
6546
+ }];
6547
+ }
6548
+ return [{
6549
+ type: "response_item",
6550
+ payload: {
6551
+ type: "custom_tool_call_output",
6552
+ call_id: item.id,
6553
+ output: JSON.stringify({
6554
+ output: item.status,
6555
+ metadata: { exit_code: item.status === "completed" ? 0 : 1 }
6556
+ })
6557
+ }
6558
+ }];
6559
+ }
6560
+ if (item.type === "mcpToolCall" || item.type === "dynamicToolCall" || item.type === "webSearch") {
6561
+ const tool2 = item.type === "webSearch" ? "web_search" : item.tool;
6562
+ const input = item.type === "webSearch" ? item.query : JSON.stringify(item.arguments);
6563
+ if (lifecycle === "started") {
6564
+ return [{
6565
+ type: "response_item",
6566
+ payload: {
6567
+ type: "custom_tool_call",
6568
+ name: tool2,
6569
+ call_id: item.id,
6570
+ input,
6571
+ status: "in_progress"
6572
+ }
6573
+ }];
6574
+ }
6575
+ const failed = item.type === "mcpToolCall" && item.status === "failed" || item.type === "dynamicToolCall" && item.success === false;
6576
+ return [{
6577
+ type: "response_item",
6578
+ payload: {
6579
+ type: "custom_tool_call_output",
6580
+ call_id: item.id,
6581
+ output: JSON.stringify({
6582
+ output: failed ? "Failed" : "Done",
6583
+ metadata: { exit_code: failed ? 1 : 0 }
6584
+ })
6585
+ }
6586
+ }];
6587
+ }
6588
+ return [];
6589
+ }
6590
+ threadToHistoryEvents(thread) {
6591
+ const events = [];
6592
+ for (const turn of thread.turns) {
6593
+ const startedAt = timestampFromSeconds(turn.startedAt);
6594
+ const completedAt = timestampFromSeconds(turn.completedAt ?? turn.startedAt);
6595
+ for (const item of turn.items) {
6596
+ if (item.type === "userMessage") {
6597
+ const message = item.content.filter((input) => input.type === "text").map((input) => input.text).join("\n");
6598
+ if (message) {
6599
+ events.push({
6600
+ timestamp: startedAt,
6601
+ type: "event_msg",
6602
+ payload: { type: "user_message", message }
6603
+ });
6604
+ }
6605
+ continue;
6606
+ }
6607
+ for (const draft of this.itemToAgentEventDrafts(item, "started")) {
6608
+ events.push({ timestamp: startedAt, ...draft });
6609
+ }
6610
+ for (const draft of this.itemToAgentEventDrafts(item, "completed")) {
6611
+ events.push({ timestamp: completedAt, ...draft });
6612
+ }
6613
+ }
6614
+ }
6615
+ return events;
6616
+ }
6617
+ seedHistoryFromThread(thread) {
6618
+ const events = this.threadToHistoryEvents(thread);
6619
+ if (events.length === 0) return;
6620
+ this.historyEvents.splice(0, this.historyEvents.length, ...events);
6621
+ }
6622
+ recordAndEmitDrafts(drafts) {
6623
+ for (const draft of drafts) {
6624
+ const event = this.recordHistoryEvent(draft.type, draft.payload);
6625
+ this.onEvent(event);
6061
6626
  }
6062
- const event = this.recordHistoryEvent("response_item", {
6063
- type: "message",
6064
- role: "assistant",
6065
- content: [{
6066
- type: "output_text",
6067
- text
6068
- }]
6069
- });
6070
- this.onEvent(event);
6071
6627
  }
6072
6628
  recordHistoryEvent(type, payload) {
6073
6629
  const event = {
@@ -6078,6 +6634,87 @@ var CodexAspManager = class extends CodingAgentManager {
6078
6634
  this.historyEvents.push(event);
6079
6635
  return event;
6080
6636
  }
6637
+ handleRateLimits(rateLimits) {
6638
+ const snapshot = extractRateLimitsSnapshot(rateLimits);
6639
+ if (snapshot) {
6640
+ this.emitQuotaStatus(snapshot);
6641
+ }
6642
+ }
6643
+ async refreshQuotaSnapshot(host) {
6644
+ try {
6645
+ const response = await host.client.request(
6646
+ ACCOUNT_RATE_LIMITS_READ_METHOD,
6647
+ void 0
6648
+ );
6649
+ this.handleRateLimits(response.rateLimits);
6650
+ } catch {
6651
+ }
6652
+ }
6653
+ emitQuotaStatus(snapshot, force = false) {
6654
+ const event = this.quotaStatus.apply(snapshot, force);
6655
+ if (!event) return;
6656
+ this.historyEvents.push(event);
6657
+ this.onEvent(event);
6658
+ }
6659
+ emitCodexTokenUsage(tokenUsage, model) {
6660
+ const payload = buildCodexTokenUsageContextUsagePayload({
6661
+ model,
6662
+ modelContextWindow: tokenUsage.modelContextWindow,
6663
+ last: {
6664
+ inputTokens: tokenUsage.last.inputTokens,
6665
+ outputTokens: tokenUsage.last.outputTokens,
6666
+ totalTokens: tokenUsage.last.totalTokens,
6667
+ cachedInputTokens: tokenUsage.last.cachedInputTokens,
6668
+ reasoningOutputTokens: tokenUsage.last.reasoningOutputTokens
6669
+ },
6670
+ total: {
6671
+ totalTokens: tokenUsage.total.totalTokens
6672
+ },
6673
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6674
+ });
6675
+ const event = this.emitContextUsage(payload);
6676
+ this.historyEvents.push(event);
6677
+ }
6678
+ formatCommandOutput(item) {
6679
+ const exitCode = item.exitCode ?? (item.status === "completed" ? 0 : 1);
6680
+ const output = item.aggregatedOutput ?? "";
6681
+ return output ? `Exit code: ${exitCode}
6682
+ ${output}` : `Exit code: ${exitCode}`;
6683
+ }
6684
+ fileChangeToPatchInput(item) {
6685
+ const lines = ["*** Begin Patch"];
6686
+ for (const change of item.changes) {
6687
+ if (change.kind.type === "add") {
6688
+ lines.push(`*** Add File: ${change.path}`);
6689
+ } else if (change.kind.type === "delete") {
6690
+ lines.push(`*** Delete File: ${change.path}`);
6691
+ } else {
6692
+ lines.push(`*** Update File: ${change.path}`);
6693
+ if (change.kind.move_path) {
6694
+ lines.push(`*** Move to: ${change.kind.move_path}`);
6695
+ }
6696
+ }
6697
+ if (change.diff.trim().length > 0) {
6698
+ lines.push(change.diff);
6699
+ }
6700
+ }
6701
+ lines.push("*** End Patch");
6702
+ return lines.join("\n");
6703
+ }
6704
+ formatTurnFailure(turn) {
6705
+ const turnError = turn.error;
6706
+ if (!turnError) {
6707
+ return "Codex ASP turn failed without error details";
6708
+ }
6709
+ const parts = [`Codex ASP turn failed: ${turnError.message}`];
6710
+ if (turnError.codexErrorInfo !== null) {
6711
+ parts.push(`codexErrorInfo=${JSON.stringify(turnError.codexErrorInfo)}`);
6712
+ }
6713
+ if (turnError.additionalDetails) {
6714
+ parts.push(`details=${turnError.additionalDetails}`);
6715
+ }
6716
+ return parts.join(" ");
6717
+ }
6081
6718
  };
6082
6719
 
6083
6720
  // src/managers/relay-tools.ts
@@ -6686,12 +7323,12 @@ var DuplicateDefaultChatError = class extends Error {
6686
7323
  };
6687
7324
 
6688
7325
  // src/services/chat/chat-service.ts
6689
- var ENGINE_DIR2 = join14(homedir12(), ".replicas", "engine");
6690
- var CHATS_FILE = join14(ENGINE_DIR2, "chats.json");
6691
- var CLAUDE_HISTORY_DIR = join14(ENGINE_DIR2, "claude-histories");
6692
- var RELAY_HISTORY_DIR = join14(ENGINE_DIR2, "relay-histories");
6693
- var CHAT_SENDERS_DIR = join14(ENGINE_DIR2, "chat-senders");
6694
- var CODEX_AUTH_PATH2 = join14(homedir12(), ".codex", "auth.json");
7326
+ var ENGINE_DIR2 = join15(homedir13(), ".replicas", "engine");
7327
+ var CHATS_FILE = join15(ENGINE_DIR2, "chats.json");
7328
+ var CLAUDE_HISTORY_DIR = join15(ENGINE_DIR2, "claude-histories");
7329
+ var RELAY_HISTORY_DIR = join15(ENGINE_DIR2, "relay-histories");
7330
+ var CHAT_SENDERS_DIR = join15(ENGINE_DIR2, "chat-senders");
7331
+ var CODEX_AUTH_PATH2 = join15(homedir13(), ".codex", "auth.json");
6695
7332
  function isChatMessageSender(value) {
6696
7333
  if (!isRecord3(value)) return false;
6697
7334
  return typeof value.senderUserId === "string" && typeof value.senderEmail === "string" && typeof value.recordedAt === "string";
@@ -6714,10 +7351,10 @@ var ChatService = class {
6714
7351
  chats = /* @__PURE__ */ new Map();
6715
7352
  writeChain = Promise.resolve();
6716
7353
  async initialize() {
6717
- await mkdir10(ENGINE_DIR2, { recursive: true });
6718
- await mkdir10(CLAUDE_HISTORY_DIR, { recursive: true });
6719
- await mkdir10(RELAY_HISTORY_DIR, { recursive: true });
6720
- await mkdir10(CHAT_SENDERS_DIR, { recursive: true });
7354
+ await mkdir11(ENGINE_DIR2, { recursive: true });
7355
+ await mkdir11(CLAUDE_HISTORY_DIR, { recursive: true });
7356
+ await mkdir11(RELAY_HISTORY_DIR, { recursive: true });
7357
+ await mkdir11(CHAT_SENDERS_DIR, { recursive: true });
6721
7358
  const persisted = await this.loadChats();
6722
7359
  for (const chat of persisted) {
6723
7360
  const runtime = this.createRuntimeChat(chat);
@@ -6812,7 +7449,7 @@ var ChatService = class {
6812
7449
  };
6813
7450
  }
6814
7451
  senderFilePath(chatId) {
6815
- return join14(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
7452
+ return join15(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
6816
7453
  }
6817
7454
  async appendSender(chatId, sender) {
6818
7455
  try {
@@ -6929,7 +7566,7 @@ var ChatService = class {
6929
7566
  async deleteHistoryFile(persisted) {
6930
7567
  if (persisted.provider === "claude" || persisted.provider === "relay") {
6931
7568
  const dir = persisted.provider === "claude" ? CLAUDE_HISTORY_DIR : RELAY_HISTORY_DIR;
6932
- await rm(join14(dir, `${persisted.id}.jsonl`), { force: true });
7569
+ await rm(join15(dir, `${persisted.id}.jsonl`), { force: true });
6933
7570
  }
6934
7571
  await rm(this.senderFilePath(persisted.id), { force: true });
6935
7572
  }
@@ -6977,7 +7614,7 @@ var ChatService = class {
6977
7614
  if (persisted.provider === "claude") {
6978
7615
  provider = new ClaudeManager({
6979
7616
  workingDirectory: this.workingDirectory,
6980
- historyFilePath: join14(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
7617
+ historyFilePath: join15(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
6981
7618
  initialSessionId: persisted.providerSessionId,
6982
7619
  onSaveSessionId: saveSession,
6983
7620
  onTurnComplete: onProviderTurnComplete,
@@ -6986,7 +7623,7 @@ var ChatService = class {
6986
7623
  } else if (persisted.provider === "relay") {
6987
7624
  provider = new RelayManager({
6988
7625
  workingDirectory: this.workingDirectory,
6989
- historyFilePath: join14(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
7626
+ historyFilePath: join15(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
6990
7627
  initialSessionId: persisted.providerSessionId,
6991
7628
  onSaveSessionId: saveSession,
6992
7629
  onTurnComplete: onProviderTurnComplete,
@@ -7122,7 +7759,7 @@ var ChatService = class {
7122
7759
  this.writeChain = this.writeChain.catch(() => {
7123
7760
  }).then(async () => {
7124
7761
  const payload = Array.from(this.chats.values()).map((chat) => chat.persisted);
7125
- await writeFile8(CHATS_FILE, JSON.stringify(payload, null, 2), "utf-8");
7762
+ await writeFile9(CHATS_FILE, JSON.stringify(payload, null, 2), "utf-8");
7126
7763
  });
7127
7764
  await this.writeChain;
7128
7765
  }
@@ -7175,7 +7812,7 @@ var ChatService = class {
7175
7812
  // src/services/repo-file-service.ts
7176
7813
  import { execFile as execFile2 } from "child_process";
7177
7814
  import { readFile as readFile9, realpath, stat as stat3 } from "fs/promises";
7178
- import { join as join15, resolve, extname } from "path";
7815
+ import { join as join16, resolve, extname } from "path";
7179
7816
  var CACHE_TTL_MS = 3e4;
7180
7817
  var SEARCH_TIMEOUT_MS = 15e3;
7181
7818
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -7335,7 +7972,7 @@ var RepoFileService = class {
7335
7972
  const repo = repos.find((r) => r.name === repoName);
7336
7973
  if (!repo) return null;
7337
7974
  try {
7338
- const fullPath = await realpath(resolve(join15(repo.path, filePath)));
7975
+ const fullPath = await realpath(resolve(join16(repo.path, filePath)));
7339
7976
  const repoRoot = await realpath(repo.path);
7340
7977
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
7341
7978
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
@@ -7447,15 +8084,15 @@ var RepoFileService = class {
7447
8084
  import { Hono } from "hono";
7448
8085
  import { z as z2 } from "zod";
7449
8086
  import { readdir as readdir6, stat as stat4, readFile as readFile13 } from "fs/promises";
7450
- import { join as join19, resolve as resolve2 } from "path";
8087
+ import { join as join20, resolve as resolve2 } from "path";
7451
8088
 
7452
8089
  // src/services/plan-service.ts
7453
8090
  import { readdir as readdir4, readFile as readFile10 } from "fs/promises";
7454
- import { homedir as homedir13 } from "os";
7455
- import { basename, join as join16 } from "path";
8091
+ import { homedir as homedir14 } from "os";
8092
+ import { basename, join as join17 } from "path";
7456
8093
  var PLAN_DIRECTORIES = [
7457
- join16(homedir13(), ".claude", "plans"),
7458
- join16(homedir13(), ".replicas", "plans")
8094
+ join17(homedir14(), ".claude", "plans"),
8095
+ join17(homedir14(), ".replicas", "plans")
7459
8096
  ];
7460
8097
  function isMarkdownFile(filename) {
7461
8098
  return filename.toLowerCase().endsWith(".md");
@@ -7489,7 +8126,7 @@ var PlanService = class {
7489
8126
  return null;
7490
8127
  }
7491
8128
  for (const directory of PLAN_DIRECTORIES) {
7492
- const filePath = join16(directory, safeFilename);
8129
+ const filePath = join17(directory, safeFilename);
7493
8130
  try {
7494
8131
  const content = await readFile10(filePath, "utf-8");
7495
8132
  return { filename: safeFilename, content };
@@ -7505,14 +8142,14 @@ var planService = new PlanService();
7505
8142
  import { spawn as spawn2 } from "child_process";
7506
8143
  import { readFile as readFile12 } from "fs/promises";
7507
8144
  import { existsSync as existsSync8 } from "fs";
7508
- import { join as join18 } from "path";
8145
+ import { join as join19 } from "path";
7509
8146
 
7510
8147
  // src/services/warm-hook-logs-service.ts
7511
8148
  import { createHash as createHash2 } from "crypto";
7512
- import { mkdir as mkdir11, readFile as readFile11, writeFile as writeFile9, readdir as readdir5 } from "fs/promises";
7513
- import { homedir as homedir14 } from "os";
7514
- import { join as join17 } from "path";
7515
- var LOGS_DIR2 = join17(homedir14(), ".replicas", "warm-hook-logs");
8149
+ import { mkdir as mkdir12, readFile as readFile11, writeFile as writeFile10, readdir as readdir5 } from "fs/promises";
8150
+ import { homedir as homedir15 } from "os";
8151
+ import { join as join18 } from "path";
8152
+ var LOGS_DIR2 = join18(homedir15(), ".replicas", "warm-hook-logs");
7516
8153
  function sanitizeFilename2(name) {
7517
8154
  const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
7518
8155
  const hash = createHash2("sha256").update(name).digest("hex").slice(0, 8);
@@ -7533,7 +8170,7 @@ function withPreview2(stored) {
7533
8170
  }
7534
8171
  var WarmHookLogsService = class {
7535
8172
  async ensureDir() {
7536
- await mkdir11(LOGS_DIR2, { recursive: true });
8173
+ await mkdir12(LOGS_DIR2, { recursive: true });
7537
8174
  }
7538
8175
  async saveGlobalHookLog(entry) {
7539
8176
  await this.ensureDir();
@@ -7542,7 +8179,7 @@ var WarmHookLogsService = class {
7542
8179
  hookName: "organization",
7543
8180
  ...entry
7544
8181
  };
7545
- await writeFile9(join17(LOGS_DIR2, globalFilename()), `${JSON.stringify(log, null, 2)}
8182
+ await writeFile10(join18(LOGS_DIR2, globalFilename()), `${JSON.stringify(log, null, 2)}
7546
8183
  `, "utf-8");
7547
8184
  }
7548
8185
  async saveEnvironmentHookLog(entry) {
@@ -7552,7 +8189,7 @@ var WarmHookLogsService = class {
7552
8189
  hookName: "environment",
7553
8190
  ...entry
7554
8191
  };
7555
- await writeFile9(join17(LOGS_DIR2, environmentFilename()), `${JSON.stringify(log, null, 2)}
8192
+ await writeFile10(join18(LOGS_DIR2, environmentFilename()), `${JSON.stringify(log, null, 2)}
7556
8193
  `, "utf-8");
7557
8194
  }
7558
8195
  async saveRepoHookLog(repoName, entry) {
@@ -7562,7 +8199,7 @@ var WarmHookLogsService = class {
7562
8199
  hookName: repoName,
7563
8200
  ...entry
7564
8201
  };
7565
- await writeFile9(join17(LOGS_DIR2, repoFilename2(repoName)), `${JSON.stringify(log, null, 2)}
8202
+ await writeFile10(join18(LOGS_DIR2, repoFilename2(repoName)), `${JSON.stringify(log, null, 2)}
7566
8203
  `, "utf-8");
7567
8204
  }
7568
8205
  async getAllLogs() {
@@ -7581,7 +8218,7 @@ var WarmHookLogsService = class {
7581
8218
  continue;
7582
8219
  }
7583
8220
  try {
7584
- const raw = await readFile11(join17(LOGS_DIR2, file), "utf-8");
8221
+ const raw = await readFile11(join18(LOGS_DIR2, file), "utf-8");
7585
8222
  const stored = JSON.parse(raw);
7586
8223
  logs.push(withPreview2(stored));
7587
8224
  } catch {
@@ -7599,7 +8236,7 @@ var WarmHookLogsService = class {
7599
8236
  async getFullOutput(hookType, hookName) {
7600
8237
  const filename = hookType === "global" ? globalFilename() : hookType === "environment" ? environmentFilename() : repoFilename2(hookName);
7601
8238
  try {
7602
- const raw = await readFile11(join17(LOGS_DIR2, filename), "utf-8");
8239
+ const raw = await readFile11(join18(LOGS_DIR2, filename), "utf-8");
7603
8240
  const stored = JSON.parse(raw);
7604
8241
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
7605
8242
  return null;
@@ -7618,7 +8255,7 @@ var warmHookLogsService = new WarmHookLogsService();
7618
8255
  // src/services/warm-hooks-service.ts
7619
8256
  async function readRepoWarmHook(repoPath) {
7620
8257
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
7621
- const configPath = join18(repoPath, filename);
8258
+ const configPath = join19(repoPath, filename);
7622
8259
  if (!existsSync8(configPath)) {
7623
8260
  continue;
7624
8261
  }
@@ -8422,7 +9059,7 @@ function createV1Routes(deps) {
8422
9059
  const logFiles = files.filter((f) => f.endsWith(".log"));
8423
9060
  const sessions = await Promise.all(
8424
9061
  logFiles.map(async (filename) => {
8425
- const filePath = join19(LOG_DIR, filename);
9062
+ const filePath = join20(LOG_DIR, filename);
8426
9063
  const fileStat = await stat4(filePath);
8427
9064
  const sessionId = filename.replace(/\.log$/, "");
8428
9065
  return {