replicas-engine 0.1.216 → 0.1.218

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 +1136 -385
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -20,9 +20,21 @@ 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";
37
+ var CHAT_GOAL_EVENT_TYPE = "chat-goal";
26
38
  var CONTEXT_USAGE_EVENT_TYPE = "context-usage";
27
39
 
28
40
  // ../shared/src/languages.ts
@@ -115,6 +127,10 @@ function detectLanguageByPath(filePath) {
115
127
  }
116
128
 
117
129
  // ../shared/src/context-usage.ts
130
+ var CODEX_CATEGORY_COLORS = {
131
+ input: "#66bb6a",
132
+ output: "#56b6c2"
133
+ };
118
134
  function clampPercentage(value) {
119
135
  if (!Number.isFinite(value)) return 0;
120
136
  if (value < 0) return 0;
@@ -130,6 +146,54 @@ function clampTokensToWindow(rawTokens, maxTokens) {
130
146
  if (rawTokens <= maxTokens) return { totalTokens: rawTokens };
131
147
  return { totalTokens: maxTokens, totalProcessedTokens: rawTokens };
132
148
  }
149
+ function compactCategories(categories) {
150
+ return categories.filter((category) => Boolean(category && category.tokens > 0)).map((category) => ({
151
+ ...category,
152
+ percentage: clampPercentage(category.percentage)
153
+ }));
154
+ }
155
+ function buildCodexTokenUsageContextUsagePayload(usage) {
156
+ const maxTokens = usage.modelContextWindow;
157
+ const inputTokens = usage.last.inputTokens;
158
+ const outputTokens = usage.last.outputTokens;
159
+ const lastTurnTokens = usage.last.totalTokens;
160
+ const { totalTokens } = clampTokensToWindow(lastTurnTokens, maxTokens);
161
+ const cumulativeTotalTokens = usage.total?.totalTokens ?? null;
162
+ const totalProcessedTokens = cumulativeTotalTokens !== null && cumulativeTotalTokens > totalTokens ? cumulativeTotalTokens : clampTokensToWindow(lastTurnTokens, maxTokens).totalProcessedTokens;
163
+ return {
164
+ provider: "codex",
165
+ source: "codex_token_count",
166
+ ...usage.model !== void 0 ? { model: usage.model } : {},
167
+ totalTokens,
168
+ ...totalProcessedTokens !== void 0 ? { totalProcessedTokens } : {},
169
+ maxTokens,
170
+ rawMaxTokens: maxTokens,
171
+ percentage: percentage(totalTokens, maxTokens),
172
+ compactsAutomatically: true,
173
+ categories: compactCategories([
174
+ {
175
+ name: "Input context",
176
+ tokens: inputTokens,
177
+ percentage: percentage(inputTokens, maxTokens),
178
+ color: CODEX_CATEGORY_COLORS.input
179
+ },
180
+ {
181
+ name: "Generated output",
182
+ tokens: outputTokens,
183
+ percentage: percentage(outputTokens, maxTokens),
184
+ color: CODEX_CATEGORY_COLORS.output
185
+ }
186
+ ]),
187
+ apiUsage: {
188
+ inputTokens,
189
+ outputTokens,
190
+ ...usage.last.cachedInputTokens !== null && usage.last.cachedInputTokens !== void 0 ? { cachedInputTokens: usage.last.cachedInputTokens } : {},
191
+ ...usage.last.reasoningOutputTokens !== null && usage.last.reasoningOutputTokens !== void 0 ? { reasoningOutputTokens: usage.last.reasoningOutputTokens } : {},
192
+ ...cumulativeTotalTokens !== null ? { totalTokens: cumulativeTotalTokens } : {}
193
+ },
194
+ updatedAt: usage.updatedAt
195
+ };
196
+ }
133
197
 
134
198
  // ../shared/src/pricing.ts
135
199
  var PLANS = {
@@ -1565,6 +1629,15 @@ var DEFAULT_DEFAULT_SKILLS = {
1565
1629
  }
1566
1630
  };
1567
1631
 
1632
+ // ../shared/src/prompts.ts
1633
+ function parseGoalCommand(message) {
1634
+ const match = message.trim().match(/^\/goal(?:\s+([\s\S]*))?$/i);
1635
+ const value = match?.[1]?.trim();
1636
+ if (!value) return null;
1637
+ if (/^(clear|reset|unset)$/i.test(value)) return { type: "clear" };
1638
+ return { type: "set", objective: value };
1639
+ }
1640
+
1568
1641
  // ../shared/src/replicas-config.ts
1569
1642
  import { parse as parseYaml } from "yaml";
1570
1643
 
@@ -1686,7 +1759,7 @@ function parseReplicasConfigString(content, filename) {
1686
1759
  }
1687
1760
 
1688
1761
  // ../shared/src/engine/environment.ts
1689
- var DAYTONA_SNAPSHOT_ID = "26-05-2026-royal-york-v3";
1762
+ var DAYTONA_SNAPSHOT_ID = "26-05-2026-royal-york-v5";
1690
1763
 
1691
1764
  // ../shared/src/engine/types.ts
1692
1765
  var DEFAULT_CHAT_TITLES = {
@@ -3548,19 +3621,19 @@ var previewService = new PreviewService();
3548
3621
 
3549
3622
  // src/services/chat/chat-service.ts
3550
3623
  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";
3624
+ import { appendFile as appendFile5, mkdir as mkdir11, readFile as readFile8, rm, writeFile as writeFile9 } from "fs/promises";
3625
+ import { homedir as homedir13 } from "os";
3626
+ import { join as join15 } from "path";
3554
3627
  import { randomUUID as randomUUID5 } from "crypto";
3555
3628
 
3556
3629
  // src/managers/claude-manager.ts
3557
3630
  import {
3558
3631
  query
3559
3632
  } 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";
3633
+ import { randomUUID as randomUUID4 } from "crypto";
3634
+ import { join as join13 } from "path";
3635
+ import { mkdir as mkdir9, appendFile as appendFile4 } from "fs/promises";
3636
+ import { homedir as homedir11 } from "os";
3564
3637
 
3565
3638
  // src/utils/jsonl-reader.ts
3566
3639
  import { readFile as readFile6 } from "fs/promises";
@@ -3593,33 +3666,6 @@ async function readJSONL(filePath) {
3593
3666
  }
3594
3667
  }
3595
3668
 
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
3669
  // src/utils/linear-converter.ts
3624
3670
  function linearThoughtToResponse(thought) {
3625
3671
  return {
@@ -3999,8 +4045,138 @@ function extractPlanFromCodexEvent(event) {
3999
4045
  status: entry.completed ? "completed" : hasIncomplete ? "inProgress" : "pending"
4000
4046
  }));
4001
4047
  }
4048
+ function codexAspReasoningText(item) {
4049
+ return [...item.summary, ...item.content].filter(Boolean).join("\n").trim();
4050
+ }
4051
+ function codexAspChangePaths(item) {
4052
+ return item.changes.map((change) => change.path).filter(Boolean).join(", ");
4053
+ }
4054
+ function codexAspStatusResult(status) {
4055
+ return status === "completed" ? "Done" : status || "Done";
4056
+ }
4057
+ function convertCodexAspItem(item, lifecycle, linearSessionId) {
4058
+ if (item.type === "agentMessage") {
4059
+ if (!item.text) return null;
4060
+ return {
4061
+ linearSessionId,
4062
+ content: {
4063
+ type: "thought",
4064
+ body: item.text
4065
+ }
4066
+ };
4067
+ }
4068
+ if (item.type === "reasoning") {
4069
+ const text = codexAspReasoningText(item);
4070
+ if (!text) return null;
4071
+ return {
4072
+ linearSessionId,
4073
+ content: {
4074
+ type: "thought",
4075
+ body: text
4076
+ }
4077
+ };
4078
+ }
4079
+ if (item.type === "commandExecution") {
4080
+ return {
4081
+ linearSessionId,
4082
+ content: {
4083
+ type: "action",
4084
+ action: "Running command",
4085
+ parameter: item.command,
4086
+ ...lifecycle === "completed" ? { result: item.exitCode !== null ? `Exit code: ${item.exitCode}` : item.aggregatedOutput || "Done" } : {}
4087
+ }
4088
+ };
4089
+ }
4090
+ if (item.type === "fileChange") {
4091
+ return {
4092
+ linearSessionId,
4093
+ content: {
4094
+ type: "action",
4095
+ action: "File change",
4096
+ parameter: codexAspChangePaths(item),
4097
+ ...lifecycle === "completed" ? { result: codexAspStatusResult(item.status) } : {}
4098
+ }
4099
+ };
4100
+ }
4101
+ if (item.type === "mcpToolCall") {
4102
+ return {
4103
+ linearSessionId,
4104
+ content: {
4105
+ type: "action",
4106
+ action: item.tool || "MCP tool call",
4107
+ parameter: item.server,
4108
+ ...lifecycle === "completed" ? { result: item.error?.message ?? codexAspStatusResult(item.status) } : {}
4109
+ }
4110
+ };
4111
+ }
4112
+ if (item.type === "dynamicToolCall") {
4113
+ return {
4114
+ linearSessionId,
4115
+ content: {
4116
+ type: "action",
4117
+ action: item.tool,
4118
+ parameter: item.namespace ?? "",
4119
+ ...lifecycle === "completed" ? { result: item.success === false ? "Failed" : codexAspStatusResult(item.status) } : {}
4120
+ }
4121
+ };
4122
+ }
4123
+ if (item.type === "webSearch") {
4124
+ return {
4125
+ linearSessionId,
4126
+ content: {
4127
+ type: "action",
4128
+ action: "Web search",
4129
+ parameter: item.query,
4130
+ ...lifecycle === "completed" ? { result: "Done" } : {}
4131
+ }
4132
+ };
4133
+ }
4134
+ if (item.type === "plan") {
4135
+ return {
4136
+ linearSessionId,
4137
+ content: {
4138
+ type: "action",
4139
+ action: "Updating plan",
4140
+ parameter: item.text,
4141
+ ...lifecycle === "completed" ? { result: "Done" } : {}
4142
+ }
4143
+ };
4144
+ }
4145
+ return null;
4146
+ }
4147
+ function convertCodexAspNotification(notification, linearSessionId) {
4148
+ if (notification.method === "turn/started") {
4149
+ return {
4150
+ linearSessionId,
4151
+ content: {
4152
+ type: "thought",
4153
+ body: "Processing..."
4154
+ }
4155
+ };
4156
+ }
4157
+ if (notification.method === "item/started") {
4158
+ return convertCodexAspItem(notification.params.item, "started", linearSessionId);
4159
+ }
4160
+ if (notification.method === "item/completed") {
4161
+ return convertCodexAspItem(notification.params.item, "completed", linearSessionId);
4162
+ }
4163
+ return null;
4164
+ }
4165
+ function extractPlanFromCodexAspNotification(notification) {
4166
+ if (notification.method !== "turn/plan/updated" || notification.params.plan.length === 0) {
4167
+ return null;
4168
+ }
4169
+ return notification.params.plan.filter((item) => item.step.trim().length > 0).map((item) => ({
4170
+ content: item.step.trim(),
4171
+ status: item.status
4172
+ }));
4173
+ }
4002
4174
 
4003
4175
  // src/utils/image-utils.ts
4176
+ import { randomUUID as randomUUID3 } from "crypto";
4177
+ import { mkdir as mkdir8, unlink as unlink2, writeFile as writeFile7 } from "fs/promises";
4178
+ import { homedir as homedir10 } from "os";
4179
+ import { join as join12 } from "path";
4004
4180
  function isImageMediaType(value) {
4005
4181
  return IMAGE_MEDIA_TYPES.includes(value);
4006
4182
  }
@@ -4078,6 +4254,26 @@ async function normalizeImages(images) {
4078
4254
  }
4079
4255
  return normalized;
4080
4256
  }
4257
+ async function saveNormalizedImagesToTempFiles(images, tempImageDir = join12(homedir10(), ".replicas", "codex", "temp-images")) {
4258
+ await mkdir8(tempImageDir, { recursive: true });
4259
+ const tempPaths = [];
4260
+ try {
4261
+ for (const image of images) {
4262
+ const ext = image.source.media_type.split("/")[1] || "png";
4263
+ const filename = `img_${randomUUID3()}.${ext}`;
4264
+ const filepath = join12(tempImageDir, filename);
4265
+ await writeFile7(filepath, Buffer.from(image.source.data, "base64"));
4266
+ tempPaths.push(filepath);
4267
+ }
4268
+ } catch (error) {
4269
+ await removeTempImageFiles(tempPaths);
4270
+ throw error;
4271
+ }
4272
+ return tempPaths;
4273
+ }
4274
+ async function removeTempImageFiles(paths) {
4275
+ await Promise.allSettled(paths.map((path4) => unlink2(path4)));
4276
+ }
4081
4277
 
4082
4278
  // src/services/message-queue-service.ts
4083
4279
  var MessageQueueService = class {
@@ -4216,6 +4412,33 @@ var MessageQueueService = class {
4216
4412
  }
4217
4413
  };
4218
4414
 
4415
+ // src/services/monolith-service.ts
4416
+ var MonolithService = class {
4417
+ async sendEvent(event) {
4418
+ if (!ENGINE_ENV.WORKSPACE_ID) {
4419
+ return;
4420
+ }
4421
+ try {
4422
+ const response = await fetch(`${ENGINE_ENV.MONOLITH_URL}/v1/engine/webhook`, {
4423
+ method: "POST",
4424
+ headers: {
4425
+ Authorization: `Bearer ${ENGINE_ENV.REPLICAS_ENGINE_SECRET}`,
4426
+ "X-Workspace-Id": ENGINE_ENV.WORKSPACE_ID,
4427
+ "Content-Type": "application/json"
4428
+ },
4429
+ body: JSON.stringify(event)
4430
+ });
4431
+ if (!response.ok) {
4432
+ const errorText = await response.text();
4433
+ console.error(`[MonolithService] Failed to send event: ${response.status} ${errorText}`);
4434
+ }
4435
+ } catch (error) {
4436
+ console.error("[MonolithService] Failed to send event:", error);
4437
+ }
4438
+ }
4439
+ };
4440
+ var monolithService = new MonolithService();
4441
+
4219
4442
  // src/managers/coding-agent-manager.ts
4220
4443
  var MAX_INTERRUPT_QUEUE_ITEMS = 1e3;
4221
4444
  var MAX_INTERRUPT_QUEUE_CHARS = 2e5;
@@ -4425,6 +4648,49 @@ async function getAgentAdditionalDirectories() {
4425
4648
  }
4426
4649
  }
4427
4650
 
4651
+ // src/utils/linear-event-forwarder.ts
4652
+ function isLinearThoughtEvent(event) {
4653
+ return event.content.type === "thought";
4654
+ }
4655
+ var LinearEventForwarder = class {
4656
+ linearSessionId;
4657
+ latestThoughtEvent = null;
4658
+ constructor(linearSessionId) {
4659
+ this.linearSessionId = linearSessionId;
4660
+ }
4661
+ sendPlan(plan) {
4662
+ if (!this.linearSessionId || !plan) return;
4663
+ monolithService.sendEvent({
4664
+ type: "agent_plan_update",
4665
+ payload: { linearSessionId: this.linearSessionId, plan }
4666
+ }).catch(() => {
4667
+ });
4668
+ }
4669
+ sendEvent(linearEvent) {
4670
+ if (!this.linearSessionId || !linearEvent) return;
4671
+ if (this.latestThoughtEvent) {
4672
+ monolithService.sendEvent({ type: "agent_update", payload: this.latestThoughtEvent }).catch(() => {
4673
+ });
4674
+ }
4675
+ if (isLinearThoughtEvent(linearEvent)) {
4676
+ this.latestThoughtEvent = linearEvent;
4677
+ return;
4678
+ }
4679
+ this.latestThoughtEvent = null;
4680
+ monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
4681
+ });
4682
+ }
4683
+ flushThoughtAsResponse() {
4684
+ if (!this.linearSessionId || !this.latestThoughtEvent) return;
4685
+ monolithService.sendEvent({
4686
+ type: "agent_update",
4687
+ payload: linearThoughtToResponse(this.latestThoughtEvent)
4688
+ }).catch(() => {
4689
+ });
4690
+ this.latestThoughtEvent = null;
4691
+ }
4692
+ };
4693
+
4428
4694
  // src/managers/claude-manager.ts
4429
4695
  var PromptStream = class {
4430
4696
  queue = [];
@@ -4464,9 +4730,6 @@ var PromptStream = class {
4464
4730
  };
4465
4731
  }
4466
4732
  };
4467
- function isLinearThoughtEvent(event) {
4468
- return event.content.type === "thought";
4469
- }
4470
4733
  var ALWAYS_ALLOWED_TOOLS = [
4471
4734
  "Read",
4472
4735
  "Glob",
@@ -4589,7 +4852,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4589
4852
  pendingToolInputs = /* @__PURE__ */ new Map();
4590
4853
  constructor(options) {
4591
4854
  super(options);
4592
- this.historyFile = options.historyFilePath ?? join12(homedir10(), ".replicas", "claude", "history.jsonl");
4855
+ this.historyFile = options.historyFilePath ?? join13(homedir11(), ".replicas", "claude", "history.jsonl");
4593
4856
  this.systemPromptOverride = options.systemPromptOverride;
4594
4857
  this.toolsOverride = options.tools;
4595
4858
  this.mcpServersConfig = options.mcpServers;
@@ -4653,7 +4916,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4653
4916
  if (!handler) {
4654
4917
  return { behavior: "allow" };
4655
4918
  }
4656
- const requestId = randomUUID3();
4919
+ const requestId = randomUUID4();
4657
4920
  const toolUseId = options.toolUseID;
4658
4921
  const { options: requestOptions, questions: requestQuestions } = handler.getRequest(input);
4659
4922
  const result = await new Promise((resolve3) => {
@@ -4813,32 +5076,12 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4813
5076
  this.activePromptStream?.close();
4814
5077
  await this.activeQuery.interrupt();
4815
5078
  }
4816
- let latestThoughtEvent = null;
5079
+ const linearForwarder = new LinearEventForwarder(linearSessionId);
4817
5080
  for await (const msg of response) {
4818
5081
  await this.handleMessage(msg);
4819
5082
  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
- }
5083
+ linearForwarder.sendPlan(extractPlanFromClaudeEvent(msg));
5084
+ linearForwarder.sendEvent(convertClaudeEvent(msg, linearSessionId));
4842
5085
  }
4843
5086
  if (msg.type === "result") {
4844
5087
  await this.recordContextUsage(response);
@@ -4846,11 +5089,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4846
5089
  break;
4847
5090
  }
4848
5091
  }
4849
- if (linearSessionId && latestThoughtEvent) {
4850
- const responseEvent = linearThoughtToResponse(latestThoughtEvent);
4851
- monolithService.sendEvent({ type: "agent_update", payload: responseEvent }).catch(() => {
4852
- });
4853
- }
5092
+ linearForwarder.flushThoughtAsResponse();
4854
5093
  } finally {
4855
5094
  this.activeQuery = null;
4856
5095
  this.activePromptStream?.close();
@@ -4927,8 +5166,8 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4927
5166
  };
4928
5167
  }
4929
5168
  async initialize() {
4930
- const historyDir = join12(homedir10(), ".replicas", "claude");
4931
- await mkdir8(historyDir, { recursive: true });
5169
+ const historyDir = join13(homedir11(), ".replicas", "claude");
5170
+ await mkdir9(historyDir, { recursive: true });
4932
5171
  if (this.initialSessionId) {
4933
5172
  this.sessionId = this.initialSessionId;
4934
5173
  console.log(`[ClaudeManager] Restored session ID from persisted state: ${this.sessionId}`);
@@ -4990,27 +5229,29 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4990
5229
 
4991
5230
  // src/managers/codex-manager.ts
4992
5231
  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";
5232
+ import { readdir as readdir3, stat as stat2, writeFile as writeFile8, mkdir as mkdir10, readFile as readFile7 } from "fs/promises";
4995
5233
  import { existsSync as existsSync6 } from "fs";
4996
- import { join as join13 } from "path";
4997
- import { homedir as homedir11 } from "os";
5234
+ import { join as join14 } from "path";
5235
+ import { homedir as homedir12 } from "os";
4998
5236
  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;
5237
+
5238
+ // src/utils/codex-quota.ts
5239
+ function buildCodexRateLimitsSnapshot(fields) {
5240
+ if (fields.unlimited === true) return null;
5241
+ let state = "ok";
5242
+ if (fields.hasCredits === false) {
5243
+ state = "out_of_credits";
5244
+ } else if (fields.rateLimitResetType !== null) {
5245
+ state = "rate_limited";
5007
5246
  }
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));
5247
+ return {
5248
+ state,
5249
+ balance: fields.balance,
5250
+ rateLimitResetType: fields.rateLimitResetType,
5251
+ planType: fields.planType
5252
+ };
5012
5253
  }
5013
- function extractRateLimitsSnapshot(parsed) {
5254
+ function extractCodexRateLimitsSnapshotFromJsonl(parsed) {
5014
5255
  if (!isRecord3(parsed)) return null;
5015
5256
  const payload = isRecord3(parsed.payload) ? parsed.payload : parsed;
5016
5257
  const rateLimits = isRecord3(payload.rate_limits) ? payload.rate_limits : null;
@@ -5019,60 +5260,100 @@ function extractRateLimitsSnapshot(parsed) {
5019
5260
  const hasCredits = credits && typeof credits.has_credits === "boolean" ? credits.has_credits : null;
5020
5261
  const unlimited = credits && typeof credits.unlimited === "boolean" ? credits.unlimited : null;
5021
5262
  const balance = credits && typeof credits.balance === "string" ? credits.balance : null;
5022
- if (unlimited === true) return null;
5023
5263
  const rateLimitResetType = typeof rateLimits.rate_limit_reached_type === "string" && rateLimits.rate_limit_reached_type.length > 0 ? rateLimits.rate_limit_reached_type : null;
5024
5264
  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";
5030
- }
5031
- return { state, balance, rateLimitResetType, planType };
5265
+ return buildCodexRateLimitsSnapshot({
5266
+ unlimited,
5267
+ hasCredits,
5268
+ balance,
5269
+ rateLimitResetType,
5270
+ planType
5271
+ });
5032
5272
  }
5033
- var CodexManager = class _CodexManager extends CodingAgentManager {
5034
- codex;
5035
- currentThreadId = null;
5036
- currentThread = null;
5037
- tempImageDir;
5038
- 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. */
5273
+ var CodexQuotaStatusTracker = class {
5042
5274
  lastEmittedQuotaState = "ok";
5043
- /** Last full snapshot, retained so we can re-emit when a user retries while blocked. */
5044
5275
  latestQuotaSnapshot = null;
5045
- /** When true, new turns short-circuit instead of running. Set by `out_of_credits`. */
5046
5276
  quotaBlocked = false;
5047
- constructor(options) {
5048
- super(options);
5049
- this.codex = this.createCodexClient();
5050
- this.tempImageDir = join13(homedir11(), ".replicas", "codex", "temp-images");
5051
- this.initializeManager(this.processMessageInternal.bind(this));
5277
+ get blocked() {
5278
+ return this.quotaBlocked;
5052
5279
  }
5053
- createCodexClient() {
5054
- const codexApiKey = resolveCodexApiKey();
5055
- return new Codex({
5056
- env: buildCodexAgentEnv(),
5057
- ...codexApiKey ? { apiKey: codexApiKey } : {}
5058
- });
5280
+ get latestSnapshot() {
5281
+ return this.latestQuotaSnapshot;
5059
5282
  }
5060
- resetCodexClient() {
5061
- this.codex = this.createCodexClient();
5062
- this.currentThread = null;
5283
+ prime(snapshot) {
5284
+ this.latestQuotaSnapshot = snapshot;
5285
+ this.quotaBlocked = snapshot.state === "out_of_credits";
5063
5286
  }
5064
- async initialize() {
5065
- if (this.initialSessionId) {
5066
- this.currentThreadId = this.initialSessionId;
5067
- console.log(`[CodexManager] Restored thread ID from persisted state: ${this.currentThreadId}`);
5287
+ apply(snapshot, force = false) {
5288
+ const stateChanged = snapshot.state !== this.lastEmittedQuotaState;
5289
+ if (!stateChanged && !force) {
5290
+ return null;
5291
+ }
5292
+ this.lastEmittedQuotaState = snapshot.state;
5293
+ this.quotaBlocked = snapshot.state === "out_of_credits";
5294
+ this.latestQuotaSnapshot = snapshot;
5295
+ const payload = {
5296
+ state: snapshot.state,
5297
+ balance: snapshot.balance,
5298
+ rateLimitResetType: snapshot.rateLimitResetType,
5299
+ planType: snapshot.planType
5300
+ };
5301
+ const event = {
5302
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5303
+ type: CODEX_QUOTA_STATUS_EVENT_TYPE,
5304
+ payload
5305
+ };
5306
+ return event;
5307
+ }
5308
+ };
5309
+
5310
+ // src/utils/codex-auth.ts
5311
+ function isCodexAuthError(error) {
5312
+ const msg = error instanceof Error ? error.message : String(error);
5313
+ const lower = msg.toLowerCase();
5314
+ 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");
5315
+ }
5316
+
5317
+ // src/managers/codex-manager.ts
5318
+ var DEFAULT_MODEL = "gpt-5.5";
5319
+ var CODEX_CONFIG_PATH = join14(homedir12(), ".codex", "config.toml");
5320
+ function isJsonlEvent2(value) {
5321
+ if (!isRecord3(value)) {
5322
+ return false;
5323
+ }
5324
+ return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord3(value.payload);
5325
+ }
5326
+ function sleep(ms) {
5327
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
5328
+ }
5329
+ var CodexManager = class extends CodingAgentManager {
5330
+ codex;
5331
+ currentThreadId = null;
5332
+ currentThread = null;
5333
+ activeAbortController = null;
5334
+ quotaStatus = new CodexQuotaStatusTracker();
5335
+ constructor(options) {
5336
+ super(options);
5337
+ this.codex = this.createCodexClient();
5338
+ this.initializeManager(this.processMessageInternal.bind(this));
5339
+ }
5340
+ createCodexClient() {
5341
+ const codexApiKey = resolveCodexApiKey();
5342
+ return new Codex({
5343
+ env: buildCodexAgentEnv(),
5344
+ ...codexApiKey ? { apiKey: codexApiKey } : {}
5345
+ });
5346
+ }
5347
+ resetCodexClient() {
5348
+ this.codex = this.createCodexClient();
5349
+ this.currentThread = null;
5350
+ }
5351
+ async initialize() {
5352
+ if (this.initialSessionId) {
5353
+ this.currentThreadId = this.initialSessionId;
5354
+ console.log(`[CodexManager] Restored thread ID from persisted state: ${this.currentThreadId}`);
5068
5355
  }
5069
5356
  }
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
5357
  async flushQuotaSnapshotFromCurrentSession() {
5077
5358
  if (!this.currentThreadId) return;
5078
5359
  try {
@@ -5084,7 +5365,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5084
5365
  for (const line of lines) {
5085
5366
  try {
5086
5367
  const parsed = JSON.parse(line);
5087
- const snapshot = extractRateLimitsSnapshot(parsed);
5368
+ const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
5088
5369
  if (snapshot) {
5089
5370
  latest = snapshot;
5090
5371
  }
@@ -5098,41 +5379,9 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5098
5379
  console.warn("[CodexManager] Failed to flush quota snapshot from session:", error);
5099
5380
  }
5100
5381
  }
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
5382
  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
- }
5383
+ const event = this.quotaStatus.apply(snapshot, force);
5384
+ if (event) this.onEvent(event);
5136
5385
  }
5137
5386
  async interruptActiveTurn() {
5138
5387
  if (this.activeAbortController) {
@@ -5145,8 +5394,8 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5145
5394
  */
5146
5395
  async updateCodexConfig(developerInstructions) {
5147
5396
  try {
5148
- const codexDir = join13(homedir11(), ".codex");
5149
- await mkdir9(codexDir, { recursive: true });
5397
+ const codexDir = join14(homedir12(), ".codex");
5398
+ await mkdir10(codexDir, { recursive: true });
5150
5399
  let config = {};
5151
5400
  if (existsSync6(CODEX_CONFIG_PATH)) {
5152
5401
  try {
@@ -5165,34 +5414,17 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5165
5414
  delete config.developer_instructions;
5166
5415
  }
5167
5416
  const tomlContent = stringifyToml(config);
5168
- await writeFile7(CODEX_CONFIG_PATH, tomlContent, "utf-8");
5417
+ await writeFile8(CODEX_CONFIG_PATH, tomlContent, "utf-8");
5169
5418
  console.log("[CodexManager] Updated config.toml with developer_instructions");
5170
5419
  } catch (error) {
5171
5420
  console.error("[CodexManager] Failed to update config.toml:", error);
5172
5421
  }
5173
5422
  }
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
5423
  async processMessageInternal(request) {
5192
5424
  try {
5193
5425
  await this.executeCodexTurn(request);
5194
5426
  } catch (error) {
5195
- if (_CodexManager.isAuthError(error)) {
5427
+ if (isCodexAuthError(error)) {
5196
5428
  const refreshed = await codexTokenManager.fetchFreshCredentials(error instanceof Error ? error.message : String(error));
5197
5429
  if (refreshed) {
5198
5430
  this.resetCodexClient();
@@ -5204,10 +5436,10 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5204
5436
  }
5205
5437
  }
5206
5438
  async executeCodexTurn(request) {
5207
- if (this.quotaBlocked && this.latestQuotaSnapshot) {
5439
+ if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
5208
5440
  await this.flushQuotaSnapshotFromCurrentSession();
5209
- if (this.quotaBlocked && this.latestQuotaSnapshot) {
5210
- this.emitQuotaStatus(this.latestQuotaSnapshot, true);
5441
+ if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
5442
+ this.emitQuotaStatus(this.quotaStatus.latestSnapshot, true);
5211
5443
  try {
5212
5444
  await this.onTurnComplete();
5213
5445
  } catch (error) {
@@ -5231,13 +5463,13 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5231
5463
  try {
5232
5464
  if (images && images.length > 0) {
5233
5465
  const normalizedImages = await normalizeImages(images);
5234
- tempImagePaths = await this.saveImagesToTempFiles(normalizedImages);
5466
+ tempImagePaths = await saveNormalizedImagesToTempFiles(normalizedImages);
5235
5467
  }
5236
5468
  const developerInstructions = this.buildCombinedInstructions(customInstructions);
5237
5469
  await this.updateCodexConfig(developerInstructions);
5238
5470
  const sandboxMode = "danger-full-access";
5239
5471
  const webSearchMode = "live";
5240
- const codexReasoningEffort = thinkingLevel ? thinkingLevel === "max" ? "xhigh" : thinkingLevel : void 0;
5472
+ const codexReasoningEffort = codexReasoningEffortForThinkingLevel(thinkingLevel);
5241
5473
  const additionalDirectories = await getAgentAdditionalDirectories();
5242
5474
  const threadOptions = {
5243
5475
  workingDirectory: this.workingDirectory,
@@ -5281,42 +5513,17 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5281
5513
  }
5282
5514
  try {
5283
5515
  const { events } = await this.currentThread.runStreamed(input, { signal: abortController.signal });
5284
- let latestThoughtEvent = null;
5516
+ const linearForwarder = new LinearEventForwarder(linearSessionId);
5285
5517
  for await (const event of events) {
5286
5518
  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
- }
5519
+ linearForwarder.sendPlan(extractPlanFromCodexEvent(event));
5520
+ linearForwarder.sendEvent(convertCodexEvent(event, linearSessionId));
5309
5521
  }
5310
5522
  }
5311
- if (linearSessionId && latestThoughtEvent) {
5312
- const responseEvent = linearThoughtToResponse(latestThoughtEvent);
5313
- monolithService.sendEvent({ type: "agent_update", payload: responseEvent }).catch(() => {
5314
- });
5315
- }
5523
+ linearForwarder.flushThoughtAsResponse();
5316
5524
  } catch (error) {
5317
5525
  await this.flushQuotaSnapshotFromCurrentSession();
5318
- if (this.quotaBlocked) {
5319
- console.warn("[CodexManager] runStreamed failed while quota was blocked \u2014 surfacing as quota state.");
5526
+ if (this.quotaStatus.blocked) {
5320
5527
  return;
5321
5528
  }
5322
5529
  throw error;
@@ -5325,6 +5532,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5325
5532
  if (stopTail) {
5326
5533
  await stopTail();
5327
5534
  }
5535
+ await removeTempImageFiles(tempImagePaths);
5328
5536
  try {
5329
5537
  await this.onTurnComplete();
5330
5538
  } catch (error) {
@@ -5333,11 +5541,6 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5333
5541
  this.activeAbortController = null;
5334
5542
  }
5335
5543
  }
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
5544
  async getHistory() {
5342
5545
  if (!this.currentThreadId) {
5343
5546
  return {
@@ -5360,13 +5563,13 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5360
5563
  }
5361
5564
  // Helper methods for finding session files
5362
5565
  async findSessionFile(threadId) {
5363
- const sessionsDir = join13(homedir11(), ".codex", "sessions");
5566
+ const sessionsDir = join14(homedir12(), ".codex", "sessions");
5364
5567
  try {
5365
5568
  const now = /* @__PURE__ */ new Date();
5366
5569
  const year = now.getFullYear();
5367
5570
  const month = String(now.getMonth() + 1).padStart(2, "0");
5368
5571
  const day = String(now.getDate()).padStart(2, "0");
5369
- const todayDir = join13(sessionsDir, String(year), month, day);
5572
+ const todayDir = join14(sessionsDir, String(year), month, day);
5370
5573
  const file = await this.findFileInDirectory(todayDir, threadId);
5371
5574
  if (file) return file;
5372
5575
  for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
@@ -5375,7 +5578,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5375
5578
  const searchYear = date.getFullYear();
5376
5579
  const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
5377
5580
  const searchDay = String(date.getDate()).padStart(2, "0");
5378
- const searchDir = join13(sessionsDir, String(searchYear), searchMonth, searchDay);
5581
+ const searchDir = join14(sessionsDir, String(searchYear), searchMonth, searchDay);
5379
5582
  const file2 = await this.findFileInDirectory(searchDir, threadId);
5380
5583
  if (file2) return file2;
5381
5584
  }
@@ -5389,7 +5592,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5389
5592
  const files = await readdir3(directory);
5390
5593
  for (const file of files) {
5391
5594
  if (file.endsWith(".jsonl") && file.includes(threadId)) {
5392
- const fullPath = join13(directory, file);
5595
+ const fullPath = join14(directory, file);
5393
5596
  const stats = await stat2(fullPath);
5394
5597
  if (stats.isFile()) {
5395
5598
  return fullPath;
@@ -5446,15 +5649,13 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5446
5649
  seenLines.add(line);
5447
5650
  try {
5448
5651
  const parsed = JSON.parse(line);
5449
- const snapshot = extractRateLimitsSnapshot(parsed);
5652
+ const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
5450
5653
  if (snapshot) latest = snapshot;
5451
5654
  } catch {
5452
5655
  }
5453
5656
  }
5454
5657
  if (latest) {
5455
- this.latestQuotaState = latest.state;
5456
- this.latestQuotaSnapshot = latest;
5457
- this.quotaBlocked = latest.state === "out_of_credits";
5658
+ this.quotaStatus.prime(latest);
5458
5659
  }
5459
5660
  } catch {
5460
5661
  }
@@ -5474,7 +5675,7 @@ var CodexManager = class _CodexManager extends CodingAgentManager {
5474
5675
  seenLines.add(trimmed);
5475
5676
  try {
5476
5677
  const parsed = JSON.parse(trimmed);
5477
- const snapshot = extractRateLimitsSnapshot(parsed);
5678
+ const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
5478
5679
  if (snapshot) {
5479
5680
  this.emitQuotaStatus(snapshot);
5480
5681
  }
@@ -5787,6 +5988,26 @@ var AppServerProcess = class {
5787
5988
  throw error;
5788
5989
  }
5789
5990
  }
5991
+ async stop() {
5992
+ const child = this.child;
5993
+ this.shuttingDown = true;
5994
+ this.client?.dispose(new Error("ASP process stopped"));
5995
+ this.client = null;
5996
+ this.child = null;
5997
+ if (!child || child.killed) {
5998
+ return;
5999
+ }
6000
+ await new Promise((resolve3) => {
6001
+ const timer = setTimeout(() => {
6002
+ child.kill("SIGKILL");
6003
+ }, 2e3);
6004
+ child.once("exit", () => {
6005
+ clearTimeout(timer);
6006
+ resolve3();
6007
+ });
6008
+ child.kill("SIGTERM");
6009
+ });
6010
+ }
5790
6011
  async loginWithConfiguredApiKey(client) {
5791
6012
  if (ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD !== "api_key" || !ENGINE_ENV.OPENAI_API_KEY) {
5792
6013
  return;
@@ -5814,12 +6035,21 @@ var AppServerProcess = class {
5814
6035
 
5815
6036
  // src/managers/codex-asp/asp-host.ts
5816
6037
  var hostPromise = null;
6038
+ var activeProcess = null;
6039
+ var restartPromise = null;
5817
6040
  async function getCodexAspHost() {
6041
+ if (restartPromise) {
6042
+ await restartPromise;
6043
+ }
5818
6044
  hostPromise ??= (async () => {
5819
6045
  try {
5820
6046
  const process2 = new AppServerProcess();
5821
6047
  const { client } = await process2.start();
6048
+ activeProcess = process2;
5822
6049
  process2.on("exit", () => {
6050
+ if (activeProcess === process2) {
6051
+ activeProcess = null;
6052
+ }
5823
6053
  hostPromise = null;
5824
6054
  });
5825
6055
  return { client };
@@ -5830,21 +6060,332 @@ async function getCodexAspHost() {
5830
6060
  })();
5831
6061
  return hostPromise;
5832
6062
  }
6063
+ async function restartCodexAspHost() {
6064
+ if (restartPromise) {
6065
+ return restartPromise;
6066
+ }
6067
+ restartPromise = (async () => {
6068
+ const process2 = activeProcess;
6069
+ hostPromise = null;
6070
+ activeProcess = null;
6071
+ if (process2) {
6072
+ await process2.stop();
6073
+ }
6074
+ })();
6075
+ try {
6076
+ await restartPromise;
6077
+ } finally {
6078
+ restartPromise = null;
6079
+ }
6080
+ }
5833
6081
 
5834
- // src/managers/codex-asp/codex-asp-manager.ts
6082
+ // src/managers/codex-asp/mappers.ts
5835
6083
  var DEFAULT_MODEL2 = "gpt-5.5";
5836
6084
  var THREAD_START_METHOD = "thread/start";
6085
+ var THREAD_RESUME_METHOD = "thread/resume";
6086
+ var THREAD_READ_METHOD = "thread/read";
6087
+ var THREAD_GOAL_SET_METHOD = "thread/goal/set";
6088
+ var THREAD_GOAL_GET_METHOD = "thread/goal/get";
6089
+ var THREAD_GOAL_CLEAR_METHOD = "thread/goal/clear";
5837
6090
  var TURN_START_METHOD = "turn/start";
5838
6091
  var TURN_INTERRUPT_METHOD = "turn/interrupt";
6092
+ var ACCOUNT_RATE_LIMITS_READ_METHOD = "account/rateLimits/read";
6093
+ function toReasoningEffort(thinkingLevel) {
6094
+ return codexReasoningEffortForThinkingLevel(thinkingLevel);
6095
+ }
6096
+ function extractRateLimitsSnapshot(rateLimits) {
6097
+ const credits = rateLimits.credits;
6098
+ return buildCodexRateLimitsSnapshot({
6099
+ unlimited: credits?.unlimited ?? null,
6100
+ hasCredits: credits?.hasCredits ?? null,
6101
+ balance: credits?.balance ?? null,
6102
+ rateLimitResetType: rateLimits.rateLimitReachedType || null,
6103
+ planType: rateLimits.planType
6104
+ });
6105
+ }
6106
+ function timestampFromSeconds(value) {
6107
+ return new Date((value ?? Date.now() / 1e3) * 1e3).toISOString();
6108
+ }
6109
+ function aspGoalToChatGoal(goal) {
6110
+ return {
6111
+ threadId: goal.threadId,
6112
+ objective: goal.objective,
6113
+ status: goal.status,
6114
+ tokenBudget: goal.tokenBudget,
6115
+ tokensUsed: goal.tokensUsed,
6116
+ timeUsedSeconds: goal.timeUsedSeconds,
6117
+ createdAt: timestampFromSeconds(goal.createdAt),
6118
+ updatedAt: timestampFromSeconds(goal.updatedAt)
6119
+ };
6120
+ }
6121
+ function formatTurnFailure(turn) {
6122
+ const turnError = turn.error;
6123
+ if (!turnError) {
6124
+ return "Codex ASP turn failed without error details";
6125
+ }
6126
+ const parts = [`Codex ASP turn failed: ${turnError.message}`];
6127
+ if (turnError.codexErrorInfo !== null) {
6128
+ parts.push(`codexErrorInfo=${JSON.stringify(turnError.codexErrorInfo)}`);
6129
+ }
6130
+ if (turnError.additionalDetails) {
6131
+ parts.push(`details=${turnError.additionalDetails}`);
6132
+ }
6133
+ return parts.join(" ");
6134
+ }
6135
+ function formatCommandOutput(item) {
6136
+ const exitCode = item.exitCode ?? (item.status === "completed" ? 0 : 1);
6137
+ const output = item.aggregatedOutput ?? "";
6138
+ return output ? `Exit code: ${exitCode}
6139
+ ${output}` : `Exit code: ${exitCode}`;
6140
+ }
6141
+ function fileChangeToPatchInput(item) {
6142
+ const lines = ["*** Begin Patch"];
6143
+ for (const change of item.changes) {
6144
+ if (change.kind.type === "add") {
6145
+ lines.push(`*** Add File: ${change.path}`);
6146
+ } else if (change.kind.type === "delete") {
6147
+ lines.push(`*** Delete File: ${change.path}`);
6148
+ } else {
6149
+ lines.push(`*** Update File: ${change.path}`);
6150
+ if (change.kind.move_path) {
6151
+ lines.push(`*** Move to: ${change.kind.move_path}`);
6152
+ }
6153
+ }
6154
+ if (change.diff.trim().length > 0) {
6155
+ lines.push(change.diff);
6156
+ }
6157
+ }
6158
+ lines.push("*** End Patch");
6159
+ return lines.join("\n");
6160
+ }
6161
+ function itemToAgentEventDrafts(item, lifecycle) {
6162
+ if (item.type === "agentMessage") {
6163
+ if (lifecycle !== "completed" || !item.text) return [];
6164
+ return [{
6165
+ type: "response_item",
6166
+ payload: {
6167
+ type: "message",
6168
+ role: "assistant",
6169
+ content: [{
6170
+ type: "output_text",
6171
+ text: item.text
6172
+ }]
6173
+ }
6174
+ }];
6175
+ }
6176
+ if (item.type === "reasoning") {
6177
+ if (lifecycle !== "completed") return [];
6178
+ const text = [...item.summary, ...item.content].filter(Boolean).join("\n").trim();
6179
+ if (!text) return [];
6180
+ return [{
6181
+ type: "event_msg",
6182
+ payload: {
6183
+ type: "agent_reasoning",
6184
+ text
6185
+ }
6186
+ }];
6187
+ }
6188
+ if (item.type === "commandExecution") {
6189
+ if (lifecycle === "started") {
6190
+ return [{
6191
+ type: "response_item",
6192
+ payload: {
6193
+ type: "function_call",
6194
+ name: "exec_command",
6195
+ call_id: item.id,
6196
+ arguments: JSON.stringify({ cmd: item.command })
6197
+ }
6198
+ }];
6199
+ }
6200
+ return [{
6201
+ type: "response_item",
6202
+ payload: {
6203
+ type: "function_call_output",
6204
+ call_id: item.id,
6205
+ output: formatCommandOutput(item)
6206
+ }
6207
+ }];
6208
+ }
6209
+ if (item.type === "fileChange") {
6210
+ if (lifecycle === "started") {
6211
+ return [{
6212
+ type: "response_item",
6213
+ payload: {
6214
+ type: "custom_tool_call",
6215
+ name: "apply_patch",
6216
+ call_id: item.id,
6217
+ input: fileChangeToPatchInput(item),
6218
+ status: "in_progress"
6219
+ }
6220
+ }];
6221
+ }
6222
+ return [{
6223
+ type: "response_item",
6224
+ payload: {
6225
+ type: "custom_tool_call_output",
6226
+ call_id: item.id,
6227
+ output: JSON.stringify({
6228
+ output: item.status,
6229
+ metadata: { exit_code: item.status === "completed" ? 0 : 1 }
6230
+ })
6231
+ }
6232
+ }];
6233
+ }
6234
+ if (item.type === "mcpToolCall" || item.type === "dynamicToolCall" || item.type === "webSearch") {
6235
+ const tool2 = item.type === "webSearch" ? "web_search" : item.tool;
6236
+ const input = item.type === "webSearch" ? item.query : JSON.stringify(item.arguments);
6237
+ if (lifecycle === "started") {
6238
+ return [{
6239
+ type: "response_item",
6240
+ payload: {
6241
+ type: "custom_tool_call",
6242
+ name: tool2,
6243
+ call_id: item.id,
6244
+ input,
6245
+ status: "in_progress"
6246
+ }
6247
+ }];
6248
+ }
6249
+ const failed = item.type === "mcpToolCall" && item.status === "failed" || item.type === "dynamicToolCall" && item.success === false;
6250
+ return [{
6251
+ type: "response_item",
6252
+ payload: {
6253
+ type: "custom_tool_call_output",
6254
+ call_id: item.id,
6255
+ output: JSON.stringify({
6256
+ output: failed ? "Failed" : "Done",
6257
+ metadata: { exit_code: failed ? 1 : 0 }
6258
+ })
6259
+ }
6260
+ }];
6261
+ }
6262
+ return [];
6263
+ }
6264
+ function threadToHistoryEvents(thread) {
6265
+ const events = [];
6266
+ for (const turn of thread.turns) {
6267
+ const startedAt = timestampFromSeconds(turn.startedAt);
6268
+ const completedAt = timestampFromSeconds(turn.completedAt ?? turn.startedAt);
6269
+ for (const item of turn.items) {
6270
+ if (item.type === "userMessage") {
6271
+ const message = item.content.filter((input) => input.type === "text").map((input) => input.text).join("\n");
6272
+ if (message) {
6273
+ events.push({
6274
+ timestamp: startedAt,
6275
+ type: "event_msg",
6276
+ payload: { type: "user_message", message }
6277
+ });
6278
+ }
6279
+ continue;
6280
+ }
6281
+ for (const draft of itemToAgentEventDrafts(item, "started")) {
6282
+ events.push({ timestamp: startedAt, ...draft });
6283
+ }
6284
+ for (const draft of itemToAgentEventDrafts(item, "completed")) {
6285
+ events.push({ timestamp: completedAt, ...draft });
6286
+ }
6287
+ }
6288
+ }
6289
+ return events;
6290
+ }
6291
+ async function buildThreadStartParams(workingDirectory, request, developerInstructions) {
6292
+ const additionalDirectories = await getAgentAdditionalDirectories();
6293
+ return {
6294
+ model: request.model ?? DEFAULT_MODEL2,
6295
+ cwd: workingDirectory,
6296
+ runtimeWorkspaceRoots: additionalDirectories,
6297
+ sandbox: "danger-full-access",
6298
+ developerInstructions: developerInstructions ?? null,
6299
+ config: { web_search: "live" },
6300
+ experimentalRawEvents: false,
6301
+ persistExtendedHistory: false
6302
+ };
6303
+ }
6304
+ async function buildThreadResumeParams(workingDirectory, threadId, request, developerInstructions) {
6305
+ const additionalDirectories = await getAgentAdditionalDirectories();
6306
+ return {
6307
+ threadId,
6308
+ model: request.model ?? DEFAULT_MODEL2,
6309
+ cwd: workingDirectory,
6310
+ runtimeWorkspaceRoots: additionalDirectories,
6311
+ sandbox: "danger-full-access",
6312
+ developerInstructions: developerInstructions ?? null,
6313
+ config: { web_search: "live" },
6314
+ excludeTurns: false,
6315
+ persistExtendedHistory: false
6316
+ };
6317
+ }
6318
+ async function buildTurnInput(request) {
6319
+ const input = [{
6320
+ type: "text",
6321
+ text: request.message,
6322
+ text_elements: []
6323
+ }];
6324
+ if (!request.images || request.images.length === 0) {
6325
+ return { input, tempImagePaths: [] };
6326
+ }
6327
+ const normalizedImages = await normalizeImages(request.images);
6328
+ const tempImagePaths = await saveNormalizedImagesToTempFiles(normalizedImages);
6329
+ input.push(...tempImagePaths.map((path4) => ({
6330
+ type: "localImage",
6331
+ path: path4
6332
+ })));
6333
+ return { input, tempImagePaths };
6334
+ }
6335
+ async function buildTurnStartParams(threadId, request, developerInstructions) {
6336
+ const effort = toReasoningEffort(request.thinkingLevel);
6337
+ const model = request.model ?? DEFAULT_MODEL2;
6338
+ const { input, tempImagePaths } = await buildTurnInput(request);
6339
+ return {
6340
+ params: {
6341
+ threadId,
6342
+ input,
6343
+ model,
6344
+ ...effort ? { effort } : {},
6345
+ ...developerInstructions ? {
6346
+ collaborationMode: {
6347
+ mode: "default",
6348
+ settings: {
6349
+ model,
6350
+ reasoning_effort: effort ?? null,
6351
+ developer_instructions: developerInstructions
6352
+ }
6353
+ }
6354
+ } : {}
6355
+ },
6356
+ tempImagePaths
6357
+ };
6358
+ }
6359
+
6360
+ // src/managers/codex-asp/notification-dispatch.ts
6361
+ var TURN_STARTED_METHOD = "turn/started";
5839
6362
  var TURN_COMPLETED_METHOD = "turn/completed";
6363
+ var TURN_PLAN_UPDATED_METHOD = "turn/plan/updated";
6364
+ var THREAD_GOAL_UPDATED_METHOD = "thread/goal/updated";
6365
+ var THREAD_GOAL_CLEARED_METHOD = "thread/goal/cleared";
6366
+ var ITEM_STARTED_METHOD = "item/started";
5840
6367
  var ITEM_COMPLETED_METHOD = "item/completed";
5841
6368
  var AGENT_MESSAGE_DELTA_METHOD = "item/agentMessage/delta";
6369
+ var ACCOUNT_RATE_LIMITS_UPDATED_METHOD = "account/rateLimits/updated";
6370
+ var THREAD_TOKEN_USAGE_UPDATED_METHOD = "thread/tokenUsage/updated";
6371
+ var THREAD_COMPACTED_METHOD = "thread/compacted";
5842
6372
  var COMMAND_APPROVAL_METHOD = "item/commandExecution/requestApproval";
5843
6373
  var FILE_CHANGE_APPROVAL_METHOD = "item/fileChange/requestApproval";
6374
+ function dispatchAspNotification(notification, handlers) {
6375
+ const handler = handlers[notification.method];
6376
+ if (!handler) return;
6377
+ handler(notification);
6378
+ }
6379
+
6380
+ // src/managers/codex-asp/codex-asp-manager.ts
5844
6381
  var CodexAspManager = class extends CodingAgentManager {
5845
6382
  currentThreadId = null;
5846
6383
  activeTurnId = null;
6384
+ threadAttached = false;
5847
6385
  historyEvents = [];
6386
+ emittedItemStages = /* @__PURE__ */ new Set();
6387
+ quotaStatus = new CodexQuotaStatusTracker();
6388
+ currentGoal = null;
5848
6389
  constructor(options) {
5849
6390
  super(options);
5850
6391
  this.initializeManager(this.processMessageInternal.bind(this));
@@ -5870,95 +6411,172 @@ var CodexAspManager = class extends CodingAgentManager {
5870
6411
  }
5871
6412
  }
5872
6413
  async getHistory() {
6414
+ if (!this.currentThreadId) {
6415
+ return { thread_id: null, events: [], goal: null };
6416
+ }
6417
+ try {
6418
+ const host = await getCodexAspHost();
6419
+ const [response] = await Promise.all([
6420
+ host.client.request(
6421
+ THREAD_READ_METHOD,
6422
+ { threadId: this.currentThreadId, includeTurns: true }
6423
+ ),
6424
+ this.refreshThreadGoal(host, this.currentThreadId)
6425
+ ]);
6426
+ const events = threadToHistoryEvents(response.thread);
6427
+ if (events.length > 0) {
6428
+ const supplementalEvents = this.historyEvents.filter((event) => event.type === CODEX_QUOTA_STATUS_EVENT_TYPE || event.type === CONTEXT_USAGE_EVENT_TYPE || event.type === CHAT_GOAL_EVENT_TYPE || event.type === "event_msg" && event.payload.command === "goal");
6429
+ return {
6430
+ thread_id: this.currentThreadId,
6431
+ events: [...events, ...supplementalEvents].sort((a, b) => a.timestamp.localeCompare(b.timestamp)),
6432
+ goal: this.currentGoal
6433
+ };
6434
+ }
6435
+ } catch {
6436
+ }
5873
6437
  return {
5874
6438
  thread_id: this.currentThreadId,
5875
- events: [...this.historyEvents]
6439
+ events: [...this.historyEvents],
6440
+ goal: this.currentGoal
5876
6441
  };
5877
6442
  }
6443
+ getGoal() {
6444
+ return this.currentGoal;
6445
+ }
5878
6446
  async processMessageInternal(request) {
5879
- let host = null;
5880
- try {
5881
- host = await getCodexAspHost();
5882
- const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
6447
+ let userMessageRecorded = false;
6448
+ const recordUserMessage = (extraPayload = {}) => {
6449
+ if (userMessageRecorded) return;
6450
+ userMessageRecorded = true;
5883
6451
  this.recordHistoryEvent("event_msg", {
5884
6452
  type: "user_message",
5885
- message: request.message
6453
+ message: request.message,
6454
+ ...extraPayload
5886
6455
  });
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);
6456
+ };
6457
+ const goalCommand = parseGoalCommand(request.message);
6458
+ const dispatch = async () => {
6459
+ if (goalCommand?.type === "clear") {
6460
+ await this.executeGoalClearCommand(request, recordUserMessage);
6461
+ return;
5894
6462
  }
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}`);
5908
- }
5909
- throw new Error(parts.join(" "));
6463
+ if (goalCommand?.type === "set") {
6464
+ await this.executeAspTurn(request, recordUserMessage, {
6465
+ runTurn: (host, threadId) => this.runGoalTurn(host, threadId, request, goalCommand.objective),
6466
+ userMessagePayload: { command: "goal" }
6467
+ });
6468
+ return;
5910
6469
  }
5911
- if (completedTurn.status === "completed") {
5912
- this.emitFinalAgentMessage(completedTurn);
6470
+ await this.executeAspTurn(request, recordUserMessage);
6471
+ };
6472
+ try {
6473
+ await dispatch();
6474
+ } catch (error) {
6475
+ if (isCodexAuthError(error)) {
6476
+ const refreshed = await codexTokenManager.fetchFreshCredentials(error instanceof Error ? error.message : String(error));
6477
+ if (refreshed) {
6478
+ await restartCodexAspHost();
6479
+ this.threadAttached = false;
6480
+ await dispatch();
6481
+ return;
6482
+ }
5913
6483
  }
6484
+ throw error;
5914
6485
  } finally {
5915
6486
  this.activeTurnId = null;
5916
- if (host?.client.isDisposed) {
5917
- this.currentThreadId = null;
5918
- void this.onSaveSessionId(null).catch(() => {
5919
- });
5920
- }
5921
6487
  await this.onTurnComplete();
5922
6488
  }
5923
6489
  }
5924
- async buildThreadStartParams(request, developerInstructions) {
5925
- const additionalDirectories = await getAgentAdditionalDirectories();
5926
- return {
5927
- model: request.model ?? DEFAULT_MODEL2,
5928
- cwd: this.workingDirectory,
5929
- runtimeWorkspaceRoots: additionalDirectories,
5930
- sandbox: "danger-full-access",
5931
- developerInstructions: developerInstructions ?? null,
5932
- config: { web_search: "live" },
5933
- experimentalRawEvents: false,
5934
- persistExtendedHistory: false
5935
- };
6490
+ async executeGoalClearCommand(request, recordUserMessage) {
6491
+ const host = await getCodexAspHost();
6492
+ const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
6493
+ const threadId = await this.ensureThread(host, request, developerInstructions);
6494
+ recordUserMessage({ command: "goal" });
6495
+ await host.client.request(
6496
+ THREAD_GOAL_CLEAR_METHOD,
6497
+ { threadId }
6498
+ );
6499
+ this.recordGoalChange(null, true);
6500
+ }
6501
+ async executeAspTurn(request, recordUserMessage, options = {}) {
6502
+ const host = await getCodexAspHost();
6503
+ if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
6504
+ await this.refreshQuotaSnapshot(host);
6505
+ if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
6506
+ recordUserMessage(options.userMessagePayload);
6507
+ this.emitQuotaStatus(this.quotaStatus.latestSnapshot, true);
6508
+ return;
6509
+ }
6510
+ }
6511
+ const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
6512
+ const threadId = await this.ensureThread(host, request, developerInstructions);
6513
+ recordUserMessage(options.userMessagePayload);
6514
+ const runTurn = options.runTurn ?? ((aspHost, aspThreadId, aspInstructions) => this.runTurn(aspHost, aspThreadId, request, aspInstructions));
6515
+ let completedTurn;
6516
+ try {
6517
+ completedTurn = await runTurn(host, threadId, developerInstructions);
6518
+ } catch (error) {
6519
+ await this.refreshQuotaSnapshot(host);
6520
+ if (this.quotaStatus.blocked) {
6521
+ return;
6522
+ }
6523
+ throw error;
6524
+ }
6525
+ if (completedTurn.status === "failed") {
6526
+ await this.refreshQuotaSnapshot(host);
6527
+ if (this.quotaStatus.blocked) {
6528
+ return;
6529
+ }
6530
+ throw new Error(formatTurnFailure(completedTurn));
6531
+ }
6532
+ if (completedTurn.status === "completed") {
6533
+ this.emitTurnCompletedItems(completedTurn);
6534
+ }
5936
6535
  }
5937
- buildTurnStartParams(threadId, request, developerInstructions) {
5938
- const effort = request.thinkingLevel === "max" ? "xhigh" : request.thinkingLevel;
5939
- const model = request.model ?? DEFAULT_MODEL2;
5940
- return {
5941
- 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
5956
- }
5957
- }
5958
- } : {}
5959
- };
6536
+ async ensureThread(host, request, developerInstructions) {
6537
+ if (this.currentThreadId) {
6538
+ if (!this.threadAttached) {
6539
+ const response = await host.client.request(
6540
+ THREAD_RESUME_METHOD,
6541
+ await buildThreadResumeParams(this.workingDirectory, this.currentThreadId, request, developerInstructions)
6542
+ );
6543
+ this.currentThreadId = response.thread.id;
6544
+ this.threadAttached = true;
6545
+ this.seedHistoryFromThread(response.thread);
6546
+ await this.onSaveSessionId(this.currentThreadId);
6547
+ }
6548
+ return this.currentThreadId;
6549
+ }
6550
+ const threadStartResponse = await host.client.request(
6551
+ THREAD_START_METHOD,
6552
+ await buildThreadStartParams(this.workingDirectory, request, developerInstructions)
6553
+ );
6554
+ this.currentThreadId = threadStartResponse.thread.id;
6555
+ this.threadAttached = true;
6556
+ await this.onSaveSessionId(this.currentThreadId);
6557
+ return this.currentThreadId;
5960
6558
  }
5961
6559
  async runTurn(host, threadId, request, developerInstructions) {
6560
+ const { params, tempImagePaths } = await buildTurnStartParams(threadId, request, developerInstructions);
6561
+ return this.observeTurn(host, threadId, request, async () => {
6562
+ const turnStartResponse = await host.client.request(
6563
+ TURN_START_METHOD,
6564
+ params
6565
+ );
6566
+ return { turn: turnStartResponse.turn, tempImagePaths };
6567
+ });
6568
+ }
6569
+ async runGoalTurn(host, threadId, request, objective) {
6570
+ return this.observeTurn(host, threadId, request, async () => {
6571
+ const response = await host.client.request(
6572
+ THREAD_GOAL_SET_METHOD,
6573
+ { threadId, objective, status: "active" }
6574
+ );
6575
+ this.recordGoalChange(response.goal, true);
6576
+ return { turn: null, tempImagePaths: [] };
6577
+ });
6578
+ }
6579
+ async observeTurn(host, threadId, request, startTurn) {
5962
6580
  let resolveCompleted;
5963
6581
  const completed = new Promise((resolve3) => {
5964
6582
  resolveCompleted = resolve3;
@@ -5973,24 +6591,66 @@ var CodexAspManager = class extends CodingAgentManager {
5973
6591
  let observedTurnId = null;
5974
6592
  const completedItems = [];
5975
6593
  const agentMessageDeltas = /* @__PURE__ */ new Map();
5976
- const onNotification = (notification) => {
5977
- if (notification.method === ITEM_COMPLETED_METHOD) {
5978
- if (notification.params.threadId === threadId && (!observedTurnId || notification.params.turnId === observedTurnId)) {
5979
- completedItems.push(notification.params.item);
5980
- }
5981
- return;
5982
- }
5983
- if (notification.method === AGENT_MESSAGE_DELTA_METHOD) {
5984
- if (notification.params.threadId === threadId && (!observedTurnId || notification.params.turnId === observedTurnId)) {
5985
- const currentText = agentMessageDeltas.get(notification.params.itemId) ?? "";
5986
- agentMessageDeltas.set(notification.params.itemId, currentText + notification.params.delta);
6594
+ const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
6595
+ const model = request.model ?? DEFAULT_MODEL2;
6596
+ let tempImagePaths = [];
6597
+ const linearForwarder = new LinearEventForwarder(linearSessionId);
6598
+ const matchesTurn = (notificationThreadId, notificationTurnId) => notificationThreadId === threadId && (!observedTurnId || notificationTurnId === null || notificationTurnId === observedTurnId);
6599
+ const handlers = {
6600
+ [ACCOUNT_RATE_LIMITS_UPDATED_METHOD]: (notification) => {
6601
+ this.handleRateLimits(notification.params.rateLimits);
6602
+ },
6603
+ [THREAD_TOKEN_USAGE_UPDATED_METHOD]: (notification) => {
6604
+ if (notification.params.threadId !== threadId) return;
6605
+ this.emitCodexTokenUsage(notification.params.tokenUsage, model);
6606
+ },
6607
+ [THREAD_GOAL_UPDATED_METHOD]: (notification) => {
6608
+ if (notification.params.threadId !== threadId) return;
6609
+ this.recordGoalChange(notification.params.goal);
6610
+ },
6611
+ [THREAD_GOAL_CLEARED_METHOD]: (notification) => {
6612
+ if (notification.params.threadId !== threadId) return;
6613
+ this.recordGoalChange(null);
6614
+ },
6615
+ [THREAD_COMPACTED_METHOD]: (notification) => {
6616
+ if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
6617
+ this.setCompacting(false);
6618
+ },
6619
+ [TURN_STARTED_METHOD]: (notification) => {
6620
+ if (notification.params.threadId !== threadId) return;
6621
+ observedTurnId = notification.params.turn.id;
6622
+ this.activeTurnId = notification.params.turn.id;
6623
+ linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
6624
+ },
6625
+ [TURN_PLAN_UPDATED_METHOD]: (notification) => {
6626
+ if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
6627
+ this.emitPlanUpdate(notification.params.plan);
6628
+ linearForwarder.sendPlan(extractPlanFromCodexAspNotification(notification));
6629
+ },
6630
+ [ITEM_STARTED_METHOD]: (notification) => {
6631
+ if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
6632
+ if (notification.params.item.type === "contextCompaction") {
6633
+ this.setCompacting(true);
5987
6634
  }
5988
- return;
5989
- }
5990
- if (notification.method === TURN_COMPLETED_METHOD) {
5991
- if (notification.params.threadId !== threadId) {
5992
- return;
6635
+ this.emitThreadItemLifecycle(notification.params.item, "started");
6636
+ linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
6637
+ },
6638
+ [ITEM_COMPLETED_METHOD]: (notification) => {
6639
+ if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
6640
+ completedItems.push(notification.params.item);
6641
+ if (notification.params.item.type === "contextCompaction") {
6642
+ this.setCompacting(false);
5993
6643
  }
6644
+ this.emitThreadItemLifecycle(notification.params.item, "completed");
6645
+ linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
6646
+ },
6647
+ [AGENT_MESSAGE_DELTA_METHOD]: (notification) => {
6648
+ if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
6649
+ const currentText = agentMessageDeltas.get(notification.params.itemId) ?? "";
6650
+ agentMessageDeltas.set(notification.params.itemId, currentText + notification.params.delta);
6651
+ },
6652
+ [TURN_COMPLETED_METHOD]: (notification) => {
6653
+ if (notification.params.threadId !== threadId) return;
5994
6654
  observedTurnId = notification.params.turn.id;
5995
6655
  const turn = notification.params.turn;
5996
6656
  const items = turn.items.length > 0 ? [...turn.items] : [];
@@ -6015,6 +6675,9 @@ var CodexAspManager = class extends CodingAgentManager {
6015
6675
  resolveCompleted(items.length > 0 ? { ...turn, items, itemsView: "full" } : turn);
6016
6676
  }
6017
6677
  };
6678
+ const onNotification = (notification) => {
6679
+ dispatchAspNotification(notification, handlers);
6680
+ };
6018
6681
  const onServerRequest = (serverRequest) => {
6019
6682
  if (serverRequest.method !== COMMAND_APPROVAL_METHOD && serverRequest.method !== FILE_CHANGE_APPROVAL_METHOD) {
6020
6683
  return;
@@ -6023,10 +6686,8 @@ var CodexAspManager = class extends CodingAgentManager {
6023
6686
  host.client.respond(serverRequest.id, { decision: "accept" });
6024
6687
  };
6025
6688
  const onDispose = (reason) => {
6026
- this.currentThreadId = null;
6689
+ this.threadAttached = false;
6027
6690
  this.activeTurnId = null;
6028
- void this.onSaveSessionId(null).catch(() => {
6029
- });
6030
6691
  const turnLabel = observedTurnId ? ` ${observedTurnId}` : "";
6031
6692
  rejectDisposed(new Error(`Codex ASP client disposed before turn${turnLabel} completed: ${reason.message}`));
6032
6693
  };
@@ -6034,41 +6695,88 @@ var CodexAspManager = class extends CodingAgentManager {
6034
6695
  host.client.on("serverRequest", onServerRequest);
6035
6696
  host.client.on("dispose", onDispose);
6036
6697
  try {
6037
- const turnStartResponse = await host.client.request(
6038
- TURN_START_METHOD,
6039
- this.buildTurnStartParams(threadId, request, developerInstructions)
6040
- );
6041
- observedTurnId = turnStartResponse.turn.id;
6042
- this.activeTurnId = turnStartResponse.turn.id;
6043
- return await Promise.race([completed, disposed]);
6698
+ const started = await startTurn();
6699
+ tempImagePaths = started.tempImagePaths;
6700
+ if (started.turn) {
6701
+ observedTurnId = started.turn.id;
6702
+ this.activeTurnId = started.turn.id;
6703
+ }
6704
+ const turn = await Promise.race([completed, disposed]);
6705
+ linearForwarder.flushThoughtAsResponse();
6706
+ return turn;
6044
6707
  } finally {
6045
6708
  host.client.off("notification", onNotification);
6046
6709
  host.client.off("serverRequest", onServerRequest);
6047
6710
  host.client.off("dispose", onDispose);
6711
+ await removeTempImageFiles(tempImagePaths);
6712
+ if (host.client.isDisposed) {
6713
+ this.threadAttached = false;
6714
+ }
6048
6715
  }
6049
6716
  }
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;
6717
+ emitThreadItemLifecycle(item, lifecycle) {
6718
+ const stageKey = `${lifecycle}:${item.id}`;
6719
+ if (this.emittedItemStages.has(stageKey)) {
6720
+ return;
6721
+ }
6722
+ if (lifecycle === "completed" && !this.emittedItemStages.has(`started:${item.id}`)) {
6723
+ this.emittedItemStages.add(`started:${item.id}`);
6724
+ this.recordAndEmitDrafts(itemToAgentEventDrafts(item, "started"));
6725
+ }
6726
+ this.emittedItemStages.add(stageKey);
6727
+ this.recordAndEmitDrafts(itemToAgentEventDrafts(item, lifecycle));
6728
+ }
6729
+ emitTurnCompletedItems(turn) {
6730
+ for (const item of turn.items) {
6731
+ this.emitThreadItemLifecycle(item, "completed");
6732
+ }
6733
+ }
6734
+ emitPlanUpdate(plan) {
6735
+ if (plan.length === 0) return;
6736
+ this.recordAndEmitDrafts([{
6737
+ type: "response_item",
6738
+ payload: {
6739
+ type: "function_call",
6740
+ name: "update_plan",
6741
+ call_id: `plan-${Date.now()}`,
6742
+ arguments: JSON.stringify({ plan })
6057
6743
  }
6744
+ }]);
6745
+ }
6746
+ seedHistoryFromThread(thread) {
6747
+ const events = threadToHistoryEvents(thread);
6748
+ if (events.length === 0) return;
6749
+ this.historyEvents.splice(0, this.historyEvents.length, ...events);
6750
+ }
6751
+ async refreshThreadGoal(host, threadId) {
6752
+ try {
6753
+ const response = await host.client.request(
6754
+ THREAD_GOAL_GET_METHOD,
6755
+ { threadId }
6756
+ );
6757
+ this.currentGoal = response.goal ? aspGoalToChatGoal(response.goal) : null;
6758
+ } catch (error) {
6759
+ console.warn("[CodexAspManager] Failed to read ASP thread goal:", error);
6058
6760
  }
6059
- if (!text) {
6761
+ }
6762
+ recordGoalChange(goal, force = false) {
6763
+ const nextGoal = goal ? aspGoalToChatGoal(goal) : null;
6764
+ if (!force && JSON.stringify(this.currentGoal) === JSON.stringify(nextGoal)) {
6060
6765
  return;
6061
6766
  }
6062
- const event = this.recordHistoryEvent("response_item", {
6063
- type: "message",
6064
- role: "assistant",
6065
- content: [{
6066
- type: "output_text",
6067
- text
6068
- }]
6069
- });
6767
+ this.currentGoal = nextGoal;
6768
+ const event = this.recordHistoryEvent(
6769
+ CHAT_GOAL_EVENT_TYPE,
6770
+ { goal: nextGoal }
6771
+ );
6070
6772
  this.onEvent(event);
6071
6773
  }
6774
+ recordAndEmitDrafts(drafts) {
6775
+ for (const draft of drafts) {
6776
+ const event = this.recordHistoryEvent(draft.type, draft.payload);
6777
+ this.onEvent(event);
6778
+ }
6779
+ }
6072
6780
  recordHistoryEvent(type, payload) {
6073
6781
  const event = {
6074
6782
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6078,6 +6786,47 @@ var CodexAspManager = class extends CodingAgentManager {
6078
6786
  this.historyEvents.push(event);
6079
6787
  return event;
6080
6788
  }
6789
+ handleRateLimits(rateLimits) {
6790
+ const snapshot = extractRateLimitsSnapshot(rateLimits);
6791
+ if (snapshot) {
6792
+ this.emitQuotaStatus(snapshot);
6793
+ }
6794
+ }
6795
+ async refreshQuotaSnapshot(host) {
6796
+ try {
6797
+ const response = await host.client.request(
6798
+ ACCOUNT_RATE_LIMITS_READ_METHOD,
6799
+ void 0
6800
+ );
6801
+ this.handleRateLimits(response.rateLimits);
6802
+ } catch {
6803
+ }
6804
+ }
6805
+ emitQuotaStatus(snapshot, force = false) {
6806
+ const event = this.quotaStatus.apply(snapshot, force);
6807
+ if (!event) return;
6808
+ this.historyEvents.push(event);
6809
+ this.onEvent(event);
6810
+ }
6811
+ emitCodexTokenUsage(tokenUsage, model) {
6812
+ const payload = buildCodexTokenUsageContextUsagePayload({
6813
+ model,
6814
+ modelContextWindow: tokenUsage.modelContextWindow,
6815
+ last: {
6816
+ inputTokens: tokenUsage.last.inputTokens,
6817
+ outputTokens: tokenUsage.last.outputTokens,
6818
+ totalTokens: tokenUsage.last.totalTokens,
6819
+ cachedInputTokens: tokenUsage.last.cachedInputTokens,
6820
+ reasoningOutputTokens: tokenUsage.last.reasoningOutputTokens
6821
+ },
6822
+ total: {
6823
+ totalTokens: tokenUsage.total.totalTokens
6824
+ },
6825
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6826
+ });
6827
+ const event = this.emitContextUsage(payload);
6828
+ this.historyEvents.push(event);
6829
+ }
6081
6830
  };
6082
6831
 
6083
6832
  // src/managers/relay-tools.ts
@@ -6686,12 +7435,12 @@ var DuplicateDefaultChatError = class extends Error {
6686
7435
  };
6687
7436
 
6688
7437
  // 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");
7438
+ var ENGINE_DIR2 = join15(homedir13(), ".replicas", "engine");
7439
+ var CHATS_FILE = join15(ENGINE_DIR2, "chats.json");
7440
+ var CLAUDE_HISTORY_DIR = join15(ENGINE_DIR2, "claude-histories");
7441
+ var RELAY_HISTORY_DIR = join15(ENGINE_DIR2, "relay-histories");
7442
+ var CHAT_SENDERS_DIR = join15(ENGINE_DIR2, "chat-senders");
7443
+ var CODEX_AUTH_PATH2 = join15(homedir13(), ".codex", "auth.json");
6695
7444
  function isChatMessageSender(value) {
6696
7445
  if (!isRecord3(value)) return false;
6697
7446
  return typeof value.senderUserId === "string" && typeof value.senderEmail === "string" && typeof value.recordedAt === "string";
@@ -6714,10 +7463,10 @@ var ChatService = class {
6714
7463
  chats = /* @__PURE__ */ new Map();
6715
7464
  writeChain = Promise.resolve();
6716
7465
  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 });
7466
+ await mkdir11(ENGINE_DIR2, { recursive: true });
7467
+ await mkdir11(CLAUDE_HISTORY_DIR, { recursive: true });
7468
+ await mkdir11(RELAY_HISTORY_DIR, { recursive: true });
7469
+ await mkdir11(CHAT_SENDERS_DIR, { recursive: true });
6721
7470
  const persisted = await this.loadChats();
6722
7471
  for (const chat of persisted) {
6723
7472
  const runtime = this.createRuntimeChat(chat);
@@ -6812,7 +7561,7 @@ var ChatService = class {
6812
7561
  };
6813
7562
  }
6814
7563
  senderFilePath(chatId) {
6815
- return join14(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
7564
+ return join15(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
6816
7565
  }
6817
7566
  async appendSender(chatId, sender) {
6818
7567
  try {
@@ -6929,7 +7678,7 @@ var ChatService = class {
6929
7678
  async deleteHistoryFile(persisted) {
6930
7679
  if (persisted.provider === "claude" || persisted.provider === "relay") {
6931
7680
  const dir = persisted.provider === "claude" ? CLAUDE_HISTORY_DIR : RELAY_HISTORY_DIR;
6932
- await rm(join14(dir, `${persisted.id}.jsonl`), { force: true });
7681
+ await rm(join15(dir, `${persisted.id}.jsonl`), { force: true });
6933
7682
  }
6934
7683
  await rm(this.senderFilePath(persisted.id), { force: true });
6935
7684
  }
@@ -6942,6 +7691,7 @@ var ChatService = class {
6942
7691
  return {
6943
7692
  thread_id: history.thread_id,
6944
7693
  events: history.events,
7694
+ goal: history.goal ?? chat.provider.getGoal?.() ?? null,
6945
7695
  senders
6946
7696
  };
6947
7697
  }
@@ -6977,7 +7727,7 @@ var ChatService = class {
6977
7727
  if (persisted.provider === "claude") {
6978
7728
  provider = new ClaudeManager({
6979
7729
  workingDirectory: this.workingDirectory,
6980
- historyFilePath: join14(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
7730
+ historyFilePath: join15(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
6981
7731
  initialSessionId: persisted.providerSessionId,
6982
7732
  onSaveSessionId: saveSession,
6983
7733
  onTurnComplete: onProviderTurnComplete,
@@ -6986,7 +7736,7 @@ var ChatService = class {
6986
7736
  } else if (persisted.provider === "relay") {
6987
7737
  provider = new RelayManager({
6988
7738
  workingDirectory: this.workingDirectory,
6989
- historyFilePath: join14(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
7739
+ historyFilePath: join15(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
6990
7740
  initialSessionId: persisted.providerSessionId,
6991
7741
  onSaveSessionId: saveSession,
6992
7742
  onTurnComplete: onProviderTurnComplete,
@@ -7027,6 +7777,7 @@ var ChatService = class {
7027
7777
  processing: chat.provider.isProcessing(),
7028
7778
  awaitingInput: chat.provider.isAwaitingInput?.() ?? false,
7029
7779
  isCompacting: chat.provider.isCompacting?.() ?? false,
7780
+ goal: chat.provider.getGoal?.() ?? null,
7030
7781
  parentChatId: chat.persisted.parentChatId
7031
7782
  };
7032
7783
  }
@@ -7069,7 +7820,7 @@ var ChatService = class {
7069
7820
  }
7070
7821
  }).catch(() => {
7071
7822
  });
7072
- if (event.type === "replicas-tool-input-request" || event.type === "replicas-tool-input-resolved" || event.type === "compaction-status") {
7823
+ if (event.type === "replicas-tool-input-request" || event.type === "replicas-tool-input-resolved" || event.type === "compaction-status" || event.type === CHAT_GOAL_EVENT_TYPE) {
7073
7824
  this.publish({
7074
7825
  type: "chat.updated",
7075
7826
  payload: { chat: this.toSummary(chat) }
@@ -7122,7 +7873,7 @@ var ChatService = class {
7122
7873
  this.writeChain = this.writeChain.catch(() => {
7123
7874
  }).then(async () => {
7124
7875
  const payload = Array.from(this.chats.values()).map((chat) => chat.persisted);
7125
- await writeFile8(CHATS_FILE, JSON.stringify(payload, null, 2), "utf-8");
7876
+ await writeFile9(CHATS_FILE, JSON.stringify(payload, null, 2), "utf-8");
7126
7877
  });
7127
7878
  await this.writeChain;
7128
7879
  }
@@ -7175,7 +7926,7 @@ var ChatService = class {
7175
7926
  // src/services/repo-file-service.ts
7176
7927
  import { execFile as execFile2 } from "child_process";
7177
7928
  import { readFile as readFile9, realpath, stat as stat3 } from "fs/promises";
7178
- import { join as join15, resolve, extname } from "path";
7929
+ import { join as join16, resolve, extname } from "path";
7179
7930
  var CACHE_TTL_MS = 3e4;
7180
7931
  var SEARCH_TIMEOUT_MS = 15e3;
7181
7932
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -7335,7 +8086,7 @@ var RepoFileService = class {
7335
8086
  const repo = repos.find((r) => r.name === repoName);
7336
8087
  if (!repo) return null;
7337
8088
  try {
7338
- const fullPath = await realpath(resolve(join15(repo.path, filePath)));
8089
+ const fullPath = await realpath(resolve(join16(repo.path, filePath)));
7339
8090
  const repoRoot = await realpath(repo.path);
7340
8091
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
7341
8092
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
@@ -7447,15 +8198,15 @@ var RepoFileService = class {
7447
8198
  import { Hono } from "hono";
7448
8199
  import { z as z2 } from "zod";
7449
8200
  import { readdir as readdir6, stat as stat4, readFile as readFile13 } from "fs/promises";
7450
- import { join as join19, resolve as resolve2 } from "path";
8201
+ import { join as join20, resolve as resolve2 } from "path";
7451
8202
 
7452
8203
  // src/services/plan-service.ts
7453
8204
  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";
8205
+ import { homedir as homedir14 } from "os";
8206
+ import { basename, join as join17 } from "path";
7456
8207
  var PLAN_DIRECTORIES = [
7457
- join16(homedir13(), ".claude", "plans"),
7458
- join16(homedir13(), ".replicas", "plans")
8208
+ join17(homedir14(), ".claude", "plans"),
8209
+ join17(homedir14(), ".replicas", "plans")
7459
8210
  ];
7460
8211
  function isMarkdownFile(filename) {
7461
8212
  return filename.toLowerCase().endsWith(".md");
@@ -7489,7 +8240,7 @@ var PlanService = class {
7489
8240
  return null;
7490
8241
  }
7491
8242
  for (const directory of PLAN_DIRECTORIES) {
7492
- const filePath = join16(directory, safeFilename);
8243
+ const filePath = join17(directory, safeFilename);
7493
8244
  try {
7494
8245
  const content = await readFile10(filePath, "utf-8");
7495
8246
  return { filename: safeFilename, content };
@@ -7505,14 +8256,14 @@ var planService = new PlanService();
7505
8256
  import { spawn as spawn2 } from "child_process";
7506
8257
  import { readFile as readFile12 } from "fs/promises";
7507
8258
  import { existsSync as existsSync8 } from "fs";
7508
- import { join as join18 } from "path";
8259
+ import { join as join19 } from "path";
7509
8260
 
7510
8261
  // src/services/warm-hook-logs-service.ts
7511
8262
  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");
8263
+ import { mkdir as mkdir12, readFile as readFile11, writeFile as writeFile10, readdir as readdir5 } from "fs/promises";
8264
+ import { homedir as homedir15 } from "os";
8265
+ import { join as join18 } from "path";
8266
+ var LOGS_DIR2 = join18(homedir15(), ".replicas", "warm-hook-logs");
7516
8267
  function sanitizeFilename2(name) {
7517
8268
  const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
7518
8269
  const hash = createHash2("sha256").update(name).digest("hex").slice(0, 8);
@@ -7533,7 +8284,7 @@ function withPreview2(stored) {
7533
8284
  }
7534
8285
  var WarmHookLogsService = class {
7535
8286
  async ensureDir() {
7536
- await mkdir11(LOGS_DIR2, { recursive: true });
8287
+ await mkdir12(LOGS_DIR2, { recursive: true });
7537
8288
  }
7538
8289
  async saveGlobalHookLog(entry) {
7539
8290
  await this.ensureDir();
@@ -7542,7 +8293,7 @@ var WarmHookLogsService = class {
7542
8293
  hookName: "organization",
7543
8294
  ...entry
7544
8295
  };
7545
- await writeFile9(join17(LOGS_DIR2, globalFilename()), `${JSON.stringify(log, null, 2)}
8296
+ await writeFile10(join18(LOGS_DIR2, globalFilename()), `${JSON.stringify(log, null, 2)}
7546
8297
  `, "utf-8");
7547
8298
  }
7548
8299
  async saveEnvironmentHookLog(entry) {
@@ -7552,7 +8303,7 @@ var WarmHookLogsService = class {
7552
8303
  hookName: "environment",
7553
8304
  ...entry
7554
8305
  };
7555
- await writeFile9(join17(LOGS_DIR2, environmentFilename()), `${JSON.stringify(log, null, 2)}
8306
+ await writeFile10(join18(LOGS_DIR2, environmentFilename()), `${JSON.stringify(log, null, 2)}
7556
8307
  `, "utf-8");
7557
8308
  }
7558
8309
  async saveRepoHookLog(repoName, entry) {
@@ -7562,7 +8313,7 @@ var WarmHookLogsService = class {
7562
8313
  hookName: repoName,
7563
8314
  ...entry
7564
8315
  };
7565
- await writeFile9(join17(LOGS_DIR2, repoFilename2(repoName)), `${JSON.stringify(log, null, 2)}
8316
+ await writeFile10(join18(LOGS_DIR2, repoFilename2(repoName)), `${JSON.stringify(log, null, 2)}
7566
8317
  `, "utf-8");
7567
8318
  }
7568
8319
  async getAllLogs() {
@@ -7581,7 +8332,7 @@ var WarmHookLogsService = class {
7581
8332
  continue;
7582
8333
  }
7583
8334
  try {
7584
- const raw = await readFile11(join17(LOGS_DIR2, file), "utf-8");
8335
+ const raw = await readFile11(join18(LOGS_DIR2, file), "utf-8");
7585
8336
  const stored = JSON.parse(raw);
7586
8337
  logs.push(withPreview2(stored));
7587
8338
  } catch {
@@ -7599,7 +8350,7 @@ var WarmHookLogsService = class {
7599
8350
  async getFullOutput(hookType, hookName) {
7600
8351
  const filename = hookType === "global" ? globalFilename() : hookType === "environment" ? environmentFilename() : repoFilename2(hookName);
7601
8352
  try {
7602
- const raw = await readFile11(join17(LOGS_DIR2, filename), "utf-8");
8353
+ const raw = await readFile11(join18(LOGS_DIR2, filename), "utf-8");
7603
8354
  const stored = JSON.parse(raw);
7604
8355
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
7605
8356
  return null;
@@ -7618,7 +8369,7 @@ var warmHookLogsService = new WarmHookLogsService();
7618
8369
  // src/services/warm-hooks-service.ts
7619
8370
  async function readRepoWarmHook(repoPath) {
7620
8371
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
7621
- const configPath = join18(repoPath, filename);
8372
+ const configPath = join19(repoPath, filename);
7622
8373
  if (!existsSync8(configPath)) {
7623
8374
  continue;
7624
8375
  }
@@ -8422,7 +9173,7 @@ function createV1Routes(deps) {
8422
9173
  const logFiles = files.filter((f) => f.endsWith(".log"));
8423
9174
  const sessions = await Promise.all(
8424
9175
  logFiles.map(async (filename) => {
8425
- const filePath = join19(LOG_DIR, filename);
9176
+ const filePath = join20(LOG_DIR, filename);
8426
9177
  const fileStat = await stat4(filePath);
8427
9178
  const sessionId = filename.replace(/\.log$/, "");
8428
9179
  return {