replicas-engine 0.1.699 → 0.1.700

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -68,6 +68,7 @@ import {
68
68
  MEMORY_ROOT,
69
69
  MEMORY_SUMMARY_FILENAME,
70
70
  MERGED_MESSAGE_SEPARATOR,
71
+ MODEL_REROUTED_METHOD,
71
72
  REASONING_SUMMARY_PART_ADDED_METHOD,
72
73
  REASONING_SUMMARY_TEXT_DELTA_METHOD,
73
74
  REASONING_TEXT_DELTA_METHOD,
@@ -80,6 +81,7 @@ import {
80
81
  THREAD_GOAL_UPDATED_METHOD,
81
82
  THREAD_TOKEN_USAGE_UPDATED_METHOD,
82
83
  TIMEOUT,
84
+ TOKEN_USAGE_PATH,
83
85
  TURN_COMPLETED_METHOD,
84
86
  TURN_PLAN_UPDATED_METHOD,
85
87
  TURN_STARTED_METHOD,
@@ -116,6 +118,7 @@ import {
116
118
  createErrorResult,
117
119
  createProviderSlashCommand,
118
120
  createSuccessResult,
121
+ createUuidFromHex,
119
122
  decodePathSegments,
120
123
  detectAgentQuotaLimit,
121
124
  detectLanguageByPath,
@@ -126,6 +129,7 @@ import {
126
129
  extractErrorText,
127
130
  extractToolResultText,
128
131
  fetchAiGatewayModels,
132
+ fetchModelsDevCatalog,
129
133
  findGitCommitSignals,
130
134
  findPrMergeSignals,
131
135
  getChatHistoryPageSenders,
@@ -159,9 +163,12 @@ import {
159
163
  isDefaultChat,
160
164
  isGitHubUrl,
161
165
  isLatestChatHistoryPage,
166
+ isNonEmptyString,
162
167
  isRecord,
163
168
  isSkillRegistryManifest,
164
169
  isTerminalBackgroundTaskStatus,
170
+ isTokenUsageCounts,
171
+ isTokenUsageUploadRecord,
165
172
  isTransientErrorText,
166
173
  isValidAgentProvider,
167
174
  isValidEngineLogSessionId,
@@ -191,16 +198,17 @@ import {
191
198
  renumberCodexAspTranscriptItems,
192
199
  resolveWarmHookConfig,
193
200
  sanitizeCanvasFilename,
201
+ sendChatMessageRequestSchema,
194
202
  serializeCanvasContentResponse,
195
203
  shellQuotePosix,
196
204
  stripAgentDiagnosticErrors
197
- } from "./chunk-UTGTXG54.js";
205
+ } from "./chunk-HMWJSWP2.js";
198
206
 
199
207
  // src/index.ts
200
208
  import { serve } from "@hono/node-server";
201
209
  import { Hono as Hono2 } from "hono";
202
- import { existsSync as existsSync11 } from "fs";
203
- import { randomUUID as randomUUID11 } from "crypto";
210
+ import { existsSync as existsSync12 } from "fs";
211
+ import { randomUUID as randomUUID12 } from "crypto";
204
212
  import { connect } from "net";
205
213
 
206
214
  // src/managers/github-token-manager.ts
@@ -2814,10 +2822,10 @@ async function registerDesktopPreview() {
2814
2822
  }
2815
2823
 
2816
2824
  // src/services/chat/chat-service.ts
2817
- import { existsSync as existsSync8 } from "fs";
2825
+ import { existsSync as existsSync9 } from "fs";
2818
2826
  import { appendFile as appendFile4, copyFile, mkdir as mkdir17, readFile as readFile17, rename as rename3, rm as rm2 } from "fs/promises";
2819
2827
  import { join as join29 } from "path";
2820
- import { randomUUID as randomUUID9 } from "crypto";
2828
+ import { randomUUID as randomUUID10 } from "crypto";
2821
2829
 
2822
2830
  // ../shared/src/workspace-chat.ts
2823
2831
  var WORKSPACE_CHATS_QUERY_SCHEMA_VERSION = 1;
@@ -6738,9 +6746,17 @@ var CodexAspManager = class extends CodingAgentManager {
6738
6746
  slashCommandsRequest = null;
6739
6747
  steeredTempImagePaths = [];
6740
6748
  steerLock = new AsyncLock();
6749
+ onCodexTurnStarted;
6750
+ onCodexTokenUsage;
6751
+ onCodexModelRerouted;
6752
+ onCodexTurnCompleted;
6741
6753
  constructor(options) {
6742
6754
  super({ ...options, provider: AGENT.CODEX });
6743
6755
  this.historyFile = options.historyFilePath ? new CodexHistoryFile(options.historyFilePath) : null;
6756
+ this.onCodexTurnStarted = options.onCodexTurnStarted;
6757
+ this.onCodexTokenUsage = options.onCodexTokenUsage;
6758
+ this.onCodexModelRerouted = options.onCodexModelRerouted;
6759
+ this.onCodexTurnCompleted = options.onCodexTurnCompleted;
6744
6760
  this.initializeManager(this.processMessageInternal.bind(this));
6745
6761
  }
6746
6762
  async initialize() {
@@ -7261,7 +7277,13 @@ var CodexAspManager = class extends CodingAgentManager {
7261
7277
  },
7262
7278
  [THREAD_TOKEN_USAGE_UPDATED_METHOD]: (notification) => {
7263
7279
  if (notification.params.threadId !== threadId) return;
7264
- this.emitCodexTokenUsage(notification.params.tokenUsage, model);
7280
+ const occurredAt = (/* @__PURE__ */ new Date()).toISOString();
7281
+ this.onCodexTokenUsage?.(notification.params, occurredAt);
7282
+ this.emitCodexTokenUsage(notification.params.tokenUsage, model, occurredAt);
7283
+ },
7284
+ [MODEL_REROUTED_METHOD]: (notification) => {
7285
+ if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
7286
+ this.onCodexModelRerouted?.(notification.params);
7265
7287
  },
7266
7288
  [THREAD_GOAL_UPDATED_METHOD]: (notification) => {
7267
7289
  if (notification.params.threadId !== threadId) return;
@@ -7282,6 +7304,7 @@ var CodexAspManager = class extends CodingAgentManager {
7282
7304
  observedTurnId = notification.params.turn.id;
7283
7305
  observedTurnIds.add(notification.params.turn.id);
7284
7306
  this.activeTurnId = notification.params.turn.id;
7307
+ this.onCodexTurnStarted?.(threadId, notification.params.turn.id);
7285
7308
  if (goalContinuationTimer) {
7286
7309
  clearTimeout(goalContinuationTimer);
7287
7310
  goalContinuationTimer = null;
@@ -7390,6 +7413,7 @@ var CodexAspManager = class extends CodingAgentManager {
7390
7413
  if (!observedTurnIds.has(notification.params.turn.id) && notification.params.turn.id !== this.activeTurnId) return;
7391
7414
  observedTurnIds.add(notification.params.turn.id);
7392
7415
  observedTurnId = notification.params.turn.id;
7416
+ this.onCodexTurnCompleted?.(threadId, notification.params.turn.id);
7393
7417
  const completedTurn = recoverCompletedTurn(notification.params.turn, completedItems, agentMessageDeltas);
7394
7418
  this.mergeTranscriptTurn(notification.params.threadId, completedTurn);
7395
7419
  this.emitTranscriptUpdated(notification.params.threadId, { immediate: true });
@@ -7432,6 +7456,7 @@ var CodexAspManager = class extends CodingAgentManager {
7432
7456
  observedTurnId = started.turn.id;
7433
7457
  observedTurnIds.add(started.turn.id);
7434
7458
  this.activeTurnId = started.turn.id;
7459
+ if (!alreadyObserved) this.onCodexTurnStarted?.(threadId, started.turn.id);
7435
7460
  if (!alreadyObserved) {
7436
7461
  attachUserMessage(started.turn);
7437
7462
  this.mergeTranscriptTurn(threadId, started.turn);
@@ -8015,7 +8040,7 @@ var CodexAspManager = class extends CodingAgentManager {
8015
8040
  this.trackHistoryEvent(event);
8016
8041
  this.onEvent(event);
8017
8042
  }
8018
- emitCodexTokenUsage(tokenUsage, model) {
8043
+ emitCodexTokenUsage(tokenUsage, model, updatedAt) {
8019
8044
  const payload = buildCodexTokenUsageContextUsagePayload({
8020
8045
  model,
8021
8046
  modelContextWindow: tokenUsage.modelContextWindow,
@@ -8029,7 +8054,7 @@ var CodexAspManager = class extends CodingAgentManager {
8029
8054
  total: {
8030
8055
  totalTokens: tokenUsage.total.totalTokens
8031
8056
  },
8032
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
8057
+ updatedAt
8033
8058
  });
8034
8059
  const event = this.emitContextUsage(payload);
8035
8060
  this.trackHistoryEvent(event);
@@ -9458,59 +9483,27 @@ import { delimiter, dirname as dirname8, join as join22 } from "path";
9458
9483
  import { randomBytes as randomBytes2 } from "crypto";
9459
9484
  import { fileURLToPath as fileURLToPath2 } from "url";
9460
9485
  import { Agent } from "undici";
9461
- import { z as z4 } from "zod";
9486
+ import { z as z3 } from "zod";
9462
9487
 
9463
9488
  // ../shared/src/credentials/opencode-go.ts
9464
- import { z as z3 } from "zod";
9465
9489
  var OPENCODE_GO_PROVIDER = "opencode-go";
9466
9490
  var OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1";
9467
- var OPENCODE_MODELS_DEV_URL = "https://models.dev/api.json";
9468
9491
  var DEFAULT_OPENCODE_GO_MODEL = "glm-5.2";
9469
- var opencodeGoModelsDevSchema = z3.object({
9470
- "opencode-go": z3.object({
9471
- models: z3.record(z3.string(), z3.object({
9472
- id: z3.string().optional(),
9473
- name: z3.string(),
9474
- description: z3.string().optional(),
9475
- status: z3.enum(["alpha", "beta", "deprecated"]).optional()
9476
- }))
9477
- }).optional()
9478
- });
9479
- function parseOpenCodeGoCatalog(value) {
9480
- const parsed = opencodeGoModelsDevSchema.safeParse(value);
9481
- const provider = parsed.success ? parsed.data["opencode-go"] : void 0;
9482
- if (!provider) return null;
9483
- const models = Object.fromEntries(
9484
- Object.entries(provider.models).filter(([, definition]) => definition.status !== "deprecated" && definition.status !== "alpha").map(([id, definition]) => [id, {
9485
- id: definition.id ?? id,
9486
- name: definition.name,
9487
- ...definition.description ? { description: definition.description } : {},
9488
- ...definition.status ? { status: definition.status } : {}
9489
- }])
9490
- );
9491
- if (Object.keys(models).length === 0) return null;
9492
- return { models, authoritative: true };
9493
- }
9494
- var OPENCODE_GO_CATALOG_CACHE_MS = 5 * 6e4;
9495
- var openCodeGoCatalogCache;
9496
- var openCodeGoCatalogRequest;
9497
9492
  async function fetchOpenCodeGoCatalog(fallbackModelIds = [DEFAULT_OPENCODE_GO_MODEL]) {
9498
- if (openCodeGoCatalogCache && openCodeGoCatalogCache.expiresAt > Date.now()) {
9499
- return openCodeGoCatalogCache.catalog;
9500
- }
9501
- openCodeGoCatalogRequest ??= (async () => {
9502
- const response = await fetch(OPENCODE_MODELS_DEV_URL, { signal: AbortSignal.timeout(5e3) });
9503
- if (!response.ok) throw new Error(`Failed to load OpenCode Go models (${response.status})`);
9504
- const catalog = parseOpenCodeGoCatalog(await response.json());
9505
- if (!catalog) throw new Error("OpenCode Go model catalog is unavailable");
9506
- return catalog;
9507
- })();
9508
9493
  try {
9509
- const catalog = await openCodeGoCatalogRequest;
9510
- openCodeGoCatalogCache = { catalog, expiresAt: Date.now() + OPENCODE_GO_CATALOG_CACHE_MS };
9511
- return catalog;
9494
+ const provider = (await fetchModelsDevCatalog())[OPENCODE_GO_PROVIDER];
9495
+ if (!provider) throw new Error("OpenCode Go model catalog is unavailable");
9496
+ const models = Object.fromEntries(
9497
+ Object.entries(provider.models).filter(([, definition]) => definition.status !== "deprecated" && definition.status !== "alpha").map(([id, definition]) => [id, {
9498
+ id: definition.id ?? id,
9499
+ name: definition.name,
9500
+ ...definition.description ? { description: definition.description } : {},
9501
+ ...definition.status ? { status: definition.status } : {}
9502
+ }])
9503
+ );
9504
+ if (Object.keys(models).length === 0) throw new Error("OpenCode Go model catalog is unavailable");
9505
+ return { models, authoritative: true };
9512
9506
  } catch {
9513
- if (openCodeGoCatalogCache) return openCodeGoCatalogCache.catalog;
9514
9507
  return {
9515
9508
  authoritative: false,
9516
9509
  models: Object.fromEntries(fallbackModelIds.map((id) => [id, {
@@ -9518,8 +9511,6 @@ async function fetchOpenCodeGoCatalog(fallbackModelIds = [DEFAULT_OPENCODE_GO_MO
9518
9511
  name: id === DEFAULT_OPENCODE_GO_MODEL ? "GLM 5.2" : id
9519
9512
  }]))
9520
9513
  };
9521
- } finally {
9522
- openCodeGoCatalogRequest = void 0;
9523
9514
  }
9524
9515
  }
9525
9516
 
@@ -9578,9 +9569,9 @@ var OPENCODE_VARIANT_CANDIDATES_BY_THINKING_LEVEL = {
9578
9569
  ultra: ["max", "xhigh", "high"],
9579
9570
  ultracode: ["max", "xhigh", "high"]
9580
9571
  };
9581
- var opencodeAuthSchema = z4.record(z4.string(), z4.object({
9582
- type: z4.string().optional(),
9583
- key: z4.string().optional()
9572
+ var opencodeAuthSchema = z3.record(z3.string(), z3.object({
9573
+ type: z3.string().optional(),
9574
+ key: z3.string().optional()
9584
9575
  }));
9585
9576
  async function hasOpencodeCredentials(provider) {
9586
9577
  if (!existsSync7(OPENCODE_AUTH_PATH)) return false;
@@ -10597,7 +10588,7 @@ var PiManager = class extends CodingAgentManager {
10597
10588
 
10598
10589
  // src/managers/relay-tools.ts
10599
10590
  import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
10600
- import { z as z5 } from "zod";
10591
+ import { z as z4 } from "zod";
10601
10592
 
10602
10593
  // src/managers/relay-providers.ts
10603
10594
  function isRelaySubagentProviderAllowed(provider, allowedProviders) {
@@ -10727,7 +10718,7 @@ async function getChatFinalResponse(chatId) {
10727
10718
  return extractFinalResponse(history) ?? "[No response from subagent]";
10728
10719
  }
10729
10720
  function buildSpawnAgentTool(parentChatId, availability = {}, getAllowedProviders = () => monolithService.getRelaySubagentProviders(), getProviderAvailability = () => availability) {
10730
- const providerEnum = z5.enum(VALID_RELAY_SUBAGENT_PROVIDERS);
10721
+ const providerEnum = z4.enum(VALID_RELAY_SUBAGENT_PROVIDERS);
10731
10722
  return tool(
10732
10723
  "spawn_agent",
10733
10724
  `Spawn a new subagent to perform a task. The subagent runs in its own chat with a fresh context window.
@@ -10742,8 +10733,8 @@ The tool blocks until the subagent completes and returns its final response.
10742
10733
  You will also receive the chatId so you can send follow-up messages or clean up the chat.`,
10743
10734
  {
10744
10735
  provider: providerEnum.describe("Which agent to use. Choose from the providers listed as currently available in Relay guidance. Prefer an available code provider for code writing, claude for exploration/analysis, and relay for complex multi-step orchestration."),
10745
- prompt: z5.string().describe("The full prompt/instructions for the subagent. Be detailed - it has no context from your conversation."),
10746
- model: z5.string().optional().describe([
10736
+ prompt: z4.string().describe("The full prompt/instructions for the subagent. Be detailed - it has no context from your conversation."),
10737
+ model: z4.string().optional().describe([
10747
10738
  `Model override. Claude: ${AGENT_MODELS.claude.join(", ")} (opus is the default; sonnet is faster).`,
10748
10739
  `Codex: ${AGENT_MODELS.codex.join(", ")}.`,
10749
10740
  `Cursor: ${AGENT_MODELS.cursor.join(", ")}.`,
@@ -10753,11 +10744,11 @@ You will also receive the chatId so you can send follow-up messages or clean up
10753
10744
  `Opencode: ${AGENT_MODELS.opencode.join(", ")}.`,
10754
10745
  `Pi: ${AGENT_MODELS.pi.join(", ")}.`
10755
10746
  ].join(" ")),
10756
- thinking_level: z5.enum(VALID_THINKING_LEVELS).optional().describe(
10747
+ thinking_level: z4.enum(VALID_THINKING_LEVELS).optional().describe(
10757
10748
  "Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, xhigh = extended effort, max = maximum effort, ultra = Codex ultra, ultracode = Claude Code dynamic workflows. Defaults: Claude = high; Codex, Cursor, DeepSeek Harness, fx, Kimi Code, Opencode, and Pi = medium."
10758
10749
  ),
10759
- title: z5.string().optional().describe("Optional title for the subagent chat (for identification)."),
10760
- timeout_minutes: z5.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
10750
+ title: z4.string().optional().describe("Optional title for the subagent chat (for identification)."),
10751
+ timeout_minutes: z4.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
10761
10752
  },
10762
10753
  async (args) => {
10763
10754
  try {
@@ -10838,13 +10829,13 @@ var messageAgentTool = tool(
10838
10829
 
10839
10830
  The tool blocks until the subagent completes and returns its response.`,
10840
10831
  {
10841
- chatId: z5.string().describe("The chat ID of the subagent (returned by spawn_agent)."),
10842
- message: z5.string().describe("The follow-up message to send."),
10843
- model: z5.string().optional().describe("Optional model override for this message."),
10844
- thinking_level: z5.enum(VALID_THINKING_LEVELS).optional().describe(
10832
+ chatId: z4.string().describe("The chat ID of the subagent (returned by spawn_agent)."),
10833
+ message: z4.string().describe("The follow-up message to send."),
10834
+ model: z4.string().optional().describe("Optional model override for this message."),
10835
+ thinking_level: z4.enum(VALID_THINKING_LEVELS).optional().describe(
10845
10836
  "Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, xhigh = extended effort, max = maximum effort, ultra = Codex ultra, ultracode = Claude Code dynamic workflows. Defaults: Claude = high, Codex = medium, Cursor = medium, Opencode = medium."
10846
10837
  ),
10847
- timeout_minutes: z5.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
10838
+ timeout_minutes: z4.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
10848
10839
  },
10849
10840
  async (args) => {
10850
10841
  try {
@@ -10882,7 +10873,7 @@ var deleteAgentTool = tool(
10882
10873
  "delete_agent",
10883
10874
  `Delete a subagent chat to free resources. Use this after a subagent has completed its work and you no longer need to send it follow-up messages.`,
10884
10875
  {
10885
- chatId: z5.string().describe("The chat ID of the subagent to delete.")
10876
+ chatId: z4.string().describe("The chat ID of the subagent to delete.")
10886
10877
  },
10887
10878
  async (args) => {
10888
10879
  try {
@@ -11248,6 +11239,17 @@ var RelayManager = class {
11248
11239
  };
11249
11240
 
11250
11241
  // src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
11242
+ import { randomUUID as randomUUID8 } from "crypto";
11243
+
11244
+ // src/analytics/analytics-upload.ts
11245
+ async function uploadAnalytics(endpoint, body) {
11246
+ const response = await monolithRequest(endpoint, { body });
11247
+ if (!response.ok) {
11248
+ throw new Error(`Analytics upload failed: ${response.status} ${await response.text()}`);
11249
+ }
11250
+ }
11251
+
11252
+ // src/analytics/analytics-record-buffer.ts
11251
11253
  import {
11252
11254
  appendFile as appendFile3,
11253
11255
  mkdir as mkdir16,
@@ -11256,51 +11258,13 @@ import {
11256
11258
  rename as rename2,
11257
11259
  unlink as unlink3
11258
11260
  } from "fs/promises";
11261
+ import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync } from "fs";
11259
11262
  import { join as join24 } from "path";
11260
- import { randomUUID as randomUUID8 } from "crypto";
11261
-
11262
- // src/analytics/agent/activity/skill-mcp-call-extractor.ts
11263
- var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "deepseek", "fx", "kimi", "opencode", "pi", "custom", "dynamic"]);
11264
- function mcpNameFromToolCall(message) {
11265
- const parsedName = parseMcpToolName(message.tool);
11266
- if (parsedName) return parsedName.server;
11267
- if (message.tool.startsWith("mcp.")) {
11268
- const [server, ...toolParts] = message.tool.slice("mcp.".length).split(".");
11269
- if (server && toolParts.length > 0) return server;
11270
- }
11271
- return NON_MCP_SERVERS.has(message.server) ? null : message.server;
11272
- }
11273
- function skillCallsFromCommand(message) {
11274
- const skillNames = message.skillNames ?? [];
11275
- return skillNames.map((skillName, index) => ({
11276
- kind: "skill",
11277
- id: skillNames.length === 1 ? message.id : `${message.id}-${index}`,
11278
- skillName,
11279
- occurredAt: message.timestamp
11280
- }));
11281
- }
11282
- function extractSkillMcpCalls(messages, provider) {
11283
- return messages.flatMap((message) => {
11284
- if (message.type === "skill") {
11285
- if (provider === "relay") return [];
11286
- return [{ kind: "skill", id: message.id, skillName: message.skillName, occurredAt: message.timestamp }];
11287
- }
11288
- if (message.type === "command") {
11289
- return skillCallsFromCommand(message);
11290
- }
11291
- if (message.type !== "tool_call") return [];
11292
- const mcpName = mcpNameFromToolCall(message);
11293
- return mcpName ? [{ kind: "mcp", id: message.id, mcpName, occurredAt: message.timestamp }] : [];
11294
- });
11295
- }
11296
-
11297
- // src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
11298
11263
  var MAX_FAILED_RECORDS = 500;
11299
11264
  var FLUSH_DEBOUNCE_MS = 15e3;
11300
11265
  var MAX_UPLOADED_SEGMENTS = 50;
11301
11266
  var UPLOADED_SUFFIX = ".uploaded";
11302
- var MAX_TRACKED_MESSAGES = 500;
11303
- var AgentChatActivityBuffer = class {
11267
+ var AnalyticsRecordBuffer = class {
11304
11268
  constructor(options) {
11305
11269
  this.options = options;
11306
11270
  this.storageNames = [
@@ -11318,26 +11282,39 @@ var AgentChatActivityBuffer = class {
11318
11282
  segmentFilePatterns;
11319
11283
  failedRecords = [];
11320
11284
  pendingAppends = /* @__PURE__ */ new Set();
11285
+ recordIds = /* @__PURE__ */ new Set();
11321
11286
  flushTimer = null;
11322
11287
  activeFlush = Promise.resolve({
11323
11288
  flushed: 0,
11324
11289
  failed: 0
11325
11290
  });
11326
- append(record) {
11327
- const pending = mkdir16(ENGINE_DIR2, { recursive: true }).then(
11328
- () => appendFile3(this.liveFile, `${JSON.stringify(record)}
11329
- `, "utf-8")
11330
- ).catch((error) => {
11291
+ append(record, retry = false) {
11292
+ const recordId = this.options.getRecordId?.(record);
11293
+ if (!retry && recordId) {
11294
+ if (this.recordIds.has(recordId)) return;
11295
+ this.recordIds.add(recordId);
11296
+ }
11297
+ const pending = mkdir16(ENGINE_DIR2, { recursive: true }).then(() => appendFile3(this.liveFile, `${JSON.stringify(record)}
11298
+ `, "utf-8")).catch((error) => {
11331
11299
  console.error(
11332
11300
  `[${this.options.storageName}] Append failed, will retry on flush:`,
11333
11301
  error
11334
11302
  );
11335
- if (this.failedRecords.length < MAX_FAILED_RECORDS)
11336
- this.failedRecords.push(record);
11303
+ if (this.failedRecords.length < MAX_FAILED_RECORDS) this.failedRecords.push(record);
11337
11304
  });
11338
11305
  this.pendingAppends.add(pending);
11339
11306
  void pending.finally(() => this.pendingAppends.delete(pending));
11340
11307
  }
11308
+ readStoredRecords() {
11309
+ if (!existsSync8(ENGINE_DIR2)) return [];
11310
+ return readdirSync(ENGINE_DIR2, { withFileTypes: true }).filter((entry) => entry.isFile() && this.storageNames.some(
11311
+ (storageName) => entry.name === `${storageName}.jsonl` || entry.name.startsWith(`${storageName}.`) && (entry.name.endsWith(".jsonl") || entry.name.endsWith(`.jsonl${UPLOADED_SUFFIX}`))
11312
+ )).map(({ name }) => name).sort().flatMap((name) => this.parseRecords(readFileSync7(join24(ENGINE_DIR2, name), "utf-8"))).map((record) => {
11313
+ const recordId = this.options.getRecordId?.(record);
11314
+ if (recordId) this.recordIds.add(recordId);
11315
+ return record;
11316
+ });
11317
+ }
11341
11318
  scheduleFlush() {
11342
11319
  if (this.flushTimer) return;
11343
11320
  this.flushTimer = setTimeout(() => {
@@ -11361,9 +11338,10 @@ var AgentChatActivityBuffer = class {
11361
11338
  return this.activeFlush;
11362
11339
  }
11363
11340
  async runFlush() {
11364
- for (const record of this.failedRecords.splice(0)) this.append(record);
11365
- while (this.pendingAppends.size > 0)
11341
+ for (const record of this.failedRecords.splice(0)) this.append(record, true);
11342
+ while (this.pendingAppends.size > 0) {
11366
11343
  await Promise.allSettled([...this.pendingAppends]);
11344
+ }
11367
11345
  for (const storageName of this.storageNames) {
11368
11346
  await rename2(
11369
11347
  join24(ENGINE_DIR2, `${storageName}.jsonl`),
@@ -11375,8 +11353,7 @@ var AgentChatActivityBuffer = class {
11375
11353
  let flushed = 0;
11376
11354
  let failed = 0;
11377
11355
  for (const entry of entries) {
11378
- if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry)))
11379
- continue;
11356
+ if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry))) continue;
11380
11357
  try {
11381
11358
  await this.uploadSegment(join24(ENGINE_DIR2, entry));
11382
11359
  flushed++;
@@ -11395,28 +11372,32 @@ var AgentChatActivityBuffer = class {
11395
11372
  flushed++;
11396
11373
  } catch (error) {
11397
11374
  failed++;
11398
- this.failedRecords.unshift(
11399
- ...failedRecords.slice(0, MAX_FAILED_RECORDS)
11400
- );
11375
+ this.failedRecords.unshift(...failedRecords.slice(0, MAX_FAILED_RECORDS));
11401
11376
  console.error(
11402
11377
  `[${this.options.storageName}] Failed records upload failed, retained for retry:`,
11403
11378
  error
11404
11379
  );
11405
11380
  }
11406
11381
  }
11407
- const uploaded = entries.filter(
11408
- (entry) => this.storageNames.some(
11382
+ if (!this.options.retainUploadedSegments) {
11383
+ const uploaded = entries.filter((entry) => this.storageNames.some(
11409
11384
  (storageName) => entry.startsWith(`${storageName}.`)
11410
- ) && entry.endsWith(UPLOADED_SUFFIX)
11411
- ).sort();
11412
- for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
11413
- await unlink3(join24(ENGINE_DIR2, entry)).catch(() => {
11414
- });
11385
+ ) && entry.endsWith(UPLOADED_SUFFIX)).sort();
11386
+ for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
11387
+ await unlink3(join24(ENGINE_DIR2, entry)).catch(() => {
11388
+ });
11389
+ }
11415
11390
  }
11416
11391
  return { flushed, failed };
11417
11392
  }
11418
11393
  async uploadSegment(filePath) {
11419
- const records = (await readFile14(filePath, "utf-8")).split("\n").flatMap((line) => {
11394
+ const records = this.parseRecords(await readFile14(filePath, "utf-8"));
11395
+ if (records.length > 0) await this.options.upload(records);
11396
+ await rename2(filePath, `${filePath}${UPLOADED_SUFFIX}`).catch(() => {
11397
+ });
11398
+ }
11399
+ parseRecords(contents) {
11400
+ return contents.split("\n").flatMap((line) => {
11420
11401
  try {
11421
11402
  const parsed = JSON.parse(line);
11422
11403
  return this.options.validate(parsed) ? [parsed] : [];
@@ -11424,16 +11405,51 @@ var AgentChatActivityBuffer = class {
11424
11405
  return [];
11425
11406
  }
11426
11407
  });
11427
- if (records.length > 0) await this.options.upload(records);
11428
- await rename2(filePath, `${filePath}${UPLOADED_SUFFIX}`).catch(() => {
11429
- });
11430
11408
  }
11431
11409
  };
11410
+
11411
+ // src/analytics/agent/activity/skill-mcp-call-extractor.ts
11412
+ var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "deepseek", "fx", "kimi", "opencode", "pi", "custom", "dynamic"]);
11413
+ function mcpNameFromToolCall(message) {
11414
+ const parsedName = parseMcpToolName(message.tool);
11415
+ if (parsedName) return parsedName.server;
11416
+ if (message.tool.startsWith("mcp.")) {
11417
+ const [server, ...toolParts] = message.tool.slice("mcp.".length).split(".");
11418
+ if (server && toolParts.length > 0) return server;
11419
+ }
11420
+ return NON_MCP_SERVERS.has(message.server) ? null : message.server;
11421
+ }
11422
+ function skillCallsFromCommand(message) {
11423
+ const skillNames = message.skillNames ?? [];
11424
+ return skillNames.map((skillName, index) => ({
11425
+ kind: "skill",
11426
+ id: skillNames.length === 1 ? message.id : `${message.id}-${index}`,
11427
+ skillName,
11428
+ occurredAt: message.timestamp
11429
+ }));
11430
+ }
11431
+ function extractSkillMcpCalls(messages, provider) {
11432
+ return messages.flatMap((message) => {
11433
+ if (message.type === "skill") {
11434
+ if (provider === "relay") return [];
11435
+ return [{ kind: "skill", id: message.id, skillName: message.skillName, occurredAt: message.timestamp }];
11436
+ }
11437
+ if (message.type === "command") {
11438
+ return skillCallsFromCommand(message);
11439
+ }
11440
+ if (message.type !== "tool_call") return [];
11441
+ const mcpName = mcpNameFromToolCall(message);
11442
+ return mcpName ? [{ kind: "mcp", id: message.id, mcpName, occurredAt: message.timestamp }] : [];
11443
+ });
11444
+ }
11445
+
11446
+ // src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
11447
+ var MAX_TRACKED_MESSAGES = 500;
11432
11448
  var AgentChatActivityRecordTracker = class {
11433
11449
  pendingRecords = /* @__PURE__ */ new Map();
11434
11450
  buffer;
11435
11451
  constructor(options) {
11436
- this.buffer = new AgentChatActivityBuffer(options);
11452
+ this.buffer = new AnalyticsRecordBuffer(options);
11437
11453
  }
11438
11454
  track(chatId, record) {
11439
11455
  const records = this.pendingRecords.get(chatId) ?? [];
@@ -11456,17 +11472,20 @@ var AgentChatTurnActivityTracker = class {
11456
11472
  activeTurns = /* @__PURE__ */ new Map();
11457
11473
  turnBuffer;
11458
11474
  constructor(uploadTurns) {
11459
- this.turnBuffer = new AgentChatActivityBuffer({
11475
+ this.turnBuffer = new AnalyticsRecordBuffer({
11460
11476
  storageName: "chat-turn-activity",
11461
11477
  legacyStorageNames: ["turn-usage"],
11462
11478
  validate: isAgentChatTurnActivityRecord,
11463
- upload: uploadTurns
11479
+ upload: uploadTurns,
11480
+ getRecordId: (turn) => turn.turnId
11464
11481
  });
11465
11482
  }
11466
11483
  noteMessageAccepted(messageId, request) {
11467
11484
  this.pendingMessages.set(messageId, {
11468
11485
  model: request.model,
11469
- senderUserId: request.senderUserId
11486
+ senderUserId: request.senderUserId,
11487
+ automationId: request.automationId,
11488
+ environmentId: request.environmentId
11470
11489
  });
11471
11490
  for (const key of this.pendingMessages.keys()) {
11472
11491
  if (this.pendingMessages.size <= MAX_TRACKED_MESSAGES) break;
@@ -11477,16 +11496,23 @@ var AgentChatTurnActivityTracker = class {
11477
11496
  const attributes = this.pendingMessages.get(messageId) ?? {};
11478
11497
  const credential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[provider];
11479
11498
  this.pendingMessages.delete(messageId);
11480
- this.activeTurns.set(chatId, {
11499
+ const context = {
11500
+ chatId,
11481
11501
  turnId: randomUUID8(),
11482
- startedAtMs: Date.now(),
11483
11502
  provider,
11484
11503
  model: attributes.model ?? getDefaultAgentModel(provider),
11485
11504
  senderUserId: attributes.senderUserId,
11505
+ automationId: attributes.automationId,
11506
+ environmentId: attributes.environmentId,
11486
11507
  credentialMethod: credential?.method,
11487
- credentialScope: credential?.scope,
11508
+ credentialScope: credential?.scope
11509
+ };
11510
+ this.activeTurns.set(chatId, {
11511
+ ...context,
11512
+ startedAtMs: Date.now(),
11488
11513
  seenCallIds: /* @__PURE__ */ new Set()
11489
11514
  });
11515
+ return context;
11490
11516
  }
11491
11517
  getActiveTurn(chatId) {
11492
11518
  return this.activeTurns.get(chatId);
@@ -11515,39 +11541,32 @@ var AgentChatTurnActivityTracker = class {
11515
11541
  return this.turnBuffer.flush();
11516
11542
  }
11517
11543
  };
11518
- async function uploadAgentChatActivity(endpoint, body) {
11519
- const response = await monolithRequest(endpoint, { body });
11520
- if (!response.ok)
11521
- throw new Error(
11522
- `upload failed: ${response.status} ${await response.text()}`
11523
- );
11524
- }
11525
11544
  var AgentChatActivityTrackerService = class {
11526
11545
  turnActivityTracker;
11527
11546
  skillActivityTracker;
11528
11547
  mcpActivityTracker;
11529
11548
  constructor() {
11530
11549
  this.turnActivityTracker = new AgentChatTurnActivityTracker(
11531
- (turns) => uploadAgentChatActivity("/v1/engine/chat-turn-activity", { turns })
11550
+ (turns) => uploadAnalytics("/v1/engine/chat-turn-activity", { turns })
11532
11551
  );
11533
11552
  this.skillActivityTracker = new AgentChatActivityRecordTracker({
11534
11553
  storageName: "skill-activity",
11535
11554
  legacyStorageNames: ["skill-usage"],
11536
11555
  validate: isAgentChatSkillActivityRecord,
11537
- upload: (skills) => uploadAgentChatActivity("/v1/engine/skill-activity", { skills })
11556
+ upload: (skills) => uploadAnalytics("/v1/engine/skill-activity", { skills })
11538
11557
  });
11539
11558
  this.mcpActivityTracker = new AgentChatActivityRecordTracker({
11540
11559
  storageName: "mcp-activity",
11541
11560
  legacyStorageNames: ["mcp-usage"],
11542
11561
  validate: isAgentChatMcpActivityRecord,
11543
- upload: (mcps) => uploadAgentChatActivity("/v1/engine/mcp-activity", { mcps })
11562
+ upload: (mcps) => uploadAnalytics("/v1/engine/mcp-activity", { mcps })
11544
11563
  });
11545
11564
  }
11546
11565
  noteMessageAccepted(messageId, request) {
11547
11566
  this.turnActivityTracker.noteMessageAccepted(messageId, request);
11548
11567
  }
11549
11568
  noteTurnStarted(chatId, messageId, provider) {
11550
- this.turnActivityTracker.noteTurnStarted(chatId, messageId, provider);
11569
+ return this.turnActivityTracker.noteTurnStarted(chatId, messageId, provider);
11551
11570
  }
11552
11571
  noteSkillMcpCalls(chatId, messages) {
11553
11572
  const turn = this.turnActivityTracker.getActiveTurn(chatId);
@@ -11597,6 +11616,383 @@ var AgentChatActivityTrackerService = class {
11597
11616
  }
11598
11617
  };
11599
11618
 
11619
+ // src/analytics/token-usage/agent-token-usage-tracker.ts
11620
+ import { randomUUID as randomUUID9 } from "crypto";
11621
+
11622
+ // src/analytics/token-usage/claude/parse.ts
11623
+ function isClaudeModelUsage(value) {
11624
+ if (!isRecord(value) || !isTokenUsageCounts(value)) return false;
11625
+ return (value.costUsd === void 0 || typeof value.costUsd === "number" && Number.isFinite(value.costUsd) && value.costUsd >= 0) && (value.pricingProvider === void 0 || isNonEmptyString(value.pricingProvider));
11626
+ }
11627
+ function parseClaudeResult(payload) {
11628
+ if (!isRecord(payload) || payload.type !== "result") return null;
11629
+ if (!isNonEmptyString(payload.session_id) || !isRecord(payload.modelUsage)) return null;
11630
+ const snapshot = {};
11631
+ for (const [rawModel, rawUsage] of Object.entries(payload.modelUsage)) {
11632
+ if (!isNonEmptyString(rawModel) || !isRecord(rawUsage)) continue;
11633
+ const usage = {
11634
+ inputTokens: rawUsage.inputTokens,
11635
+ outputTokens: rawUsage.outputTokens,
11636
+ cacheReadInputTokens: rawUsage.cacheReadInputTokens,
11637
+ cacheWriteInputTokens: rawUsage.cacheCreationInputTokens,
11638
+ ...typeof rawUsage.costUSD === "number" && Number.isFinite(rawUsage.costUSD) && rawUsage.costUSD >= 0 ? { costUsd: rawUsage.costUSD } : {},
11639
+ ...isNonEmptyString(rawUsage.provider) ? { pricingProvider: rawUsage.provider } : {}
11640
+ };
11641
+ if (!isClaudeModelUsage(usage)) continue;
11642
+ const model = isNonEmptyString(rawUsage.canonicalModel) ? rawUsage.canonicalModel : rawModel.replace(/-\d{8}$/, "");
11643
+ const previous = snapshot[model];
11644
+ snapshot[model] = previous ? {
11645
+ inputTokens: previous.inputTokens + usage.inputTokens,
11646
+ outputTokens: previous.outputTokens + usage.outputTokens,
11647
+ cacheReadInputTokens: previous.cacheReadInputTokens + usage.cacheReadInputTokens,
11648
+ cacheWriteInputTokens: previous.cacheWriteInputTokens + usage.cacheWriteInputTokens,
11649
+ ...previous.costUsd !== void 0 || usage.costUsd !== void 0 ? { costUsd: (previous.costUsd ?? 0) + (usage.costUsd ?? 0) } : {},
11650
+ ...usage.pricingProvider ?? previous.pricingProvider ? { pricingProvider: usage.pricingProvider ?? previous.pricingProvider } : {}
11651
+ } : usage;
11652
+ }
11653
+ if (Object.keys(snapshot).length === 0) return null;
11654
+ return {
11655
+ sessionId: payload.session_id,
11656
+ snapshot
11657
+ };
11658
+ }
11659
+
11660
+ // src/analytics/token-usage/claude/types.ts
11661
+ var CLAUDE_HARNESS = "claude";
11662
+
11663
+ // src/analytics/token-usage/claude/index.ts
11664
+ function isClaudeTokenUsageCheckpoint(value) {
11665
+ return isRecord(value) && isNonEmptyString(value.chatId) && isNonEmptyString(value.sessionId) && isRecord(value.snapshot) && Object.values(value.snapshot).every(isClaudeModelUsage);
11666
+ }
11667
+ function normalizeClaudeTokenUsage(result, previous, context, occurredAt) {
11668
+ return Object.entries(result.snapshot).flatMap(([model, currentMetrics]) => {
11669
+ const previousMetrics = previous?.[model];
11670
+ const usage = !previousMetrics || currentMetrics.inputTokens < previousMetrics.inputTokens || currentMetrics.outputTokens < previousMetrics.outputTokens || currentMetrics.cacheReadInputTokens < previousMetrics.cacheReadInputTokens || currentMetrics.cacheWriteInputTokens < previousMetrics.cacheWriteInputTokens ? currentMetrics : {
11671
+ inputTokens: currentMetrics.inputTokens - previousMetrics.inputTokens,
11672
+ outputTokens: currentMetrics.outputTokens - previousMetrics.outputTokens,
11673
+ cacheReadInputTokens: currentMetrics.cacheReadInputTokens - previousMetrics.cacheReadInputTokens,
11674
+ cacheWriteInputTokens: currentMetrics.cacheWriteInputTokens - previousMetrics.cacheWriteInputTokens
11675
+ };
11676
+ const tokenCounts = {
11677
+ inputTokens: usage.inputTokens,
11678
+ outputTokens: usage.outputTokens,
11679
+ cacheReadInputTokens: usage.cacheReadInputTokens,
11680
+ cacheWriteInputTokens: usage.cacheWriteInputTokens
11681
+ };
11682
+ const providerCostUsd = currentMetrics.costUsd === void 0 ? void 0 : previousMetrics?.costUsd === void 0 || currentMetrics.costUsd < previousMetrics.costUsd ? currentMetrics.costUsd : currentMetrics.costUsd - previousMetrics.costUsd;
11683
+ const pricingProvider = currentMetrics.pricingProvider ?? previousMetrics?.pricingProvider;
11684
+ return Object.values(tokenCounts).some((count) => count > 0) ? [{
11685
+ ...context,
11686
+ model,
11687
+ ...tokenCounts,
11688
+ harness: CLAUDE_HARNESS,
11689
+ occurredAt,
11690
+ ...pricingProvider ? { pricingProvider } : {},
11691
+ ...providerCostUsd !== void 0 ? { reportedCostUsd: providerCostUsd } : {}
11692
+ }] : [];
11693
+ });
11694
+ }
11695
+ var ClaudeTokenUsageService = class {
11696
+ snapshots = /* @__PURE__ */ new Map();
11697
+ record(event) {
11698
+ if (event.type !== "usage-updated") return void 0;
11699
+ const result = parseClaudeResult(event.payload);
11700
+ if (!result) return void 0;
11701
+ const snapshotsBySession = this.snapshots.get(event.chatId) ?? /* @__PURE__ */ new Map();
11702
+ this.snapshots.set(event.chatId, snapshotsBySession);
11703
+ const previous = snapshotsBySession.get(result.sessionId);
11704
+ const records = normalizeClaudeTokenUsage(
11705
+ result,
11706
+ previous,
11707
+ event.context,
11708
+ event.occurredAt
11709
+ );
11710
+ snapshotsBySession.set(result.sessionId, result.snapshot);
11711
+ return {
11712
+ records,
11713
+ checkpoint: {
11714
+ chatId: event.chatId,
11715
+ sessionId: result.sessionId,
11716
+ snapshot: result.snapshot
11717
+ }
11718
+ };
11719
+ }
11720
+ restoreCheckpoint(value) {
11721
+ if (!isClaudeTokenUsageCheckpoint(value)) return;
11722
+ const snapshotsBySession = this.snapshots.get(value.chatId) ?? /* @__PURE__ */ new Map();
11723
+ snapshotsBySession.set(value.sessionId, value.snapshot);
11724
+ this.snapshots.set(value.chatId, snapshotsBySession);
11725
+ }
11726
+ };
11727
+
11728
+ // src/analytics/token-usage/codex/index.ts
11729
+ import { createHash as createHash2 } from "crypto";
11730
+
11731
+ // src/analytics/token-usage/codex/parse.ts
11732
+ function isTokenCount(value) {
11733
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
11734
+ }
11735
+ function parseCodexTurn(value) {
11736
+ if (!isRecord(value) || !isNonEmptyString(value.threadId) || !isNonEmptyString(value.turnId)) return void 0;
11737
+ return { threadId: value.threadId, turnId: value.turnId };
11738
+ }
11739
+ function parseCodexUsage(value) {
11740
+ if (!isRecord(value) || !isTokenCount(value.inputTokens) || !isTokenCount(value.cachedInputTokens) || !isTokenCount(value.outputTokens)) {
11741
+ return void 0;
11742
+ }
11743
+ const cacheWriteInputTokens = value.cacheWriteInputTokens;
11744
+ if (cacheWriteInputTokens !== void 0 && !isTokenCount(cacheWriteInputTokens)) return void 0;
11745
+ return {
11746
+ inputTokens: value.inputTokens,
11747
+ cachedInputTokens: value.cachedInputTokens,
11748
+ outputTokens: value.outputTokens,
11749
+ ...cacheWriteInputTokens === void 0 ? {} : { cacheWriteInputTokens }
11750
+ };
11751
+ }
11752
+ function parseCodexUsageUpdate(value) {
11753
+ if (!isRecord(value)) return void 0;
11754
+ const turn = parseCodexTurn(value);
11755
+ const usage = isRecord(value.tokenUsage) ? parseCodexUsage(value.tokenUsage.last) : void 0;
11756
+ if (!turn || !usage) return void 0;
11757
+ return { ...turn, usage };
11758
+ }
11759
+ function parseCodexModelReroute(value) {
11760
+ if (!isRecord(value) || !isNonEmptyString(value.toModel)) return void 0;
11761
+ const turn = parseCodexTurn(value);
11762
+ return turn ? { ...turn, model: value.toModel } : void 0;
11763
+ }
11764
+
11765
+ // src/analytics/token-usage/codex/types.ts
11766
+ var CODEX_HARNESS = "codex";
11767
+
11768
+ // src/analytics/token-usage/codex/index.ts
11769
+ var CodexTokenUsageAdapter = class {
11770
+ constructor(chatId, getContext, recordEvent) {
11771
+ this.chatId = chatId;
11772
+ this.getContext = getContext;
11773
+ this.recordEvent = recordEvent;
11774
+ }
11775
+ chatId;
11776
+ getContext;
11777
+ recordEvent;
11778
+ onTurnStarted = (threadId, turnId) => {
11779
+ const context = this.getContext();
11780
+ if (!context) return;
11781
+ this.recordEvent({
11782
+ harness: CODEX_HARNESS,
11783
+ type: "turn-started",
11784
+ chatId: this.chatId,
11785
+ payload: { threadId, turnId },
11786
+ context
11787
+ });
11788
+ };
11789
+ onTokenUsage = (notification, occurredAt) => {
11790
+ const context = this.getContext();
11791
+ if (!context) return;
11792
+ this.recordEvent({
11793
+ harness: CODEX_HARNESS,
11794
+ type: "usage-updated",
11795
+ chatId: this.chatId,
11796
+ payload: notification,
11797
+ context,
11798
+ occurredAt
11799
+ });
11800
+ };
11801
+ onModelRerouted = (notification) => {
11802
+ if (!this.getContext()) return;
11803
+ this.recordEvent({
11804
+ harness: CODEX_HARNESS,
11805
+ type: "model-rerouted",
11806
+ chatId: this.chatId,
11807
+ payload: notification
11808
+ });
11809
+ };
11810
+ onTurnCompleted = (threadId, turnId) => {
11811
+ this.recordEvent({
11812
+ harness: CODEX_HARNESS,
11813
+ type: "turn-completed",
11814
+ chatId: this.chatId,
11815
+ payload: { threadId, turnId }
11816
+ });
11817
+ };
11818
+ };
11819
+ function getTurnKey({ threadId, turnId }) {
11820
+ return `${threadId}\0${turnId}`;
11821
+ }
11822
+ var CodexTokenUsageService = class {
11823
+ turns = /* @__PURE__ */ new Map();
11824
+ record(event) {
11825
+ switch (event.type) {
11826
+ case "turn-started": {
11827
+ const turn = parseCodexTurn(event.payload);
11828
+ if (!turn) return void 0;
11829
+ const key = getTurnKey(turn);
11830
+ if (!this.turns.has(key)) {
11831
+ this.turns.set(key, { ...turn, context: event.context });
11832
+ }
11833
+ return void 0;
11834
+ }
11835
+ case "usage-updated": {
11836
+ const update = parseCodexUsageUpdate(event.payload);
11837
+ if (!update) return void 0;
11838
+ const key = getTurnKey(update);
11839
+ const turn = this.turns.get(key) ?? {
11840
+ ...update,
11841
+ context: event.context
11842
+ };
11843
+ turn.latestUsage = update.usage;
11844
+ turn.occurredAt = event.occurredAt;
11845
+ this.turns.set(key, turn);
11846
+ return void 0;
11847
+ }
11848
+ case "model-rerouted": {
11849
+ const reroute = parseCodexModelReroute(event.payload);
11850
+ if (!reroute) return void 0;
11851
+ const tracked = this.turns.get(getTurnKey(reroute));
11852
+ if (tracked) tracked.context = { ...tracked.context, model: reroute.model };
11853
+ return void 0;
11854
+ }
11855
+ case "turn-completed": {
11856
+ const turn = parseCodexTurn(event.payload);
11857
+ return turn ? this.finishTurn(event.chatId, turn) : void 0;
11858
+ }
11859
+ case "chat-ended": {
11860
+ const records = [];
11861
+ for (const turn of this.turns.values()) {
11862
+ if (turn.context.chatId !== event.chatId) continue;
11863
+ const batch = this.finishTurn(event.chatId, turn);
11864
+ if (batch) records.push(...batch.records);
11865
+ }
11866
+ return records.length > 0 ? { records } : void 0;
11867
+ }
11868
+ }
11869
+ }
11870
+ restoreCheckpoint() {
11871
+ }
11872
+ finishTurn(chatId, turn) {
11873
+ const key = getTurnKey(turn);
11874
+ const tracked = this.turns.get(key);
11875
+ if (!tracked || tracked.context.chatId !== chatId) return void 0;
11876
+ this.turns.delete(key);
11877
+ if (!tracked.latestUsage || !tracked.occurredAt) return void 0;
11878
+ const cacheWriteInputTokens = tracked.latestUsage.cacheWriteInputTokens ?? 0;
11879
+ return {
11880
+ records: [{
11881
+ ...tracked.context,
11882
+ eventId: createUuidFromHex(createHash2("sha256").update(`codex:${turn.threadId}:${turn.turnId}`).digest("hex")),
11883
+ harness: CODEX_HARNESS,
11884
+ inputTokens: tracked.latestUsage.inputTokens - tracked.latestUsage.cachedInputTokens - cacheWriteInputTokens,
11885
+ outputTokens: tracked.latestUsage.outputTokens,
11886
+ cacheReadInputTokens: tracked.latestUsage.cachedInputTokens,
11887
+ cacheWriteInputTokens,
11888
+ occurredAt: tracked.occurredAt
11889
+ }]
11890
+ };
11891
+ }
11892
+ };
11893
+
11894
+ // src/analytics/token-usage/agent-token-usage-factory.ts
11895
+ function createAgentTokenUsageService(harness) {
11896
+ switch (harness) {
11897
+ case CLAUDE_HARNESS:
11898
+ return new ClaudeTokenUsageService();
11899
+ case CODEX_HARNESS:
11900
+ return new CodexTokenUsageService();
11901
+ default:
11902
+ return void 0;
11903
+ }
11904
+ }
11905
+
11906
+ // src/analytics/token-usage/agent-token-usage-tracker.ts
11907
+ var AgentTokenUsageTracker = class {
11908
+ activeTurns = /* @__PURE__ */ new Map();
11909
+ services = /* @__PURE__ */ new Map();
11910
+ buffer;
11911
+ constructor(uploadAnalytics2) {
11912
+ this.buffer = new AnalyticsRecordBuffer({
11913
+ storageName: "chat-token-usage",
11914
+ validate: (value) => isRecord(value) && isNonEmptyString(value.harness) && Array.isArray(value.records) && value.records.every(isTokenUsageUploadRecord),
11915
+ upload: (batches) => uploadAnalytics2(TOKEN_USAGE_PATH, {
11916
+ records: batches.flatMap(({ records }) => records)
11917
+ }),
11918
+ getRecordId: ({ harness, records }) => {
11919
+ const eventIds = records.flatMap((record) => record.eventId ? [record.eventId] : []);
11920
+ if (eventIds.length === 0 || eventIds.length !== records.length) return void 0;
11921
+ return [harness, ...eventIds].join("\0");
11922
+ },
11923
+ retainUploadedSegments: true
11924
+ });
11925
+ for (const batch of this.buffer.readStoredRecords()) {
11926
+ if (batch.checkpoint !== void 0) {
11927
+ this.getService(batch.harness)?.restoreCheckpoint(batch.checkpoint);
11928
+ }
11929
+ }
11930
+ }
11931
+ startTurn(context) {
11932
+ this.activeTurns.set(context.chatId, context);
11933
+ }
11934
+ getTokenUsageContext(chatId) {
11935
+ const context = this.activeTurns.get(chatId);
11936
+ if (!context?.environmentId) return void 0;
11937
+ return {
11938
+ chatId: context.chatId,
11939
+ model: context.model,
11940
+ credentialMethod: context.credentialMethod,
11941
+ credentialScope: context.credentialScope,
11942
+ senderUserId: context.senderUserId,
11943
+ automationId: context.automationId,
11944
+ environmentId: context.environmentId
11945
+ };
11946
+ }
11947
+ noteAgentEvent(chatId, event) {
11948
+ const context = this.activeTurns.get(chatId);
11949
+ const tokenUsageContext = this.getTokenUsageContext(chatId);
11950
+ if (!context || !tokenUsageContext) return;
11951
+ this.recordEvent({
11952
+ harness: context.provider,
11953
+ type: "usage-updated",
11954
+ chatId,
11955
+ payload: event.payload,
11956
+ context: tokenUsageContext,
11957
+ occurredAt: event.timestamp
11958
+ });
11959
+ }
11960
+ endTurn(chatId) {
11961
+ const context = this.activeTurns.get(chatId);
11962
+ if (!context) return;
11963
+ this.recordEvent({
11964
+ harness: context.provider,
11965
+ type: "chat-ended",
11966
+ chatId
11967
+ });
11968
+ this.activeTurns.delete(chatId);
11969
+ }
11970
+ flush(finalizeInFlight = true) {
11971
+ if (finalizeInFlight) {
11972
+ for (const chatId of [...this.activeTurns.keys()]) this.endTurn(chatId);
11973
+ }
11974
+ return this.buffer.flush();
11975
+ }
11976
+ recordEvent(event) {
11977
+ const batch = this.getService(event.harness)?.record(event);
11978
+ if (!batch) return;
11979
+ this.buffer.append({
11980
+ harness: event.harness,
11981
+ records: batch.records.map((record) => ({
11982
+ ...record,
11983
+ eventId: record.eventId ?? randomUUID9()
11984
+ })),
11985
+ checkpoint: batch.checkpoint
11986
+ });
11987
+ this.buffer.scheduleFlush();
11988
+ }
11989
+ getService(harness) {
11990
+ const service = this.services.get(harness) ?? createAgentTokenUsageService(harness);
11991
+ if (service) this.services.set(harness, service);
11992
+ return service;
11993
+ }
11994
+ };
11995
+
11600
11996
  // src/services/keep-alive-service.ts
11601
11997
  var KeepAliveService = class _KeepAliveService {
11602
11998
  interval = null;
@@ -11872,7 +12268,7 @@ async function reconcileCanvasItems(filenames) {
11872
12268
 
11873
12269
  // src/services/upload-chat-transcripts.ts
11874
12270
  import { createReadStream } from "fs";
11875
- import { createHash as createHash2 } from "crypto";
12271
+ import { createHash as createHash3 } from "crypto";
11876
12272
  import { readFile as readFile16, readdir as readdir8, stat as stat4 } from "fs/promises";
11877
12273
  import { basename as basename2, join as join27 } from "path";
11878
12274
 
@@ -11943,7 +12339,7 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
11943
12339
  deletedAt: chat.deletedAt ?? null
11944
12340
  } : {};
11945
12341
  if (capture) {
11946
- const hash = createHash2("sha256");
12342
+ const hash = createHash3("sha256");
11947
12343
  for await (const chunk of createReadStream(filePath)) hash.update(chunk);
11948
12344
  metadata.captureId = capture.captureId;
11949
12345
  metadata.captureReason = capture.reason;
@@ -12164,7 +12560,7 @@ function isCodexAvailable() {
12164
12560
  return detectCodexAuthMethod() !== "none";
12165
12561
  }
12166
12562
  function isOpencodeAvailable() {
12167
- return existsSync8(OPENCODE_AUTH_PATH);
12563
+ return existsSync9(OPENCODE_AUTH_PATH);
12168
12564
  }
12169
12565
  function isDeepseekAvailable() {
12170
12566
  return Boolean(getDeepseekApiKey());
@@ -12176,7 +12572,7 @@ function isKimiAvailable() {
12176
12572
  return Boolean(ENGINE_ENV.AI_GATEWAY_API_KEY);
12177
12573
  }
12178
12574
  function isPiAvailable() {
12179
- return existsSync8(PI_AUTH_PATH);
12575
+ return existsSync9(PI_AUTH_PATH);
12180
12576
  }
12181
12577
  function isCursorAvailable() {
12182
12578
  return Boolean(ENGINE_ENV.CURSOR_API_KEY);
@@ -12300,15 +12696,17 @@ function corruptChatsFilePath() {
12300
12696
  return `${CHATS_FILE}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
12301
12697
  }
12302
12698
  var ChatService = class {
12303
- constructor(workingDirectory, agentChatActivityTrackerService2) {
12699
+ constructor(workingDirectory, agentChatActivityTrackerService2, agentTokenUsageTracker2) {
12304
12700
  this.workingDirectory = workingDirectory;
12305
12701
  this.agentChatActivityTrackerService = agentChatActivityTrackerService2;
12702
+ this.agentTokenUsageTracker = agentTokenUsageTracker2;
12306
12703
  keepAliveService.setActivityCheck(
12307
12704
  () => [...this.chats.values()].some((chat) => !chat.persisted.deletedAt && hasActiveAgentWork(chat))
12308
12705
  );
12309
12706
  }
12310
12707
  workingDirectory;
12311
12708
  agentChatActivityTrackerService;
12709
+ agentTokenUsageTracker;
12312
12710
  chats = /* @__PURE__ */ new Map();
12313
12711
  persistPromise = null;
12314
12712
  persistQueued = false;
@@ -12386,7 +12784,7 @@ var ChatService = class {
12386
12784
  throw new ChatNotFoundError(parentChatId);
12387
12785
  }
12388
12786
  const persisted = {
12389
- id: request.id ?? randomUUID9(),
12787
+ id: request.id ?? randomUUID10(),
12390
12788
  provider: request.provider,
12391
12789
  title,
12392
12790
  createdAt: now,
@@ -12528,7 +12926,7 @@ var ChatService = class {
12528
12926
  async interrupt(chatId) {
12529
12927
  const chat = this.requireChat(chatId);
12530
12928
  const result = await chat.provider.interrupt();
12531
- this.agentChatActivityTrackerService.noteTurnEnded(chatId);
12929
+ this.endTrackedTurn(chatId);
12532
12930
  chat.hasActiveTurn = false;
12533
12931
  chat.activeMessageId = null;
12534
12932
  chat.pendingMessageIds = [];
@@ -12550,7 +12948,7 @@ var ChatService = class {
12550
12948
  return { interrupted: false, queue: [], goal: null };
12551
12949
  }
12552
12950
  const interruptResult = await chat.provider.interrupt();
12553
- this.agentChatActivityTrackerService.noteTurnEnded(chatId);
12951
+ this.endTrackedTurn(chatId);
12554
12952
  chat.hasActiveTurn = false;
12555
12953
  chat.activeMessageId = null;
12556
12954
  chat.pendingMessageIds = [];
@@ -12677,7 +13075,7 @@ var ChatService = class {
12677
13075
  messageId,
12678
13076
  ...chat.pendingMessageIds.filter((pendingMessageId) => pendingMessageId !== messageId && pendingMessageId !== interruptedMessageId)
12679
13077
  ];
12680
- this.agentChatActivityTrackerService.noteTurnEnded(chatId);
13078
+ this.endTrackedTurn(chatId);
12681
13079
  chat.hasActiveTurn = false;
12682
13080
  chat.activeMessageId = null;
12683
13081
  if (interruptedMessageId) {
@@ -12830,12 +13228,15 @@ var ChatService = class {
12830
13228
  const chatsById = new Map(
12831
13229
  [...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
12832
13230
  );
12833
- const [chatTranscripts, canvas, repoState, engineLogs, chatActivity] = await Promise.all([
13231
+ const [chatTranscripts, canvas, repoState, engineLogs, [chatActivity]] = await Promise.all([
12834
13232
  flushAllChatTranscripts(chatsById, capture),
12835
13233
  flushAllCanvasItems(),
12836
13234
  flushRepoState(),
12837
13235
  flushAllEngineLogs(),
12838
- this.agentChatActivityTrackerService.flush()
13236
+ Promise.all([
13237
+ this.agentChatActivityTrackerService.flush(),
13238
+ this.agentTokenUsageTracker.flush()
13239
+ ])
12839
13240
  ]);
12840
13241
  return {
12841
13242
  chatTranscripts,
@@ -12981,6 +13382,11 @@ var ChatService = class {
12981
13382
  onMessageStarted
12982
13383
  });
12983
13384
  } else {
13385
+ const codexTokenUsage = new CodexTokenUsageAdapter(
13386
+ persisted.id,
13387
+ () => this.agentTokenUsageTracker.getTokenUsageContext(persisted.id),
13388
+ (event) => this.agentTokenUsageTracker.recordEvent(event)
13389
+ );
12984
13390
  provider = new CodexAspManager({
12985
13391
  workingDirectory: this.workingDirectory,
12986
13392
  historyFilePath: join29(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
@@ -12989,7 +13395,11 @@ var ChatService = class {
12989
13395
  onTurnComplete: onProviderTurnComplete,
12990
13396
  onEvent: onProviderEvent,
12991
13397
  onProcessingChanged,
12992
- onMessageStarted
13398
+ onMessageStarted,
13399
+ onCodexTurnStarted: codexTokenUsage.onTurnStarted,
13400
+ onCodexTokenUsage: codexTokenUsage.onTokenUsage,
13401
+ onCodexModelRerouted: codexTokenUsage.onModelRerouted,
13402
+ onCodexTurnCompleted: codexTokenUsage.onTurnCompleted
12993
13403
  });
12994
13404
  }
12995
13405
  return {
@@ -13039,25 +13449,29 @@ var ChatService = class {
13039
13449
  }
13040
13450
  return chat;
13041
13451
  }
13452
+ activateTurn(chat, messageId) {
13453
+ chat.hasActiveTurn = true;
13454
+ chat.activeMessageId = messageId;
13455
+ const context = this.agentChatActivityTrackerService.noteTurnStarted(
13456
+ chat.persisted.id,
13457
+ messageId,
13458
+ chat.persisted.provider
13459
+ );
13460
+ this.agentTokenUsageTracker.startTurn(context);
13461
+ chat.activeErrorNotificationTarget = chat.errorNotificationTargets.get(messageId) ?? null;
13462
+ chat.errorNotificationTargets.delete(messageId);
13463
+ }
13042
13464
  async handleTurnStarted(chatId, message) {
13043
13465
  const chat = this.getRuntimeChat(chatId);
13044
13466
  if (!chat) return;
13045
13467
  chat.pendingMessageIds = chat.pendingMessageIds.filter((messageId) => messageId !== message.id);
13046
- chat.hasActiveTurn = true;
13047
- chat.activeMessageId = message.id;
13048
13468
  const { event: acceptedEvent, sender } = createTurnMetadata(
13049
13469
  message,
13050
13470
  message.id,
13051
13471
  message.queuedAt
13052
13472
  );
13053
13473
  chat.acceptedUserEvents.set(message.id, acceptedEvent);
13054
- this.agentChatActivityTrackerService.noteTurnStarted(
13055
- chat.persisted.id,
13056
- message.id,
13057
- chat.persisted.provider
13058
- );
13059
- chat.activeErrorNotificationTarget = chat.errorNotificationTargets.get(message.id) ?? null;
13060
- chat.errorNotificationTargets.delete(message.id);
13474
+ this.activateTurn(chat, message.id);
13061
13475
  await this.publish({
13062
13476
  type: "chat.turn.started",
13063
13477
  payload: {
@@ -13074,6 +13488,22 @@ var ChatService = class {
13074
13488
  return;
13075
13489
  }
13076
13490
  keepAliveService.noteActivity();
13491
+ if (!chat.hasActiveTurn && chat.pendingMessageIds.length > 0) {
13492
+ const messageId = chat.pendingMessageIds.shift();
13493
+ if (!messageId) {
13494
+ return;
13495
+ }
13496
+ this.activateTurn(chat, messageId);
13497
+ this.publish({
13498
+ type: "chat.turn.started",
13499
+ payload: {
13500
+ chatId: chat.persisted.id,
13501
+ messageId
13502
+ }
13503
+ }).catch(() => {
13504
+ });
13505
+ }
13506
+ this.agentTokenUsageTracker.noteAgentEvent(chatId, event);
13077
13507
  const codexTranscript = getCodexTranscriptFromEvent(event);
13078
13508
  if (chat.hasActiveTurn) {
13079
13509
  const displayMessages = codexTranscript ? parseLatestCodexAspTranscriptTurn(codexTranscript) : parseDisplayMessages([event], chat.persisted.provider, null, {
@@ -13134,6 +13564,10 @@ var ChatService = class {
13134
13564
  });
13135
13565
  }
13136
13566
  }
13567
+ endTrackedTurn(chatId) {
13568
+ this.agentTokenUsageTracker.endTurn(chatId);
13569
+ this.agentChatActivityTrackerService.noteTurnEnded(chatId);
13570
+ }
13137
13571
  async handleTurnFinished(chatId) {
13138
13572
  const chat = this.getRuntimeChat(chatId);
13139
13573
  if (!chat) {
@@ -13146,7 +13580,7 @@ var ChatService = class {
13146
13580
  }
13147
13581
  chat.hasActiveTurn = false;
13148
13582
  chat.activeMessageId = null;
13149
- this.agentChatActivityTrackerService.noteTurnEnded(chatId);
13583
+ this.endTrackedTurn(chatId);
13150
13584
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
13151
13585
  const processing = chat.provider.getQueue().length > 0 || (chat.provider.hasActiveBackgroundTasks?.() ?? false);
13152
13586
  this.publish({
@@ -13231,7 +13665,7 @@ var ChatService = class {
13231
13665
  }
13232
13666
  async publish(input) {
13233
13667
  const event = {
13234
- id: randomUUID9(),
13668
+ id: randomUUID10(),
13235
13669
  ts: (/* @__PURE__ */ new Date()).toISOString(),
13236
13670
  ...input
13237
13671
  };
@@ -13584,14 +14018,14 @@ var RepoFileService = class {
13584
14018
 
13585
14019
  // src/v1-routes.ts
13586
14020
  import { Hono } from "hono";
13587
- import { z as z6 } from "zod";
14021
+ import { z as z5 } from "zod";
13588
14022
  import { readdir as readdir11, stat as stat7, readFile as readFile21 } from "fs/promises";
13589
14023
  import { join as join33, resolve as resolve3 } from "path";
13590
14024
 
13591
14025
  // src/services/warm-hooks-service.ts
13592
14026
  import { spawn as spawn6 } from "child_process";
13593
14027
  import { readFile as readFile20 } from "fs/promises";
13594
- import { existsSync as existsSync9 } from "fs";
14028
+ import { existsSync as existsSync10 } from "fs";
13595
14029
  import { join as join32 } from "path";
13596
14030
 
13597
14031
  // src/services/warm-hook-logs-service.ts
@@ -13713,7 +14147,7 @@ var warmHookLogsService = new WarmHookLogsService();
13713
14147
  async function readRepoWarmHook(repoPath) {
13714
14148
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
13715
14149
  const configPath = join32(repoPath, filename);
13716
- if (!existsSync9(configPath)) {
14150
+ if (!existsSync10(configPath)) {
13717
14151
  continue;
13718
14152
  }
13719
14153
  try {
@@ -13973,8 +14407,8 @@ ${combinedScript}` : combinedScript;
13973
14407
  }
13974
14408
 
13975
14409
  // src/services/terminal-service.ts
13976
- import { randomUUID as randomUUID10 } from "crypto";
13977
- import { existsSync as existsSync10 } from "fs";
14410
+ import { randomUUID as randomUUID11 } from "crypto";
14411
+ import { existsSync as existsSync11 } from "fs";
13978
14412
  import { spawn as spawn7 } from "node-pty";
13979
14413
  var MAX_REPLAY_CHARS = 1024 * 1024;
13980
14414
  var MAX_TERMINAL_SESSIONS = 8;
@@ -13992,8 +14426,8 @@ var TerminalService = class {
13992
14426
  code: "limit"
13993
14427
  });
13994
14428
  }
13995
- const id = randomUUID10();
13996
- const shell = process.env.SHELL && existsSync10(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
14429
+ const id = randomUUID11();
14430
+ const shell = process.env.SHELL && existsSync11(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
13997
14431
  const pty = spawn7(shell, ["-l"], {
13998
14432
  name: "xterm-256color",
13999
14433
  cols,
@@ -14092,72 +14526,26 @@ var TerminalService = class {
14092
14526
  var terminalService = new TerminalService();
14093
14527
 
14094
14528
  // src/v1-routes.ts
14095
- var imageMediaTypeSchema = z6.enum(IMAGE_MEDIA_TYPES);
14096
- var createPreviewSchema = z6.object({
14097
- port: z6.number().int().min(1).max(65535),
14098
- publicUrl: z6.string().min(1)
14529
+ var createPreviewSchema = z5.object({
14530
+ port: z5.number().int().min(1).max(65535),
14531
+ publicUrl: z5.string().min(1)
14099
14532
  });
14100
- var terminalSizeSchema = z6.object({
14101
- cols: z6.number().int().min(2).max(500),
14102
- rows: z6.number().int().min(1).max(200)
14533
+ var terminalSizeSchema = z5.object({
14534
+ cols: z5.number().int().min(2).max(500),
14535
+ rows: z5.number().int().min(1).max(200)
14103
14536
  });
14104
- var writeTerminalSessionSchema = z6.object({
14105
- data: z6.string().max(64 * 1024),
14106
- generation: z6.number().int().nonnegative(),
14107
- sequence: z6.number().int().nonnegative()
14537
+ var writeTerminalSessionSchema = z5.object({
14538
+ data: z5.string().max(64 * 1024),
14539
+ generation: z5.number().int().nonnegative(),
14540
+ sequence: z5.number().int().nonnegative()
14108
14541
  });
14109
- var sendMessageSchema = z6.object({
14110
- messageId: z6.string().min(1).optional(),
14111
- submittedAt: z6.string().datetime().optional(),
14112
- message: z6.string().min(1),
14113
- model: z6.string().optional(),
14114
- customInstructions: z6.string().optional(),
14115
- planMode: z6.boolean().optional(),
14116
- images: z6.array(z6.object({
14117
- type: z6.literal("image"),
14118
- source: z6.union([
14119
- z6.object({
14120
- type: z6.literal("base64"),
14121
- media_type: imageMediaTypeSchema,
14122
- data: z6.string().min(1)
14123
- }),
14124
- z6.object({
14125
- type: z6.literal("url"),
14126
- url: z6.string().url()
14127
- })
14128
- ])
14129
- })).optional(),
14130
- thinkingLevel: z6.enum(VALID_THINKING_LEVELS).optional(),
14131
- goalMode: z6.boolean().optional(),
14132
- fastMode: z6.boolean().optional(),
14133
- enableInteractiveTools: z6.boolean().optional(),
14134
- type: z6.string().min(1).optional(),
14135
- merge: z6.boolean().optional(),
14136
- idempotencyKey: z6.string().min(1).max(128).optional(),
14137
- senderUserId: z6.string().optional(),
14138
- senderEmail: z6.string().optional(),
14139
- senderDisplayName: z6.string().optional(),
14140
- senderAvatarUrl: z6.string().optional(),
14141
- errorNotificationTarget: z6.discriminatedUnion("type", [
14142
- z6.object({ type: z6.literal("slack") }),
14143
- z6.object({ type: z6.literal("linear"), sessionId: z6.string().min(1) }),
14144
- z6.object({
14145
- type: z6.literal("code_host"),
14146
- provider: z6.enum(["github", "gitlab"]),
14147
- resource: z6.enum(["issue", "pull_request"]),
14148
- repositoryId: z6.string().min(1),
14149
- resourceNumber: z6.number().int().positive()
14150
- }),
14151
- z6.object({ type: z6.literal("automation"), executionId: z6.string().min(1) })
14152
- ]).optional()
14153
- });
14154
- var respondToolInputSchema = z6.object({
14155
- requestId: z6.string().min(1),
14156
- selectionId: z6.string().min(1)
14542
+ var respondToolInputSchema = z5.object({
14543
+ requestId: z5.string().min(1),
14544
+ selectionId: z5.string().min(1)
14157
14545
  });
14158
- var updateGoalSchema = z6.object({
14159
- objective: z6.string().trim().min(1).max(MAX_CODEX_GOAL_OBJECTIVE_CHARS).optional(),
14160
- status: z6.enum(["active", "paused"]).optional()
14546
+ var updateGoalSchema = z5.object({
14547
+ objective: z5.string().trim().min(1).max(MAX_CODEX_GOAL_OBJECTIVE_CHARS).optional(),
14548
+ status: z5.enum(["active", "paused"]).optional()
14161
14549
  }).refine((body) => body.objective !== void 0 || body.status !== void 0, {
14162
14550
  message: "Goal objective or status required"
14163
14551
  });
@@ -14332,7 +14720,7 @@ function createV1Routes(deps) {
14332
14720
  });
14333
14721
  app2.post("/chats/:chatId/messages", async (c) => {
14334
14722
  try {
14335
- const body = sendMessageSchema.parse(await c.req.json());
14723
+ const body = sendChatMessageRequestSchema.strip().parse(await c.req.json());
14336
14724
  const goalValidationError = getGoalCommandObjectiveValidationError(body.message, body.goalMode);
14337
14725
  if (goalValidationError) {
14338
14726
  return c.json(jsonError(goalValidationError), 400);
@@ -14371,7 +14759,7 @@ function createV1Routes(deps) {
14371
14759
  const result = await deps.chatService.updateGoal(c.req.param("chatId"), body);
14372
14760
  return c.json(result);
14373
14761
  } catch (error) {
14374
- if (error instanceof z6.ZodError) {
14762
+ if (error instanceof z5.ZodError) {
14375
14763
  return c.json(jsonError(error.issues[0]?.message || "Invalid goal update"), 400);
14376
14764
  }
14377
14765
  if (error instanceof ChatNotFoundError) {
@@ -15114,7 +15502,7 @@ async function timeStartupStep(name, fn) {
15114
15502
  async function waitForInitializationGate() {
15115
15503
  if (!ENGINE_ENV.REPLICAS_ENGINE_DEFER_INITIALIZATION) return;
15116
15504
  const deadline = Date.now() + ENGINE_INIT_GATE_TIMEOUT_MS;
15117
- while (!existsSync11(SANDBOX_PATHS.ENGINE_INIT_GATE)) {
15505
+ while (!existsSync12(SANDBOX_PATHS.ENGINE_INIT_GATE)) {
15118
15506
  if (Date.now() >= deadline) {
15119
15507
  throw new Error("Timed out waiting for deferred engine initialization gate");
15120
15508
  }
@@ -15179,7 +15567,12 @@ var authMiddleware = async (c, next) => {
15179
15567
  await next();
15180
15568
  };
15181
15569
  var agentChatActivityTrackerService = new AgentChatActivityTrackerService();
15182
- var chatService = new ChatService(gitService.getWorkspaceRoot(), agentChatActivityTrackerService);
15570
+ var agentTokenUsageTracker = new AgentTokenUsageTracker(uploadAnalytics);
15571
+ var chatService = new ChatService(
15572
+ gitService.getWorkspaceRoot(),
15573
+ agentChatActivityTrackerService,
15574
+ agentTokenUsageTracker
15575
+ );
15183
15576
  app.get("/health", async (c) => {
15184
15577
  const requestedWaitMs = Number(c.req.query(ENGINE_HEALTH_WAIT_QUERY_PARAM));
15185
15578
  const waitMs = Number.isFinite(requestedWaitMs) ? Math.min(Math.max(requestedWaitMs, 0), ENGINE_HEALTH_MAX_WAIT_MS) : 0;
@@ -15281,7 +15674,7 @@ function startStatusBroadcaster() {
15281
15674
  if (serialized !== previousRepoStatus) {
15282
15675
  previousRepoStatus = serialized;
15283
15676
  eventService.publish({
15284
- id: randomUUID11(),
15677
+ id: randomUUID12(),
15285
15678
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15286
15679
  type: "repo.status.changed",
15287
15680
  payload: { repos }
@@ -15302,7 +15695,7 @@ function startStatusBroadcaster() {
15302
15695
  if (engineStatusJson !== previousEngineStatus) {
15303
15696
  previousEngineStatus = engineStatusJson;
15304
15697
  eventService.publish({
15305
- id: randomUUID11(),
15698
+ id: randomUUID12(),
15306
15699
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15307
15700
  type: "engine.status.changed",
15308
15701
  payload: { status: engineStatus }
@@ -15321,7 +15714,7 @@ function startStatusBroadcaster() {
15321
15714
  previousHookStatus = hookSnapshot;
15322
15715
  if (!lastHooksRunning && hooksRunning) {
15323
15716
  eventService.publish({
15324
- id: randomUUID11(),
15717
+ id: randomUUID12(),
15325
15718
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15326
15719
  type: "hooks.started",
15327
15720
  payload: { running: true, completed: false }
@@ -15330,7 +15723,7 @@ function startStatusBroadcaster() {
15330
15723
  }
15331
15724
  if (hooksRunning) {
15332
15725
  eventService.publish({
15333
- id: randomUUID11(),
15726
+ id: randomUUID12(),
15334
15727
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15335
15728
  type: "hooks.progress",
15336
15729
  payload: { running: true, completed: false }
@@ -15339,7 +15732,7 @@ function startStatusBroadcaster() {
15339
15732
  }
15340
15733
  if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
15341
15734
  eventService.publish({
15342
- id: randomUUID11(),
15735
+ id: randomUUID12(),
15343
15736
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15344
15737
  type: "hooks.completed",
15345
15738
  payload: { running: false, completed: true }
@@ -15348,7 +15741,7 @@ function startStatusBroadcaster() {
15348
15741
  }
15349
15742
  if (lastHooksRunning && !hooksRunning && hooksFailed) {
15350
15743
  eventService.publish({
15351
- id: randomUUID11(),
15744
+ id: randomUUID12(),
15352
15745
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15353
15746
  type: "hooks.failed",
15354
15747
  payload: { running: false, completed: hooksCompleted }
@@ -15356,7 +15749,7 @@ function startStatusBroadcaster() {
15356
15749
  });
15357
15750
  }
15358
15751
  eventService.publish({
15359
- id: randomUUID11(),
15752
+ id: randomUUID12(),
15360
15753
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15361
15754
  type: "hooks.status",
15362
15755
  payload: {
@@ -15411,20 +15804,20 @@ serve(
15411
15804
  }
15412
15805
  const repos = await gitService.listRepos();
15413
15806
  await eventService.publish({
15414
- id: randomUUID11(),
15807
+ id: randomUUID12(),
15415
15808
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15416
15809
  type: "repo.discovered",
15417
15810
  payload: { repos }
15418
15811
  });
15419
15812
  const repoStatuses = await gitService.listRepos();
15420
15813
  await eventService.publish({
15421
- id: randomUUID11(),
15814
+ id: randomUUID12(),
15422
15815
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15423
15816
  type: "repo.status.changed",
15424
15817
  payload: { repos: repoStatuses }
15425
15818
  });
15426
15819
  await eventService.publish({
15427
- id: randomUUID11(),
15820
+ id: randomUUID12(),
15428
15821
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15429
15822
  type: "engine.ready",
15430
15823
  payload: { version: "v1" }