openmates 0.27.0-alpha.57 → 0.27.0-alpha.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EHETUQZT.js → chunk-ACLJYOK7.js} +1232 -1128
- package/dist/{cli-D2oJ-ctT.d.ts → cli-BAQLcaZb.d.ts} +3 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/manifest.json +9 -9
- package/package.json +4 -3
|
@@ -2395,8 +2395,13 @@ async function parseGenericImportBuffer(payload, sourceName = "generic-transcrip
|
|
|
2395
2395
|
}
|
|
2396
2396
|
|
|
2397
2397
|
// src/http.ts
|
|
2398
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2398
2399
|
var DEFAULT_JSON_REQUEST_TIMEOUT_MS = 18e4;
|
|
2399
2400
|
var JSON_REQUEST_TIMEOUT_ENV = "OPENMATES_CLI_HTTP_TIMEOUT_MS";
|
|
2401
|
+
var requestAbortScope = new AsyncLocalStorage();
|
|
2402
|
+
function withHttpAbortSignal(signal, operation) {
|
|
2403
|
+
return requestAbortScope.run(signal, operation);
|
|
2404
|
+
}
|
|
2400
2405
|
var OpenMatesHttpClient = class {
|
|
2401
2406
|
apiUrl;
|
|
2402
2407
|
cookies;
|
|
@@ -2505,7 +2510,12 @@ var OpenMatesHttpClient = class {
|
|
|
2505
2510
|
if (body !== void 0) {
|
|
2506
2511
|
requestHeaders["Content-Type"] = "application/json";
|
|
2507
2512
|
}
|
|
2513
|
+
const scopedSignal = requestAbortScope.getStore();
|
|
2514
|
+
scopedSignal?.throwIfAborted();
|
|
2508
2515
|
const controller = new AbortController();
|
|
2516
|
+
const onScopedAbort = () => controller.abort(scopedSignal?.reason);
|
|
2517
|
+
scopedSignal?.addEventListener("abort", onScopedAbort, { once: true });
|
|
2518
|
+
if (scopedSignal?.aborted) onScopedAbort();
|
|
2509
2519
|
const timeout = setTimeout(() => controller.abort(), getJsonRequestTimeoutMs());
|
|
2510
2520
|
try {
|
|
2511
2521
|
let response;
|
|
@@ -2517,19 +2527,23 @@ var OpenMatesHttpClient = class {
|
|
|
2517
2527
|
signal: controller.signal
|
|
2518
2528
|
});
|
|
2519
2529
|
} catch (error) {
|
|
2530
|
+
if (scopedSignal?.aborted) throw new Error(`HTTP ${method} ${path2} cancelled`);
|
|
2520
2531
|
if (controller.signal.aborted) {
|
|
2521
2532
|
throw new Error(`HTTP ${method} ${path2} timed out after ${getJsonRequestTimeoutMs()}ms`);
|
|
2522
2533
|
}
|
|
2523
2534
|
throw error;
|
|
2524
2535
|
}
|
|
2525
2536
|
await this.captureCookies(response, path2, sessionId, sentToken);
|
|
2537
|
+
scopedSignal?.throwIfAborted();
|
|
2526
2538
|
let data = {};
|
|
2527
2539
|
try {
|
|
2528
2540
|
data = await response.json();
|
|
2529
2541
|
} catch {
|
|
2542
|
+
if (scopedSignal?.aborted) throw new Error(`HTTP ${method} ${path2} cancelled`);
|
|
2530
2543
|
if (controller.signal.aborted) throw new Error(`HTTP ${method} ${path2} timed out`);
|
|
2531
2544
|
data = {};
|
|
2532
2545
|
}
|
|
2546
|
+
scopedSignal?.throwIfAborted();
|
|
2533
2547
|
return {
|
|
2534
2548
|
ok: response.ok,
|
|
2535
2549
|
status: response.status,
|
|
@@ -2538,6 +2552,7 @@ var OpenMatesHttpClient = class {
|
|
|
2538
2552
|
};
|
|
2539
2553
|
} finally {
|
|
2540
2554
|
clearTimeout(timeout);
|
|
2555
|
+
scopedSignal?.removeEventListener("abort", onScopedAbort);
|
|
2541
2556
|
}
|
|
2542
2557
|
}
|
|
2543
2558
|
formatCookieHeader() {
|
|
@@ -13203,6 +13218,20 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13203
13218
|
if (!current2()) throw new Error("Chat workspace changed");
|
|
13204
13219
|
return result;
|
|
13205
13220
|
}
|
|
13221
|
+
chatActivitySocket = null;
|
|
13222
|
+
/** Warm the active chat scope over the TUI's existing authenticated activity socket. */
|
|
13223
|
+
warmChatMetadata() {
|
|
13224
|
+
const activity = this.chatActivitySocket;
|
|
13225
|
+
if (!activity || !activity.ownerCurrent()) return;
|
|
13226
|
+
const teamId = this.getActiveTeamId();
|
|
13227
|
+
const scope2 = teamId ?? "personal";
|
|
13228
|
+
if (activity.lastWarmupScope === scope2) return;
|
|
13229
|
+
try {
|
|
13230
|
+
activity.ws.send("chat_metadata_warmup", teamId ? { team_id: teamId } : {});
|
|
13231
|
+
activity.lastWarmupScope = scope2;
|
|
13232
|
+
} catch {
|
|
13233
|
+
}
|
|
13234
|
+
}
|
|
13206
13235
|
/** Observe lifecycle metadata without claiming or collecting AI responses. */
|
|
13207
13236
|
async observeChatActivity(onChange) {
|
|
13208
13237
|
const session = this.requireSession();
|
|
@@ -13231,6 +13260,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13231
13260
|
removeHandlers = [];
|
|
13232
13261
|
const active = socket;
|
|
13233
13262
|
socket = null;
|
|
13263
|
+
if (this.chatActivitySocket?.ws === active) this.chatActivitySocket = null;
|
|
13234
13264
|
active?.close();
|
|
13235
13265
|
};
|
|
13236
13266
|
const scheduleReconnect = () => {
|
|
@@ -13256,6 +13286,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13256
13286
|
return;
|
|
13257
13287
|
}
|
|
13258
13288
|
socket = opened;
|
|
13289
|
+
this.chatActivitySocket = { ws: opened, ownerCurrent, lastWarmupScope: null };
|
|
13290
|
+
this.warmChatMetadata();
|
|
13259
13291
|
removeHandlers = types.map((type) => opened.onMessageType(type, () => {
|
|
13260
13292
|
if (!ownerCurrent()) {
|
|
13261
13293
|
stop();
|
|
@@ -13269,6 +13301,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13269
13301
|
removeHandlers.push(opened.onClose(() => {
|
|
13270
13302
|
if (socket !== opened) return;
|
|
13271
13303
|
socket = null;
|
|
13304
|
+
if (this.chatActivitySocket?.ws === opened) this.chatActivitySocket = null;
|
|
13272
13305
|
removeHandlers.forEach((remove) => remove());
|
|
13273
13306
|
removeHandlers = [];
|
|
13274
13307
|
scheduleReconnect();
|
|
@@ -15510,6 +15543,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15510
15543
|
params.onPrivacyPrepared?.(finalMessage);
|
|
15511
15544
|
const explicitTasksAppSkill = messageExplicitlyRequestsTasksAppSkill(finalMessage);
|
|
15512
15545
|
const taskUpdateJobsEnabled = params.taskUpdateJobs !== false && !explicitTasksAppSkill;
|
|
15546
|
+
const authSocketStartedAt = Date.now();
|
|
15513
15547
|
const { ws, ownerId: ownerId2 } = await this.openWsClient({
|
|
15514
15548
|
taskUpdateJobs: taskUpdateJobsEnabled,
|
|
15515
15549
|
projectFileJobs: !params.incognito,
|
|
@@ -15520,994 +15554,937 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15520
15554
|
ws.close();
|
|
15521
15555
|
throw new Error("Authenticated user identity is required for saved chat recovery.");
|
|
15522
15556
|
}
|
|
15523
|
-
|
|
15524
|
-
|
|
15525
|
-
|
|
15526
|
-
|
|
15527
|
-
|
|
15528
|
-
|
|
15529
|
-
|
|
15530
|
-
}
|
|
15531
|
-
const messageId = randomUUID9();
|
|
15532
|
-
const wallCreatedAt = Math.floor(Date.now() / 1e3);
|
|
15533
|
-
const createdAt = !teamId && !params.incognito && params.chatId ? nextPersonalUserCreatedAt(wallCreatedAt, savedPrivacyHistory?.messages.map((row3) => row3.createdAt) ?? []) : wallCreatedAt;
|
|
15534
|
-
const isNewChat = !params.chatId;
|
|
15535
|
-
let messageHistoryForRequest = params.messageHistory;
|
|
15536
|
-
if (!params.incognito && !isNewChat && !messageHistoryForRequest) {
|
|
15537
|
-
const { messages } = savedPrivacyHistory;
|
|
15538
|
-
messageHistoryForRequest = buildSavedChatMessageHistory(messages, chatId);
|
|
15557
|
+
const authSocketMs = Date.now() - authSocketStartedAt;
|
|
15558
|
+
let contextSession;
|
|
15559
|
+
try {
|
|
15560
|
+
contextSession = this.requireSession();
|
|
15561
|
+
} catch (error) {
|
|
15562
|
+
ws.close();
|
|
15563
|
+
throw error;
|
|
15539
15564
|
}
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
)
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
}
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15586
|
-
|
|
15587
|
-
|
|
15588
|
-
|
|
15589
|
-
|
|
15565
|
+
const {
|
|
15566
|
+
apiUrl: contextApiUrl,
|
|
15567
|
+
hashedEmail: contextHashedEmail,
|
|
15568
|
+
sessionId: contextSessionId,
|
|
15569
|
+
createdAt: contextSessionCreatedAt,
|
|
15570
|
+
masterKeyExportedB64: contextMasterKey,
|
|
15571
|
+
activeTeamId: contextActiveTeamId
|
|
15572
|
+
} = contextSession;
|
|
15573
|
+
const contextScope = { teamId, personal: !teamId };
|
|
15574
|
+
const contextStartedAt = Date.now();
|
|
15575
|
+
let recoveryMs = 0;
|
|
15576
|
+
const contextOwnerIsCurrent = () => {
|
|
15577
|
+
const current2 = this.session;
|
|
15578
|
+
return Boolean(current2 && current2.apiUrl === contextApiUrl && current2.hashedEmail === contextHashedEmail && current2.sessionId === contextSessionId && current2.createdAt === contextSessionCreatedAt && current2.masterKeyExportedB64 === contextMasterKey && current2.activeTeamId === contextActiveTeamId);
|
|
15579
|
+
};
|
|
15580
|
+
const earlyContextAbort = new AbortController();
|
|
15581
|
+
const earlyContext = !params.incognito && !params.chatId && !params.projectId ? withHttpAbortSignal(earlyContextAbort.signal, () => prepareCliJevContext(this, chatId, contextScope, params.jevContext, finalMessage)).then(
|
|
15582
|
+
(value) => ({ ok: true, value, elapsedMs: Date.now() - contextStartedAt }),
|
|
15583
|
+
(error) => ({ ok: false, error, elapsedMs: Date.now() - contextStartedAt })
|
|
15584
|
+
) : null;
|
|
15585
|
+
try {
|
|
15586
|
+
if (!params.incognito) {
|
|
15587
|
+
try {
|
|
15588
|
+
ws.send("chat_metadata_warmup", teamId ? { team_id: teamId } : {});
|
|
15589
|
+
} catch {
|
|
15590
|
+
}
|
|
15591
|
+
try {
|
|
15592
|
+
const recoveryStartedAt = Date.now();
|
|
15593
|
+
await ws.waitForRecoveryOutputDiscovery();
|
|
15594
|
+
recoveryMs = Date.now() - recoveryStartedAt;
|
|
15595
|
+
} catch (error) {
|
|
15596
|
+
ws.close();
|
|
15597
|
+
throw error;
|
|
15598
|
+
}
|
|
15599
|
+
}
|
|
15600
|
+
const messageId = randomUUID9();
|
|
15601
|
+
const wallCreatedAt = Math.floor(Date.now() / 1e3);
|
|
15602
|
+
const createdAt = !teamId && !params.incognito && params.chatId ? nextPersonalUserCreatedAt(wallCreatedAt, savedPrivacyHistory?.messages.map((row3) => row3.createdAt) ?? []) : wallCreatedAt;
|
|
15603
|
+
const isNewChat = !params.chatId;
|
|
15604
|
+
let messageHistoryForRequest = params.messageHistory;
|
|
15605
|
+
if (!params.incognito && !isNewChat && !messageHistoryForRequest) {
|
|
15606
|
+
const { messages } = savedPrivacyHistory;
|
|
15607
|
+
messageHistoryForRequest = buildSavedChatMessageHistory(messages, chatId);
|
|
15608
|
+
}
|
|
15609
|
+
ws.send("set_active_chat", { chat_id: chatId, ...teamId ? { team_id: teamId } : {} });
|
|
15610
|
+
const connectedAccountDirectory = buildConnectedAccountDirectoryPayload(
|
|
15611
|
+
params.connectedAccountDirectory
|
|
15612
|
+
);
|
|
15613
|
+
const connectedAccountTokenRefs = params.connectedAccountTokenRefInputs?.length ? await this.createTurnTokenRefs({
|
|
15614
|
+
chatId,
|
|
15615
|
+
messageId,
|
|
15616
|
+
refs: params.connectedAccountTokenRefInputs,
|
|
15617
|
+
teamId
|
|
15618
|
+
}) : [];
|
|
15619
|
+
assertNoConnectedAccountSecretLeak(connectedAccountTokenRefs);
|
|
15620
|
+
let chatKeyBytes = null;
|
|
15621
|
+
let activeFocusId = null;
|
|
15622
|
+
let encryptedChatKey = null;
|
|
15623
|
+
let chatSlugLookupKey = null;
|
|
15624
|
+
let focusPhaseState = {};
|
|
15625
|
+
let baselineMessagesV = 0;
|
|
15626
|
+
let notificationTitle = null;
|
|
15627
|
+
let terminalExpectedMessagesV = 1;
|
|
15628
|
+
let savedTurnId = null;
|
|
15629
|
+
if (!params.incognito) {
|
|
15630
|
+
const masterKey = this.getMasterKeyBytes();
|
|
15631
|
+
const wrappingKey = await this.getChatWrappingKey(teamId, masterKey);
|
|
15632
|
+
chatSlugLookupKey = wrappingKey;
|
|
15633
|
+
if (isNewChat) {
|
|
15634
|
+
chatKeyBytes = globalThis.crypto ? new Uint8Array(globalThis.crypto.getRandomValues(new Uint8Array(32))) : new Uint8Array(
|
|
15635
|
+
(await import("crypto")).webcrypto.getRandomValues(
|
|
15636
|
+
new Uint8Array(32)
|
|
15637
|
+
)
|
|
15638
|
+
);
|
|
15639
|
+
encryptedChatKey = await encryptBytesWithAesGcm(
|
|
15640
|
+
chatKeyBytes,
|
|
15641
|
+
wrappingKey
|
|
15642
|
+
);
|
|
15643
|
+
} else {
|
|
15644
|
+
const cache = loadSyncCache(teamId) ?? await this.ensureSynced(false, [], { teamId });
|
|
15645
|
+
const chat = cache.chats.find(
|
|
15646
|
+
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
15647
|
+
);
|
|
15648
|
+
if (chat) {
|
|
15649
|
+
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
15650
|
+
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
15651
|
+
if (encKey) {
|
|
15652
|
+
chatKeyBytes = await decryptBytesWithAesGcm(encKey, wrappingKey);
|
|
15653
|
+
encryptedChatKey = encKey;
|
|
15654
|
+
if (!chatKeyBytes) throw new Error("Could not decrypt the saved chat key.");
|
|
15655
|
+
if (typeof chat.details.encrypted_title === "string") {
|
|
15656
|
+
try {
|
|
15657
|
+
notificationTitle = await decryptWithAesGcmCombined(chat.details.encrypted_title, chatKeyBytes);
|
|
15658
|
+
} catch {
|
|
15659
|
+
}
|
|
15590
15660
|
}
|
|
15591
|
-
|
|
15592
|
-
|
|
15593
|
-
|
|
15594
|
-
|
|
15595
|
-
|
|
15596
|
-
|
|
15597
|
-
|
|
15598
|
-
|
|
15599
|
-
|
|
15600
|
-
|
|
15601
|
-
|
|
15602
|
-
|
|
15661
|
+
const encryptedPhaseState = chat.details.encrypted_focus_phase_state;
|
|
15662
|
+
if (typeof encryptedPhaseState === "string" && encryptedPhaseState) {
|
|
15663
|
+
const phaseText = await decryptWithAesGcmCombined(encryptedPhaseState, chatKeyBytes);
|
|
15664
|
+
if (!phaseText) throw new Error("Could not decrypt focus phase progress. Sync before sending again.");
|
|
15665
|
+
focusPhaseState = JSON.parse(phaseText);
|
|
15666
|
+
}
|
|
15667
|
+
const encryptedFocusId = chat.details.encrypted_active_focus_id;
|
|
15668
|
+
if (typeof encryptedFocusId === "string" && encryptedFocusId) {
|
|
15669
|
+
activeFocusId = await decryptWithAesGcmCombined(encryptedFocusId, chatKeyBytes);
|
|
15670
|
+
if (!activeFocusId) {
|
|
15671
|
+
throw new Error("Could not decrypt the chat's active focus. Sync before sending again.");
|
|
15672
|
+
}
|
|
15603
15673
|
}
|
|
15604
15674
|
}
|
|
15605
15675
|
}
|
|
15676
|
+
if (!chatKeyBytes || !encryptedChatKey) {
|
|
15677
|
+
throw new Error(`Encrypted chat key not found for chat '${chatId}'. Sync and try again.`);
|
|
15678
|
+
}
|
|
15606
15679
|
}
|
|
15607
|
-
if (!chatKeyBytes || !encryptedChatKey) {
|
|
15608
|
-
throw new Error(`Encrypted chat key not found for chat '${chatId}'. Sync and try again.`);
|
|
15609
|
-
}
|
|
15610
|
-
}
|
|
15611
|
-
}
|
|
15612
|
-
let focusPersistence = Promise.resolve();
|
|
15613
|
-
let focusPersistenceError = null;
|
|
15614
|
-
const chatContextEvents = !params.incognito && chatKeyBytes ? registerChatContextEvents({
|
|
15615
|
-
ws,
|
|
15616
|
-
chatId,
|
|
15617
|
-
chatKey: chatKeyBytes,
|
|
15618
|
-
existingMessageIds: savedPrivacyHistory?.messages.map(canonicalSavedChatMessageId) ?? [],
|
|
15619
|
-
previousRulesSetKey: savedPrivacyHistory?.messages.filter((message) => message.role === "system").map((message) => parseChatContextContent(message.content)).filter((event) => event?.type === "rules_loaded").at(-1)?.set_key,
|
|
15620
|
-
onApplied: params.onChatContextApplied
|
|
15621
|
-
}) : null;
|
|
15622
|
-
let projectAuthoringAvailable = null;
|
|
15623
|
-
if (!params.incognito) ws.onMessageType("project_authoring_available", (value) => {
|
|
15624
|
-
if (!value || typeof value !== "object") return;
|
|
15625
|
-
const frame = value;
|
|
15626
|
-
if (frame.chat_id === chatId && frame.user_message_id === messageId && typeof frame.project_id === "string")
|
|
15627
|
-
projectAuthoringAvailable = { chat_id: chatId, project_id: frame.project_id, user_message_id: messageId };
|
|
15628
|
-
});
|
|
15629
|
-
const projectFocusCountdown = !params.incognito ? registerProjectFocusCountdown({
|
|
15630
|
-
client: this,
|
|
15631
|
-
ws,
|
|
15632
|
-
chatId,
|
|
15633
|
-
teamId,
|
|
15634
|
-
onPending: params.onProjectFocusPending,
|
|
15635
|
-
onError: (error) => {
|
|
15636
|
-
focusPersistenceError = error;
|
|
15637
15680
|
}
|
|
15638
|
-
|
|
15639
|
-
|
|
15640
|
-
|
|
15641
|
-
|
|
15642
|
-
|
|
15643
|
-
|
|
15644
|
-
|
|
15645
|
-
|
|
15646
|
-
|
|
15647
|
-
|
|
15648
|
-
|
|
15649
|
-
|
|
15650
|
-
|
|
15651
|
-
|
|
15681
|
+
let focusPersistence = Promise.resolve();
|
|
15682
|
+
let focusPersistenceError = null;
|
|
15683
|
+
const chatContextEvents = !params.incognito && chatKeyBytes ? registerChatContextEvents({
|
|
15684
|
+
ws,
|
|
15685
|
+
chatId,
|
|
15686
|
+
chatKey: chatKeyBytes,
|
|
15687
|
+
existingMessageIds: savedPrivacyHistory?.messages.map(canonicalSavedChatMessageId) ?? [],
|
|
15688
|
+
previousRulesSetKey: savedPrivacyHistory?.messages.filter((message) => message.role === "system").map((message) => parseChatContextContent(message.content)).filter((event) => event?.type === "rules_loaded").at(-1)?.set_key,
|
|
15689
|
+
onApplied: params.onChatContextApplied
|
|
15690
|
+
}) : null;
|
|
15691
|
+
let projectAuthoringAvailable = null;
|
|
15692
|
+
if (!params.incognito) ws.onMessageType("project_authoring_available", (value) => {
|
|
15693
|
+
if (!value || typeof value !== "object") return;
|
|
15694
|
+
const frame = value;
|
|
15695
|
+
if (frame.chat_id === chatId && frame.user_message_id === messageId && typeof frame.project_id === "string")
|
|
15696
|
+
projectAuthoringAvailable = { chat_id: chatId, project_id: frame.project_id, user_message_id: messageId };
|
|
15652
15697
|
});
|
|
15653
|
-
|
|
15654
|
-
|
|
15655
|
-
|
|
15656
|
-
|
|
15657
|
-
|
|
15658
|
-
|
|
15659
|
-
|
|
15660
|
-
|
|
15661
|
-
throw new Error("Invalid focus phase state received");
|
|
15662
|
-
}
|
|
15663
|
-
if (focusPhaseState[focusId]?.run_id === phase.run_id && focusPhaseState[focusId].version > phase.version) states2[focusId] = focusPhaseState[focusId];
|
|
15698
|
+
const projectFocusCountdown = !params.incognito ? registerProjectFocusCountdown({
|
|
15699
|
+
client: this,
|
|
15700
|
+
ws,
|
|
15701
|
+
chatId,
|
|
15702
|
+
teamId,
|
|
15703
|
+
onPending: params.onProjectFocusPending,
|
|
15704
|
+
onError: (error) => {
|
|
15705
|
+
focusPersistenceError = error;
|
|
15664
15706
|
}
|
|
15665
|
-
|
|
15666
|
-
|
|
15667
|
-
|
|
15668
|
-
|
|
15669
|
-
|
|
15670
|
-
|
|
15671
|
-
)
|
|
15672
|
-
|
|
15673
|
-
|
|
15674
|
-
...teamId ? { team_id: teamId } : {},
|
|
15675
|
-
encrypted_focus_phase_state: encryptedState
|
|
15676
|
-
});
|
|
15677
|
-
await persisted;
|
|
15678
|
-
const eventIds = /* @__PURE__ */ new Set();
|
|
15679
|
-
for (const phase of Object.values(states2)) for (const transition of phase.transitions) {
|
|
15680
|
-
if (transition.chat_id !== chatId || transition.focus_id !== phase.focus_id || !transition.event_id || eventIds.has(transition.event_id)) continue;
|
|
15681
|
-
eventIds.add(transition.event_id);
|
|
15682
|
-
const content = JSON.stringify(transition);
|
|
15683
|
-
await ws.sendAsync("chat_system_message_added", {
|
|
15707
|
+
}) : null;
|
|
15708
|
+
ws.onMessageType("focus_mode_activated", (event) => {
|
|
15709
|
+
if (event.chat_id !== chatId || !event.focus_id || !chatKeyBytes || params.incognito) return;
|
|
15710
|
+
const focusId = event.focus_id;
|
|
15711
|
+
if (!focusId.startsWith("project-")) delete focusPhaseState[focusId];
|
|
15712
|
+
const key = chatKeyBytes;
|
|
15713
|
+
focusPersistence = focusPersistence.then(async () => {
|
|
15714
|
+
const encryptedFocusId = await encryptWithAesGcmCombined(focusId, key);
|
|
15715
|
+
await ws.sendAsync("update_encrypted_active_focus_id", {
|
|
15684
15716
|
chat_id: chatId,
|
|
15685
|
-
|
|
15686
|
-
message_id: transition.event_id,
|
|
15687
|
-
role: "system",
|
|
15688
|
-
status: "synced",
|
|
15689
|
-
encrypted_content: await encryptWithAesGcmCombined(content, key),
|
|
15690
|
-
created_at: transition.created_at
|
|
15691
|
-
}
|
|
15717
|
+
encrypted_active_focus_id: encryptedFocusId
|
|
15692
15718
|
});
|
|
15693
|
-
}
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
focusPersistenceError = error;
|
|
15719
|
+
}).catch((error) => {
|
|
15720
|
+
focusPersistenceError = error;
|
|
15721
|
+
});
|
|
15697
15722
|
});
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
15707
|
-
|
|
15708
|
-
|
|
15709
|
-
|
|
15710
|
-
|
|
15723
|
+
ws.onMessageType("focus_phases_updated", (event) => {
|
|
15724
|
+
if (event.chat_id !== chatId || !event.states || !chatKeyBytes || params.incognito) return;
|
|
15725
|
+
const key = chatKeyBytes;
|
|
15726
|
+
const states2 = event.states;
|
|
15727
|
+
focusPersistence = focusPersistence.then(async () => {
|
|
15728
|
+
for (const [focusId, phase] of Object.entries(states2)) {
|
|
15729
|
+
if (phase.schema_version !== 1 || phase.chat_id !== chatId || phase.focus_id !== focusId || !Number.isSafeInteger(phase.version) || phase.version < 0 || !Array.isArray(phase.transitions) || phase.transitions.length > 32) {
|
|
15730
|
+
throw new Error("Invalid focus phase state received");
|
|
15731
|
+
}
|
|
15732
|
+
if (focusPhaseState[focusId]?.run_id === phase.run_id && focusPhaseState[focusId].version > phase.version) states2[focusId] = focusPhaseState[focusId];
|
|
15733
|
+
}
|
|
15734
|
+
focusPhaseState = states2;
|
|
15735
|
+
const encryptedState = await encryptWithAesGcmCombined(JSON.stringify(states2), key);
|
|
15736
|
+
const persisted = ws.waitForMessage(
|
|
15737
|
+
"encrypted_metadata_stored",
|
|
15738
|
+
(payload) => payload.chat_id === chatId,
|
|
15739
|
+
2e4
|
|
15740
|
+
);
|
|
15741
|
+
await ws.sendAsync("encrypted_chat_metadata", {
|
|
15742
|
+
chat_id: chatId,
|
|
15743
|
+
...teamId ? { team_id: teamId } : {},
|
|
15744
|
+
encrypted_focus_phase_state: encryptedState
|
|
15745
|
+
});
|
|
15746
|
+
await persisted;
|
|
15747
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
15748
|
+
for (const phase of Object.values(states2)) for (const transition of phase.transitions) {
|
|
15749
|
+
if (transition.chat_id !== chatId || transition.focus_id !== phase.focus_id || !transition.event_id || eventIds.has(transition.event_id)) continue;
|
|
15750
|
+
eventIds.add(transition.event_id);
|
|
15751
|
+
const content = JSON.stringify(transition);
|
|
15752
|
+
await ws.sendAsync("chat_system_message_added", {
|
|
15753
|
+
chat_id: chatId,
|
|
15754
|
+
message: {
|
|
15755
|
+
message_id: transition.event_id,
|
|
15756
|
+
role: "system",
|
|
15757
|
+
status: "synced",
|
|
15758
|
+
encrypted_content: await encryptWithAesGcmCombined(content, key),
|
|
15759
|
+
created_at: transition.created_at
|
|
15760
|
+
}
|
|
15761
|
+
});
|
|
15762
|
+
}
|
|
15763
|
+
clearSyncCache(teamId);
|
|
15764
|
+
}).catch((error) => {
|
|
15765
|
+
focusPersistenceError = error;
|
|
15766
|
+
});
|
|
15711
15767
|
});
|
|
15712
|
-
clientCapabilities
|
|
15713
|
-
|
|
15714
|
-
|
|
15715
|
-
|
|
15716
|
-
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
sender_name: "User",
|
|
15729
|
-
status: "sent",
|
|
15730
|
-
content: finalMessage,
|
|
15731
|
-
created_at: createdAt,
|
|
15732
|
-
chat_has_title: Boolean(params.chatId)
|
|
15768
|
+
const clientCapabilities = taskUpdateJobsEnabled ? ["task_update_jobs"] : [];
|
|
15769
|
+
if (!params.incognito && chatKeyBytes) {
|
|
15770
|
+
registerCliProjectFileExecutor({
|
|
15771
|
+
client: this,
|
|
15772
|
+
ws,
|
|
15773
|
+
chatId,
|
|
15774
|
+
chatKey: chatKeyBytes,
|
|
15775
|
+
piiMappings,
|
|
15776
|
+
memories: availableMemories,
|
|
15777
|
+
requestApproval: params.onProjectWriteApproval,
|
|
15778
|
+
requestReadApproval: params.onProjectReadApproval,
|
|
15779
|
+
onHostedVersionCommitted: params.onHostedVersionCommitted
|
|
15780
|
+
});
|
|
15781
|
+
clientCapabilities.push("project_file_jobs");
|
|
15782
|
+
registerRemoteCommandOriginClient({ ws, client: this, chatId, onReview: params.onRemoteCommandReview, onEvent: params.onRemoteCommandEvent });
|
|
15783
|
+
clientCapabilities.push("remote_command_jobs");
|
|
15733
15784
|
}
|
|
15734
|
-
|
|
15735
|
-
if (!params.incognito) Object.assign(
|
|
15736
|
-
messagePayload,
|
|
15737
|
-
await prepareCliJevContext(this, chatId, { teamId, personal: !teamId }, params.jevContext, finalMessage)
|
|
15738
|
-
);
|
|
15739
|
-
if (params.testMockMarker !== void 0) {
|
|
15740
|
-
const { capacityReplayMarker } = await import("./capacityReplayMarker-3NFJNDTZ.js");
|
|
15741
|
-
messagePayload.test_mock_marker = capacityReplayMarker(
|
|
15742
|
-
params.testMockMarker,
|
|
15743
|
-
this.apiUrl,
|
|
15744
|
-
process.env.OPENMATES_CAPACITY_CLIENT_REPLAY === "1"
|
|
15745
|
-
);
|
|
15746
|
-
}
|
|
15747
|
-
if (isNewChat && encryptedChatKey) {
|
|
15748
|
-
messagePayload.encrypted_chat_key = encryptedChatKey;
|
|
15749
|
-
}
|
|
15750
|
-
if (memoryMetadataKeys.length > 0) {
|
|
15751
|
-
messagePayload.app_settings_memories_metadata = memoryMetadataKeys;
|
|
15752
|
-
}
|
|
15753
|
-
if (connectedAccountDirectory) {
|
|
15754
|
-
messagePayload.connected_account_directory = connectedAccountDirectory;
|
|
15755
|
-
}
|
|
15756
|
-
if (connectedAccountTokenRefs.length > 0) {
|
|
15757
|
-
messagePayload.connected_account_token_refs = connectedAccountTokenRefs;
|
|
15758
|
-
}
|
|
15759
|
-
if (params.benchmarkMetadata) {
|
|
15760
|
-
messagePayload.benchmark_metadata = params.benchmarkMetadata;
|
|
15761
|
-
}
|
|
15762
|
-
if (params.learningMode) {
|
|
15763
|
-
messagePayload.learning_mode = {
|
|
15764
|
-
enabled: params.learningMode.enabled,
|
|
15765
|
-
age_group: params.learningMode.ageGroup ?? null
|
|
15766
|
-
};
|
|
15767
|
-
}
|
|
15768
|
-
if (params.incognito) {
|
|
15769
|
-
const providedHistory = (params.messageHistory ?? []).map(
|
|
15770
|
-
(historyMessage) => buildInferenceHistoryMessage(historyMessage, historyMessage.chat_id ?? chatId)
|
|
15771
|
-
);
|
|
15772
|
-
messagePayload.message_history = [...providedHistory, {
|
|
15773
|
-
message_id: messageId,
|
|
15785
|
+
const messagePayload = {
|
|
15774
15786
|
chat_id: chatId,
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15786
|
-
|
|
15787
|
-
|
|
15788
|
-
|
|
15789
|
-
|
|
15790
|
-
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15787
|
+
...teamId ? { team_id: teamId } : {},
|
|
15788
|
+
client_capabilities: clientCapabilities,
|
|
15789
|
+
// Only decrypted current metadata restores focus; history is never authority.
|
|
15790
|
+
active_focus_id: activeFocusId,
|
|
15791
|
+
focus_phase_state: focusPhaseState,
|
|
15792
|
+
is_incognito: Boolean(params.incognito),
|
|
15793
|
+
message: {
|
|
15794
|
+
message_id: messageId,
|
|
15795
|
+
chat_id: chatId,
|
|
15796
|
+
role: "user",
|
|
15797
|
+
sender_name: "User",
|
|
15798
|
+
status: "sent",
|
|
15799
|
+
content: finalMessage,
|
|
15800
|
+
created_at: createdAt,
|
|
15801
|
+
chat_has_title: Boolean(params.chatId)
|
|
15802
|
+
}
|
|
15803
|
+
};
|
|
15804
|
+
if (!params.incognito) {
|
|
15805
|
+
const serialContextStartedAt = Date.now();
|
|
15806
|
+
const prepared = earlyContext ? await earlyContext : { ok: true, value: await prepareCliJevContext(this, chatId, contextScope, params.jevContext, finalMessage), elapsedMs: Date.now() - serialContextStartedAt };
|
|
15807
|
+
if (!prepared.ok) throw prepared.error;
|
|
15808
|
+
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
15809
|
+
if (process.env.OPENMATES_DEBUG === "1") {
|
|
15810
|
+
console.error(`[chat preparation] auth_ws_ms=${authSocketMs} recovery_ms=${recoveryMs} context_ms=${prepared.elapsedMs}`);
|
|
15811
|
+
}
|
|
15812
|
+
Object.assign(messagePayload, prepared.value);
|
|
15813
|
+
}
|
|
15814
|
+
if (params.testMockMarker !== void 0) {
|
|
15815
|
+
const { capacityReplayMarker } = await import("./capacityReplayMarker-3NFJNDTZ.js");
|
|
15816
|
+
messagePayload.test_mock_marker = capacityReplayMarker(
|
|
15817
|
+
params.testMockMarker,
|
|
15818
|
+
this.apiUrl,
|
|
15819
|
+
process.env.OPENMATES_CAPACITY_CLIENT_REPLAY === "1"
|
|
15820
|
+
);
|
|
15798
15821
|
}
|
|
15799
|
-
if (
|
|
15800
|
-
|
|
15822
|
+
if (isNewChat && encryptedChatKey) {
|
|
15823
|
+
messagePayload.encrypted_chat_key = encryptedChatKey;
|
|
15801
15824
|
}
|
|
15802
|
-
if (
|
|
15803
|
-
|
|
15825
|
+
if (memoryMetadataKeys.length > 0) {
|
|
15826
|
+
messagePayload.app_settings_memories_metadata = memoryMetadataKeys;
|
|
15804
15827
|
}
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
throw new Error("Prepared message embed ID is invalid.");
|
|
15828
|
+
if (connectedAccountDirectory) {
|
|
15829
|
+
messagePayload.connected_account_directory = connectedAccountDirectory;
|
|
15808
15830
|
}
|
|
15809
|
-
|
|
15810
|
-
|
|
15811
|
-
const selectedIds = preparedIds.slice(offset, offset + 20);
|
|
15812
|
-
if (Buffer.byteLength(JSON.stringify({ embed_ids: selectedIds }), "utf8") > 4096) {
|
|
15813
|
-
throw new Error("Message embed availability request exceeds its byte limit.");
|
|
15814
|
-
}
|
|
15815
|
-
const path2 = this.appendTeamQuery(
|
|
15816
|
-
`/v1/embeds/chats/${encodeURIComponent(chatId)}/references/availability`,
|
|
15817
|
-
{ teamId }
|
|
15818
|
-
);
|
|
15819
|
-
const availability = await this.http.post(path2, { embed_ids: selectedIds }, this.getCliRequestHeaders());
|
|
15820
|
-
if (!availability.ok) {
|
|
15821
|
-
throw new Error(`Message embed availability lookup failed with HTTP ${availability.status}.`);
|
|
15822
|
-
}
|
|
15823
|
-
const page = classifyMessageEmbedAvailability(selectedIds, availability.data.results);
|
|
15824
|
-
for (const id of page.ready) canonicalReady.add(id);
|
|
15831
|
+
if (connectedAccountTokenRefs.length > 0) {
|
|
15832
|
+
messagePayload.connected_account_token_refs = connectedAccountTokenRefs;
|
|
15825
15833
|
}
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
if (canonicalReady.has(embed.embedId)) continue;
|
|
15829
|
-
const encrypted = await encryptEmbed(
|
|
15830
|
-
embed,
|
|
15831
|
-
masterKey,
|
|
15832
|
-
chatKeyBytes,
|
|
15833
|
-
chatId,
|
|
15834
|
-
messageId,
|
|
15835
|
-
ownerId2
|
|
15836
|
-
);
|
|
15837
|
-
if (!encrypted?.encrypted_content || !encrypted.encrypted_type || encrypted.embed_keys.length !== 2) {
|
|
15838
|
-
throw new Error(`Prepared message embed ${embed.embedId} is missing required ciphertext or key wrappers.`);
|
|
15839
|
-
}
|
|
15840
|
-
encryptedEmbeds.push(encrypted);
|
|
15834
|
+
if (params.benchmarkMetadata) {
|
|
15835
|
+
messagePayload.benchmark_metadata = params.benchmarkMetadata;
|
|
15841
15836
|
}
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
|
|
15851
|
-
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
15858
|
-
|
|
15859
|
-
|
|
15860
|
-
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
15864
|
-
chatId,
|
|
15865
|
-
chatKeyVersion
|
|
15866
|
-
);
|
|
15867
|
-
const encryptedContent = await encryptWithAesGcmCombined(
|
|
15868
|
-
finalMessage,
|
|
15869
|
-
chatKeyBytes
|
|
15870
|
-
);
|
|
15871
|
-
const encryptedUserMessage = {
|
|
15872
|
-
client_message_id: messageId,
|
|
15873
|
-
chat_id: chatId,
|
|
15874
|
-
encrypted_content: encryptedContent,
|
|
15875
|
-
role: "user",
|
|
15876
|
-
created_at: createdAt,
|
|
15877
|
-
updated_at: createdAt
|
|
15878
|
-
};
|
|
15879
|
-
encryptedUserMessage.encrypted_sender_name = await encryptWithAesGcmCombined(
|
|
15880
|
-
teamSenderName,
|
|
15881
|
-
chatKeyBytes
|
|
15882
|
-
);
|
|
15883
|
-
if (piiMappings.length > 0) {
|
|
15884
|
-
encryptedUserMessage.encrypted_pii_mappings = await encryptWithAesGcmCombined(
|
|
15885
|
-
JSON.stringify(piiMappings),
|
|
15886
|
-
chatKeyBytes
|
|
15837
|
+
if (params.learningMode) {
|
|
15838
|
+
messagePayload.learning_mode = {
|
|
15839
|
+
enabled: params.learningMode.enabled,
|
|
15840
|
+
age_group: params.learningMode.ageGroup ?? null
|
|
15841
|
+
};
|
|
15842
|
+
}
|
|
15843
|
+
if (params.incognito) {
|
|
15844
|
+
const providedHistory = (params.messageHistory ?? []).map(
|
|
15845
|
+
(historyMessage) => buildInferenceHistoryMessage(historyMessage, historyMessage.chat_id ?? chatId)
|
|
15846
|
+
);
|
|
15847
|
+
messagePayload.message_history = [...providedHistory, {
|
|
15848
|
+
message_id: messageId,
|
|
15849
|
+
chat_id: chatId,
|
|
15850
|
+
role: "user",
|
|
15851
|
+
sender_name: "User",
|
|
15852
|
+
content: finalMessage,
|
|
15853
|
+
created_at: createdAt
|
|
15854
|
+
}];
|
|
15855
|
+
} else if (messageHistoryForRequest && messageHistoryForRequest.length > 0) {
|
|
15856
|
+
messagePayload.message_history = messageHistoryForRequest.map(
|
|
15857
|
+
(historyMessage) => buildInferenceHistoryMessage(historyMessage, chatId)
|
|
15887
15858
|
);
|
|
15888
15859
|
}
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
15860
|
+
if (params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
15861
|
+
messagePayload.embeds = params.preparedEmbeds.map((embed) => ({
|
|
15862
|
+
embed_id: embed.embedId,
|
|
15863
|
+
type: embed.type,
|
|
15864
|
+
content: embed.content,
|
|
15865
|
+
status: embed.status,
|
|
15866
|
+
text_preview: embed.textPreview
|
|
15867
|
+
}));
|
|
15868
|
+
}
|
|
15869
|
+
const encryptedEmbeds = [...params.encryptedEmbeds ?? []];
|
|
15870
|
+
if (!params.incognito && params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
15871
|
+
if (!ownerId2) {
|
|
15872
|
+
throw new Error("Authenticated user identity is required to encrypt embeds.");
|
|
15873
|
+
}
|
|
15874
|
+
if (!chatKeyBytes) {
|
|
15875
|
+
throw new Error("Saved chat key is required to encrypt every prepared embed.");
|
|
15876
|
+
}
|
|
15877
|
+
if (new Set(params.preparedEmbeds.map((embed) => embed.embedId)).size !== params.preparedEmbeds.length) {
|
|
15878
|
+
throw new Error("Prepared message embed IDs must be unique.");
|
|
15879
|
+
}
|
|
15880
|
+
const preparedIds = params.preparedEmbeds.map((embed) => embed.embedId);
|
|
15881
|
+
if (preparedIds.some((id) => !id || id.length > 128)) {
|
|
15882
|
+
throw new Error("Prepared message embed ID is invalid.");
|
|
15883
|
+
}
|
|
15884
|
+
const canonicalReady = /* @__PURE__ */ new Set();
|
|
15885
|
+
for (let offset = 0; offset < preparedIds.length; offset += 20) {
|
|
15886
|
+
const selectedIds = preparedIds.slice(offset, offset + 20);
|
|
15887
|
+
if (Buffer.byteLength(JSON.stringify({ embed_ids: selectedIds }), "utf8") > 4096) {
|
|
15888
|
+
throw new Error("Message embed availability request exceeds its byte limit.");
|
|
15910
15889
|
}
|
|
15911
|
-
|
|
15912
|
-
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15890
|
+
const path2 = this.appendTeamQuery(
|
|
15891
|
+
`/v1/embeds/chats/${encodeURIComponent(chatId)}/references/availability`,
|
|
15892
|
+
{ teamId }
|
|
15893
|
+
);
|
|
15894
|
+
const availability = await this.http.post(path2, { embed_ids: selectedIds }, this.getCliRequestHeaders());
|
|
15895
|
+
if (!availability.ok) {
|
|
15896
|
+
throw new Error(`Message embed availability lookup failed with HTTP ${availability.status}.`);
|
|
15897
|
+
}
|
|
15898
|
+
const page = classifyMessageEmbedAvailability(selectedIds, availability.data.results);
|
|
15899
|
+
for (const id of page.ready) canonicalReady.add(id);
|
|
15900
|
+
}
|
|
15901
|
+
const masterKey = this.getMasterKeyBytes();
|
|
15902
|
+
for (const embed of params.preparedEmbeds) {
|
|
15903
|
+
if (canonicalReady.has(embed.embedId)) continue;
|
|
15904
|
+
const encrypted = await encryptEmbed(
|
|
15905
|
+
embed,
|
|
15906
|
+
masterKey,
|
|
15907
|
+
chatKeyBytes,
|
|
15908
|
+
chatId,
|
|
15909
|
+
messageId,
|
|
15910
|
+
ownerId2
|
|
15911
|
+
);
|
|
15912
|
+
if (!encrypted?.encrypted_content || !encrypted.encrypted_type || encrypted.embed_keys.length !== 2) {
|
|
15913
|
+
throw new Error(`Prepared message embed ${embed.embedId} is missing required ciphertext or key wrappers.`);
|
|
15914
|
+
}
|
|
15915
|
+
encryptedEmbeds.push(encrypted);
|
|
15921
15916
|
}
|
|
15922
15917
|
}
|
|
15923
|
-
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
|
|
15927
|
-
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
recovery_public_key: recoveryKeypair.publicKey,
|
|
15932
|
-
expected_messages_v: baselineMessagesV,
|
|
15933
|
-
encrypted_user_message: encryptedUserMessage,
|
|
15934
|
-
inference_request: preflightInferenceRequest
|
|
15918
|
+
if (!params.incognito && encryptedEmbeds.length > 0) {
|
|
15919
|
+
if (!ownerId2) throw new Error("Authenticated owner identity is required for encrypted message embeds.");
|
|
15920
|
+
assertCompleteEncryptedEmbedBundle(encryptedEmbeds, { chatId, messageId, ownerId: ownerId2 });
|
|
15921
|
+
}
|
|
15922
|
+
if (encryptedEmbeds.length > 0) {
|
|
15923
|
+
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
15924
|
+
}
|
|
15925
|
+
let dispatchHandlersReady = () => {
|
|
15935
15926
|
};
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
const
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
15948
|
-
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15927
|
+
const handlersReady = new Promise((resolve20) => {
|
|
15928
|
+
dispatchHandlersReady = resolve20;
|
|
15929
|
+
});
|
|
15930
|
+
let precollectedResponse = params.precollectResponse && params.incognito && shouldWaitForAi ? ws.collectAiResponse(messageId, chatId, { onStream: params.onStream, timeoutMs: params.responseTimeoutMs }) : null;
|
|
15931
|
+
if (!params.incognito && chatKeyBytes && encryptedChatKey) {
|
|
15932
|
+
const protocolVersion = 1;
|
|
15933
|
+
const chatKeyVersion = 1;
|
|
15934
|
+
const turnId = randomUUID9();
|
|
15935
|
+
savedTurnId = turnId;
|
|
15936
|
+
terminalExpectedMessagesV = baselineMessagesV + 1;
|
|
15937
|
+
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
15938
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
15939
|
+
chatId,
|
|
15940
|
+
chatKeyVersion
|
|
15941
|
+
);
|
|
15942
|
+
const encryptedContent = await encryptWithAesGcmCombined(
|
|
15943
|
+
finalMessage,
|
|
15944
|
+
chatKeyBytes
|
|
15945
|
+
);
|
|
15946
|
+
const encryptedUserMessage = {
|
|
15947
|
+
client_message_id: messageId,
|
|
15948
|
+
chat_id: chatId,
|
|
15949
|
+
encrypted_content: encryptedContent,
|
|
15950
|
+
role: "user",
|
|
15955
15951
|
created_at: createdAt,
|
|
15956
15952
|
updated_at: createdAt
|
|
15957
15953
|
};
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15954
|
+
encryptedUserMessage.encrypted_sender_name = await encryptWithAesGcmCombined(
|
|
15955
|
+
teamSenderName,
|
|
15956
|
+
chatKeyBytes
|
|
15957
|
+
);
|
|
15958
|
+
if (piiMappings.length > 0) {
|
|
15959
|
+
encryptedUserMessage.encrypted_pii_mappings = await encryptWithAesGcmCombined(
|
|
15960
|
+
JSON.stringify(piiMappings),
|
|
15961
|
+
chatKeyBytes
|
|
15962
|
+
);
|
|
15963
|
+
}
|
|
15964
|
+
Object.assign(messagePayload, {
|
|
15965
|
+
turn_id: turnId,
|
|
15966
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
15967
|
+
chat_key_version: chatKeyVersion
|
|
15968
|
+
});
|
|
15969
|
+
let preflightInferenceRequest = messagePayload;
|
|
15961
15970
|
if (teamId) {
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
|
|
15967
|
-
|
|
15968
|
-
|
|
15971
|
+
const teamInferenceHistory = [
|
|
15972
|
+
...(messageHistoryForRequest ?? []).map((historyMessage) => ({
|
|
15973
|
+
role: historyMessage.role,
|
|
15974
|
+
content: historyMessage.content,
|
|
15975
|
+
category: historyMessage.category ?? null,
|
|
15976
|
+
sender_name: historyMessage.sender_name ?? historyMessage.role,
|
|
15977
|
+
created_at: historyMessage.created_at
|
|
15978
|
+
})),
|
|
15979
|
+
{
|
|
15980
|
+
role: "user",
|
|
15981
|
+
content: finalMessage,
|
|
15982
|
+
category: null,
|
|
15983
|
+
sender_name: teamSenderName,
|
|
15984
|
+
created_at: createdAt
|
|
15985
|
+
}
|
|
15986
|
+
];
|
|
15987
|
+
const teamMessageEnvelope = { ...encryptedUserMessage, message_id: messageId };
|
|
15988
|
+
delete messagePayload.message_history;
|
|
15989
|
+
messagePayload.message = teamMessageEnvelope;
|
|
15990
|
+
if (shouldWaitForAi) {
|
|
15991
|
+
messagePayload.team_ai_invocation = { history: teamInferenceHistory };
|
|
15992
|
+
preflightInferenceRequest = {
|
|
15993
|
+
...messagePayload,
|
|
15994
|
+
message_history: teamInferenceHistory
|
|
15995
|
+
};
|
|
15996
|
+
}
|
|
15969
15997
|
}
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
15998
|
+
const preflightPayload = {
|
|
15999
|
+
protocol_version: protocolVersion,
|
|
16000
|
+
chat_id: chatId,
|
|
16001
|
+
...teamId ? { team_id: teamId } : {},
|
|
16002
|
+
turn_id: turnId,
|
|
16003
|
+
message_id: messageId,
|
|
16004
|
+
chat_key_version: chatKeyVersion,
|
|
16005
|
+
encrypted_chat_key: encryptedChatKey,
|
|
16006
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
16007
|
+
expected_messages_v: baselineMessagesV,
|
|
16008
|
+
encrypted_user_message: encryptedUserMessage,
|
|
16009
|
+
inference_request: preflightInferenceRequest
|
|
16010
|
+
};
|
|
16011
|
+
if (isNewChat) {
|
|
16012
|
+
if (!chatSlugLookupKey) {
|
|
16013
|
+
throw new Error("Encrypted chat slug lookup key was not initialized.");
|
|
16014
|
+
}
|
|
16015
|
+
const initialTitle = teamId && !shouldWaitForAi ? teamChatTitleFromMessage(params.message) : "";
|
|
16016
|
+
const slugMetadata = await buildEncryptedObjectSlugMetadata({
|
|
16017
|
+
value: selectNewChatSlugValue({
|
|
16018
|
+
message: finalMessage,
|
|
16019
|
+
chatId,
|
|
16020
|
+
explicitSlug: params.slug
|
|
16021
|
+
}),
|
|
16022
|
+
encryptionKey: chatKeyBytes,
|
|
16023
|
+
lookupKey: chatSlugLookupKey
|
|
15979
16024
|
});
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
Object.assign(preflightInferenceRequest, focusedContext);
|
|
16025
|
+
preflightPayload.encrypted_chat_metadata = {
|
|
16026
|
+
encrypted_title: await encryptWithAesGcmCombined(initialTitle, chatKeyBytes),
|
|
16027
|
+
encrypted_slug: slugMetadata.encrypted_slug,
|
|
16028
|
+
slug_lookup_hash: slugMetadata.slug_lookup_hash,
|
|
16029
|
+
encrypted_chat_key: encryptedChatKey,
|
|
16030
|
+
created_at: createdAt,
|
|
16031
|
+
updated_at: createdAt
|
|
16032
|
+
};
|
|
15989
16033
|
}
|
|
15990
|
-
|
|
15991
|
-
"chat_turn_preflight_ack",
|
|
15992
|
-
(payload) => payload.turn_id === turnId
|
|
15993
|
-
);
|
|
16034
|
+
let ackPayload;
|
|
15994
16035
|
try {
|
|
15995
|
-
|
|
16036
|
+
if (teamId) {
|
|
16037
|
+
await stageCliTeamNotificationPreview(ws, {
|
|
16038
|
+
teamId,
|
|
16039
|
+
chatId,
|
|
16040
|
+
messageId,
|
|
16041
|
+
content: finalMessage,
|
|
16042
|
+
title: notificationTitle
|
|
16043
|
+
});
|
|
16044
|
+
}
|
|
16045
|
+
if (params.projectId) {
|
|
16046
|
+
await prepareCliProjectFocusForPreflight({
|
|
16047
|
+
ws,
|
|
16048
|
+
chatId,
|
|
16049
|
+
teamId,
|
|
16050
|
+
isNewChat,
|
|
16051
|
+
encryptedChatKey,
|
|
16052
|
+
createdAt,
|
|
16053
|
+
activateFocus: () => activateCliProjectFocus(this, params.projectId, chatId, teamId)
|
|
16054
|
+
});
|
|
16055
|
+
const focusedContext = await prepareCliJevContext(
|
|
16056
|
+
this,
|
|
16057
|
+
chatId,
|
|
16058
|
+
{ teamId, personal: !teamId },
|
|
16059
|
+
params.jevContext,
|
|
16060
|
+
finalMessage
|
|
16061
|
+
);
|
|
16062
|
+
Object.assign(messagePayload, focusedContext);
|
|
16063
|
+
Object.assign(preflightInferenceRequest, focusedContext);
|
|
16064
|
+
}
|
|
16065
|
+
const preflightAck = ws.waitForMessage(
|
|
16066
|
+
"chat_turn_preflight_ack",
|
|
16067
|
+
(payload) => payload.turn_id === turnId
|
|
16068
|
+
);
|
|
16069
|
+
try {
|
|
16070
|
+
await ws.sendAsync("chat_turn_preflight", preflightPayload);
|
|
16071
|
+
} catch (error) {
|
|
16072
|
+
void preflightAck.catch(() => {
|
|
16073
|
+
});
|
|
16074
|
+
throw error;
|
|
16075
|
+
}
|
|
16076
|
+
ackPayload = (await preflightAck).payload;
|
|
15996
16077
|
} catch (error) {
|
|
15997
|
-
|
|
15998
|
-
});
|
|
16078
|
+
ws.close();
|
|
15999
16079
|
throw error;
|
|
16000
16080
|
}
|
|
16001
|
-
ackPayload
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16081
|
+
if (typeof ackPayload.preflight_id !== "string" || !ackPayload.preflight_id) {
|
|
16082
|
+
ws.close();
|
|
16083
|
+
throw new Error("Encrypted chat preflight acknowledgement omitted preflight_id.");
|
|
16084
|
+
}
|
|
16085
|
+
Object.assign(messagePayload, {
|
|
16086
|
+
protocol_version: protocolVersion,
|
|
16087
|
+
preflight_id: ackPayload.preflight_id
|
|
16088
|
+
});
|
|
16089
|
+
if (typeof ackPayload.committed_messages_v === "number" && Number.isSafeInteger(ackPayload.committed_messages_v)) {
|
|
16090
|
+
terminalExpectedMessagesV = ackPayload.committed_messages_v;
|
|
16091
|
+
}
|
|
16009
16092
|
}
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
|
|
16093
|
+
if (!params.incognito && shouldWaitForAi) {
|
|
16094
|
+
precollectedResponse = ws.collectAiResponse(messageId, chatId, {
|
|
16095
|
+
onStream: params.onStream,
|
|
16096
|
+
onSubChatEvent: async (event) => {
|
|
16097
|
+
await handlersReady;
|
|
16098
|
+
await handleSubChatEvent(event);
|
|
16099
|
+
},
|
|
16100
|
+
onAppSettingsMemoriesRequest: async (event) => {
|
|
16101
|
+
await handlersReady;
|
|
16102
|
+
await handleAppSettingsMemoriesRequest(event);
|
|
16103
|
+
},
|
|
16104
|
+
timeoutMs: params.responseTimeoutMs,
|
|
16105
|
+
recoveryTurnId: savedTurnId,
|
|
16106
|
+
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
16107
|
+
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
16108
|
+
});
|
|
16016
16109
|
}
|
|
16017
|
-
|
|
16018
|
-
|
|
16019
|
-
|
|
16020
|
-
|
|
16021
|
-
|
|
16022
|
-
await handlersReady;
|
|
16023
|
-
await handleSubChatEvent(event);
|
|
16024
|
-
},
|
|
16025
|
-
onAppSettingsMemoriesRequest: async (event) => {
|
|
16026
|
-
await handlersReady;
|
|
16027
|
-
await handleAppSettingsMemoriesRequest(event);
|
|
16028
|
-
},
|
|
16029
|
-
timeoutMs: params.responseTimeoutMs,
|
|
16030
|
-
recoveryTurnId: savedTurnId,
|
|
16031
|
-
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
16032
|
-
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
16110
|
+
const queuedAdmission = !params.incognito && shouldWaitForAi ? ws.waitForMessage("message_queued", (payload) => {
|
|
16111
|
+
const event = payload;
|
|
16112
|
+
return event.chat_id === chatId && event.user_message_id === messageId;
|
|
16113
|
+
}, params.responseTimeoutMs ?? 9e4) : null;
|
|
16114
|
+
if (queuedAdmission) void queuedAdmission.catch(() => {
|
|
16033
16115
|
});
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
if (queuedAdmission) void queuedAdmission.catch(() => {
|
|
16040
|
-
});
|
|
16041
|
-
const pausedHandoff = !params.incognito && shouldWaitForAi ? ws.waitForMessage("queued_handoff_paused", (payload) => {
|
|
16042
|
-
const event = payload;
|
|
16043
|
-
return event.chat_id === chatId && (event.message_id === messageId || event.message_id === void 0);
|
|
16044
|
-
}, params.responseTimeoutMs ?? 9e4) : null;
|
|
16045
|
-
if (pausedHandoff) void pausedHandoff.catch(() => {
|
|
16046
|
-
});
|
|
16047
|
-
const confirmed = ws.waitForMessage(
|
|
16048
|
-
"chat_message_confirmed",
|
|
16049
|
-
(payload) => {
|
|
16050
|
-
const eventPayload = payload;
|
|
16051
|
-
return eventPayload.chat_id === chatId && eventPayload.message_id === messageId;
|
|
16052
|
-
},
|
|
16053
|
-
params.responseTimeoutMs ?? DEFAULT_CHAT_MESSAGE_CONFIRMATION_TIMEOUT_MS
|
|
16054
|
-
);
|
|
16055
|
-
let confirmedPayload;
|
|
16056
|
-
try {
|
|
16057
|
-
await ws.sendAsync("chat_message_added", messagePayload);
|
|
16058
|
-
confirmedPayload = (await confirmed).payload;
|
|
16059
|
-
} catch (error) {
|
|
16060
|
-
dispatchHandlersReady();
|
|
16061
|
-
void confirmed.catch(() => {
|
|
16116
|
+
const pausedHandoff = !params.incognito && shouldWaitForAi ? ws.waitForMessage("queued_handoff_paused", (payload) => {
|
|
16117
|
+
const event = payload;
|
|
16118
|
+
return event.chat_id === chatId && (event.message_id === messageId || event.message_id === void 0);
|
|
16119
|
+
}, params.responseTimeoutMs ?? 9e4) : null;
|
|
16120
|
+
if (pausedHandoff) void pausedHandoff.catch(() => {
|
|
16062
16121
|
});
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16066
|
-
|
|
16067
|
-
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
let taskEvents = [];
|
|
16084
|
-
let pendingTaskUpdateJobs = [];
|
|
16085
|
-
let subChatEvents = [];
|
|
16086
|
-
const appSettingsMemoryRequests = [];
|
|
16087
|
-
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
16088
|
-
const isApprovalWithinServerLimits = (request) => {
|
|
16089
|
-
const count = request.subChats.length;
|
|
16090
|
-
if (count === 0) return false;
|
|
16091
|
-
if (request.remainingSubChats !== null && count > request.remainingSubChats) {
|
|
16092
|
-
return false;
|
|
16122
|
+
const confirmed = ws.waitForMessage(
|
|
16123
|
+
"chat_message_confirmed",
|
|
16124
|
+
(payload) => {
|
|
16125
|
+
const eventPayload = payload;
|
|
16126
|
+
return eventPayload.chat_id === chatId && eventPayload.message_id === messageId;
|
|
16127
|
+
},
|
|
16128
|
+
params.responseTimeoutMs ?? DEFAULT_CHAT_MESSAGE_CONFIRMATION_TIMEOUT_MS
|
|
16129
|
+
);
|
|
16130
|
+
let confirmedPayload;
|
|
16131
|
+
try {
|
|
16132
|
+
await ws.sendAsync("chat_message_added", messagePayload);
|
|
16133
|
+
confirmedPayload = (await confirmed).payload;
|
|
16134
|
+
} catch (error) {
|
|
16135
|
+
dispatchHandlersReady();
|
|
16136
|
+
void confirmed.catch(() => {
|
|
16137
|
+
});
|
|
16138
|
+
if (precollectedResponse) void precollectedResponse.catch(() => {
|
|
16139
|
+
});
|
|
16140
|
+
ws.close();
|
|
16141
|
+
throw error;
|
|
16093
16142
|
}
|
|
16094
|
-
if (
|
|
16095
|
-
|
|
16143
|
+
if (!params.incognito && typeof confirmedPayload.new_messages_v === "number" && Number.isSafeInteger(confirmedPayload.new_messages_v)) {
|
|
16144
|
+
terminalExpectedMessagesV = confirmedPayload.new_messages_v;
|
|
16096
16145
|
}
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
const isAutoApprovalWithinServerLimits = (request) => {
|
|
16100
|
-
if (!isApprovalWithinServerLimits(request)) return false;
|
|
16101
|
-
return request.maxAutoSubChats === null || request.subChats.length <= request.maxAutoSubChats;
|
|
16102
|
-
};
|
|
16103
|
-
const handleSubChatEvent = async (event) => {
|
|
16104
|
-
params.onSubChatEvent?.(event);
|
|
16105
|
-
if (event.type === "spawn_sub_chats") {
|
|
16106
|
-
if (params.incognito || !chatKeyBytes) return;
|
|
16107
|
-
const rawSubChats = event.payload.sub_chats;
|
|
16108
|
-
if (!Array.isArray(rawSubChats)) return;
|
|
16109
|
-
const metadataPayloads = await buildSubChatEncryptedMetadataPayloads({
|
|
16110
|
-
parentChatId: chatId,
|
|
16111
|
-
parentChatKey: chatKeyBytes,
|
|
16112
|
-
encryptedParentChatKey: encryptedChatKey,
|
|
16113
|
-
subChats: rawSubChats
|
|
16114
|
-
});
|
|
16115
|
-
for (const metadataPayload of metadataPayloads) {
|
|
16116
|
-
await ws.sendAsync("encrypted_chat_metadata", metadataPayload);
|
|
16117
|
-
}
|
|
16118
|
-
return;
|
|
16146
|
+
if (!params.incognito && (!teamId || shouldWaitForAi)) {
|
|
16147
|
+
clearSyncCache(teamId);
|
|
16119
16148
|
}
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
|
|
16138
|
-
|
|
16139
|
-
buildSubChatConfirmationPayload({
|
|
16140
|
-
chatId: request.chatId,
|
|
16141
|
-
taskId: request.taskId,
|
|
16142
|
-
approved,
|
|
16143
|
-
approveCount: approved ? request.subChats.length : void 0
|
|
16144
|
-
})
|
|
16145
|
-
);
|
|
16146
|
-
};
|
|
16147
|
-
const persistSystemMessage = async (systemMessage) => {
|
|
16148
|
-
if (!chatKeyBytes) return;
|
|
16149
|
-
await ws.sendAsync("chat_system_message_added", {
|
|
16150
|
-
chat_id: chatId,
|
|
16151
|
-
message: {
|
|
16152
|
-
message_id: systemMessage.message_id,
|
|
16153
|
-
role: "system",
|
|
16154
|
-
encrypted_content: await encryptWithAesGcmCombined(
|
|
16155
|
-
systemMessage.content,
|
|
16156
|
-
chatKeyBytes
|
|
16157
|
-
),
|
|
16158
|
-
created_at: systemMessage.created_at,
|
|
16159
|
-
user_message_id: systemMessage.user_message_id
|
|
16149
|
+
let assistant = "";
|
|
16150
|
+
let assistantMessageId = null;
|
|
16151
|
+
let category = null;
|
|
16152
|
+
let modelName = null;
|
|
16153
|
+
let tokenUsage = null;
|
|
16154
|
+
let promptBudget = null;
|
|
16155
|
+
let followUpSuggestions = [];
|
|
16156
|
+
let taskProposals = [];
|
|
16157
|
+
let taskUpdateProposals = [];
|
|
16158
|
+
let taskEvents = [];
|
|
16159
|
+
let pendingTaskUpdateJobs = [];
|
|
16160
|
+
let subChatEvents = [];
|
|
16161
|
+
const appSettingsMemoryRequests = [];
|
|
16162
|
+
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
16163
|
+
const isApprovalWithinServerLimits = (request) => {
|
|
16164
|
+
const count = request.subChats.length;
|
|
16165
|
+
if (count === 0) return false;
|
|
16166
|
+
if (request.remainingSubChats !== null && count > request.remainingSubChats) {
|
|
16167
|
+
return false;
|
|
16160
16168
|
}
|
|
16161
|
-
|
|
16162
|
-
|
|
16163
|
-
|
|
16164
|
-
|
|
16165
|
-
|
|
16166
|
-
|
|
16167
|
-
|
|
16168
|
-
|
|
16169
|
-
|
|
16170
|
-
|
|
16171
|
-
|
|
16172
|
-
|
|
16173
|
-
|
|
16174
|
-
|
|
16175
|
-
|
|
16176
|
-
|
|
16177
|
-
|
|
16178
|
-
|
|
16179
|
-
|
|
16180
|
-
|
|
16181
|
-
const responseCategoriesFromMemories = (approvedMemories) => {
|
|
16182
|
-
const counts = /* @__PURE__ */ new Map();
|
|
16183
|
-
for (const memory of approvedMemories) {
|
|
16184
|
-
const key = `${memory.app_id}-${memory.item_key}`;
|
|
16185
|
-
const existing = counts.get(key);
|
|
16186
|
-
if (existing) {
|
|
16187
|
-
existing.entryCount += 1;
|
|
16188
|
-
} else {
|
|
16189
|
-
counts.set(key, {
|
|
16190
|
-
appId: memory.app_id,
|
|
16191
|
-
itemType: memory.item_key,
|
|
16192
|
-
entryCount: 1
|
|
16169
|
+
if (request.maxDirectSubChats !== null && request.existingSubChats !== null && request.existingSubChats + count > request.maxDirectSubChats) {
|
|
16170
|
+
return false;
|
|
16171
|
+
}
|
|
16172
|
+
return true;
|
|
16173
|
+
};
|
|
16174
|
+
const isAutoApprovalWithinServerLimits = (request) => {
|
|
16175
|
+
if (!isApprovalWithinServerLimits(request)) return false;
|
|
16176
|
+
return request.maxAutoSubChats === null || request.subChats.length <= request.maxAutoSubChats;
|
|
16177
|
+
};
|
|
16178
|
+
const handleSubChatEvent = async (event) => {
|
|
16179
|
+
params.onSubChatEvent?.(event);
|
|
16180
|
+
if (event.type === "spawn_sub_chats") {
|
|
16181
|
+
if (params.incognito || !chatKeyBytes) return;
|
|
16182
|
+
const rawSubChats = event.payload.sub_chats;
|
|
16183
|
+
if (!Array.isArray(rawSubChats)) return;
|
|
16184
|
+
const metadataPayloads = await buildSubChatEncryptedMetadataPayloads({
|
|
16185
|
+
parentChatId: chatId,
|
|
16186
|
+
parentChatKey: chatKeyBytes,
|
|
16187
|
+
encryptedParentChatKey: encryptedChatKey,
|
|
16188
|
+
subChats: rawSubChats
|
|
16193
16189
|
});
|
|
16190
|
+
for (const metadataPayload of metadataPayloads) {
|
|
16191
|
+
await ws.sendAsync("encrypted_chat_metadata", metadataPayload);
|
|
16192
|
+
}
|
|
16193
|
+
return;
|
|
16194
16194
|
}
|
|
16195
|
-
|
|
16196
|
-
|
|
16197
|
-
|
|
16198
|
-
|
|
16199
|
-
|
|
16200
|
-
|
|
16201
|
-
const
|
|
16202
|
-
|
|
16203
|
-
|
|
16195
|
+
if (event.type !== "sub_chat_confirmation_required") return;
|
|
16196
|
+
const payload = event.payload;
|
|
16197
|
+
const confirmationChatId = typeof payload.chat_id === "string" ? payload.chat_id : chatId;
|
|
16198
|
+
const taskId = typeof payload.task_id === "string" ? payload.task_id : null;
|
|
16199
|
+
const subChats = Array.isArray(payload.sub_chats) ? payload.sub_chats : [];
|
|
16200
|
+
if (!taskId) return;
|
|
16201
|
+
const request = {
|
|
16202
|
+
chatId: confirmationChatId,
|
|
16203
|
+
taskId,
|
|
16204
|
+
subChats,
|
|
16205
|
+
maxAutoSubChats: numberOrNull(payload.max_auto_sub_chats),
|
|
16206
|
+
maxDirectSubChats: numberOrNull(payload.max_direct_sub_chats),
|
|
16207
|
+
existingSubChats: numberOrNull(payload.existing_sub_chats),
|
|
16208
|
+
remainingSubChats: numberOrNull(payload.remaining_sub_chats)
|
|
16209
|
+
};
|
|
16210
|
+
const withinLimits = isApprovalWithinServerLimits(request);
|
|
16211
|
+
const approved = params.autoApproveSubChats ? isAutoApprovalWithinServerLimits(request) : withinLimits && params.onSubChatApprovalRequest ? await params.onSubChatApprovalRequest(request) : false;
|
|
16212
|
+
await ws.sendAsync(
|
|
16213
|
+
"sub_chat_confirmation",
|
|
16214
|
+
buildSubChatConfirmationPayload({
|
|
16215
|
+
chatId: request.chatId,
|
|
16216
|
+
taskId: request.taskId,
|
|
16217
|
+
approved,
|
|
16218
|
+
approveCount: approved ? request.subChats.length : void 0
|
|
16219
|
+
})
|
|
16204
16220
|
);
|
|
16205
|
-
|
|
16206
|
-
|
|
16207
|
-
|
|
16208
|
-
|
|
16209
|
-
|
|
16210
|
-
|
|
16211
|
-
|
|
16212
|
-
|
|
16213
|
-
|
|
16214
|
-
|
|
16215
|
-
|
|
16216
|
-
|
|
16217
|
-
|
|
16218
|
-
|
|
16219
|
-
|
|
16220
|
-
requestId: event.requestId,
|
|
16221
|
-
requestedKeys: event.requestedKeys,
|
|
16222
|
-
approvedKeys,
|
|
16223
|
-
entryCount: approvedMemories.length
|
|
16224
|
-
});
|
|
16225
|
-
await ws.sendAsync("app_settings_memories_confirmed", {
|
|
16226
|
-
chat_id: event.chatId,
|
|
16227
|
-
app_settings_memories: approvedMemories
|
|
16221
|
+
};
|
|
16222
|
+
const persistSystemMessage = async (systemMessage) => {
|
|
16223
|
+
if (!chatKeyBytes) return;
|
|
16224
|
+
await ws.sendAsync("chat_system_message_added", {
|
|
16225
|
+
chat_id: chatId,
|
|
16226
|
+
message: {
|
|
16227
|
+
message_id: systemMessage.message_id,
|
|
16228
|
+
role: "system",
|
|
16229
|
+
encrypted_content: await encryptWithAesGcmCombined(
|
|
16230
|
+
systemMessage.content,
|
|
16231
|
+
chatKeyBytes
|
|
16232
|
+
),
|
|
16233
|
+
created_at: systemMessage.created_at,
|
|
16234
|
+
user_message_id: systemMessage.user_message_id
|
|
16235
|
+
}
|
|
16228
16236
|
});
|
|
16229
|
-
await
|
|
16237
|
+
await ws.waitForMessage(
|
|
16238
|
+
"system_message_confirmed",
|
|
16239
|
+
(payload) => {
|
|
16240
|
+
const p = payload;
|
|
16241
|
+
return p.message_id === systemMessage.message_id;
|
|
16242
|
+
},
|
|
16243
|
+
2e4
|
|
16244
|
+
);
|
|
16245
|
+
};
|
|
16246
|
+
const persistMemoryRequestSystemMessage = async (event) => {
|
|
16247
|
+
const requestId = event.requestId ?? `${messageId}-memory-request`;
|
|
16248
|
+
await persistSystemMessage(buildAppSettingsMemoryRequestSystemMessage({
|
|
16230
16249
|
userMessageId: messageId,
|
|
16231
|
-
|
|
16232
|
-
|
|
16233
|
-
|
|
16250
|
+
requestId,
|
|
16251
|
+
requestedKeys: event.requestedKeys,
|
|
16252
|
+
entryCounts: memoryCountsLoaded ? new Map(event.requestedKeys.map((key) => [key, availableMemories.filter((memory) => `${memory.app_id}-${memory.item_type}` === key).length])) : void 0,
|
|
16234
16253
|
createdAt: Math.floor(Date.now() / 1e3)
|
|
16235
16254
|
}));
|
|
16236
|
-
|
|
16237
|
-
|
|
16238
|
-
|
|
16239
|
-
|
|
16240
|
-
|
|
16241
|
-
|
|
16242
|
-
|
|
16243
|
-
|
|
16244
|
-
|
|
16245
|
-
|
|
16246
|
-
|
|
16247
|
-
|
|
16248
|
-
|
|
16249
|
-
|
|
16250
|
-
if (!chatKeyBytes || events.length === 0) return;
|
|
16251
|
-
for (const event of events) {
|
|
16252
|
-
if (persistedTaskEventIds.has(event.event_id)) continue;
|
|
16253
|
-
await this.persistEncryptedSystemMessage(
|
|
16254
|
-
ws,
|
|
16255
|
-
await buildTaskEventSystemMessage({
|
|
16256
|
-
chatKey: chatKeyBytes,
|
|
16257
|
-
userMessageId: messageId,
|
|
16258
|
-
event
|
|
16259
|
-
}),
|
|
16260
|
-
chatId
|
|
16261
|
-
);
|
|
16262
|
-
persistedTaskEventIds.add(event.event_id);
|
|
16263
|
-
}
|
|
16264
|
-
};
|
|
16265
|
-
const streamOpts = {
|
|
16266
|
-
onStream: params.onStream,
|
|
16267
|
-
onSubChatEvent: handleSubChatEvent,
|
|
16268
|
-
onAppSettingsMemoriesRequest: handleAppSettingsMemoriesRequest,
|
|
16269
|
-
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
16270
|
-
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
16271
|
-
};
|
|
16272
|
-
dispatchHandlersReady();
|
|
16273
|
-
if (!shouldWaitForAi) {
|
|
16274
|
-
ws.close();
|
|
16275
|
-
} else if (params.incognito) {
|
|
16276
|
-
try {
|
|
16277
|
-
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId, {
|
|
16278
|
-
...streamOpts,
|
|
16279
|
-
timeoutMs: params.responseTimeoutMs,
|
|
16280
|
-
recoveryTurnId: savedTurnId
|
|
16281
|
-
}));
|
|
16282
|
-
assistantMessageId = resp.messageId;
|
|
16283
|
-
assistant = resp.content;
|
|
16284
|
-
category = resp.category;
|
|
16285
|
-
modelName = resp.modelName;
|
|
16286
|
-
tokenUsage = resp.tokenUsage;
|
|
16287
|
-
promptBudget = resp.promptBudget;
|
|
16288
|
-
taskEvents = resp.taskEvents;
|
|
16289
|
-
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
16290
|
-
subChatEvents = resp.subChatEvents;
|
|
16291
|
-
if (resp.status === "waiting_for_user") {
|
|
16292
|
-
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
16293
|
-
return {
|
|
16294
|
-
status: resp.status,
|
|
16295
|
-
chatId,
|
|
16296
|
-
messageId: assistantMessageId,
|
|
16297
|
-
assistant,
|
|
16298
|
-
category,
|
|
16299
|
-
modelName,
|
|
16300
|
-
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
16301
|
-
followUpSuggestions,
|
|
16302
|
-
taskProposals,
|
|
16303
|
-
taskUpdateProposals,
|
|
16304
|
-
taskEvents,
|
|
16305
|
-
pendingTaskUpdateJobs,
|
|
16306
|
-
subChatEvents,
|
|
16307
|
-
appSettingsMemoryRequests,
|
|
16308
|
-
tokenUsage,
|
|
16309
|
-
promptBudget
|
|
16310
|
-
};
|
|
16311
|
-
}
|
|
16312
|
-
} finally {
|
|
16313
|
-
ws.close();
|
|
16314
|
-
}
|
|
16315
|
-
} else {
|
|
16316
|
-
try {
|
|
16317
|
-
const recoverCurrentTurnOutputs = async () => {
|
|
16318
|
-
const pages = ws.drainAvailableRecoveryOutputPages();
|
|
16319
|
-
if (pages.length === 0) return;
|
|
16320
|
-
if (!ownerId2) throw new Error("Saved chat recovery output requires the owner identity.");
|
|
16321
|
-
if (pages.some((page) => page.some((output) => output.root_chat_id === chatId || output.target_chat_id === chatId)) && (!chatKeyBytes || !encryptedChatKey)) {
|
|
16322
|
-
throw new Error("Saved chat recovery output requires the root chat key.");
|
|
16255
|
+
};
|
|
16256
|
+
const responseCategoriesFromMemories = (approvedMemories) => {
|
|
16257
|
+
const counts = /* @__PURE__ */ new Map();
|
|
16258
|
+
for (const memory of approvedMemories) {
|
|
16259
|
+
const key = `${memory.app_id}-${memory.item_key}`;
|
|
16260
|
+
const existing = counts.get(key);
|
|
16261
|
+
if (existing) {
|
|
16262
|
+
existing.entryCount += 1;
|
|
16263
|
+
} else {
|
|
16264
|
+
counts.set(key, {
|
|
16265
|
+
appId: memory.app_id,
|
|
16266
|
+
itemType: memory.item_key,
|
|
16267
|
+
entryCount: 1
|
|
16268
|
+
});
|
|
16323
16269
|
}
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16336
|
-
|
|
16270
|
+
}
|
|
16271
|
+
return [...counts.values()];
|
|
16272
|
+
};
|
|
16273
|
+
const handleAppSettingsMemoriesRequest = async (event) => {
|
|
16274
|
+
await persistMemoryRequestSystemMessage(event);
|
|
16275
|
+
if (params.autoApproveMemories) {
|
|
16276
|
+
const requested = new Set(event.requestedKeys);
|
|
16277
|
+
const unadvertisedKeys = event.requestedKeys.filter(
|
|
16278
|
+
(key) => !memoryMetadataKeys.includes(key)
|
|
16279
|
+
);
|
|
16280
|
+
if (unadvertisedKeys.length > 0) {
|
|
16281
|
+
throw new Error(
|
|
16282
|
+
`Refusing to auto-approve unadvertised memory categories: ${unadvertisedKeys.join(", ")}`
|
|
16283
|
+
);
|
|
16337
16284
|
}
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
|
|
16345
|
-
|
|
16346
|
-
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
...pausedHandoff ? [pausedHandoff.then(
|
|
16353
|
-
(frame) => ({ kind: "paused", frame }),
|
|
16354
|
-
() => new Promise(() => {
|
|
16355
|
-
})
|
|
16356
|
-
)] : []
|
|
16357
|
-
]);
|
|
16358
|
-
if (outcome.kind === "paused") {
|
|
16359
|
-
void responsePromise.catch(() => {
|
|
16285
|
+
const approvedMemories = availableMemories.filter((memory) => requested.has(`${memory.app_id}-${memory.item_type}`)).map((memory) => ({
|
|
16286
|
+
app_id: memory.app_id,
|
|
16287
|
+
item_key: memory.item_type,
|
|
16288
|
+
content: memory.data
|
|
16289
|
+
}));
|
|
16290
|
+
const approvedKeys = [
|
|
16291
|
+
...new Set(approvedMemories.map((memory) => `${memory.app_id}-${memory.item_key}`))
|
|
16292
|
+
];
|
|
16293
|
+
const categories = responseCategoriesFromMemories(approvedMemories);
|
|
16294
|
+
appSettingsMemoryRequests.push({
|
|
16295
|
+
requestId: event.requestId,
|
|
16296
|
+
requestedKeys: event.requestedKeys,
|
|
16297
|
+
approvedKeys,
|
|
16298
|
+
entryCount: approvedMemories.length
|
|
16360
16299
|
});
|
|
16361
|
-
|
|
16362
|
-
|
|
16363
|
-
|
|
16364
|
-
void responsePromise.catch(() => {
|
|
16300
|
+
await ws.sendAsync("app_settings_memories_confirmed", {
|
|
16301
|
+
chat_id: event.chatId,
|
|
16302
|
+
app_settings_memories: approvedMemories
|
|
16365
16303
|
});
|
|
16366
|
-
|
|
16367
|
-
return {
|
|
16368
|
-
status: "queued",
|
|
16369
|
-
chatId,
|
|
16370
|
-
messageId: null,
|
|
16304
|
+
await persistSystemMessage(buildAppSettingsMemoryResponseSystemMessage({
|
|
16371
16305
|
userMessageId: messageId,
|
|
16372
|
-
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
|
|
16378
|
-
followUpSuggestions: [],
|
|
16379
|
-
taskProposals: [],
|
|
16380
|
-
taskUpdateProposals: [],
|
|
16381
|
-
taskEvents: [],
|
|
16382
|
-
pendingTaskUpdateJobs: [],
|
|
16383
|
-
subChatEvents: [],
|
|
16384
|
-
appSettingsMemoryRequests: [],
|
|
16385
|
-
tokenUsage: null,
|
|
16386
|
-
promptBudget: null
|
|
16387
|
-
};
|
|
16306
|
+
messageId: `${messageId}-memory-response`,
|
|
16307
|
+
action: "included",
|
|
16308
|
+
categories,
|
|
16309
|
+
createdAt: Math.floor(Date.now() / 1e3)
|
|
16310
|
+
}));
|
|
16311
|
+
return;
|
|
16388
16312
|
}
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
|
|
16399
|
-
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16404
|
-
await
|
|
16405
|
-
|
|
16406
|
-
|
|
16407
|
-
|
|
16408
|
-
|
|
16409
|
-
|
|
16410
|
-
|
|
16411
|
-
|
|
16412
|
-
|
|
16413
|
-
|
|
16414
|
-
taskProposals,
|
|
16415
|
-
taskUpdateProposals,
|
|
16416
|
-
taskEvents,
|
|
16417
|
-
pendingTaskUpdateJobs,
|
|
16418
|
-
subChatEvents,
|
|
16419
|
-
appSettingsMemoryRequests,
|
|
16420
|
-
tokenUsage,
|
|
16421
|
-
promptBudget
|
|
16422
|
-
};
|
|
16313
|
+
appSettingsMemoryRequests.push({
|
|
16314
|
+
requestId: event.requestId,
|
|
16315
|
+
requestedKeys: event.requestedKeys,
|
|
16316
|
+
approvedKeys: [],
|
|
16317
|
+
entryCount: 0
|
|
16318
|
+
});
|
|
16319
|
+
throw new Error(
|
|
16320
|
+
`The assistant requested memories (${event.requestedKeys.join(", ")}). Rerun with --auto-approve-memories to explicitly approve requested memory categories from the CLI, or continue this chat in the web app to approve or reject the request.`
|
|
16321
|
+
);
|
|
16322
|
+
};
|
|
16323
|
+
const persistedTaskEventIds = /* @__PURE__ */ new Set();
|
|
16324
|
+
const persistTaskEventSystemMessages = async (events) => {
|
|
16325
|
+
if (!chatKeyBytes || events.length === 0) return;
|
|
16326
|
+
for (const event of events) {
|
|
16327
|
+
if (persistedTaskEventIds.has(event.event_id)) continue;
|
|
16328
|
+
await this.persistEncryptedSystemMessage(
|
|
16329
|
+
ws,
|
|
16330
|
+
await buildTaskEventSystemMessage({
|
|
16331
|
+
chatKey: chatKeyBytes,
|
|
16332
|
+
userMessageId: messageId,
|
|
16333
|
+
event
|
|
16334
|
+
}),
|
|
16335
|
+
chatId
|
|
16336
|
+
);
|
|
16337
|
+
persistedTaskEventIds.add(event.event_id);
|
|
16423
16338
|
}
|
|
16424
|
-
|
|
16425
|
-
|
|
16426
|
-
|
|
16339
|
+
};
|
|
16340
|
+
const streamOpts = {
|
|
16341
|
+
onStream: params.onStream,
|
|
16342
|
+
onSubChatEvent: handleSubChatEvent,
|
|
16343
|
+
onAppSettingsMemoriesRequest: handleAppSettingsMemoriesRequest,
|
|
16344
|
+
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
16345
|
+
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
16346
|
+
};
|
|
16347
|
+
dispatchHandlersReady();
|
|
16348
|
+
if (!shouldWaitForAi) {
|
|
16349
|
+
ws.close();
|
|
16350
|
+
} else if (params.incognito) {
|
|
16351
|
+
try {
|
|
16352
|
+
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId, {
|
|
16353
|
+
...streamOpts,
|
|
16354
|
+
timeoutMs: params.responseTimeoutMs,
|
|
16355
|
+
recoveryTurnId: savedTurnId
|
|
16356
|
+
}));
|
|
16357
|
+
assistantMessageId = resp.messageId;
|
|
16358
|
+
assistant = resp.content;
|
|
16359
|
+
category = resp.category;
|
|
16360
|
+
modelName = resp.modelName;
|
|
16361
|
+
tokenUsage = resp.tokenUsage;
|
|
16362
|
+
promptBudget = resp.promptBudget;
|
|
16363
|
+
taskEvents = resp.taskEvents;
|
|
16364
|
+
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
16365
|
+
subChatEvents = resp.subChatEvents;
|
|
16366
|
+
if (resp.status === "waiting_for_user") {
|
|
16367
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
16368
|
+
return {
|
|
16369
|
+
status: resp.status,
|
|
16370
|
+
chatId,
|
|
16371
|
+
messageId: assistantMessageId,
|
|
16372
|
+
assistant,
|
|
16373
|
+
category,
|
|
16374
|
+
modelName,
|
|
16375
|
+
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
16376
|
+
followUpSuggestions,
|
|
16377
|
+
taskProposals,
|
|
16378
|
+
taskUpdateProposals,
|
|
16379
|
+
taskEvents,
|
|
16380
|
+
pendingTaskUpdateJobs,
|
|
16381
|
+
subChatEvents,
|
|
16382
|
+
appSettingsMemoryRequests,
|
|
16383
|
+
tokenUsage,
|
|
16384
|
+
promptBudget
|
|
16385
|
+
};
|
|
16427
16386
|
}
|
|
16428
|
-
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
|
|
16438
|
-
|
|
16439
|
-
});
|
|
16440
|
-
const claimPromise = ws.waitForMessage(
|
|
16441
|
-
"recovery_job_claimed",
|
|
16442
|
-
(payload) => {
|
|
16443
|
-
const received = payload;
|
|
16444
|
-
return received.job_id === recoveryJobId && (received.request_id === void 0 || received.request_id === requestId);
|
|
16445
|
-
},
|
|
16446
|
-
claimRetryDeadline === null ? 2e4 : Math.min(2e4, Math.max(1, claimRetryDeadline - Date.now()))
|
|
16447
|
-
);
|
|
16448
|
-
try {
|
|
16449
|
-
try {
|
|
16450
|
-
await ws.sendAsync("recovery_job_claim", {
|
|
16451
|
-
protocol_version: 1,
|
|
16452
|
-
job_id: recoveryJobId,
|
|
16453
|
-
request_id: requestId
|
|
16454
|
-
});
|
|
16455
|
-
} catch (error) {
|
|
16456
|
-
void claimPromise.catch(() => {
|
|
16457
|
-
});
|
|
16458
|
-
throw error;
|
|
16459
|
-
}
|
|
16460
|
-
claim = (await claimPromise).payload;
|
|
16461
|
-
break;
|
|
16462
|
-
} catch (error) {
|
|
16463
|
-
if (!(error instanceof WebSocketProtocolError) || error.code !== "lease_conflict" || !matchingLeaseConflict) throw error;
|
|
16464
|
-
claimRetryDeadline ??= Date.now() + Math.min(
|
|
16465
|
-
1e4,
|
|
16466
|
-
Math.max(0, params.responseTimeoutMs ?? 9e4)
|
|
16467
|
-
);
|
|
16468
|
-
const remainingMs = claimRetryDeadline - Date.now();
|
|
16469
|
-
if (remainingMs <= claimRetryDelayMs) throw error;
|
|
16470
|
-
await new Promise((resolve20) => setTimeout(resolve20, claimRetryDelayMs));
|
|
16471
|
-
claimRetryDelayMs = Math.min(claimRetryDelayMs * 2, 2e3);
|
|
16472
|
-
} finally {
|
|
16473
|
-
stopWatchingErrors();
|
|
16387
|
+
} finally {
|
|
16388
|
+
ws.close();
|
|
16389
|
+
}
|
|
16390
|
+
} else {
|
|
16391
|
+
try {
|
|
16392
|
+
const recoverCurrentTurnOutputs = async () => {
|
|
16393
|
+
const pages = ws.drainAvailableRecoveryOutputPages();
|
|
16394
|
+
if (pages.length === 0) return;
|
|
16395
|
+
if (!ownerId2) throw new Error("Saved chat recovery output requires the owner identity.");
|
|
16396
|
+
if (pages.some((page) => page.some((output) => output.root_chat_id === chatId || output.target_chat_id === chatId)) && (!chatKeyBytes || !encryptedChatKey)) {
|
|
16397
|
+
throw new Error("Saved chat recovery output requires the root chat key.");
|
|
16474
16398
|
}
|
|
16399
|
+
const cache = loadSyncCache(teamId) ?? {
|
|
16400
|
+
syncedAt: Date.now(),
|
|
16401
|
+
totalChatCount: 0,
|
|
16402
|
+
loadedChatCount: 0,
|
|
16403
|
+
chats: [],
|
|
16404
|
+
embeds: [],
|
|
16405
|
+
embedKeys: [],
|
|
16406
|
+
chatKeyWrappers: []
|
|
16407
|
+
};
|
|
16408
|
+
if (encryptedChatKey) {
|
|
16409
|
+
const root = cache.chats.find((chat) => String(chat.details.id ?? "") === chatId);
|
|
16410
|
+
if (root) root.details.encrypted_chat_key = encryptedChatKey;
|
|
16411
|
+
else cache.chats.push({ details: { id: chatId, encrypted_chat_key: encryptedChatKey }, messages: [] });
|
|
16412
|
+
}
|
|
16413
|
+
await this.replayCurrentTurnRecoveryOutputPages(ws, ownerId2, pages, cache, teamId, chatId);
|
|
16414
|
+
};
|
|
16415
|
+
const responsePromise = precollectedResponse ?? ws.collectAiResponse(messageId, chatId, {
|
|
16416
|
+
...streamOpts,
|
|
16417
|
+
timeoutMs: params.responseTimeoutMs,
|
|
16418
|
+
recoveryTurnId: savedTurnId
|
|
16419
|
+
});
|
|
16420
|
+
const outcome = await Promise.race([
|
|
16421
|
+
responsePromise.then((response) => ({ kind: "response", response })),
|
|
16422
|
+
...queuedAdmission ? [queuedAdmission.then(
|
|
16423
|
+
(frame) => ({ kind: "queued", frame }),
|
|
16424
|
+
() => new Promise(() => {
|
|
16425
|
+
})
|
|
16426
|
+
)] : [],
|
|
16427
|
+
...pausedHandoff ? [pausedHandoff.then(
|
|
16428
|
+
(frame) => ({ kind: "paused", frame }),
|
|
16429
|
+
() => new Promise(() => {
|
|
16430
|
+
})
|
|
16431
|
+
)] : []
|
|
16432
|
+
]);
|
|
16433
|
+
if (outcome.kind === "paused") {
|
|
16434
|
+
void responsePromise.catch(() => {
|
|
16435
|
+
});
|
|
16436
|
+
throw new Error("Message saved, but AI handoff is paused. No assistant run started; check the chat before retrying.");
|
|
16475
16437
|
}
|
|
16476
|
-
|
|
16477
|
-
|
|
16478
|
-
const assistantId = typeof claim.assistant_message_id === "string" ? claim.assistant_message_id : null;
|
|
16479
|
-
const keyVersion = typeof claim.chat_key_version === "number" && Number.isSafeInteger(claim.chat_key_version) ? claim.chat_key_version : null;
|
|
16480
|
-
const claimMatchesExpectedTerminal = assistantId && keyVersion === 1 && claim.chat_id === chatId && claim.turn_id === savedTurnId;
|
|
16481
|
-
if (claim.state === "TERMINAL" && claimMatchesExpectedTerminal) {
|
|
16482
|
-
assistant = resp.content;
|
|
16483
|
-
category = resp.category;
|
|
16484
|
-
modelName = resp.modelName;
|
|
16485
|
-
assistantMessageId = assistantId;
|
|
16486
|
-
await this.persistStreamedEmbeds({
|
|
16487
|
-
ws,
|
|
16488
|
-
embeds: resp.embeds,
|
|
16489
|
-
chatId,
|
|
16490
|
-
chatKeyBytes,
|
|
16491
|
-
fallbackMessageId: assistantId,
|
|
16492
|
-
ownerId: ownerId2
|
|
16438
|
+
if (outcome.kind === "queued") {
|
|
16439
|
+
void responsePromise.catch(() => {
|
|
16493
16440
|
});
|
|
16494
|
-
|
|
16495
|
-
|
|
16441
|
+
const frame = outcome.frame.payload;
|
|
16442
|
+
return {
|
|
16443
|
+
status: "queued",
|
|
16444
|
+
chatId,
|
|
16445
|
+
messageId: null,
|
|
16446
|
+
userMessageId: messageId,
|
|
16447
|
+
queueMode: frame.queue_mode === "steer" ? "steer" : "follow_up",
|
|
16448
|
+
queueRevision: typeof frame.queue_revision === "number" ? frame.queue_revision : 1,
|
|
16449
|
+
assistant: "",
|
|
16450
|
+
category: null,
|
|
16451
|
+
modelName: null,
|
|
16452
|
+
mateName: null,
|
|
16453
|
+
followUpSuggestions: [],
|
|
16454
|
+
taskProposals: [],
|
|
16455
|
+
taskUpdateProposals: [],
|
|
16456
|
+
taskEvents: [],
|
|
16457
|
+
pendingTaskUpdateJobs: [],
|
|
16458
|
+
subChatEvents: [],
|
|
16459
|
+
appSettingsMemoryRequests: [],
|
|
16460
|
+
tokenUsage: null,
|
|
16461
|
+
promptBudget: null
|
|
16462
|
+
};
|
|
16463
|
+
}
|
|
16464
|
+
const resp = outcome.response;
|
|
16465
|
+
assistantMessageId = resp.messageId;
|
|
16466
|
+
assistant = resp.content;
|
|
16467
|
+
category = resp.category;
|
|
16468
|
+
modelName = resp.modelName;
|
|
16469
|
+
tokenUsage = resp.tokenUsage;
|
|
16470
|
+
promptBudget = resp.promptBudget;
|
|
16471
|
+
followUpSuggestions = resp.followUpSuggestions;
|
|
16472
|
+
taskProposals = resp.taskProposals;
|
|
16473
|
+
taskUpdateProposals = resp.taskUpdateProposals;
|
|
16474
|
+
taskEvents = resp.taskEvents;
|
|
16475
|
+
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
16476
|
+
subChatEvents = resp.subChatEvents;
|
|
16477
|
+
if (resp.status === "waiting_for_user") {
|
|
16496
16478
|
await persistTaskEventSystemMessages(taskEvents);
|
|
16497
16479
|
await recoverCurrentTurnOutputs();
|
|
16498
|
-
await projectFocusCountdown?.flush();
|
|
16499
|
-
await focusPersistence;
|
|
16500
|
-
await chatContextEvents?.flush();
|
|
16501
|
-
if (focusPersistenceError) throw focusPersistenceError;
|
|
16502
|
-
const mateName2 = category ? MATE_NAMES[category] ?? null : null;
|
|
16503
16480
|
return {
|
|
16504
|
-
status:
|
|
16481
|
+
status: resp.status,
|
|
16505
16482
|
chatId,
|
|
16506
16483
|
messageId: assistantMessageId,
|
|
16507
16484
|
assistant,
|
|
16508
16485
|
category,
|
|
16509
16486
|
modelName,
|
|
16510
|
-
mateName:
|
|
16487
|
+
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
16511
16488
|
followUpSuggestions,
|
|
16512
16489
|
taskProposals,
|
|
16513
16490
|
taskUpdateProposals,
|
|
@@ -16519,223 +16496,326 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16519
16496
|
promptBudget
|
|
16520
16497
|
};
|
|
16521
16498
|
}
|
|
16522
|
-
if (
|
|
16523
|
-
|
|
16524
|
-
|
|
16525
|
-
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
|
|
16529
|
-
|
|
16530
|
-
|
|
16531
|
-
|
|
16532
|
-
|
|
16533
|
-
|
|
16534
|
-
|
|
16535
|
-
|
|
16536
|
-
|
|
16537
|
-
|
|
16538
|
-
|
|
16539
|
-
|
|
16540
|
-
|
|
16499
|
+
if (chatKeyBytes) {
|
|
16500
|
+
if (!resp.recoveryJobId || !savedTurnId || !ownerId2) {
|
|
16501
|
+
throw new Error("Saved chat completion did not include recoverable terminal identity.");
|
|
16502
|
+
}
|
|
16503
|
+
const recoveryJobId = resp.recoveryJobId;
|
|
16504
|
+
let claimRetryDeadline = null;
|
|
16505
|
+
let claim;
|
|
16506
|
+
let claimRetryDelayMs = 500;
|
|
16507
|
+
while (true) {
|
|
16508
|
+
const requestId = randomUUID9();
|
|
16509
|
+
let matchingLeaseConflict = false;
|
|
16510
|
+
const stopWatchingErrors = ws.onMessageType("error", (payload) => {
|
|
16511
|
+
if (payload?.code === "lease_conflict" && payload.job_id === recoveryJobId && payload.request_id === requestId) {
|
|
16512
|
+
matchingLeaseConflict = true;
|
|
16513
|
+
}
|
|
16514
|
+
});
|
|
16515
|
+
const claimPromise = ws.waitForMessage(
|
|
16516
|
+
"recovery_job_claimed",
|
|
16517
|
+
(payload) => {
|
|
16518
|
+
const received = payload;
|
|
16519
|
+
return received.job_id === recoveryJobId && (received.request_id === void 0 || received.request_id === requestId);
|
|
16520
|
+
},
|
|
16521
|
+
claimRetryDeadline === null ? 2e4 : Math.min(2e4, Math.max(1, claimRetryDeadline - Date.now()))
|
|
16522
|
+
);
|
|
16523
|
+
try {
|
|
16524
|
+
try {
|
|
16525
|
+
await ws.sendAsync("recovery_job_claim", {
|
|
16526
|
+
protocol_version: 1,
|
|
16527
|
+
job_id: recoveryJobId,
|
|
16528
|
+
request_id: requestId
|
|
16529
|
+
});
|
|
16530
|
+
} catch (error) {
|
|
16531
|
+
void claimPromise.catch(() => {
|
|
16532
|
+
});
|
|
16533
|
+
throw error;
|
|
16534
|
+
}
|
|
16535
|
+
claim = (await claimPromise).payload;
|
|
16536
|
+
break;
|
|
16537
|
+
} catch (error) {
|
|
16538
|
+
if (!(error instanceof WebSocketProtocolError) || error.code !== "lease_conflict" || !matchingLeaseConflict) throw error;
|
|
16539
|
+
claimRetryDeadline ??= Date.now() + Math.min(
|
|
16540
|
+
1e4,
|
|
16541
|
+
Math.max(0, params.responseTimeoutMs ?? 9e4)
|
|
16542
|
+
);
|
|
16543
|
+
const remainingMs = claimRetryDeadline - Date.now();
|
|
16544
|
+
if (remainingMs <= claimRetryDelayMs) throw error;
|
|
16545
|
+
await new Promise((resolve20) => setTimeout(resolve20, claimRetryDelayMs));
|
|
16546
|
+
claimRetryDelayMs = Math.min(claimRetryDelayMs * 2, 2e3);
|
|
16547
|
+
} finally {
|
|
16548
|
+
stopWatchingErrors();
|
|
16549
|
+
}
|
|
16550
|
+
}
|
|
16551
|
+
const leaseToken = typeof claim.lease_token === "string" ? claim.lease_token : null;
|
|
16552
|
+
const leaseGeneration = typeof claim.lease_generation === "number" && Number.isSafeInteger(claim.lease_generation) ? claim.lease_generation : null;
|
|
16553
|
+
const assistantId = typeof claim.assistant_message_id === "string" ? claim.assistant_message_id : null;
|
|
16554
|
+
const keyVersion = typeof claim.chat_key_version === "number" && Number.isSafeInteger(claim.chat_key_version) ? claim.chat_key_version : null;
|
|
16555
|
+
const claimMatchesExpectedTerminal = assistantId && keyVersion === 1 && claim.chat_id === chatId && claim.turn_id === savedTurnId;
|
|
16556
|
+
if (claim.state === "TERMINAL" && claimMatchesExpectedTerminal) {
|
|
16557
|
+
assistant = resp.content;
|
|
16558
|
+
category = resp.category;
|
|
16559
|
+
modelName = resp.modelName;
|
|
16560
|
+
assistantMessageId = assistantId;
|
|
16561
|
+
await this.persistStreamedEmbeds({
|
|
16562
|
+
ws,
|
|
16563
|
+
embeds: resp.embeds,
|
|
16564
|
+
chatId,
|
|
16565
|
+
chatKeyBytes,
|
|
16566
|
+
fallbackMessageId: assistantId,
|
|
16567
|
+
ownerId: ownerId2
|
|
16568
|
+
});
|
|
16569
|
+
clearSyncCache(teamId);
|
|
16570
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
16571
|
+
await persistTaskEventSystemMessages(taskEvents);
|
|
16572
|
+
await recoverCurrentTurnOutputs();
|
|
16573
|
+
await projectFocusCountdown?.flush();
|
|
16574
|
+
await focusPersistence;
|
|
16575
|
+
await chatContextEvents?.flush();
|
|
16576
|
+
if (focusPersistenceError) throw focusPersistenceError;
|
|
16577
|
+
const mateName2 = category ? MATE_NAMES[category] ?? null : null;
|
|
16578
|
+
return {
|
|
16579
|
+
status: "completed",
|
|
16580
|
+
chatId,
|
|
16581
|
+
messageId: assistantMessageId,
|
|
16582
|
+
assistant,
|
|
16583
|
+
category,
|
|
16584
|
+
modelName,
|
|
16585
|
+
mateName: mateName2,
|
|
16586
|
+
followUpSuggestions,
|
|
16587
|
+
taskProposals,
|
|
16588
|
+
taskUpdateProposals,
|
|
16589
|
+
taskEvents,
|
|
16590
|
+
pendingTaskUpdateJobs,
|
|
16591
|
+
subChatEvents,
|
|
16592
|
+
appSettingsMemoryRequests,
|
|
16593
|
+
tokenUsage,
|
|
16594
|
+
promptBudget
|
|
16595
|
+
};
|
|
16596
|
+
}
|
|
16597
|
+
if (claim.state !== "LEASED" || !leaseToken || leaseGeneration === null || leaseGeneration < 1 || !assistantId || keyVersion === null || keyVersion !== 1 || claim.chat_id !== chatId || claim.turn_id !== savedTurnId || typeof claim.sealed_payload !== "string") {
|
|
16598
|
+
throw new Error("Recovery job claim returned invalid lease or identity data.");
|
|
16599
|
+
}
|
|
16600
|
+
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
16601
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
16541
16602
|
chatId,
|
|
16542
|
-
turnId: savedTurnId,
|
|
16543
|
-
jobId: recoveryJobId,
|
|
16544
|
-
assistantMessageId: assistantId,
|
|
16545
16603
|
keyVersion
|
|
16604
|
+
);
|
|
16605
|
+
let envelope;
|
|
16606
|
+
try {
|
|
16607
|
+
envelope = JSON.parse(claim.sealed_payload);
|
|
16608
|
+
} catch {
|
|
16609
|
+
throw new Error("Recovery job contained an invalid sealed envelope.");
|
|
16546
16610
|
}
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
|
|
16551
|
-
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16560
|
-
|
|
16561
|
-
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
if (Object.keys(recovered).sort().join(",") !== expectedRecoveryFields.join(",") || recovered.assistant_message_id !== assistantId || recovered.chat_id !== chatId || recovered.turn_id !== savedTurnId || recovered.job_id !== recoveryJobId || recovered.key_version !== keyVersion || typeof recovered.content !== "string" || recovered.category !== null && typeof recovered.category !== "string" || recovered.model_name !== null && typeof recovered.model_name !== "string") {
|
|
16565
|
-
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
16566
|
-
}
|
|
16567
|
-
assistant = recovered.content;
|
|
16568
|
-
category = recovered.category;
|
|
16569
|
-
modelName = recovered.model_name;
|
|
16570
|
-
const completedAt = resp.recoveryCreatedAt ?? Math.floor(Date.now() / 1e3);
|
|
16571
|
-
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
16572
|
-
assistant,
|
|
16573
|
-
chatKeyBytes
|
|
16574
|
-
);
|
|
16575
|
-
const encryptedSenderName = await encryptWithAesGcmCombined(category ? MATE_NAMES[category] ?? "Assistant" : "Assistant", chatKeyBytes);
|
|
16576
|
-
const encryptedCategory = recovered.category ? await encryptWithAesGcmCombined(recovered.category, chatKeyBytes) : void 0;
|
|
16577
|
-
const encryptedModelName = recovered.model_name ? await encryptWithAesGcmCombined(recovered.model_name, chatKeyBytes) : void 0;
|
|
16578
|
-
const terminalPersistPayload = {
|
|
16579
|
-
protocol_version: 1,
|
|
16580
|
-
job_id: recoveryJobId,
|
|
16581
|
-
lease_token: leaseToken,
|
|
16582
|
-
lease_generation: leaseGeneration,
|
|
16583
|
-
encrypted_assistant_message: {
|
|
16584
|
-
client_message_id: assistantId,
|
|
16585
|
-
chat_id: chatId,
|
|
16586
|
-
encrypted_content: encryptedAssistantContent,
|
|
16587
|
-
encrypted_sender_name: encryptedSenderName,
|
|
16588
|
-
encrypted_category: encryptedCategory,
|
|
16589
|
-
encrypted_model_name: encryptedModelName,
|
|
16590
|
-
role: "assistant",
|
|
16591
|
-
user_message_id: messageId,
|
|
16592
|
-
created_at: completedAt,
|
|
16593
|
-
updated_at: completedAt
|
|
16611
|
+
const plaintext = await openChatCompletionRecoveryEnvelope(
|
|
16612
|
+
envelope,
|
|
16613
|
+
{
|
|
16614
|
+
recoveryPrivateKey: recoveryKeypair.privateKey,
|
|
16615
|
+
ownerId: ownerId2,
|
|
16616
|
+
chatId,
|
|
16617
|
+
turnId: savedTurnId,
|
|
16618
|
+
jobId: recoveryJobId,
|
|
16619
|
+
assistantMessageId: assistantId,
|
|
16620
|
+
keyVersion
|
|
16621
|
+
}
|
|
16622
|
+
);
|
|
16623
|
+
let recovered;
|
|
16624
|
+
try {
|
|
16625
|
+
recovered = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(plaintext));
|
|
16626
|
+
} catch {
|
|
16627
|
+
throw new Error("Recovery job plaintext was not valid UTF-8 JSON.");
|
|
16594
16628
|
}
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
"
|
|
16599
|
-
|
|
16600
|
-
|
|
16629
|
+
const expectedRecoveryFields = [
|
|
16630
|
+
"assistant_message_id",
|
|
16631
|
+
"category",
|
|
16632
|
+
"chat_id",
|
|
16633
|
+
"content",
|
|
16634
|
+
"job_id",
|
|
16635
|
+
"key_version",
|
|
16636
|
+
"model_name",
|
|
16637
|
+
"turn_id"
|
|
16638
|
+
];
|
|
16639
|
+
if (Object.keys(recovered).sort().join(",") !== expectedRecoveryFields.join(",") || recovered.assistant_message_id !== assistantId || recovered.chat_id !== chatId || recovered.turn_id !== savedTurnId || recovered.job_id !== recoveryJobId || recovered.key_version !== keyVersion || typeof recovered.content !== "string" || recovered.category !== null && typeof recovered.category !== "string" || recovered.model_name !== null && typeof recovered.model_name !== "string") {
|
|
16640
|
+
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
16641
|
+
}
|
|
16642
|
+
assistant = recovered.content;
|
|
16643
|
+
category = recovered.category;
|
|
16644
|
+
modelName = recovered.model_name;
|
|
16645
|
+
const completedAt = resp.recoveryCreatedAt ?? Math.floor(Date.now() / 1e3);
|
|
16646
|
+
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
16647
|
+
assistant,
|
|
16648
|
+
chatKeyBytes
|
|
16601
16649
|
);
|
|
16602
|
-
await
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
await persistTerminalRecovery(latestMessagesV);
|
|
16622
|
-
terminalExpectedMessagesV = latestMessagesV;
|
|
16623
|
-
terminalPersisted = true;
|
|
16624
|
-
}
|
|
16650
|
+
const encryptedSenderName = await encryptWithAesGcmCombined(category ? MATE_NAMES[category] ?? "Assistant" : "Assistant", chatKeyBytes);
|
|
16651
|
+
const encryptedCategory = recovered.category ? await encryptWithAesGcmCombined(recovered.category, chatKeyBytes) : void 0;
|
|
16652
|
+
const encryptedModelName = recovered.model_name ? await encryptWithAesGcmCombined(recovered.model_name, chatKeyBytes) : void 0;
|
|
16653
|
+
const terminalPersistPayload = {
|
|
16654
|
+
protocol_version: 1,
|
|
16655
|
+
job_id: recoveryJobId,
|
|
16656
|
+
lease_token: leaseToken,
|
|
16657
|
+
lease_generation: leaseGeneration,
|
|
16658
|
+
encrypted_assistant_message: {
|
|
16659
|
+
client_message_id: assistantId,
|
|
16660
|
+
chat_id: chatId,
|
|
16661
|
+
encrypted_content: encryptedAssistantContent,
|
|
16662
|
+
encrypted_sender_name: encryptedSenderName,
|
|
16663
|
+
encrypted_category: encryptedCategory,
|
|
16664
|
+
encrypted_model_name: encryptedModelName,
|
|
16665
|
+
role: "assistant",
|
|
16666
|
+
user_message_id: messageId,
|
|
16667
|
+
created_at: completedAt,
|
|
16668
|
+
updated_at: completedAt
|
|
16625
16669
|
}
|
|
16626
|
-
|
|
16627
|
-
|
|
16670
|
+
};
|
|
16671
|
+
const persistTerminalRecovery = async (expectedMessagesV) => {
|
|
16672
|
+
const persistedPromise = ws.waitForMessage(
|
|
16673
|
+
"recovery_job_persisted",
|
|
16674
|
+
(payload) => payload.job_id === recoveryJobId,
|
|
16675
|
+
2e4
|
|
16676
|
+
);
|
|
16677
|
+
await ws.sendAsync("recovery_job_persist", {
|
|
16678
|
+
...terminalPersistPayload,
|
|
16679
|
+
expected_messages_v: expectedMessagesV
|
|
16680
|
+
});
|
|
16681
|
+
await persistedPromise;
|
|
16682
|
+
};
|
|
16683
|
+
let terminalPersisted = false;
|
|
16684
|
+
try {
|
|
16685
|
+
await persistTerminalRecovery(terminalExpectedMessagesV);
|
|
16686
|
+
terminalPersisted = true;
|
|
16687
|
+
} catch (error) {
|
|
16688
|
+
if (error instanceof WebSocketProtocolError) {
|
|
16689
|
+
if (error.code === "version_conflict") {
|
|
16690
|
+
const latestCache = await this.ensureSynced(true, [chatId], { teamId });
|
|
16691
|
+
const latestChat = latestCache.chats.find(
|
|
16692
|
+
(chat) => String(chat.details.id ?? "") === chatId
|
|
16693
|
+
);
|
|
16694
|
+
const latestMessagesV = typeof latestChat?.details.messages_v === "number" && Number.isSafeInteger(latestChat.details.messages_v) ? latestChat.details.messages_v : null;
|
|
16695
|
+
if (latestMessagesV !== null && latestMessagesV > terminalExpectedMessagesV) {
|
|
16696
|
+
await persistTerminalRecovery(latestMessagesV);
|
|
16697
|
+
terminalExpectedMessagesV = latestMessagesV;
|
|
16698
|
+
terminalPersisted = true;
|
|
16699
|
+
}
|
|
16700
|
+
}
|
|
16701
|
+
if (!terminalPersisted) {
|
|
16702
|
+
throw new Error(`${error.message} (${error.code})`);
|
|
16703
|
+
}
|
|
16704
|
+
} else {
|
|
16705
|
+
throw error;
|
|
16628
16706
|
}
|
|
16629
|
-
} else {
|
|
16630
|
-
throw error;
|
|
16631
16707
|
}
|
|
16632
|
-
|
|
16633
|
-
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
ownerId: ownerId2
|
|
16644
|
-
});
|
|
16645
|
-
if (isNewChat && (resp.generatedTitle || resp.generatedIcon)) {
|
|
16646
|
-
const metadataStored = ws.waitForMessage(
|
|
16647
|
-
"encrypted_metadata_stored",
|
|
16648
|
-
(payload) => payload.chat_id === chatId,
|
|
16649
|
-
2e4
|
|
16650
|
-
);
|
|
16651
|
-
await ws.sendAsync("encrypted_chat_metadata", {
|
|
16652
|
-
chat_id: chatId,
|
|
16653
|
-
...teamId ? { team_id: teamId } : {},
|
|
16654
|
-
encrypted_chat_key: encryptedChatKey,
|
|
16655
|
-
...resp.generatedTitle ? { encrypted_title: await encryptWithAesGcmCombined(resp.generatedTitle, chatKeyBytes) } : {},
|
|
16656
|
-
...category ? { encrypted_chat_category: await encryptWithAesGcmCombined(category, chatKeyBytes) } : {},
|
|
16657
|
-
...resp.generatedIcon ? { encrypted_icon: await encryptWithAesGcmCombined(resp.generatedIcon, chatKeyBytes) } : {},
|
|
16658
|
-
versions: { messages_v: terminalExpectedMessagesV + 1 }
|
|
16708
|
+
if (!terminalPersisted) {
|
|
16709
|
+
throw new Error("Encrypted completion recovery did not persist terminal state.");
|
|
16710
|
+
}
|
|
16711
|
+
assistantMessageId = assistantId;
|
|
16712
|
+
await this.persistStreamedEmbeds({
|
|
16713
|
+
ws,
|
|
16714
|
+
embeds: resp.embeds,
|
|
16715
|
+
chatId,
|
|
16716
|
+
chatKeyBytes,
|
|
16717
|
+
fallbackMessageId: assistantId,
|
|
16718
|
+
ownerId: ownerId2
|
|
16659
16719
|
});
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
|
|
16668
|
-
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
const persistedTaskJobIds = await this.persistPendingTaskUpdateJobs({
|
|
16720
|
+
if (isNewChat && (resp.generatedTitle || resp.generatedIcon)) {
|
|
16721
|
+
const metadataStored = ws.waitForMessage(
|
|
16722
|
+
"encrypted_metadata_stored",
|
|
16723
|
+
(payload) => payload.chat_id === chatId,
|
|
16724
|
+
2e4
|
|
16725
|
+
);
|
|
16726
|
+
await ws.sendAsync("encrypted_chat_metadata", {
|
|
16727
|
+
chat_id: chatId,
|
|
16728
|
+
...teamId ? { team_id: teamId } : {},
|
|
16729
|
+
encrypted_chat_key: encryptedChatKey,
|
|
16730
|
+
...resp.generatedTitle ? { encrypted_title: await encryptWithAesGcmCombined(resp.generatedTitle, chatKeyBytes) } : {},
|
|
16731
|
+
...category ? { encrypted_chat_category: await encryptWithAesGcmCombined(category, chatKeyBytes) } : {},
|
|
16732
|
+
...resp.generatedIcon ? { encrypted_icon: await encryptWithAesGcmCombined(resp.generatedIcon, chatKeyBytes) } : {},
|
|
16733
|
+
versions: { messages_v: terminalExpectedMessagesV + 1 }
|
|
16734
|
+
});
|
|
16735
|
+
await metadataStored;
|
|
16736
|
+
}
|
|
16737
|
+
await this.persistPostProcessingMetadata({
|
|
16679
16738
|
ws,
|
|
16680
|
-
|
|
16681
|
-
events: taskEvents,
|
|
16739
|
+
chatId,
|
|
16682
16740
|
teamId,
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16741
|
+
chatKeyBytes,
|
|
16742
|
+
followUpSuggestions: resp.followUpSuggestions,
|
|
16743
|
+
newChatSuggestions: resp.newChatSuggestions,
|
|
16744
|
+
chatSummary: resp.chatSummary,
|
|
16745
|
+
chatTags: resp.chatTags,
|
|
16746
|
+
updatedChatTitle: resp.updatedChatTitle,
|
|
16747
|
+
sourceTitleVersion: resp.sourceTitleVersion,
|
|
16748
|
+
sourceMetadataVersion: resp.sourceMetadataVersion,
|
|
16749
|
+
encryptedChatKey
|
|
16687
16750
|
});
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
|
|
16697
|
-
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16701
|
-
...messageHistoryForRequest ?? [],
|
|
16702
|
-
{ role: "user", content: finalMessage },
|
|
16703
|
-
{ role: "assistant", content: assistant }
|
|
16704
|
-
]);
|
|
16705
|
-
const recommendations = await assessCliProjectAuthoring(this, projectAuthoringAvailable, history);
|
|
16706
|
-
for (const event of recommendations) chatContextEvents.persist(event);
|
|
16707
|
-
} catch {
|
|
16751
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
16752
|
+
if (taskUpdateJobsEnabled) {
|
|
16753
|
+
const persistedTaskJobIds = await this.persistPendingTaskUpdateJobs({
|
|
16754
|
+
ws,
|
|
16755
|
+
jobs: pendingTaskUpdateJobs,
|
|
16756
|
+
events: taskEvents,
|
|
16757
|
+
teamId,
|
|
16758
|
+
activeChatId: chatId,
|
|
16759
|
+
activeChatKeyBytes: chatKeyBytes,
|
|
16760
|
+
fallbackUserMessageId: messageId,
|
|
16761
|
+
requireActiveTurnEvent: true
|
|
16762
|
+
});
|
|
16763
|
+
pendingTaskUpdateJobs = pendingTaskUpdateJobs.filter((job) => !persistedTaskJobIds.has(job.job_id));
|
|
16708
16764
|
}
|
|
16765
|
+
await persistTaskEventSystemMessages(taskEvents);
|
|
16766
|
+
await recoverCurrentTurnOutputs();
|
|
16767
|
+
clearSyncCache(teamId);
|
|
16709
16768
|
}
|
|
16710
|
-
await chatContextEvents?.flush();
|
|
16711
16769
|
} finally {
|
|
16712
|
-
|
|
16713
|
-
|
|
16714
|
-
|
|
16770
|
+
try {
|
|
16771
|
+
await projectFocusCountdown?.flush();
|
|
16772
|
+
await focusPersistence;
|
|
16773
|
+
if (projectAuthoringAvailable && assistant && chatContextEvents) {
|
|
16774
|
+
try {
|
|
16775
|
+
const history = boundedAuthoringHistory([
|
|
16776
|
+
...messageHistoryForRequest ?? [],
|
|
16777
|
+
{ role: "user", content: finalMessage },
|
|
16778
|
+
{ role: "assistant", content: assistant }
|
|
16779
|
+
]);
|
|
16780
|
+
const recommendations = await assessCliProjectAuthoring(this, projectAuthoringAvailable, history);
|
|
16781
|
+
for (const event of recommendations) chatContextEvents.persist(event);
|
|
16782
|
+
} catch {
|
|
16783
|
+
}
|
|
16784
|
+
}
|
|
16785
|
+
await chatContextEvents?.flush();
|
|
16786
|
+
} finally {
|
|
16787
|
+
projectFocusCountdown?.();
|
|
16788
|
+
chatContextEvents?.stop();
|
|
16789
|
+
ws.close();
|
|
16790
|
+
}
|
|
16715
16791
|
}
|
|
16792
|
+
if (focusPersistenceError) throw focusPersistenceError;
|
|
16716
16793
|
}
|
|
16717
|
-
|
|
16794
|
+
const mateName = category ? MATE_NAMES[category] ?? null : null;
|
|
16795
|
+
return {
|
|
16796
|
+
status: "completed",
|
|
16797
|
+
chatId,
|
|
16798
|
+
messageId: assistantMessageId,
|
|
16799
|
+
userMessageId: messageId,
|
|
16800
|
+
assistant,
|
|
16801
|
+
category,
|
|
16802
|
+
modelName,
|
|
16803
|
+
mateName,
|
|
16804
|
+
followUpSuggestions,
|
|
16805
|
+
taskProposals,
|
|
16806
|
+
taskUpdateProposals,
|
|
16807
|
+
taskEvents,
|
|
16808
|
+
pendingTaskUpdateJobs,
|
|
16809
|
+
subChatEvents,
|
|
16810
|
+
appSettingsMemoryRequests,
|
|
16811
|
+
tokenUsage,
|
|
16812
|
+
promptBudget
|
|
16813
|
+
};
|
|
16814
|
+
} finally {
|
|
16815
|
+
earlyContextAbort.abort();
|
|
16816
|
+
ws.close();
|
|
16817
|
+
if (earlyContext) await earlyContext;
|
|
16718
16818
|
}
|
|
16719
|
-
const mateName = category ? MATE_NAMES[category] ?? null : null;
|
|
16720
|
-
return {
|
|
16721
|
-
status: "completed",
|
|
16722
|
-
chatId,
|
|
16723
|
-
messageId: assistantMessageId,
|
|
16724
|
-
userMessageId: messageId,
|
|
16725
|
-
assistant,
|
|
16726
|
-
category,
|
|
16727
|
-
modelName,
|
|
16728
|
-
mateName,
|
|
16729
|
-
followUpSuggestions,
|
|
16730
|
-
taskProposals,
|
|
16731
|
-
taskUpdateProposals,
|
|
16732
|
-
taskEvents,
|
|
16733
|
-
pendingTaskUpdateJobs,
|
|
16734
|
-
subChatEvents,
|
|
16735
|
-
appSettingsMemoryRequests,
|
|
16736
|
-
tokenUsage,
|
|
16737
|
-
promptBudget
|
|
16738
|
-
};
|
|
16739
16819
|
}
|
|
16740
16820
|
async persistEncryptedSystemMessage(ws, systemMessage, targetChatId) {
|
|
16741
16821
|
await ws.sendAsync("chat_system_message_added", {
|
|
@@ -104687,6 +104767,9 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
|
|
|
104687
104767
|
let lastFrameOwner = null;
|
|
104688
104768
|
let closed = false;
|
|
104689
104769
|
let stopActivity;
|
|
104770
|
+
let workspaceReady = false;
|
|
104771
|
+
let activityOwner = null;
|
|
104772
|
+
let activityEpoch = 0;
|
|
104690
104773
|
let activityTimer;
|
|
104691
104774
|
let refreshingActivity = false;
|
|
104692
104775
|
let identityScope = "";
|
|
@@ -104745,6 +104828,35 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
|
|
|
104745
104828
|
hydrateExamples(state);
|
|
104746
104829
|
}
|
|
104747
104830
|
};
|
|
104831
|
+
const syncActivityObserver = () => {
|
|
104832
|
+
if (!workspaceReady || closed) return;
|
|
104833
|
+
const session = state.signedIn && client2.hasSession() && typeof client2.getSession === "function" ? client2.getSession() : null;
|
|
104834
|
+
const owner2 = session ? JSON.stringify([
|
|
104835
|
+
session.apiUrl,
|
|
104836
|
+
session.hashedEmail,
|
|
104837
|
+
session.sessionId,
|
|
104838
|
+
session.createdAt,
|
|
104839
|
+
session.masterKeyExportedB64
|
|
104840
|
+
]) : null;
|
|
104841
|
+
if (owner2 === activityOwner) return;
|
|
104842
|
+
activityOwner = owner2;
|
|
104843
|
+
const epoch = ++activityEpoch;
|
|
104844
|
+
stopActivity?.();
|
|
104845
|
+
stopActivity = void 0;
|
|
104846
|
+
if (!owner2 || typeof client2.observeChatActivity !== "function") return;
|
|
104847
|
+
void client2.observeChatActivity(() => {
|
|
104848
|
+
if (closed || activityEpoch !== epoch || activityOwner !== owner2) return;
|
|
104849
|
+
teamChatRefresh?.request();
|
|
104850
|
+
clearTimeout(activityTimer);
|
|
104851
|
+
activityTimer = setTimeout(() => {
|
|
104852
|
+
void refreshActivity();
|
|
104853
|
+
}, 300);
|
|
104854
|
+
}).then((stop) => {
|
|
104855
|
+
if (closed || activityEpoch !== epoch || activityOwner !== owner2) stop();
|
|
104856
|
+
else stopActivity = stop;
|
|
104857
|
+
}).catch(() => {
|
|
104858
|
+
});
|
|
104859
|
+
};
|
|
104748
104860
|
const render = () => {
|
|
104749
104861
|
if (closed) return;
|
|
104750
104862
|
syncSessionView();
|
|
@@ -104784,6 +104896,8 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
|
|
|
104784
104896
|
}
|
|
104785
104897
|
workspaceScope = nextScope;
|
|
104786
104898
|
state.activeTeamId = teamId;
|
|
104899
|
+
syncActivityObserver();
|
|
104900
|
+
if (workspaceReady && state.signedIn && typeof client2.warmChatMetadata === "function") client2.warmChatMetadata();
|
|
104787
104901
|
if (teamId && state.signedIn && nextScope) {
|
|
104788
104902
|
if (nextScope !== identityScope) {
|
|
104789
104903
|
identityScope = nextScope;
|
|
@@ -104825,6 +104939,7 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
|
|
|
104825
104939
|
const stopHomeSync = startHomeSync(state, client2, render, () => closed);
|
|
104826
104940
|
const finish = (result2) => {
|
|
104827
104941
|
closed = true;
|
|
104942
|
+
activityEpoch++;
|
|
104828
104943
|
state.homeAbortController?.abort();
|
|
104829
104944
|
Object.values(state.chatContextAuthoringControls).forEach((control) => control.stop());
|
|
104830
104945
|
stopHomeSync();
|
|
@@ -104852,22 +104967,11 @@ async function runTui(client2, terminal = new TuiTerminal(), startupServices) {
|
|
|
104852
104967
|
services: startupServices,
|
|
104853
104968
|
updated: () => finish({ action: "restart" }),
|
|
104854
104969
|
ready: () => {
|
|
104970
|
+
workspaceReady = true;
|
|
104971
|
+
syncActivityObserver();
|
|
104855
104972
|
void loadHomeData(state, client2, render);
|
|
104856
104973
|
void refreshActivity();
|
|
104857
104974
|
warmTuiWorkspaceLists(client2);
|
|
104858
|
-
if (state.signedIn && typeof client2.observeChatActivity === "function") {
|
|
104859
|
-
void client2.observeChatActivity(() => {
|
|
104860
|
-
teamChatRefresh?.request();
|
|
104861
|
-
clearTimeout(activityTimer);
|
|
104862
|
-
activityTimer = setTimeout(() => {
|
|
104863
|
-
void refreshActivity();
|
|
104864
|
-
}, 300);
|
|
104865
|
-
}).then((stop) => {
|
|
104866
|
-
if (closed) stop();
|
|
104867
|
-
else stopActivity = stop;
|
|
104868
|
-
}).catch(() => {
|
|
104869
|
-
});
|
|
104870
|
-
}
|
|
104871
104975
|
}
|
|
104872
104976
|
});
|
|
104873
104977
|
terminal.enter();
|