replicas-engine 0.1.698 → 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/README.md +1 -0
- package/dist/src/{chunk-2BBE2BVQ.js → chunk-HMWJSWP2.js} +133 -62
- package/dist/src/headless-agent.js +1 -1
- package/dist/src/index.js +653 -260
- package/package.json +1 -1
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-
|
|
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
|
|
203
|
-
import { randomUUID as
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
9510
|
-
|
|
9511
|
-
|
|
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 =
|
|
9582
|
-
type:
|
|
9583
|
-
key:
|
|
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
|
|
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 =
|
|
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:
|
|
10746
|
-
model:
|
|
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:
|
|
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:
|
|
10760
|
-
timeout_minutes:
|
|
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:
|
|
10842
|
-
message:
|
|
10843
|
-
model:
|
|
10844
|
-
thinking_level:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
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"))
|
|
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
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 ??
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
13977
|
-
import { existsSync as
|
|
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 =
|
|
13996
|
-
const shell = process.env.SHELL &&
|
|
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
|
|
14096
|
-
|
|
14097
|
-
|
|
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 =
|
|
14101
|
-
cols:
|
|
14102
|
-
rows:
|
|
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 =
|
|
14105
|
-
data:
|
|
14106
|
-
generation:
|
|
14107
|
-
sequence:
|
|
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
|
|
14110
|
-
|
|
14111
|
-
|
|
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 =
|
|
14159
|
-
objective:
|
|
14160
|
-
status:
|
|
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 =
|
|
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
|
|
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 (!
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
15820
|
+
id: randomUUID12(),
|
|
15428
15821
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15429
15822
|
type: "engine.ready",
|
|
15430
15823
|
payload: { version: "v1" }
|