replicas-engine 0.1.552 → 0.1.554
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 +2 -0
- package/dist/src/index.js +619 -218
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -260,7 +260,10 @@ var AGENT_CREDENTIAL_METHODS_IN_ORDER = {
|
|
|
260
260
|
]
|
|
261
261
|
};
|
|
262
262
|
|
|
263
|
-
// ../shared/src/
|
|
263
|
+
// ../shared/src/analytics/types.ts
|
|
264
|
+
function isUsageRecord(value) {
|
|
265
|
+
return isRecord(value) && typeof value.chatId === "string" && typeof value.provider === "string" && isValidAgentProvider(value.provider) && typeof value.model === "string" && (value.credentialMethod === void 0 || isAuthMethod(value.credentialMethod)) && (value.credentialScope === void 0 || isCredentialScope(value.credentialScope)) && value.credentialMethod === void 0 === (value.credentialScope === void 0) && (value.senderUserId === void 0 || typeof value.senderUserId === "string") && typeof value.occurredAt === "string";
|
|
266
|
+
}
|
|
264
267
|
function isAuthMethod(value) {
|
|
265
268
|
return typeof value === "string" && Object.values(CREDENTIAL_METHOD).some((method) => method === value);
|
|
266
269
|
}
|
|
@@ -268,7 +271,21 @@ function isCredentialScope(value) {
|
|
|
268
271
|
return typeof value === "string" && CREDENTIAL_SCOPES.some((scope) => scope === value);
|
|
269
272
|
}
|
|
270
273
|
function isChatTurnUsageRecord(value) {
|
|
271
|
-
return
|
|
274
|
+
return isUsageRecord(value) && typeof value.turnId === "string" && typeof value.seconds === "number" && Number.isFinite(value.seconds) && value.seconds >= 0;
|
|
275
|
+
}
|
|
276
|
+
function isCallUsageRecord(value) {
|
|
277
|
+
return isUsageRecord(value) && typeof value.callId === "string" && value.callId.length > 0;
|
|
278
|
+
}
|
|
279
|
+
function isNamedCallUsageRecord(value, field) {
|
|
280
|
+
if (!isCallUsageRecord(value)) return false;
|
|
281
|
+
const name = value[field];
|
|
282
|
+
return typeof name === "string" && name.length > 0;
|
|
283
|
+
}
|
|
284
|
+
function isSkillUsageRecord(value) {
|
|
285
|
+
return isNamedCallUsageRecord(value, "skillName");
|
|
286
|
+
}
|
|
287
|
+
function isMcpUsageRecord(value) {
|
|
288
|
+
return isNamedCallUsageRecord(value, "mcpName");
|
|
272
289
|
}
|
|
273
290
|
|
|
274
291
|
// ../shared/src/aster.ts
|
|
@@ -708,7 +725,7 @@ var WORKSPACE_SIZES = ["small", "large"];
|
|
|
708
725
|
var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
|
|
709
726
|
|
|
710
727
|
// ../shared/src/e2b.ts
|
|
711
|
-
var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-08-
|
|
728
|
+
var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-08-04-v1";
|
|
712
729
|
|
|
713
730
|
// ../shared/src/runtime-env.ts
|
|
714
731
|
function shellQuotePosix(value) {
|
|
@@ -2809,6 +2826,16 @@ var DEFAULT_DEFAULT_SKILLS = {
|
|
|
2809
2826
|
}
|
|
2810
2827
|
};
|
|
2811
2828
|
|
|
2829
|
+
// ../shared/src/prompts.ts
|
|
2830
|
+
var REPLICAS_INSTRUCTIONS_TAG = "replicas_instructions";
|
|
2831
|
+
function removeTag(text, tag) {
|
|
2832
|
+
const regex = new RegExp(`<${tag}>[\\s\\S]*?</${tag}>`, "g");
|
|
2833
|
+
return text.replace(regex, "").trim();
|
|
2834
|
+
}
|
|
2835
|
+
function removeReplicasInstructions(text) {
|
|
2836
|
+
return removeTag(text, REPLICAS_INSTRUCTIONS_TAG);
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2812
2839
|
// ../shared/src/replicas-config.ts
|
|
2813
2840
|
import { parse as parseYaml } from "yaml";
|
|
2814
2841
|
|
|
@@ -3560,7 +3587,17 @@ function isTerminalBackgroundTaskStatus(status) {
|
|
|
3560
3587
|
// ../shared/src/display-message/constants.ts
|
|
3561
3588
|
var USER_MESSAGE_MATCH_GRACE_PERIOD_MS = 3e4;
|
|
3562
3589
|
|
|
3590
|
+
// ../shared/src/json.ts
|
|
3591
|
+
function safeJsonParse(str, fallback) {
|
|
3592
|
+
try {
|
|
3593
|
+
return JSON.parse(str);
|
|
3594
|
+
} catch {
|
|
3595
|
+
return fallback;
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3563
3599
|
// ../shared/src/display-message/parsers/utils.ts
|
|
3600
|
+
var INTERRUPTED_MESSAGE_REGEX = /^\[Request interrupted by user.*\]$/;
|
|
3564
3601
|
function userMessageImages(value) {
|
|
3565
3602
|
if (!Array.isArray(value)) return void 0;
|
|
3566
3603
|
const images = value.filter((item) => isRecord(item) && item.type === "image" && typeof item.mediaType === "string" && typeof item.data === "string");
|
|
@@ -3575,6 +3612,32 @@ function stringifyDisplayValue(value) {
|
|
|
3575
3612
|
return String(value);
|
|
3576
3613
|
}
|
|
3577
3614
|
}
|
|
3615
|
+
function upsertDisplayMessage(messages, message) {
|
|
3616
|
+
const index = messages.findIndex((candidate) => candidate.id === message.id);
|
|
3617
|
+
if (index === -1) messages.push(message);
|
|
3618
|
+
else messages[index] = message;
|
|
3619
|
+
}
|
|
3620
|
+
function parseSkillName(tool2, input) {
|
|
3621
|
+
if (typeof tool2 !== "string") return null;
|
|
3622
|
+
const normalizedTool = tool2.toLowerCase();
|
|
3623
|
+
if (normalizedTool.startsWith("skill.")) return tool2.slice("skill.".length) || null;
|
|
3624
|
+
if (normalizedTool !== "skill") return null;
|
|
3625
|
+
const parsedInput = typeof input === "string" ? safeJsonParse(input, null) : input;
|
|
3626
|
+
if (!isRecord(parsedInput)) return null;
|
|
3627
|
+
const skillName = parsedInput.skill ?? parsedInput.name;
|
|
3628
|
+
return typeof skillName === "string" && skillName ? skillName : null;
|
|
3629
|
+
}
|
|
3630
|
+
function createCallDisplayMessage(message) {
|
|
3631
|
+
const skillName = message.server.toLowerCase() === "skills" ? message.tool : parseSkillName(message.tool, message.input);
|
|
3632
|
+
if (skillName === null) return { ...message, type: "tool_call" };
|
|
3633
|
+
return {
|
|
3634
|
+
id: message.id,
|
|
3635
|
+
type: "skill",
|
|
3636
|
+
skillName,
|
|
3637
|
+
status: message.status,
|
|
3638
|
+
timestamp: message.timestamp
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3578
3641
|
|
|
3579
3642
|
// ../shared/src/user-message-matching.ts
|
|
3580
3643
|
function parseTimestampMs(timestamp) {
|
|
@@ -3656,15 +3719,6 @@ function parseAgentEventJsonlWithCodexAspTranscript(content, options = {}) {
|
|
|
3656
3719
|
return { events, transcript, transcriptsByThreadId };
|
|
3657
3720
|
}
|
|
3658
3721
|
|
|
3659
|
-
// ../shared/src/json.ts
|
|
3660
|
-
function safeJsonParse(str, fallback) {
|
|
3661
|
-
try {
|
|
3662
|
-
return JSON.parse(str);
|
|
3663
|
-
} catch {
|
|
3664
|
-
return fallback;
|
|
3665
|
-
}
|
|
3666
|
-
}
|
|
3667
|
-
|
|
3668
3722
|
// ../shared/src/display-message/parsers/codex-parser.ts
|
|
3669
3723
|
function getStatusFromExitCode(exitCode) {
|
|
3670
3724
|
return exitCode === 0 ? "completed" : "failed";
|
|
@@ -3831,17 +3885,17 @@ function parseCodexEvents(events) {
|
|
|
3831
3885
|
const operations = parsePatch(input);
|
|
3832
3886
|
pendingPatches.set(callId, { input, status, timestamp: event.timestamp, operations });
|
|
3833
3887
|
} else {
|
|
3834
|
-
const
|
|
3835
|
-
|
|
3836
|
-
|
|
3888
|
+
const id = `toolcall-${callId || getPayloadString(event, CODEX_ASP_ITEM_ID_PAYLOAD_KEY) || `${event.timestamp}-${eventIndex}`}`;
|
|
3889
|
+
const msg = createCallDisplayMessage({
|
|
3890
|
+
id,
|
|
3837
3891
|
server,
|
|
3838
3892
|
tool: name,
|
|
3839
3893
|
input,
|
|
3840
3894
|
status,
|
|
3841
3895
|
timestamp: event.timestamp
|
|
3842
|
-
};
|
|
3896
|
+
});
|
|
3843
3897
|
messages.push(msg);
|
|
3844
|
-
if (callId) {
|
|
3898
|
+
if (callId && msg.type === "tool_call") {
|
|
3845
3899
|
pendingToolCalls.set(callId, msg);
|
|
3846
3900
|
}
|
|
3847
3901
|
}
|
|
@@ -3925,10 +3979,10 @@ function getCursorThinkingText(event) {
|
|
|
3925
3979
|
function isTerminalStatus(status) {
|
|
3926
3980
|
return status === "FINISHED" || status === "ERROR" || status === "CANCELLED" || status === "EXPIRED";
|
|
3927
3981
|
}
|
|
3928
|
-
function
|
|
3929
|
-
for (const index of
|
|
3982
|
+
function finalizeOpenCalls(messages, callIndexes, status) {
|
|
3983
|
+
for (const index of callIndexes.values()) {
|
|
3930
3984
|
const message = messages[index];
|
|
3931
|
-
if (message?.type === "tool_call" && message.status === "in_progress") {
|
|
3985
|
+
if ((message?.type === "tool_call" || message?.type === "skill") && message.status === "in_progress") {
|
|
3932
3986
|
messages[index] = {
|
|
3933
3987
|
...message,
|
|
3934
3988
|
status
|
|
@@ -3938,7 +3992,7 @@ function finalizeOpenTools(messages, toolIndexes, status) {
|
|
|
3938
3992
|
}
|
|
3939
3993
|
function parseCursorEvents(events) {
|
|
3940
3994
|
const messages = [];
|
|
3941
|
-
const
|
|
3995
|
+
const callIndexes = /* @__PURE__ */ new Map();
|
|
3942
3996
|
const runAssistantSegments = /* @__PURE__ */ new Map();
|
|
3943
3997
|
const runThinkingSegments = /* @__PURE__ */ new Map();
|
|
3944
3998
|
let activeAssistant = null;
|
|
@@ -4030,21 +4084,20 @@ function parseCursorEvents(events) {
|
|
|
4030
4084
|
const tool2 = typeof event.payload.name === "string" ? event.payload.name : "tool";
|
|
4031
4085
|
const status = cursorStatusToDisplayStatus(event.payload.status);
|
|
4032
4086
|
const input = isRecord(event.payload.args) ? event.payload.args : typeof event.payload.args === "string" ? event.payload.args : void 0;
|
|
4033
|
-
const existing =
|
|
4034
|
-
const next = {
|
|
4087
|
+
const existing = callIndexes.get(callId);
|
|
4088
|
+
const next = createCallDisplayMessage({
|
|
4035
4089
|
id: callId,
|
|
4036
|
-
type: "tool_call",
|
|
4037
4090
|
server: "cursor",
|
|
4038
4091
|
tool: tool2,
|
|
4039
4092
|
...input !== void 0 ? { input } : {},
|
|
4040
4093
|
output: stringifyDisplayValue(event.payload.result),
|
|
4041
4094
|
status,
|
|
4042
4095
|
timestamp: event.timestamp
|
|
4043
|
-
};
|
|
4096
|
+
});
|
|
4044
4097
|
if (existing === void 0) {
|
|
4045
|
-
|
|
4098
|
+
callIndexes.set(callId, messages.push(next) - 1);
|
|
4046
4099
|
} else {
|
|
4047
|
-
messages[existing] =
|
|
4100
|
+
messages[existing] = next;
|
|
4048
4101
|
}
|
|
4049
4102
|
continue;
|
|
4050
4103
|
}
|
|
@@ -4066,7 +4119,7 @@ function parseCursorEvents(events) {
|
|
|
4066
4119
|
if (isTerminalStatus(status)) {
|
|
4067
4120
|
const displayStatus = cursorStatusToDisplayStatus(status);
|
|
4068
4121
|
finalizeActiveThinking(displayStatus);
|
|
4069
|
-
|
|
4122
|
+
finalizeOpenCalls(messages, callIndexes, displayStatus);
|
|
4070
4123
|
activeThinking = null;
|
|
4071
4124
|
activeAssistant = null;
|
|
4072
4125
|
}
|
|
@@ -4074,7 +4127,7 @@ function parseCursorEvents(events) {
|
|
|
4074
4127
|
}
|
|
4075
4128
|
if (event.type === "cursor-error") {
|
|
4076
4129
|
finalizeActiveThinking("failed");
|
|
4077
|
-
|
|
4130
|
+
finalizeOpenCalls(messages, callIndexes, "failed");
|
|
4078
4131
|
activeAssistant = null;
|
|
4079
4132
|
activeThinking = null;
|
|
4080
4133
|
const message = typeof event.payload.message === "string" ? event.payload.message : "Cursor run failed";
|
|
@@ -4099,14 +4152,6 @@ function partFromEvent(event) {
|
|
|
4099
4152
|
function toolStatus(status) {
|
|
4100
4153
|
return status === "completed" ? "completed" : status === "error" ? "failed" : "in_progress";
|
|
4101
4154
|
}
|
|
4102
|
-
function setById(messages, id, next) {
|
|
4103
|
-
const index = messages.findIndex((message) => message.id === id);
|
|
4104
|
-
if (index === -1) {
|
|
4105
|
-
messages.push(next);
|
|
4106
|
-
} else {
|
|
4107
|
-
messages[index] = next;
|
|
4108
|
-
}
|
|
4109
|
-
}
|
|
4110
4155
|
function assistantError(info) {
|
|
4111
4156
|
if (!isRecord(info) || !isRecord(info.error)) return null;
|
|
4112
4157
|
const data = isRecord(info.error.data) ? info.error.data : null;
|
|
@@ -4151,15 +4196,32 @@ function parseOpencodeEvents(events) {
|
|
|
4151
4196
|
});
|
|
4152
4197
|
continue;
|
|
4153
4198
|
}
|
|
4199
|
+
if (event.type === "opencode-session.next.tool.called") {
|
|
4200
|
+
const tool2 = typeof event.payload.tool === "string" ? event.payload.tool : "tool";
|
|
4201
|
+
const input = event.payload.input ?? event.payload.arguments;
|
|
4202
|
+
const callId = typeof event.payload.callID === "string" ? event.payload.callID : null;
|
|
4203
|
+
const id2 = callId ? `opencode-${callId}` : `opencode-${event.timestamp}-${messages.length}`;
|
|
4204
|
+
upsertDisplayMessage(messages, createCallDisplayMessage({
|
|
4205
|
+
id: id2,
|
|
4206
|
+
server: "opencode",
|
|
4207
|
+
tool: tool2,
|
|
4208
|
+
input: isRecord(input) ? input : stringifyDisplayValue(input),
|
|
4209
|
+
status: "in_progress",
|
|
4210
|
+
timestamp: event.timestamp
|
|
4211
|
+
}));
|
|
4212
|
+
continue;
|
|
4213
|
+
}
|
|
4154
4214
|
const part = partFromEvent(event);
|
|
4155
4215
|
if (!part) continue;
|
|
4156
|
-
|
|
4216
|
+
let id = `opencode-${event.timestamp}-${messages.length}`;
|
|
4217
|
+
if (typeof part.callID === "string") id = `opencode-${part.callID}`;
|
|
4218
|
+
else if (typeof part.id === "string") id = `opencode-${part.id}`;
|
|
4157
4219
|
const time = isRecord(part.time) ? part.time : null;
|
|
4158
4220
|
const timestamp = timestampFromMs(time?.start, event.timestamp);
|
|
4159
4221
|
if (part.type === "text") {
|
|
4160
4222
|
const text = typeof part.text === "string" ? part.text : "";
|
|
4161
4223
|
if (text.trim()) {
|
|
4162
|
-
|
|
4224
|
+
upsertDisplayMessage(messages, {
|
|
4163
4225
|
id,
|
|
4164
4226
|
type: "agent",
|
|
4165
4227
|
content: text,
|
|
@@ -4171,7 +4233,7 @@ function parseOpencodeEvents(events) {
|
|
|
4171
4233
|
if (part.type === "reasoning") {
|
|
4172
4234
|
const text = typeof part.text === "string" ? part.text : "";
|
|
4173
4235
|
if (text.trim()) {
|
|
4174
|
-
|
|
4236
|
+
upsertDisplayMessage(messages, {
|
|
4175
4237
|
id,
|
|
4176
4238
|
type: "reasoning",
|
|
4177
4239
|
content: text,
|
|
@@ -4184,23 +4246,24 @@ function parseOpencodeEvents(events) {
|
|
|
4184
4246
|
if (part.type === "tool" && isRecord(part.state)) {
|
|
4185
4247
|
const state = part.state;
|
|
4186
4248
|
const status = toolStatus(state.status);
|
|
4249
|
+
const tool2 = typeof part.tool === "string" ? part.tool : "tool";
|
|
4250
|
+
const input = state.input;
|
|
4187
4251
|
const output = state.status === "completed" ? stringifyDisplayValue(state.output) : state.status === "error" ? stringifyDisplayValue(state.error) : void 0;
|
|
4188
|
-
|
|
4252
|
+
upsertDisplayMessage(messages, createCallDisplayMessage({
|
|
4189
4253
|
id,
|
|
4190
|
-
type: "tool_call",
|
|
4191
4254
|
server: "opencode",
|
|
4192
|
-
tool:
|
|
4193
|
-
input: isRecord(
|
|
4255
|
+
tool: tool2,
|
|
4256
|
+
input: isRecord(input) ? input : stringifyDisplayValue(input),
|
|
4194
4257
|
output,
|
|
4195
4258
|
status,
|
|
4196
4259
|
timestamp
|
|
4197
|
-
});
|
|
4260
|
+
}));
|
|
4198
4261
|
continue;
|
|
4199
4262
|
}
|
|
4200
4263
|
if (part.type === "patch") {
|
|
4201
4264
|
const files = Array.isArray(part.files) ? part.files.filter((file) => typeof file === "string") : [];
|
|
4202
4265
|
if (files.length > 0) {
|
|
4203
|
-
|
|
4266
|
+
upsertDisplayMessage(messages, {
|
|
4204
4267
|
id,
|
|
4205
4268
|
type: "file_change",
|
|
4206
4269
|
changes: files.map((path6) => ({ path: path6, kind: "update" })),
|
|
@@ -4217,11 +4280,6 @@ function parseOpencodeEvents(events) {
|
|
|
4217
4280
|
function nestedPayload(event) {
|
|
4218
4281
|
return isRecord(event.payload.assistantMessageEvent) ? event.payload.assistantMessageEvent : event.payload;
|
|
4219
4282
|
}
|
|
4220
|
-
function setById2(messages, id, message) {
|
|
4221
|
-
const index = messages.findIndex((candidate) => candidate.id === id);
|
|
4222
|
-
if (index === -1) messages.push(message);
|
|
4223
|
-
else messages[index] = message;
|
|
4224
|
-
}
|
|
4225
4283
|
function toolStatus2(value) {
|
|
4226
4284
|
return value === "completed" || value === "success" ? "completed" : value === "error" ? "failed" : "in_progress";
|
|
4227
4285
|
}
|
|
@@ -4257,8 +4315,8 @@ function parsePiEvents(events) {
|
|
|
4257
4315
|
if (payload.type === "thinking_delta" && typeof payload.delta === "string") thinking.set(id, `${thinking.get(id) ?? ""}${payload.delta}`);
|
|
4258
4316
|
const textContent = text.get(id);
|
|
4259
4317
|
const thinkingContent = thinking.get(id);
|
|
4260
|
-
if (textContent !== void 0)
|
|
4261
|
-
if (thinkingContent !== void 0)
|
|
4318
|
+
if (textContent !== void 0) upsertDisplayMessage(messages, { id: `pi-${id}`, type: "agent", content: textContent, timestamp: event.timestamp });
|
|
4319
|
+
if (thinkingContent !== void 0) upsertDisplayMessage(messages, { id: `pi-thinking-${id}`, type: "reasoning", content: thinkingContent, status: "in_progress", timestamp: event.timestamp });
|
|
4262
4320
|
continue;
|
|
4263
4321
|
}
|
|
4264
4322
|
if (event.type === "pi-message_start") {
|
|
@@ -4270,16 +4328,18 @@ function parsePiEvents(events) {
|
|
|
4270
4328
|
const payload = event.payload;
|
|
4271
4329
|
const input = payload.args ?? payload.input;
|
|
4272
4330
|
const id = typeof payload.toolCallId === "string" ? payload.toolCallId : typeof payload.id === "string" ? payload.id : `tool-${event.timestamp}`;
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4331
|
+
const tool2 = typeof payload.toolName === "string" ? payload.toolName : "tool";
|
|
4332
|
+
const messageId = `pi-tool-${id}`;
|
|
4333
|
+
const status = event.type === "pi-tool_execution_end" ? toolStatus2(payload.isError ? "error" : "completed") : "in_progress";
|
|
4334
|
+
upsertDisplayMessage(messages, createCallDisplayMessage({
|
|
4335
|
+
id: messageId,
|
|
4276
4336
|
server: "pi",
|
|
4277
|
-
tool:
|
|
4337
|
+
tool: tool2,
|
|
4278
4338
|
input: isRecord(input) ? input : stringifyDisplayValue(input),
|
|
4279
4339
|
output: event.type === "pi-tool_execution_end" ? stringifyDisplayValue(payload.result ?? payload.error) : void 0,
|
|
4280
|
-
status
|
|
4340
|
+
status,
|
|
4281
4341
|
timestamp: event.timestamp
|
|
4282
|
-
});
|
|
4342
|
+
}));
|
|
4283
4343
|
}
|
|
4284
4344
|
}
|
|
4285
4345
|
return messages;
|
|
@@ -4443,14 +4503,6 @@ function isClaudeResultError(payload) {
|
|
|
4443
4503
|
if (payload.errors?.length && stripAgentDiagnosticErrors(payload.errors).length === 0) return false;
|
|
4444
4504
|
return Boolean(payload.is_error) || payload.subtype !== "success";
|
|
4445
4505
|
}
|
|
4446
|
-
function upsertDisplayMessage(messages, message) {
|
|
4447
|
-
const index = messages.findIndex((existing) => existing.id === message.id);
|
|
4448
|
-
if (index === -1) {
|
|
4449
|
-
messages.push(message);
|
|
4450
|
-
} else {
|
|
4451
|
-
messages[index] = message;
|
|
4452
|
-
}
|
|
4453
|
-
}
|
|
4454
4506
|
var LOCAL_COMMAND_ECHO_REGEX = /^<(?:command-name|command-message|local-command-stdout|local-command-stderr)>/;
|
|
4455
4507
|
function parseClaudeEvents(events, parentToolUseId) {
|
|
4456
4508
|
const messages = [];
|
|
@@ -4733,7 +4785,7 @@ function parseClaudeEvents(events, parentToolUseId) {
|
|
|
4733
4785
|
} else if (toolName === "Skill") {
|
|
4734
4786
|
const inputObj = typeof toolInput === "string" ? safeJsonParse(toolInput, {}) : toolInput;
|
|
4735
4787
|
messages.push({
|
|
4736
|
-
id: `skill-${
|
|
4788
|
+
id: `skill-${toolUseId}`,
|
|
4737
4789
|
type: "skill",
|
|
4738
4790
|
skillName: inputObj.skill || "unknown",
|
|
4739
4791
|
args: inputObj.args,
|
|
@@ -4942,8 +4994,196 @@ function parseClaudeEvents(events, parentToolUseId) {
|
|
|
4942
4994
|
|
|
4943
4995
|
// ../shared/src/display-message/parsers/codex-asp-parser.ts
|
|
4944
4996
|
var DUPLICATE_WINDOW_MS = 5 * 60 * 1e3;
|
|
4997
|
+
function nearTimestamp(a, b) {
|
|
4998
|
+
return Math.abs(parseTimestampMs(a) - parseTimestampMs(b)) <= DUPLICATE_WINDOW_MS;
|
|
4999
|
+
}
|
|
5000
|
+
function inputForDisplay(input) {
|
|
5001
|
+
if (input === void 0 || input === null) return void 0;
|
|
5002
|
+
if (typeof input === "string") return input;
|
|
5003
|
+
if (isRecord(input)) return input;
|
|
5004
|
+
return String(input);
|
|
5005
|
+
}
|
|
5006
|
+
function isInternalUserMessage(content) {
|
|
5007
|
+
const trimmed = content.trim();
|
|
5008
|
+
return trimmed.startsWith("<environment_context>") || trimmed.startsWith("<goal_context>");
|
|
5009
|
+
}
|
|
5010
|
+
function messageForItem(item) {
|
|
5011
|
+
if (item.type === "userMessage") {
|
|
5012
|
+
if (isInternalUserMessage(item.content)) return null;
|
|
5013
|
+
return {
|
|
5014
|
+
id: `user-${item.id}`,
|
|
5015
|
+
type: "user",
|
|
5016
|
+
content: item.content,
|
|
5017
|
+
images: item.images,
|
|
5018
|
+
timestamp: item.timestamp
|
|
5019
|
+
};
|
|
5020
|
+
}
|
|
5021
|
+
if (item.type === "agentMessage") {
|
|
5022
|
+
if (!item.text) return null;
|
|
5023
|
+
return {
|
|
5024
|
+
id: `agent-${item.id}`,
|
|
5025
|
+
type: "agent",
|
|
5026
|
+
content: item.text,
|
|
5027
|
+
timestamp: item.timestamp
|
|
5028
|
+
};
|
|
5029
|
+
}
|
|
5030
|
+
if (item.type === "reasoning") {
|
|
5031
|
+
if (!item.text || item.status === "completed") return null;
|
|
5032
|
+
return {
|
|
5033
|
+
id: `reasoning-${item.id}`,
|
|
5034
|
+
type: "reasoning",
|
|
5035
|
+
content: item.text,
|
|
5036
|
+
status: normalizeCodexAspTranscriptStatus(item.status),
|
|
5037
|
+
timestamp: item.timestamp
|
|
5038
|
+
};
|
|
5039
|
+
}
|
|
5040
|
+
if (item.type === "commandExecution") {
|
|
5041
|
+
return {
|
|
5042
|
+
id: `command-${item.id}`,
|
|
5043
|
+
type: "command",
|
|
5044
|
+
command: item.command,
|
|
5045
|
+
output: item.output,
|
|
5046
|
+
exitCode: item.exitCode ?? void 0,
|
|
5047
|
+
status: normalizeCodexAspTranscriptStatus(item.status),
|
|
5048
|
+
timestamp: item.timestamp
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
if (item.type === "fileChange") {
|
|
5052
|
+
return {
|
|
5053
|
+
id: `patch-${item.id}`,
|
|
5054
|
+
type: "patch",
|
|
5055
|
+
operations: item.operations,
|
|
5056
|
+
output: item.output,
|
|
5057
|
+
exitCode: item.exitCode ?? void 0,
|
|
5058
|
+
status: normalizeCodexAspTranscriptStatus(item.status),
|
|
5059
|
+
timestamp: item.timestamp
|
|
5060
|
+
};
|
|
5061
|
+
}
|
|
5062
|
+
if (item.type === "toolCall") {
|
|
5063
|
+
return createCallDisplayMessage({
|
|
5064
|
+
id: `toolcall-${item.id}`,
|
|
5065
|
+
server: item.server,
|
|
5066
|
+
tool: item.tool,
|
|
5067
|
+
input: inputForDisplay(item.input),
|
|
5068
|
+
output: item.output,
|
|
5069
|
+
status: normalizeCodexAspTranscriptStatus(item.status),
|
|
5070
|
+
timestamp: item.timestamp
|
|
5071
|
+
});
|
|
5072
|
+
}
|
|
5073
|
+
if (item.type === "subagent") {
|
|
5074
|
+
return {
|
|
5075
|
+
id: `subagent-${item.id}`,
|
|
5076
|
+
type: "subagent",
|
|
5077
|
+
toolUseId: item.id,
|
|
5078
|
+
description: item.description,
|
|
5079
|
+
prompt: item.prompt,
|
|
5080
|
+
subagentType: item.subagentType,
|
|
5081
|
+
...item.receiverThreadIds ? { receiverThreadIds: item.receiverThreadIds } : {},
|
|
5082
|
+
...item.model ? { model: item.model } : {},
|
|
5083
|
+
status: normalizeCodexAspTranscriptStatus(item.status),
|
|
5084
|
+
...item.output ? { output: item.output } : {},
|
|
5085
|
+
nestedEvents: [],
|
|
5086
|
+
timestamp: item.timestamp
|
|
5087
|
+
};
|
|
5088
|
+
}
|
|
5089
|
+
if (item.type === "webSearch") {
|
|
5090
|
+
return {
|
|
5091
|
+
id: `web-search-${item.id}`,
|
|
5092
|
+
type: "web_search",
|
|
5093
|
+
query: item.query,
|
|
5094
|
+
status: normalizeCodexAspTranscriptStatus(item.status),
|
|
5095
|
+
timestamp: item.timestamp
|
|
5096
|
+
};
|
|
5097
|
+
}
|
|
5098
|
+
if (item.type === "plan") {
|
|
5099
|
+
if (!item.text) return null;
|
|
5100
|
+
return {
|
|
5101
|
+
id: `todo-${item.id}`,
|
|
5102
|
+
type: "todo_list",
|
|
5103
|
+
items: item.text.split("\n").map((line) => line.trim()).filter(Boolean).map((text) => ({ text, completed: item.status === "completed" })),
|
|
5104
|
+
status: normalizeCodexAspTranscriptStatus(item.status),
|
|
5105
|
+
timestamp: item.timestamp
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
5108
|
+
if (item.type === "contextCompaction") {
|
|
5109
|
+
return {
|
|
5110
|
+
id: `reasoning-${item.id}`,
|
|
5111
|
+
type: "reasoning",
|
|
5112
|
+
content: "Context compacted",
|
|
5113
|
+
status: normalizeCodexAspTranscriptStatus(item.status),
|
|
5114
|
+
timestamp: item.timestamp
|
|
5115
|
+
};
|
|
5116
|
+
}
|
|
5117
|
+
return {
|
|
5118
|
+
id: `error-${item.id}`,
|
|
5119
|
+
type: "error",
|
|
5120
|
+
message: item.message,
|
|
5121
|
+
timestamp: item.timestamp
|
|
5122
|
+
};
|
|
5123
|
+
}
|
|
5124
|
+
function stableString(value) {
|
|
5125
|
+
if (value === void 0) return "";
|
|
5126
|
+
if (typeof value !== "object" || value === null) return String(value);
|
|
5127
|
+
try {
|
|
5128
|
+
return JSON.stringify(value, Object.keys(value).sort());
|
|
5129
|
+
} catch {
|
|
5130
|
+
return String(value);
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
function duplicateMessage(a, b) {
|
|
5134
|
+
if (a.id === b.id) return true;
|
|
5135
|
+
if (a.type !== b.type || !nearTimestamp(a.timestamp, b.timestamp)) {
|
|
5136
|
+
return false;
|
|
5137
|
+
}
|
|
5138
|
+
if (a.type === "user" && b.type === "user") return a.content === b.content;
|
|
5139
|
+
if (a.type === "agent" && b.type === "agent") return a.content === b.content;
|
|
5140
|
+
if (a.type === "reasoning" && b.type === "reasoning") return a.content === b.content;
|
|
5141
|
+
if (a.type === "command" && b.type === "command") return a.command === b.command;
|
|
5142
|
+
if (a.type === "web_search" && b.type === "web_search") return a.query === b.query;
|
|
5143
|
+
if (a.type === "subagent" && b.type === "subagent") {
|
|
5144
|
+
return a.description === b.description && a.prompt === b.prompt && a.subagentType === b.subagentType && a.model === b.model;
|
|
5145
|
+
}
|
|
5146
|
+
if (a.type === "todo_list" && b.type === "todo_list") {
|
|
5147
|
+
return stableString(a.items) === stableString(b.items);
|
|
5148
|
+
}
|
|
5149
|
+
if (a.type === "patch" && b.type === "patch") {
|
|
5150
|
+
return stableString(a.operations) === stableString(b.operations);
|
|
5151
|
+
}
|
|
5152
|
+
if (a.type === "tool_call" && b.type === "tool_call") {
|
|
5153
|
+
return a.server === b.server && a.tool === b.tool && stableString(a.input) === stableString(b.input);
|
|
5154
|
+
}
|
|
5155
|
+
return false;
|
|
5156
|
+
}
|
|
5157
|
+
function mergeCodexAspDisplayMessages(primary, supplemental) {
|
|
5158
|
+
const merged = [...primary];
|
|
5159
|
+
for (const message of supplemental) {
|
|
5160
|
+
const duplicateIndex = merged.findIndex((existing) => duplicateMessage(existing, message));
|
|
5161
|
+
if (duplicateIndex === -1) {
|
|
5162
|
+
merged.push(message);
|
|
5163
|
+
} else if (message.type === "user") {
|
|
5164
|
+
merged[duplicateIndex] = { ...merged[duplicateIndex], timestamp: message.timestamp };
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5167
|
+
return merged.map((message, index) => ({ message, index })).sort((a, b) => parseTimestampMs(a.message.timestamp) - parseTimestampMs(b.message.timestamp) || a.index - b.index).map(({ message }) => message);
|
|
5168
|
+
}
|
|
5169
|
+
function parseCodexAspTranscript(transcript) {
|
|
5170
|
+
const messages = [];
|
|
5171
|
+
const orderedItems = transcript.turns.map((turn, turnIndex) => ({ turn, turnIndex })).flatMap(({ turn, turnIndex }) => turn.items.map((item, itemIndex) => ({ item, turnIndex, itemIndex }))).sort((a, b) => {
|
|
5172
|
+
const aSequence = a.item.sequence ?? Number.MAX_SAFE_INTEGER;
|
|
5173
|
+
const bSequence = b.item.sequence ?? Number.MAX_SAFE_INTEGER;
|
|
5174
|
+
return aSequence - bSequence || parseTimestampMs(a.item.timestamp) - parseTimestampMs(b.item.timestamp) || a.turnIndex - b.turnIndex || a.itemIndex - b.itemIndex;
|
|
5175
|
+
});
|
|
5176
|
+
for (const { item } of orderedItems) {
|
|
5177
|
+
const message = messageForItem(item);
|
|
5178
|
+
if (message) {
|
|
5179
|
+
messages.push(message);
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5182
|
+
return messages;
|
|
5183
|
+
}
|
|
4945
5184
|
|
|
4946
5185
|
// ../shared/src/display-message/parsers/index.ts
|
|
5186
|
+
var INTERRUPTION_DEDUP_WINDOW_MS = 15e3;
|
|
4947
5187
|
function parseAgentEvents(events, agentType) {
|
|
4948
5188
|
if (agentType === "codex") {
|
|
4949
5189
|
return parseCodexEvents(events);
|
|
@@ -4959,11 +5199,82 @@ function parseAgentEvents(events, agentType) {
|
|
|
4959
5199
|
}
|
|
4960
5200
|
return parseClaudeEvents(events);
|
|
4961
5201
|
}
|
|
5202
|
+
function parseDisplayMessages(events, agentType, codexAspTranscript, options = {}) {
|
|
5203
|
+
const shouldFilter = options.filter ?? true;
|
|
5204
|
+
const parsedEvents = agentType === "claude" || agentType === "relay" ? parseClaudeEvents(events, options.parentToolUseId) : parseAgentEvents(events, agentType);
|
|
5205
|
+
const legacyMessages = shouldFilter ? filterDisplayMessages(parsedEvents, agentType) : parsedEvents;
|
|
5206
|
+
if (agentType !== "codex" || !codexAspTranscript) {
|
|
5207
|
+
return shouldFilter ? applyInterruptions(legacyMessages, events) : legacyMessages;
|
|
5208
|
+
}
|
|
5209
|
+
const nativeCodexMessages = shouldFilter ? filterDisplayMessages(parseCodexAspTranscript(codexAspTranscript), agentType) : parseCodexAspTranscript(codexAspTranscript);
|
|
5210
|
+
const merged = mergeCodexAspDisplayMessages(nativeCodexMessages, legacyMessages);
|
|
5211
|
+
return shouldFilter ? applyInterruptions(merged, events) : merged;
|
|
5212
|
+
}
|
|
5213
|
+
function applyInterruptions(messages, events) {
|
|
5214
|
+
const result = [...messages];
|
|
5215
|
+
for (const event of events) {
|
|
5216
|
+
if (event.type !== CHAT_INTERRUPTED_EVENT_TYPE) continue;
|
|
5217
|
+
const eventMs = parseTimestampMs(event.timestamp);
|
|
5218
|
+
let index = result.length;
|
|
5219
|
+
while (index > 0 && parseTimestampMs(result[index - 1].timestamp) > eventMs) index--;
|
|
5220
|
+
result.splice(index, 0, {
|
|
5221
|
+
id: `interruption-${event.timestamp}`,
|
|
5222
|
+
type: "interruption",
|
|
5223
|
+
timestamp: event.timestamp
|
|
5224
|
+
});
|
|
5225
|
+
}
|
|
5226
|
+
let lastUserMs = null;
|
|
5227
|
+
let lastInterruption = null;
|
|
5228
|
+
const finalized = [];
|
|
5229
|
+
for (const msg of result) {
|
|
5230
|
+
if (msg.type === "user") {
|
|
5231
|
+
lastUserMs = parseTimestampMs(msg.timestamp);
|
|
5232
|
+
lastInterruption = null;
|
|
5233
|
+
finalized.push(msg);
|
|
5234
|
+
continue;
|
|
5235
|
+
}
|
|
5236
|
+
if (msg.type !== "interruption") {
|
|
5237
|
+
finalized.push(msg);
|
|
5238
|
+
continue;
|
|
5239
|
+
}
|
|
5240
|
+
const ms = parseTimestampMs(msg.timestamp);
|
|
5241
|
+
if (lastInterruption && ms - parseTimestampMs(lastInterruption.timestamp) <= INTERRUPTION_DEDUP_WINDOW_MS) {
|
|
5242
|
+
continue;
|
|
5243
|
+
}
|
|
5244
|
+
lastInterruption = {
|
|
5245
|
+
...msg,
|
|
5246
|
+
...lastUserMs !== null && ms >= lastUserMs ? { durationMs: ms - lastUserMs } : {}
|
|
5247
|
+
};
|
|
5248
|
+
finalized.push(lastInterruption);
|
|
5249
|
+
}
|
|
5250
|
+
return finalized;
|
|
5251
|
+
}
|
|
5252
|
+
function isCodexInitializationPrompt(message) {
|
|
5253
|
+
return message.type === "user" && removeReplicasInstructions(message.content).trim() === "Hello";
|
|
5254
|
+
}
|
|
4962
5255
|
function isAgentBackendEvent(value) {
|
|
4963
5256
|
if (!value || typeof value !== "object") return false;
|
|
4964
5257
|
const candidate = value;
|
|
4965
5258
|
return typeof candidate.timestamp === "string" && typeof candidate.type === "string" && typeof candidate.payload === "object" && candidate.payload !== null;
|
|
4966
5259
|
}
|
|
5260
|
+
function filterDisplayMessages(messages, provider) {
|
|
5261
|
+
let result = messages;
|
|
5262
|
+
if (provider === "codex") {
|
|
5263
|
+
const userMessages = result.map((msg, index) => ({ msg, index })).filter(({ msg }) => msg.type === "user");
|
|
5264
|
+
if (userMessages.length >= 2 && isCodexInitializationPrompt(userMessages[0].msg)) {
|
|
5265
|
+
result = result.slice(userMessages[1].index);
|
|
5266
|
+
}
|
|
5267
|
+
}
|
|
5268
|
+
result = result.map((msg) => {
|
|
5269
|
+
if (msg.type !== "user") return msg;
|
|
5270
|
+
const cleaned = removeReplicasInstructions(msg.content).trim();
|
|
5271
|
+
if (INTERRUPTED_MESSAGE_REGEX.test(cleaned)) {
|
|
5272
|
+
return { id: msg.id, type: "interruption", timestamp: msg.timestamp };
|
|
5273
|
+
}
|
|
5274
|
+
return cleaned !== msg.content ? { ...msg, content: cleaned } : msg;
|
|
5275
|
+
});
|
|
5276
|
+
return result;
|
|
5277
|
+
}
|
|
4967
5278
|
|
|
4968
5279
|
// ../shared/src/onboarding-emails.ts
|
|
4969
5280
|
var EMAIL_KEYS = [
|
|
@@ -10297,7 +10608,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
|
|
|
10297
10608
|
var MIN_CODEX_CLI_VERSION = "0.144.6";
|
|
10298
10609
|
var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
|
|
10299
10610
|
var codexCliVersionEnsured = null;
|
|
10300
|
-
var ENGINE_PACKAGE_VERSION = "0.1.
|
|
10611
|
+
var ENGINE_PACKAGE_VERSION = "0.1.554";
|
|
10301
10612
|
var INITIALIZE_METHOD = "initialize";
|
|
10302
10613
|
var INITIALIZED_NOTIFICATION = "initialized";
|
|
10303
10614
|
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
@@ -14519,153 +14830,234 @@ var RelayManager = class {
|
|
|
14519
14830
|
}
|
|
14520
14831
|
};
|
|
14521
14832
|
|
|
14522
|
-
// src/
|
|
14833
|
+
// src/analytics/analytics.service.ts
|
|
14523
14834
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14835
|
+
|
|
14836
|
+
// src/analytics/analytics-buffer.service.ts
|
|
14524
14837
|
import { appendFile as appendFile3, mkdir as mkdir14, readFile as readFile12, readdir as readdir6, rename as rename2, unlink as unlink3 } from "fs/promises";
|
|
14525
14838
|
import { join as join22 } from "path";
|
|
14526
|
-
var LIVE_FILE = join22(ENGINE_DIR2, "turn-usage.jsonl");
|
|
14527
|
-
var SEGMENT_FILE_RE = /^turn-usage\.(\d+)\.jsonl$/;
|
|
14528
|
-
var UPLOADED_SUFFIX = ".uploaded";
|
|
14529
|
-
var MAX_TRACKED_MESSAGES = 500;
|
|
14530
14839
|
var MAX_FAILED_RECORDS = 500;
|
|
14531
14840
|
var FLUSH_DEBOUNCE_MS = 15e3;
|
|
14532
14841
|
var MAX_UPLOADED_SEGMENTS = 50;
|
|
14533
|
-
var
|
|
14534
|
-
var
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
14542
|
-
|
|
14543
|
-
|
|
14544
|
-
|
|
14545
|
-
|
|
14546
|
-
|
|
14547
|
-
|
|
14548
|
-
}
|
|
14549
|
-
|
|
14550
|
-
|
|
14551
|
-
|
|
14552
|
-
turnId: randomUUID5(),
|
|
14553
|
-
messageId,
|
|
14554
|
-
startedAtMs: Date.now(),
|
|
14555
|
-
provider,
|
|
14556
|
-
...credential ? { credentialMethod: credential.method, credentialScope: credential.scope } : {}
|
|
14557
|
-
});
|
|
14558
|
-
}
|
|
14559
|
-
function noteTurnEnded(chatId) {
|
|
14560
|
-
const turn = activeTurns.get(chatId);
|
|
14561
|
-
if (!turn) return;
|
|
14562
|
-
activeTurns.delete(chatId);
|
|
14563
|
-
recordTurn(chatId, turn);
|
|
14564
|
-
scheduleFlush();
|
|
14565
|
-
}
|
|
14566
|
-
function scheduleFlush() {
|
|
14567
|
-
if (flushTimer) return;
|
|
14568
|
-
flushTimer = setTimeout(() => {
|
|
14569
|
-
flushTimer = null;
|
|
14570
|
-
void flushAllTurnUsage(false).catch((err) => {
|
|
14571
|
-
console.error("[TurnUsage] Scheduled flush failed, retrying on the next turn:", err);
|
|
14842
|
+
var UPLOADED_SUFFIX = ".uploaded";
|
|
14843
|
+
var AnalyticsBufferService = class {
|
|
14844
|
+
constructor(options) {
|
|
14845
|
+
this.options = options;
|
|
14846
|
+
this.liveFile = join22(ENGINE_DIR2, `${options.name}.jsonl`);
|
|
14847
|
+
this.segmentFilePattern = new RegExp(`^${options.name}\\.(\\d+)\\.jsonl$`);
|
|
14848
|
+
}
|
|
14849
|
+
options;
|
|
14850
|
+
liveFile;
|
|
14851
|
+
segmentFilePattern;
|
|
14852
|
+
failedRecords = [];
|
|
14853
|
+
pendingAppends = /* @__PURE__ */ new Set();
|
|
14854
|
+
flushTimer = null;
|
|
14855
|
+
activeFlush = Promise.resolve({ flushed: 0, failed: 0 });
|
|
14856
|
+
append(record) {
|
|
14857
|
+
const pending = mkdir14(ENGINE_DIR2, { recursive: true }).then(() => appendFile3(this.liveFile, `${JSON.stringify(record)}
|
|
14858
|
+
`, "utf-8")).catch((error) => {
|
|
14859
|
+
console.error(`[${this.options.name}] Append failed, will retry on flush:`, error);
|
|
14860
|
+
if (this.failedRecords.length < MAX_FAILED_RECORDS) this.failedRecords.push(record);
|
|
14572
14861
|
});
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
}
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
|
|
14586
|
-
|
|
14587
|
-
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
}
|
|
14591
|
-
function writeRecord(record) {
|
|
14592
|
-
const pending = mkdir14(ENGINE_DIR2, { recursive: true }).then(() => appendFile3(LIVE_FILE, JSON.stringify(record) + "\n", "utf-8")).catch((err) => {
|
|
14593
|
-
console.error("[TurnUsage] Append failed, will retry on flush:", err);
|
|
14594
|
-
if (failedRecords.length < MAX_FAILED_RECORDS) failedRecords.push(record);
|
|
14595
|
-
});
|
|
14596
|
-
pendingAppends.add(pending);
|
|
14597
|
-
void pending.finally(() => pendingAppends.delete(pending));
|
|
14598
|
-
}
|
|
14599
|
-
function flushAllTurnUsage(finalizeInFlight = true) {
|
|
14600
|
-
if (flushTimer) {
|
|
14601
|
-
clearTimeout(flushTimer);
|
|
14602
|
-
flushTimer = null;
|
|
14603
|
-
}
|
|
14604
|
-
activeFlush = activeFlush.catch(() => {
|
|
14605
|
-
}).then(() => runFlush(finalizeInFlight));
|
|
14606
|
-
return activeFlush;
|
|
14607
|
-
}
|
|
14608
|
-
async function runFlush(finalizeInFlight) {
|
|
14609
|
-
for (const record of failedRecords.splice(0)) writeRecord(record);
|
|
14610
|
-
if (finalizeInFlight) {
|
|
14611
|
-
for (const [chatId, turn] of activeTurns) recordTurn(chatId, turn);
|
|
14612
|
-
activeTurns.clear();
|
|
14613
|
-
}
|
|
14614
|
-
while (pendingAppends.size > 0) await Promise.allSettled([...pendingAppends]);
|
|
14615
|
-
await rename2(LIVE_FILE, join22(ENGINE_DIR2, `turn-usage.${Date.now()}.jsonl`)).catch(() => {
|
|
14616
|
-
});
|
|
14617
|
-
const entries = await readdir6(ENGINE_DIR2).catch(() => []);
|
|
14618
|
-
let flushed = 0;
|
|
14619
|
-
let failed = 0;
|
|
14620
|
-
for (const entry of entries) {
|
|
14621
|
-
if (!SEGMENT_FILE_RE.test(entry)) continue;
|
|
14622
|
-
try {
|
|
14623
|
-
await uploadSegment(join22(ENGINE_DIR2, entry));
|
|
14624
|
-
flushed++;
|
|
14625
|
-
} catch (err) {
|
|
14626
|
-
failed++;
|
|
14627
|
-
console.error("[TurnUsage] Segment upload failed, retained for retry:", { entry, err });
|
|
14862
|
+
this.pendingAppends.add(pending);
|
|
14863
|
+
void pending.finally(() => this.pendingAppends.delete(pending));
|
|
14864
|
+
}
|
|
14865
|
+
scheduleFlush() {
|
|
14866
|
+
if (this.flushTimer) return;
|
|
14867
|
+
this.flushTimer = setTimeout(() => {
|
|
14868
|
+
this.flushTimer = null;
|
|
14869
|
+
void this.flush().catch((error) => {
|
|
14870
|
+
console.error(`[${this.options.name}] Scheduled flush failed, retrying on the next record:`, error);
|
|
14871
|
+
});
|
|
14872
|
+
}, FLUSH_DEBOUNCE_MS);
|
|
14873
|
+
this.flushTimer.unref?.();
|
|
14874
|
+
}
|
|
14875
|
+
flush() {
|
|
14876
|
+
if (this.flushTimer) {
|
|
14877
|
+
clearTimeout(this.flushTimer);
|
|
14878
|
+
this.flushTimer = null;
|
|
14628
14879
|
}
|
|
14880
|
+
this.activeFlush = this.activeFlush.catch(() => {
|
|
14881
|
+
}).then(() => this.runFlush());
|
|
14882
|
+
return this.activeFlush;
|
|
14629
14883
|
}
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14884
|
+
async runFlush() {
|
|
14885
|
+
for (const record of this.failedRecords.splice(0)) this.append(record);
|
|
14886
|
+
while (this.pendingAppends.size > 0) await Promise.allSettled([...this.pendingAppends]);
|
|
14887
|
+
await rename2(this.liveFile, join22(ENGINE_DIR2, `${this.options.name}.${Date.now()}.jsonl`)).catch(() => {
|
|
14888
|
+
});
|
|
14889
|
+
const entries = await readdir6(ENGINE_DIR2).catch(() => []);
|
|
14890
|
+
let flushed = 0;
|
|
14891
|
+
let failed = 0;
|
|
14892
|
+
for (const entry of entries) {
|
|
14893
|
+
if (!this.segmentFilePattern.test(entry)) continue;
|
|
14894
|
+
try {
|
|
14895
|
+
await this.uploadSegment(join22(ENGINE_DIR2, entry));
|
|
14896
|
+
flushed++;
|
|
14897
|
+
} catch (error) {
|
|
14898
|
+
failed++;
|
|
14899
|
+
console.error(`[${this.options.name}] Segment upload failed, retained for retry:`, { entry, error });
|
|
14900
|
+
}
|
|
14639
14901
|
}
|
|
14902
|
+
const failedRecords = this.failedRecords.splice(0);
|
|
14903
|
+
if (failedRecords.length > 0) {
|
|
14904
|
+
try {
|
|
14905
|
+
await this.options.upload(failedRecords);
|
|
14906
|
+
flushed++;
|
|
14907
|
+
} catch (error) {
|
|
14908
|
+
failed++;
|
|
14909
|
+
this.failedRecords.unshift(...failedRecords.slice(0, MAX_FAILED_RECORDS));
|
|
14910
|
+
console.error(`[${this.options.name}] Failed records upload failed, retained for retry:`, error);
|
|
14911
|
+
}
|
|
14912
|
+
}
|
|
14913
|
+
const uploaded = entries.filter((entry) => entry.startsWith(`${this.options.name}.`) && entry.endsWith(UPLOADED_SUFFIX)).sort();
|
|
14914
|
+
for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
|
|
14915
|
+
await unlink3(join22(ENGINE_DIR2, entry)).catch(() => {
|
|
14916
|
+
});
|
|
14917
|
+
}
|
|
14918
|
+
return { flushed, failed };
|
|
14640
14919
|
}
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14920
|
+
async uploadSegment(filePath) {
|
|
14921
|
+
const records = (await readFile12(filePath, "utf-8")).split("\n").flatMap((line) => {
|
|
14922
|
+
try {
|
|
14923
|
+
const parsed = JSON.parse(line);
|
|
14924
|
+
return this.options.validate(parsed) ? [parsed] : [];
|
|
14925
|
+
} catch {
|
|
14926
|
+
return [];
|
|
14927
|
+
}
|
|
14928
|
+
});
|
|
14929
|
+
if (records.length > 0) await this.options.upload(records);
|
|
14930
|
+
await rename2(filePath, `${filePath}${UPLOADED_SUFFIX}`).catch(() => {
|
|
14644
14931
|
});
|
|
14645
14932
|
}
|
|
14646
|
-
|
|
14933
|
+
};
|
|
14934
|
+
|
|
14935
|
+
// src/analytics/extract-skill-mcp-calls.ts
|
|
14936
|
+
var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "opencode", "pi", "custom", "dynamic"]);
|
|
14937
|
+
function mcpNameFromToolCall(message) {
|
|
14938
|
+
const parsedName = parseMcpToolName(message.tool);
|
|
14939
|
+
if (parsedName) return parsedName.server;
|
|
14940
|
+
if (message.tool.startsWith("mcp.")) {
|
|
14941
|
+
const [server, ...toolParts] = message.tool.slice("mcp.".length).split(".");
|
|
14942
|
+
if (server && toolParts.length > 0) return server;
|
|
14943
|
+
}
|
|
14944
|
+
return NON_MCP_SERVERS.has(message.server) ? null : message.server;
|
|
14945
|
+
}
|
|
14946
|
+
function extractSkillMcpCalls(messages) {
|
|
14947
|
+
return messages.flatMap((message) => {
|
|
14948
|
+
if (message.type === "skill") {
|
|
14949
|
+
return [{ kind: "skill", id: message.id, skillName: message.skillName, occurredAt: message.timestamp }];
|
|
14950
|
+
}
|
|
14951
|
+
if (message.type !== "tool_call") return [];
|
|
14952
|
+
const mcpName = mcpNameFromToolCall(message);
|
|
14953
|
+
return mcpName ? [{ kind: "mcp", id: message.id, mcpName, occurredAt: message.timestamp }] : [];
|
|
14954
|
+
});
|
|
14647
14955
|
}
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14956
|
+
|
|
14957
|
+
// src/analytics/analytics.service.ts
|
|
14958
|
+
var MAX_TRACKED_MESSAGES = 500;
|
|
14959
|
+
async function uploadAnalyticsRecords(endpoint, body) {
|
|
14960
|
+
const response = await monolithRequest(endpoint, { body });
|
|
14961
|
+
if (!response.ok) throw new Error(`upload failed: ${response.status} ${await response.text()}`);
|
|
14962
|
+
}
|
|
14963
|
+
var AnalyticsService = class {
|
|
14964
|
+
pendingMessages = /* @__PURE__ */ new Map();
|
|
14965
|
+
activeTurns = /* @__PURE__ */ new Map();
|
|
14966
|
+
turnBuffer = new AnalyticsBufferService({
|
|
14967
|
+
name: "turn-usage",
|
|
14968
|
+
validate: isChatTurnUsageRecord,
|
|
14969
|
+
upload: (turns) => uploadAnalyticsRecords("/v1/engine/chat-turn-usage", { turns })
|
|
14656
14970
|
});
|
|
14657
|
-
|
|
14658
|
-
|
|
14971
|
+
skillBuffer = new AnalyticsBufferService({
|
|
14972
|
+
name: "skill-usage",
|
|
14973
|
+
validate: isSkillUsageRecord,
|
|
14974
|
+
upload: (skills) => uploadAnalyticsRecords("/v1/engine/skill-usage", { skills })
|
|
14659
14975
|
});
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14976
|
+
mcpBuffer = new AnalyticsBufferService({
|
|
14977
|
+
name: "mcp-usage",
|
|
14978
|
+
validate: isMcpUsageRecord,
|
|
14979
|
+
upload: (mcps) => uploadAnalyticsRecords("/v1/engine/mcp-usage", { mcps })
|
|
14980
|
+
});
|
|
14981
|
+
noteMessageAccepted(messageId, request) {
|
|
14982
|
+
this.pendingMessages.set(messageId, {
|
|
14983
|
+
model: request.model,
|
|
14984
|
+
senderUserId: request.senderUserId
|
|
14985
|
+
});
|
|
14986
|
+
for (const key of this.pendingMessages.keys()) {
|
|
14987
|
+
if (this.pendingMessages.size <= MAX_TRACKED_MESSAGES) break;
|
|
14988
|
+
this.pendingMessages.delete(key);
|
|
14989
|
+
}
|
|
14990
|
+
}
|
|
14991
|
+
noteTurnStarted(chatId, messageId, provider) {
|
|
14992
|
+
const attributes = this.pendingMessages.get(messageId) ?? {};
|
|
14993
|
+
const credential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[provider];
|
|
14994
|
+
this.pendingMessages.delete(messageId);
|
|
14995
|
+
this.activeTurns.set(chatId, {
|
|
14996
|
+
turnId: randomUUID5(),
|
|
14997
|
+
startedAtMs: Date.now(),
|
|
14998
|
+
provider,
|
|
14999
|
+
model: attributes.model ?? getDefaultAgentModel(provider),
|
|
15000
|
+
senderUserId: attributes.senderUserId,
|
|
15001
|
+
credentialMethod: credential?.method,
|
|
15002
|
+
credentialScope: credential?.scope,
|
|
15003
|
+
skills: [],
|
|
15004
|
+
mcps: [],
|
|
15005
|
+
seenCallIds: /* @__PURE__ */ new Set()
|
|
15006
|
+
});
|
|
14667
15007
|
}
|
|
14668
|
-
|
|
15008
|
+
noteSkillMcpCalls(chatId, messages) {
|
|
15009
|
+
const turn = this.activeTurns.get(chatId);
|
|
15010
|
+
if (!turn) return;
|
|
15011
|
+
for (const call of extractSkillMcpCalls(messages)) {
|
|
15012
|
+
if (turn.seenCallIds.has(call.id)) continue;
|
|
15013
|
+
turn.seenCallIds.add(call.id);
|
|
15014
|
+
const common = {
|
|
15015
|
+
callId: call.id,
|
|
15016
|
+
chatId,
|
|
15017
|
+
provider: turn.provider,
|
|
15018
|
+
model: turn.model,
|
|
15019
|
+
senderUserId: turn.senderUserId,
|
|
15020
|
+
occurredAt: call.occurredAt,
|
|
15021
|
+
credentialMethod: turn.credentialMethod,
|
|
15022
|
+
credentialScope: turn.credentialScope
|
|
15023
|
+
};
|
|
15024
|
+
if (call.kind === "skill") turn.skills.push({ ...common, skillName: call.skillName });
|
|
15025
|
+
else turn.mcps.push({ ...common, mcpName: call.mcpName });
|
|
15026
|
+
}
|
|
15027
|
+
}
|
|
15028
|
+
noteTurnEnded(chatId) {
|
|
15029
|
+
const turn = this.activeTurns.get(chatId);
|
|
15030
|
+
if (!turn) return;
|
|
15031
|
+
this.activeTurns.delete(chatId);
|
|
15032
|
+
this.turnBuffer.append({
|
|
15033
|
+
turnId: turn.turnId,
|
|
15034
|
+
chatId,
|
|
15035
|
+
provider: turn.provider,
|
|
15036
|
+
model: turn.model,
|
|
15037
|
+
senderUserId: turn.senderUserId,
|
|
15038
|
+
credentialMethod: turn.credentialMethod,
|
|
15039
|
+
credentialScope: turn.credentialScope,
|
|
15040
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15041
|
+
seconds: Math.round((Date.now() - turn.startedAtMs) / 1e3)
|
|
15042
|
+
});
|
|
15043
|
+
for (const skill of turn.skills) this.skillBuffer.append(skill);
|
|
15044
|
+
for (const mcp of turn.mcps) this.mcpBuffer.append(mcp);
|
|
15045
|
+
this.turnBuffer.scheduleFlush();
|
|
15046
|
+
if (turn.skills.length > 0) this.skillBuffer.scheduleFlush();
|
|
15047
|
+
if (turn.mcps.length > 0) this.mcpBuffer.scheduleFlush();
|
|
15048
|
+
}
|
|
15049
|
+
async flush(finalizeInFlight = true) {
|
|
15050
|
+
if (finalizeInFlight) {
|
|
15051
|
+
for (const chatId of this.activeTurns.keys()) this.noteTurnEnded(chatId);
|
|
15052
|
+
}
|
|
15053
|
+
const [turnUsage, skillUsage, mcpUsage] = await Promise.all([
|
|
15054
|
+
this.turnBuffer.flush(),
|
|
15055
|
+
this.skillBuffer.flush(),
|
|
15056
|
+
this.mcpBuffer.flush()
|
|
15057
|
+
]);
|
|
15058
|
+
return { turnUsage, skillUsage, mcpUsage };
|
|
15059
|
+
}
|
|
15060
|
+
};
|
|
14669
15061
|
|
|
14670
15062
|
// src/services/keep-alive-service.ts
|
|
14671
15063
|
var KeepAliveService = class _KeepAliveService {
|
|
@@ -15217,15 +15609,9 @@ function getCodexTranscriptUserMessages(transcript) {
|
|
|
15217
15609
|
}
|
|
15218
15610
|
function getCodexTranscriptFromEvent(event) {
|
|
15219
15611
|
if (event.type !== CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE) return null;
|
|
15220
|
-
|
|
15221
|
-
if (
|
|
15222
|
-
|
|
15223
|
-
}
|
|
15224
|
-
const transcriptDelta = event.payload.transcriptDelta;
|
|
15225
|
-
if (!isCodexAspTranscriptDelta(transcriptDelta)) {
|
|
15226
|
-
return null;
|
|
15227
|
-
}
|
|
15228
|
-
return applyCodexAspTranscriptDelta(null, transcriptDelta);
|
|
15612
|
+
if (isCodexAspTranscript(event.payload.transcript)) return event.payload.transcript;
|
|
15613
|
+
if (!isCodexAspTranscriptDelta(event.payload.transcriptDelta)) return null;
|
|
15614
|
+
return applyCodexAspTranscriptDelta(null, event.payload.transcriptDelta);
|
|
15229
15615
|
}
|
|
15230
15616
|
function terminalErrorsFromEvent(event, provider, codexTranscript) {
|
|
15231
15617
|
if (event.type === "claude-result") {
|
|
@@ -15296,13 +15682,15 @@ function corruptChatsFilePath() {
|
|
|
15296
15682
|
return `${CHATS_FILE}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
15297
15683
|
}
|
|
15298
15684
|
var ChatService = class {
|
|
15299
|
-
constructor(workingDirectory) {
|
|
15685
|
+
constructor(workingDirectory, analyticsService2) {
|
|
15300
15686
|
this.workingDirectory = workingDirectory;
|
|
15687
|
+
this.analyticsService = analyticsService2;
|
|
15301
15688
|
keepAliveService.setActivityCheck(
|
|
15302
15689
|
() => [...this.chats.values()].some((chat) => !chat.persisted.deletedAt && isChatActive(chat))
|
|
15303
15690
|
);
|
|
15304
15691
|
}
|
|
15305
15692
|
workingDirectory;
|
|
15693
|
+
analyticsService;
|
|
15306
15694
|
chats = /* @__PURE__ */ new Map();
|
|
15307
15695
|
persistInFlight = false;
|
|
15308
15696
|
persistQueued = false;
|
|
@@ -15424,7 +15812,7 @@ var ChatService = class {
|
|
|
15424
15812
|
request.images
|
|
15425
15813
|
);
|
|
15426
15814
|
chat.pendingMessageIds.push(result.messageId);
|
|
15427
|
-
noteMessageAccepted(result.messageId, request);
|
|
15815
|
+
this.analyticsService.noteMessageAccepted(result.messageId, request);
|
|
15428
15816
|
if (request.errorNotificationTarget) {
|
|
15429
15817
|
chat.errorNotificationTargets.set(result.messageId, request.errorNotificationTarget);
|
|
15430
15818
|
}
|
|
@@ -15480,7 +15868,7 @@ var ChatService = class {
|
|
|
15480
15868
|
async interrupt(chatId) {
|
|
15481
15869
|
const chat = this.requireChat(chatId);
|
|
15482
15870
|
const result = await chat.provider.interrupt();
|
|
15483
|
-
noteTurnEnded(chatId);
|
|
15871
|
+
this.analyticsService.noteTurnEnded(chatId);
|
|
15484
15872
|
chat.hasActiveTurn = false;
|
|
15485
15873
|
chat.activeMessageId = null;
|
|
15486
15874
|
chat.pendingMessageIds = [];
|
|
@@ -15502,7 +15890,7 @@ var ChatService = class {
|
|
|
15502
15890
|
return { interrupted: false, queue: [], goal: null };
|
|
15503
15891
|
}
|
|
15504
15892
|
const interruptResult = await chat.provider.interrupt();
|
|
15505
|
-
noteTurnEnded(chatId);
|
|
15893
|
+
this.analyticsService.noteTurnEnded(chatId);
|
|
15506
15894
|
chat.hasActiveTurn = false;
|
|
15507
15895
|
chat.activeMessageId = null;
|
|
15508
15896
|
chat.pendingMessageIds = [];
|
|
@@ -15692,14 +16080,14 @@ var ChatService = class {
|
|
|
15692
16080
|
const chatsById = new Map(
|
|
15693
16081
|
[...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
|
|
15694
16082
|
);
|
|
15695
|
-
const [chatTranscripts, canvas, repoState, engineLogs,
|
|
16083
|
+
const [chatTranscripts, canvas, repoState, engineLogs, analytics] = await Promise.all([
|
|
15696
16084
|
flushAllChatTranscripts(chatsById),
|
|
15697
16085
|
flushAllCanvasItems(),
|
|
15698
16086
|
flushRepoState(),
|
|
15699
16087
|
flushAllEngineLogs(),
|
|
15700
|
-
|
|
16088
|
+
this.analyticsService.flush()
|
|
15701
16089
|
]);
|
|
15702
|
-
return { chatTranscripts, canvas, repoState, engineLogs,
|
|
16090
|
+
return { chatTranscripts, canvas, repoState, engineLogs, ...analytics };
|
|
15703
16091
|
}
|
|
15704
16092
|
createRuntimeChat(persisted) {
|
|
15705
16093
|
const saveSession = async (sessionId) => {
|
|
@@ -15839,7 +16227,7 @@ var ChatService = class {
|
|
|
15839
16227
|
}
|
|
15840
16228
|
chat.hasActiveTurn = true;
|
|
15841
16229
|
chat.activeMessageId = messageId;
|
|
15842
|
-
noteTurnStarted(chat.persisted.id, messageId, chat.persisted.provider);
|
|
16230
|
+
this.analyticsService.noteTurnStarted(chat.persisted.id, messageId, chat.persisted.provider);
|
|
15843
16231
|
chat.activeErrorNotificationTarget = chat.errorNotificationTargets.get(messageId) ?? null;
|
|
15844
16232
|
chat.errorNotificationTargets.delete(messageId);
|
|
15845
16233
|
this.publish({
|
|
@@ -15852,6 +16240,18 @@ var ChatService = class {
|
|
|
15852
16240
|
});
|
|
15853
16241
|
}
|
|
15854
16242
|
const codexTranscript = getCodexTranscriptFromEvent(event);
|
|
16243
|
+
if (chat.hasActiveTurn) {
|
|
16244
|
+
const displayMessages = parseDisplayMessages(
|
|
16245
|
+
codexTranscript ? [] : [event],
|
|
16246
|
+
chat.persisted.provider,
|
|
16247
|
+
codexTranscript,
|
|
16248
|
+
{
|
|
16249
|
+
filter: false,
|
|
16250
|
+
parentToolUseId: typeof event.payload.parent_tool_use_id === "string" ? event.payload.parent_tool_use_id : null
|
|
16251
|
+
}
|
|
16252
|
+
);
|
|
16253
|
+
this.analyticsService.noteSkillMcpCalls(chatId, displayMessages);
|
|
16254
|
+
}
|
|
15855
16255
|
const terminalErrors = terminalErrorsFromEvent(event, chat.persisted.provider, codexTranscript);
|
|
15856
16256
|
if (terminalErrors !== void 0) {
|
|
15857
16257
|
chat.lastTurnErrors = terminalErrors;
|
|
@@ -15914,7 +16314,7 @@ var ChatService = class {
|
|
|
15914
16314
|
}
|
|
15915
16315
|
chat.hasActiveTurn = false;
|
|
15916
16316
|
chat.activeMessageId = null;
|
|
15917
|
-
noteTurnEnded(chatId);
|
|
16317
|
+
this.analyticsService.noteTurnEnded(chatId);
|
|
15918
16318
|
this.publish({
|
|
15919
16319
|
type: "chat.turn.completed",
|
|
15920
16320
|
payload: {
|
|
@@ -17912,7 +18312,8 @@ var authMiddleware = async (c, next) => {
|
|
|
17912
18312
|
}
|
|
17913
18313
|
await next();
|
|
17914
18314
|
};
|
|
17915
|
-
var
|
|
18315
|
+
var analyticsService = new AnalyticsService();
|
|
18316
|
+
var chatService = new ChatService(gitService.getWorkspaceRoot(), analyticsService);
|
|
17916
18317
|
app.get("/health", async (c) => {
|
|
17917
18318
|
const requestedWaitMs = Number(c.req.query(ENGINE_HEALTH_WAIT_QUERY_PARAM));
|
|
17918
18319
|
const waitMs = Number.isFinite(requestedWaitMs) ? Math.min(Math.max(requestedWaitMs, 0), ENGINE_HEALTH_MAX_WAIT_MS) : 0;
|