replicas-engine 0.1.697 → 0.1.698
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.
|
@@ -724,6 +724,10 @@ function coerceClaudePartialMessagePayload(payload) {
|
|
|
724
724
|
status: payload.status === "completed" ? "completed" : "in_progress"
|
|
725
725
|
};
|
|
726
726
|
}
|
|
727
|
+
function getClaudePartialMessageStreamId(event) {
|
|
728
|
+
if (event.type !== CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE) return null;
|
|
729
|
+
return typeof event.payload.streamId === "string" && event.payload.streamId ? event.payload.streamId : null;
|
|
730
|
+
}
|
|
727
731
|
var ACCEPTED_USER_MESSAGE_SOURCE = "replicas-chat-turn-accepted";
|
|
728
732
|
var USER_MESSAGE_ID_PAYLOAD_KEY = "replicasMessageId";
|
|
729
733
|
var CODEX_ASP_ITEM_ID_PAYLOAD_KEY = "codexAspItemId";
|
|
@@ -4059,10 +4063,11 @@ function getEventTimestampMs(event) {
|
|
|
4059
4063
|
function areSameUserMessageEvents(a, b) {
|
|
4060
4064
|
const aMessage = getUserMessage(a);
|
|
4061
4065
|
const bMessage = getUserMessage(b);
|
|
4062
|
-
if (!aMessage ||
|
|
4066
|
+
if (!aMessage || !bMessage) return false;
|
|
4063
4067
|
const aMessageId = getUserMessageId(a);
|
|
4064
4068
|
const bMessageId = getUserMessageId(b);
|
|
4065
4069
|
if (aMessageId || bMessageId) return aMessageId === bMessageId;
|
|
4070
|
+
if (aMessage !== bMessage) return false;
|
|
4066
4071
|
const aItemId = getUserMessageItemId(a);
|
|
4067
4072
|
const bItemId = getUserMessageItemId(b);
|
|
4068
4073
|
if (aItemId || bItemId) return aItemId === bItemId;
|
|
@@ -4562,7 +4567,7 @@ function parseCursorEvents(events) {
|
|
|
4562
4567
|
const message = event.payload.message;
|
|
4563
4568
|
if (typeof message === "string" && message.trim()) {
|
|
4564
4569
|
messages.push({
|
|
4565
|
-
id: `cursor-user-${event.timestamp}-${messages.length}`,
|
|
4570
|
+
id: getUserMessageId(event) ?? `cursor-user-${event.timestamp}-${messages.length}`,
|
|
4566
4571
|
type: "user",
|
|
4567
4572
|
content: message,
|
|
4568
4573
|
timestamp: event.timestamp
|
|
@@ -5143,7 +5148,7 @@ function parsePiEvents(events) {
|
|
|
5143
5148
|
let activeMessageId = "assistant";
|
|
5144
5149
|
for (const event of events) {
|
|
5145
5150
|
if (event.type === "event_msg" && event.payload.type === "user_message" && typeof event.payload.message === "string") {
|
|
5146
|
-
messages.push({ id: `pi-user-${event.timestamp}-${messages.length}`, type: "user", content: event.payload.message, timestamp: event.timestamp });
|
|
5151
|
+
messages.push({ id: getUserMessageId(event) ?? `pi-user-${event.timestamp}-${messages.length}`, type: "user", content: event.payload.message, timestamp: event.timestamp });
|
|
5147
5152
|
if (typeof event.payload.skillName === "string" && event.payload.skillName) {
|
|
5148
5153
|
messages.push({
|
|
5149
5154
|
id: `pi-skill-${event.timestamp}-${messages.length}`,
|
|
@@ -6184,7 +6189,12 @@ function parseAgentEvents(events, agentType) {
|
|
|
6184
6189
|
function parseDisplayMessages(events, agentType, codexAspTranscript, options = {}) {
|
|
6185
6190
|
const shouldFilter = options.filter ?? true;
|
|
6186
6191
|
const parsedEvents = agentType === "claude" || agentType === "relay" ? parseClaudeEvents(events, options.parentToolUseId) : parseAgentEvents(events, agentType);
|
|
6187
|
-
const
|
|
6192
|
+
const reconciledEvents = [];
|
|
6193
|
+
for (const message of parsedEvents) {
|
|
6194
|
+
if (message.type === "user") upsertDisplayMessage(reconciledEvents, message);
|
|
6195
|
+
else reconciledEvents.push(message);
|
|
6196
|
+
}
|
|
6197
|
+
const legacyMessages = shouldFilter ? filterDisplayMessages(reconciledEvents, agentType) : reconciledEvents;
|
|
6188
6198
|
const applySyntheticNotices = (messages) => shouldFilter ? applyAuthFallbackNotices(applyInterruptions(messages, events), events) : messages;
|
|
6189
6199
|
if (agentType !== "codex" || !codexAspTranscript) {
|
|
6190
6200
|
return applySyntheticNotices(legacyMessages);
|
|
@@ -6765,7 +6775,7 @@ var DEFAULT_CODEX_ARGS = [
|
|
|
6765
6775
|
var MIN_CODEX_CLI_VERSION = "0.144.6";
|
|
6766
6776
|
var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
|
|
6767
6777
|
var codexCliVersionEnsured = null;
|
|
6768
|
-
var ENGINE_PACKAGE_VERSION = "0.1.
|
|
6778
|
+
var ENGINE_PACKAGE_VERSION = "0.1.698";
|
|
6769
6779
|
var INITIALIZE_METHOD = "initialize";
|
|
6770
6780
|
var INITIALIZED_NOTIFICATION = "initialized";
|
|
6771
6781
|
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
@@ -7037,6 +7047,8 @@ export {
|
|
|
7037
7047
|
isAgentChatSkillActivityRecord,
|
|
7038
7048
|
isAgentChatMcpActivityRecord,
|
|
7039
7049
|
CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE,
|
|
7050
|
+
getClaudePartialMessageStreamId,
|
|
7051
|
+
ACCEPTED_USER_MESSAGE_SOURCE,
|
|
7040
7052
|
USER_MESSAGE_ID_PAYLOAD_KEY,
|
|
7041
7053
|
CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE,
|
|
7042
7054
|
CODEX_QUOTA_STATUS_EVENT_TYPE,
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
ACCEPTED_USER_MESSAGE_SOURCE,
|
|
3
4
|
ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
|
|
4
5
|
AGENT,
|
|
5
6
|
AGENT_MESSAGE_DELTA_METHOD,
|
|
@@ -131,6 +132,7 @@ import {
|
|
|
131
132
|
getChatHistoryPageWindow,
|
|
132
133
|
getChatTranscriptHistorySize,
|
|
133
134
|
getClaudeModelContextWindow,
|
|
135
|
+
getClaudePartialMessageStreamId,
|
|
134
136
|
getCodexAspTurnResponse,
|
|
135
137
|
getDeepseekAssistantMessageText,
|
|
136
138
|
getDefaultAgentModel,
|
|
@@ -192,13 +194,13 @@ import {
|
|
|
192
194
|
serializeCanvasContentResponse,
|
|
193
195
|
shellQuotePosix,
|
|
194
196
|
stripAgentDiagnosticErrors
|
|
195
|
-
} from "./chunk-
|
|
197
|
+
} from "./chunk-2BBE2BVQ.js";
|
|
196
198
|
|
|
197
199
|
// src/index.ts
|
|
198
200
|
import { serve } from "@hono/node-server";
|
|
199
201
|
import { Hono as Hono2 } from "hono";
|
|
200
202
|
import { existsSync as existsSync11 } from "fs";
|
|
201
|
-
import { randomUUID as
|
|
203
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
202
204
|
import { connect } from "net";
|
|
203
205
|
|
|
204
206
|
// src/managers/github-token-manager.ts
|
|
@@ -569,6 +571,7 @@ var MonolithService = class {
|
|
|
569
571
|
var monolithService = new MonolithService();
|
|
570
572
|
|
|
571
573
|
// src/utils/file.ts
|
|
574
|
+
import { randomUUID } from "crypto";
|
|
572
575
|
import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
|
|
573
576
|
import { dirname, join as join3 } from "path";
|
|
574
577
|
|
|
@@ -587,7 +590,7 @@ var AsyncLock = class {
|
|
|
587
590
|
|
|
588
591
|
// src/utils/file.ts
|
|
589
592
|
async function atomicWriteFile(path6, data, options) {
|
|
590
|
-
const tmpFile = `${path6}.${process.pid}.${
|
|
593
|
+
const tmpFile = `${path6}.${process.pid}.${randomUUID()}.tmp`;
|
|
591
594
|
try {
|
|
592
595
|
await writeFile(tmpFile, data, { encoding: "utf-8", mode: options?.mode });
|
|
593
596
|
await rename(tmpFile, path6);
|
|
@@ -2663,7 +2666,7 @@ var replicasConfigService = new ReplicasConfigService();
|
|
|
2663
2666
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
2664
2667
|
import { homedir as homedir9 } from "os";
|
|
2665
2668
|
import { join as join12 } from "path";
|
|
2666
|
-
import { randomUUID } from "crypto";
|
|
2669
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2667
2670
|
var ENGINE_DIR = join12(homedir9(), ".replicas", "engine");
|
|
2668
2671
|
var EVENTS_FILE = join12(ENGINE_DIR, "events.jsonl");
|
|
2669
2672
|
var EventService = class {
|
|
@@ -2674,7 +2677,7 @@ var EventService = class {
|
|
|
2674
2677
|
this.writer.open(EVENTS_FILE);
|
|
2675
2678
|
}
|
|
2676
2679
|
subscribe(subscriber) {
|
|
2677
|
-
const id =
|
|
2680
|
+
const id = randomUUID2();
|
|
2678
2681
|
this.subscribers.set(id, subscriber);
|
|
2679
2682
|
return () => {
|
|
2680
2683
|
this.subscribers.delete(id);
|
|
@@ -2698,7 +2701,7 @@ var eventService = new EventService();
|
|
|
2698
2701
|
// src/services/preview-service.ts
|
|
2699
2702
|
import { mkdir as mkdir8, readFile as readFile7 } from "fs/promises";
|
|
2700
2703
|
import { existsSync as existsSync4 } from "fs";
|
|
2701
|
-
import { randomUUID as
|
|
2704
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2702
2705
|
import { homedir as homedir10 } from "os";
|
|
2703
2706
|
import { dirname as dirname2, join as join13 } from "path";
|
|
2704
2707
|
var PREVIEW_PORTS_FILE = join13(homedir10(), ".replicas", "preview-ports.json");
|
|
@@ -2737,7 +2740,7 @@ var PreviewService = class {
|
|
|
2737
2740
|
data.previews.push(preview);
|
|
2738
2741
|
await writePreviewsFile(data);
|
|
2739
2742
|
eventService.publish({
|
|
2740
|
-
id:
|
|
2743
|
+
id: randomUUID3(),
|
|
2741
2744
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2742
2745
|
type: "preview.changed",
|
|
2743
2746
|
payload: { previews: data.previews }
|
|
@@ -2757,7 +2760,7 @@ var PreviewService = class {
|
|
|
2757
2760
|
}
|
|
2758
2761
|
await writePreviewsFile(data);
|
|
2759
2762
|
eventService.publish({
|
|
2760
|
-
id:
|
|
2763
|
+
id: randomUUID3(),
|
|
2761
2764
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2762
2765
|
type: "preview.changed",
|
|
2763
2766
|
payload: { previews: data.previews }
|
|
@@ -2814,13 +2817,138 @@ async function registerDesktopPreview() {
|
|
|
2814
2817
|
import { existsSync as existsSync8 } from "fs";
|
|
2815
2818
|
import { appendFile as appendFile4, copyFile, mkdir as mkdir17, readFile as readFile17, rename as rename3, rm as rm2 } from "fs/promises";
|
|
2816
2819
|
import { join as join29 } from "path";
|
|
2817
|
-
import { randomUUID as
|
|
2820
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
2821
|
+
|
|
2822
|
+
// ../shared/src/workspace-chat.ts
|
|
2823
|
+
var WORKSPACE_CHATS_QUERY_SCHEMA_VERSION = 1;
|
|
2824
|
+
var WORKSPACE_CHATS_QUERY_ROOT = `workspace-chats:v${WORKSPACE_CHATS_QUERY_SCHEMA_VERSION}`;
|
|
2825
|
+
function isAcceptedUserMessage(event) {
|
|
2826
|
+
return getUserMessage(event) !== null && event.payload.source === ACCEPTED_USER_MESSAGE_SOURCE;
|
|
2827
|
+
}
|
|
2828
|
+
function getComparableUserMessage(event) {
|
|
2829
|
+
const message = getUserMessage(event);
|
|
2830
|
+
if (message !== null) return message;
|
|
2831
|
+
const payloadMessage = event.payload.message;
|
|
2832
|
+
if (event.type !== "claude-user" || typeof payloadMessage !== "object" || payloadMessage === null || !("content" in payloadMessage)) {
|
|
2833
|
+
return null;
|
|
2834
|
+
}
|
|
2835
|
+
return extractToolResultText(payloadMessage.content) || null;
|
|
2836
|
+
}
|
|
2837
|
+
function areDuplicateUserMessages(a, b) {
|
|
2838
|
+
if (areSameUserMessageEvents(a, b)) return true;
|
|
2839
|
+
const aMessage = getComparableUserMessage(a);
|
|
2840
|
+
const bMessage = getComparableUserMessage(b);
|
|
2841
|
+
if (!aMessage || aMessage !== bMessage) return false;
|
|
2842
|
+
const aAccepted = isAcceptedUserMessage(a);
|
|
2843
|
+
const bAccepted = isAcceptedUserMessage(b);
|
|
2844
|
+
return aAccepted !== bAccepted ? areUserMessagesWithinMatchWindow(
|
|
2845
|
+
{ content: aMessage, timestamp: a.timestamp },
|
|
2846
|
+
{ content: bMessage, timestamp: b.timestamp }
|
|
2847
|
+
) : false;
|
|
2848
|
+
}
|
|
2849
|
+
var eventJsonCache = /* @__PURE__ */ new WeakMap();
|
|
2850
|
+
function workspaceChatEventSignals(event) {
|
|
2851
|
+
let json = eventJsonCache.get(event);
|
|
2852
|
+
if (json === void 0) {
|
|
2853
|
+
json = JSON.stringify(event);
|
|
2854
|
+
eventJsonCache.set(event, json);
|
|
2855
|
+
}
|
|
2856
|
+
return {
|
|
2857
|
+
streamId: getClaudePartialMessageStreamId(event),
|
|
2858
|
+
json,
|
|
2859
|
+
userMessage: getComparableUserMessage(event),
|
|
2860
|
+
userMessageId: getUserMessageId(event)
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2863
|
+
function indexMergedEvent(index, event, position) {
|
|
2864
|
+
const { streamId, json, userMessage, userMessageId } = workspaceChatEventSignals(event);
|
|
2865
|
+
if (streamId !== null && !index.byStreamId.has(streamId)) {
|
|
2866
|
+
index.byStreamId.set(streamId, position);
|
|
2867
|
+
}
|
|
2868
|
+
if (!index.byJson.has(json)) index.byJson.set(json, position);
|
|
2869
|
+
if (userMessageId !== null && !index.byUserMessageId.has(userMessageId)) {
|
|
2870
|
+
index.byUserMessageId.set(userMessageId, position);
|
|
2871
|
+
}
|
|
2872
|
+
if (userMessage !== null) {
|
|
2873
|
+
const bucket = index.byUserMessage.get(userMessage);
|
|
2874
|
+
if (!bucket) {
|
|
2875
|
+
index.byUserMessage.set(userMessage, [{ position, event }]);
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
const insertAt = bucket.findIndex((entry) => entry.position > position);
|
|
2879
|
+
bucket.splice(insertAt === -1 ? bucket.length : insertAt, 0, { position, event });
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
function reindexReplacedEvent(index, previous, event, position) {
|
|
2883
|
+
const { streamId, json, userMessage, userMessageId } = workspaceChatEventSignals(previous);
|
|
2884
|
+
if (streamId !== null && index.byStreamId.get(streamId) === position) {
|
|
2885
|
+
index.byStreamId.delete(streamId);
|
|
2886
|
+
}
|
|
2887
|
+
if (index.byJson.get(json) === position) index.byJson.delete(json);
|
|
2888
|
+
if (userMessageId !== null && index.byUserMessageId.get(userMessageId) === position) {
|
|
2889
|
+
index.byUserMessageId.delete(userMessageId);
|
|
2890
|
+
}
|
|
2891
|
+
if (userMessage !== null) {
|
|
2892
|
+
const bucket = index.byUserMessage.get(userMessage) ?? [];
|
|
2893
|
+
const entryIndex = bucket.findIndex((entry) => entry.position === position);
|
|
2894
|
+
if (entryIndex !== -1) bucket.splice(entryIndex, 1);
|
|
2895
|
+
if (bucket.length === 0) {
|
|
2896
|
+
index.byUserMessage.delete(userMessage);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
indexMergedEvent(index, event, position);
|
|
2900
|
+
}
|
|
2901
|
+
function findDuplicatePosition(index, event) {
|
|
2902
|
+
const { streamId, json, userMessage, userMessageId } = workspaceChatEventSignals(event);
|
|
2903
|
+
let match = -1;
|
|
2904
|
+
const consider = (position) => {
|
|
2905
|
+
if (position !== void 0 && (match === -1 || position < match)) match = position;
|
|
2906
|
+
};
|
|
2907
|
+
if (streamId !== null) consider(index.byStreamId.get(streamId));
|
|
2908
|
+
consider(index.byJson.get(json));
|
|
2909
|
+
if (userMessageId !== null) consider(index.byUserMessageId.get(userMessageId));
|
|
2910
|
+
if (userMessage !== null) {
|
|
2911
|
+
for (const entry of index.byUserMessage.get(userMessage) ?? []) {
|
|
2912
|
+
if (areDuplicateUserMessages(entry.event, event)) {
|
|
2913
|
+
consider(entry.position);
|
|
2914
|
+
break;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
return match;
|
|
2919
|
+
}
|
|
2920
|
+
function mergeWorkspaceChatHistoryEvents(primary, supplemental) {
|
|
2921
|
+
if (supplemental.length === 0) return [...primary];
|
|
2922
|
+
const merged = [...primary];
|
|
2923
|
+
const index = {
|
|
2924
|
+
byStreamId: /* @__PURE__ */ new Map(),
|
|
2925
|
+
byJson: /* @__PURE__ */ new Map(),
|
|
2926
|
+
byUserMessageId: /* @__PURE__ */ new Map(),
|
|
2927
|
+
byUserMessage: /* @__PURE__ */ new Map()
|
|
2928
|
+
};
|
|
2929
|
+
for (let position = 0; position < merged.length; position++) {
|
|
2930
|
+
indexMergedEvent(index, merged[position], position);
|
|
2931
|
+
}
|
|
2932
|
+
for (const event of supplemental) {
|
|
2933
|
+
const position = findDuplicatePosition(index, event);
|
|
2934
|
+
const messageId = getUserMessageId(event);
|
|
2935
|
+
if (position === -1) {
|
|
2936
|
+
indexMergedEvent(index, event, merged.length);
|
|
2937
|
+
merged.push(event);
|
|
2938
|
+
} else if (getClaudePartialMessageStreamId(event) !== null || isAcceptedUserMessage(merged[position]) && !isAcceptedUserMessage(event) || messageId !== null && messageId === getUserMessageId(merged[position])) {
|
|
2939
|
+
const previous = merged[position];
|
|
2940
|
+
merged[position] = event;
|
|
2941
|
+
reindexReplacedEvent(index, previous, event, position);
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
return merged;
|
|
2945
|
+
}
|
|
2818
2946
|
|
|
2819
2947
|
// src/managers/claude-manager.ts
|
|
2820
2948
|
import {
|
|
2821
2949
|
query
|
|
2822
2950
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
2823
|
-
import { randomUUID as
|
|
2951
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
2824
2952
|
import { dirname as dirname4, join as join17 } from "path";
|
|
2825
2953
|
import { mkdir as mkdir10 } from "fs/promises";
|
|
2826
2954
|
import { homedir as homedir12 } from "os";
|
|
@@ -3206,7 +3334,7 @@ function extractPlanFromCodexAspNotification(notification) {
|
|
|
3206
3334
|
}
|
|
3207
3335
|
|
|
3208
3336
|
// src/utils/image-utils.ts
|
|
3209
|
-
import { randomUUID as
|
|
3337
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
3210
3338
|
import { mkdir as mkdir9, unlink as unlink2, writeFile as writeFile5 } from "fs/promises";
|
|
3211
3339
|
import { homedir as homedir11 } from "os";
|
|
3212
3340
|
import { join as join14 } from "path";
|
|
@@ -3315,7 +3443,7 @@ async function saveNormalizedImagesToTempFiles(images, tempImageDir = join14(hom
|
|
|
3315
3443
|
try {
|
|
3316
3444
|
for (const image of images) {
|
|
3317
3445
|
const ext = image.source.media_type.split("/")[1] || "png";
|
|
3318
|
-
const filename = `img_${
|
|
3446
|
+
const filename = `img_${randomUUID4()}.${ext}`;
|
|
3319
3447
|
const filepath = join14(tempImageDir, filename);
|
|
3320
3448
|
await writeFile5(filepath, Buffer.from(image.source.data, "base64"));
|
|
3321
3449
|
tempPaths.push(filepath);
|
|
@@ -3385,6 +3513,12 @@ var MessageQueueService = class {
|
|
|
3385
3513
|
onProcessingChanged;
|
|
3386
3514
|
onMessageStarted;
|
|
3387
3515
|
acceptanceByMessage = /* @__PURE__ */ new WeakMap();
|
|
3516
|
+
// A withdrawal mark implies the entry was present when its acceptance
|
|
3517
|
+
// rejected; intentional removals (remove, clear, drain, steer) can never
|
|
3518
|
+
// coincide with it, so recovery only needs this mark plus the cancellation
|
|
3519
|
+
// version below.
|
|
3520
|
+
acceptanceWithdrawals = /* @__PURE__ */ new WeakSet();
|
|
3521
|
+
queueCancellationVersion = 0;
|
|
3388
3522
|
constructor(processMessage, onProcessingChanged = () => {
|
|
3389
3523
|
}, onMessageStarted = async () => {
|
|
3390
3524
|
}) {
|
|
@@ -3403,13 +3537,32 @@ var MessageQueueService = class {
|
|
|
3403
3537
|
}) {
|
|
3404
3538
|
if (this.processing && this.canMergeIntoTail(request)) {
|
|
3405
3539
|
const tail = this.queue[this.queue.length - 1];
|
|
3406
|
-
this.mergeInto(tail, request);
|
|
3407
3540
|
const response2 = {
|
|
3408
3541
|
queued: true,
|
|
3409
3542
|
messageId: tail.id,
|
|
3410
3543
|
position: this.queue.length
|
|
3411
3544
|
};
|
|
3412
|
-
const
|
|
3545
|
+
const cancellationVersion = this.queueCancellationVersion;
|
|
3546
|
+
const prior = this.acceptanceByMessage.get(tail) ?? Promise.resolve();
|
|
3547
|
+
const acceptance = prior.catch(() => {
|
|
3548
|
+
}).then(async () => {
|
|
3549
|
+
let acceptedMessage;
|
|
3550
|
+
while (true) {
|
|
3551
|
+
const baseMessage = tail.message;
|
|
3552
|
+
acceptedMessage = {
|
|
3553
|
+
...tail,
|
|
3554
|
+
message: `${baseMessage}${MERGED_MESSAGE_SEPARATOR}${request.message}`,
|
|
3555
|
+
...tail.images || request.images?.length ? { images: [...tail.images ?? [], ...request.images ?? []] } : {}
|
|
3556
|
+
};
|
|
3557
|
+
await onAccepted(response2, acceptedMessage);
|
|
3558
|
+
if (tail.message === baseMessage) break;
|
|
3559
|
+
}
|
|
3560
|
+
tail.message = acceptedMessage.message;
|
|
3561
|
+
if (acceptedMessage.images) tail.images = [...acceptedMessage.images];
|
|
3562
|
+
if (!this.queue.includes(tail) && this.queueCancellationVersion === cancellationVersion && this.acceptanceWithdrawals.delete(tail)) {
|
|
3563
|
+
this.queue.push(tail);
|
|
3564
|
+
}
|
|
3565
|
+
});
|
|
3413
3566
|
this.acceptanceByMessage.set(tail, acceptance);
|
|
3414
3567
|
await acceptance;
|
|
3415
3568
|
return response2;
|
|
@@ -3430,7 +3583,14 @@ var MessageQueueService = class {
|
|
|
3430
3583
|
messageId,
|
|
3431
3584
|
position: this.queue.length
|
|
3432
3585
|
};
|
|
3433
|
-
const acceptance = onAccepted(response2)
|
|
3586
|
+
const acceptance = onAccepted(response2, queuedMessage).catch((error) => {
|
|
3587
|
+
const index = this.queue.indexOf(queuedMessage);
|
|
3588
|
+
if (index !== -1) {
|
|
3589
|
+
this.queue.splice(index, 1);
|
|
3590
|
+
this.acceptanceWithdrawals.add(queuedMessage);
|
|
3591
|
+
}
|
|
3592
|
+
throw error;
|
|
3593
|
+
});
|
|
3434
3594
|
this.acceptanceByMessage.set(queuedMessage, acceptance);
|
|
3435
3595
|
await acceptance;
|
|
3436
3596
|
return response2;
|
|
@@ -3440,7 +3600,7 @@ var MessageQueueService = class {
|
|
|
3440
3600
|
messageId,
|
|
3441
3601
|
position: 0
|
|
3442
3602
|
};
|
|
3443
|
-
await onAccepted(response);
|
|
3603
|
+
await onAccepted(response, queuedMessage);
|
|
3444
3604
|
this.startProcessing(queuedMessage).catch((error) => {
|
|
3445
3605
|
console.error("[MessageQueue] Unhandled error in startProcessing:", error);
|
|
3446
3606
|
});
|
|
@@ -3452,12 +3612,6 @@ var MessageQueueService = class {
|
|
|
3452
3612
|
if (!tail) return false;
|
|
3453
3613
|
return tail.merge === true && tail.type === request.type;
|
|
3454
3614
|
}
|
|
3455
|
-
mergeInto(tail, request) {
|
|
3456
|
-
tail.message = `${tail.message}${MERGED_MESSAGE_SEPARATOR}${request.message}`;
|
|
3457
|
-
if (request.images && request.images.length > 0) {
|
|
3458
|
-
tail.images = [...tail.images ?? [], ...request.images];
|
|
3459
|
-
}
|
|
3460
|
-
}
|
|
3461
3615
|
async startProcessing(queuedMessage) {
|
|
3462
3616
|
this.processing = true;
|
|
3463
3617
|
this.onProcessingChanged(true);
|
|
@@ -3506,6 +3660,7 @@ var MessageQueueService = class {
|
|
|
3506
3660
|
drained.push(text);
|
|
3507
3661
|
totalChars += text.length;
|
|
3508
3662
|
}
|
|
3663
|
+
this.queueCancellationVersion++;
|
|
3509
3664
|
this.queue = [];
|
|
3510
3665
|
return drained;
|
|
3511
3666
|
}
|
|
@@ -3526,9 +3681,10 @@ var MessageQueueService = class {
|
|
|
3526
3681
|
return true;
|
|
3527
3682
|
}
|
|
3528
3683
|
clearQueue() {
|
|
3529
|
-
|
|
3684
|
+
const hadMessages = this.queue.length > 0;
|
|
3685
|
+
this.queueCancellationVersion++;
|
|
3530
3686
|
this.queue = [];
|
|
3531
|
-
return
|
|
3687
|
+
return hadMessages;
|
|
3532
3688
|
}
|
|
3533
3689
|
updateQueuedMessage(messageId, message) {
|
|
3534
3690
|
const queuedMessage = this.queue.find((item) => item.id === messageId);
|
|
@@ -3569,6 +3725,7 @@ var MessageQueueService = class {
|
|
|
3569
3725
|
* Reset everything including clearing processing state
|
|
3570
3726
|
*/
|
|
3571
3727
|
reset() {
|
|
3728
|
+
this.queueCancellationVersion++;
|
|
3572
3729
|
this.queue = [];
|
|
3573
3730
|
this.processing = false;
|
|
3574
3731
|
}
|
|
@@ -3647,6 +3804,21 @@ var CodingAgentManager = class {
|
|
|
3647
3804
|
this.onEvent(event);
|
|
3648
3805
|
historyFile.append(event);
|
|
3649
3806
|
}
|
|
3807
|
+
recordUserMessageHistoryEvent(request, historyFile, extraPayload = {}) {
|
|
3808
|
+
this.recordHistoryEvent(
|
|
3809
|
+
"event_msg",
|
|
3810
|
+
this.createUserMessageHistoryPayload(request, extraPayload),
|
|
3811
|
+
historyFile
|
|
3812
|
+
);
|
|
3813
|
+
}
|
|
3814
|
+
createUserMessageHistoryPayload(request, extraPayload = {}) {
|
|
3815
|
+
return {
|
|
3816
|
+
type: "user_message",
|
|
3817
|
+
message: request.message,
|
|
3818
|
+
...extraPayload,
|
|
3819
|
+
...request.messageId ? { [USER_MESSAGE_ID_PAYLOAD_KEY]: request.messageId } : {}
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3650
3822
|
initializeManager(processMessage) {
|
|
3651
3823
|
this.messageQueue = new MessageQueueService(
|
|
3652
3824
|
processMessage,
|
|
@@ -3655,6 +3827,11 @@ var CodingAgentManager = class {
|
|
|
3655
3827
|
);
|
|
3656
3828
|
this.initialized = this.initialize();
|
|
3657
3829
|
}
|
|
3830
|
+
async persistAcceptedMessage(event) {
|
|
3831
|
+
const history = this.getHistorySink();
|
|
3832
|
+
if (!history?.appendDurably) throw new Error("Chat history is unavailable");
|
|
3833
|
+
await history.appendDurably(event);
|
|
3834
|
+
}
|
|
3658
3835
|
async interrupt() {
|
|
3659
3836
|
const queue = this.messageQueue.drainQueue({
|
|
3660
3837
|
maxItems: MAX_INTERRUPT_QUEUE_ITEMS,
|
|
@@ -4262,12 +4439,13 @@ var CodexHistoryFile = class {
|
|
|
4262
4439
|
writeLock = new AsyncLock();
|
|
4263
4440
|
/** Best-effort ordered append; failures must not disrupt the turn. */
|
|
4264
4441
|
append(event) {
|
|
4265
|
-
void this.
|
|
4266
|
-
(
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
)
|
|
4442
|
+
void this.appendDurably(event).catch((error) => {
|
|
4443
|
+
console.error("[CodexHistoryFile] Failed to append event:", error);
|
|
4444
|
+
});
|
|
4445
|
+
}
|
|
4446
|
+
appendDurably(event) {
|
|
4447
|
+
return this.writeLock.run(() => appendFile2(this.filePath, `${JSON.stringify(event)}
|
|
4448
|
+
`));
|
|
4271
4449
|
}
|
|
4272
4450
|
async flush() {
|
|
4273
4451
|
await this.writeLock.drain();
|
|
@@ -4791,7 +4969,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
4791
4969
|
if (!handler) {
|
|
4792
4970
|
return { behavior: "allow" };
|
|
4793
4971
|
}
|
|
4794
|
-
const requestId =
|
|
4972
|
+
const requestId = randomUUID5();
|
|
4795
4973
|
const toolUseId = options.toolUseID;
|
|
4796
4974
|
const { options: requestOptions, questions: requestQuestions } = handler.getRequest(input);
|
|
4797
4975
|
const result = await new Promise((resolve5) => {
|
|
@@ -6462,6 +6640,13 @@ var DuplicateDefaultChatError = class extends Error {
|
|
|
6462
6640
|
this.name = "DuplicateDefaultChatError";
|
|
6463
6641
|
}
|
|
6464
6642
|
};
|
|
6643
|
+
var ChatMessagePersistenceError = class extends Error {
|
|
6644
|
+
constructor(cause) {
|
|
6645
|
+
super(`Failed to persist user message: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
6646
|
+
this.name = "ChatMessagePersistenceError";
|
|
6647
|
+
this.cause = cause;
|
|
6648
|
+
}
|
|
6649
|
+
};
|
|
6465
6650
|
|
|
6466
6651
|
// src/managers/codex-asp/codex-asp-manager.ts
|
|
6467
6652
|
var CodexQuotaError = class extends Error {
|
|
@@ -6571,7 +6756,13 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6571
6756
|
this.currentThreadId = this.initialSessionId ?? replayed?.transcript?.threadId ?? null;
|
|
6572
6757
|
}
|
|
6573
6758
|
getHistorySink() {
|
|
6574
|
-
return {
|
|
6759
|
+
return {
|
|
6760
|
+
append: (event) => this.trackHistoryEvent(event),
|
|
6761
|
+
appendDurably: (event) => {
|
|
6762
|
+
if (!this.historyFile) throw new Error("Chat history is unavailable");
|
|
6763
|
+
return this.historyFile.appendDurably(event);
|
|
6764
|
+
}
|
|
6765
|
+
};
|
|
6575
6766
|
}
|
|
6576
6767
|
async interruptActiveTurn() {
|
|
6577
6768
|
if (!this.currentThreadId || !this.activeTurnId) {
|
|
@@ -6769,13 +6960,10 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6769
6960
|
}
|
|
6770
6961
|
recordUserMessageEvent(request, extraPayload = {}) {
|
|
6771
6962
|
const images = imageContentToUserMessageImages(request.images);
|
|
6772
|
-
this.recordCodexHistoryEvent("event_msg", {
|
|
6773
|
-
type: "user_message",
|
|
6774
|
-
message: request.message,
|
|
6775
|
-
...request.messageId ? { [USER_MESSAGE_ID_PAYLOAD_KEY]: request.messageId } : {},
|
|
6963
|
+
this.recordCodexHistoryEvent("event_msg", this.createUserMessageHistoryPayload(request, {
|
|
6776
6964
|
...images ? { images } : {},
|
|
6777
6965
|
...extraPayload
|
|
6778
|
-
});
|
|
6966
|
+
}));
|
|
6779
6967
|
}
|
|
6780
6968
|
async processMessageInternal(request) {
|
|
6781
6969
|
let userMessageRecorded = false;
|
|
@@ -8088,10 +8276,7 @@ var CursorManager = class extends CodingAgentManager {
|
|
|
8088
8276
|
}
|
|
8089
8277
|
}, CURSOR_RECONNECT_NOTICE_DELAY_MS);
|
|
8090
8278
|
agent = await this.ensureAgent(request);
|
|
8091
|
-
this.
|
|
8092
|
-
type: "user_message",
|
|
8093
|
-
message: request.message
|
|
8094
|
-
}, this.historyFile);
|
|
8279
|
+
this.recordUserMessageHistoryEvent(request, this.historyFile);
|
|
8095
8280
|
run = await agent.send(message, {
|
|
8096
8281
|
model: { id: model },
|
|
8097
8282
|
mode: request.planMode ? "plan" : "agent",
|
|
@@ -8337,7 +8522,7 @@ ${instructions}
|
|
|
8337
8522
|
|
|
8338
8523
|
// src/managers/deepseek-manager.ts
|
|
8339
8524
|
import { spawn as spawn3 } from "child_process";
|
|
8340
|
-
import { randomUUID as
|
|
8525
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
8341
8526
|
import { createRequire } from "module";
|
|
8342
8527
|
import { dirname as dirname6, join as join21 } from "path";
|
|
8343
8528
|
import { fileURLToPath } from "url";
|
|
@@ -8379,7 +8564,7 @@ var DeepseekApiClient = class extends AbstractApiClient {
|
|
|
8379
8564
|
return this.readWebSocket("/api/events.host", signal, hostFrameSchema, onOpen);
|
|
8380
8565
|
}
|
|
8381
8566
|
async executeCommand(sessionId, line) {
|
|
8382
|
-
const rpcId =
|
|
8567
|
+
const rpcId = randomUUID6();
|
|
8383
8568
|
const response = await this.doFetch(new URL("/api/commands/execute", this.baseUrl), {
|
|
8384
8569
|
method: "POST",
|
|
8385
8570
|
headers: { "content-type": "application/json" },
|
|
@@ -8492,7 +8677,7 @@ var DeepseekManager = class extends CodingAgentManager {
|
|
|
8492
8677
|
mode: "steer",
|
|
8493
8678
|
content: [{ type: "text", text: request.message }]
|
|
8494
8679
|
}));
|
|
8495
|
-
this.
|
|
8680
|
+
this.recordUserMessageHistoryEvent(request, this.historyFile);
|
|
8496
8681
|
return true;
|
|
8497
8682
|
}
|
|
8498
8683
|
async getHistory(page = {}) {
|
|
@@ -8706,7 +8891,7 @@ var DeepseekManager = class extends CodingAgentManager {
|
|
|
8706
8891
|
...effort ? { reasoningEffort: effort } : {}
|
|
8707
8892
|
}));
|
|
8708
8893
|
}
|
|
8709
|
-
this.
|
|
8894
|
+
this.recordUserMessageHistoryEvent(request, this.historyFile);
|
|
8710
8895
|
this.turnCompletion = new Promise((resolve5) => {
|
|
8711
8896
|
this.resolveTurnCompletion = resolve5;
|
|
8712
8897
|
});
|
|
@@ -8747,7 +8932,7 @@ import { spawn as spawn5 } from "child_process";
|
|
|
8747
8932
|
|
|
8748
8933
|
// src/managers/acp-manager.ts
|
|
8749
8934
|
import { spawn as spawn4 } from "child_process";
|
|
8750
|
-
import { randomUUID as
|
|
8935
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
8751
8936
|
import { mkdir as mkdir13, readFile as readFile11, writeFile as writeFile6 } from "fs/promises";
|
|
8752
8937
|
import { dirname as dirname7 } from "path";
|
|
8753
8938
|
import { Writable } from "stream";
|
|
@@ -8920,7 +9105,7 @@ var AcpManager = class extends CodingAgentManager {
|
|
|
8920
9105
|
createTerminal(params) {
|
|
8921
9106
|
this.assertSession(params.sessionId);
|
|
8922
9107
|
if (this.planMode) throw new Error("ACP terminals are disabled in plan mode.");
|
|
8923
|
-
const terminalId =
|
|
9108
|
+
const terminalId = randomUUID7();
|
|
8924
9109
|
const outputByteLimit = Math.max(1, Math.min(params.outputByteLimit ?? 1048576, 10485760));
|
|
8925
9110
|
const child = spawn4(params.command, params.args ?? [], {
|
|
8926
9111
|
cwd: params.cwd ?? this.workingDirectory,
|
|
@@ -9051,7 +9236,7 @@ var AcpManager = class extends CodingAgentManager {
|
|
|
9051
9236
|
}
|
|
9052
9237
|
async requestUserInput(params) {
|
|
9053
9238
|
if (!this.interactiveTools) return { outcome: { outcome: "cancelled" } };
|
|
9054
|
-
const requestId =
|
|
9239
|
+
const requestId = randomUUID7();
|
|
9055
9240
|
const toolCallId = params.toolCall.toolCallId;
|
|
9056
9241
|
return new Promise((resolve5) => {
|
|
9057
9242
|
this.pendingPermissions.set(requestId, { toolCallId, options: params.options, resolve: resolve5 });
|
|
@@ -9112,7 +9297,7 @@ var AcpManager = class extends CodingAgentManager {
|
|
|
9112
9297
|
const context = this.context;
|
|
9113
9298
|
const sessionId = this.sessionId;
|
|
9114
9299
|
if (!context || !sessionId) throw new Error(`${this.acp.command} ACP failed to initialize.`);
|
|
9115
|
-
this.
|
|
9300
|
+
this.recordUserMessageHistoryEvent(request, this.historyFile);
|
|
9116
9301
|
this.planMode = request.planMode ?? false;
|
|
9117
9302
|
this.interactiveTools = request.enableInteractiveTools ?? false;
|
|
9118
9303
|
await this.setConfig("model", model);
|
|
@@ -9824,10 +10009,7 @@ var OpencodeManager = class extends CodingAgentManager {
|
|
|
9824
10009
|
const variant = await this.getThinkingVariant(client2, providerModel, request.thinkingLevel);
|
|
9825
10010
|
const sessionId = await this.ensureSession(client2, providerModel, agent, variant);
|
|
9826
10011
|
const system = this.buildCombinedInstructions(request.customInstructions);
|
|
9827
|
-
this.
|
|
9828
|
-
type: "user_message",
|
|
9829
|
-
message: request.message
|
|
9830
|
-
}, this.historyFile);
|
|
10012
|
+
this.recordUserMessageHistoryEvent(request, this.historyFile);
|
|
9831
10013
|
const result = await client2.session.prompt({
|
|
9832
10014
|
sessionID: sessionId,
|
|
9833
10015
|
directory: this.workingDirectory,
|
|
@@ -10261,7 +10443,7 @@ var PiManager = class extends CodingAgentManager {
|
|
|
10261
10443
|
const session = this.session;
|
|
10262
10444
|
if (!session?.isStreaming) return false;
|
|
10263
10445
|
await session.steer(request.message);
|
|
10264
|
-
this.recordUserMessage(session, request
|
|
10446
|
+
this.recordUserMessage(session, request);
|
|
10265
10447
|
return true;
|
|
10266
10448
|
}
|
|
10267
10449
|
async getHistory(page = {}) {
|
|
@@ -10386,18 +10568,18 @@ var PiManager = class extends CodingAgentManager {
|
|
|
10386
10568
|
const payload = eventPayload(event);
|
|
10387
10569
|
this.recordHistoryEvent(`pi-${event.type}`, payload, this.historyFile);
|
|
10388
10570
|
}
|
|
10389
|
-
recordUserMessage(session,
|
|
10390
|
-
const skillName = explicitSkillName(session, message);
|
|
10391
|
-
this.
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10571
|
+
recordUserMessage(session, request) {
|
|
10572
|
+
const skillName = explicitSkillName(session, request.message);
|
|
10573
|
+
this.recordUserMessageHistoryEvent(
|
|
10574
|
+
request,
|
|
10575
|
+
this.historyFile,
|
|
10576
|
+
skillName ? { skillName } : {}
|
|
10577
|
+
);
|
|
10396
10578
|
}
|
|
10397
10579
|
async processMessageInternal(request) {
|
|
10398
10580
|
try {
|
|
10399
10581
|
const session = await this.ensureSession(request);
|
|
10400
|
-
this.recordUserMessage(session, request
|
|
10582
|
+
this.recordUserMessage(session, request);
|
|
10401
10583
|
const images = request.images && request.images.length > 0 ? (await normalizeImages(request.images)).map((image) => ({
|
|
10402
10584
|
type: "image",
|
|
10403
10585
|
data: image.source.data,
|
|
@@ -11024,6 +11206,9 @@ var RelayManager = class {
|
|
|
11024
11206
|
async enqueueMessage(request, onAccepted) {
|
|
11025
11207
|
return this.inner.enqueueMessage(request, onAccepted);
|
|
11026
11208
|
}
|
|
11209
|
+
persistAcceptedMessage(event) {
|
|
11210
|
+
return this.inner.persistAcceptedMessage(event);
|
|
11211
|
+
}
|
|
11027
11212
|
async interrupt() {
|
|
11028
11213
|
return this.inner.interrupt();
|
|
11029
11214
|
}
|
|
@@ -11072,7 +11257,7 @@ import {
|
|
|
11072
11257
|
unlink as unlink3
|
|
11073
11258
|
} from "fs/promises";
|
|
11074
11259
|
import { join as join24 } from "path";
|
|
11075
|
-
import { randomUUID as
|
|
11260
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
11076
11261
|
|
|
11077
11262
|
// src/analytics/agent/activity/skill-mcp-call-extractor.ts
|
|
11078
11263
|
var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "deepseek", "fx", "kimi", "opencode", "pi", "custom", "dynamic"]);
|
|
@@ -11293,7 +11478,7 @@ var AgentChatTurnActivityTracker = class {
|
|
|
11293
11478
|
const credential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[provider];
|
|
11294
11479
|
this.pendingMessages.delete(messageId);
|
|
11295
11480
|
this.activeTurns.set(chatId, {
|
|
11296
|
-
turnId:
|
|
11481
|
+
turnId: randomUUID8(),
|
|
11297
11482
|
startedAtMs: Date.now(),
|
|
11298
11483
|
provider,
|
|
11299
11484
|
model: attributes.model ?? getDefaultAgentModel(provider),
|
|
@@ -12125,7 +12310,7 @@ var ChatService = class {
|
|
|
12125
12310
|
workingDirectory;
|
|
12126
12311
|
agentChatActivityTrackerService;
|
|
12127
12312
|
chats = /* @__PURE__ */ new Map();
|
|
12128
|
-
|
|
12313
|
+
persistPromise = null;
|
|
12129
12314
|
persistQueued = false;
|
|
12130
12315
|
async initialize() {
|
|
12131
12316
|
await mkdir17(ENGINE_DIR2, { recursive: true });
|
|
@@ -12191,7 +12376,7 @@ var ChatService = class {
|
|
|
12191
12376
|
}
|
|
12192
12377
|
const existingDefault = isDefaultChat({ provider: request.provider, title }) ? Array.from(this.chats.values()).find((chat) => !chat.persisted.deletedAt && chat.persisted.provider === request.provider && isDefaultChat(chat.persisted)) : void 0;
|
|
12193
12378
|
if (existingDefault) {
|
|
12194
|
-
if (!request.id || hasChatStarted(this.toSummary(existingDefault))) {
|
|
12379
|
+
if (!request.id || existingDefault.acceptingMessages > 0 || hasChatStarted(this.toSummary(existingDefault))) {
|
|
12195
12380
|
throw new DuplicateDefaultChatError(request.provider);
|
|
12196
12381
|
}
|
|
12197
12382
|
this.chats.delete(existingDefault.persisted.id);
|
|
@@ -12201,7 +12386,7 @@ var ChatService = class {
|
|
|
12201
12386
|
throw new ChatNotFoundError(parentChatId);
|
|
12202
12387
|
}
|
|
12203
12388
|
const persisted = {
|
|
12204
|
-
id: request.id ??
|
|
12389
|
+
id: request.id ?? randomUUID9(),
|
|
12205
12390
|
provider: request.provider,
|
|
12206
12391
|
title,
|
|
12207
12392
|
createdAt: now,
|
|
@@ -12223,42 +12408,94 @@ var ChatService = class {
|
|
|
12223
12408
|
}
|
|
12224
12409
|
async sendMessage(chatId, request) {
|
|
12225
12410
|
const chat = this.requireChat(chatId);
|
|
12226
|
-
|
|
12227
|
-
|
|
12411
|
+
const idempotencyKey = request.idempotencyKey ?? request.messageId;
|
|
12412
|
+
if (idempotencyKey) {
|
|
12413
|
+
const accepted = chat.acceptedSendResponses.get(idempotencyKey);
|
|
12228
12414
|
if (accepted) {
|
|
12229
12415
|
return accepted;
|
|
12230
12416
|
}
|
|
12417
|
+
const accepting2 = chat.acceptingSendResponses.get(idempotencyKey);
|
|
12418
|
+
if (accepting2) return accepting2;
|
|
12231
12419
|
}
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12420
|
+
const accepting = (async () => {
|
|
12421
|
+
chat.acceptingMessages += 1;
|
|
12422
|
+
try {
|
|
12423
|
+
return await chat.provider.enqueueMessage(
|
|
12424
|
+
request,
|
|
12425
|
+
(result, acceptedMessage) => this.handleMessageAccepted(
|
|
12426
|
+
chatId,
|
|
12427
|
+
chat,
|
|
12428
|
+
request,
|
|
12429
|
+
acceptedMessage,
|
|
12430
|
+
result
|
|
12431
|
+
)
|
|
12432
|
+
);
|
|
12433
|
+
} finally {
|
|
12434
|
+
chat.acceptingMessages -= 1;
|
|
12435
|
+
}
|
|
12436
|
+
})();
|
|
12437
|
+
if (!idempotencyKey) return accepting;
|
|
12438
|
+
chat.acceptingSendResponses.set(idempotencyKey, accepting);
|
|
12439
|
+
try {
|
|
12440
|
+
return await accepting;
|
|
12441
|
+
} finally {
|
|
12442
|
+
if (chat.acceptingSendResponses.get(idempotencyKey) === accepting) {
|
|
12443
|
+
chat.acceptingSendResponses.delete(idempotencyKey);
|
|
12243
12444
|
}
|
|
12244
|
-
chat.persisted.acceptedSendResponses = Object.fromEntries(chat.acceptedSendResponses);
|
|
12245
12445
|
}
|
|
12446
|
+
}
|
|
12447
|
+
async handleMessageAccepted(chatId, chat, request, acceptedMessage, result) {
|
|
12246
12448
|
const submittedAt = request.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
12247
12449
|
const { event: acceptedEvent, sender: recordedSender } = createTurnMetadata(
|
|
12248
|
-
|
|
12450
|
+
acceptedMessage,
|
|
12249
12451
|
result.messageId,
|
|
12250
12452
|
submittedAt
|
|
12251
12453
|
);
|
|
12454
|
+
try {
|
|
12455
|
+
await chat.provider.persistAcceptedMessage(acceptedEvent);
|
|
12456
|
+
} catch (error) {
|
|
12457
|
+
throw new ChatMessagePersistenceError(error);
|
|
12458
|
+
}
|
|
12459
|
+
const previousAcceptedSendResponses = new Map(chat.acceptedSendResponses);
|
|
12460
|
+
const previousPendingMessageIds = [...chat.pendingMessageIds];
|
|
12461
|
+
const previousAcceptedEvent = chat.acceptedUserEvents.get(result.messageId);
|
|
12462
|
+
const previousPersistedResponses = chat.persisted.acceptedSendResponses;
|
|
12463
|
+
const previousLastMessageText = chat.persisted.lastMessageText;
|
|
12464
|
+
const previousUpdatedAt = chat.persisted.updatedAt;
|
|
12465
|
+
const idempotencyKey = request.idempotencyKey ?? request.messageId;
|
|
12466
|
+
if (idempotencyKey) {
|
|
12467
|
+
chat.acceptedSendResponses.set(idempotencyKey, result);
|
|
12468
|
+
for (const key of chat.acceptedSendResponses.keys()) {
|
|
12469
|
+
if (chat.acceptedSendResponses.size <= MAX_ACCEPTED_SEND_RESPONSES) break;
|
|
12470
|
+
chat.acceptedSendResponses.delete(key);
|
|
12471
|
+
}
|
|
12472
|
+
chat.persisted.acceptedSendResponses = Object.fromEntries(chat.acceptedSendResponses);
|
|
12473
|
+
}
|
|
12252
12474
|
if (!chat.pendingMessageIds.includes(result.messageId)) {
|
|
12253
12475
|
chat.pendingMessageIds.push(result.messageId);
|
|
12254
12476
|
}
|
|
12477
|
+
chat.acceptedUserEvents.set(result.messageId, acceptedEvent);
|
|
12478
|
+
chat.persisted.lastMessageText = request.message.trim().slice(0, LAST_MESSAGE_PREVIEW_MAX) || null;
|
|
12479
|
+
chat.persisted.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12480
|
+
try {
|
|
12481
|
+
await this.persistAllChats();
|
|
12482
|
+
} catch (error) {
|
|
12483
|
+
chat.acceptedSendResponses.clear();
|
|
12484
|
+
for (const [key, response] of previousAcceptedSendResponses) {
|
|
12485
|
+
chat.acceptedSendResponses.set(key, response);
|
|
12486
|
+
}
|
|
12487
|
+
chat.pendingMessageIds = previousPendingMessageIds;
|
|
12488
|
+
if (previousAcceptedEvent) chat.acceptedUserEvents.set(result.messageId, previousAcceptedEvent);
|
|
12489
|
+
else chat.acceptedUserEvents.delete(result.messageId);
|
|
12490
|
+
chat.persisted.acceptedSendResponses = previousPersistedResponses;
|
|
12491
|
+
chat.persisted.lastMessageText = previousLastMessageText;
|
|
12492
|
+
chat.persisted.updatedAt = previousUpdatedAt;
|
|
12493
|
+
throw new ChatMessagePersistenceError(error);
|
|
12494
|
+
}
|
|
12255
12495
|
this.agentChatActivityTrackerService.noteMessageAccepted(result.messageId, request);
|
|
12256
12496
|
if (request.errorNotificationTarget) {
|
|
12257
12497
|
chat.errorNotificationTargets.set(result.messageId, request.errorNotificationTarget);
|
|
12258
12498
|
}
|
|
12259
|
-
chat.acceptedUserEvents.set(result.messageId, acceptedEvent);
|
|
12260
|
-
chat.persisted.lastMessageText = request.message.trim().slice(0, LAST_MESSAGE_PREVIEW_MAX) || null;
|
|
12261
|
-
this.touch(chat);
|
|
12262
12499
|
if (recordedSender) {
|
|
12263
12500
|
await this.appendSender(chatId, recordedSender);
|
|
12264
12501
|
}
|
|
@@ -12535,15 +12772,16 @@ var ChatService = class {
|
|
|
12535
12772
|
chat.provider.getHistory(page),
|
|
12536
12773
|
this.readSenders(chatId)
|
|
12537
12774
|
]);
|
|
12775
|
+
const historyEvents = mergeWorkspaceChatHistoryEvents([], history.events);
|
|
12538
12776
|
for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
|
|
12539
|
-
if (acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) ||
|
|
12777
|
+
if (acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) || historyEvents.some((event) => isSameAcceptedUserEvent(event, acceptedEvent))) {
|
|
12540
12778
|
chat.acceptedUserEvents.delete(messageId);
|
|
12541
12779
|
}
|
|
12542
12780
|
}
|
|
12543
12781
|
const isLatestPage = isLatestChatHistoryPage(page);
|
|
12544
12782
|
const queuedMessageIds = new Set(chat.provider.getQueue().map((message) => message.id));
|
|
12545
|
-
const acceptedEvents = (isLatestPage ? [...chat.acceptedUserEvents.entries()] : []).filter(([messageId]) => messageId === chat.activeMessageId || !chat.hasActiveTurn && chat.pendingMessageIds[0] === messageId && !queuedMessageIds.has(messageId)).map(([, acceptedEvent]) => acceptedEvent).filter((acceptedEvent) => !acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) && !
|
|
12546
|
-
const events = [...
|
|
12783
|
+
const acceptedEvents = (isLatestPage ? [...chat.acceptedUserEvents.entries()] : []).filter(([messageId]) => messageId === chat.activeMessageId || !chat.hasActiveTurn && chat.pendingMessageIds[0] === messageId && !queuedMessageIds.has(messageId)).map(([, acceptedEvent]) => acceptedEvent).filter((acceptedEvent) => !acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) && !historyEvents.some((event) => isSameAcceptedUserEvent(event, acceptedEvent)));
|
|
12784
|
+
const events = [...historyEvents, ...acceptedEvents].sort((a, b) => getEventTimestampMs(a) - getEventTimestampMs(b));
|
|
12547
12785
|
if (page.limit !== void 0 && "beforeCursor" in history) {
|
|
12548
12786
|
const cursor = parseChatHistoryCursor(history.beforeCursor ?? void 0);
|
|
12549
12787
|
return {
|
|
@@ -12561,7 +12799,7 @@ var ChatService = class {
|
|
|
12561
12799
|
return {
|
|
12562
12800
|
...history,
|
|
12563
12801
|
events,
|
|
12564
|
-
totalEvents: (history.totalEvents ?? history.eventsStartIndex + history.events.length) + acceptedEvents.length,
|
|
12802
|
+
totalEvents: (history.totalEvents ?? history.eventsStartIndex + history.events.length) - (history.events.length - historyEvents.length) + acceptedEvents.length,
|
|
12565
12803
|
goal: history.goal ?? chat.provider.getGoal?.() ?? null,
|
|
12566
12804
|
senders
|
|
12567
12805
|
};
|
|
@@ -12760,6 +12998,8 @@ var ChatService = class {
|
|
|
12760
12998
|
pendingMessageIds: [],
|
|
12761
12999
|
acceptedUserEvents: /* @__PURE__ */ new Map(),
|
|
12762
13000
|
acceptedSendResponses: new Map(Object.entries(persisted.acceptedSendResponses ?? {})),
|
|
13001
|
+
acceptingSendResponses: /* @__PURE__ */ new Map(),
|
|
13002
|
+
acceptingMessages: 0,
|
|
12763
13003
|
activeMessageId: null,
|
|
12764
13004
|
hasActiveTurn: false,
|
|
12765
13005
|
observedBranchesByRepo: /* @__PURE__ */ new Map(),
|
|
@@ -12965,35 +13205,33 @@ var ChatService = class {
|
|
|
12965
13205
|
}
|
|
12966
13206
|
}
|
|
12967
13207
|
}
|
|
12968
|
-
|
|
12969
|
-
if (this.
|
|
13208
|
+
persistAllChats() {
|
|
13209
|
+
if (this.persistPromise) {
|
|
12970
13210
|
this.persistQueued = true;
|
|
12971
|
-
return;
|
|
13211
|
+
return this.persistPromise;
|
|
12972
13212
|
}
|
|
12973
|
-
this.
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
12980
|
-
|
|
13213
|
+
this.persistPromise = (async () => {
|
|
13214
|
+
do {
|
|
13215
|
+
this.persistQueued = false;
|
|
13216
|
+
const payload = Array.from(this.chats.values()).map((chat) => chat.persisted);
|
|
13217
|
+
try {
|
|
13218
|
+
await copyFile(CHATS_FILE, CHATS_BACKUP_FILE);
|
|
13219
|
+
} catch (error) {
|
|
13220
|
+
if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) {
|
|
13221
|
+
console.error("[ChatService] Failed to update chats backup:", error);
|
|
13222
|
+
}
|
|
12981
13223
|
}
|
|
12982
|
-
|
|
12983
|
-
await atomicWriteFile(CHATS_FILE, `${JSON.stringify(payload, null, 2)}
|
|
13224
|
+
await atomicWriteFile(CHATS_FILE, `${JSON.stringify(payload, null, 2)}
|
|
12984
13225
|
`);
|
|
12985
|
-
|
|
12986
|
-
}
|
|
12987
|
-
this.
|
|
12988
|
-
|
|
12989
|
-
|
|
12990
|
-
void this.persistAllChats();
|
|
12991
|
-
}
|
|
12992
|
-
}
|
|
13226
|
+
} while (this.persistQueued);
|
|
13227
|
+
})().finally(() => {
|
|
13228
|
+
this.persistPromise = null;
|
|
13229
|
+
});
|
|
13230
|
+
return this.persistPromise;
|
|
12993
13231
|
}
|
|
12994
13232
|
async publish(input) {
|
|
12995
13233
|
const event = {
|
|
12996
|
-
id:
|
|
13234
|
+
id: randomUUID9(),
|
|
12997
13235
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12998
13236
|
...input
|
|
12999
13237
|
};
|
|
@@ -13735,7 +13973,7 @@ ${combinedScript}` : combinedScript;
|
|
|
13735
13973
|
}
|
|
13736
13974
|
|
|
13737
13975
|
// src/services/terminal-service.ts
|
|
13738
|
-
import { randomUUID as
|
|
13976
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
13739
13977
|
import { existsSync as existsSync10 } from "fs";
|
|
13740
13978
|
import { spawn as spawn7 } from "node-pty";
|
|
13741
13979
|
var MAX_REPLAY_CHARS = 1024 * 1024;
|
|
@@ -13754,7 +13992,7 @@ var TerminalService = class {
|
|
|
13754
13992
|
code: "limit"
|
|
13755
13993
|
});
|
|
13756
13994
|
}
|
|
13757
|
-
const id =
|
|
13995
|
+
const id = randomUUID10();
|
|
13758
13996
|
const shell = process.env.SHELL && existsSync10(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
|
|
13759
13997
|
const pty = spawn7(shell, ["-l"], {
|
|
13760
13998
|
name: "xterm-256color",
|
|
@@ -14109,6 +14347,9 @@ function createV1Routes(deps) {
|
|
|
14109
14347
|
if (details.includes("Chat not found")) {
|
|
14110
14348
|
return c.json(jsonError("Failed to send message", details), 404);
|
|
14111
14349
|
}
|
|
14350
|
+
if (error instanceof ChatMessagePersistenceError) {
|
|
14351
|
+
return c.json(jsonError("Failed to save user message", details), 500);
|
|
14352
|
+
}
|
|
14112
14353
|
return c.json(jsonError("Failed to send message", details), 400);
|
|
14113
14354
|
}
|
|
14114
14355
|
});
|
|
@@ -15040,7 +15281,7 @@ function startStatusBroadcaster() {
|
|
|
15040
15281
|
if (serialized !== previousRepoStatus) {
|
|
15041
15282
|
previousRepoStatus = serialized;
|
|
15042
15283
|
eventService.publish({
|
|
15043
|
-
id:
|
|
15284
|
+
id: randomUUID11(),
|
|
15044
15285
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15045
15286
|
type: "repo.status.changed",
|
|
15046
15287
|
payload: { repos }
|
|
@@ -15061,7 +15302,7 @@ function startStatusBroadcaster() {
|
|
|
15061
15302
|
if (engineStatusJson !== previousEngineStatus) {
|
|
15062
15303
|
previousEngineStatus = engineStatusJson;
|
|
15063
15304
|
eventService.publish({
|
|
15064
|
-
id:
|
|
15305
|
+
id: randomUUID11(),
|
|
15065
15306
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15066
15307
|
type: "engine.status.changed",
|
|
15067
15308
|
payload: { status: engineStatus }
|
|
@@ -15080,7 +15321,7 @@ function startStatusBroadcaster() {
|
|
|
15080
15321
|
previousHookStatus = hookSnapshot;
|
|
15081
15322
|
if (!lastHooksRunning && hooksRunning) {
|
|
15082
15323
|
eventService.publish({
|
|
15083
|
-
id:
|
|
15324
|
+
id: randomUUID11(),
|
|
15084
15325
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15085
15326
|
type: "hooks.started",
|
|
15086
15327
|
payload: { running: true, completed: false }
|
|
@@ -15089,7 +15330,7 @@ function startStatusBroadcaster() {
|
|
|
15089
15330
|
}
|
|
15090
15331
|
if (hooksRunning) {
|
|
15091
15332
|
eventService.publish({
|
|
15092
|
-
id:
|
|
15333
|
+
id: randomUUID11(),
|
|
15093
15334
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15094
15335
|
type: "hooks.progress",
|
|
15095
15336
|
payload: { running: true, completed: false }
|
|
@@ -15098,7 +15339,7 @@ function startStatusBroadcaster() {
|
|
|
15098
15339
|
}
|
|
15099
15340
|
if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
|
|
15100
15341
|
eventService.publish({
|
|
15101
|
-
id:
|
|
15342
|
+
id: randomUUID11(),
|
|
15102
15343
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15103
15344
|
type: "hooks.completed",
|
|
15104
15345
|
payload: { running: false, completed: true }
|
|
@@ -15107,7 +15348,7 @@ function startStatusBroadcaster() {
|
|
|
15107
15348
|
}
|
|
15108
15349
|
if (lastHooksRunning && !hooksRunning && hooksFailed) {
|
|
15109
15350
|
eventService.publish({
|
|
15110
|
-
id:
|
|
15351
|
+
id: randomUUID11(),
|
|
15111
15352
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15112
15353
|
type: "hooks.failed",
|
|
15113
15354
|
payload: { running: false, completed: hooksCompleted }
|
|
@@ -15115,7 +15356,7 @@ function startStatusBroadcaster() {
|
|
|
15115
15356
|
});
|
|
15116
15357
|
}
|
|
15117
15358
|
eventService.publish({
|
|
15118
|
-
id:
|
|
15359
|
+
id: randomUUID11(),
|
|
15119
15360
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15120
15361
|
type: "hooks.status",
|
|
15121
15362
|
payload: {
|
|
@@ -15170,20 +15411,20 @@ serve(
|
|
|
15170
15411
|
}
|
|
15171
15412
|
const repos = await gitService.listRepos();
|
|
15172
15413
|
await eventService.publish({
|
|
15173
|
-
id:
|
|
15414
|
+
id: randomUUID11(),
|
|
15174
15415
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15175
15416
|
type: "repo.discovered",
|
|
15176
15417
|
payload: { repos }
|
|
15177
15418
|
});
|
|
15178
15419
|
const repoStatuses = await gitService.listRepos();
|
|
15179
15420
|
await eventService.publish({
|
|
15180
|
-
id:
|
|
15421
|
+
id: randomUUID11(),
|
|
15181
15422
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15182
15423
|
type: "repo.status.changed",
|
|
15183
15424
|
payload: { repos: repoStatuses }
|
|
15184
15425
|
});
|
|
15185
15426
|
await eventService.publish({
|
|
15186
|
-
id:
|
|
15427
|
+
id: randomUUID11(),
|
|
15187
15428
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15188
15429
|
type: "engine.ready",
|
|
15189
15430
|
payload: { version: "v1" }
|