openmates 0.27.0-alpha.80 → 0.27.0-alpha.82
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-VTD4VHNF.js → chunk-4TPIRQDS.js} +2081 -1814
- package/dist/{cli-CDXMwvV4.d.ts → cli-YqU8OOuk.d.ts} +6 -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 +1 -1
|
@@ -778,6 +778,45 @@ function assertCodexClaimCaller(threadId, currentThread = process.env.CODEX_THRE
|
|
|
778
778
|
}
|
|
779
779
|
}
|
|
780
780
|
|
|
781
|
+
// src/recoveryBatch.ts
|
|
782
|
+
async function runRecoveryBatch(items, work, dependsOn = () => false) {
|
|
783
|
+
const results = new Array(items.length);
|
|
784
|
+
const pending = new Set(items.map((_, index) => index));
|
|
785
|
+
const complete = /* @__PURE__ */ new Set();
|
|
786
|
+
const active = /* @__PURE__ */ new Map();
|
|
787
|
+
let failed = false;
|
|
788
|
+
let failure2;
|
|
789
|
+
while (pending.size || active.size) {
|
|
790
|
+
if (!failed) for (const index of pending) {
|
|
791
|
+
if (active.size >= 4) break;
|
|
792
|
+
if (items.some((dependency, other) => other !== index && !complete.has(other) && dependsOn(items[index], dependency, index, other))) continue;
|
|
793
|
+
pending.delete(index);
|
|
794
|
+
const task = Promise.resolve().then(() => work(items[index], index)).then(
|
|
795
|
+
(result) => {
|
|
796
|
+
results[index] = result;
|
|
797
|
+
complete.add(index);
|
|
798
|
+
},
|
|
799
|
+
(error) => {
|
|
800
|
+
if (!failed) {
|
|
801
|
+
failed = true;
|
|
802
|
+
failure2 = error;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
).finally(() => {
|
|
806
|
+
active.delete(index);
|
|
807
|
+
});
|
|
808
|
+
active.set(index, task);
|
|
809
|
+
}
|
|
810
|
+
if (!active.size) {
|
|
811
|
+
if (failed) throw failure2;
|
|
812
|
+
throw new Error("Recovery embed dependency is cyclic or unresolved; outputs remain pending.");
|
|
813
|
+
}
|
|
814
|
+
await Promise.race(active.values());
|
|
815
|
+
if (failed && !active.size) throw failure2;
|
|
816
|
+
}
|
|
817
|
+
return results;
|
|
818
|
+
}
|
|
819
|
+
|
|
781
820
|
// src/preparedPrivacy.ts
|
|
782
821
|
var PREPARED_PRIVACY_IDLE_MS = 2 * 60 * 6e4;
|
|
783
822
|
var PreparedPrivacyCache = class {
|
|
@@ -6217,6 +6256,68 @@ function parseFocusAuthoringDocument(document) {
|
|
|
6217
6256
|
throw new Error("invalid_focus_document");
|
|
6218
6257
|
}
|
|
6219
6258
|
}
|
|
6259
|
+
async function collectSelectedCliProjectFocusRoutingCatalog(client2, projectId, options) {
|
|
6260
|
+
const detail = await client2.getProject(projectId, options);
|
|
6261
|
+
if (detail.project.project_id !== projectId || detail.project.archived) throw new Error("Project catalog unavailable.");
|
|
6262
|
+
const key = await client2.decryptProjectKey(detail.project, options);
|
|
6263
|
+
const focuses = [];
|
|
6264
|
+
for (const item of detail.items) {
|
|
6265
|
+
if (!(["embed", "upload"].includes(item.item_type) && item.target_id_hash && !item.deleted_target_state && typeof item.encrypted_metadata === "string")) continue;
|
|
6266
|
+
const decrypted = await decryptWithAesGcmCombined(item.encrypted_metadata, key);
|
|
6267
|
+
if (!decrypted) throw new Error("Project catalog metadata unavailable.");
|
|
6268
|
+
const metadata = JSON.parse(decrypted);
|
|
6269
|
+
const path2 = metadata.display_path;
|
|
6270
|
+
if (typeof path2 !== "string" || !/^\.openmates\/focuses\/[a-zA-Z0-9_-]+\/SKILL\.md$/.test(path2) || typeof metadata.focus_title !== "string" || typeof metadata.focus_description !== "string" || typeof metadata.focus_when_to_use !== "string") continue;
|
|
6271
|
+
focuses.push({
|
|
6272
|
+
item_id: item.project_item_id,
|
|
6273
|
+
revision: createHash9("sha256").update(["updated_at", "encrypted_metadata", "encrypted_note", "target_id_hash", "deleted_target_state"].map((field3) => item[field3] ? String(item[field3]) : "").join("\0")).digest("hex"),
|
|
6274
|
+
title: metadata.focus_title.slice(0, 180),
|
|
6275
|
+
description: metadata.focus_description.slice(0, 640),
|
|
6276
|
+
when_to_use: metadata.focus_when_to_use.slice(0, 640)
|
|
6277
|
+
});
|
|
6278
|
+
if (focuses.length >= 20) break;
|
|
6279
|
+
}
|
|
6280
|
+
return focuses;
|
|
6281
|
+
}
|
|
6282
|
+
function registerCliProjectFocusCatalogResponder(input) {
|
|
6283
|
+
let active = true;
|
|
6284
|
+
const answered = /* @__PURE__ */ new Set();
|
|
6285
|
+
const stopEvent = input.ws.onMessageType("project_focus_catalog_requested", (event) => {
|
|
6286
|
+
if (!active || !input.ws.isOpen() || !input.isCurrent() || event.chat_id !== input.chatId || typeof event.request_id !== "string" || !event.request_id || event.request_id.length > 128 || typeof event.project_id !== "string" || !event.project_id || event.project_id.length > 128 || event.team_id !== input.teamId || !input.isOfferedProject(event.project_id) || answered.has(event.request_id)) return;
|
|
6287
|
+
answered.add(event.request_id);
|
|
6288
|
+
void (async () => {
|
|
6289
|
+
let focuses;
|
|
6290
|
+
try {
|
|
6291
|
+
focuses = await collectSelectedCliProjectFocusRoutingCatalog(
|
|
6292
|
+
input.client,
|
|
6293
|
+
event.project_id,
|
|
6294
|
+
{ teamId: input.teamId, personal: !input.teamId }
|
|
6295
|
+
);
|
|
6296
|
+
} catch {
|
|
6297
|
+
focuses = null;
|
|
6298
|
+
}
|
|
6299
|
+
if (!active || !input.ws.isOpen() || !input.isCurrent()) return;
|
|
6300
|
+
await input.ws.sendAsync("project_focus_catalog_result", {
|
|
6301
|
+
chat_id: input.chatId,
|
|
6302
|
+
request_id: event.request_id,
|
|
6303
|
+
project_id: event.project_id,
|
|
6304
|
+
focuses
|
|
6305
|
+
});
|
|
6306
|
+
})().catch((error) => {
|
|
6307
|
+
if (!active || !input.ws.isOpen() || !input.isCurrent()) return;
|
|
6308
|
+
input.onDeliveryFailure(error instanceof Error ? error : new Error("Project catalog delivery failed."));
|
|
6309
|
+
});
|
|
6310
|
+
});
|
|
6311
|
+
const stopClose = input.ws.onClose(() => {
|
|
6312
|
+
active = false;
|
|
6313
|
+
stopEvent();
|
|
6314
|
+
});
|
|
6315
|
+
return () => {
|
|
6316
|
+
active = false;
|
|
6317
|
+
stopEvent();
|
|
6318
|
+
stopClose();
|
|
6319
|
+
};
|
|
6320
|
+
}
|
|
6220
6321
|
async function discoverCliProjectCandidates(client2, options, requireComplete = false) {
|
|
6221
6322
|
const records = (await client2.listProjects(options)).filter((record10) => !record10.archived).slice(0, 40);
|
|
6222
6323
|
const entries = await Promise.all(records.map(async (record10) => {
|
|
@@ -13605,6 +13706,39 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13605
13706
|
chatActivitySocket = null;
|
|
13606
13707
|
preparedSendSocket = null;
|
|
13607
13708
|
preparedSendSocketAttemptOwner = null;
|
|
13709
|
+
foregroundSendCount = 0;
|
|
13710
|
+
pendingSendSocketRearm = null;
|
|
13711
|
+
cancelSendSocketRearm() {
|
|
13712
|
+
const pending = this.pendingSendSocketRearm;
|
|
13713
|
+
this.pendingSendSocketRearm = null;
|
|
13714
|
+
pending?.removeClose();
|
|
13715
|
+
}
|
|
13716
|
+
maybeRearmSendSocket() {
|
|
13717
|
+
const pending = this.pendingSendSocketRearm;
|
|
13718
|
+
if (!pending || this.foregroundSendCount > 0 || pending.ws.isOpen()) return;
|
|
13719
|
+
this.cancelSendSocketRearm();
|
|
13720
|
+
if (!this.interactiveViewerAllowed || !this.preparedChatOwnerCurrent(pending.owner, pending.teamId) || pending.ownerKey !== this.sendSocketOwnerKey(this.requireSession(), pending.teamId) || authCookieFingerprint(this.http.getCookieMap()) !== authCookieFingerprint(this.requireSession().cookies)) return;
|
|
13721
|
+
this.preparedSendSocketAttemptOwner = null;
|
|
13722
|
+
this.warmSendSocket();
|
|
13723
|
+
}
|
|
13724
|
+
rearmSendSocketWhenClosed(ws, teamId) {
|
|
13725
|
+
if (!this.interactiveViewerAllowed || !this.hasSession()) return;
|
|
13726
|
+
this.cancelSendSocketRearm();
|
|
13727
|
+
const session = this.requireSession();
|
|
13728
|
+
const pending = {
|
|
13729
|
+
ws,
|
|
13730
|
+
teamId,
|
|
13731
|
+
owner: this.preparedChatOwner(session, teamId),
|
|
13732
|
+
ownerKey: this.sendSocketOwnerKey(session, teamId),
|
|
13733
|
+
removeClose: () => {
|
|
13734
|
+
}
|
|
13735
|
+
};
|
|
13736
|
+
this.pendingSendSocketRearm = pending;
|
|
13737
|
+
pending.removeClose = ws.onClose(() => {
|
|
13738
|
+
this.maybeRearmSendSocket();
|
|
13739
|
+
});
|
|
13740
|
+
this.maybeRearmSendSocket();
|
|
13741
|
+
}
|
|
13608
13742
|
sendSocketOwnerKey(session, teamId) {
|
|
13609
13743
|
return JSON.stringify([
|
|
13610
13744
|
this.preparedChatOwner(session, teamId),
|
|
@@ -13631,7 +13765,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13631
13765
|
}
|
|
13632
13766
|
/** Prepare one authenticated send socket for the interactive TUI's next turn. */
|
|
13633
13767
|
warmSendSocket() {
|
|
13634
|
-
if (!this.interactiveViewerAllowed || this.explicitSession || !this.hasSession()) return;
|
|
13768
|
+
if (!this.interactiveViewerAllowed || this.foregroundSendCount > 0 || this.explicitSession || !this.hasSession()) return;
|
|
13635
13769
|
const session = this.requireSession();
|
|
13636
13770
|
const teamId = this.getActiveTeamId();
|
|
13637
13771
|
const owner2 = this.preparedChatOwner(session, teamId);
|
|
@@ -15562,14 +15696,32 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15562
15696
|
}
|
|
15563
15697
|
return { bySubject, acknowledged: /* @__PURE__ */ new Map(), visiting: /* @__PURE__ */ new Set() };
|
|
15564
15698
|
}
|
|
15699
|
+
captureRecoveryOwner() {
|
|
15700
|
+
const account = this.captureTeamAccountOwner();
|
|
15701
|
+
const team = this.getActiveTeamId();
|
|
15702
|
+
const master = Buffer.from(this.getMasterKeyBytes());
|
|
15703
|
+
return () => {
|
|
15704
|
+
if (!this.hasSession() || !account.isCurrent() || this.getActiveTeamId() !== team || !master.equals(Buffer.from(this.getMasterKeyBytes()))) {
|
|
15705
|
+
throw new Error("Recovery account, Team or key changed; outputs remain pending.");
|
|
15706
|
+
}
|
|
15707
|
+
};
|
|
15708
|
+
}
|
|
15565
15709
|
async persistAvailableRecoveryOutputs(ws, ownerId2, cache, outputs, teamId, dependencies = this.recoveryReplayDependencies([outputs])) {
|
|
15566
15710
|
if (outputs.length > 100) throw new Error("Recovery output discovery exceeded the bounded batch size.");
|
|
15567
15711
|
const order = { message: 0, embed: 1, diff: 2, summary: 3, checkpoint: 4 };
|
|
15568
15712
|
const ordered = [...outputs].sort((a, b) => order[a.output_kind] - order[b.output_kind] || (a.output_kind === "message" && b.output_kind === "message" ? (a.message_role === "user" ? 0 : 1) - (b.message_role === "user" ? 0 : 1) : 0) || a.output_version - b.output_version);
|
|
15713
|
+
const assertOwner = this.captureRecoveryOwner();
|
|
15569
15714
|
const masterKey = this.getMasterKeyBytes();
|
|
15570
15715
|
const wrappingKey = await this.getChatWrappingKey(teamId, masterKey);
|
|
15716
|
+
const http = { get: async (path2, headers) => {
|
|
15717
|
+
assertOwner();
|
|
15718
|
+
const result = await this.http.get(path2, headers);
|
|
15719
|
+
assertOwner();
|
|
15720
|
+
return result;
|
|
15721
|
+
} };
|
|
15571
15722
|
let acknowledged = 0;
|
|
15572
15723
|
const request = async (type, responseType, recordId, payload) => {
|
|
15724
|
+
assertOwner();
|
|
15573
15725
|
const requestId = randomUUID9();
|
|
15574
15726
|
const response = ws.waitForMessage(responseType, (value) => {
|
|
15575
15727
|
const frame = value;
|
|
@@ -15577,10 +15729,14 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15577
15729
|
}, 2e4);
|
|
15578
15730
|
void response.catch(() => {
|
|
15579
15731
|
});
|
|
15732
|
+
assertOwner();
|
|
15580
15733
|
await ws.sendAsync(type, { protocol_version: 1, record_id: recordId, request_id: requestId, ...payload });
|
|
15581
|
-
|
|
15734
|
+
const received = await response;
|
|
15735
|
+
assertOwner();
|
|
15736
|
+
return received.payload;
|
|
15582
15737
|
};
|
|
15583
15738
|
const storeWithReceipt = async (type, responseType, embedId, payload, recoveryRecordId, version2) => {
|
|
15739
|
+
assertOwner();
|
|
15584
15740
|
const requestId = randomUUID9();
|
|
15585
15741
|
const receipt = ws.waitForMessage(
|
|
15586
15742
|
responseType,
|
|
@@ -15589,8 +15745,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15589
15745
|
);
|
|
15590
15746
|
void receipt.catch(() => {
|
|
15591
15747
|
});
|
|
15748
|
+
assertOwner();
|
|
15592
15749
|
await ws.sendAsync(type, { ...payload, request_id: requestId, recovery_record_id: recoveryRecordId });
|
|
15593
|
-
|
|
15750
|
+
const received = await receipt;
|
|
15751
|
+
assertOwner();
|
|
15752
|
+
return received.payload;
|
|
15594
15753
|
};
|
|
15595
15754
|
const reconcileAcknowledgedEmbed = async (output, chatKey, sealedContent) => {
|
|
15596
15755
|
if (output.output_kind !== "embed" && output.output_kind !== "diff") {
|
|
@@ -15613,7 +15772,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15613
15772
|
throw new Error(`Recovery output ${output.record_id} lacks verified canonical acknowledgement.`);
|
|
15614
15773
|
}
|
|
15615
15774
|
const embedPath = `/v1/embeds/chats/${encodeURIComponent(output.target_chat_id)}/embeds/${encodeURIComponent(output.subject_id)}` + (teamId ? `?team_id=${encodeURIComponent(teamId)}` : "");
|
|
15616
|
-
const canonical = await
|
|
15775
|
+
const canonical = await http.get(embedPath, this.getCliRequestHeaders());
|
|
15617
15776
|
const row3 = canonical.data?.embed;
|
|
15618
15777
|
if (!canonical.ok || !row3 || row3.hashed_chat_id !== computeSHA256(output.target_chat_id) || typeof row3.encrypted_content !== "string" || typeof row3.encrypted_type !== "string") {
|
|
15619
15778
|
throw new Error(`Recovery output ${output.record_id} canonical embed is unavailable.`);
|
|
@@ -15638,7 +15797,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15638
15797
|
} else {
|
|
15639
15798
|
const query = new URLSearchParams({ capability: "bounded-v1", chat_id: output.target_chat_id });
|
|
15640
15799
|
if (teamId) query.set("team_id", teamId);
|
|
15641
|
-
const history = await
|
|
15800
|
+
const history = await http.get(
|
|
15642
15801
|
`/v1/embeds/${encodeURIComponent(output.subject_id)}/versions/${output.output_version}?${query}`,
|
|
15643
15802
|
this.getCliRequestHeaders()
|
|
15644
15803
|
);
|
|
@@ -15652,12 +15811,14 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15652
15811
|
if (typeof encryptedPatch === "string") await decryptWithAesGcmCombined(encryptedPatch, embedKey);
|
|
15653
15812
|
}
|
|
15654
15813
|
};
|
|
15655
|
-
|
|
15814
|
+
const rootKeys = /* @__PURE__ */ new Map();
|
|
15815
|
+
const prepareOne = async (output) => {
|
|
15816
|
+
assertOwner();
|
|
15656
15817
|
const identity3 = recoveryOutputIdentity(output);
|
|
15657
15818
|
const committed = dependencies.acknowledged.get(output.record_id);
|
|
15658
15819
|
if (committed !== void 0) {
|
|
15659
15820
|
if (committed !== identity3) throw new Error("Acknowledged recovery dependency changed identity.");
|
|
15660
|
-
|
|
15821
|
+
return null;
|
|
15661
15822
|
}
|
|
15662
15823
|
if (dependencies.visiting.has(output.record_id) || dependencies.visiting.size >= 32) {
|
|
15663
15824
|
throw new Error("Recovery embed dependency is cyclic or exceeds the bounded depth.");
|
|
@@ -15665,26 +15826,35 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15665
15826
|
if (output.root_hashed_team_id !== void 0 && output.root_hashed_team_id !== (teamId ? computeSHA256(teamId) : null)) {
|
|
15666
15827
|
throw new Error(`Recovery output ${output.record_id} Team key context is unavailable; output remains pending.`);
|
|
15667
15828
|
}
|
|
15668
|
-
let
|
|
15669
|
-
|
|
15670
|
-
|
|
15671
|
-
|
|
15672
|
-
|
|
15673
|
-
|
|
15674
|
-
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15829
|
+
let rootKeyPromise = rootKeys.get(output.root_chat_id);
|
|
15830
|
+
if (!rootKeyPromise) {
|
|
15831
|
+
rootKeyPromise = (async () => {
|
|
15832
|
+
let root = cache.chats.find((chat) => String(chat.details.id ?? "") === output.root_chat_id);
|
|
15833
|
+
let rootKey2 = root ? await this.resolveChatKey(cache, root, wrappingKey, teamId) : null;
|
|
15834
|
+
if (!rootKey2) {
|
|
15835
|
+
const response = await http.get(
|
|
15836
|
+
`/v1/chats/${encodeURIComponent(output.root_chat_id)}/wrappers/window` + (teamId ? `?team_id=${encodeURIComponent(teamId)}` : ""),
|
|
15837
|
+
this.getCliRequestHeaders()
|
|
15838
|
+
);
|
|
15839
|
+
if (!response.ok) throw new Error(`Recovery root key lookup failed (HTTP ${response.status}); output remains pending.`);
|
|
15840
|
+
const keyType = teamId ? "team" : "master";
|
|
15841
|
+
const wrapper = (response.data.wrappers ?? []).find((item) => item.key_type === keyType && typeof item.encrypted_chat_key === "string" && item.encrypted_chat_key);
|
|
15842
|
+
if (!wrapper) throw new Error(`Recovery root chat ${output.root_chat_id} key wrapper is unavailable.`);
|
|
15843
|
+
cache.chatKeyWrappers ??= [];
|
|
15844
|
+
cache.chatKeyWrappers.push(wrapper);
|
|
15845
|
+
if (!root) {
|
|
15846
|
+
root = { details: { id: output.root_chat_id, encrypted_chat_key: wrapper.encrypted_chat_key }, messages: [] };
|
|
15847
|
+
cache.chats.push(root);
|
|
15848
|
+
}
|
|
15849
|
+
rootKey2 = await this.resolveChatKey(cache, root, wrappingKey, teamId);
|
|
15850
|
+
}
|
|
15851
|
+
if (!rootKey2) throw new Error(`Recovery root chat ${output.root_chat_id} key is unavailable.`);
|
|
15852
|
+
return rootKey2;
|
|
15853
|
+
})();
|
|
15854
|
+
rootKeys.set(output.root_chat_id, rootKeyPromise);
|
|
15855
|
+
}
|
|
15856
|
+
const rootKey = await rootKeyPromise;
|
|
15857
|
+
assertOwner();
|
|
15688
15858
|
let ready2;
|
|
15689
15859
|
try {
|
|
15690
15860
|
ready2 = await request("recovery_output_get", "recovery_output_ready", output.record_id, {});
|
|
@@ -15693,7 +15863,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15693
15863
|
await reconcileAcknowledgedEmbed(output, rootKey);
|
|
15694
15864
|
dependencies.acknowledged.set(output.record_id, identity3);
|
|
15695
15865
|
acknowledged += 1;
|
|
15696
|
-
|
|
15866
|
+
return null;
|
|
15697
15867
|
}
|
|
15698
15868
|
for (const field3 of ["record_id", "root_chat_id", "target_chat_id", "turn_id", "subject_id", "output_kind", "output_version", "chat_key_version"]) {
|
|
15699
15869
|
if (ready2[field3] !== output[field3]) throw new Error(`Recovery output ${output.record_id} identity mismatch: ${field3}.`);
|
|
@@ -15729,6 +15899,27 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15729
15899
|
throw new Error("Recovery output plaintext identity mismatch.");
|
|
15730
15900
|
}
|
|
15731
15901
|
const content = recovered.content;
|
|
15902
|
+
assertOwner();
|
|
15903
|
+
if (typeof content.parent_embed_id === "string") {
|
|
15904
|
+
const candidates = (dependencies.bySubject.get(content.parent_embed_id) ?? []).filter((candidate) => candidate.output_kind === "embed");
|
|
15905
|
+
if (candidates.length && !candidates.some((candidate) => candidate.output_kind === "embed" && candidate.root_chat_id === output.root_chat_id && candidate.target_chat_id === output.target_chat_id && candidate.turn_id === output.turn_id && candidate.chat_key_version === output.chat_key_version && (candidate.root_hashed_team_id ?? null) === (output.root_hashed_team_id ?? null))) {
|
|
15906
|
+
const parentPath = `/v1/embeds/chats/${encodeURIComponent(output.target_chat_id)}/embeds/${encodeURIComponent(content.parent_embed_id)}` + (teamId ? `?team_id=${encodeURIComponent(teamId)}` : "");
|
|
15907
|
+
const parent = await http.get(parentPath, this.getCliRequestHeaders());
|
|
15908
|
+
if (!parent.ok) throw new Error("Canonical recovery parent embed key scope was unavailable.");
|
|
15909
|
+
}
|
|
15910
|
+
}
|
|
15911
|
+
return { output, ready: ready2, content, rootKey, identity: identity3 };
|
|
15912
|
+
};
|
|
15913
|
+
const prepared = [];
|
|
15914
|
+
for (const output of ordered) prepared.push(await prepareOne(output));
|
|
15915
|
+
const persistOne = async ({ output, ready: ready2, content, rootKey, identity: identity3 }) => {
|
|
15916
|
+
assertOwner();
|
|
15917
|
+
const committed = dependencies.acknowledged.get(output.record_id);
|
|
15918
|
+
if (committed !== void 0) {
|
|
15919
|
+
if (committed !== identity3) throw new Error("Acknowledged recovery dependency changed identity.");
|
|
15920
|
+
return;
|
|
15921
|
+
}
|
|
15922
|
+
const messageRole = output.message_role ?? "assistant";
|
|
15732
15923
|
const childKey = rootKey;
|
|
15733
15924
|
const now = Math.floor(Date.now() / 1e3);
|
|
15734
15925
|
const encryptedChatKey = typeof ready2.encrypted_chat_key === "string" && ready2.encrypted_chat_key ? ready2.encrypted_chat_key : await encryptBytesWithAesGcm(childKey, wrappingKey);
|
|
@@ -15803,6 +15994,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15803
15994
|
}, 2e4);
|
|
15804
15995
|
void stored.catch(() => {
|
|
15805
15996
|
});
|
|
15997
|
+
assertOwner();
|
|
15806
15998
|
await ws.sendAsync("store_chat_compression_checkpoint", {
|
|
15807
15999
|
chat_id: output.target_chat_id,
|
|
15808
16000
|
checkpoint_id: output.subject_id,
|
|
@@ -15830,7 +16022,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15830
16022
|
} else {
|
|
15831
16023
|
if (content.embed_id !== output.subject_id) throw new Error("Recovery embed identity mismatch.");
|
|
15832
16024
|
const embedPath = `/v1/embeds/chats/${encodeURIComponent(output.target_chat_id)}/embeds/${encodeURIComponent(output.subject_id)}` + (teamId ? `?team_id=${encodeURIComponent(teamId)}` : "");
|
|
15833
|
-
let canonicalBefore = await
|
|
16025
|
+
let canonicalBefore = await http.get(
|
|
15834
16026
|
embedPath,
|
|
15835
16027
|
this.getCliRequestHeaders()
|
|
15836
16028
|
);
|
|
@@ -15929,7 +16121,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15929
16121
|
}
|
|
15930
16122
|
const query = new URLSearchParams({ capability: "bounded-v1", chat_id: output.target_chat_id });
|
|
15931
16123
|
if (teamId) query.set("team_id", teamId);
|
|
15932
|
-
const existing = await
|
|
16124
|
+
const existing = await http.get(
|
|
15933
16125
|
`/v1/embeds/${encodeURIComponent(output.subject_id)}/versions/${output.output_version}?${query}`,
|
|
15934
16126
|
this.getCliRequestHeaders()
|
|
15935
16127
|
);
|
|
@@ -15985,7 +16177,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15985
16177
|
} else {
|
|
15986
16178
|
const query = new URLSearchParams({ capability: "bounded-v1", chat_id: output.target_chat_id });
|
|
15987
16179
|
if (teamId) query.set("team_id", teamId);
|
|
15988
|
-
const history = await
|
|
16180
|
+
const history = await http.get(
|
|
15989
16181
|
`/v1/embeds/${encodeURIComponent(output.subject_id)}/versions/${output.output_version}?${query}`,
|
|
15990
16182
|
this.getCliRequestHeaders()
|
|
15991
16183
|
);
|
|
@@ -16106,7 +16298,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16106
16298
|
}
|
|
16107
16299
|
const keyPath = `/v1/embeds/chats/${encodeURIComponent(output.target_chat_id)}/embeds/${encodeURIComponent(keySubject)}` + (teamId ? `?team_id=${encodeURIComponent(teamId)}` : "");
|
|
16108
16300
|
const bootstrapRootKeys = output.output_kind === "embed" && !parentId && !canonicalBefore.ok && canonicalBefore.status === 404;
|
|
16109
|
-
const keyPage = bootstrapRootKeys ? null : keySubject === output.subject_id && canonicalBefore.ok ? canonicalBefore : parentPage ?? await
|
|
16301
|
+
const keyPage = bootstrapRootKeys ? null : keySubject === output.subject_id && canonicalBefore.ok ? canonicalBefore : parentPage ?? await http.get(
|
|
16110
16302
|
keyPath,
|
|
16111
16303
|
this.getCliRequestHeaders()
|
|
16112
16304
|
);
|
|
@@ -16133,20 +16325,24 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16133
16325
|
}
|
|
16134
16326
|
].filter((key) => !existingTypes.has(key.key_type));
|
|
16135
16327
|
if (keys2.length > 0) {
|
|
16328
|
+
assertOwner();
|
|
16136
16329
|
const requestId = randomUUID9();
|
|
16137
16330
|
const receipt = ws.waitForMessage("store_embed_keys_confirmed", (value) => value.request_id === requestId, 3e4);
|
|
16138
16331
|
void receipt.catch(() => {
|
|
16139
16332
|
});
|
|
16333
|
+
assertOwner();
|
|
16140
16334
|
await ws.sendAsync("store_embed_keys", {
|
|
16141
16335
|
request_id: requestId,
|
|
16142
16336
|
recovery_record_id: output.record_id,
|
|
16143
16337
|
keys: keys2
|
|
16144
16338
|
});
|
|
16145
|
-
const
|
|
16339
|
+
const received = await receipt;
|
|
16340
|
+
assertOwner();
|
|
16341
|
+
const result = received.payload;
|
|
16146
16342
|
if (result.failed_count !== 0 || result.created_count !== keys2.length || result.requested_count !== keys2.length) {
|
|
16147
16343
|
throw new Error("Recovery embed key wrappers were not exactly confirmed.");
|
|
16148
16344
|
}
|
|
16149
|
-
const durable = await
|
|
16345
|
+
const durable = await http.get(
|
|
16150
16346
|
keyPath,
|
|
16151
16347
|
this.getCliRequestHeaders()
|
|
16152
16348
|
);
|
|
@@ -16156,7 +16352,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16156
16352
|
throw new Error("Recovery embed key wrappers are not both durable.");
|
|
16157
16353
|
}
|
|
16158
16354
|
}
|
|
16159
|
-
const canonicalAfter = await
|
|
16355
|
+
const canonicalAfter = await http.get(
|
|
16160
16356
|
embedPath,
|
|
16161
16357
|
this.getCliRequestHeaders()
|
|
16162
16358
|
);
|
|
@@ -16171,7 +16367,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16171
16367
|
} else {
|
|
16172
16368
|
const query = new URLSearchParams({ capability: "bounded-v1", chat_id: output.target_chat_id });
|
|
16173
16369
|
if (teamId) query.set("team_id", teamId);
|
|
16174
|
-
const history = await
|
|
16370
|
+
const history = await http.get(
|
|
16175
16371
|
`/v1/embeds/${encodeURIComponent(output.subject_id)}/versions/${output.output_version}?${query}`,
|
|
16176
16372
|
this.getCliRequestHeaders()
|
|
16177
16373
|
);
|
|
@@ -16195,7 +16391,14 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16195
16391
|
}
|
|
16196
16392
|
}
|
|
16197
16393
|
dependencies.acknowledged.set(output.record_id, identity3);
|
|
16394
|
+
assertOwner();
|
|
16198
16395
|
acknowledged += 1;
|
|
16396
|
+
};
|
|
16397
|
+
for (const kind of ["message", "embed", "diff", "summary", "checkpoint"]) {
|
|
16398
|
+
const group = prepared.filter((item) => item !== null && item.output.output_kind === kind);
|
|
16399
|
+
if (kind === "embed" || kind === "diff") {
|
|
16400
|
+
await runRecoveryBatch(group, persistOne, (item, dependency, index, other) => item.output.subject_id === dependency.output.subject_id && other < index || item.content.parent_embed_id === dependency.output.subject_id && item.output.root_chat_id === dependency.output.root_chat_id && item.output.target_chat_id === dependency.output.target_chat_id && item.output.turn_id === dependency.output.turn_id && item.output.chat_key_version === dependency.output.chat_key_version && (item.output.root_hashed_team_id ?? null) === (dependency.output.root_hashed_team_id ?? null) || typeof item.content.parent_embed_id === "string" && item.content.parent_embed_id === dependency.content.parent_embed_id && other < index && !group.some((candidate) => candidate.output.subject_id === item.content.parent_embed_id));
|
|
16401
|
+
} else for (const item of group) await persistOne(item);
|
|
16199
16402
|
}
|
|
16200
16403
|
return acknowledged;
|
|
16201
16404
|
}
|
|
@@ -16321,1651 +16524,1577 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16321
16524
|
}
|
|
16322
16525
|
}
|
|
16323
16526
|
async sendMessage(params) {
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
|
|
16345
|
-
|
|
16346
|
-
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16355
|
-
)
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
|
|
16360
|
-
|
|
16361
|
-
|
|
16362
|
-
|
|
16363
|
-
|
|
16364
|
-
|
|
16365
|
-
|
|
16366
|
-
|
|
16527
|
+
this.cancelSendSocketRearm();
|
|
16528
|
+
this.foregroundSendCount++;
|
|
16529
|
+
try {
|
|
16530
|
+
const preWsStartedAt = performance.now();
|
|
16531
|
+
const traceSendTiming = (phase) => {
|
|
16532
|
+
if (process.env.OPENMATES_TRACE_SEND_TIMING === "1") {
|
|
16533
|
+
console.error(`[chat send timing] phase=${phase} elapsed_ms=${Math.round(performance.now() - preWsStartedAt)}`);
|
|
16534
|
+
}
|
|
16535
|
+
};
|
|
16536
|
+
traceSendTiming("send_begin");
|
|
16537
|
+
if (this.preparedSendSocket && !this.preparedSendSocket.opened) this.closePreparedSendSocket();
|
|
16538
|
+
const teamId = this.resolveTeamContext({ teamId: params.teamId, personal: params.personal });
|
|
16539
|
+
const teamSenderName = teamId ? await this.ownTeamSenderName(teamId) : "User";
|
|
16540
|
+
if (params.chatId && params.newChatId) {
|
|
16541
|
+
throw new Error("chatId and newChatId cannot be used together.");
|
|
16542
|
+
}
|
|
16543
|
+
if (params.newChatId && !CANONICAL_UUID_PATTERN.test(params.newChatId)) {
|
|
16544
|
+
throw new Error("newChatId must be a canonical UUID.");
|
|
16545
|
+
}
|
|
16546
|
+
if (params.incognito && params.projectId) {
|
|
16547
|
+
throw new Error("Project file execution requires a saved chat.");
|
|
16548
|
+
}
|
|
16549
|
+
const ownsPreparedId = Boolean(params.newChatId && this.preparedChatKey?.chatId === params.newChatId && this.preparedChatOwnerCurrent(this.preparedChatKey.owner, teamId));
|
|
16550
|
+
const sdkMintedChatId = params.chatId === void 0 && params.projectId === void 0 && (params.newChatId === void 0 || ownsPreparedId);
|
|
16551
|
+
let chatId;
|
|
16552
|
+
if (params.newChatId) {
|
|
16553
|
+
chatId = params.newChatId;
|
|
16554
|
+
} else if (!params.chatId) {
|
|
16555
|
+
chatId = randomUUID9();
|
|
16556
|
+
} else if (!CANONICAL_UUID_PATTERN.test(params.chatId)) {
|
|
16557
|
+
const resolved = await this.resolveFullChatId(params.chatId, { teamId });
|
|
16558
|
+
if (!resolved) {
|
|
16559
|
+
throw new Error(
|
|
16560
|
+
`Chat not found for '${params.chatId}'. Use a slug, full UUID, or the first 8 characters of an existing chat ID.`
|
|
16561
|
+
);
|
|
16562
|
+
}
|
|
16563
|
+
chatId = resolved;
|
|
16564
|
+
} else {
|
|
16565
|
+
chatId = params.chatId;
|
|
16566
|
+
}
|
|
16567
|
+
const historyAbort = new AbortController();
|
|
16568
|
+
let savedPrivacyHistoryRead = null;
|
|
16569
|
+
let historySnapshotOwner = null;
|
|
16570
|
+
const readSavedPrivacyHistory = () => {
|
|
16571
|
+
if (!savedPrivacyHistoryRead) {
|
|
16572
|
+
savedPrivacyHistoryRead = withHttpAbortSignal(historyAbort.signal, () => this.getChatMessages(
|
|
16573
|
+
chatId,
|
|
16574
|
+
{ personal: !teamId, teamId, freshChatOnly: true, signal: historyAbort.signal }
|
|
16575
|
+
)).then((history) => {
|
|
16576
|
+
historySnapshotOwner = this.preparedChatOwner(this.requireSession(), teamId);
|
|
16577
|
+
return history;
|
|
16578
|
+
});
|
|
16579
|
+
void savedPrivacyHistoryRead.catch(() => {
|
|
16580
|
+
});
|
|
16581
|
+
}
|
|
16582
|
+
return savedPrivacyHistoryRead;
|
|
16583
|
+
};
|
|
16584
|
+
const joinSavedPrivacyHistory = async () => {
|
|
16585
|
+
const history = await readSavedPrivacyHistory();
|
|
16586
|
+
if (!historySnapshotOwner || !this.preparedChatOwnerCurrent(historySnapshotOwner, teamId)) {
|
|
16587
|
+
throw new Error("Chat workspace changed during history preparation.");
|
|
16588
|
+
}
|
|
16589
|
+
return history;
|
|
16590
|
+
};
|
|
16591
|
+
try {
|
|
16592
|
+
let finalMessage = params.message;
|
|
16593
|
+
if (hasRememberMessageReference(finalMessage)) {
|
|
16594
|
+
const rememberedHistory = (params.messageHistory ?? []).map((message) => ({ id: message.message_id, content: message.content })).filter((message) => Boolean(message.id) && typeof message.content === "string");
|
|
16595
|
+
finalMessage = rewriteRememberMessageReferences(finalMessage, rememberedHistory);
|
|
16596
|
+
}
|
|
16597
|
+
if (params.chatId && !params.incognito && hasRememberMessageReference(finalMessage)) {
|
|
16598
|
+
const { messages } = await joinSavedPrivacyHistory();
|
|
16599
|
+
finalMessage = rewriteRememberMessageReferences(finalMessage, messages);
|
|
16600
|
+
}
|
|
16601
|
+
const shouldWaitForAi = shouldWaitForTeamAi(finalMessage, teamId);
|
|
16602
|
+
const explicitTasksAppSkill = messageExplicitlyRequestsTasksAppSkill(finalMessage);
|
|
16603
|
+
const taskUpdateJobsEnabled = params.taskUpdateJobs !== false && !explicitTasksAppSkill;
|
|
16604
|
+
const sendSocketOptions = {
|
|
16605
|
+
taskUpdateJobs: taskUpdateJobsEnabled,
|
|
16606
|
+
projectFileJobs: !params.incognito,
|
|
16607
|
+
remoteCommandJobs: !params.incognito,
|
|
16608
|
+
interactiveHuman: params.interactiveHuman === true && this.interactiveViewerAllowed && process.stdin.isTTY === true && process.stdout.isTTY === true
|
|
16609
|
+
};
|
|
16610
|
+
const independentKeyOwner = this.preparedChatOwner(this.requireSession(), teamId);
|
|
16611
|
+
const independentNewChatKey = !params.incognito && !params.chatId ? (async () => {
|
|
16612
|
+
if (ownsPreparedId && this.preparedChatKey?.chatId === chatId) {
|
|
16613
|
+
const prepared = await this.preparedChatKey.value;
|
|
16614
|
+
if (teamId) independentKeyOwner.teamKeyFingerprint = createHash14("sha256").update(bytesToBase64(prepared.wrappingKey)).digest("hex");
|
|
16615
|
+
return prepared;
|
|
16616
|
+
}
|
|
16617
|
+
const wrappingKey = await this.getChatWrappingKey(teamId, this.getMasterKeyBytes());
|
|
16618
|
+
if (teamId) independentKeyOwner.teamKeyFingerprint = createHash14("sha256").update(bytesToBase64(wrappingKey)).digest("hex");
|
|
16619
|
+
const key = new Uint8Array(randomBytes4(32));
|
|
16620
|
+
return { key, wrappingKey, encryptedKey: await encryptBytesWithAesGcm(key, wrappingKey) };
|
|
16621
|
+
})() : null;
|
|
16622
|
+
void independentNewChatKey?.catch(() => {
|
|
16623
|
+
});
|
|
16624
|
+
const independentRecoveryKeypair = independentNewChatKey?.then((prepared) => deriveChatCompletionRecoveryKeypair(
|
|
16625
|
+
Buffer.from(prepared.key).toString("base64url"),
|
|
16367
16626
|
chatId,
|
|
16368
|
-
|
|
16369
|
-
))
|
|
16370
|
-
|
|
16371
|
-
return history;
|
|
16627
|
+
1
|
|
16628
|
+
));
|
|
16629
|
+
void independentRecoveryKeypair?.catch(() => {
|
|
16372
16630
|
});
|
|
16373
|
-
|
|
16631
|
+
const privacyTurnId = params.incognito ? null : randomUUID9();
|
|
16632
|
+
let conditionalPrivacyReceipt = null;
|
|
16633
|
+
let compiledPrivacy;
|
|
16634
|
+
let memorySocket = null;
|
|
16635
|
+
let socketSnapshotAttempted = false;
|
|
16636
|
+
let socketSnapshotResultRows = null;
|
|
16637
|
+
let socketProjectCandidateRows = null;
|
|
16638
|
+
let verifiedProjectCandidates = null;
|
|
16639
|
+
let deferredProjectDirectory = false;
|
|
16640
|
+
let usePersonalRoutingDirectory = false;
|
|
16641
|
+
let projectCandidatesReadyFromSendStartMs = null;
|
|
16642
|
+
let socketMemoryReceipt = null;
|
|
16643
|
+
const socketMemoryOptionsKey = () => JSON.stringify([
|
|
16644
|
+
this.sendSocketOptionsKey(sendSocketOptions),
|
|
16645
|
+
params.projectId ?? null,
|
|
16646
|
+
params.teamId ?? null,
|
|
16647
|
+
params.personal === true,
|
|
16648
|
+
params.incognito === true,
|
|
16649
|
+
params.autoApproveMemories === true,
|
|
16650
|
+
params.piiDetection !== false,
|
|
16651
|
+
params.learningMode?.enabled ?? null,
|
|
16652
|
+
params.learningMode?.ageGroup ?? null
|
|
16653
|
+
]);
|
|
16654
|
+
if (!params.incognito && params.chatId) readSavedPrivacyHistory();
|
|
16655
|
+
const followupDiscoveryOwner = !params.incognito && params.chatId && !teamId && !params.projectId ? this.preparedChatOwner(this.requireSession(), teamId) : null;
|
|
16656
|
+
const followupDiscoveryRead = followupDiscoveryOwner ? withHttpAbortSignal(historyAbort.signal, async () => prefetchCliJevDiscovery(this, chatId, { personal: true, teamId })) : null;
|
|
16657
|
+
void followupDiscoveryRead?.catch(() => {
|
|
16374
16658
|
});
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
|
|
16399
|
-
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16404
|
-
|
|
16405
|
-
|
|
16406
|
-
|
|
16407
|
-
|
|
16408
|
-
|
|
16409
|
-
|
|
16410
|
-
|
|
16411
|
-
|
|
16412
|
-
|
|
16413
|
-
|
|
16414
|
-
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16418
|
-
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
16425
|
-
|
|
16426
|
-
|
|
16427
|
-
|
|
16428
|
-
let memorySocket = null;
|
|
16429
|
-
let socketSnapshotAttempted = false;
|
|
16430
|
-
let socketSnapshotResultRows = null;
|
|
16431
|
-
let socketProjectCandidateRows = null;
|
|
16432
|
-
let verifiedProjectCandidates = null;
|
|
16433
|
-
let deferredProjectDirectory = false;
|
|
16434
|
-
let usePersonalRoutingDirectory = false;
|
|
16435
|
-
let projectCandidatesReadyFromSendStartMs = null;
|
|
16436
|
-
let socketMemoryReceipt = null;
|
|
16437
|
-
const socketMemoryOptionsKey = () => JSON.stringify([
|
|
16438
|
-
this.sendSocketOptionsKey(sendSocketOptions),
|
|
16439
|
-
params.projectId ?? null,
|
|
16440
|
-
params.teamId ?? null,
|
|
16441
|
-
params.personal === true,
|
|
16442
|
-
params.incognito === true,
|
|
16443
|
-
params.autoApproveMemories === true,
|
|
16444
|
-
params.piiDetection !== false,
|
|
16445
|
-
params.learningMode?.enabled ?? null,
|
|
16446
|
-
params.learningMode?.ageGroup ?? null
|
|
16447
|
-
]);
|
|
16448
|
-
if (!params.incognito && params.chatId) readSavedPrivacyHistory();
|
|
16449
|
-
const followupDiscoveryOwner = !params.incognito && params.chatId && !teamId && !params.projectId ? this.preparedChatOwner(this.requireSession(), teamId) : null;
|
|
16450
|
-
const followupDiscoveryRead = followupDiscoveryOwner ? withHttpAbortSignal(historyAbort.signal, async () => prefetchCliJevDiscovery(this, chatId, { personal: true, teamId })) : null;
|
|
16451
|
-
void followupDiscoveryRead?.catch(() => {
|
|
16452
|
-
});
|
|
16453
|
-
const memoryStartedAt = performance.now();
|
|
16454
|
-
let availableMemories = params.memorySnapshot ?? [];
|
|
16455
|
-
let memoryCountsLoaded = false;
|
|
16456
|
-
let memoryMetadataKeys = [];
|
|
16457
|
-
if (!params.incognito) {
|
|
16458
|
-
try {
|
|
16459
|
-
if (!teamId) {
|
|
16460
|
-
try {
|
|
16461
|
-
socketSnapshotAttempted = true;
|
|
16462
|
-
memorySocket = (sendSocketOptions.interactiveHuman ? await this.takePreparedSendSocket(sendSocketOptions, teamId) : null) ?? await this.openWsClient(sendSocketOptions);
|
|
16463
|
-
const ownerKey = JSON.stringify([
|
|
16464
|
-
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16465
|
-
authCookieFingerprint(this.http.getCookieMap())
|
|
16466
|
-
]);
|
|
16467
|
-
const conditionalSupported = await memorySocket.ws.waitForServerCapability("personal_send_context_snapshot_v2");
|
|
16468
|
-
const combinedSupported = conditionalSupported || await memorySocket.ws.waitForServerCapability("personal_send_context_snapshot_v1");
|
|
16469
|
-
const supported = combinedSupported || await memorySocket.ws.waitForServerCapability("personal_memory_snapshot_v1");
|
|
16470
|
-
if (supported) {
|
|
16471
|
-
const deferProjectDirectory = sdkMintedChatId && !teamId && !params.incognito && !params.projectId && shouldWaitForAi && memorySocket.ws.supportsServerCapability("personal_memory_snapshot_v1") && memorySocket.ws.supportsServerCapability("chat_turn_admit_v1") && memorySocket.ws.supportsServerCapability("chat_turn_context_v1");
|
|
16472
|
-
deferredProjectDirectory = deferProjectDirectory;
|
|
16473
|
-
usePersonalRoutingDirectory = deferProjectDirectory && memorySocket.ws.supportsServerCapability("personal_project_routing_directory_v1");
|
|
16474
|
-
traceSendTiming("snapshot_request");
|
|
16475
|
-
const privacyOwnerKey = this.preparedPrivacyOwnerKey(null);
|
|
16476
|
-
const preferencesKey = JSON.stringify(await readPrivacyPreferences());
|
|
16477
|
-
const preparedBundle = conditionalSupported ? this.preparedPrivacy.get(privacyOwnerKey, preferencesKey) : null;
|
|
16478
|
-
const conditional = conditionalSupported ? await memorySocket.ws.requestPersonalSendContextSnapshotV2({
|
|
16479
|
-
request_id: randomUUID9(),
|
|
16480
|
-
purpose: "send",
|
|
16481
|
-
turn_id: privacyTurnId,
|
|
16482
|
-
...preparedBundle ? { known_generation: preparedBundle.generation } : {}
|
|
16483
|
-
}) : null;
|
|
16484
|
-
const combined = !conditional && combinedSupported && !deferProjectDirectory ? await memorySocket.ws.requestPersonalSendContextSnapshot(randomUUID9()) : null;
|
|
16485
|
-
const snapshot2 = conditional ? conditional.personal_memories ?? null : combined?.personal_memories ?? await memorySocket.ws.requestPersonalMemorySnapshot(randomUUID9());
|
|
16486
|
-
traceSendTiming("snapshot_ready");
|
|
16487
|
-
if (conditional || combined) {
|
|
16488
|
-
socketProjectCandidateRows = (conditional ?? combined).project_candidates;
|
|
16489
|
-
verifiedProjectCandidates = await decodePersonalProjectCandidates(this, (conditional ?? combined).project_candidates);
|
|
16490
|
-
projectCandidatesReadyFromSendStartMs = Math.round(performance.now() - preWsStartedAt);
|
|
16491
|
-
}
|
|
16492
|
-
if (conditional?.unchanged) {
|
|
16493
|
-
if (!preparedBundle || conditional.privacy_generation !== preparedBundle.generation) throw new Error("Unchanged privacy snapshot has no matching prepared bundle.");
|
|
16494
|
-
availableMemories = preparedBundle.memories;
|
|
16495
|
-
compiledPrivacy = preparedBundle.privacy;
|
|
16496
|
-
socketSnapshotResultRows = [];
|
|
16497
|
-
} else {
|
|
16498
|
-
if (!snapshot2) throw new Error("Full Personal privacy snapshot is required.");
|
|
16499
|
-
socketSnapshotResultRows = snapshot2.memories;
|
|
16500
|
-
availableMemories = await this.listMemoriesUnshared({ personal: true }, snapshot2);
|
|
16659
|
+
const memoryStartedAt = performance.now();
|
|
16660
|
+
let availableMemories = params.memorySnapshot ?? [];
|
|
16661
|
+
let memoryCountsLoaded = false;
|
|
16662
|
+
let memoryMetadataKeys = [];
|
|
16663
|
+
if (!params.incognito) {
|
|
16664
|
+
try {
|
|
16665
|
+
if (!teamId) {
|
|
16666
|
+
try {
|
|
16667
|
+
socketSnapshotAttempted = true;
|
|
16668
|
+
memorySocket = (sendSocketOptions.interactiveHuman ? await this.takePreparedSendSocket(sendSocketOptions, teamId) : null) ?? await this.openWsClient(sendSocketOptions);
|
|
16669
|
+
const ownerKey = JSON.stringify([
|
|
16670
|
+
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16671
|
+
authCookieFingerprint(this.http.getCookieMap())
|
|
16672
|
+
]);
|
|
16673
|
+
const conditionalSupported = await memorySocket.ws.waitForServerCapability("personal_send_context_snapshot_v2");
|
|
16674
|
+
const combinedSupported = conditionalSupported || await memorySocket.ws.waitForServerCapability("personal_send_context_snapshot_v1");
|
|
16675
|
+
const supported = combinedSupported || await memorySocket.ws.waitForServerCapability("personal_memory_snapshot_v1");
|
|
16676
|
+
if (supported) {
|
|
16677
|
+
const deferProjectDirectory = sdkMintedChatId && !teamId && !params.incognito && !params.projectId && shouldWaitForAi && memorySocket.ws.supportsServerCapability("personal_memory_snapshot_v1") && memorySocket.ws.supportsServerCapability("chat_turn_admit_v1") && memorySocket.ws.supportsServerCapability("chat_turn_context_v1");
|
|
16678
|
+
deferredProjectDirectory = deferProjectDirectory;
|
|
16679
|
+
usePersonalRoutingDirectory = deferProjectDirectory && memorySocket.ws.supportsServerCapability("personal_project_routing_directory_v1");
|
|
16680
|
+
traceSendTiming("snapshot_request");
|
|
16681
|
+
const privacyOwnerKey = this.preparedPrivacyOwnerKey(null);
|
|
16682
|
+
const preferencesKey = JSON.stringify(await readPrivacyPreferences());
|
|
16683
|
+
const preparedBundle = conditionalSupported ? this.preparedPrivacy.get(privacyOwnerKey, preferencesKey) : null;
|
|
16684
|
+
const conditional = conditionalSupported ? await memorySocket.ws.requestPersonalSendContextSnapshotV2({
|
|
16685
|
+
request_id: randomUUID9(),
|
|
16686
|
+
purpose: "send",
|
|
16687
|
+
turn_id: privacyTurnId,
|
|
16688
|
+
...preparedBundle ? { known_generation: preparedBundle.generation } : {}
|
|
16689
|
+
}) : null;
|
|
16690
|
+
const combined = !conditional && combinedSupported && !deferProjectDirectory ? await memorySocket.ws.requestPersonalSendContextSnapshot(randomUUID9()) : null;
|
|
16691
|
+
const snapshot2 = conditional ? conditional.personal_memories ?? null : combined?.personal_memories ?? await memorySocket.ws.requestPersonalMemorySnapshot(randomUUID9());
|
|
16692
|
+
traceSendTiming("snapshot_ready");
|
|
16693
|
+
if (conditional || combined) {
|
|
16694
|
+
socketProjectCandidateRows = (conditional ?? combined).project_candidates;
|
|
16695
|
+
verifiedProjectCandidates = await decodePersonalProjectCandidates(this, (conditional ?? combined).project_candidates);
|
|
16696
|
+
projectCandidatesReadyFromSendStartMs = Math.round(performance.now() - preWsStartedAt);
|
|
16697
|
+
}
|
|
16698
|
+
if (conditional?.unchanged) {
|
|
16699
|
+
if (!preparedBundle || conditional.privacy_generation !== preparedBundle.generation) throw new Error("Unchanged privacy snapshot has no matching prepared bundle.");
|
|
16700
|
+
availableMemories = preparedBundle.memories;
|
|
16701
|
+
compiledPrivacy = preparedBundle.privacy;
|
|
16702
|
+
socketSnapshotResultRows = [];
|
|
16703
|
+
} else {
|
|
16704
|
+
if (!snapshot2) throw new Error("Full Personal privacy snapshot is required.");
|
|
16705
|
+
socketSnapshotResultRows = snapshot2.memories;
|
|
16706
|
+
availableMemories = await this.listMemoriesUnshared({ personal: true }, snapshot2);
|
|
16707
|
+
if (conditional) {
|
|
16708
|
+
this.preparedPrivacy.set(privacyOwnerKey, preferencesKey, conditional.privacy_generation, availableMemories);
|
|
16709
|
+
compiledPrivacy = this.preparedPrivacy.get(privacyOwnerKey, preferencesKey)?.privacy;
|
|
16710
|
+
}
|
|
16711
|
+
}
|
|
16501
16712
|
if (conditional) {
|
|
16502
|
-
|
|
16503
|
-
|
|
16713
|
+
if (!conditional.receipt_id || conditional.turn_id !== privacyTurnId) throw new Error("Conditional privacy snapshot has no valid send receipt.");
|
|
16714
|
+
conditionalPrivacyReceipt = {
|
|
16715
|
+
receiptId: conditional.receipt_id,
|
|
16716
|
+
generation: conditional.privacy_generation,
|
|
16717
|
+
preferencesKey,
|
|
16718
|
+
epoch: this.preparedPrivacyEpoch
|
|
16719
|
+
};
|
|
16504
16720
|
}
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16721
|
+
traceSendTiming("memory_decrypt_ready");
|
|
16722
|
+
if (!memorySocket.ws.isOpen() || JSON.stringify([
|
|
16723
|
+
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16724
|
+
authCookieFingerprint(this.http.getCookieMap())
|
|
16725
|
+
]) !== ownerKey) {
|
|
16726
|
+
throw new Error("Personal Memory snapshot owner or socket changed during preparation.");
|
|
16727
|
+
}
|
|
16728
|
+
socketMemoryReceipt = {
|
|
16729
|
+
ws: memorySocket.ws,
|
|
16730
|
+
snapshot: availableMemories,
|
|
16731
|
+
resultRows: socketSnapshotResultRows,
|
|
16732
|
+
projectRows: socketProjectCandidateRows,
|
|
16733
|
+
candidates: verifiedProjectCandidates,
|
|
16734
|
+
ownerKey,
|
|
16735
|
+
optionsKey: socketMemoryOptionsKey(),
|
|
16736
|
+
used: false
|
|
16513
16737
|
};
|
|
16738
|
+
} else {
|
|
16739
|
+
memorySocket.ws.close();
|
|
16740
|
+
memorySocket = null;
|
|
16741
|
+
socketSnapshotAttempted = false;
|
|
16514
16742
|
}
|
|
16515
|
-
|
|
16516
|
-
|
|
16517
|
-
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16518
|
-
authCookieFingerprint(this.http.getCookieMap())
|
|
16519
|
-
]) !== ownerKey) {
|
|
16520
|
-
throw new Error("Personal Memory snapshot owner or socket changed during preparation.");
|
|
16521
|
-
}
|
|
16522
|
-
socketMemoryReceipt = {
|
|
16523
|
-
ws: memorySocket.ws,
|
|
16524
|
-
snapshot: availableMemories,
|
|
16525
|
-
resultRows: socketSnapshotResultRows,
|
|
16526
|
-
projectRows: socketProjectCandidateRows,
|
|
16527
|
-
candidates: verifiedProjectCandidates,
|
|
16528
|
-
ownerKey,
|
|
16529
|
-
optionsKey: socketMemoryOptionsKey(),
|
|
16530
|
-
used: false
|
|
16531
|
-
};
|
|
16532
|
-
} else {
|
|
16533
|
-
memorySocket.ws.close();
|
|
16743
|
+
} catch (error) {
|
|
16744
|
+
memorySocket?.ws.close();
|
|
16534
16745
|
memorySocket = null;
|
|
16535
|
-
|
|
16746
|
+
throw error;
|
|
16536
16747
|
}
|
|
16537
|
-
} catch (error) {
|
|
16538
|
-
memorySocket?.ws.close();
|
|
16539
|
-
memorySocket = null;
|
|
16540
|
-
throw error;
|
|
16541
16748
|
}
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
|
|
16551
|
-
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
16749
|
+
if (!socketMemoryReceipt) availableMemories = teamId && params.memorySnapshot ? params.memorySnapshot : await this.listMemoriesUnshared({ teamId }, void 0, !teamId);
|
|
16750
|
+
memoryCountsLoaded = true;
|
|
16751
|
+
memoryMetadataKeys = [
|
|
16752
|
+
...new Set(
|
|
16753
|
+
availableMemories.filter((memory) => memory.app_id && memory.item_type).map((memory) => `${memory.app_id}-${memory.item_type}`)
|
|
16754
|
+
)
|
|
16755
|
+
];
|
|
16756
|
+
} catch (error) {
|
|
16757
|
+
if (socketSnapshotAttempted || !teamId) throw error;
|
|
16758
|
+
if (params.autoApproveMemories) {
|
|
16759
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16760
|
+
throw new Error(`Failed to load memories for auto-approval: ${message}`);
|
|
16761
|
+
}
|
|
16555
16762
|
}
|
|
16556
16763
|
}
|
|
16557
|
-
}
|
|
16558
|
-
try {
|
|
16559
|
-
const snapshotAuthHandoff = this.personalMemoryAuthHandoff?.snapshot.deref() === availableMemories ? this.personalMemoryAuthHandoff.auth : null;
|
|
16560
|
-
this.personalMemoryAuthHandoff = null;
|
|
16561
|
-
this.pendingWhoAmIAuthHandoff = null;
|
|
16562
|
-
const personalSendAuthHandoff = !teamId && !params.incognito ? snapshotAuthHandoff : null;
|
|
16563
|
-
const memoryPrepMs = Math.round(performance.now() - memoryStartedAt);
|
|
16564
|
-
const privacyStartedAt = performance.now();
|
|
16565
|
-
traceSendTiming("privacy_begin");
|
|
16566
|
-
const savedPrivacyHistory = !params.incognito && params.chatId ? await joinSavedPrivacyHistory() : null;
|
|
16567
|
-
let piiMappings = [...savedPrivacyHistory?.messages.flatMap((message) => (message.piiMappings ?? []).map((mapping) => ({ ...mapping, type: mapping.type ?? "OTHER" }))) ?? [], ...params.piiMappings ?? []];
|
|
16568
16764
|
try {
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
}
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
|
|
16765
|
+
const snapshotAuthHandoff = this.personalMemoryAuthHandoff?.snapshot.deref() === availableMemories ? this.personalMemoryAuthHandoff.auth : null;
|
|
16766
|
+
this.personalMemoryAuthHandoff = null;
|
|
16767
|
+
this.pendingWhoAmIAuthHandoff = null;
|
|
16768
|
+
const personalSendAuthHandoff = !teamId && !params.incognito ? snapshotAuthHandoff : null;
|
|
16769
|
+
const memoryPrepMs = Math.round(performance.now() - memoryStartedAt);
|
|
16770
|
+
const privacyStartedAt = performance.now();
|
|
16771
|
+
traceSendTiming("privacy_begin");
|
|
16772
|
+
const savedPrivacyHistory = !params.incognito && params.chatId ? await joinSavedPrivacyHistory() : null;
|
|
16773
|
+
let piiMappings = [...savedPrivacyHistory?.messages.flatMap((message) => (message.piiMappings ?? []).map((mapping) => ({ ...mapping, type: mapping.type ?? "OTHER" }))) ?? [], ...params.piiMappings ?? []];
|
|
16774
|
+
try {
|
|
16775
|
+
if (params.piiDetection !== false) {
|
|
16776
|
+
const prepared = await prepareCliMessagePrivacy(finalMessage, availableMemories, piiMappings, { kind: "message" }, params.onPrivacyProgress, compiledPrivacy);
|
|
16777
|
+
finalMessage = prepared.message;
|
|
16778
|
+
piiMappings = prepared.mappings;
|
|
16779
|
+
for (const embed of params.preparedEmbeds ?? []) {
|
|
16780
|
+
if (!["code-code", "docs-doc", "sheets-sheet"].includes(embed.type)) continue;
|
|
16781
|
+
const decoded = toonDecode(embed.content);
|
|
16782
|
+
for (const field3 of ["code", "html", "table", "text"]) if (typeof decoded[field3] === "string") {
|
|
16783
|
+
const value = await prepareCliMessagePrivacy(decoded[field3], availableMemories, piiMappings, { kind: "document" }, params.onPrivacyProgress, compiledPrivacy);
|
|
16784
|
+
decoded[field3] = value.message;
|
|
16785
|
+
piiMappings = value.mappings;
|
|
16786
|
+
}
|
|
16787
|
+
embed.content = toonEncodeContent(decoded);
|
|
16788
|
+
const content = decoded.code ?? decoded.table ?? decoded.html ?? decoded.text;
|
|
16789
|
+
if (typeof content === "string") {
|
|
16790
|
+
embed.contentHash = createHash14("sha256").update(content).digest("hex");
|
|
16791
|
+
embed.textLengthChars = content.length;
|
|
16792
|
+
}
|
|
16586
16793
|
}
|
|
16587
16794
|
}
|
|
16795
|
+
params.onPrivacyPrepared?.(finalMessage);
|
|
16796
|
+
} catch (error) {
|
|
16797
|
+
memorySocket?.ws.close();
|
|
16798
|
+
throw error;
|
|
16588
16799
|
}
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
let publishProjectRouting;
|
|
16614
|
-
let rejectProjectRouting;
|
|
16615
|
-
const projectRoutingContext = new Promise((resolve20, reject) => {
|
|
16616
|
-
publishProjectRouting = resolve20;
|
|
16617
|
-
rejectProjectRouting = reject;
|
|
16618
|
-
});
|
|
16619
|
-
void projectRoutingContext.catch(() => {
|
|
16620
|
-
});
|
|
16621
|
-
const publishProjectRoutingTimed = (context) => {
|
|
16622
|
-
projectCandidatesReadyFromSendStartMs ??= Math.round(performance.now() - preWsStartedAt);
|
|
16623
|
-
publishProjectRouting(deferredProjectDirectory ? { ...context, project_focus_candidates: context.project_focus_candidates?.filter((candidate) => candidate.auto_selection === true) } : context);
|
|
16624
|
-
};
|
|
16625
|
-
let recoveryMs = 0;
|
|
16626
|
-
const contextOwnerIsCurrent = () => {
|
|
16627
|
-
const current2 = this.session;
|
|
16628
|
-
return Boolean(current2 && current2.apiUrl === contextApiUrl && current2.hashedEmail === contextHashedEmail && current2.sessionId === contextSessionId && current2.createdAt === contextSessionCreatedAt && current2.masterKeyExportedB64 === contextMasterKey && current2.activeTeamId === contextActiveTeamId);
|
|
16629
|
-
};
|
|
16630
|
-
const earlyContextAbort = new AbortController();
|
|
16631
|
-
const earlyContext = !params.incognito && !params.chatId && !params.projectId ? withHttpAbortSignal(earlyContextAbort.signal, () => prepareCliJevContext(this, chatId, contextScope, suppliedContext, finalMessage, {
|
|
16632
|
-
sdkMintedChatId,
|
|
16633
|
-
onRouting: publishRouting,
|
|
16634
|
-
verifiedProjectCandidates: verifiedProjectCandidates ?? void 0,
|
|
16635
|
-
usePersonalRoutingDirectory,
|
|
16636
|
-
onProjectRouting: publishProjectRoutingTimed,
|
|
16637
|
-
onProjectRoutingError: rejectProjectRouting
|
|
16638
|
-
})).then(
|
|
16639
|
-
(value) => ({ ok: true, value, elapsedMs: Date.now() - contextStartedAt }),
|
|
16640
|
-
(error) => ({ ok: false, error, elapsedMs: Date.now() - contextStartedAt })
|
|
16641
|
-
) : null;
|
|
16642
|
-
const authSocketStartedAt = Date.now();
|
|
16643
|
-
let opened;
|
|
16644
|
-
try {
|
|
16645
|
-
opened = memorySocket ?? (sendSocketOptions.interactiveHuman ? await this.takePreparedSendSocket(sendSocketOptions, teamId) : null) ?? await this.openWsClient({ ...sendSocketOptions, personalSendAuthHandoff });
|
|
16646
|
-
} catch (error) {
|
|
16647
|
-
earlyContextAbort.abort();
|
|
16648
|
-
if (earlyContext) await earlyContext;
|
|
16649
|
-
throw error;
|
|
16650
|
-
}
|
|
16651
|
-
const { ws, ownerId: ownerId2 } = opened;
|
|
16652
|
-
const consumeSocketMemoryReceipt = async () => {
|
|
16653
|
-
if (!socketMemoryReceipt) return;
|
|
16654
|
-
const receipt = socketMemoryReceipt;
|
|
16655
|
-
if (receipt.used || receipt.ws !== ws || receipt.snapshot !== availableMemories || receipt.resultRows !== socketSnapshotResultRows || receipt.projectRows !== socketProjectCandidateRows || receipt.candidates !== verifiedProjectCandidates || !ws.isOpen() || receipt.optionsKey !== socketMemoryOptionsKey()) {
|
|
16656
|
-
throw new Error("Personal Memory snapshot socket receipt is invalid.");
|
|
16657
|
-
}
|
|
16658
|
-
await this.withFreshStoredSession(async () => {
|
|
16659
|
-
if (!ws.isOpen() || JSON.stringify([
|
|
16660
|
-
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16661
|
-
authCookieFingerprint(this.http.getCookieMap())
|
|
16662
|
-
]) !== receipt.ownerKey) {
|
|
16663
|
-
throw new Error("Personal Memory snapshot owner changed before send.");
|
|
16664
|
-
}
|
|
16800
|
+
const privacyPrepMs = Math.round(performance.now() - privacyStartedAt);
|
|
16801
|
+
traceSendTiming("privacy_ready");
|
|
16802
|
+
const preWsMs = Math.round(performance.now() - preWsStartedAt);
|
|
16803
|
+
const contextSession = this.requireSession();
|
|
16804
|
+
const {
|
|
16805
|
+
apiUrl: contextApiUrl,
|
|
16806
|
+
hashedEmail: contextHashedEmail,
|
|
16807
|
+
sessionId: contextSessionId,
|
|
16808
|
+
createdAt: contextSessionCreatedAt,
|
|
16809
|
+
masterKeyExportedB64: contextMasterKey,
|
|
16810
|
+
activeTeamId: contextActiveTeamId
|
|
16811
|
+
} = contextSession;
|
|
16812
|
+
const contextScope = { teamId, personal: !teamId };
|
|
16813
|
+
const suppliedContext = deferredProjectDirectory ? { ...params.jevContext, project_focus_candidates: void 0 } : params.jevContext;
|
|
16814
|
+
const contextStartedAt = Date.now();
|
|
16815
|
+
let publishRouting;
|
|
16816
|
+
const routingContext = new Promise((resolve20) => {
|
|
16817
|
+
publishRouting = resolve20;
|
|
16818
|
+
});
|
|
16819
|
+
let publishProjectRouting;
|
|
16820
|
+
let rejectProjectRouting;
|
|
16821
|
+
const projectRoutingContext = new Promise((resolve20, reject) => {
|
|
16822
|
+
publishProjectRouting = resolve20;
|
|
16823
|
+
rejectProjectRouting = reject;
|
|
16665
16824
|
});
|
|
16666
|
-
|
|
16667
|
-
|
|
16825
|
+
void projectRoutingContext.catch(() => {
|
|
16826
|
+
});
|
|
16827
|
+
const catalogEligibleProjectIds = /* @__PURE__ */ new Set();
|
|
16828
|
+
const publishProjectRoutingTimed = (context) => {
|
|
16829
|
+
projectCandidatesReadyFromSendStartMs ??= Math.round(performance.now() - preWsStartedAt);
|
|
16830
|
+
const offered = context.project_focus_candidates?.filter((candidate) => candidate.auto_selection === true) ?? [];
|
|
16831
|
+
for (const candidate of offered) catalogEligibleProjectIds.add(candidate.project_id);
|
|
16832
|
+
publishProjectRouting(deferredProjectDirectory ? { ...context, project_focus_candidates: offered } : context);
|
|
16833
|
+
};
|
|
16834
|
+
let recoveryMs = 0;
|
|
16835
|
+
const contextOwnerIsCurrent = () => {
|
|
16836
|
+
const current2 = this.session;
|
|
16837
|
+
return Boolean(current2 && current2.apiUrl === contextApiUrl && current2.hashedEmail === contextHashedEmail && current2.sessionId === contextSessionId && current2.createdAt === contextSessionCreatedAt && current2.masterKeyExportedB64 === contextMasterKey && current2.activeTeamId === contextActiveTeamId);
|
|
16838
|
+
};
|
|
16839
|
+
const earlyContextAbort = new AbortController();
|
|
16840
|
+
const earlyContext = !params.incognito && !params.chatId && !params.projectId ? withHttpAbortSignal(earlyContextAbort.signal, () => prepareCliJevContext(this, chatId, contextScope, suppliedContext, finalMessage, {
|
|
16841
|
+
sdkMintedChatId,
|
|
16842
|
+
onRouting: publishRouting,
|
|
16843
|
+
verifiedProjectCandidates: verifiedProjectCandidates ?? void 0,
|
|
16844
|
+
usePersonalRoutingDirectory,
|
|
16845
|
+
onProjectRouting: publishProjectRoutingTimed,
|
|
16846
|
+
onProjectRoutingError: rejectProjectRouting
|
|
16847
|
+
})).then(
|
|
16848
|
+
(value) => ({ ok: true, value, elapsedMs: Date.now() - contextStartedAt }),
|
|
16849
|
+
(error) => ({ ok: false, error, elapsedMs: Date.now() - contextStartedAt })
|
|
16850
|
+
) : null;
|
|
16851
|
+
const authSocketStartedAt = Date.now();
|
|
16852
|
+
let opened;
|
|
16853
|
+
try {
|
|
16854
|
+
opened = memorySocket ?? (sendSocketOptions.interactiveHuman ? await this.takePreparedSendSocket(sendSocketOptions, teamId) : null) ?? await this.openWsClient({ ...sendSocketOptions, personalSendAuthHandoff });
|
|
16855
|
+
} catch (error) {
|
|
16856
|
+
earlyContextAbort.abort();
|
|
16857
|
+
if (earlyContext) await earlyContext;
|
|
16858
|
+
throw error;
|
|
16668
16859
|
}
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
}
|
|
16693
|
-
const authSocketMs = Date.now() - authSocketStartedAt;
|
|
16694
|
-
const mayDeferRecovery = !params.incognito && sdkMintedChatId && !teamId && !params.projectId && shouldWaitForAi;
|
|
16695
|
-
let recoveryDiscovery = null;
|
|
16696
|
-
let recoveryDiscoveryError = null;
|
|
16697
|
-
let contextAckRejection = null;
|
|
16698
|
-
let removeContextAckWatcher = null;
|
|
16699
|
-
try {
|
|
16700
|
-
if (!params.incognito) {
|
|
16701
|
-
try {
|
|
16702
|
-
ws.send("chat_metadata_warmup", teamId ? { team_id: teamId } : {});
|
|
16703
|
-
} catch {
|
|
16860
|
+
const { ws, ownerId: ownerId2 } = opened;
|
|
16861
|
+
let catalogTurnId = null;
|
|
16862
|
+
let rejectCatalogDelivery;
|
|
16863
|
+
const catalogDeliveryFailure = new Promise((_resolve, reject) => {
|
|
16864
|
+
rejectCatalogDelivery = reject;
|
|
16865
|
+
});
|
|
16866
|
+
void catalogDeliveryFailure.catch(() => {
|
|
16867
|
+
});
|
|
16868
|
+
const stopProjectCatalogResponder = !params.incognito ? registerCliProjectFocusCatalogResponder({
|
|
16869
|
+
ws,
|
|
16870
|
+
client: this,
|
|
16871
|
+
chatId,
|
|
16872
|
+
teamId: teamId ?? null,
|
|
16873
|
+
isCurrent: () => contextOwnerIsCurrent() && !!catalogTurnId && this.activeSubmittedChatTurns.get(chatId) === catalogTurnId,
|
|
16874
|
+
isOfferedProject: (projectId) => catalogEligibleProjectIds.has(projectId),
|
|
16875
|
+
onDeliveryFailure: (error) => rejectCatalogDelivery(error)
|
|
16876
|
+
}) : () => {
|
|
16877
|
+
};
|
|
16878
|
+
const consumeSocketMemoryReceipt = async () => {
|
|
16879
|
+
if (!socketMemoryReceipt) return;
|
|
16880
|
+
const receipt = socketMemoryReceipt;
|
|
16881
|
+
if (receipt.used || receipt.ws !== ws || receipt.snapshot !== availableMemories || receipt.resultRows !== socketSnapshotResultRows || receipt.projectRows !== socketProjectCandidateRows || receipt.candidates !== verifiedProjectCandidates || !ws.isOpen() || receipt.optionsKey !== socketMemoryOptionsKey()) {
|
|
16882
|
+
throw new Error("Personal Memory snapshot socket receipt is invalid.");
|
|
16704
16883
|
}
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
recoveryDiscoveryError = error;
|
|
16712
|
-
throw error;
|
|
16884
|
+
await this.withFreshStoredSession(async () => {
|
|
16885
|
+
if (!ws.isOpen() || JSON.stringify([
|
|
16886
|
+
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16887
|
+
authCookieFingerprint(this.http.getCookieMap())
|
|
16888
|
+
]) !== receipt.ownerKey) {
|
|
16889
|
+
throw new Error("Personal Memory snapshot owner changed before send.");
|
|
16713
16890
|
}
|
|
16714
|
-
);
|
|
16715
|
-
void recoveryDiscovery.catch(() => {
|
|
16716
16891
|
});
|
|
16717
|
-
if (
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
16723
|
-
|
|
16724
|
-
|
|
16725
|
-
|
|
16726
|
-
|
|
16727
|
-
let
|
|
16728
|
-
|
|
16729
|
-
|
|
16730
|
-
|
|
16892
|
+
if (conditionalPrivacyReceipt && (conditionalPrivacyReceipt.epoch !== this.preparedPrivacyEpoch || conditionalPrivacyReceipt.preferencesKey !== JSON.stringify(await readPrivacyPreferences()))) {
|
|
16893
|
+
throw new Error("Privacy preferences changed before send; prepare the turn again.");
|
|
16894
|
+
}
|
|
16895
|
+
receipt.used = true;
|
|
16896
|
+
};
|
|
16897
|
+
let metadataTransportOwned = false;
|
|
16898
|
+
let metadataHandoffReady = false;
|
|
16899
|
+
let metadataHandoffSuggestion = null;
|
|
16900
|
+
let initialHeaderWork = null;
|
|
16901
|
+
let initialHeaderNotice = null;
|
|
16902
|
+
let initialAdmissionDurable = false;
|
|
16903
|
+
let initialHeaderStopped = false;
|
|
16904
|
+
let initialTitleScheduled;
|
|
16905
|
+
let initialIconScheduled;
|
|
16906
|
+
let persistOptionalHeader;
|
|
16907
|
+
let optionalMetadata = null;
|
|
16908
|
+
const socketReadyForSendAt = performance.now();
|
|
16909
|
+
let capabilitiesWaitMs = 0;
|
|
16910
|
+
let keyWaitMs = 0;
|
|
16911
|
+
let recoveryKeypairMs = 0;
|
|
16912
|
+
let encryptPayloadMs = 0;
|
|
16913
|
+
if (!params.incognito && !ownerId2) {
|
|
16914
|
+
earlyContextAbort.abort();
|
|
16915
|
+
ws.close();
|
|
16916
|
+
if (earlyContext) await earlyContext;
|
|
16917
|
+
throw new Error("Authenticated user identity is required for saved chat recovery.");
|
|
16731
16918
|
}
|
|
16732
|
-
|
|
16733
|
-
const
|
|
16734
|
-
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
|
|
16743
|
-
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
|
|
16752
|
-
|
|
16753
|
-
traceSendTiming("key_begin");
|
|
16754
|
-
const masterKey = this.getMasterKeyBytes();
|
|
16755
|
-
const preparedKey = isNewChat && ownsPreparedId && this.preparedChatKey?.chatId === chatId && this.preparedChatOwnerCurrent(this.preparedChatKey.owner, teamId) ? this.preparedChatKey : null;
|
|
16756
|
-
if (preparedKey) this.preparedChatKey = null;
|
|
16757
|
-
const keyWaitStartedAt = performance.now();
|
|
16758
|
-
const prepared = independentNewChatKey ? await independentNewChatKey : preparedKey ? await preparedKey.value : null;
|
|
16759
|
-
if (prepared && !this.preparedChatOwnerCurrent(independentKeyOwner, teamId)) throw new Error("Chat key preparation owner changed before send.");
|
|
16760
|
-
const wrappingKey = prepared?.wrappingKey ?? await this.getChatWrappingKey(teamId, masterKey);
|
|
16761
|
-
keyWaitMs = Math.round(performance.now() - keyWaitStartedAt);
|
|
16762
|
-
chatSlugLookupKey = wrappingKey;
|
|
16763
|
-
if (isNewChat && prepared) {
|
|
16764
|
-
chatKeyBytes = prepared.key;
|
|
16765
|
-
encryptedChatKey = prepared.encryptedKey;
|
|
16766
|
-
} else if (isNewChat) {
|
|
16767
|
-
chatKeyBytes = globalThis.crypto ? new Uint8Array(globalThis.crypto.getRandomValues(new Uint8Array(32))) : new Uint8Array(
|
|
16768
|
-
(await import("crypto")).webcrypto.getRandomValues(
|
|
16769
|
-
new Uint8Array(32)
|
|
16770
|
-
)
|
|
16771
|
-
);
|
|
16772
|
-
encryptedChatKey = await encryptBytesWithAesGcm(
|
|
16773
|
-
chatKeyBytes,
|
|
16774
|
-
wrappingKey
|
|
16775
|
-
);
|
|
16776
|
-
} else {
|
|
16777
|
-
const cache = (savedPrivacyHistory && this.scopedChatHistory.get(savedPrivacyHistory)) ?? loadSyncCache(teamId) ?? await this.ensureSynced(false, [], { teamId });
|
|
16778
|
-
const chat = cache.chats.find(
|
|
16779
|
-
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
16919
|
+
const authSocketMs = Date.now() - authSocketStartedAt;
|
|
16920
|
+
const mayDeferRecovery = !params.incognito && sdkMintedChatId && !teamId && !params.projectId && shouldWaitForAi;
|
|
16921
|
+
let recoveryDiscovery = null;
|
|
16922
|
+
let recoveryDiscoveryError = null;
|
|
16923
|
+
let contextAckRejection = null;
|
|
16924
|
+
let removeContextAckWatcher = null;
|
|
16925
|
+
try {
|
|
16926
|
+
if (!params.incognito) {
|
|
16927
|
+
try {
|
|
16928
|
+
ws.send("chat_metadata_warmup", teamId ? { team_id: teamId } : {});
|
|
16929
|
+
} catch {
|
|
16930
|
+
}
|
|
16931
|
+
const recoveryStartedAt = Date.now();
|
|
16932
|
+
recoveryDiscovery = ws.waitForRecoveryOutputDiscovery().then(
|
|
16933
|
+
() => {
|
|
16934
|
+
recoveryMs = Date.now() - recoveryStartedAt;
|
|
16935
|
+
},
|
|
16936
|
+
(error) => {
|
|
16937
|
+
recoveryDiscoveryError = error;
|
|
16938
|
+
throw error;
|
|
16939
|
+
}
|
|
16780
16940
|
);
|
|
16781
|
-
|
|
16782
|
-
|
|
16783
|
-
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16941
|
+
void recoveryDiscovery.catch(() => {
|
|
16942
|
+
});
|
|
16943
|
+
if (mayDeferRecovery) {
|
|
16944
|
+
const capabilitiesWaitStartedAt = performance.now();
|
|
16945
|
+
await Promise.race([ws.waitForServerCapabilities(), recoveryDiscovery]);
|
|
16946
|
+
capabilitiesWaitMs = Math.round(performance.now() - capabilitiesWaitStartedAt);
|
|
16947
|
+
} else await recoveryDiscovery;
|
|
16948
|
+
}
|
|
16949
|
+
const messageId2 = randomUUID9();
|
|
16950
|
+
const wallCreatedAt = Math.floor(Date.now() / 1e3);
|
|
16951
|
+
const createdAt = !teamId && !params.incognito && params.chatId ? nextPersonalUserCreatedAt(wallCreatedAt, savedPrivacyHistory?.messages.map((row3) => row3.createdAt) ?? []) : wallCreatedAt;
|
|
16952
|
+
const isNewChat = !params.chatId;
|
|
16953
|
+
let messageHistoryForRequest = params.messageHistory;
|
|
16954
|
+
if (!params.incognito && !isNewChat && !messageHistoryForRequest) {
|
|
16955
|
+
const { messages } = savedPrivacyHistory;
|
|
16956
|
+
messageHistoryForRequest = buildSavedChatMessageHistory(messages, chatId);
|
|
16957
|
+
}
|
|
16958
|
+
ws.send("set_active_chat", { chat_id: chatId, ...teamId ? { team_id: teamId } : {} });
|
|
16959
|
+
const connectedAccountDirectory = buildConnectedAccountDirectoryPayload(
|
|
16960
|
+
params.connectedAccountDirectory
|
|
16961
|
+
);
|
|
16962
|
+
const connectedAccountTokenRefs = params.connectedAccountTokenRefInputs?.length ? await this.createTurnTokenRefs({
|
|
16963
|
+
chatId,
|
|
16964
|
+
messageId: messageId2,
|
|
16965
|
+
refs: params.connectedAccountTokenRefInputs,
|
|
16966
|
+
teamId
|
|
16967
|
+
}) : [];
|
|
16968
|
+
assertNoConnectedAccountSecretLeak(connectedAccountTokenRefs);
|
|
16969
|
+
let chatKeyBytes = null;
|
|
16970
|
+
let activeFocusId = null;
|
|
16971
|
+
let encryptedChatKey = null;
|
|
16972
|
+
let chatSlugLookupKey = null;
|
|
16973
|
+
let focusPhaseState = {};
|
|
16974
|
+
let baselineMessagesV = 0;
|
|
16975
|
+
let notificationTitle = null;
|
|
16976
|
+
let terminalExpectedMessagesV = 1;
|
|
16977
|
+
let savedTurnId = null;
|
|
16978
|
+
if (!params.incognito) {
|
|
16979
|
+
traceSendTiming("key_begin");
|
|
16980
|
+
const masterKey = this.getMasterKeyBytes();
|
|
16981
|
+
const preparedKey = isNewChat && ownsPreparedId && this.preparedChatKey?.chatId === chatId && this.preparedChatOwnerCurrent(this.preparedChatKey.owner, teamId) ? this.preparedChatKey : null;
|
|
16982
|
+
if (preparedKey) this.preparedChatKey = null;
|
|
16983
|
+
const keyWaitStartedAt = performance.now();
|
|
16984
|
+
const prepared = independentNewChatKey ? await independentNewChatKey : preparedKey ? await preparedKey.value : null;
|
|
16985
|
+
if (prepared && !this.preparedChatOwnerCurrent(independentKeyOwner, teamId)) throw new Error("Chat key preparation owner changed before send.");
|
|
16986
|
+
const wrappingKey = prepared?.wrappingKey ?? await this.getChatWrappingKey(teamId, masterKey);
|
|
16987
|
+
keyWaitMs = Math.round(performance.now() - keyWaitStartedAt);
|
|
16988
|
+
chatSlugLookupKey = wrappingKey;
|
|
16989
|
+
if (isNewChat && prepared) {
|
|
16990
|
+
chatKeyBytes = prepared.key;
|
|
16991
|
+
encryptedChatKey = prepared.encryptedKey;
|
|
16992
|
+
} else if (isNewChat) {
|
|
16993
|
+
chatKeyBytes = globalThis.crypto ? new Uint8Array(globalThis.crypto.getRandomValues(new Uint8Array(32))) : new Uint8Array(
|
|
16994
|
+
(await import("crypto")).webcrypto.getRandomValues(
|
|
16995
|
+
new Uint8Array(32)
|
|
16996
|
+
)
|
|
16997
|
+
);
|
|
16998
|
+
encryptedChatKey = await encryptBytesWithAesGcm(
|
|
16999
|
+
chatKeyBytes,
|
|
17000
|
+
wrappingKey
|
|
17001
|
+
);
|
|
17002
|
+
} else {
|
|
17003
|
+
const cache = (savedPrivacyHistory && this.scopedChatHistory.get(savedPrivacyHistory)) ?? loadSyncCache(teamId) ?? await this.ensureSynced(false, [], { teamId });
|
|
17004
|
+
const chat = cache.chats.find(
|
|
17005
|
+
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
17006
|
+
);
|
|
17007
|
+
if (chat) {
|
|
17008
|
+
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
17009
|
+
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
17010
|
+
if (encKey) {
|
|
17011
|
+
chatKeyBytes = await this.resolveChatKey(cache, chat, wrappingKey, teamId);
|
|
17012
|
+
encryptedChatKey = encKey;
|
|
17013
|
+
if (!chatKeyBytes) throw new Error("Could not decrypt the saved chat key.");
|
|
17014
|
+
if (typeof chat.details.encrypted_title === "string") {
|
|
17015
|
+
try {
|
|
17016
|
+
notificationTitle = await decryptWithAesGcmCombined(chat.details.encrypted_title, chatKeyBytes);
|
|
17017
|
+
} catch {
|
|
17018
|
+
}
|
|
16792
17019
|
}
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
17020
|
+
const encryptedPhaseState = chat.details.encrypted_focus_phase_state;
|
|
17021
|
+
if (typeof encryptedPhaseState === "string" && encryptedPhaseState) {
|
|
17022
|
+
const phaseText = await decryptWithAesGcmCombined(encryptedPhaseState, chatKeyBytes);
|
|
17023
|
+
if (!phaseText) throw new Error("Could not decrypt focus phase progress. Sync before sending again.");
|
|
17024
|
+
focusPhaseState = JSON.parse(phaseText);
|
|
17025
|
+
}
|
|
17026
|
+
const encryptedFocusId = chat.details.encrypted_active_focus_id;
|
|
17027
|
+
if (typeof encryptedFocusId === "string" && encryptedFocusId) {
|
|
17028
|
+
activeFocusId = await decryptWithAesGcmCombined(encryptedFocusId, chatKeyBytes);
|
|
17029
|
+
if (!activeFocusId) {
|
|
17030
|
+
throw new Error("Could not decrypt the chat's active focus. Sync before sending again.");
|
|
17031
|
+
}
|
|
16805
17032
|
}
|
|
16806
17033
|
}
|
|
16807
17034
|
}
|
|
17035
|
+
if (!chatKeyBytes || !encryptedChatKey) {
|
|
17036
|
+
throw new Error(`Encrypted chat key not found for chat '${chatId}'. Sync and try again.`);
|
|
17037
|
+
}
|
|
16808
17038
|
}
|
|
16809
|
-
|
|
16810
|
-
throw new Error(`Encrypted chat key not found for chat '${chatId}'. Sync and try again.`);
|
|
16811
|
-
}
|
|
17039
|
+
traceSendTiming("key_ready");
|
|
16812
17040
|
}
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
|
|
16822
|
-
|
|
16823
|
-
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
16828
|
-
|
|
16829
|
-
if (frame.chat_id === chatId && frame.user_message_id === messageId2 && typeof frame.project_id === "string")
|
|
16830
|
-
projectAuthoringAvailable = { chat_id: chatId, project_id: frame.project_id, user_message_id: messageId2 };
|
|
16831
|
-
});
|
|
16832
|
-
const projectFocusCountdown = !params.incognito ? registerProjectFocusCountdown({
|
|
16833
|
-
client: this,
|
|
16834
|
-
ws,
|
|
16835
|
-
chatId,
|
|
16836
|
-
teamId,
|
|
16837
|
-
onPending: params.onProjectFocusPending,
|
|
16838
|
-
onError: (error) => {
|
|
16839
|
-
focusPersistenceError = error;
|
|
16840
|
-
}
|
|
16841
|
-
}) : null;
|
|
16842
|
-
ws.onMessageType("focus_mode_activated", (event) => {
|
|
16843
|
-
if (event.chat_id !== chatId || !event.focus_id || !chatKeyBytes || params.incognito) return;
|
|
16844
|
-
const focusId = event.focus_id;
|
|
16845
|
-
if (!focusId.startsWith("project-")) delete focusPhaseState[focusId];
|
|
16846
|
-
const key = chatKeyBytes;
|
|
16847
|
-
focusPersistence = focusPersistence.then(async () => {
|
|
16848
|
-
const encryptedFocusId = await encryptWithAesGcmCombined(focusId, key);
|
|
16849
|
-
await ws.sendAsync("update_encrypted_active_focus_id", {
|
|
16850
|
-
chat_id: chatId,
|
|
16851
|
-
encrypted_active_focus_id: encryptedFocusId
|
|
16852
|
-
});
|
|
16853
|
-
}).catch((error) => {
|
|
16854
|
-
focusPersistenceError = error;
|
|
17041
|
+
let focusPersistence = Promise.resolve();
|
|
17042
|
+
let focusPersistenceError = null;
|
|
17043
|
+
const chatContextEvents = !params.incognito && chatKeyBytes ? registerChatContextEvents({
|
|
17044
|
+
ws,
|
|
17045
|
+
chatId,
|
|
17046
|
+
chatKey: chatKeyBytes,
|
|
17047
|
+
existingMessageIds: savedPrivacyHistory?.messages.map(canonicalSavedChatMessageId) ?? [],
|
|
17048
|
+
previousRulesSetKey: savedPrivacyHistory?.messages.filter((message) => message.role === "system").map((message) => parseChatContextContent(message.content)).filter((event) => event?.type === "rules_loaded").at(-1)?.set_key,
|
|
17049
|
+
onApplied: params.onChatContextApplied
|
|
17050
|
+
}) : null;
|
|
17051
|
+
let projectAuthoringAvailable = null;
|
|
17052
|
+
if (!params.incognito) ws.onMessageType("project_authoring_available", (value) => {
|
|
17053
|
+
if (!value || typeof value !== "object") return;
|
|
17054
|
+
const frame = value;
|
|
17055
|
+
if (frame.chat_id === chatId && frame.user_message_id === messageId2 && typeof frame.project_id === "string")
|
|
17056
|
+
projectAuthoringAvailable = { chat_id: chatId, project_id: frame.project_id, user_message_id: messageId2 };
|
|
16855
17057
|
});
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16859
|
-
|
|
16860
|
-
|
|
16861
|
-
|
|
16862
|
-
|
|
16863
|
-
|
|
16864
|
-
throw new Error("Invalid focus phase state received");
|
|
16865
|
-
}
|
|
16866
|
-
if (focusPhaseState[focusId]?.run_id === phase.run_id && focusPhaseState[focusId].version > phase.version) states2[focusId] = focusPhaseState[focusId];
|
|
17058
|
+
const projectFocusCountdown = !params.incognito ? registerProjectFocusCountdown({
|
|
17059
|
+
client: this,
|
|
17060
|
+
ws,
|
|
17061
|
+
chatId,
|
|
17062
|
+
teamId,
|
|
17063
|
+
onPending: params.onProjectFocusPending,
|
|
17064
|
+
onError: (error) => {
|
|
17065
|
+
focusPersistenceError = error;
|
|
16867
17066
|
}
|
|
16868
|
-
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
|
|
16872
|
-
|
|
16873
|
-
|
|
16874
|
-
)
|
|
16875
|
-
|
|
16876
|
-
|
|
16877
|
-
...teamId ? { team_id: teamId } : {},
|
|
16878
|
-
encrypted_focus_phase_state: encryptedState
|
|
16879
|
-
});
|
|
16880
|
-
await persisted;
|
|
16881
|
-
const eventIds = /* @__PURE__ */ new Set();
|
|
16882
|
-
for (const phase of Object.values(states2)) for (const transition of phase.transitions) {
|
|
16883
|
-
if (transition.chat_id !== chatId || transition.focus_id !== phase.focus_id || !transition.event_id || eventIds.has(transition.event_id)) continue;
|
|
16884
|
-
eventIds.add(transition.event_id);
|
|
16885
|
-
const content = JSON.stringify(transition);
|
|
16886
|
-
await ws.sendAsync("chat_system_message_added", {
|
|
17067
|
+
}) : null;
|
|
17068
|
+
ws.onMessageType("focus_mode_activated", (event) => {
|
|
17069
|
+
if (event.chat_id !== chatId || !event.focus_id || !chatKeyBytes || params.incognito) return;
|
|
17070
|
+
const focusId = event.focus_id;
|
|
17071
|
+
if (!focusId.startsWith("project-")) delete focusPhaseState[focusId];
|
|
17072
|
+
const key = chatKeyBytes;
|
|
17073
|
+
focusPersistence = focusPersistence.then(async () => {
|
|
17074
|
+
const encryptedFocusId = await encryptWithAesGcmCombined(focusId, key);
|
|
17075
|
+
await ws.sendAsync("update_encrypted_active_focus_id", {
|
|
16887
17076
|
chat_id: chatId,
|
|
16888
|
-
|
|
16889
|
-
message_id: transition.event_id,
|
|
16890
|
-
role: "system",
|
|
16891
|
-
status: "synced",
|
|
16892
|
-
encrypted_content: await encryptWithAesGcmCombined(content, key),
|
|
16893
|
-
created_at: transition.created_at
|
|
16894
|
-
}
|
|
17077
|
+
encrypted_active_focus_id: encryptedFocusId
|
|
16895
17078
|
});
|
|
16896
|
-
}
|
|
16897
|
-
|
|
16898
|
-
|
|
16899
|
-
focusPersistenceError = error;
|
|
17079
|
+
}).catch((error) => {
|
|
17080
|
+
focusPersistenceError = error;
|
|
17081
|
+
});
|
|
16900
17082
|
});
|
|
16901
|
-
|
|
16902
|
-
|
|
16903
|
-
|
|
16904
|
-
|
|
16905
|
-
|
|
16906
|
-
|
|
16907
|
-
|
|
16908
|
-
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
17083
|
+
ws.onMessageType("focus_phases_updated", (event) => {
|
|
17084
|
+
if (event.chat_id !== chatId || !event.states || !chatKeyBytes || params.incognito) return;
|
|
17085
|
+
const key = chatKeyBytes;
|
|
17086
|
+
const states2 = event.states;
|
|
17087
|
+
focusPersistence = focusPersistence.then(async () => {
|
|
17088
|
+
for (const [focusId, phase] of Object.entries(states2)) {
|
|
17089
|
+
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) {
|
|
17090
|
+
throw new Error("Invalid focus phase state received");
|
|
17091
|
+
}
|
|
17092
|
+
if (focusPhaseState[focusId]?.run_id === phase.run_id && focusPhaseState[focusId].version > phase.version) states2[focusId] = focusPhaseState[focusId];
|
|
17093
|
+
}
|
|
17094
|
+
focusPhaseState = states2;
|
|
17095
|
+
const encryptedState = await encryptWithAesGcmCombined(JSON.stringify(states2), key);
|
|
17096
|
+
const persisted = ws.waitForMessage(
|
|
17097
|
+
"encrypted_metadata_stored",
|
|
17098
|
+
(payload) => payload.chat_id === chatId,
|
|
17099
|
+
2e4
|
|
17100
|
+
);
|
|
17101
|
+
await ws.sendAsync("encrypted_chat_metadata", {
|
|
17102
|
+
chat_id: chatId,
|
|
17103
|
+
...teamId ? { team_id: teamId } : {},
|
|
17104
|
+
encrypted_focus_phase_state: encryptedState
|
|
17105
|
+
});
|
|
17106
|
+
await persisted;
|
|
17107
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
17108
|
+
for (const phase of Object.values(states2)) for (const transition of phase.transitions) {
|
|
17109
|
+
if (transition.chat_id !== chatId || transition.focus_id !== phase.focus_id || !transition.event_id || eventIds.has(transition.event_id)) continue;
|
|
17110
|
+
eventIds.add(transition.event_id);
|
|
17111
|
+
const content = JSON.stringify(transition);
|
|
17112
|
+
await ws.sendAsync("chat_system_message_added", {
|
|
17113
|
+
chat_id: chatId,
|
|
17114
|
+
message: {
|
|
17115
|
+
message_id: transition.event_id,
|
|
17116
|
+
role: "system",
|
|
17117
|
+
status: "synced",
|
|
17118
|
+
encrypted_content: await encryptWithAesGcmCombined(content, key),
|
|
17119
|
+
created_at: transition.created_at
|
|
17120
|
+
}
|
|
17121
|
+
});
|
|
17122
|
+
}
|
|
17123
|
+
clearSyncCache(teamId);
|
|
17124
|
+
}).catch((error) => {
|
|
17125
|
+
focusPersistenceError = error;
|
|
17126
|
+
});
|
|
16914
17127
|
});
|
|
16915
|
-
clientCapabilities
|
|
16916
|
-
|
|
16917
|
-
|
|
16918
|
-
|
|
16919
|
-
|
|
16920
|
-
|
|
16921
|
-
|
|
16922
|
-
|
|
16923
|
-
|
|
16924
|
-
|
|
16925
|
-
|
|
16926
|
-
|
|
16927
|
-
|
|
16928
|
-
|
|
16929
|
-
|
|
16930
|
-
|
|
16931
|
-
sender_name: "User",
|
|
16932
|
-
status: "sent",
|
|
16933
|
-
content: finalMessage,
|
|
16934
|
-
created_at: createdAt,
|
|
16935
|
-
chat_has_title: Boolean(params.chatId)
|
|
16936
|
-
}
|
|
16937
|
-
};
|
|
16938
|
-
const useCombinedAdmission = ws.supportsServerCapability?.("chat_turn_admit_v1") === true;
|
|
16939
|
-
const useDeferredContext = useCombinedAdmission && !params.chatId && !params.projectId && !teamId && ws.supportsServerCapability?.("chat_turn_context_v1") === true;
|
|
16940
|
-
const useSelectionDetailsV2 = useDeferredContext && sdkMintedChatId && ws.supportsServerCapability?.("chat_turn_context_selection_details_v2") === true;
|
|
16941
|
-
const contextRequestId = !params.incognito && shouldWaitForAi && useDeferredContext ? randomUUID9() : null;
|
|
16942
|
-
let pendingContext = null;
|
|
16943
|
-
if (!params.incognito && shouldWaitForAi) {
|
|
16944
|
-
if (contextRequestId) {
|
|
16945
|
-
messagePayload.client_context_pending = true;
|
|
16946
|
-
messagePayload.client_context_request_id = contextRequestId;
|
|
17128
|
+
const clientCapabilities = taskUpdateJobsEnabled ? ["task_update_jobs"] : [];
|
|
17129
|
+
if (!params.incognito && chatKeyBytes) {
|
|
17130
|
+
registerCliProjectFileExecutor({
|
|
17131
|
+
client: this,
|
|
17132
|
+
ws,
|
|
17133
|
+
chatId,
|
|
17134
|
+
chatKey: chatKeyBytes,
|
|
17135
|
+
piiMappings,
|
|
17136
|
+
memories: availableMemories,
|
|
17137
|
+
requestApproval: params.onProjectWriteApproval,
|
|
17138
|
+
requestReadApproval: params.onProjectReadApproval,
|
|
17139
|
+
onHostedVersionCommitted: params.onHostedVersionCommitted
|
|
17140
|
+
});
|
|
17141
|
+
clientCapabilities.push("project_file_jobs");
|
|
17142
|
+
registerRemoteCommandOriginClient({ ws, client: this, chatId, onReview: params.onRemoteCommandReview, onEvent: params.onRemoteCommandEvent });
|
|
17143
|
+
clientCapabilities.push("remote_command_jobs");
|
|
16947
17144
|
}
|
|
16948
|
-
|
|
16949
|
-
|
|
16950
|
-
|
|
16951
|
-
|
|
16952
|
-
|
|
16953
|
-
|
|
16954
|
-
|
|
16955
|
-
|
|
16956
|
-
|
|
16957
|
-
|
|
16958
|
-
|
|
16959
|
-
|
|
16960
|
-
|
|
16961
|
-
|
|
16962
|
-
|
|
16963
|
-
|
|
16964
|
-
|
|
16965
|
-
console.error(`[chat preparation] pre_ws_ms=${preWsMs} memory_ms=${memoryPrepMs} privacy_ms=${privacyPrepMs} auth_ws_ms=${authSocketMs} recovery_ms=${recoveryMs} context_ms=${prepared.elapsedMs} project_candidates_ready_from_send_start_ms=${projectCandidatesReadyFromSendStartMs ?? "pending"}`);
|
|
17145
|
+
const messagePayload = {
|
|
17146
|
+
chat_id: chatId,
|
|
17147
|
+
...teamId ? { team_id: teamId } : {},
|
|
17148
|
+
client_capabilities: clientCapabilities,
|
|
17149
|
+
// Only decrypted current metadata restores focus; history is never authority.
|
|
17150
|
+
active_focus_id: activeFocusId,
|
|
17151
|
+
focus_phase_state: focusPhaseState,
|
|
17152
|
+
is_incognito: Boolean(params.incognito),
|
|
17153
|
+
message: {
|
|
17154
|
+
message_id: messageId2,
|
|
17155
|
+
chat_id: chatId,
|
|
17156
|
+
role: "user",
|
|
17157
|
+
sender_name: "User",
|
|
17158
|
+
status: "sent",
|
|
17159
|
+
content: finalMessage,
|
|
17160
|
+
created_at: createdAt,
|
|
17161
|
+
chat_has_title: Boolean(params.chatId)
|
|
16966
17162
|
}
|
|
16967
|
-
return deferredProjectDirectory ? { ...prepared.value, project_focus_candidates: prepared.value.project_focus_candidates?.filter((candidate) => candidate.auto_selection === true) } : prepared.value;
|
|
16968
|
-
})();
|
|
16969
|
-
void pendingContext.catch(() => {
|
|
16970
|
-
});
|
|
16971
|
-
}
|
|
16972
|
-
if (params.testMockMarker !== void 0) {
|
|
16973
|
-
const { capacityReplayMarker } = await import("./capacityReplayMarker-3NFJNDTZ.js");
|
|
16974
|
-
messagePayload.test_mock_marker = capacityReplayMarker(
|
|
16975
|
-
params.testMockMarker,
|
|
16976
|
-
this.apiUrl,
|
|
16977
|
-
process.env.OPENMATES_CAPACITY_CLIENT_REPLAY === "1"
|
|
16978
|
-
);
|
|
16979
|
-
}
|
|
16980
|
-
if (isNewChat && encryptedChatKey) {
|
|
16981
|
-
messagePayload.encrypted_chat_key = encryptedChatKey;
|
|
16982
|
-
}
|
|
16983
|
-
if (memoryMetadataKeys.length > 0) {
|
|
16984
|
-
messagePayload.app_settings_memories_metadata = memoryMetadataKeys;
|
|
16985
|
-
}
|
|
16986
|
-
if (connectedAccountDirectory) {
|
|
16987
|
-
messagePayload.connected_account_directory = connectedAccountDirectory;
|
|
16988
|
-
}
|
|
16989
|
-
if (connectedAccountTokenRefs.length > 0) {
|
|
16990
|
-
messagePayload.connected_account_token_refs = connectedAccountTokenRefs;
|
|
16991
|
-
}
|
|
16992
|
-
if (params.benchmarkMetadata) {
|
|
16993
|
-
messagePayload.benchmark_metadata = params.benchmarkMetadata;
|
|
16994
|
-
}
|
|
16995
|
-
if (params.learningMode) {
|
|
16996
|
-
messagePayload.learning_mode = {
|
|
16997
|
-
enabled: params.learningMode.enabled,
|
|
16998
|
-
age_group: params.learningMode.ageGroup ?? null
|
|
16999
17163
|
};
|
|
17000
|
-
|
|
17001
|
-
|
|
17002
|
-
const
|
|
17003
|
-
|
|
17004
|
-
|
|
17005
|
-
|
|
17006
|
-
|
|
17007
|
-
|
|
17008
|
-
|
|
17009
|
-
|
|
17010
|
-
|
|
17011
|
-
|
|
17012
|
-
|
|
17013
|
-
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17164
|
+
const useCombinedAdmission = ws.supportsServerCapability?.("chat_turn_admit_v1") === true;
|
|
17165
|
+
const useDeferredContext = useCombinedAdmission && !params.chatId && !params.projectId && !teamId && ws.supportsServerCapability?.("chat_turn_context_v1") === true;
|
|
17166
|
+
const useSelectionDetailsV2 = useDeferredContext && sdkMintedChatId && ws.supportsServerCapability?.("chat_turn_context_selection_details_v2") === true;
|
|
17167
|
+
const contextRequestId = !params.incognito && shouldWaitForAi && useDeferredContext ? randomUUID9() : null;
|
|
17168
|
+
let pendingContext = null;
|
|
17169
|
+
if (!params.incognito && shouldWaitForAi) {
|
|
17170
|
+
if (contextRequestId) {
|
|
17171
|
+
messagePayload.client_context_pending = true;
|
|
17172
|
+
messagePayload.client_context_request_id = contextRequestId;
|
|
17173
|
+
}
|
|
17174
|
+
pendingContext = (async () => {
|
|
17175
|
+
const prefetchedDiscovery2 = followupDiscoveryRead ? await followupDiscoveryRead : void 0;
|
|
17176
|
+
if (followupDiscoveryOwner && !this.preparedChatOwnerCurrent(followupDiscoveryOwner, teamId)) {
|
|
17177
|
+
throw new Error("Chat workspace changed during context discovery.");
|
|
17178
|
+
}
|
|
17179
|
+
const prepared = earlyContext ? await earlyContext : { ok: true, value: await withHttpAbortSignal(earlyContextAbort.signal, () => prepareCliJevContext(this, chatId, contextScope, suppliedContext, finalMessage, {
|
|
17180
|
+
prefetchedDiscovery: prefetchedDiscovery2,
|
|
17181
|
+
sdkMintedChatId,
|
|
17182
|
+
onRouting: publishRouting,
|
|
17183
|
+
verifiedProjectCandidates: verifiedProjectCandidates ?? void 0,
|
|
17184
|
+
usePersonalRoutingDirectory,
|
|
17185
|
+
onProjectRouting: publishProjectRoutingTimed,
|
|
17186
|
+
onProjectRoutingError: rejectProjectRouting
|
|
17187
|
+
})), elapsedMs: Date.now() - contextStartedAt };
|
|
17188
|
+
if (!prepared.ok) throw prepared.error;
|
|
17189
|
+
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17190
|
+
if (process.env.OPENMATES_DEBUG === "1") {
|
|
17191
|
+
console.error(`[chat preparation] pre_ws_ms=${preWsMs} memory_ms=${memoryPrepMs} privacy_ms=${privacyPrepMs} auth_ws_ms=${authSocketMs} recovery_ms=${recoveryMs} context_ms=${prepared.elapsedMs} project_candidates_ready_from_send_start_ms=${projectCandidatesReadyFromSendStartMs ?? "pending"}`);
|
|
17192
|
+
}
|
|
17193
|
+
return deferredProjectDirectory ? { ...prepared.value, project_focus_candidates: prepared.value.project_focus_candidates?.filter((candidate) => candidate.auto_selection === true) } : prepared.value;
|
|
17194
|
+
})();
|
|
17195
|
+
void pendingContext.catch(() => {
|
|
17196
|
+
});
|
|
17031
17197
|
}
|
|
17032
|
-
if (
|
|
17033
|
-
|
|
17198
|
+
if (params.testMockMarker !== void 0) {
|
|
17199
|
+
const { capacityReplayMarker } = await import("./capacityReplayMarker-3NFJNDTZ.js");
|
|
17200
|
+
messagePayload.test_mock_marker = capacityReplayMarker(
|
|
17201
|
+
params.testMockMarker,
|
|
17202
|
+
this.apiUrl,
|
|
17203
|
+
process.env.OPENMATES_CAPACITY_CLIENT_REPLAY === "1"
|
|
17204
|
+
);
|
|
17034
17205
|
}
|
|
17035
|
-
if (
|
|
17036
|
-
|
|
17206
|
+
if (isNewChat && encryptedChatKey) {
|
|
17207
|
+
messagePayload.encrypted_chat_key = encryptedChatKey;
|
|
17037
17208
|
}
|
|
17038
|
-
|
|
17039
|
-
|
|
17040
|
-
throw new Error("Prepared message embed ID is invalid.");
|
|
17209
|
+
if (memoryMetadataKeys.length > 0) {
|
|
17210
|
+
messagePayload.app_settings_memories_metadata = memoryMetadataKeys;
|
|
17041
17211
|
}
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
const selectedIds = preparedIds.slice(offset, offset + 20);
|
|
17045
|
-
if (Buffer.byteLength(JSON.stringify({ embed_ids: selectedIds }), "utf8") > 4096) {
|
|
17046
|
-
throw new Error("Message embed availability request exceeds its byte limit.");
|
|
17047
|
-
}
|
|
17048
|
-
const path2 = this.appendTeamQuery(
|
|
17049
|
-
`/v1/embeds/chats/${encodeURIComponent(chatId)}/references/availability`,
|
|
17050
|
-
{ teamId }
|
|
17051
|
-
);
|
|
17052
|
-
const availability = await this.http.post(path2, { embed_ids: selectedIds }, this.getCliRequestHeaders());
|
|
17053
|
-
if (!availability.ok) {
|
|
17054
|
-
throw new Error(`Message embed availability lookup failed with HTTP ${availability.status}.`);
|
|
17055
|
-
}
|
|
17056
|
-
const page = classifyMessageEmbedAvailability(selectedIds, availability.data.results);
|
|
17057
|
-
for (const id of page.ready) canonicalReady.add(id);
|
|
17212
|
+
if (connectedAccountDirectory) {
|
|
17213
|
+
messagePayload.connected_account_directory = connectedAccountDirectory;
|
|
17058
17214
|
}
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
17062
|
-
|
|
17063
|
-
|
|
17064
|
-
|
|
17065
|
-
|
|
17066
|
-
|
|
17067
|
-
|
|
17068
|
-
|
|
17215
|
+
if (connectedAccountTokenRefs.length > 0) {
|
|
17216
|
+
messagePayload.connected_account_token_refs = connectedAccountTokenRefs;
|
|
17217
|
+
}
|
|
17218
|
+
if (params.benchmarkMetadata) {
|
|
17219
|
+
messagePayload.benchmark_metadata = params.benchmarkMetadata;
|
|
17220
|
+
}
|
|
17221
|
+
if (params.learningMode) {
|
|
17222
|
+
messagePayload.learning_mode = {
|
|
17223
|
+
enabled: params.learningMode.enabled,
|
|
17224
|
+
age_group: params.learningMode.ageGroup ?? null
|
|
17225
|
+
};
|
|
17226
|
+
}
|
|
17227
|
+
if (params.incognito) {
|
|
17228
|
+
const providedHistory = (params.messageHistory ?? []).map(
|
|
17229
|
+
(historyMessage) => buildInferenceHistoryMessage(historyMessage, historyMessage.chat_id ?? chatId)
|
|
17230
|
+
);
|
|
17231
|
+
messagePayload.message_history = [...providedHistory, {
|
|
17232
|
+
message_id: messageId2,
|
|
17233
|
+
chat_id: chatId,
|
|
17234
|
+
role: "user",
|
|
17235
|
+
sender_name: "User",
|
|
17236
|
+
content: finalMessage,
|
|
17237
|
+
created_at: createdAt
|
|
17238
|
+
}];
|
|
17239
|
+
} else if (messageHistoryForRequest && messageHistoryForRequest.length > 0) {
|
|
17240
|
+
messagePayload.message_history = messageHistoryForRequest.map(
|
|
17241
|
+
(historyMessage) => buildInferenceHistoryMessage(historyMessage, chatId)
|
|
17069
17242
|
);
|
|
17070
|
-
if (!encrypted?.encrypted_content || !encrypted.encrypted_type || encrypted.embed_keys.length !== 2) {
|
|
17071
|
-
throw new Error(`Prepared message embed ${embed.embedId} is missing required ciphertext or key wrappers.`);
|
|
17072
|
-
}
|
|
17073
|
-
encryptedEmbeds.push(encrypted);
|
|
17074
17243
|
}
|
|
17075
|
-
|
|
17076
|
-
|
|
17077
|
-
|
|
17078
|
-
|
|
17079
|
-
|
|
17080
|
-
|
|
17081
|
-
|
|
17082
|
-
|
|
17083
|
-
|
|
17084
|
-
|
|
17085
|
-
|
|
17086
|
-
|
|
17087
|
-
|
|
17088
|
-
|
|
17089
|
-
|
|
17090
|
-
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
|
|
17094
|
-
|
|
17095
|
-
|
|
17096
|
-
|
|
17097
|
-
|
|
17098
|
-
|
|
17099
|
-
|
|
17100
|
-
|
|
17101
|
-
|
|
17102
|
-
|
|
17103
|
-
|
|
17104
|
-
const encryptPayloadStartedAt = performance.now();
|
|
17105
|
-
const [encryptedContent, encryptedSenderName, encryptedPiiMappings] = await Promise.all([
|
|
17106
|
-
encryptWithAesGcmCombined(finalMessage, chatKeyBytes),
|
|
17107
|
-
encryptWithAesGcmCombined(teamSenderName, chatKeyBytes),
|
|
17108
|
-
piiMappings.length ? encryptWithAesGcmCombined(JSON.stringify(piiMappings), chatKeyBytes) : null
|
|
17109
|
-
]);
|
|
17110
|
-
const encryptedUserMessage = {
|
|
17111
|
-
client_message_id: messageId2,
|
|
17112
|
-
chat_id: chatId,
|
|
17113
|
-
encrypted_content: encryptedContent,
|
|
17114
|
-
role: "user",
|
|
17115
|
-
created_at: createdAt,
|
|
17116
|
-
updated_at: createdAt
|
|
17117
|
-
};
|
|
17118
|
-
encryptedUserMessage.encrypted_sender_name = encryptedSenderName;
|
|
17119
|
-
if (encryptedPiiMappings) encryptedUserMessage.encrypted_pii_mappings = encryptedPiiMappings;
|
|
17120
|
-
Object.assign(messagePayload, {
|
|
17121
|
-
turn_id: turnId,
|
|
17122
|
-
recovery_public_key: recoveryKeypair.publicKey,
|
|
17123
|
-
chat_key_version: chatKeyVersion
|
|
17124
|
-
});
|
|
17125
|
-
let preflightInferenceRequest = messagePayload;
|
|
17126
|
-
if (teamId) {
|
|
17127
|
-
const teamInferenceHistory = [
|
|
17128
|
-
...(messageHistoryForRequest ?? []).map((historyMessage) => ({
|
|
17129
|
-
role: historyMessage.role,
|
|
17130
|
-
content: historyMessage.content,
|
|
17131
|
-
category: historyMessage.category ?? null,
|
|
17132
|
-
sender_name: historyMessage.sender_name ?? historyMessage.role,
|
|
17133
|
-
created_at: historyMessage.created_at
|
|
17134
|
-
})),
|
|
17135
|
-
{
|
|
17136
|
-
role: "user",
|
|
17137
|
-
content: finalMessage,
|
|
17138
|
-
category: null,
|
|
17139
|
-
sender_name: teamSenderName,
|
|
17140
|
-
created_at: createdAt
|
|
17244
|
+
if (params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
17245
|
+
messagePayload.embeds = params.preparedEmbeds.map((embed) => ({
|
|
17246
|
+
embed_id: embed.embedId,
|
|
17247
|
+
type: embed.type,
|
|
17248
|
+
content: embed.content,
|
|
17249
|
+
status: embed.status,
|
|
17250
|
+
text_preview: embed.textPreview
|
|
17251
|
+
}));
|
|
17252
|
+
}
|
|
17253
|
+
const encryptedEmbeds = [...params.encryptedEmbeds ?? []];
|
|
17254
|
+
if (!params.incognito && params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
17255
|
+
if (!ownerId2) {
|
|
17256
|
+
throw new Error("Authenticated user identity is required to encrypt embeds.");
|
|
17257
|
+
}
|
|
17258
|
+
if (!chatKeyBytes) {
|
|
17259
|
+
throw new Error("Saved chat key is required to encrypt every prepared embed.");
|
|
17260
|
+
}
|
|
17261
|
+
if (new Set(params.preparedEmbeds.map((embed) => embed.embedId)).size !== params.preparedEmbeds.length) {
|
|
17262
|
+
throw new Error("Prepared message embed IDs must be unique.");
|
|
17263
|
+
}
|
|
17264
|
+
const preparedIds = params.preparedEmbeds.map((embed) => embed.embedId);
|
|
17265
|
+
if (preparedIds.some((id) => !id || id.length > 128)) {
|
|
17266
|
+
throw new Error("Prepared message embed ID is invalid.");
|
|
17267
|
+
}
|
|
17268
|
+
const canonicalReady = /* @__PURE__ */ new Set();
|
|
17269
|
+
for (let offset = 0; offset < preparedIds.length; offset += 20) {
|
|
17270
|
+
const selectedIds = preparedIds.slice(offset, offset + 20);
|
|
17271
|
+
if (Buffer.byteLength(JSON.stringify({ embed_ids: selectedIds }), "utf8") > 4096) {
|
|
17272
|
+
throw new Error("Message embed availability request exceeds its byte limit.");
|
|
17141
17273
|
}
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17274
|
+
const path2 = this.appendTeamQuery(
|
|
17275
|
+
`/v1/embeds/chats/${encodeURIComponent(chatId)}/references/availability`,
|
|
17276
|
+
{ teamId }
|
|
17277
|
+
);
|
|
17278
|
+
const availability = await this.http.post(path2, { embed_ids: selectedIds }, this.getCliRequestHeaders());
|
|
17279
|
+
if (!availability.ok) {
|
|
17280
|
+
throw new Error(`Message embed availability lookup failed with HTTP ${availability.status}.`);
|
|
17281
|
+
}
|
|
17282
|
+
const page = classifyMessageEmbedAvailability(selectedIds, availability.data.results);
|
|
17283
|
+
for (const id of page.ready) canonicalReady.add(id);
|
|
17284
|
+
}
|
|
17285
|
+
const masterKey = this.getMasterKeyBytes();
|
|
17286
|
+
for (const embed of params.preparedEmbeds) {
|
|
17287
|
+
if (canonicalReady.has(embed.embedId)) continue;
|
|
17288
|
+
const encrypted = await encryptEmbed(
|
|
17289
|
+
embed,
|
|
17290
|
+
masterKey,
|
|
17291
|
+
chatKeyBytes,
|
|
17292
|
+
chatId,
|
|
17293
|
+
messageId2,
|
|
17294
|
+
ownerId2
|
|
17295
|
+
);
|
|
17296
|
+
if (!encrypted?.encrypted_content || !encrypted.encrypted_type || encrypted.embed_keys.length !== 2) {
|
|
17297
|
+
throw new Error(`Prepared message embed ${embed.embedId} is missing required ciphertext or key wrappers.`);
|
|
17298
|
+
}
|
|
17299
|
+
encryptedEmbeds.push(encrypted);
|
|
17152
17300
|
}
|
|
17153
17301
|
}
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
recovery_public_key: recoveryKeypair.publicKey,
|
|
17163
|
-
expected_messages_v: baselineMessagesV,
|
|
17164
|
-
encrypted_user_message: encryptedUserMessage,
|
|
17165
|
-
inference_request: preflightInferenceRequest,
|
|
17166
|
-
...conditionalPrivacyReceipt ? {
|
|
17167
|
-
privacy_receipt_id: conditionalPrivacyReceipt.receiptId,
|
|
17168
|
-
privacy_generation: conditionalPrivacyReceipt.generation
|
|
17169
|
-
} : {}
|
|
17302
|
+
if (!params.incognito && encryptedEmbeds.length > 0) {
|
|
17303
|
+
if (!ownerId2) throw new Error("Authenticated owner identity is required for encrypted message embeds.");
|
|
17304
|
+
assertCompleteEncryptedEmbedBundle(encryptedEmbeds, { chatId, messageId: messageId2, ownerId: ownerId2 });
|
|
17305
|
+
}
|
|
17306
|
+
if (encryptedEmbeds.length > 0) {
|
|
17307
|
+
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
17308
|
+
}
|
|
17309
|
+
let dispatchHandlersReady = () => {
|
|
17170
17310
|
};
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
|
|
17176
|
-
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17188
|
-
|
|
17189
|
-
|
|
17311
|
+
const handlersReady = new Promise((resolve20) => {
|
|
17312
|
+
dispatchHandlersReady = resolve20;
|
|
17313
|
+
});
|
|
17314
|
+
let admissionPayload = null;
|
|
17315
|
+
let precollectedResponse = params.precollectResponse && params.incognito && shouldWaitForAi ? ws.collectAiResponse(messageId2, chatId, { onStream: params.onStream, timeoutMs: params.responseTimeoutMs }) : null;
|
|
17316
|
+
if (!params.incognito && chatKeyBytes && encryptedChatKey) {
|
|
17317
|
+
traceSendTiming("preflight_begin");
|
|
17318
|
+
const protocolVersion = 1;
|
|
17319
|
+
const chatKeyVersion = 1;
|
|
17320
|
+
const turnId = privacyTurnId;
|
|
17321
|
+
savedTurnId = turnId;
|
|
17322
|
+
catalogTurnId = turnId;
|
|
17323
|
+
terminalExpectedMessagesV = baselineMessagesV + 1;
|
|
17324
|
+
const recoveryKeypairStartedAt = performance.now();
|
|
17325
|
+
const recoveryKeypair = independentRecoveryKeypair ? await independentRecoveryKeypair : await deriveChatCompletionRecoveryKeypair(
|
|
17326
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
17327
|
+
chatId,
|
|
17328
|
+
chatKeyVersion
|
|
17329
|
+
);
|
|
17330
|
+
recoveryKeypairMs = Math.round(performance.now() - recoveryKeypairStartedAt);
|
|
17331
|
+
const encryptPayloadStartedAt = performance.now();
|
|
17332
|
+
const [encryptedContent, encryptedSenderName, encryptedPiiMappings] = await Promise.all([
|
|
17333
|
+
encryptWithAesGcmCombined(finalMessage, chatKeyBytes),
|
|
17334
|
+
encryptWithAesGcmCombined(teamSenderName, chatKeyBytes),
|
|
17335
|
+
piiMappings.length ? encryptWithAesGcmCombined(JSON.stringify(piiMappings), chatKeyBytes) : null
|
|
17336
|
+
]);
|
|
17337
|
+
const encryptedUserMessage = {
|
|
17338
|
+
client_message_id: messageId2,
|
|
17339
|
+
chat_id: chatId,
|
|
17340
|
+
encrypted_content: encryptedContent,
|
|
17341
|
+
role: "user",
|
|
17190
17342
|
created_at: createdAt,
|
|
17191
17343
|
updated_at: createdAt
|
|
17192
17344
|
};
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17345
|
+
encryptedUserMessage.encrypted_sender_name = encryptedSenderName;
|
|
17346
|
+
if (encryptedPiiMappings) encryptedUserMessage.encrypted_pii_mappings = encryptedPiiMappings;
|
|
17347
|
+
Object.assign(messagePayload, {
|
|
17348
|
+
turn_id: turnId,
|
|
17349
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
17350
|
+
chat_key_version: chatKeyVersion
|
|
17351
|
+
});
|
|
17352
|
+
let preflightInferenceRequest = messagePayload;
|
|
17196
17353
|
if (teamId) {
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17354
|
+
const teamInferenceHistory = [
|
|
17355
|
+
...(messageHistoryForRequest ?? []).map((historyMessage) => ({
|
|
17356
|
+
role: historyMessage.role,
|
|
17357
|
+
content: historyMessage.content,
|
|
17358
|
+
category: historyMessage.category ?? null,
|
|
17359
|
+
sender_name: historyMessage.sender_name ?? historyMessage.role,
|
|
17360
|
+
created_at: historyMessage.created_at
|
|
17361
|
+
})),
|
|
17362
|
+
{
|
|
17363
|
+
role: "user",
|
|
17364
|
+
content: finalMessage,
|
|
17365
|
+
category: null,
|
|
17366
|
+
sender_name: teamSenderName,
|
|
17367
|
+
created_at: createdAt
|
|
17368
|
+
}
|
|
17369
|
+
];
|
|
17370
|
+
const teamMessageEnvelope = { ...encryptedUserMessage, message_id: messageId2 };
|
|
17371
|
+
delete messagePayload.message_history;
|
|
17372
|
+
messagePayload.message = teamMessageEnvelope;
|
|
17373
|
+
if (shouldWaitForAi) {
|
|
17374
|
+
messagePayload.team_ai_invocation = { history: teamInferenceHistory };
|
|
17375
|
+
preflightInferenceRequest = {
|
|
17376
|
+
...messagePayload,
|
|
17377
|
+
message_history: teamInferenceHistory
|
|
17378
|
+
};
|
|
17379
|
+
}
|
|
17380
|
+
}
|
|
17381
|
+
const preflightPayload = {
|
|
17382
|
+
protocol_version: protocolVersion,
|
|
17383
|
+
chat_id: chatId,
|
|
17384
|
+
...teamId ? { team_id: teamId } : {},
|
|
17385
|
+
turn_id: turnId,
|
|
17386
|
+
message_id: messageId2,
|
|
17387
|
+
chat_key_version: chatKeyVersion,
|
|
17388
|
+
encrypted_chat_key: encryptedChatKey,
|
|
17389
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
17390
|
+
expected_messages_v: baselineMessagesV,
|
|
17391
|
+
encrypted_user_message: encryptedUserMessage,
|
|
17392
|
+
inference_request: preflightInferenceRequest,
|
|
17393
|
+
...conditionalPrivacyReceipt ? {
|
|
17394
|
+
privacy_receipt_id: conditionalPrivacyReceipt.receiptId,
|
|
17395
|
+
privacy_generation: conditionalPrivacyReceipt.generation
|
|
17396
|
+
} : {}
|
|
17397
|
+
};
|
|
17398
|
+
if (isNewChat) {
|
|
17399
|
+
if (!chatSlugLookupKey) {
|
|
17400
|
+
throw new Error("Encrypted chat slug lookup key was not initialized.");
|
|
17401
|
+
}
|
|
17402
|
+
const initialTitle = teamId && !shouldWaitForAi ? teamChatTitleFromMessage(params.message) : "";
|
|
17403
|
+
const slugMetadata = await buildEncryptedObjectSlugMetadata({
|
|
17404
|
+
value: selectNewChatSlugValue({
|
|
17405
|
+
message: finalMessage,
|
|
17406
|
+
chatId,
|
|
17407
|
+
explicitSlug: params.slug
|
|
17408
|
+
}),
|
|
17409
|
+
encryptionKey: chatKeyBytes,
|
|
17410
|
+
lookupKey: chatSlugLookupKey
|
|
17203
17411
|
});
|
|
17412
|
+
preflightPayload.encrypted_chat_metadata = {
|
|
17413
|
+
encrypted_title: await encryptWithAesGcmCombined(initialTitle, chatKeyBytes),
|
|
17414
|
+
encrypted_slug: slugMetadata.encrypted_slug,
|
|
17415
|
+
slug_lookup_hash: slugMetadata.slug_lookup_hash,
|
|
17416
|
+
encrypted_chat_key: encryptedChatKey,
|
|
17417
|
+
created_at: createdAt,
|
|
17418
|
+
updated_at: createdAt
|
|
17419
|
+
};
|
|
17420
|
+
}
|
|
17421
|
+
encryptPayloadMs = Math.round(performance.now() - encryptPayloadStartedAt);
|
|
17422
|
+
try {
|
|
17423
|
+
if (teamId) {
|
|
17424
|
+
await stageCliTeamNotificationPreview(ws, {
|
|
17425
|
+
teamId,
|
|
17426
|
+
chatId,
|
|
17427
|
+
messageId: messageId2,
|
|
17428
|
+
content: finalMessage,
|
|
17429
|
+
title: notificationTitle
|
|
17430
|
+
});
|
|
17431
|
+
}
|
|
17432
|
+
if (params.projectId) {
|
|
17433
|
+
await prepareCliProjectFocusForPreflight({
|
|
17434
|
+
ws,
|
|
17435
|
+
chatId,
|
|
17436
|
+
teamId,
|
|
17437
|
+
isNewChat,
|
|
17438
|
+
encryptedChatKey,
|
|
17439
|
+
createdAt,
|
|
17440
|
+
activateFocus: () => activateCliProjectFocus(this, params.projectId, chatId, teamId)
|
|
17441
|
+
});
|
|
17442
|
+
const focusedContext = await prepareCliJevContext(
|
|
17443
|
+
this,
|
|
17444
|
+
chatId,
|
|
17445
|
+
{ teamId, personal: !teamId },
|
|
17446
|
+
params.jevContext,
|
|
17447
|
+
finalMessage
|
|
17448
|
+
);
|
|
17449
|
+
pendingContext = Promise.resolve(focusedContext);
|
|
17450
|
+
}
|
|
17451
|
+
admissionPayload = preflightPayload;
|
|
17452
|
+
traceSendTiming("preflight_ready");
|
|
17453
|
+
} catch (error) {
|
|
17454
|
+
ws.close();
|
|
17455
|
+
throw error;
|
|
17204
17456
|
}
|
|
17205
|
-
|
|
17206
|
-
|
|
17457
|
+
}
|
|
17458
|
+
const initialHeaderOwner = this.preparedChatOwner(this.requireSession(), teamId);
|
|
17459
|
+
const startInitialHeader = () => {
|
|
17460
|
+
if (!initialAdmissionDurable || !initialHeaderNotice || !chatKeyBytes || !isNewChat || params.incognito || !savedTurnId) return;
|
|
17461
|
+
const notice = { ...initialHeaderNotice }, key = chatKeyBytes, turn = savedTurnId;
|
|
17462
|
+
const titleChanged = !!notice.chatTitle && notice.chatTitle !== initialTitleScheduled;
|
|
17463
|
+
const iconChanged = !!notice.chatIcon && notice.chatIcon !== initialIconScheduled;
|
|
17464
|
+
if (!titleChanged && !iconChanged) return;
|
|
17465
|
+
initialTitleScheduled = notice.chatTitle ?? initialTitleScheduled;
|
|
17466
|
+
initialIconScheduled = notice.chatIcon ?? initialIconScheduled;
|
|
17467
|
+
const current2 = () => !initialHeaderStopped && ws.isOpen() && this.preparedChatOwnerCurrent(initialHeaderOwner, teamId) && this.activeSubmittedChatTurns.get(chatId) === turn;
|
|
17468
|
+
initialHeaderWork = (initialHeaderWork ?? Promise.resolve()).then(async () => {
|
|
17469
|
+
if (!current2()) return;
|
|
17470
|
+
if (titleChanged) await this.persistPostProcessingMetadata({
|
|
17207
17471
|
ws,
|
|
17208
17472
|
chatId,
|
|
17209
17473
|
teamId,
|
|
17210
|
-
|
|
17474
|
+
chatKeyBytes: key,
|
|
17211
17475
|
encryptedChatKey,
|
|
17212
|
-
|
|
17213
|
-
|
|
17476
|
+
followUpSuggestions: [],
|
|
17477
|
+
suggestedFollowupMessage: null,
|
|
17478
|
+
newChatSuggestions: [],
|
|
17479
|
+
chatSummary: null,
|
|
17480
|
+
chatTags: [],
|
|
17481
|
+
updatedChatTitle: notice.chatTitle ?? null,
|
|
17482
|
+
sourceTitleVersion: notice.sourceTitleVersion ?? null,
|
|
17483
|
+
sourceMetadataVersion: notice.sourceMetadataVersion ?? null,
|
|
17484
|
+
unversionedGeneratedTitle: notice.sourceTitleVersion === void 0 && notice.sourceMetadataVersion === void 0,
|
|
17485
|
+
ownerCurrent: current2
|
|
17214
17486
|
});
|
|
17215
|
-
|
|
17216
|
-
|
|
17217
|
-
|
|
17218
|
-
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17487
|
+
if (!current2()) return;
|
|
17488
|
+
if (iconChanged || notice.category) {
|
|
17489
|
+
const [encryptedIcon, encryptedCategory] = await Promise.all([
|
|
17490
|
+
notice.chatIcon ? encryptWithAesGcmCombined(notice.chatIcon, key) : null,
|
|
17491
|
+
notice.category ? encryptWithAesGcmCombined(notice.category, key) : null
|
|
17492
|
+
]);
|
|
17493
|
+
if (!current2()) return;
|
|
17494
|
+
const stored = ws.waitForMessage("encrypted_metadata_stored", (value) => value.chat_id === chatId, 2e4);
|
|
17495
|
+
void stored.catch(() => {
|
|
17496
|
+
});
|
|
17497
|
+
await ws.sendAsync("encrypted_chat_metadata", {
|
|
17498
|
+
chat_id: chatId,
|
|
17499
|
+
...teamId ? { team_id: teamId } : {},
|
|
17500
|
+
encrypted_chat_key: encryptedChatKey,
|
|
17501
|
+
...encryptedIcon ? { encrypted_icon: encryptedIcon } : {},
|
|
17502
|
+
...encryptedCategory ? { encrypted_chat_category: encryptedCategory } : {}
|
|
17503
|
+
});
|
|
17504
|
+
await stored;
|
|
17505
|
+
}
|
|
17506
|
+
if (current2()) clearSyncCache(teamId);
|
|
17507
|
+
}).catch(() => {
|
|
17508
|
+
});
|
|
17509
|
+
};
|
|
17510
|
+
const onTurnStream = (event) => {
|
|
17511
|
+
if (isNewChat && event.kind === "typing" && (event.chatTitle || event.chatIcon) && event.chatId === chatId && event.userMessageId === messageId2 && event.turnId === savedTurnId && event.taskId && (!initialHeaderNotice?.taskId || event.taskId === initialHeaderNotice.taskId)) {
|
|
17512
|
+
initialHeaderNotice = { ...initialHeaderNotice, ...event };
|
|
17513
|
+
startInitialHeader();
|
|
17223
17514
|
}
|
|
17224
|
-
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
if (!initialAdmissionDurable || !initialHeaderNotice || !chatKeyBytes || !isNewChat || params.incognito || !savedTurnId) return;
|
|
17234
|
-
const notice = { ...initialHeaderNotice }, key = chatKeyBytes, turn = savedTurnId;
|
|
17235
|
-
const titleChanged = !!notice.chatTitle && notice.chatTitle !== initialTitleScheduled;
|
|
17236
|
-
const iconChanged = !!notice.chatIcon && notice.chatIcon !== initialIconScheduled;
|
|
17237
|
-
if (!titleChanged && !iconChanged) return;
|
|
17238
|
-
initialTitleScheduled = notice.chatTitle ?? initialTitleScheduled;
|
|
17239
|
-
initialIconScheduled = notice.chatIcon ?? initialIconScheduled;
|
|
17240
|
-
const current2 = () => !initialHeaderStopped && ws.isOpen() && this.preparedChatOwnerCurrent(initialHeaderOwner, teamId) && this.activeSubmittedChatTurns.get(chatId) === turn;
|
|
17241
|
-
initialHeaderWork = (initialHeaderWork ?? Promise.resolve()).then(async () => {
|
|
17242
|
-
if (!current2()) return;
|
|
17243
|
-
if (titleChanged) await this.persistPostProcessingMetadata({
|
|
17515
|
+
params.onStream?.(event);
|
|
17516
|
+
};
|
|
17517
|
+
if (!params.incognito && chatKeyBytes && savedTurnId) {
|
|
17518
|
+
const key = chatKeyBytes;
|
|
17519
|
+
const ownedTurnId = savedTurnId;
|
|
17520
|
+
this.activeSubmittedChatTurns.set(chatId, ownedTurnId);
|
|
17521
|
+
const owner2 = this.preparedChatOwner(this.requireSession(), teamId);
|
|
17522
|
+
const stopOwned = () => optionalMetadata?.stop();
|
|
17523
|
+
optionalMetadata = observeOwnedTurnMetadata({
|
|
17244
17524
|
ws,
|
|
17245
17525
|
chatId,
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
|
|
17257
|
-
|
|
17258
|
-
|
|
17526
|
+
turnId: savedTurnId,
|
|
17527
|
+
userMessageId: messageId2,
|
|
17528
|
+
closeAfterMetadata: true,
|
|
17529
|
+
ownerCurrent: () => this.preparedChatOwnerCurrent(owner2, teamId),
|
|
17530
|
+
persist: async (metadata) => {
|
|
17531
|
+
await this.persistPostProcessingMetadata({ ws, teamId, chatKeyBytes: key, encryptedChatKey, ...metadata });
|
|
17532
|
+
clearSyncCache(teamId);
|
|
17533
|
+
},
|
|
17534
|
+
onMetadata: params.onMetadata,
|
|
17535
|
+
onReleased: () => {
|
|
17536
|
+
this.ownedMetadataSubscriptions.delete(stopOwned);
|
|
17537
|
+
if (this.activeSubmittedChatTurns.get(chatId) === ownedTurnId) this.activeSubmittedChatTurns.delete(chatId);
|
|
17538
|
+
if (metadataTransportOwned) ws.close();
|
|
17539
|
+
}
|
|
17259
17540
|
});
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
const [encryptedIcon, encryptedCategory] = await Promise.all([
|
|
17263
|
-
notice.chatIcon ? encryptWithAesGcmCombined(notice.chatIcon, key) : null,
|
|
17264
|
-
notice.category ? encryptWithAesGcmCombined(notice.category, key) : null
|
|
17265
|
-
]);
|
|
17266
|
-
if (!current2()) return;
|
|
17267
|
-
const stored = ws.waitForMessage("encrypted_metadata_stored", (value) => value.chat_id === chatId, 2e4);
|
|
17268
|
-
void stored.catch(() => {
|
|
17269
|
-
});
|
|
17270
|
-
await ws.sendAsync("encrypted_chat_metadata", {
|
|
17271
|
-
chat_id: chatId,
|
|
17272
|
-
...teamId ? { team_id: teamId } : {},
|
|
17273
|
-
encrypted_chat_key: encryptedChatKey,
|
|
17274
|
-
...encryptedIcon ? { encrypted_icon: encryptedIcon } : {},
|
|
17275
|
-
...encryptedCategory ? { encrypted_chat_category: encryptedCategory } : {}
|
|
17276
|
-
});
|
|
17277
|
-
await stored;
|
|
17278
|
-
}
|
|
17279
|
-
if (current2()) clearSyncCache(teamId);
|
|
17280
|
-
}).catch(() => {
|
|
17281
|
-
});
|
|
17282
|
-
};
|
|
17283
|
-
const onTurnStream = (event) => {
|
|
17284
|
-
if (isNewChat && event.kind === "typing" && (event.chatTitle || event.chatIcon) && event.chatId === chatId && event.userMessageId === messageId2 && event.turnId === savedTurnId && event.taskId && (!initialHeaderNotice?.taskId || event.taskId === initialHeaderNotice.taskId)) {
|
|
17285
|
-
initialHeaderNotice = { ...initialHeaderNotice, ...event };
|
|
17286
|
-
startInitialHeader();
|
|
17541
|
+
while (this.ownedMetadataSubscriptions.size >= 8) this.ownedMetadataSubscriptions.values().next().value?.();
|
|
17542
|
+
this.ownedMetadataSubscriptions.add(stopOwned);
|
|
17287
17543
|
}
|
|
17288
|
-
params.
|
|
17289
|
-
|
|
17290
|
-
|
|
17291
|
-
|
|
17292
|
-
|
|
17293
|
-
|
|
17294
|
-
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17304
|
-
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
if (metadataTransportOwned) ws.close();
|
|
17312
|
-
}
|
|
17544
|
+
if (!params.incognito && shouldWaitForAi) {
|
|
17545
|
+
precollectedResponse = ws.collectAiResponse(messageId2, chatId, {
|
|
17546
|
+
waitForPostProcessing: false,
|
|
17547
|
+
onStream: onTurnStream,
|
|
17548
|
+
onSubChatEvent: async (event) => {
|
|
17549
|
+
await handlersReady;
|
|
17550
|
+
await handleSubChatEvent(event);
|
|
17551
|
+
},
|
|
17552
|
+
onAppSettingsMemoriesRequest: async (event) => {
|
|
17553
|
+
await handlersReady;
|
|
17554
|
+
await handleAppSettingsMemoriesRequest(event);
|
|
17555
|
+
},
|
|
17556
|
+
timeoutMs: params.responseTimeoutMs,
|
|
17557
|
+
recoveryTurnId: savedTurnId,
|
|
17558
|
+
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
17559
|
+
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
17560
|
+
});
|
|
17561
|
+
}
|
|
17562
|
+
const queuedAdmission = !params.incognito && shouldWaitForAi ? ws.waitForMessage("message_queued", (payload) => {
|
|
17563
|
+
const event = payload;
|
|
17564
|
+
return event.chat_id === chatId && event.user_message_id === messageId2;
|
|
17565
|
+
}, params.responseTimeoutMs ?? 9e4) : null;
|
|
17566
|
+
if (queuedAdmission) void queuedAdmission.catch(() => {
|
|
17313
17567
|
});
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17319
|
-
waitForPostProcessing: false,
|
|
17320
|
-
onStream: onTurnStream,
|
|
17321
|
-
onSubChatEvent: async (event) => {
|
|
17322
|
-
await handlersReady;
|
|
17323
|
-
await handleSubChatEvent(event);
|
|
17324
|
-
},
|
|
17325
|
-
onAppSettingsMemoriesRequest: async (event) => {
|
|
17326
|
-
await handlersReady;
|
|
17327
|
-
await handleAppSettingsMemoriesRequest(event);
|
|
17328
|
-
},
|
|
17329
|
-
timeoutMs: params.responseTimeoutMs,
|
|
17330
|
-
recoveryTurnId: savedTurnId,
|
|
17331
|
-
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
17332
|
-
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
17568
|
+
const pausedHandoff = !params.incognito && shouldWaitForAi ? ws.waitForMessage("queued_handoff_paused", (payload) => {
|
|
17569
|
+
const event = payload;
|
|
17570
|
+
return event.chat_id === chatId && (event.message_id === messageId2 || event.message_id === void 0);
|
|
17571
|
+
}, params.responseTimeoutMs ?? 9e4) : null;
|
|
17572
|
+
if (pausedHandoff) void pausedHandoff.catch(() => {
|
|
17333
17573
|
});
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
17337
|
-
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17350
|
-
|
|
17351
|
-
|
|
17352
|
-
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
|
|
17356
|
-
let admissionContinuationPhase = "before_dispatch";
|
|
17357
|
-
let admissionAckObserved = false;
|
|
17358
|
-
let routingFrameSent = false;
|
|
17359
|
-
let detailsFrameSent = false;
|
|
17360
|
-
const admissionContinuationStartedAt = performance.now();
|
|
17361
|
-
try {
|
|
17362
|
-
if (admissionPayload) {
|
|
17363
|
-
if (!useDeferredContext && recoveryDiscovery) await recoveryDiscovery;
|
|
17364
|
-
if (recoveryDiscoveryError) throw recoveryDiscoveryError;
|
|
17365
|
-
if (!contextRequestId && pendingContext) {
|
|
17366
|
-
const context = await pendingContext;
|
|
17367
|
-
Object.assign(messagePayload, context);
|
|
17368
|
-
Object.assign(admissionPayload.inference_request, context);
|
|
17369
|
-
}
|
|
17370
|
-
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17371
|
-
const admissionAck = ws.waitForMessage("chat_turn_preflight_ack", (payload) => payload.turn_id === savedTurnId);
|
|
17372
|
-
void admissionAck.catch(() => {
|
|
17373
|
-
});
|
|
17374
|
-
const frameDispatchAt = performance.now();
|
|
17375
|
-
admissionContinuationPhase = "await_ack";
|
|
17376
|
-
await consumeSocketMemoryReceipt();
|
|
17377
|
-
await ws.sendAsync(useCombinedAdmission ? "chat_turn_admit" : "chat_turn_preflight", admissionPayload);
|
|
17378
|
-
traceSendTiming("admission_frame");
|
|
17379
|
-
if (process.env.OPENMATES_DEBUG === "1") {
|
|
17380
|
-
console.error(`[chat admission preparation] sdk_minted=${sdkMintedChatId} defer_recovery=${mayDeferRecovery} combined_admission=${useCombinedAdmission} caps_wait_ms=${capabilitiesWaitMs} key_wait_ms=${keyWaitMs} recovery_keypair_ms=${recoveryKeypairMs} encrypt_payload_ms=${encryptPayloadMs} socket_ready_to_frame_dispatch_ms=${Math.round(frameDispatchAt - socketReadyForSendAt)} frame_dispatch_from_send_start_ms=${Math.round(frameDispatchAt - preWsStartedAt)} wire_send_ms=${Math.round(performance.now() - frameDispatchAt)}`);
|
|
17381
|
-
}
|
|
17382
|
-
const ackPayload = (await admissionAck).payload;
|
|
17383
|
-
traceSendTiming("admission_ack");
|
|
17384
|
-
admissionAckObserved = true;
|
|
17385
|
-
if (typeof ackPayload.preflight_id !== "string" || !ackPayload.preflight_id) {
|
|
17386
|
-
throw new Error("Encrypted chat admission acknowledgement omitted preflight_id.");
|
|
17387
|
-
}
|
|
17388
|
-
if (typeof ackPayload.committed_messages_v === "number" && Number.isSafeInteger(ackPayload.committed_messages_v)) {
|
|
17389
|
-
terminalExpectedMessagesV = ackPayload.committed_messages_v;
|
|
17390
|
-
}
|
|
17391
|
-
initialAdmissionDurable = true;
|
|
17392
|
-
startInitialHeader();
|
|
17393
|
-
if (!useCombinedAdmission) {
|
|
17394
|
-
Object.assign(messagePayload, { protocol_version: admissionPayload.protocol_version, preflight_id: ackPayload.preflight_id });
|
|
17395
|
-
await ws.sendAsync("chat_message_added", messagePayload);
|
|
17396
|
-
}
|
|
17397
|
-
if (pendingContext && contextRequestId) {
|
|
17398
|
-
admissionContinuationPhase = "await_routing";
|
|
17399
|
-
const identity3 = {
|
|
17400
|
-
chat_id: chatId,
|
|
17401
|
-
turn_id: savedTurnId,
|
|
17402
|
-
message_id: messageId2,
|
|
17403
|
-
request_id: contextRequestId,
|
|
17404
|
-
...teamId ? { team_id: teamId } : {}
|
|
17405
|
-
};
|
|
17406
|
-
let failureNotice = null;
|
|
17407
|
-
const notifyContextFailure = (reason) => {
|
|
17408
|
-
if (failureNotice) return failureNotice;
|
|
17409
|
-
if (!ws.supportsServerCapability("chat_turn_context_failure_v1") || !ws.isOpen() || !contextOwnerIsCurrent() || !savedTurnId) return Promise.resolve();
|
|
17410
|
-
failureNotice = (async () => {
|
|
17411
|
-
const failureAck = ws.waitForMessage("chat_turn_context_failed_ack", (payload) => {
|
|
17412
|
-
const event = payload;
|
|
17413
|
-
return event.chat_id === chatId && event.turn_id === savedTurnId && event.message_id === messageId2 && event.request_id === contextRequestId;
|
|
17414
|
-
}, 5e3);
|
|
17415
|
-
void failureAck.catch(() => {
|
|
17416
|
-
});
|
|
17417
|
-
await ws.sendAsync("chat_turn_context_failed", { ...identity3, reason });
|
|
17418
|
-
const status = (await failureAck).payload;
|
|
17419
|
-
if (status.status !== "accepted" && status.status !== "duplicate") {
|
|
17420
|
-
throw new Error("Chat context failure receipt was rejected.");
|
|
17421
|
-
}
|
|
17422
|
-
})();
|
|
17423
|
-
return failureNotice;
|
|
17424
|
-
};
|
|
17425
|
-
let routing;
|
|
17426
|
-
try {
|
|
17427
|
-
routing = useSelectionDetailsV2 ? await projectRoutingContext : deferredProjectDirectory ? (await Promise.all([pendingContext, projectRoutingContext]))[0] : params.projectId ? await pendingContext : await Promise.race([routingContext, pendingContext]);
|
|
17428
|
-
} catch (error) {
|
|
17429
|
-
try {
|
|
17430
|
-
await notifyContextFailure(useSelectionDetailsV2 ? "project_discovery_failed" : "context_preparation_failed");
|
|
17431
|
-
} catch {
|
|
17432
|
-
}
|
|
17433
|
-
throw error;
|
|
17574
|
+
const confirmed = ws.waitForMessage(
|
|
17575
|
+
"chat_message_confirmed",
|
|
17576
|
+
(payload) => {
|
|
17577
|
+
const eventPayload = payload;
|
|
17578
|
+
return eventPayload.chat_id === chatId && eventPayload.message_id === messageId2;
|
|
17579
|
+
},
|
|
17580
|
+
params.responseTimeoutMs ?? DEFAULT_CHAT_MESSAGE_CONFIRMATION_TIMEOUT_MS
|
|
17581
|
+
);
|
|
17582
|
+
let confirmedPayload;
|
|
17583
|
+
let admissionContinuationPhase = "before_dispatch";
|
|
17584
|
+
let admissionAckObserved = false;
|
|
17585
|
+
let routingFrameSent = false;
|
|
17586
|
+
let detailsFrameSent = false;
|
|
17587
|
+
const admissionContinuationStartedAt = performance.now();
|
|
17588
|
+
try {
|
|
17589
|
+
if (admissionPayload) {
|
|
17590
|
+
if (!useDeferredContext && recoveryDiscovery) await recoveryDiscovery;
|
|
17591
|
+
if (recoveryDiscoveryError) throw recoveryDiscoveryError;
|
|
17592
|
+
if (!contextRequestId && pendingContext) {
|
|
17593
|
+
const context = await pendingContext;
|
|
17594
|
+
Object.assign(messagePayload, context);
|
|
17595
|
+
Object.assign(admissionPayload.inference_request, context);
|
|
17434
17596
|
}
|
|
17435
17597
|
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17436
|
-
|
|
17437
|
-
|
|
17438
|
-
rejectContextAck = reject;
|
|
17598
|
+
const admissionAck = ws.waitForMessage("chat_turn_preflight_ack", (payload) => payload.turn_id === savedTurnId);
|
|
17599
|
+
void admissionAck.catch(() => {
|
|
17439
17600
|
});
|
|
17440
|
-
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17601
|
+
const frameDispatchAt = performance.now();
|
|
17602
|
+
admissionContinuationPhase = "await_ack";
|
|
17603
|
+
await consumeSocketMemoryReceipt();
|
|
17604
|
+
await ws.sendAsync(useCombinedAdmission ? "chat_turn_admit" : "chat_turn_preflight", admissionPayload);
|
|
17605
|
+
traceSendTiming("admission_frame");
|
|
17606
|
+
if (process.env.OPENMATES_DEBUG === "1") {
|
|
17607
|
+
console.error(`[chat admission preparation] sdk_minted=${sdkMintedChatId} defer_recovery=${mayDeferRecovery} combined_admission=${useCombinedAdmission} caps_wait_ms=${capabilitiesWaitMs} key_wait_ms=${keyWaitMs} recovery_keypair_ms=${recoveryKeypairMs} encrypt_payload_ms=${encryptPayloadMs} socket_ready_to_frame_dispatch_ms=${Math.round(frameDispatchAt - socketReadyForSendAt)} frame_dispatch_from_send_start_ms=${Math.round(frameDispatchAt - preWsStartedAt)} wire_send_ms=${Math.round(performance.now() - frameDispatchAt)}`);
|
|
17608
|
+
}
|
|
17609
|
+
const ackPayload = (await admissionAck).payload;
|
|
17610
|
+
traceSendTiming("admission_ack");
|
|
17611
|
+
admissionAckObserved = true;
|
|
17612
|
+
if (typeof ackPayload.preflight_id !== "string" || !ackPayload.preflight_id) {
|
|
17613
|
+
throw new Error("Encrypted chat admission acknowledgement omitted preflight_id.");
|
|
17614
|
+
}
|
|
17615
|
+
if (typeof ackPayload.committed_messages_v === "number" && Number.isSafeInteger(ackPayload.committed_messages_v)) {
|
|
17616
|
+
terminalExpectedMessagesV = ackPayload.committed_messages_v;
|
|
17617
|
+
}
|
|
17618
|
+
initialAdmissionDurable = true;
|
|
17619
|
+
startInitialHeader();
|
|
17620
|
+
if (!useCombinedAdmission) {
|
|
17621
|
+
Object.assign(messagePayload, { protocol_version: admissionPayload.protocol_version, preflight_id: ackPayload.preflight_id });
|
|
17622
|
+
await ws.sendAsync("chat_message_added", messagePayload);
|
|
17623
|
+
}
|
|
17624
|
+
if (pendingContext && contextRequestId) {
|
|
17625
|
+
admissionContinuationPhase = "await_routing";
|
|
17626
|
+
const identity3 = {
|
|
17627
|
+
chat_id: chatId,
|
|
17628
|
+
turn_id: savedTurnId,
|
|
17629
|
+
message_id: messageId2,
|
|
17630
|
+
request_id: contextRequestId,
|
|
17631
|
+
...teamId ? { team_id: teamId } : {}
|
|
17632
|
+
};
|
|
17633
|
+
let failureNotice = null;
|
|
17634
|
+
const notifyContextFailure = (reason) => {
|
|
17635
|
+
if (failureNotice) return failureNotice;
|
|
17636
|
+
if (!ws.supportsServerCapability("chat_turn_context_failure_v1") || !ws.isOpen() || !contextOwnerIsCurrent() || !savedTurnId) return Promise.resolve();
|
|
17637
|
+
failureNotice = (async () => {
|
|
17638
|
+
const failureAck = ws.waitForMessage("chat_turn_context_failed_ack", (payload) => {
|
|
17639
|
+
const event = payload;
|
|
17640
|
+
return event.chat_id === chatId && event.turn_id === savedTurnId && event.message_id === messageId2 && event.request_id === contextRequestId;
|
|
17641
|
+
}, 5e3);
|
|
17642
|
+
void failureAck.catch(() => {
|
|
17643
|
+
});
|
|
17644
|
+
await ws.sendAsync("chat_turn_context_failed", { ...identity3, reason });
|
|
17645
|
+
const status = (await failureAck).payload;
|
|
17646
|
+
if (status.status !== "accepted" && status.status !== "duplicate") {
|
|
17647
|
+
throw new Error("Chat context failure receipt was rejected.");
|
|
17648
|
+
}
|
|
17649
|
+
})();
|
|
17650
|
+
return failureNotice;
|
|
17651
|
+
};
|
|
17652
|
+
let routing;
|
|
17653
|
+
try {
|
|
17654
|
+
routing = useSelectionDetailsV2 ? await projectRoutingContext : deferredProjectDirectory ? (await Promise.all([pendingContext, projectRoutingContext]))[0] : params.projectId ? await pendingContext : await Promise.race([routingContext, pendingContext]);
|
|
17655
|
+
} catch (error) {
|
|
17656
|
+
try {
|
|
17657
|
+
await notifyContextFailure(useSelectionDetailsV2 ? "project_discovery_failed" : "context_preparation_failed");
|
|
17658
|
+
} catch {
|
|
17659
|
+
}
|
|
17660
|
+
throw error;
|
|
17455
17661
|
}
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17662
|
+
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17663
|
+
let rejectContextAck;
|
|
17664
|
+
contextAckRejection = new Promise((_resolve, reject) => {
|
|
17665
|
+
rejectContextAck = reject;
|
|
17459
17666
|
});
|
|
17460
|
-
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
|
|
17465
|
-
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17470
|
-
|
|
17471
|
-
|
|
17667
|
+
void contextAckRejection.catch(() => {
|
|
17668
|
+
});
|
|
17669
|
+
let rejectedPhaseSeen = false;
|
|
17670
|
+
const attemptedContextPhases = /* @__PURE__ */ new Set();
|
|
17671
|
+
removeContextAckWatcher = ws.onMessageType("chat_turn_context_ack", (event) => {
|
|
17672
|
+
if (rejectedPhaseSeen || event.chat_id !== chatId || event.turn_id !== savedTurnId || event.message_id !== messageId2 || event.request_id !== contextRequestId || event.phase !== "routing" && event.phase !== "details" || !attemptedContextPhases.has(event.phase)) return;
|
|
17673
|
+
if (event.status === "accepted" || event.status === "duplicate") {
|
|
17674
|
+
traceSendTiming(event.phase === "routing" ? "routing_ack" : "details_ack");
|
|
17675
|
+
return;
|
|
17676
|
+
}
|
|
17677
|
+
if (event.status !== "rejected") return;
|
|
17678
|
+
rejectedPhaseSeen = true;
|
|
17679
|
+
if (!contextOwnerIsCurrent()) {
|
|
17680
|
+
rejectContextAck(new Error("Chat workspace changed while preparing context."));
|
|
17681
|
+
return;
|
|
17682
|
+
}
|
|
17683
|
+
void notifyContextFailure("context_preparation_failed").catch(() => {
|
|
17684
|
+
}).then(() => {
|
|
17685
|
+
rejectContextAck(new Error("Chat context delivery was rejected."));
|
|
17686
|
+
});
|
|
17687
|
+
});
|
|
17688
|
+
const { project_focus_candidates, related_task_candidates, project_focus_catalog, accepted_plan_context } = routing;
|
|
17689
|
+
traceSendTiming("routing_prepared");
|
|
17690
|
+
attemptedContextPhases.add("routing");
|
|
17691
|
+
await ws.sendAsync("chat_turn_context_ready", { ...identity3, phase: "routing", context: useSelectionDetailsV2 ? { project_focus_candidates } : { project_focus_candidates, related_task_candidates, project_focus_catalog, accepted_plan_context } });
|
|
17692
|
+
routingFrameSent = true;
|
|
17693
|
+
traceSendTiming("routing_frame");
|
|
17694
|
+
admissionContinuationPhase = "await_details";
|
|
17695
|
+
let context;
|
|
17472
17696
|
try {
|
|
17473
|
-
await
|
|
17474
|
-
} catch {
|
|
17697
|
+
context = await Promise.race([pendingContext, contextAckRejection, catalogDeliveryFailure]);
|
|
17698
|
+
} catch (error) {
|
|
17699
|
+
try {
|
|
17700
|
+
await notifyContextFailure("context_preparation_failed");
|
|
17701
|
+
} catch {
|
|
17702
|
+
}
|
|
17703
|
+
throw error;
|
|
17475
17704
|
}
|
|
17476
|
-
throw
|
|
17705
|
+
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17706
|
+
traceSendTiming("details_prepared");
|
|
17707
|
+
attemptedContextPhases.add("details");
|
|
17708
|
+
await ws.sendAsync("chat_turn_context_ready", { ...identity3, phase: "details", context: {
|
|
17709
|
+
custom_memory_documents: context.custom_memory_documents ?? [],
|
|
17710
|
+
project_focus_documents: context.project_focus_documents ?? [],
|
|
17711
|
+
project_context_documents: context.project_context_documents ?? [],
|
|
17712
|
+
...useSelectionDetailsV2 ? {
|
|
17713
|
+
related_task_candidates: context.related_task_candidates ?? [],
|
|
17714
|
+
project_focus_catalog: context.project_focus_catalog ?? [],
|
|
17715
|
+
accepted_plan_context: context.accepted_plan_context ?? null,
|
|
17716
|
+
...connectedAccountDirectory ? { connected_account_directory: connectedAccountDirectory } : {}
|
|
17717
|
+
} : {}
|
|
17718
|
+
} });
|
|
17719
|
+
detailsFrameSent = true;
|
|
17720
|
+
traceSendTiming("details_frame");
|
|
17477
17721
|
}
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
await ws.sendAsync("
|
|
17482
|
-
custom_memory_documents: context.custom_memory_documents ?? [],
|
|
17483
|
-
project_focus_documents: context.project_focus_documents ?? [],
|
|
17484
|
-
project_context_documents: context.project_context_documents ?? [],
|
|
17485
|
-
...useSelectionDetailsV2 ? {
|
|
17486
|
-
related_task_candidates: context.related_task_candidates ?? [],
|
|
17487
|
-
project_focus_catalog: context.project_focus_catalog ?? [],
|
|
17488
|
-
accepted_plan_context: context.accepted_plan_context ?? null,
|
|
17489
|
-
...connectedAccountDirectory ? { connected_account_directory: connectedAccountDirectory } : {}
|
|
17490
|
-
} : {}
|
|
17491
|
-
} });
|
|
17492
|
-
detailsFrameSent = true;
|
|
17493
|
-
traceSendTiming("details_frame");
|
|
17722
|
+
admissionContinuationPhase = "await_confirmation";
|
|
17723
|
+
} else {
|
|
17724
|
+
await consumeSocketMemoryReceipt();
|
|
17725
|
+
await ws.sendAsync("chat_message_added", messagePayload);
|
|
17494
17726
|
}
|
|
17495
|
-
|
|
17496
|
-
}
|
|
17497
|
-
|
|
17498
|
-
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
|
|
17502
|
-
|
|
17503
|
-
|
|
17727
|
+
confirmedPayload = (await Promise.race([confirmed, catalogDeliveryFailure, ...contextAckRejection ? [contextAckRejection] : []])).payload;
|
|
17728
|
+
} catch (error) {
|
|
17729
|
+
if (process.env.OPENMATES_DEBUG === "1" && admissionPayload) {
|
|
17730
|
+
console.error(`[chat admission continuation] phase=${admissionContinuationPhase} ack_observed=${admissionAckObserved} routing_sent=${routingFrameSent} details_sent=${detailsFrameSent} socket_open=${ws.isOpen()} elapsed_ms=${Math.round(performance.now() - admissionContinuationStartedAt)}`);
|
|
17731
|
+
}
|
|
17732
|
+
dispatchHandlersReady();
|
|
17733
|
+
void confirmed.catch(() => {
|
|
17734
|
+
});
|
|
17735
|
+
if (precollectedResponse) void precollectedResponse.catch(() => {
|
|
17736
|
+
});
|
|
17737
|
+
ws.close();
|
|
17738
|
+
throw error;
|
|
17504
17739
|
}
|
|
17505
|
-
|
|
17506
|
-
|
|
17507
|
-
});
|
|
17508
|
-
if (precollectedResponse) void precollectedResponse.catch(() => {
|
|
17509
|
-
});
|
|
17510
|
-
ws.close();
|
|
17511
|
-
throw error;
|
|
17512
|
-
}
|
|
17513
|
-
if (!params.incognito && typeof confirmedPayload.new_messages_v === "number" && Number.isSafeInteger(confirmedPayload.new_messages_v)) {
|
|
17514
|
-
terminalExpectedMessagesV = confirmedPayload.new_messages_v;
|
|
17515
|
-
}
|
|
17516
|
-
if (!params.incognito && (!teamId || shouldWaitForAi)) {
|
|
17517
|
-
clearSyncCache(teamId);
|
|
17518
|
-
}
|
|
17519
|
-
let assistant = "";
|
|
17520
|
-
let assistantMessageId = null;
|
|
17521
|
-
let category = null;
|
|
17522
|
-
let modelName = null;
|
|
17523
|
-
let tokenUsage = null;
|
|
17524
|
-
let promptBudget = null;
|
|
17525
|
-
let followUpSuggestions = [];
|
|
17526
|
-
let suggestedFollowupMessage = null;
|
|
17527
|
-
let taskProposals = [];
|
|
17528
|
-
let taskUpdateProposals = [];
|
|
17529
|
-
let taskEvents = [];
|
|
17530
|
-
let pendingTaskUpdateJobs = [];
|
|
17531
|
-
let subChatEvents = [];
|
|
17532
|
-
const appSettingsMemoryRequests = [];
|
|
17533
|
-
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
17534
|
-
const isApprovalWithinServerLimits = (request) => {
|
|
17535
|
-
const count = request.subChats.length;
|
|
17536
|
-
if (count === 0) return false;
|
|
17537
|
-
if (request.remainingSubChats !== null && count > request.remainingSubChats) {
|
|
17538
|
-
return false;
|
|
17740
|
+
if (!params.incognito && typeof confirmedPayload.new_messages_v === "number" && Number.isSafeInteger(confirmedPayload.new_messages_v)) {
|
|
17741
|
+
terminalExpectedMessagesV = confirmedPayload.new_messages_v;
|
|
17539
17742
|
}
|
|
17540
|
-
if (
|
|
17541
|
-
|
|
17743
|
+
if (!params.incognito && (!teamId || shouldWaitForAi)) {
|
|
17744
|
+
clearSyncCache(teamId);
|
|
17542
17745
|
}
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
|
|
17555
|
-
|
|
17556
|
-
|
|
17557
|
-
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17746
|
+
let assistant = "";
|
|
17747
|
+
let assistantMessageId = null;
|
|
17748
|
+
let category = null;
|
|
17749
|
+
let modelName = null;
|
|
17750
|
+
let tokenUsage = null;
|
|
17751
|
+
let promptBudget = null;
|
|
17752
|
+
let followUpSuggestions = [];
|
|
17753
|
+
let suggestedFollowupMessage = null;
|
|
17754
|
+
let taskProposals = [];
|
|
17755
|
+
let taskUpdateProposals = [];
|
|
17756
|
+
let taskEvents = [];
|
|
17757
|
+
let pendingTaskUpdateJobs = [];
|
|
17758
|
+
let subChatEvents = [];
|
|
17759
|
+
const appSettingsMemoryRequests = [];
|
|
17760
|
+
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
17761
|
+
const isApprovalWithinServerLimits = (request) => {
|
|
17762
|
+
const count = request.subChats.length;
|
|
17763
|
+
if (count === 0) return false;
|
|
17764
|
+
if (request.remainingSubChats !== null && count > request.remainingSubChats) {
|
|
17765
|
+
return false;
|
|
17563
17766
|
}
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
if (event.type !== "sub_chat_confirmation_required") return;
|
|
17567
|
-
const payload = event.payload;
|
|
17568
|
-
const confirmationChatId = typeof payload.chat_id === "string" ? payload.chat_id : chatId;
|
|
17569
|
-
const taskId = typeof payload.task_id === "string" ? payload.task_id : null;
|
|
17570
|
-
const subChats = Array.isArray(payload.sub_chats) ? payload.sub_chats : [];
|
|
17571
|
-
if (!taskId) return;
|
|
17572
|
-
const request = {
|
|
17573
|
-
chatId: confirmationChatId,
|
|
17574
|
-
taskId,
|
|
17575
|
-
subChats,
|
|
17576
|
-
maxAutoSubChats: numberOrNull(payload.max_auto_sub_chats),
|
|
17577
|
-
maxDirectSubChats: numberOrNull(payload.max_direct_sub_chats),
|
|
17578
|
-
existingSubChats: numberOrNull(payload.existing_sub_chats),
|
|
17579
|
-
remainingSubChats: numberOrNull(payload.remaining_sub_chats)
|
|
17580
|
-
};
|
|
17581
|
-
const withinLimits = isApprovalWithinServerLimits(request);
|
|
17582
|
-
const approved = params.autoApproveSubChats ? isAutoApprovalWithinServerLimits(request) : withinLimits && params.onSubChatApprovalRequest ? await params.onSubChatApprovalRequest(request) : false;
|
|
17583
|
-
await ws.sendAsync(
|
|
17584
|
-
"sub_chat_confirmation",
|
|
17585
|
-
buildSubChatConfirmationPayload({
|
|
17586
|
-
chatId: request.chatId,
|
|
17587
|
-
taskId: request.taskId,
|
|
17588
|
-
approved,
|
|
17589
|
-
approveCount: approved ? request.subChats.length : void 0
|
|
17590
|
-
})
|
|
17591
|
-
);
|
|
17592
|
-
};
|
|
17593
|
-
const persistSystemMessage = async (systemMessage) => {
|
|
17594
|
-
if (!chatKeyBytes) return;
|
|
17595
|
-
await ws.sendAsync("chat_system_message_added", {
|
|
17596
|
-
chat_id: chatId,
|
|
17597
|
-
message: {
|
|
17598
|
-
message_id: systemMessage.message_id,
|
|
17599
|
-
role: "system",
|
|
17600
|
-
encrypted_content: await encryptWithAesGcmCombined(
|
|
17601
|
-
systemMessage.content,
|
|
17602
|
-
chatKeyBytes
|
|
17603
|
-
),
|
|
17604
|
-
created_at: systemMessage.created_at,
|
|
17605
|
-
user_message_id: systemMessage.user_message_id
|
|
17767
|
+
if (request.maxDirectSubChats !== null && request.existingSubChats !== null && request.existingSubChats + count > request.maxDirectSubChats) {
|
|
17768
|
+
return false;
|
|
17606
17769
|
}
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
|
|
17610
|
-
(
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
|
|
17620
|
-
|
|
17621
|
-
|
|
17622
|
-
|
|
17623
|
-
|
|
17624
|
-
createdAt: Math.floor(Date.now() / 1e3)
|
|
17625
|
-
}));
|
|
17626
|
-
};
|
|
17627
|
-
const responseCategoriesFromMemories = (approvedMemories) => {
|
|
17628
|
-
const counts = /* @__PURE__ */ new Map();
|
|
17629
|
-
for (const memory of approvedMemories) {
|
|
17630
|
-
const key = `${memory.app_id}-${memory.item_key}`;
|
|
17631
|
-
const existing = counts.get(key);
|
|
17632
|
-
if (existing) {
|
|
17633
|
-
existing.entryCount += 1;
|
|
17634
|
-
} else {
|
|
17635
|
-
counts.set(key, {
|
|
17636
|
-
appId: memory.app_id,
|
|
17637
|
-
itemType: memory.item_key,
|
|
17638
|
-
entryCount: 1
|
|
17770
|
+
return true;
|
|
17771
|
+
};
|
|
17772
|
+
const isAutoApprovalWithinServerLimits = (request) => {
|
|
17773
|
+
if (!isApprovalWithinServerLimits(request)) return false;
|
|
17774
|
+
return request.maxAutoSubChats === null || request.subChats.length <= request.maxAutoSubChats;
|
|
17775
|
+
};
|
|
17776
|
+
const handleSubChatEvent = async (event) => {
|
|
17777
|
+
params.onSubChatEvent?.(event);
|
|
17778
|
+
if (event.type === "spawn_sub_chats") {
|
|
17779
|
+
if (params.incognito || !chatKeyBytes) return;
|
|
17780
|
+
const rawSubChats = event.payload.sub_chats;
|
|
17781
|
+
if (!Array.isArray(rawSubChats)) return;
|
|
17782
|
+
const metadataPayloads = await buildSubChatEncryptedMetadataPayloads({
|
|
17783
|
+
parentChatId: chatId,
|
|
17784
|
+
parentChatKey: chatKeyBytes,
|
|
17785
|
+
encryptedParentChatKey: encryptedChatKey,
|
|
17786
|
+
subChats: rawSubChats
|
|
17639
17787
|
});
|
|
17788
|
+
for (const metadataPayload of metadataPayloads) {
|
|
17789
|
+
await ws.sendAsync("encrypted_chat_metadata", metadataPayload);
|
|
17790
|
+
}
|
|
17791
|
+
return;
|
|
17640
17792
|
}
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
const
|
|
17648
|
-
|
|
17649
|
-
|
|
17793
|
+
if (event.type !== "sub_chat_confirmation_required") return;
|
|
17794
|
+
const payload = event.payload;
|
|
17795
|
+
const confirmationChatId = typeof payload.chat_id === "string" ? payload.chat_id : chatId;
|
|
17796
|
+
const taskId = typeof payload.task_id === "string" ? payload.task_id : null;
|
|
17797
|
+
const subChats = Array.isArray(payload.sub_chats) ? payload.sub_chats : [];
|
|
17798
|
+
if (!taskId) return;
|
|
17799
|
+
const request = {
|
|
17800
|
+
chatId: confirmationChatId,
|
|
17801
|
+
taskId,
|
|
17802
|
+
subChats,
|
|
17803
|
+
maxAutoSubChats: numberOrNull(payload.max_auto_sub_chats),
|
|
17804
|
+
maxDirectSubChats: numberOrNull(payload.max_direct_sub_chats),
|
|
17805
|
+
existingSubChats: numberOrNull(payload.existing_sub_chats),
|
|
17806
|
+
remainingSubChats: numberOrNull(payload.remaining_sub_chats)
|
|
17807
|
+
};
|
|
17808
|
+
const withinLimits = isApprovalWithinServerLimits(request);
|
|
17809
|
+
const approved = params.autoApproveSubChats ? isAutoApprovalWithinServerLimits(request) : withinLimits && params.onSubChatApprovalRequest ? await params.onSubChatApprovalRequest(request) : false;
|
|
17810
|
+
await ws.sendAsync(
|
|
17811
|
+
"sub_chat_confirmation",
|
|
17812
|
+
buildSubChatConfirmationPayload({
|
|
17813
|
+
chatId: request.chatId,
|
|
17814
|
+
taskId: request.taskId,
|
|
17815
|
+
approved,
|
|
17816
|
+
approveCount: approved ? request.subChats.length : void 0
|
|
17817
|
+
})
|
|
17650
17818
|
);
|
|
17651
|
-
|
|
17652
|
-
|
|
17653
|
-
|
|
17654
|
-
|
|
17655
|
-
|
|
17656
|
-
|
|
17657
|
-
|
|
17658
|
-
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
requestId: event.requestId,
|
|
17667
|
-
requestedKeys: event.requestedKeys,
|
|
17668
|
-
approvedKeys,
|
|
17669
|
-
entryCount: approvedMemories.length
|
|
17670
|
-
});
|
|
17671
|
-
await ws.sendAsync("app_settings_memories_confirmed", {
|
|
17672
|
-
chat_id: event.chatId,
|
|
17673
|
-
app_settings_memories: approvedMemories
|
|
17819
|
+
};
|
|
17820
|
+
const persistSystemMessage = async (systemMessage) => {
|
|
17821
|
+
if (!chatKeyBytes) return;
|
|
17822
|
+
await ws.sendAsync("chat_system_message_added", {
|
|
17823
|
+
chat_id: chatId,
|
|
17824
|
+
message: {
|
|
17825
|
+
message_id: systemMessage.message_id,
|
|
17826
|
+
role: "system",
|
|
17827
|
+
encrypted_content: await encryptWithAesGcmCombined(
|
|
17828
|
+
systemMessage.content,
|
|
17829
|
+
chatKeyBytes
|
|
17830
|
+
),
|
|
17831
|
+
created_at: systemMessage.created_at,
|
|
17832
|
+
user_message_id: systemMessage.user_message_id
|
|
17833
|
+
}
|
|
17674
17834
|
});
|
|
17675
|
-
await
|
|
17835
|
+
await ws.waitForMessage(
|
|
17836
|
+
"system_message_confirmed",
|
|
17837
|
+
(payload) => {
|
|
17838
|
+
const p = payload;
|
|
17839
|
+
return p.message_id === systemMessage.message_id;
|
|
17840
|
+
},
|
|
17841
|
+
2e4
|
|
17842
|
+
);
|
|
17843
|
+
};
|
|
17844
|
+
const persistMemoryRequestSystemMessage = async (event) => {
|
|
17845
|
+
const requestId = event.requestId ?? `${messageId2}-memory-request`;
|
|
17846
|
+
await persistSystemMessage(buildAppSettingsMemoryRequestSystemMessage({
|
|
17676
17847
|
userMessageId: messageId2,
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17848
|
+
requestId,
|
|
17849
|
+
requestedKeys: event.requestedKeys,
|
|
17850
|
+
entryCounts: memoryCountsLoaded ? new Map(event.requestedKeys.map((key) => [key, availableMemories.filter((memory) => `${memory.app_id}-${memory.item_type}` === key).length])) : void 0,
|
|
17680
17851
|
createdAt: Math.floor(Date.now() / 1e3)
|
|
17681
17852
|
}));
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17686
|
-
|
|
17687
|
-
|
|
17688
|
-
|
|
17689
|
-
|
|
17690
|
-
|
|
17691
|
-
|
|
17692
|
-
|
|
17693
|
-
|
|
17694
|
-
|
|
17695
|
-
|
|
17696
|
-
if (!chatKeyBytes || events.length === 0) return;
|
|
17697
|
-
for (const event of events) {
|
|
17698
|
-
if (persistedTaskEventIds.has(event.event_id)) continue;
|
|
17699
|
-
await this.persistEncryptedSystemMessage(
|
|
17700
|
-
ws,
|
|
17701
|
-
await buildTaskEventSystemMessage({
|
|
17702
|
-
chatKey: chatKeyBytes,
|
|
17703
|
-
userMessageId: messageId2,
|
|
17704
|
-
event
|
|
17705
|
-
}),
|
|
17706
|
-
chatId
|
|
17707
|
-
);
|
|
17708
|
-
persistedTaskEventIds.add(event.event_id);
|
|
17709
|
-
}
|
|
17710
|
-
};
|
|
17711
|
-
const streamOpts = {
|
|
17712
|
-
waitForPostProcessing: false,
|
|
17713
|
-
onStream: onTurnStream,
|
|
17714
|
-
onSubChatEvent: handleSubChatEvent,
|
|
17715
|
-
onAppSettingsMemoriesRequest: handleAppSettingsMemoriesRequest,
|
|
17716
|
-
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
17717
|
-
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
17718
|
-
};
|
|
17719
|
-
dispatchHandlersReady();
|
|
17720
|
-
if (!shouldWaitForAi) {
|
|
17721
|
-
ws.close();
|
|
17722
|
-
} else if (params.incognito) {
|
|
17723
|
-
try {
|
|
17724
|
-
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId2, chatId, {
|
|
17725
|
-
...streamOpts,
|
|
17726
|
-
timeoutMs: params.responseTimeoutMs,
|
|
17727
|
-
recoveryTurnId: savedTurnId
|
|
17728
|
-
}));
|
|
17729
|
-
assistantMessageId = resp.messageId;
|
|
17730
|
-
assistant = resp.content;
|
|
17731
|
-
category = resp.category;
|
|
17732
|
-
modelName = resp.modelName;
|
|
17733
|
-
tokenUsage = resp.tokenUsage;
|
|
17734
|
-
promptBudget = resp.promptBudget;
|
|
17735
|
-
taskEvents = resp.taskEvents;
|
|
17736
|
-
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
17737
|
-
subChatEvents = resp.subChatEvents;
|
|
17738
|
-
if (resp.status === "waiting_for_user") {
|
|
17739
|
-
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
17740
|
-
return {
|
|
17741
|
-
status: resp.status,
|
|
17742
|
-
chatId,
|
|
17743
|
-
messageId: assistantMessageId,
|
|
17744
|
-
assistant,
|
|
17745
|
-
category,
|
|
17746
|
-
modelName,
|
|
17747
|
-
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
17748
|
-
followUpSuggestions,
|
|
17749
|
-
taskProposals,
|
|
17750
|
-
taskUpdateProposals,
|
|
17751
|
-
taskEvents,
|
|
17752
|
-
pendingTaskUpdateJobs,
|
|
17753
|
-
subChatEvents,
|
|
17754
|
-
appSettingsMemoryRequests,
|
|
17755
|
-
tokenUsage,
|
|
17756
|
-
promptBudget
|
|
17757
|
-
};
|
|
17758
|
-
}
|
|
17759
|
-
} finally {
|
|
17760
|
-
ws.close();
|
|
17761
|
-
}
|
|
17762
|
-
} else {
|
|
17763
|
-
try {
|
|
17764
|
-
const recoverCurrentTurnOutputs = async () => {
|
|
17765
|
-
if (recoveryDiscovery) {
|
|
17766
|
-
try {
|
|
17767
|
-
await recoveryDiscovery;
|
|
17768
|
-
} catch (error) {
|
|
17769
|
-
if (!useDeferredContext) throw error;
|
|
17770
|
-
return;
|
|
17771
|
-
}
|
|
17772
|
-
}
|
|
17773
|
-
const pages = ws.drainAvailableRecoveryOutputPages();
|
|
17774
|
-
if (pages.length === 0) return;
|
|
17775
|
-
if (!ownerId2) throw new Error("Saved chat recovery output requires the owner identity.");
|
|
17776
|
-
if (pages.some((page) => page.some((output) => output.root_chat_id === chatId || output.target_chat_id === chatId)) && (!chatKeyBytes || !encryptedChatKey)) {
|
|
17777
|
-
throw new Error("Saved chat recovery output requires the root chat key.");
|
|
17853
|
+
};
|
|
17854
|
+
const responseCategoriesFromMemories = (approvedMemories) => {
|
|
17855
|
+
const counts = /* @__PURE__ */ new Map();
|
|
17856
|
+
for (const memory of approvedMemories) {
|
|
17857
|
+
const key = `${memory.app_id}-${memory.item_key}`;
|
|
17858
|
+
const existing = counts.get(key);
|
|
17859
|
+
if (existing) {
|
|
17860
|
+
existing.entryCount += 1;
|
|
17861
|
+
} else {
|
|
17862
|
+
counts.set(key, {
|
|
17863
|
+
appId: memory.app_id,
|
|
17864
|
+
itemType: memory.item_key,
|
|
17865
|
+
entryCount: 1
|
|
17866
|
+
});
|
|
17778
17867
|
}
|
|
17779
|
-
|
|
17780
|
-
|
|
17781
|
-
|
|
17782
|
-
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17786
|
-
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
17868
|
+
}
|
|
17869
|
+
return [...counts.values()];
|
|
17870
|
+
};
|
|
17871
|
+
const handleAppSettingsMemoriesRequest = async (event) => {
|
|
17872
|
+
await persistMemoryRequestSystemMessage(event);
|
|
17873
|
+
if (params.autoApproveMemories) {
|
|
17874
|
+
const requested = new Set(event.requestedKeys);
|
|
17875
|
+
const unadvertisedKeys = event.requestedKeys.filter(
|
|
17876
|
+
(key) => !memoryMetadataKeys.includes(key)
|
|
17877
|
+
);
|
|
17878
|
+
if (unadvertisedKeys.length > 0) {
|
|
17879
|
+
throw new Error(
|
|
17880
|
+
`Refusing to auto-approve unadvertised memory categories: ${unadvertisedKeys.join(", ")}`
|
|
17881
|
+
);
|
|
17792
17882
|
}
|
|
17793
|
-
|
|
17794
|
-
|
|
17795
|
-
|
|
17796
|
-
|
|
17797
|
-
|
|
17798
|
-
|
|
17799
|
-
|
|
17800
|
-
|
|
17801
|
-
|
|
17802
|
-
|
|
17803
|
-
|
|
17804
|
-
|
|
17805
|
-
|
|
17806
|
-
|
|
17807
|
-
)] : [],
|
|
17808
|
-
...pausedHandoff ? [pausedHandoff.then(
|
|
17809
|
-
(frame) => ({ kind: "paused", frame }),
|
|
17810
|
-
() => new Promise(() => {
|
|
17811
|
-
})
|
|
17812
|
-
)] : []
|
|
17813
|
-
]);
|
|
17814
|
-
if (outcome.kind === "paused") {
|
|
17815
|
-
void responsePromise.catch(() => {
|
|
17883
|
+
const approvedMemories = availableMemories.filter((memory) => requested.has(`${memory.app_id}-${memory.item_type}`)).map((memory) => ({
|
|
17884
|
+
app_id: memory.app_id,
|
|
17885
|
+
item_key: memory.item_type,
|
|
17886
|
+
content: memory.data
|
|
17887
|
+
}));
|
|
17888
|
+
const approvedKeys = [
|
|
17889
|
+
...new Set(approvedMemories.map((memory) => `${memory.app_id}-${memory.item_key}`))
|
|
17890
|
+
];
|
|
17891
|
+
const categories = responseCategoriesFromMemories(approvedMemories);
|
|
17892
|
+
appSettingsMemoryRequests.push({
|
|
17893
|
+
requestId: event.requestId,
|
|
17894
|
+
requestedKeys: event.requestedKeys,
|
|
17895
|
+
approvedKeys,
|
|
17896
|
+
entryCount: approvedMemories.length
|
|
17816
17897
|
});
|
|
17817
|
-
|
|
17818
|
-
|
|
17819
|
-
|
|
17820
|
-
void responsePromise.catch(() => {
|
|
17898
|
+
await ws.sendAsync("app_settings_memories_confirmed", {
|
|
17899
|
+
chat_id: event.chatId,
|
|
17900
|
+
app_settings_memories: approvedMemories
|
|
17821
17901
|
});
|
|
17822
|
-
|
|
17823
|
-
return {
|
|
17824
|
-
status: "queued",
|
|
17825
|
-
chatId,
|
|
17826
|
-
messageId: null,
|
|
17902
|
+
await persistSystemMessage(buildAppSettingsMemoryResponseSystemMessage({
|
|
17827
17903
|
userMessageId: messageId2,
|
|
17828
|
-
|
|
17829
|
-
|
|
17830
|
-
|
|
17831
|
-
|
|
17832
|
-
|
|
17833
|
-
|
|
17834
|
-
followUpSuggestions: [],
|
|
17835
|
-
taskProposals: [],
|
|
17836
|
-
taskUpdateProposals: [],
|
|
17837
|
-
taskEvents: [],
|
|
17838
|
-
pendingTaskUpdateJobs: [],
|
|
17839
|
-
subChatEvents: [],
|
|
17840
|
-
appSettingsMemoryRequests: [],
|
|
17841
|
-
tokenUsage: null,
|
|
17842
|
-
promptBudget: null
|
|
17843
|
-
};
|
|
17904
|
+
messageId: `${messageId2}-memory-response`,
|
|
17905
|
+
action: "included",
|
|
17906
|
+
categories,
|
|
17907
|
+
createdAt: Math.floor(Date.now() / 1e3)
|
|
17908
|
+
}));
|
|
17909
|
+
return;
|
|
17844
17910
|
}
|
|
17845
|
-
|
|
17846
|
-
|
|
17847
|
-
|
|
17848
|
-
|
|
17849
|
-
|
|
17850
|
-
|
|
17851
|
-
|
|
17852
|
-
|
|
17853
|
-
|
|
17854
|
-
|
|
17855
|
-
|
|
17856
|
-
|
|
17857
|
-
|
|
17858
|
-
|
|
17859
|
-
|
|
17860
|
-
await
|
|
17861
|
-
|
|
17862
|
-
|
|
17863
|
-
|
|
17864
|
-
|
|
17865
|
-
|
|
17866
|
-
|
|
17867
|
-
|
|
17868
|
-
|
|
17869
|
-
|
|
17870
|
-
followUpSuggestions,
|
|
17871
|
-
taskProposals,
|
|
17872
|
-
taskUpdateProposals,
|
|
17873
|
-
taskEvents,
|
|
17874
|
-
pendingTaskUpdateJobs,
|
|
17875
|
-
subChatEvents,
|
|
17876
|
-
appSettingsMemoryRequests,
|
|
17877
|
-
tokenUsage,
|
|
17878
|
-
promptBudget
|
|
17879
|
-
};
|
|
17911
|
+
appSettingsMemoryRequests.push({
|
|
17912
|
+
requestId: event.requestId,
|
|
17913
|
+
requestedKeys: event.requestedKeys,
|
|
17914
|
+
approvedKeys: [],
|
|
17915
|
+
entryCount: 0
|
|
17916
|
+
});
|
|
17917
|
+
throw new Error(
|
|
17918
|
+
`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.`
|
|
17919
|
+
);
|
|
17920
|
+
};
|
|
17921
|
+
const persistedTaskEventIds = /* @__PURE__ */ new Set();
|
|
17922
|
+
const persistTaskEventSystemMessages = async (events) => {
|
|
17923
|
+
if (!chatKeyBytes || events.length === 0) return;
|
|
17924
|
+
for (const event of events) {
|
|
17925
|
+
if (persistedTaskEventIds.has(event.event_id)) continue;
|
|
17926
|
+
await this.persistEncryptedSystemMessage(
|
|
17927
|
+
ws,
|
|
17928
|
+
await buildTaskEventSystemMessage({
|
|
17929
|
+
chatKey: chatKeyBytes,
|
|
17930
|
+
userMessageId: messageId2,
|
|
17931
|
+
event
|
|
17932
|
+
}),
|
|
17933
|
+
chatId
|
|
17934
|
+
);
|
|
17935
|
+
persistedTaskEventIds.add(event.event_id);
|
|
17880
17936
|
}
|
|
17881
|
-
|
|
17882
|
-
|
|
17883
|
-
|
|
17937
|
+
};
|
|
17938
|
+
const streamOpts = {
|
|
17939
|
+
waitForPostProcessing: false,
|
|
17940
|
+
onStream: onTurnStream,
|
|
17941
|
+
onSubChatEvent: handleSubChatEvent,
|
|
17942
|
+
onAppSettingsMemoriesRequest: handleAppSettingsMemoriesRequest,
|
|
17943
|
+
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
17944
|
+
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
17945
|
+
};
|
|
17946
|
+
dispatchHandlersReady();
|
|
17947
|
+
if (!shouldWaitForAi) {
|
|
17948
|
+
ws.close();
|
|
17949
|
+
} else if (params.incognito) {
|
|
17950
|
+
try {
|
|
17951
|
+
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId2, chatId, {
|
|
17952
|
+
...streamOpts,
|
|
17953
|
+
timeoutMs: params.responseTimeoutMs,
|
|
17954
|
+
recoveryTurnId: savedTurnId
|
|
17955
|
+
}));
|
|
17956
|
+
assistantMessageId = resp.messageId;
|
|
17957
|
+
assistant = resp.content;
|
|
17958
|
+
category = resp.category;
|
|
17959
|
+
modelName = resp.modelName;
|
|
17960
|
+
tokenUsage = resp.tokenUsage;
|
|
17961
|
+
promptBudget = resp.promptBudget;
|
|
17962
|
+
taskEvents = resp.taskEvents;
|
|
17963
|
+
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
17964
|
+
subChatEvents = resp.subChatEvents;
|
|
17965
|
+
if (resp.status === "waiting_for_user") {
|
|
17966
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
17967
|
+
return {
|
|
17968
|
+
status: resp.status,
|
|
17969
|
+
chatId,
|
|
17970
|
+
messageId: assistantMessageId,
|
|
17971
|
+
assistant,
|
|
17972
|
+
category,
|
|
17973
|
+
modelName,
|
|
17974
|
+
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
17975
|
+
followUpSuggestions,
|
|
17976
|
+
taskProposals,
|
|
17977
|
+
taskUpdateProposals,
|
|
17978
|
+
taskEvents,
|
|
17979
|
+
pendingTaskUpdateJobs,
|
|
17980
|
+
subChatEvents,
|
|
17981
|
+
appSettingsMemoryRequests,
|
|
17982
|
+
tokenUsage,
|
|
17983
|
+
promptBudget
|
|
17984
|
+
};
|
|
17884
17985
|
}
|
|
17885
|
-
|
|
17886
|
-
|
|
17887
|
-
|
|
17888
|
-
|
|
17889
|
-
|
|
17890
|
-
|
|
17891
|
-
|
|
17892
|
-
const stopWatchingErrors = ws.onMessageType("error", (payload) => {
|
|
17893
|
-
if (payload?.code === "lease_conflict" && payload.job_id === recoveryJobId && payload.request_id === requestId) {
|
|
17894
|
-
matchingLeaseConflict = true;
|
|
17895
|
-
}
|
|
17896
|
-
});
|
|
17897
|
-
const claimPromise = ws.waitForMessage(
|
|
17898
|
-
"recovery_job_claimed",
|
|
17899
|
-
(payload) => {
|
|
17900
|
-
const received = payload;
|
|
17901
|
-
return received.job_id === recoveryJobId && (received.request_id === void 0 || received.request_id === requestId);
|
|
17902
|
-
},
|
|
17903
|
-
claimRetryDeadline === null ? 2e4 : Math.min(2e4, Math.max(1, claimRetryDeadline - Date.now()))
|
|
17904
|
-
);
|
|
17905
|
-
try {
|
|
17986
|
+
} finally {
|
|
17987
|
+
ws.close();
|
|
17988
|
+
}
|
|
17989
|
+
} else {
|
|
17990
|
+
try {
|
|
17991
|
+
const recoverCurrentTurnOutputs = async () => {
|
|
17992
|
+
if (recoveryDiscovery) {
|
|
17906
17993
|
try {
|
|
17907
|
-
await
|
|
17908
|
-
protocol_version: 1,
|
|
17909
|
-
job_id: recoveryJobId,
|
|
17910
|
-
request_id: requestId
|
|
17911
|
-
});
|
|
17994
|
+
await recoveryDiscovery;
|
|
17912
17995
|
} catch (error) {
|
|
17913
|
-
|
|
17914
|
-
|
|
17915
|
-
throw error;
|
|
17996
|
+
if (!useDeferredContext) throw error;
|
|
17997
|
+
return;
|
|
17916
17998
|
}
|
|
17917
|
-
claim = (await claimPromise).payload;
|
|
17918
|
-
break;
|
|
17919
|
-
} catch (error) {
|
|
17920
|
-
if (!(error instanceof WebSocketProtocolError) || error.code !== "lease_conflict" || !matchingLeaseConflict) throw error;
|
|
17921
|
-
claimRetryDeadline ??= Date.now() + Math.min(
|
|
17922
|
-
1e4,
|
|
17923
|
-
Math.max(0, params.responseTimeoutMs ?? 9e4)
|
|
17924
|
-
);
|
|
17925
|
-
const remainingMs = claimRetryDeadline - Date.now();
|
|
17926
|
-
if (remainingMs <= claimRetryDelayMs) throw error;
|
|
17927
|
-
await new Promise((resolve20) => setTimeout(resolve20, claimRetryDelayMs));
|
|
17928
|
-
claimRetryDelayMs = Math.min(claimRetryDelayMs * 2, 2e3);
|
|
17929
|
-
} finally {
|
|
17930
|
-
stopWatchingErrors();
|
|
17931
17999
|
}
|
|
18000
|
+
const pages = ws.drainAvailableRecoveryOutputPages();
|
|
18001
|
+
if (pages.length === 0) return;
|
|
18002
|
+
if (!ownerId2) throw new Error("Saved chat recovery output requires the owner identity.");
|
|
18003
|
+
if (pages.some((page) => page.some((output) => output.root_chat_id === chatId || output.target_chat_id === chatId)) && (!chatKeyBytes || !encryptedChatKey)) {
|
|
18004
|
+
throw new Error("Saved chat recovery output requires the root chat key.");
|
|
18005
|
+
}
|
|
18006
|
+
const cache = loadSyncCache(teamId) ?? {
|
|
18007
|
+
syncedAt: Date.now(),
|
|
18008
|
+
totalChatCount: 0,
|
|
18009
|
+
loadedChatCount: 0,
|
|
18010
|
+
chats: [],
|
|
18011
|
+
embeds: [],
|
|
18012
|
+
embedKeys: [],
|
|
18013
|
+
chatKeyWrappers: []
|
|
18014
|
+
};
|
|
18015
|
+
if (encryptedChatKey) {
|
|
18016
|
+
const root = cache.chats.find((chat) => String(chat.details.id ?? "") === chatId);
|
|
18017
|
+
if (root) root.details.encrypted_chat_key = encryptedChatKey;
|
|
18018
|
+
else cache.chats.push({ details: { id: chatId, encrypted_chat_key: encryptedChatKey }, messages: [] });
|
|
18019
|
+
}
|
|
18020
|
+
await this.replayCurrentTurnRecoveryOutputPages(ws, ownerId2, pages, cache, teamId, chatId);
|
|
18021
|
+
};
|
|
18022
|
+
const responsePromise = precollectedResponse ?? ws.collectAiResponse(messageId2, chatId, {
|
|
18023
|
+
...streamOpts,
|
|
18024
|
+
timeoutMs: params.responseTimeoutMs,
|
|
18025
|
+
recoveryTurnId: savedTurnId
|
|
18026
|
+
});
|
|
18027
|
+
const outcome = await Promise.race([
|
|
18028
|
+
responsePromise.then((response) => ({ kind: "response", response })),
|
|
18029
|
+
...contextAckRejection ? [contextAckRejection] : [],
|
|
18030
|
+
catalogDeliveryFailure,
|
|
18031
|
+
...queuedAdmission ? [queuedAdmission.then(
|
|
18032
|
+
(frame) => ({ kind: "queued", frame }),
|
|
18033
|
+
() => new Promise(() => {
|
|
18034
|
+
})
|
|
18035
|
+
)] : [],
|
|
18036
|
+
...pausedHandoff ? [pausedHandoff.then(
|
|
18037
|
+
(frame) => ({ kind: "paused", frame }),
|
|
18038
|
+
() => new Promise(() => {
|
|
18039
|
+
})
|
|
18040
|
+
)] : []
|
|
18041
|
+
]);
|
|
18042
|
+
if (outcome.kind === "paused") {
|
|
18043
|
+
void responsePromise.catch(() => {
|
|
18044
|
+
});
|
|
18045
|
+
throw new Error("Message saved, but AI handoff is paused. No assistant run started; check the chat before retrying.");
|
|
17932
18046
|
}
|
|
17933
|
-
|
|
17934
|
-
|
|
17935
|
-
const assistantId = typeof claim.assistant_message_id === "string" ? claim.assistant_message_id : null;
|
|
17936
|
-
const keyVersion = typeof claim.chat_key_version === "number" && Number.isSafeInteger(claim.chat_key_version) ? claim.chat_key_version : null;
|
|
17937
|
-
const claimMatchesExpectedTerminal = assistantId && keyVersion === 1 && claim.chat_id === chatId && claim.turn_id === savedTurnId;
|
|
17938
|
-
if (claim.state === "TERMINAL" && claimMatchesExpectedTerminal) {
|
|
17939
|
-
assistant = resp.content;
|
|
17940
|
-
category = resp.category;
|
|
17941
|
-
modelName = resp.modelName;
|
|
17942
|
-
assistantMessageId = assistantId;
|
|
17943
|
-
await this.persistStreamedEmbeds({
|
|
17944
|
-
ws,
|
|
17945
|
-
embeds: resp.embeds,
|
|
17946
|
-
chatId,
|
|
17947
|
-
chatKeyBytes,
|
|
17948
|
-
fallbackMessageId: assistantId,
|
|
17949
|
-
ownerId: ownerId2,
|
|
17950
|
-
teamId
|
|
18047
|
+
if (outcome.kind === "queued") {
|
|
18048
|
+
void responsePromise.catch(() => {
|
|
17951
18049
|
});
|
|
17952
|
-
|
|
17953
|
-
|
|
18050
|
+
const frame = outcome.frame.payload;
|
|
18051
|
+
return {
|
|
18052
|
+
status: "queued",
|
|
18053
|
+
chatId,
|
|
18054
|
+
messageId: null,
|
|
18055
|
+
userMessageId: messageId2,
|
|
18056
|
+
queueMode: frame.queue_mode === "steer" ? "steer" : "follow_up",
|
|
18057
|
+
queueRevision: typeof frame.queue_revision === "number" ? frame.queue_revision : 1,
|
|
18058
|
+
assistant: "",
|
|
18059
|
+
category: null,
|
|
18060
|
+
modelName: null,
|
|
18061
|
+
mateName: null,
|
|
18062
|
+
followUpSuggestions: [],
|
|
18063
|
+
taskProposals: [],
|
|
18064
|
+
taskUpdateProposals: [],
|
|
18065
|
+
taskEvents: [],
|
|
18066
|
+
pendingTaskUpdateJobs: [],
|
|
18067
|
+
subChatEvents: [],
|
|
18068
|
+
appSettingsMemoryRequests: [],
|
|
18069
|
+
tokenUsage: null,
|
|
18070
|
+
promptBudget: null
|
|
18071
|
+
};
|
|
18072
|
+
}
|
|
18073
|
+
const resp = outcome.response;
|
|
18074
|
+
assistantMessageId = resp.messageId;
|
|
18075
|
+
assistant = resp.content;
|
|
18076
|
+
category = resp.category;
|
|
18077
|
+
modelName = resp.modelName;
|
|
18078
|
+
tokenUsage = resp.tokenUsage;
|
|
18079
|
+
promptBudget = resp.promptBudget;
|
|
18080
|
+
followUpSuggestions = resp.followUpSuggestions;
|
|
18081
|
+
suggestedFollowupMessage = resp.suggestedFollowupMessage;
|
|
18082
|
+
taskProposals = resp.taskProposals;
|
|
18083
|
+
taskUpdateProposals = resp.taskUpdateProposals;
|
|
18084
|
+
taskEvents = resp.taskEvents;
|
|
18085
|
+
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
18086
|
+
subChatEvents = resp.subChatEvents;
|
|
18087
|
+
if (resp.status === "waiting_for_user") {
|
|
17954
18088
|
await persistTaskEventSystemMessages(taskEvents);
|
|
17955
18089
|
await recoverCurrentTurnOutputs();
|
|
17956
|
-
await projectFocusCountdown?.flush();
|
|
17957
|
-
await focusPersistence;
|
|
17958
|
-
await chatContextEvents?.flush();
|
|
17959
|
-
if (focusPersistenceError) throw focusPersistenceError;
|
|
17960
|
-
const mateName2 = category ? MATE_NAMES[category] ?? null : null;
|
|
17961
18090
|
return {
|
|
17962
|
-
status:
|
|
18091
|
+
status: resp.status,
|
|
17963
18092
|
chatId,
|
|
17964
18093
|
messageId: assistantMessageId,
|
|
17965
18094
|
assistant,
|
|
17966
18095
|
category,
|
|
17967
18096
|
modelName,
|
|
17968
|
-
mateName:
|
|
18097
|
+
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
17969
18098
|
followUpSuggestions,
|
|
17970
18099
|
taskProposals,
|
|
17971
18100
|
taskUpdateProposals,
|
|
@@ -17977,234 +18106,339 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
17977
18106
|
promptBudget
|
|
17978
18107
|
};
|
|
17979
18108
|
}
|
|
17980
|
-
if (
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
17984
|
-
|
|
17985
|
-
|
|
17986
|
-
|
|
17987
|
-
|
|
17988
|
-
|
|
17989
|
-
|
|
17990
|
-
|
|
17991
|
-
|
|
17992
|
-
|
|
17993
|
-
|
|
17994
|
-
|
|
17995
|
-
|
|
17996
|
-
|
|
17997
|
-
|
|
17998
|
-
|
|
18109
|
+
if (chatKeyBytes) {
|
|
18110
|
+
if (!resp.recoveryJobId || !savedTurnId || !ownerId2) {
|
|
18111
|
+
throw new Error("Saved chat completion did not include recoverable terminal identity.");
|
|
18112
|
+
}
|
|
18113
|
+
const recoveryJobId = resp.recoveryJobId;
|
|
18114
|
+
let claimRetryDeadline = null;
|
|
18115
|
+
let claim;
|
|
18116
|
+
let claimRetryDelayMs = 500;
|
|
18117
|
+
while (true) {
|
|
18118
|
+
const requestId = randomUUID9();
|
|
18119
|
+
let matchingLeaseConflict = false;
|
|
18120
|
+
const stopWatchingErrors = ws.onMessageType("error", (payload) => {
|
|
18121
|
+
if (payload?.code === "lease_conflict" && payload.job_id === recoveryJobId && payload.request_id === requestId) {
|
|
18122
|
+
matchingLeaseConflict = true;
|
|
18123
|
+
}
|
|
18124
|
+
});
|
|
18125
|
+
const claimPromise = ws.waitForMessage(
|
|
18126
|
+
"recovery_job_claimed",
|
|
18127
|
+
(payload) => {
|
|
18128
|
+
const received = payload;
|
|
18129
|
+
return received.job_id === recoveryJobId && (received.request_id === void 0 || received.request_id === requestId);
|
|
18130
|
+
},
|
|
18131
|
+
claimRetryDeadline === null ? 2e4 : Math.min(2e4, Math.max(1, claimRetryDeadline - Date.now()))
|
|
18132
|
+
);
|
|
18133
|
+
try {
|
|
18134
|
+
try {
|
|
18135
|
+
await ws.sendAsync("recovery_job_claim", {
|
|
18136
|
+
protocol_version: 1,
|
|
18137
|
+
job_id: recoveryJobId,
|
|
18138
|
+
request_id: requestId
|
|
18139
|
+
});
|
|
18140
|
+
} catch (error) {
|
|
18141
|
+
void claimPromise.catch(() => {
|
|
18142
|
+
});
|
|
18143
|
+
throw error;
|
|
18144
|
+
}
|
|
18145
|
+
claim = (await claimPromise).payload;
|
|
18146
|
+
break;
|
|
18147
|
+
} catch (error) {
|
|
18148
|
+
if (!(error instanceof WebSocketProtocolError) || error.code !== "lease_conflict" || !matchingLeaseConflict) throw error;
|
|
18149
|
+
claimRetryDeadline ??= Date.now() + Math.min(
|
|
18150
|
+
1e4,
|
|
18151
|
+
Math.max(0, params.responseTimeoutMs ?? 9e4)
|
|
18152
|
+
);
|
|
18153
|
+
const remainingMs = claimRetryDeadline - Date.now();
|
|
18154
|
+
if (remainingMs <= claimRetryDelayMs) throw error;
|
|
18155
|
+
await new Promise((resolve20) => setTimeout(resolve20, claimRetryDelayMs));
|
|
18156
|
+
claimRetryDelayMs = Math.min(claimRetryDelayMs * 2, 2e3);
|
|
18157
|
+
} finally {
|
|
18158
|
+
stopWatchingErrors();
|
|
18159
|
+
}
|
|
18160
|
+
}
|
|
18161
|
+
const leaseToken = typeof claim.lease_token === "string" ? claim.lease_token : null;
|
|
18162
|
+
const leaseGeneration = typeof claim.lease_generation === "number" && Number.isSafeInteger(claim.lease_generation) ? claim.lease_generation : null;
|
|
18163
|
+
const assistantId = typeof claim.assistant_message_id === "string" ? claim.assistant_message_id : null;
|
|
18164
|
+
const keyVersion = typeof claim.chat_key_version === "number" && Number.isSafeInteger(claim.chat_key_version) ? claim.chat_key_version : null;
|
|
18165
|
+
const claimMatchesExpectedTerminal = assistantId && keyVersion === 1 && claim.chat_id === chatId && claim.turn_id === savedTurnId;
|
|
18166
|
+
if (claim.state === "TERMINAL" && claimMatchesExpectedTerminal) {
|
|
18167
|
+
assistant = resp.content;
|
|
18168
|
+
category = resp.category;
|
|
18169
|
+
modelName = resp.modelName;
|
|
18170
|
+
assistantMessageId = assistantId;
|
|
18171
|
+
await this.persistStreamedEmbeds({
|
|
18172
|
+
ws,
|
|
18173
|
+
embeds: resp.embeds,
|
|
18174
|
+
chatId,
|
|
18175
|
+
chatKeyBytes,
|
|
18176
|
+
fallbackMessageId: assistantId,
|
|
18177
|
+
ownerId: ownerId2,
|
|
18178
|
+
teamId
|
|
18179
|
+
});
|
|
18180
|
+
clearSyncCache(teamId);
|
|
18181
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
18182
|
+
await persistTaskEventSystemMessages(taskEvents);
|
|
18183
|
+
await recoverCurrentTurnOutputs();
|
|
18184
|
+
await projectFocusCountdown?.flush();
|
|
18185
|
+
await focusPersistence;
|
|
18186
|
+
await chatContextEvents?.flush();
|
|
18187
|
+
if (focusPersistenceError) throw focusPersistenceError;
|
|
18188
|
+
const mateName2 = category ? MATE_NAMES[category] ?? null : null;
|
|
18189
|
+
return {
|
|
18190
|
+
status: "completed",
|
|
18191
|
+
chatId,
|
|
18192
|
+
messageId: assistantMessageId,
|
|
18193
|
+
assistant,
|
|
18194
|
+
category,
|
|
18195
|
+
modelName,
|
|
18196
|
+
mateName: mateName2,
|
|
18197
|
+
followUpSuggestions,
|
|
18198
|
+
taskProposals,
|
|
18199
|
+
taskUpdateProposals,
|
|
18200
|
+
taskEvents,
|
|
18201
|
+
pendingTaskUpdateJobs,
|
|
18202
|
+
subChatEvents,
|
|
18203
|
+
appSettingsMemoryRequests,
|
|
18204
|
+
tokenUsage,
|
|
18205
|
+
promptBudget
|
|
18206
|
+
};
|
|
18207
|
+
}
|
|
18208
|
+
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") {
|
|
18209
|
+
throw new Error("Recovery job claim returned invalid lease or identity data.");
|
|
18210
|
+
}
|
|
18211
|
+
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
18212
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
17999
18213
|
chatId,
|
|
18000
|
-
turnId: savedTurnId,
|
|
18001
|
-
jobId: recoveryJobId,
|
|
18002
|
-
assistantMessageId: assistantId,
|
|
18003
18214
|
keyVersion
|
|
18215
|
+
);
|
|
18216
|
+
let envelope;
|
|
18217
|
+
try {
|
|
18218
|
+
envelope = JSON.parse(claim.sealed_payload);
|
|
18219
|
+
} catch {
|
|
18220
|
+
throw new Error("Recovery job contained an invalid sealed envelope.");
|
|
18004
18221
|
}
|
|
18005
|
-
|
|
18006
|
-
|
|
18007
|
-
|
|
18008
|
-
|
|
18009
|
-
|
|
18010
|
-
|
|
18011
|
-
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
|
|
18022
|
-
if (Object.keys(recovered).filter((field3) => ![
|
|
18023
|
-
"thinking_content",
|
|
18024
|
-
"thinking_signature",
|
|
18025
|
-
"has_thinking",
|
|
18026
|
-
"thinking_token_count",
|
|
18027
|
-
"thinking_duration_seconds"
|
|
18028
|
-
].includes(field3)).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") {
|
|
18029
|
-
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
18030
|
-
}
|
|
18031
|
-
assistant = recovered.content;
|
|
18032
|
-
category = recovered.category;
|
|
18033
|
-
modelName = recovered.model_name;
|
|
18034
|
-
const completedAt = resp.recoveryCreatedAt ?? Math.floor(Date.now() / 1e3);
|
|
18035
|
-
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
18036
|
-
assistant,
|
|
18037
|
-
chatKeyBytes
|
|
18038
|
-
);
|
|
18039
|
-
const encryptedSenderName = await encryptWithAesGcmCombined(category ? MATE_NAMES[category] ?? "Assistant" : "Assistant", chatKeyBytes);
|
|
18040
|
-
const encryptedCategory = recovered.category ? await encryptWithAesGcmCombined(recovered.category, chatKeyBytes) : void 0;
|
|
18041
|
-
const encryptedModelName = recovered.model_name ? await encryptWithAesGcmCombined(recovered.model_name, chatKeyBytes) : void 0;
|
|
18042
|
-
const terminalPersistPayload = {
|
|
18043
|
-
protocol_version: 1,
|
|
18044
|
-
job_id: recoveryJobId,
|
|
18045
|
-
lease_token: leaseToken,
|
|
18046
|
-
lease_generation: leaseGeneration,
|
|
18047
|
-
encrypted_assistant_message: {
|
|
18048
|
-
client_message_id: assistantId,
|
|
18049
|
-
chat_id: chatId,
|
|
18050
|
-
encrypted_content: encryptedAssistantContent,
|
|
18051
|
-
...await this.encryptThinkingFields(recovered, chatKeyBytes),
|
|
18052
|
-
encrypted_sender_name: encryptedSenderName,
|
|
18053
|
-
encrypted_category: encryptedCategory,
|
|
18054
|
-
encrypted_model_name: encryptedModelName,
|
|
18055
|
-
role: "assistant",
|
|
18056
|
-
user_message_id: messageId2,
|
|
18057
|
-
created_at: completedAt,
|
|
18058
|
-
updated_at: completedAt
|
|
18222
|
+
const plaintext = await openChatCompletionRecoveryEnvelope(
|
|
18223
|
+
envelope,
|
|
18224
|
+
{
|
|
18225
|
+
recoveryPrivateKey: recoveryKeypair.privateKey,
|
|
18226
|
+
ownerId: ownerId2,
|
|
18227
|
+
chatId,
|
|
18228
|
+
turnId: savedTurnId,
|
|
18229
|
+
jobId: recoveryJobId,
|
|
18230
|
+
assistantMessageId: assistantId,
|
|
18231
|
+
keyVersion
|
|
18232
|
+
}
|
|
18233
|
+
);
|
|
18234
|
+
let recovered;
|
|
18235
|
+
try {
|
|
18236
|
+
recovered = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(plaintext));
|
|
18237
|
+
} catch {
|
|
18238
|
+
throw new Error("Recovery job plaintext was not valid UTF-8 JSON.");
|
|
18059
18239
|
}
|
|
18060
|
-
|
|
18061
|
-
|
|
18062
|
-
|
|
18063
|
-
"
|
|
18064
|
-
|
|
18065
|
-
|
|
18240
|
+
const expectedRecoveryFields = [
|
|
18241
|
+
"assistant_message_id",
|
|
18242
|
+
"category",
|
|
18243
|
+
"chat_id",
|
|
18244
|
+
"content",
|
|
18245
|
+
"job_id",
|
|
18246
|
+
"key_version",
|
|
18247
|
+
"model_name",
|
|
18248
|
+
"turn_id"
|
|
18249
|
+
];
|
|
18250
|
+
if (Object.keys(recovered).filter((field3) => ![
|
|
18251
|
+
"thinking_content",
|
|
18252
|
+
"thinking_signature",
|
|
18253
|
+
"has_thinking",
|
|
18254
|
+
"thinking_token_count",
|
|
18255
|
+
"thinking_duration_seconds"
|
|
18256
|
+
].includes(field3)).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") {
|
|
18257
|
+
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
18258
|
+
}
|
|
18259
|
+
assistant = recovered.content;
|
|
18260
|
+
category = recovered.category;
|
|
18261
|
+
modelName = recovered.model_name;
|
|
18262
|
+
const completedAt = resp.recoveryCreatedAt ?? Math.floor(Date.now() / 1e3);
|
|
18263
|
+
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
18264
|
+
assistant,
|
|
18265
|
+
chatKeyBytes
|
|
18066
18266
|
);
|
|
18067
|
-
await
|
|
18068
|
-
|
|
18069
|
-
|
|
18070
|
-
|
|
18071
|
-
|
|
18072
|
-
|
|
18073
|
-
|
|
18074
|
-
|
|
18075
|
-
|
|
18076
|
-
|
|
18077
|
-
|
|
18078
|
-
|
|
18079
|
-
|
|
18080
|
-
|
|
18081
|
-
|
|
18082
|
-
|
|
18083
|
-
|
|
18084
|
-
|
|
18085
|
-
|
|
18086
|
-
|
|
18087
|
-
{ skipTypedRecoveryOutputReplay: true }
|
|
18088
|
-
);
|
|
18089
|
-
const latestChat = latestCache.chats.find(
|
|
18090
|
-
(chat) => String(chat.details.id ?? "") === chatId
|
|
18091
|
-
);
|
|
18092
|
-
const latestMessagesV = typeof latestChat?.details.messages_v === "number" && Number.isSafeInteger(latestChat.details.messages_v) ? latestChat.details.messages_v : null;
|
|
18093
|
-
if (latestMessagesV !== null && latestMessagesV > terminalExpectedMessagesV) {
|
|
18094
|
-
await persistTerminalRecovery(latestMessagesV);
|
|
18095
|
-
terminalExpectedMessagesV = latestMessagesV;
|
|
18096
|
-
terminalPersisted = true;
|
|
18097
|
-
}
|
|
18267
|
+
const encryptedSenderName = await encryptWithAesGcmCombined(category ? MATE_NAMES[category] ?? "Assistant" : "Assistant", chatKeyBytes);
|
|
18268
|
+
const encryptedCategory = recovered.category ? await encryptWithAesGcmCombined(recovered.category, chatKeyBytes) : void 0;
|
|
18269
|
+
const encryptedModelName = recovered.model_name ? await encryptWithAesGcmCombined(recovered.model_name, chatKeyBytes) : void 0;
|
|
18270
|
+
const terminalPersistPayload = {
|
|
18271
|
+
protocol_version: 1,
|
|
18272
|
+
job_id: recoveryJobId,
|
|
18273
|
+
lease_token: leaseToken,
|
|
18274
|
+
lease_generation: leaseGeneration,
|
|
18275
|
+
encrypted_assistant_message: {
|
|
18276
|
+
client_message_id: assistantId,
|
|
18277
|
+
chat_id: chatId,
|
|
18278
|
+
encrypted_content: encryptedAssistantContent,
|
|
18279
|
+
...await this.encryptThinkingFields(recovered, chatKeyBytes),
|
|
18280
|
+
encrypted_sender_name: encryptedSenderName,
|
|
18281
|
+
encrypted_category: encryptedCategory,
|
|
18282
|
+
encrypted_model_name: encryptedModelName,
|
|
18283
|
+
role: "assistant",
|
|
18284
|
+
user_message_id: messageId2,
|
|
18285
|
+
created_at: completedAt,
|
|
18286
|
+
updated_at: completedAt
|
|
18098
18287
|
}
|
|
18099
|
-
|
|
18100
|
-
|
|
18288
|
+
};
|
|
18289
|
+
const persistTerminalRecovery = async (expectedMessagesV) => {
|
|
18290
|
+
const persistedPromise = ws.waitForMessage(
|
|
18291
|
+
"recovery_job_persisted",
|
|
18292
|
+
(payload) => payload.job_id === recoveryJobId,
|
|
18293
|
+
2e4
|
|
18294
|
+
);
|
|
18295
|
+
await ws.sendAsync("recovery_job_persist", {
|
|
18296
|
+
...terminalPersistPayload,
|
|
18297
|
+
expected_messages_v: expectedMessagesV
|
|
18298
|
+
});
|
|
18299
|
+
await persistedPromise;
|
|
18300
|
+
};
|
|
18301
|
+
let terminalPersisted = false;
|
|
18302
|
+
try {
|
|
18303
|
+
await persistTerminalRecovery(terminalExpectedMessagesV);
|
|
18304
|
+
terminalPersisted = true;
|
|
18305
|
+
} catch (error) {
|
|
18306
|
+
if (error instanceof WebSocketProtocolError) {
|
|
18307
|
+
if (error.code === "version_conflict") {
|
|
18308
|
+
const latestCache = await this.ensureSynced(
|
|
18309
|
+
true,
|
|
18310
|
+
[chatId],
|
|
18311
|
+
{ teamId },
|
|
18312
|
+
false,
|
|
18313
|
+
void 0,
|
|
18314
|
+
void 0,
|
|
18315
|
+
{ skipTypedRecoveryOutputReplay: true }
|
|
18316
|
+
);
|
|
18317
|
+
const latestChat = latestCache.chats.find(
|
|
18318
|
+
(chat) => String(chat.details.id ?? "") === chatId
|
|
18319
|
+
);
|
|
18320
|
+
const latestMessagesV = typeof latestChat?.details.messages_v === "number" && Number.isSafeInteger(latestChat.details.messages_v) ? latestChat.details.messages_v : null;
|
|
18321
|
+
if (latestMessagesV !== null && latestMessagesV > terminalExpectedMessagesV) {
|
|
18322
|
+
await persistTerminalRecovery(latestMessagesV);
|
|
18323
|
+
terminalExpectedMessagesV = latestMessagesV;
|
|
18324
|
+
terminalPersisted = true;
|
|
18325
|
+
}
|
|
18326
|
+
}
|
|
18327
|
+
if (!terminalPersisted) {
|
|
18328
|
+
throw new Error(`${error.message} (${error.code})`);
|
|
18329
|
+
}
|
|
18330
|
+
} else {
|
|
18331
|
+
throw error;
|
|
18101
18332
|
}
|
|
18102
|
-
} else {
|
|
18103
|
-
throw error;
|
|
18104
18333
|
}
|
|
18105
|
-
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
18109
|
-
|
|
18110
|
-
await this.persistStreamedEmbeds({
|
|
18111
|
-
ws,
|
|
18112
|
-
embeds: resp.embeds,
|
|
18113
|
-
chatId,
|
|
18114
|
-
chatKeyBytes,
|
|
18115
|
-
fallbackMessageId: assistantId,
|
|
18116
|
-
ownerId: ownerId2,
|
|
18117
|
-
teamId
|
|
18118
|
-
});
|
|
18119
|
-
if (initialHeaderWork) persistOptionalHeader = () => initialHeaderWork;
|
|
18120
|
-
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
18121
|
-
if (taskUpdateJobsEnabled) {
|
|
18122
|
-
const persistedTaskJobIds = await this.persistPendingTaskUpdateJobs({
|
|
18334
|
+
if (!terminalPersisted) {
|
|
18335
|
+
throw new Error("Encrypted completion recovery did not persist terminal state.");
|
|
18336
|
+
}
|
|
18337
|
+
assistantMessageId = assistantId;
|
|
18338
|
+
await this.persistStreamedEmbeds({
|
|
18123
18339
|
ws,
|
|
18124
|
-
|
|
18125
|
-
|
|
18126
|
-
|
|
18127
|
-
|
|
18128
|
-
|
|
18129
|
-
|
|
18130
|
-
requireActiveTurnEvent: true
|
|
18340
|
+
embeds: resp.embeds,
|
|
18341
|
+
chatId,
|
|
18342
|
+
chatKeyBytes,
|
|
18343
|
+
fallbackMessageId: assistantId,
|
|
18344
|
+
ownerId: ownerId2,
|
|
18345
|
+
teamId
|
|
18131
18346
|
});
|
|
18132
|
-
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
|
|
18136
|
-
|
|
18137
|
-
|
|
18138
|
-
|
|
18139
|
-
|
|
18140
|
-
|
|
18141
|
-
|
|
18142
|
-
|
|
18143
|
-
|
|
18144
|
-
|
|
18145
|
-
|
|
18146
|
-
|
|
18147
|
-
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
|
|
18153
|
-
const recommendations = await assessCliProjectAuthoring(this, projectAuthoringAvailable, history);
|
|
18154
|
-
for (const event of recommendations) chatContextEvents.persist(event);
|
|
18155
|
-
} catch {
|
|
18347
|
+
if (initialHeaderWork) persistOptionalHeader = () => initialHeaderWork;
|
|
18348
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
18349
|
+
if (taskUpdateJobsEnabled) {
|
|
18350
|
+
const persistedTaskJobIds = await this.persistPendingTaskUpdateJobs({
|
|
18351
|
+
ws,
|
|
18352
|
+
jobs: pendingTaskUpdateJobs,
|
|
18353
|
+
events: taskEvents,
|
|
18354
|
+
teamId,
|
|
18355
|
+
activeChatId: chatId,
|
|
18356
|
+
activeChatKeyBytes: chatKeyBytes,
|
|
18357
|
+
fallbackUserMessageId: messageId2,
|
|
18358
|
+
requireActiveTurnEvent: true
|
|
18359
|
+
});
|
|
18360
|
+
pendingTaskUpdateJobs = pendingTaskUpdateJobs.filter((job) => !persistedTaskJobIds.has(job.job_id));
|
|
18361
|
+
}
|
|
18362
|
+
await persistTaskEventSystemMessages(taskEvents);
|
|
18363
|
+
await recoverCurrentTurnOutputs();
|
|
18364
|
+
clearSyncCache(teamId);
|
|
18365
|
+
if (optionalMetadata) {
|
|
18366
|
+
metadataHandoffReady = true;
|
|
18367
|
+
metadataHandoffSuggestion = resp.suggestedFollowupMessage;
|
|
18156
18368
|
}
|
|
18157
18369
|
}
|
|
18158
|
-
await chatContextEvents?.flush();
|
|
18159
18370
|
} finally {
|
|
18160
|
-
|
|
18161
|
-
|
|
18162
|
-
|
|
18371
|
+
try {
|
|
18372
|
+
await projectFocusCountdown?.flush();
|
|
18373
|
+
await focusPersistence;
|
|
18374
|
+
if (projectAuthoringAvailable && assistant && chatContextEvents) {
|
|
18375
|
+
try {
|
|
18376
|
+
const history = boundedAuthoringHistory([
|
|
18377
|
+
...messageHistoryForRequest ?? [],
|
|
18378
|
+
{ role: "user", content: finalMessage },
|
|
18379
|
+
{ role: "assistant", content: assistant }
|
|
18380
|
+
]);
|
|
18381
|
+
const recommendations = await assessCliProjectAuthoring(this, projectAuthoringAvailable, history);
|
|
18382
|
+
for (const event of recommendations) chatContextEvents.persist(event);
|
|
18383
|
+
} catch {
|
|
18384
|
+
}
|
|
18385
|
+
}
|
|
18386
|
+
await chatContextEvents?.flush();
|
|
18387
|
+
} finally {
|
|
18388
|
+
projectFocusCountdown?.();
|
|
18389
|
+
chatContextEvents?.stop();
|
|
18390
|
+
if (!metadataHandoffReady) ws.close();
|
|
18391
|
+
}
|
|
18392
|
+
}
|
|
18393
|
+
if (focusPersistenceError) throw focusPersistenceError;
|
|
18394
|
+
if (metadataHandoffReady && optionalMetadata) {
|
|
18395
|
+
metadataTransportOwned = optionalMetadata.handOff(metadataHandoffSuggestion, persistOptionalHeader);
|
|
18396
|
+
if (!metadataTransportOwned) ws.close();
|
|
18163
18397
|
}
|
|
18164
18398
|
}
|
|
18165
|
-
|
|
18166
|
-
if (
|
|
18167
|
-
|
|
18168
|
-
|
|
18399
|
+
const mateName = category ? MATE_NAMES[category] ?? null : null;
|
|
18400
|
+
if (params.interactiveHuman && shouldWaitForAi && !params.incognito) this.rearmSendSocketWhenClosed(ws, teamId);
|
|
18401
|
+
return {
|
|
18402
|
+
status: "completed",
|
|
18403
|
+
chatId,
|
|
18404
|
+
messageId: assistantMessageId,
|
|
18405
|
+
userMessageId: messageId2,
|
|
18406
|
+
assistant,
|
|
18407
|
+
category,
|
|
18408
|
+
modelName,
|
|
18409
|
+
mateName,
|
|
18410
|
+
followUpSuggestions,
|
|
18411
|
+
suggestedFollowupMessage,
|
|
18412
|
+
taskProposals,
|
|
18413
|
+
taskUpdateProposals,
|
|
18414
|
+
taskEvents,
|
|
18415
|
+
pendingTaskUpdateJobs,
|
|
18416
|
+
subChatEvents,
|
|
18417
|
+
appSettingsMemoryRequests,
|
|
18418
|
+
tokenUsage,
|
|
18419
|
+
promptBudget
|
|
18420
|
+
};
|
|
18421
|
+
} finally {
|
|
18422
|
+
stopProjectCatalogResponder();
|
|
18423
|
+
removeContextAckWatcher?.();
|
|
18424
|
+
earlyContextAbort.abort();
|
|
18425
|
+
if (!metadataTransportOwned) {
|
|
18426
|
+
initialHeaderStopped = true;
|
|
18427
|
+
optionalMetadata?.stop();
|
|
18428
|
+
ws.close();
|
|
18169
18429
|
}
|
|
18430
|
+
if (earlyContext) await earlyContext;
|
|
18170
18431
|
}
|
|
18171
|
-
|
|
18172
|
-
|
|
18173
|
-
|
|
18174
|
-
chatId,
|
|
18175
|
-
messageId: assistantMessageId,
|
|
18176
|
-
userMessageId: messageId2,
|
|
18177
|
-
assistant,
|
|
18178
|
-
category,
|
|
18179
|
-
modelName,
|
|
18180
|
-
mateName,
|
|
18181
|
-
followUpSuggestions,
|
|
18182
|
-
suggestedFollowupMessage,
|
|
18183
|
-
taskProposals,
|
|
18184
|
-
taskUpdateProposals,
|
|
18185
|
-
taskEvents,
|
|
18186
|
-
pendingTaskUpdateJobs,
|
|
18187
|
-
subChatEvents,
|
|
18188
|
-
appSettingsMemoryRequests,
|
|
18189
|
-
tokenUsage,
|
|
18190
|
-
promptBudget
|
|
18191
|
-
};
|
|
18192
|
-
} finally {
|
|
18193
|
-
removeContextAckWatcher?.();
|
|
18194
|
-
earlyContextAbort.abort();
|
|
18195
|
-
if (!metadataTransportOwned) {
|
|
18196
|
-
initialHeaderStopped = true;
|
|
18197
|
-
optionalMetadata?.stop();
|
|
18198
|
-
ws.close();
|
|
18199
|
-
}
|
|
18200
|
-
if (earlyContext) await earlyContext;
|
|
18432
|
+
} catch (error) {
|
|
18433
|
+
memorySocket?.ws.close();
|
|
18434
|
+
throw error;
|
|
18201
18435
|
}
|
|
18202
|
-
}
|
|
18203
|
-
|
|
18204
|
-
throw error;
|
|
18436
|
+
} finally {
|
|
18437
|
+
historyAbort.abort();
|
|
18205
18438
|
}
|
|
18206
18439
|
} finally {
|
|
18207
|
-
|
|
18440
|
+
this.foregroundSendCount--;
|
|
18441
|
+
this.maybeRearmSendSocket();
|
|
18208
18442
|
}
|
|
18209
18443
|
}
|
|
18210
18444
|
async persistEncryptedSystemMessage(ws, systemMessage, targetChatId) {
|
|
@@ -18439,6 +18673,30 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18439
18673
|
return handledJobIds;
|
|
18440
18674
|
}
|
|
18441
18675
|
async persistStreamedEmbeds(params) {
|
|
18676
|
+
const assertOwner = this.captureRecoveryOwner();
|
|
18677
|
+
const ws = {
|
|
18678
|
+
waitForMessage: (...args) => {
|
|
18679
|
+
assertOwner();
|
|
18680
|
+
const result = params.ws.waitForMessage(...args).then((value) => {
|
|
18681
|
+
assertOwner();
|
|
18682
|
+
return value;
|
|
18683
|
+
});
|
|
18684
|
+
void result.catch(() => {
|
|
18685
|
+
});
|
|
18686
|
+
return result;
|
|
18687
|
+
},
|
|
18688
|
+
sendAsync: async (...args) => {
|
|
18689
|
+
assertOwner();
|
|
18690
|
+
await params.ws.sendAsync(...args);
|
|
18691
|
+
assertOwner();
|
|
18692
|
+
}
|
|
18693
|
+
};
|
|
18694
|
+
const http = { get: async (path2, headers) => {
|
|
18695
|
+
assertOwner();
|
|
18696
|
+
const result = await this.http.get(path2, headers);
|
|
18697
|
+
assertOwner();
|
|
18698
|
+
return result;
|
|
18699
|
+
} };
|
|
18442
18700
|
const finalized = new Map(
|
|
18443
18701
|
params.embeds.filter((embed) => {
|
|
18444
18702
|
const status = embed.status ?? "finished";
|
|
@@ -18449,7 +18707,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18449
18707
|
for (const parent of finalized.values()) {
|
|
18450
18708
|
for (const childId of parent.embed_ids ?? []) {
|
|
18451
18709
|
if (finalized.has(childId)) continue;
|
|
18452
|
-
const childResponse =
|
|
18710
|
+
const childResponse = ws.waitForMessage(
|
|
18453
18711
|
"send_embed_data",
|
|
18454
18712
|
(payload) => {
|
|
18455
18713
|
const frame = payload;
|
|
@@ -18457,7 +18715,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18457
18715
|
return nested.embed_id === childId;
|
|
18458
18716
|
}
|
|
18459
18717
|
);
|
|
18460
|
-
|
|
18718
|
+
assertOwner();
|
|
18719
|
+
await ws.sendAsync("request_embed", { embed_id: childId });
|
|
18461
18720
|
const envelope = (await childResponse).payload;
|
|
18462
18721
|
const child = envelope.payload ?? envelope;
|
|
18463
18722
|
if (child.parent_embed_id !== parent.embed_id || !child.content || (child.status ?? "finished") !== "finished" || child.chat_id && child.chat_id !== params.chatId && child.chat_id !== computeSHA256(params.chatId)) {
|
|
@@ -18471,7 +18730,6 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18471
18730
|
}
|
|
18472
18731
|
const masterKey = this.getMasterKeyBytes();
|
|
18473
18732
|
const parentKeys = /* @__PURE__ */ new Map();
|
|
18474
|
-
const processed = /* @__PURE__ */ new Set();
|
|
18475
18733
|
const cachedEmbeds = loadSyncCache();
|
|
18476
18734
|
const resolveCachedParentKey = async (parentId) => {
|
|
18477
18735
|
if (!cachedEmbeds) return void 0;
|
|
@@ -18489,6 +18747,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18489
18747
|
) ?? void 0;
|
|
18490
18748
|
};
|
|
18491
18749
|
const persistOne = async (embed, embedKey, isChild) => {
|
|
18750
|
+
assertOwner();
|
|
18492
18751
|
const now = Math.floor(Date.now() / 1e3);
|
|
18493
18752
|
const createdAt = normalizeUnixSeconds(embed.createdAt, now);
|
|
18494
18753
|
const updatedAt = normalizeUnixSeconds(embed.updatedAt, now);
|
|
@@ -18528,14 +18787,15 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18528
18787
|
}
|
|
18529
18788
|
] : [];
|
|
18530
18789
|
const storeRequestId = randomUUID9();
|
|
18531
|
-
const storeConfirmed =
|
|
18790
|
+
const storeConfirmed = ws.waitForMessage(
|
|
18532
18791
|
"store_embed_confirmed",
|
|
18533
18792
|
(payload) => matchesRecoveryEmbedReceipt(payload, storeRequestId, embed.embed_id),
|
|
18534
18793
|
3e4
|
|
18535
18794
|
);
|
|
18536
18795
|
void storeConfirmed.catch(() => {
|
|
18537
18796
|
});
|
|
18538
|
-
|
|
18797
|
+
assertOwner();
|
|
18798
|
+
await ws.sendAsync("store_embed", {
|
|
18539
18799
|
request_id: storeRequestId,
|
|
18540
18800
|
embed_id: embed.embed_id,
|
|
18541
18801
|
encrypted_type: encryptedType,
|
|
@@ -18566,10 +18826,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18566
18826
|
let storedHead = true;
|
|
18567
18827
|
try {
|
|
18568
18828
|
await storeConfirmed;
|
|
18829
|
+
assertOwner();
|
|
18569
18830
|
} catch (error) {
|
|
18570
18831
|
if (!(error instanceof WebSocketProtocolError) || error.code !== "embed_write_denied") throw error;
|
|
18571
18832
|
const query = params.teamId ? `?team_id=${encodeURIComponent(params.teamId)}` : "";
|
|
18572
|
-
const canonical = await
|
|
18833
|
+
const canonical = await http.get(
|
|
18573
18834
|
`/v1/embeds/chats/${encodeURIComponent(params.chatId)}/embeds/${encodeURIComponent(embed.embed_id)}${query}`,
|
|
18574
18835
|
this.getCliRequestHeaders()
|
|
18575
18836
|
);
|
|
@@ -18634,14 +18895,15 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18634
18895
|
const expectedEncryptedType = head.encrypted_type;
|
|
18635
18896
|
const expectedDigest = computeSHA256(expectedCiphertext);
|
|
18636
18897
|
const requestId = randomUUID9();
|
|
18637
|
-
const confirmed =
|
|
18898
|
+
const confirmed = ws.waitForMessage(
|
|
18638
18899
|
"store_embed_confirmed",
|
|
18639
18900
|
(payload) => matchesRecoveryEmbedReceipt(payload, requestId, embed.embed_id),
|
|
18640
18901
|
3e4
|
|
18641
18902
|
);
|
|
18642
18903
|
void confirmed.catch(() => {
|
|
18643
18904
|
});
|
|
18644
|
-
|
|
18905
|
+
assertOwner();
|
|
18906
|
+
await ws.sendAsync("store_embed", {
|
|
18645
18907
|
request_id: requestId,
|
|
18646
18908
|
embed_id: embed.embed_id,
|
|
18647
18909
|
chat_id: params.chatId,
|
|
@@ -18661,7 +18923,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18661
18923
|
if (receipt.canonical_source !== "head" || receipt.canonical_digest !== expectedDigest) {
|
|
18662
18924
|
throw new Error(`Embed ${embed.embed_id} metadata enrichment receipt does not match canonical head.`);
|
|
18663
18925
|
}
|
|
18664
|
-
const enriched = await
|
|
18926
|
+
const enriched = await http.get(
|
|
18665
18927
|
`/v1/embeds/chats/${encodeURIComponent(params.chatId)}/embeds/${encodeURIComponent(embed.embed_id)}${query}`,
|
|
18666
18928
|
this.getCliRequestHeaders()
|
|
18667
18929
|
);
|
|
@@ -18674,7 +18936,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18674
18936
|
}
|
|
18675
18937
|
if (storedHead && keys2.length > 0) {
|
|
18676
18938
|
const keysRequestId = randomUUID9();
|
|
18677
|
-
const keysConfirmed =
|
|
18939
|
+
const keysConfirmed = ws.waitForMessage(
|
|
18678
18940
|
"store_embed_keys_confirmed",
|
|
18679
18941
|
(payload) => {
|
|
18680
18942
|
const p = payload;
|
|
@@ -18682,8 +18944,10 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18682
18944
|
},
|
|
18683
18945
|
3e4
|
|
18684
18946
|
);
|
|
18685
|
-
|
|
18947
|
+
assertOwner();
|
|
18948
|
+
await ws.sendAsync("store_embed_keys", { request_id: keysRequestId, keys: keys2 });
|
|
18686
18949
|
const keysReceipt = (await keysConfirmed).payload;
|
|
18950
|
+
assertOwner();
|
|
18687
18951
|
if (keysReceipt.failed_count !== 0 || keysReceipt.created_count !== keys2.length) {
|
|
18688
18952
|
throw new Error(`Embed ${embed.embed_id} key wrappers were not durably stored.`);
|
|
18689
18953
|
}
|
|
@@ -18698,7 +18962,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18698
18962
|
if (row3.version_number === 1) {
|
|
18699
18963
|
const query = new URLSearchParams({ capability: "bounded-v1", chat_id: params.chatId });
|
|
18700
18964
|
if (params.teamId) query.set("team_id", params.teamId);
|
|
18701
|
-
const existing = await
|
|
18965
|
+
const existing = await http.get(
|
|
18702
18966
|
`/v1/embeds/${encodeURIComponent(embed.embed_id)}/versions/1?${query}`,
|
|
18703
18967
|
this.getCliRequestHeaders()
|
|
18704
18968
|
);
|
|
@@ -18721,7 +18985,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18721
18985
|
const encryptedPatch = typeof row3.patch === "string" ? await encryptWithAesGcmCombined(row3.patch, embedKey) : void 0;
|
|
18722
18986
|
if (!encryptedSnapshot && !encryptedPatch) continue;
|
|
18723
18987
|
const diffRequestId = randomUUID9();
|
|
18724
|
-
const diffConfirmed =
|
|
18988
|
+
const diffConfirmed = ws.waitForMessage(
|
|
18725
18989
|
"store_embed_diff_confirmed",
|
|
18726
18990
|
(payload) => {
|
|
18727
18991
|
const p = payload;
|
|
@@ -18731,7 +18995,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18731
18995
|
);
|
|
18732
18996
|
void diffConfirmed.catch(() => {
|
|
18733
18997
|
});
|
|
18734
|
-
|
|
18998
|
+
assertOwner();
|
|
18999
|
+
await ws.sendAsync("store_embed_diff", {
|
|
18735
19000
|
request_id: diffRequestId,
|
|
18736
19001
|
embed_id: row3.embed_id,
|
|
18737
19002
|
version_number: row3.version_number,
|
|
@@ -18741,6 +19006,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18741
19006
|
created_at: normalizeUnixSeconds(row3.created_at, now)
|
|
18742
19007
|
});
|
|
18743
19008
|
const diffReceipt = (await diffConfirmed).payload;
|
|
19009
|
+
assertOwner();
|
|
18744
19010
|
const expectedDigest = computeSHA256(JSON.stringify([encryptedSnapshot ?? null, encryptedPatch ?? null]));
|
|
18745
19011
|
if (diffReceipt.canonical_source !== "version_row" || diffReceipt.canonical_digest !== expectedDigest) {
|
|
18746
19012
|
throw new Error(`Embed ${embed.embed_id} v${row3.version_number} durable receipt did not match the stored source.`);
|
|
@@ -18748,34 +19014,19 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18748
19014
|
}
|
|
18749
19015
|
}
|
|
18750
19016
|
};
|
|
18751
|
-
|
|
18752
|
-
|
|
18753
|
-
madeProgress = false;
|
|
18754
|
-
for (const embed of finalized.values()) {
|
|
18755
|
-
if (processed.has(embed.embed_id)) continue;
|
|
18756
|
-
const parentId = embed.parent_embed_id || null;
|
|
18757
|
-
if (parentId && finalized.has(parentId) && !parentKeys.has(parentId)) {
|
|
18758
|
-
continue;
|
|
18759
|
-
}
|
|
18760
|
-
const parentKey = parentId ? parentKeys.get(parentId) ?? await resolveCachedParentKey(parentId) : void 0;
|
|
18761
|
-
if (parentId && !parentKey) continue;
|
|
18762
|
-
const embedKey = parentKey ?? await deriveEmbedKeyFromChatKey(params.chatKeyBytes, embed.embed_id);
|
|
18763
|
-
await persistOne(embed, embedKey, Boolean(parentId));
|
|
18764
|
-
processed.add(embed.embed_id);
|
|
18765
|
-
madeProgress = true;
|
|
18766
|
-
}
|
|
18767
|
-
}
|
|
18768
|
-
for (const embed of finalized.values()) {
|
|
18769
|
-
if (processed.has(embed.embed_id)) continue;
|
|
19017
|
+
await runRecoveryBatch([...finalized.values()], async (embed) => {
|
|
19018
|
+
assertOwner();
|
|
18770
19019
|
const parentId = embed.parent_embed_id || null;
|
|
18771
19020
|
const parentKey = parentId ? parentKeys.get(parentId) ?? await resolveCachedParentKey(parentId) : void 0;
|
|
19021
|
+
assertOwner();
|
|
18772
19022
|
if (parentId && !parentKey) {
|
|
18773
19023
|
throw new Error(`Cannot persist child embed ${embed.embed_id}: parent embed key ${parentId} is unavailable.`);
|
|
18774
19024
|
}
|
|
18775
19025
|
const embedKey = parentKey ?? await deriveEmbedKeyFromChatKey(params.chatKeyBytes, embed.embed_id);
|
|
19026
|
+
assertOwner();
|
|
18776
19027
|
await persistOne(embed, embedKey, Boolean(parentId));
|
|
18777
|
-
|
|
18778
|
-
}
|
|
19028
|
+
assertOwner();
|
|
19029
|
+
}, (embed, dependency) => embed.parent_embed_id === dependency.embed_id);
|
|
18779
19030
|
}
|
|
18780
19031
|
async persistPostProcessingMetadata(params) {
|
|
18781
19032
|
const encryptedFollowUps = params.followUpSuggestions.length > 0 ? await encryptWithAesGcmCombined(
|
|
@@ -23103,6 +23354,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
23103
23354
|
interactiveViewerAllowed = false;
|
|
23104
23355
|
interactiveViewerGeneration = 0;
|
|
23105
23356
|
beginInteractiveViewerSession() {
|
|
23357
|
+
this.cancelSendSocketRearm();
|
|
23106
23358
|
this.interactiveAdmissionEnabled = true;
|
|
23107
23359
|
this.interactiveAdmissionOwner = null;
|
|
23108
23360
|
this.interactiveAdmissionAuth = null;
|
|
@@ -23110,6 +23362,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
23110
23362
|
this.preparedSendSocketAttemptOwner = null;
|
|
23111
23363
|
}
|
|
23112
23364
|
endInteractiveViewerSession() {
|
|
23365
|
+
this.cancelSendSocketRearm();
|
|
23113
23366
|
this.interactiveAdmissionEnabled = false;
|
|
23114
23367
|
this.interactiveAdmissionOwner = null;
|
|
23115
23368
|
this.interactiveAdmissionAuth = null;
|
|
@@ -42590,6 +42843,20 @@ async function verifyCaddyCoreRoutes(apiUrl, origin) {
|
|
|
42590
42843
|
"Access-Control-Request-Headers": "content-type"
|
|
42591
42844
|
} });
|
|
42592
42845
|
if (!embedPreflight.ok || embedPreflight.headers.get("access-control-allow-origin") !== origin || embedPreflight.headers.get("access-control-allow-credentials") !== "true") throw new Error("caddy_embed_reference_cors_failed");
|
|
42846
|
+
let accountKeyPreflight;
|
|
42847
|
+
try {
|
|
42848
|
+
accountKeyPreflight = await request("/v1/account/chat-encryption-key", { method: "OPTIONS", headers: {
|
|
42849
|
+
Origin: origin,
|
|
42850
|
+
"Access-Control-Request-Method": "GET",
|
|
42851
|
+
"Access-Control-Request-Headers": "content-type"
|
|
42852
|
+
} });
|
|
42853
|
+
} catch {
|
|
42854
|
+
throw new Error("caddy_account_chat_key_preflight_failed");
|
|
42855
|
+
}
|
|
42856
|
+
const accountKeyMethods = accountKeyPreflight.headers.get("access-control-allow-methods")?.split(",").map((value) => value.trim().toUpperCase()) ?? [];
|
|
42857
|
+
if (accountKeyPreflight.status !== 200 || accountKeyPreflight.headers.get("access-control-allow-origin") !== origin || accountKeyPreflight.headers.get("access-control-allow-credentials") !== "true" || !accountKeyMethods.includes("GET")) {
|
|
42858
|
+
throw new Error("caddy_account_chat_key_preflight_failed");
|
|
42859
|
+
}
|
|
42593
42860
|
const availability = await request("/v1/features/availability");
|
|
42594
42861
|
if (!availability.ok) throw new Error("caddy_availability_route_failed");
|
|
42595
42862
|
const features = await availability.json();
|