replicas-engine 0.1.608 → 0.1.610

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 CHANGED
@@ -134,6 +134,8 @@ Outgoing endpoints:
134
134
  - `POST /v1/engine/claude/refresh-credentials`
135
135
  - `POST /v1/engine/codex/refresh-credentials`
136
136
  - `POST /v1/engine/infisical/refresh-token`
137
- - `POST /v1/engine/chat-turn-usage`
138
- - `POST /v1/engine/skill-usage`
139
- - `POST /v1/engine/mcp-usage`
137
+ - `POST /v1/engine/chat-turn-activity`
138
+ - `POST /v1/engine/skill-activity`
139
+ - `POST /v1/engine/mcp-activity`
140
+
141
+ The previous `chat-turn-usage`, `skill-usage`, and `mcp-usage` paths remain accepted for older engine versions.
package/dist/src/index.js CHANGED
@@ -263,7 +263,7 @@ var AGENT_CREDENTIAL_METHODS_IN_ORDER = {
263
263
  };
264
264
 
265
265
  // ../shared/src/analytics/types.ts
266
- function isUsageRecord(value) {
266
+ function isAgentChatActivityRecord(value) {
267
267
  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";
268
268
  }
269
269
  function isAuthMethod(value) {
@@ -272,22 +272,22 @@ function isAuthMethod(value) {
272
272
  function isCredentialScope(value) {
273
273
  return typeof value === "string" && CREDENTIAL_SCOPES.some((scope) => scope === value);
274
274
  }
275
- function isChatTurnUsageRecord(value) {
276
- return isUsageRecord(value) && typeof value.turnId === "string" && typeof value.seconds === "number" && Number.isFinite(value.seconds) && value.seconds >= 0;
275
+ function isAgentChatTurnActivityRecord(value) {
276
+ return isAgentChatActivityRecord(value) && typeof value.turnId === "string" && typeof value.seconds === "number" && Number.isFinite(value.seconds) && value.seconds >= 0;
277
277
  }
278
- function isCallUsageRecord(value) {
279
- return isUsageRecord(value) && typeof value.callId === "string" && value.callId.length > 0;
278
+ function isAgentChatActivityCallRecord(value) {
279
+ return isAgentChatActivityRecord(value) && typeof value.callId === "string" && value.callId.length > 0;
280
280
  }
281
- function isNamedCallUsageRecord(value, field) {
282
- if (!isCallUsageRecord(value)) return false;
281
+ function isNamedAgentChatActivityRecord(value, field) {
282
+ if (!isAgentChatActivityCallRecord(value)) return false;
283
283
  const name = value[field];
284
284
  return typeof name === "string" && name.length > 0;
285
285
  }
286
- function isSkillUsageRecord(value) {
287
- return isNamedCallUsageRecord(value, "skillName");
286
+ function isAgentChatSkillActivityRecord(value) {
287
+ return isNamedAgentChatActivityRecord(value, "skillName");
288
288
  }
289
- function isMcpUsageRecord(value) {
290
- return isNamedCallUsageRecord(value, "mcpName");
289
+ function isAgentChatMcpActivityRecord(value) {
290
+ return isNamedAgentChatActivityRecord(value, "mcpName");
291
291
  }
292
292
 
293
293
  // ../shared/src/aster.ts
@@ -7594,13 +7594,13 @@ import { mkdir as mkdir7 } from "fs/promises";
7594
7594
  import { homedir as homedir9 } from "os";
7595
7595
  import { join as join12 } from "path";
7596
7596
  import { randomUUID } from "crypto";
7597
- var ENGINE_DIR = join12(homedir9(), ".replicas", "engine");
7598
- var EVENTS_FILE = join12(ENGINE_DIR, "events.jsonl");
7597
+ var ENGINE_DIR2 = join12(homedir9(), ".replicas", "engine");
7598
+ var EVENTS_FILE = join12(ENGINE_DIR2, "events.jsonl");
7599
7599
  var EventService = class {
7600
7600
  subscribers = /* @__PURE__ */ new Map();
7601
7601
  writer = new StreamWriter();
7602
7602
  async initialize() {
7603
- await mkdir7(ENGINE_DIR, { recursive: true });
7603
+ await mkdir7(ENGINE_DIR2, { recursive: true });
7604
7604
  this.writer.open(EVENTS_FILE);
7605
7605
  }
7606
7606
  subscribe(subscriber) {
@@ -10670,7 +10670,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
10670
10670
  var MIN_CODEX_CLI_VERSION = "0.144.6";
10671
10671
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
10672
10672
  var codexCliVersionEnsured = null;
10673
- var ENGINE_PACKAGE_VERSION = "0.1.608";
10673
+ var ENGINE_PACKAGE_VERSION = "0.1.610";
10674
10674
  var INITIALIZE_METHOD = "initialize";
10675
10675
  var INITIALIZED_NOTIFICATION = "initialized";
10676
10676
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -11599,14 +11599,14 @@ var TranscriptUpdateCoalescer = class {
11599
11599
  // src/services/chat/history-paths.ts
11600
11600
  import { homedir as homedir13 } from "os";
11601
11601
  import { join as join17 } from "path";
11602
- var ENGINE_DIR2 = join17(homedir13(), ".replicas", "engine");
11603
- var CHATS_FILE = join17(ENGINE_DIR2, "chats.json");
11604
- var CLAUDE_HISTORY_DIR = join17(ENGINE_DIR2, "claude-histories");
11605
- var RELAY_HISTORY_DIR = join17(ENGINE_DIR2, "relay-histories");
11606
- var CODEX_HISTORY_DIR = join17(ENGINE_DIR2, "codex-histories");
11607
- var CURSOR_HISTORY_DIR = join17(ENGINE_DIR2, "cursor-histories");
11608
- var OPENCODE_HISTORY_DIR = join17(ENGINE_DIR2, "opencode-histories");
11609
- var PI_HISTORY_DIR = join17(ENGINE_DIR2, "pi-histories");
11602
+ var ENGINE_DIR3 = join17(homedir13(), ".replicas", "engine");
11603
+ var CHATS_FILE = join17(ENGINE_DIR3, "chats.json");
11604
+ var CLAUDE_HISTORY_DIR = join17(ENGINE_DIR3, "claude-histories");
11605
+ var RELAY_HISTORY_DIR = join17(ENGINE_DIR3, "relay-histories");
11606
+ var CODEX_HISTORY_DIR = join17(ENGINE_DIR3, "codex-histories");
11607
+ var CURSOR_HISTORY_DIR = join17(ENGINE_DIR3, "cursor-histories");
11608
+ var OPENCODE_HISTORY_DIR = join17(ENGINE_DIR3, "opencode-histories");
11609
+ var PI_HISTORY_DIR = join17(ENGINE_DIR3, "pi-histories");
11610
11610
  var HISTORY_DIR_BY_PROVIDER = {
11611
11611
  claude: CLAUDE_HISTORY_DIR,
11612
11612
  relay: RELAY_HISTORY_DIR,
@@ -14966,34 +14966,93 @@ var RelayManager = class {
14966
14966
  }
14967
14967
  };
14968
14968
 
14969
- // src/analytics/analytics.service.ts
14969
+ // src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
14970
+ import {
14971
+ appendFile as appendFile3,
14972
+ mkdir as mkdir14,
14973
+ readFile as readFile12,
14974
+ readdir as readdir6,
14975
+ rename as rename2,
14976
+ unlink as unlink3
14977
+ } from "fs/promises";
14978
+ import { join as join22 } from "path";
14970
14979
  import { randomUUID as randomUUID5 } from "crypto";
14971
14980
 
14972
- // src/analytics/analytics-buffer.service.ts
14973
- import { appendFile as appendFile3, mkdir as mkdir14, readFile as readFile12, readdir as readdir6, rename as rename2, unlink as unlink3 } from "fs/promises";
14974
- import { join as join22 } from "path";
14981
+ // src/analytics/agent/activity/skill-mcp-call-extractor.ts
14982
+ var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "opencode", "pi", "custom", "dynamic"]);
14983
+ function mcpNameFromToolCall(message) {
14984
+ const parsedName = parseMcpToolName(message.tool);
14985
+ if (parsedName) return parsedName.server;
14986
+ if (message.tool.startsWith("mcp.")) {
14987
+ const [server, ...toolParts] = message.tool.slice("mcp.".length).split(".");
14988
+ if (server && toolParts.length > 0) return server;
14989
+ }
14990
+ return NON_MCP_SERVERS.has(message.server) ? null : message.server;
14991
+ }
14992
+ function skillCallsFromCommand(message) {
14993
+ const skillNames = message.skillNames ?? [];
14994
+ return skillNames.map((skillName, index) => ({
14995
+ kind: "skill",
14996
+ id: skillNames.length === 1 ? message.id : `${message.id}-${index}`,
14997
+ skillName,
14998
+ occurredAt: message.timestamp
14999
+ }));
15000
+ }
15001
+ function extractSkillMcpCalls(messages, provider) {
15002
+ return messages.flatMap((message) => {
15003
+ if (message.type === "skill") {
15004
+ if (provider === "relay") return [];
15005
+ return [{ kind: "skill", id: message.id, skillName: message.skillName, occurredAt: message.timestamp }];
15006
+ }
15007
+ if (message.type === "command") {
15008
+ return skillCallsFromCommand(message);
15009
+ }
15010
+ if (message.type !== "tool_call") return [];
15011
+ const mcpName = mcpNameFromToolCall(message);
15012
+ return mcpName ? [{ kind: "mcp", id: message.id, mcpName, occurredAt: message.timestamp }] : [];
15013
+ });
15014
+ }
15015
+
15016
+ // src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
14975
15017
  var MAX_FAILED_RECORDS = 500;
14976
15018
  var FLUSH_DEBOUNCE_MS = 15e3;
14977
15019
  var MAX_UPLOADED_SEGMENTS = 50;
14978
15020
  var UPLOADED_SUFFIX = ".uploaded";
14979
- var AnalyticsBufferService = class {
15021
+ var MAX_TRACKED_MESSAGES = 500;
15022
+ var AgentChatActivityBuffer = class {
14980
15023
  constructor(options) {
14981
15024
  this.options = options;
14982
- this.liveFile = join22(ENGINE_DIR2, `${options.name}.jsonl`);
14983
- this.segmentFilePattern = new RegExp(`^${options.name}\\.(\\d+)\\.jsonl$`);
15025
+ this.storageNames = [
15026
+ options.storageName,
15027
+ ...options.legacyStorageNames ?? []
15028
+ ];
15029
+ this.liveFile = join22(ENGINE_DIR, `${options.storageName}.jsonl`);
15030
+ this.segmentFilePatterns = this.storageNames.map(
15031
+ (storageName) => new RegExp(`^${storageName}\\.(\\d+)\\.jsonl$`)
15032
+ );
14984
15033
  }
14985
15034
  options;
14986
15035
  liveFile;
14987
- segmentFilePattern;
15036
+ storageNames;
15037
+ segmentFilePatterns;
14988
15038
  failedRecords = [];
14989
15039
  pendingAppends = /* @__PURE__ */ new Set();
14990
15040
  flushTimer = null;
14991
- activeFlush = Promise.resolve({ flushed: 0, failed: 0 });
15041
+ activeFlush = Promise.resolve({
15042
+ flushed: 0,
15043
+ failed: 0
15044
+ });
14992
15045
  append(record) {
14993
- const pending = mkdir14(ENGINE_DIR2, { recursive: true }).then(() => appendFile3(this.liveFile, `${JSON.stringify(record)}
14994
- `, "utf-8")).catch((error) => {
14995
- console.error(`[${this.options.name}] Append failed, will retry on flush:`, error);
14996
- if (this.failedRecords.length < MAX_FAILED_RECORDS) this.failedRecords.push(record);
15046
+ const pending = mkdir14(ENGINE_DIR, { recursive: true }).then(
15047
+ () => appendFile3(this.liveFile, `${JSON.stringify(record)}
15048
+ `, "utf-8")
15049
+ ).catch((error) => {
15050
+ console.error(
15051
+ `[${this.options.storageName}] Append failed, will retry on flush:`,
15052
+ error
15053
+ );
15054
+ if (this.failedRecords.length < MAX_FAILED_RECORDS)
15055
+ this.failedRecords.push(record);
14997
15056
  });
14998
15057
  this.pendingAppends.add(pending);
14999
15058
  void pending.finally(() => this.pendingAppends.delete(pending));
@@ -15003,7 +15062,10 @@ var AnalyticsBufferService = class {
15003
15062
  this.flushTimer = setTimeout(() => {
15004
15063
  this.flushTimer = null;
15005
15064
  void this.flush().catch((error) => {
15006
- console.error(`[${this.options.name}] Scheduled flush failed, retrying on the next record:`, error);
15065
+ console.error(
15066
+ `[${this.options.storageName}] Scheduled flush failed, retrying on the next record:`,
15067
+ error
15068
+ );
15007
15069
  });
15008
15070
  }, FLUSH_DEBOUNCE_MS);
15009
15071
  this.flushTimer.unref?.();
@@ -15019,20 +15081,30 @@ var AnalyticsBufferService = class {
15019
15081
  }
15020
15082
  async runFlush() {
15021
15083
  for (const record of this.failedRecords.splice(0)) this.append(record);
15022
- while (this.pendingAppends.size > 0) await Promise.allSettled([...this.pendingAppends]);
15023
- await rename2(this.liveFile, join22(ENGINE_DIR2, `${this.options.name}.${Date.now()}.jsonl`)).catch(() => {
15024
- });
15025
- const entries = await readdir6(ENGINE_DIR2).catch(() => []);
15084
+ while (this.pendingAppends.size > 0)
15085
+ await Promise.allSettled([...this.pendingAppends]);
15086
+ for (const storageName of this.storageNames) {
15087
+ await rename2(
15088
+ join22(ENGINE_DIR, `${storageName}.jsonl`),
15089
+ join22(ENGINE_DIR, `${storageName}.${Date.now()}.jsonl`)
15090
+ ).catch(() => {
15091
+ });
15092
+ }
15093
+ const entries = await readdir6(ENGINE_DIR).catch(() => []);
15026
15094
  let flushed = 0;
15027
15095
  let failed = 0;
15028
15096
  for (const entry of entries) {
15029
- if (!this.segmentFilePattern.test(entry)) continue;
15097
+ if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry)))
15098
+ continue;
15030
15099
  try {
15031
- await this.uploadSegment(join22(ENGINE_DIR2, entry));
15100
+ await this.uploadSegment(join22(ENGINE_DIR, entry));
15032
15101
  flushed++;
15033
15102
  } catch (error) {
15034
15103
  failed++;
15035
- console.error(`[${this.options.name}] Segment upload failed, retained for retry:`, { entry, error });
15104
+ console.error(
15105
+ `[${this.options.storageName}] Segment upload failed, retained for retry:`,
15106
+ { entry, error }
15107
+ );
15036
15108
  }
15037
15109
  }
15038
15110
  const failedRecords = this.failedRecords.splice(0);
@@ -15042,13 +15114,22 @@ var AnalyticsBufferService = class {
15042
15114
  flushed++;
15043
15115
  } catch (error) {
15044
15116
  failed++;
15045
- this.failedRecords.unshift(...failedRecords.slice(0, MAX_FAILED_RECORDS));
15046
- console.error(`[${this.options.name}] Failed records upload failed, retained for retry:`, error);
15117
+ this.failedRecords.unshift(
15118
+ ...failedRecords.slice(0, MAX_FAILED_RECORDS)
15119
+ );
15120
+ console.error(
15121
+ `[${this.options.storageName}] Failed records upload failed, retained for retry:`,
15122
+ error
15123
+ );
15047
15124
  }
15048
15125
  }
15049
- const uploaded = entries.filter((entry) => entry.startsWith(`${this.options.name}.`) && entry.endsWith(UPLOADED_SUFFIX)).sort();
15126
+ const uploaded = entries.filter(
15127
+ (entry) => this.storageNames.some(
15128
+ (storageName) => entry.startsWith(`${storageName}.`)
15129
+ ) && entry.endsWith(UPLOADED_SUFFIX)
15130
+ ).sort();
15050
15131
  for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
15051
- await unlink3(join22(ENGINE_DIR2, entry)).catch(() => {
15132
+ await unlink3(join22(ENGINE_DIR, entry)).catch(() => {
15052
15133
  });
15053
15134
  }
15054
15135
  return { flushed, failed };
@@ -15067,66 +15148,40 @@ var AnalyticsBufferService = class {
15067
15148
  });
15068
15149
  }
15069
15150
  };
15070
-
15071
- // src/analytics/extract-skill-mcp-calls.ts
15072
- var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "opencode", "pi", "custom", "dynamic"]);
15073
- function mcpNameFromToolCall(message) {
15074
- const parsedName = parseMcpToolName(message.tool);
15075
- if (parsedName) return parsedName.server;
15076
- if (message.tool.startsWith("mcp.")) {
15077
- const [server, ...toolParts] = message.tool.slice("mcp.".length).split(".");
15078
- if (server && toolParts.length > 0) return server;
15151
+ var AgentChatActivityRecordTracker = class {
15152
+ pendingRecords = /* @__PURE__ */ new Map();
15153
+ buffer;
15154
+ constructor(options) {
15155
+ this.buffer = new AgentChatActivityBuffer(options);
15079
15156
  }
15080
- return NON_MCP_SERVERS.has(message.server) ? null : message.server;
15081
- }
15082
- function skillCallsFromCommand(message) {
15083
- const skillNames = message.skillNames ?? [];
15084
- return skillNames.map((skillName, index) => ({
15085
- kind: "skill",
15086
- id: skillNames.length === 1 ? message.id : `${message.id}-${index}`,
15087
- skillName,
15088
- occurredAt: message.timestamp
15089
- }));
15090
- }
15091
- function extractSkillMcpCalls(messages, provider) {
15092
- return messages.flatMap((message) => {
15093
- if (message.type === "skill") {
15094
- if (provider === "relay") return [];
15095
- return [{ kind: "skill", id: message.id, skillName: message.skillName, occurredAt: message.timestamp }];
15096
- }
15097
- if (message.type === "command") {
15098
- return skillCallsFromCommand(message);
15099
- }
15100
- if (message.type !== "tool_call") return [];
15101
- const mcpName = mcpNameFromToolCall(message);
15102
- return mcpName ? [{ kind: "mcp", id: message.id, mcpName, occurredAt: message.timestamp }] : [];
15103
- });
15104
- }
15105
-
15106
- // src/analytics/analytics.service.ts
15107
- var MAX_TRACKED_MESSAGES = 500;
15108
- async function uploadAnalyticsRecords(endpoint, body) {
15109
- const response = await monolithRequest(endpoint, { body });
15110
- if (!response.ok) throw new Error(`upload failed: ${response.status} ${await response.text()}`);
15111
- }
15112
- var AnalyticsService = class {
15157
+ track(chatId, record) {
15158
+ const records = this.pendingRecords.get(chatId) ?? [];
15159
+ records.push(record);
15160
+ this.pendingRecords.set(chatId, records);
15161
+ }
15162
+ noteTurnEnded(chatId) {
15163
+ const records = this.pendingRecords.get(chatId);
15164
+ if (!records) return;
15165
+ this.pendingRecords.delete(chatId);
15166
+ for (const record of records) this.buffer.append(record);
15167
+ this.buffer.scheduleFlush();
15168
+ }
15169
+ flush() {
15170
+ return this.buffer.flush();
15171
+ }
15172
+ };
15173
+ var AgentChatTurnActivityTracker = class {
15113
15174
  pendingMessages = /* @__PURE__ */ new Map();
15114
15175
  activeTurns = /* @__PURE__ */ new Map();
15115
- turnBuffer = new AnalyticsBufferService({
15116
- name: "turn-usage",
15117
- validate: isChatTurnUsageRecord,
15118
- upload: (turns) => uploadAnalyticsRecords("/v1/engine/chat-turn-usage", { turns })
15119
- });
15120
- skillBuffer = new AnalyticsBufferService({
15121
- name: "skill-usage",
15122
- validate: isSkillUsageRecord,
15123
- upload: (skills) => uploadAnalyticsRecords("/v1/engine/skill-usage", { skills })
15124
- });
15125
- mcpBuffer = new AnalyticsBufferService({
15126
- name: "mcp-usage",
15127
- validate: isMcpUsageRecord,
15128
- upload: (mcps) => uploadAnalyticsRecords("/v1/engine/mcp-usage", { mcps })
15129
- });
15176
+ turnBuffer;
15177
+ constructor(uploadTurns) {
15178
+ this.turnBuffer = new AgentChatActivityBuffer({
15179
+ storageName: "chat-turn-activity",
15180
+ legacyStorageNames: ["turn-usage"],
15181
+ validate: isAgentChatTurnActivityRecord,
15182
+ upload: uploadTurns
15183
+ });
15184
+ }
15130
15185
  noteMessageAccepted(messageId, request) {
15131
15186
  this.pendingMessages.set(messageId, {
15132
15187
  model: request.model,
@@ -15149,30 +15204,14 @@ var AnalyticsService = class {
15149
15204
  senderUserId: attributes.senderUserId,
15150
15205
  credentialMethod: credential?.method,
15151
15206
  credentialScope: credential?.scope,
15152
- skills: [],
15153
- mcps: [],
15154
15207
  seenCallIds: /* @__PURE__ */ new Set()
15155
15208
  });
15156
15209
  }
15157
- noteSkillMcpCalls(chatId, messages) {
15158
- const turn = this.activeTurns.get(chatId);
15159
- if (!turn) return;
15160
- for (const call of extractSkillMcpCalls(messages, turn.provider)) {
15161
- if (turn.seenCallIds.has(call.id)) continue;
15162
- turn.seenCallIds.add(call.id);
15163
- const common = {
15164
- callId: call.id,
15165
- chatId,
15166
- provider: turn.provider,
15167
- model: turn.model,
15168
- senderUserId: turn.senderUserId,
15169
- occurredAt: call.occurredAt,
15170
- credentialMethod: turn.credentialMethod,
15171
- credentialScope: turn.credentialScope
15172
- };
15173
- if (call.kind === "skill") turn.skills.push({ ...common, skillName: call.skillName });
15174
- else turn.mcps.push({ ...common, mcpName: call.mcpName });
15175
- }
15210
+ getActiveTurn(chatId) {
15211
+ return this.activeTurns.get(chatId);
15212
+ }
15213
+ getActiveChatIds() {
15214
+ return [...this.activeTurns.keys()];
15176
15215
  }
15177
15216
  noteTurnEnded(chatId) {
15178
15217
  const turn = this.activeTurns.get(chatId);
@@ -15189,22 +15228,91 @@ var AnalyticsService = class {
15189
15228
  occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
15190
15229
  seconds: Math.round((Date.now() - turn.startedAtMs) / 1e3)
15191
15230
  });
15192
- for (const skill of turn.skills) this.skillBuffer.append(skill);
15193
- for (const mcp of turn.mcps) this.mcpBuffer.append(mcp);
15194
15231
  this.turnBuffer.scheduleFlush();
15195
- if (turn.skills.length > 0) this.skillBuffer.scheduleFlush();
15196
- if (turn.mcps.length > 0) this.mcpBuffer.scheduleFlush();
15232
+ }
15233
+ flush() {
15234
+ return this.turnBuffer.flush();
15235
+ }
15236
+ };
15237
+ async function uploadAgentChatActivity(endpoint, body) {
15238
+ const response = await monolithRequest(endpoint, { body });
15239
+ if (!response.ok)
15240
+ throw new Error(
15241
+ `upload failed: ${response.status} ${await response.text()}`
15242
+ );
15243
+ }
15244
+ var AgentChatActivityTrackerService = class {
15245
+ turnActivityTracker;
15246
+ skillActivityTracker;
15247
+ mcpActivityTracker;
15248
+ constructor() {
15249
+ this.turnActivityTracker = new AgentChatTurnActivityTracker(
15250
+ (turns) => uploadAgentChatActivity("/v1/engine/chat-turn-activity", { turns })
15251
+ );
15252
+ this.skillActivityTracker = new AgentChatActivityRecordTracker({
15253
+ storageName: "skill-activity",
15254
+ legacyStorageNames: ["skill-usage"],
15255
+ validate: isAgentChatSkillActivityRecord,
15256
+ upload: (skills) => uploadAgentChatActivity("/v1/engine/skill-activity", { skills })
15257
+ });
15258
+ this.mcpActivityTracker = new AgentChatActivityRecordTracker({
15259
+ storageName: "mcp-activity",
15260
+ legacyStorageNames: ["mcp-usage"],
15261
+ validate: isAgentChatMcpActivityRecord,
15262
+ upload: (mcps) => uploadAgentChatActivity("/v1/engine/mcp-activity", { mcps })
15263
+ });
15264
+ }
15265
+ noteMessageAccepted(messageId, request) {
15266
+ this.turnActivityTracker.noteMessageAccepted(messageId, request);
15267
+ }
15268
+ noteTurnStarted(chatId, messageId, provider) {
15269
+ this.turnActivityTracker.noteTurnStarted(chatId, messageId, provider);
15270
+ }
15271
+ noteSkillMcpCalls(chatId, messages) {
15272
+ const turn = this.turnActivityTracker.getActiveTurn(chatId);
15273
+ if (!turn) return;
15274
+ for (const call of extractSkillMcpCalls(messages, turn.provider)) {
15275
+ if (turn.seenCallIds.has(call.id)) continue;
15276
+ turn.seenCallIds.add(call.id);
15277
+ const activityRecord = {
15278
+ callId: call.id,
15279
+ chatId,
15280
+ provider: turn.provider,
15281
+ model: turn.model,
15282
+ senderUserId: turn.senderUserId,
15283
+ occurredAt: call.occurredAt,
15284
+ credentialMethod: turn.credentialMethod,
15285
+ credentialScope: turn.credentialScope
15286
+ };
15287
+ if (call.kind === "skill") {
15288
+ this.skillActivityTracker.track(chatId, {
15289
+ ...activityRecord,
15290
+ skillName: call.skillName
15291
+ });
15292
+ } else {
15293
+ this.mcpActivityTracker.track(chatId, {
15294
+ ...activityRecord,
15295
+ mcpName: call.mcpName
15296
+ });
15297
+ }
15298
+ }
15299
+ }
15300
+ noteTurnEnded(chatId) {
15301
+ this.skillActivityTracker.noteTurnEnded(chatId);
15302
+ this.mcpActivityTracker.noteTurnEnded(chatId);
15303
+ this.turnActivityTracker.noteTurnEnded(chatId);
15197
15304
  }
15198
15305
  async flush(finalizeInFlight = true) {
15199
15306
  if (finalizeInFlight) {
15200
- for (const chatId of this.activeTurns.keys()) this.noteTurnEnded(chatId);
15307
+ for (const chatId of this.turnActivityTracker.getActiveChatIds())
15308
+ this.noteTurnEnded(chatId);
15201
15309
  }
15202
- const [turnUsage, skillUsage, mcpUsage] = await Promise.all([
15203
- this.turnBuffer.flush(),
15204
- this.skillBuffer.flush(),
15205
- this.mcpBuffer.flush()
15310
+ const [turnActivity, skillActivity, mcpActivity] = await Promise.all([
15311
+ this.turnActivityTracker.flush(),
15312
+ this.skillActivityTracker.flush(),
15313
+ this.mcpActivityTracker.flush()
15206
15314
  ]);
15207
- return { turnUsage, skillUsage, mcpUsage };
15315
+ return { turnActivity, skillActivity, mcpActivity };
15208
15316
  }
15209
15317
  };
15210
15318
 
@@ -15490,7 +15598,7 @@ import { basename as basename2, join as join25 } from "path";
15490
15598
 
15491
15599
  // src/services/chat/chat-senders.ts
15492
15600
  import { join as join24 } from "path";
15493
- var CHAT_SENDERS_DIR = join24(ENGINE_DIR2, "chat-senders");
15601
+ var CHAT_SENDERS_DIR = join24(ENGINE_DIR3, "chat-senders");
15494
15602
  function chatMessageSendersFilePath(chatId) {
15495
15603
  return join24(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
15496
15604
  }
@@ -15508,9 +15616,9 @@ function parseChatMessageSendersJsonl(content) {
15508
15616
 
15509
15617
  // src/services/upload-chat-transcripts.ts
15510
15618
  var HISTORY_DIRS = [
15511
- join25(ENGINE_DIR2, "claude-histories"),
15512
- join25(ENGINE_DIR2, "relay-histories"),
15513
- join25(ENGINE_DIR2, "codex-histories")
15619
+ join25(ENGINE_DIR3, "claude-histories"),
15620
+ join25(ENGINE_DIR3, "relay-histories"),
15621
+ join25(ENGINE_DIR3, "codex-histories")
15514
15622
  ];
15515
15623
  async function putTranscript(uploadUrl, filePath, size) {
15516
15624
  await new Promise((resolve5, reject) => {
@@ -15881,20 +15989,20 @@ function corruptChatsFilePath() {
15881
15989
  return `${CHATS_FILE}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
15882
15990
  }
15883
15991
  var ChatService = class {
15884
- constructor(workingDirectory, analyticsService2) {
15992
+ constructor(workingDirectory, agentChatActivityTrackerService2) {
15885
15993
  this.workingDirectory = workingDirectory;
15886
- this.analyticsService = analyticsService2;
15994
+ this.agentChatActivityTrackerService = agentChatActivityTrackerService2;
15887
15995
  keepAliveService.setActivityCheck(
15888
15996
  () => [...this.chats.values()].some((chat) => !chat.persisted.deletedAt && hasActiveAgentWork(chat))
15889
15997
  );
15890
15998
  }
15891
15999
  workingDirectory;
15892
- analyticsService;
16000
+ agentChatActivityTrackerService;
15893
16001
  chats = /* @__PURE__ */ new Map();
15894
16002
  persistInFlight = false;
15895
16003
  persistQueued = false;
15896
16004
  async initialize() {
15897
- await mkdir15(ENGINE_DIR2, { recursive: true });
16005
+ await mkdir15(ENGINE_DIR3, { recursive: true });
15898
16006
  await mkdir15(CLAUDE_HISTORY_DIR, { recursive: true });
15899
16007
  await mkdir15(RELAY_HISTORY_DIR, { recursive: true });
15900
16008
  await mkdir15(CODEX_HISTORY_DIR, { recursive: true });
@@ -16011,7 +16119,7 @@ var ChatService = class {
16011
16119
  request.images
16012
16120
  );
16013
16121
  chat.pendingMessageIds.push(result.messageId);
16014
- this.analyticsService.noteMessageAccepted(result.messageId, request);
16122
+ this.agentChatActivityTrackerService.noteMessageAccepted(result.messageId, request);
16015
16123
  if (request.errorNotificationTarget) {
16016
16124
  chat.errorNotificationTargets.set(result.messageId, request.errorNotificationTarget);
16017
16125
  }
@@ -16067,7 +16175,7 @@ var ChatService = class {
16067
16175
  async interrupt(chatId) {
16068
16176
  const chat = this.requireChat(chatId);
16069
16177
  const result = await chat.provider.interrupt();
16070
- this.analyticsService.noteTurnEnded(chatId);
16178
+ this.agentChatActivityTrackerService.noteTurnEnded(chatId);
16071
16179
  chat.hasActiveTurn = false;
16072
16180
  chat.activeMessageId = null;
16073
16181
  chat.pendingMessageIds = [];
@@ -16089,7 +16197,7 @@ var ChatService = class {
16089
16197
  return { interrupted: false, queue: [], goal: null };
16090
16198
  }
16091
16199
  const interruptResult = await chat.provider.interrupt();
16092
- this.analyticsService.noteTurnEnded(chatId);
16200
+ this.agentChatActivityTrackerService.noteTurnEnded(chatId);
16093
16201
  chat.hasActiveTurn = false;
16094
16202
  chat.activeMessageId = null;
16095
16203
  chat.pendingMessageIds = [];
@@ -16176,7 +16284,7 @@ var ChatService = class {
16176
16284
  messageId,
16177
16285
  ...chat.pendingMessageIds.filter((pendingMessageId) => pendingMessageId !== messageId && pendingMessageId !== interruptedMessageId)
16178
16286
  ];
16179
- this.analyticsService.noteTurnEnded(chatId);
16287
+ this.agentChatActivityTrackerService.noteTurnEnded(chatId);
16180
16288
  chat.hasActiveTurn = false;
16181
16289
  chat.activeMessageId = null;
16182
16290
  if (interruptedMessageId) {
@@ -16310,14 +16418,22 @@ var ChatService = class {
16310
16418
  const chatsById = new Map(
16311
16419
  [...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
16312
16420
  );
16313
- const [chatTranscripts, canvas, repoState, engineLogs, analytics] = await Promise.all([
16421
+ const [chatTranscripts, canvas, repoState, engineLogs, chatActivity] = await Promise.all([
16314
16422
  flushAllChatTranscripts(chatsById),
16315
16423
  flushAllCanvasItems(),
16316
16424
  flushRepoState(),
16317
16425
  flushAllEngineLogs(),
16318
- this.analyticsService.flush()
16426
+ this.agentChatActivityTrackerService.flush()
16319
16427
  ]);
16320
- return { chatTranscripts, canvas, repoState, engineLogs, ...analytics };
16428
+ return {
16429
+ chatTranscripts,
16430
+ canvas,
16431
+ repoState,
16432
+ engineLogs,
16433
+ turnActivity: chatActivity.turnActivity,
16434
+ skillActivity: chatActivity.skillActivity,
16435
+ mcpActivity: chatActivity.mcpActivity
16436
+ };
16321
16437
  }
16322
16438
  createRuntimeChat(persisted) {
16323
16439
  const saveSession = async (sessionId) => {
@@ -16474,7 +16590,7 @@ var ChatService = class {
16474
16590
  }
16475
16591
  chat.hasActiveTurn = true;
16476
16592
  chat.activeMessageId = messageId;
16477
- this.analyticsService.noteTurnStarted(chat.persisted.id, messageId, chat.persisted.provider);
16593
+ this.agentChatActivityTrackerService.noteTurnStarted(chat.persisted.id, messageId, chat.persisted.provider);
16478
16594
  chat.activeErrorNotificationTarget = chat.errorNotificationTargets.get(messageId) ?? null;
16479
16595
  chat.errorNotificationTargets.delete(messageId);
16480
16596
  this.publish({
@@ -16492,7 +16608,7 @@ var ChatService = class {
16492
16608
  filter: false,
16493
16609
  parentToolUseId: typeof event.payload.parent_tool_use_id === "string" ? event.payload.parent_tool_use_id : null
16494
16610
  });
16495
- this.analyticsService.noteSkillMcpCalls(chatId, displayMessages);
16611
+ this.agentChatActivityTrackerService.noteSkillMcpCalls(chatId, displayMessages);
16496
16612
  }
16497
16613
  const terminalErrors = terminalErrorsFromEvent(event, chat.persisted.provider, codexTranscript);
16498
16614
  if (terminalErrors !== void 0) {
@@ -16556,7 +16672,7 @@ var ChatService = class {
16556
16672
  }
16557
16673
  chat.hasActiveTurn = false;
16558
16674
  chat.activeMessageId = null;
16559
- this.analyticsService.noteTurnEnded(chatId);
16675
+ this.agentChatActivityTrackerService.noteTurnEnded(chatId);
16560
16676
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
16561
16677
  this.publish({
16562
16678
  type: "chat.turn.completed",
@@ -18592,8 +18708,8 @@ var authMiddleware = async (c, next) => {
18592
18708
  }
18593
18709
  await next();
18594
18710
  };
18595
- var analyticsService = new AnalyticsService();
18596
- var chatService = new ChatService(gitService.getWorkspaceRoot(), analyticsService);
18711
+ var agentChatActivityTrackerService = new AgentChatActivityTrackerService();
18712
+ var chatService = new ChatService(gitService.getWorkspaceRoot(), agentChatActivityTrackerService);
18597
18713
  app.get("/health", async (c) => {
18598
18714
  const requestedWaitMs = Number(c.req.query(ENGINE_HEALTH_WAIT_QUERY_PARAM));
18599
18715
  const waitMs = Number.isFinite(requestedWaitMs) ? Math.min(Math.max(requestedWaitMs, 0), ENGINE_HEALTH_MAX_WAIT_MS) : 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.608",
3
+ "version": "0.1.610",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",