openmates 0.27.0-alpha.80 → 0.27.0-alpha.81
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-EGFP6UOU.js} +1996 -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 {
|
|
@@ -13605,6 +13644,39 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13605
13644
|
chatActivitySocket = null;
|
|
13606
13645
|
preparedSendSocket = null;
|
|
13607
13646
|
preparedSendSocketAttemptOwner = null;
|
|
13647
|
+
foregroundSendCount = 0;
|
|
13648
|
+
pendingSendSocketRearm = null;
|
|
13649
|
+
cancelSendSocketRearm() {
|
|
13650
|
+
const pending = this.pendingSendSocketRearm;
|
|
13651
|
+
this.pendingSendSocketRearm = null;
|
|
13652
|
+
pending?.removeClose();
|
|
13653
|
+
}
|
|
13654
|
+
maybeRearmSendSocket() {
|
|
13655
|
+
const pending = this.pendingSendSocketRearm;
|
|
13656
|
+
if (!pending || this.foregroundSendCount > 0 || pending.ws.isOpen()) return;
|
|
13657
|
+
this.cancelSendSocketRearm();
|
|
13658
|
+
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;
|
|
13659
|
+
this.preparedSendSocketAttemptOwner = null;
|
|
13660
|
+
this.warmSendSocket();
|
|
13661
|
+
}
|
|
13662
|
+
rearmSendSocketWhenClosed(ws, teamId) {
|
|
13663
|
+
if (!this.interactiveViewerAllowed || !this.hasSession()) return;
|
|
13664
|
+
this.cancelSendSocketRearm();
|
|
13665
|
+
const session = this.requireSession();
|
|
13666
|
+
const pending = {
|
|
13667
|
+
ws,
|
|
13668
|
+
teamId,
|
|
13669
|
+
owner: this.preparedChatOwner(session, teamId),
|
|
13670
|
+
ownerKey: this.sendSocketOwnerKey(session, teamId),
|
|
13671
|
+
removeClose: () => {
|
|
13672
|
+
}
|
|
13673
|
+
};
|
|
13674
|
+
this.pendingSendSocketRearm = pending;
|
|
13675
|
+
pending.removeClose = ws.onClose(() => {
|
|
13676
|
+
this.maybeRearmSendSocket();
|
|
13677
|
+
});
|
|
13678
|
+
this.maybeRearmSendSocket();
|
|
13679
|
+
}
|
|
13608
13680
|
sendSocketOwnerKey(session, teamId) {
|
|
13609
13681
|
return JSON.stringify([
|
|
13610
13682
|
this.preparedChatOwner(session, teamId),
|
|
@@ -13631,7 +13703,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13631
13703
|
}
|
|
13632
13704
|
/** Prepare one authenticated send socket for the interactive TUI's next turn. */
|
|
13633
13705
|
warmSendSocket() {
|
|
13634
|
-
if (!this.interactiveViewerAllowed || this.explicitSession || !this.hasSession()) return;
|
|
13706
|
+
if (!this.interactiveViewerAllowed || this.foregroundSendCount > 0 || this.explicitSession || !this.hasSession()) return;
|
|
13635
13707
|
const session = this.requireSession();
|
|
13636
13708
|
const teamId = this.getActiveTeamId();
|
|
13637
13709
|
const owner2 = this.preparedChatOwner(session, teamId);
|
|
@@ -15562,14 +15634,32 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15562
15634
|
}
|
|
15563
15635
|
return { bySubject, acknowledged: /* @__PURE__ */ new Map(), visiting: /* @__PURE__ */ new Set() };
|
|
15564
15636
|
}
|
|
15637
|
+
captureRecoveryOwner() {
|
|
15638
|
+
const account = this.captureTeamAccountOwner();
|
|
15639
|
+
const team = this.getActiveTeamId();
|
|
15640
|
+
const master = Buffer.from(this.getMasterKeyBytes());
|
|
15641
|
+
return () => {
|
|
15642
|
+
if (!this.hasSession() || !account.isCurrent() || this.getActiveTeamId() !== team || !master.equals(Buffer.from(this.getMasterKeyBytes()))) {
|
|
15643
|
+
throw new Error("Recovery account, Team or key changed; outputs remain pending.");
|
|
15644
|
+
}
|
|
15645
|
+
};
|
|
15646
|
+
}
|
|
15565
15647
|
async persistAvailableRecoveryOutputs(ws, ownerId2, cache, outputs, teamId, dependencies = this.recoveryReplayDependencies([outputs])) {
|
|
15566
15648
|
if (outputs.length > 100) throw new Error("Recovery output discovery exceeded the bounded batch size.");
|
|
15567
15649
|
const order = { message: 0, embed: 1, diff: 2, summary: 3, checkpoint: 4 };
|
|
15568
15650
|
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);
|
|
15651
|
+
const assertOwner = this.captureRecoveryOwner();
|
|
15569
15652
|
const masterKey = this.getMasterKeyBytes();
|
|
15570
15653
|
const wrappingKey = await this.getChatWrappingKey(teamId, masterKey);
|
|
15654
|
+
const http = { get: async (path2, headers) => {
|
|
15655
|
+
assertOwner();
|
|
15656
|
+
const result = await this.http.get(path2, headers);
|
|
15657
|
+
assertOwner();
|
|
15658
|
+
return result;
|
|
15659
|
+
} };
|
|
15571
15660
|
let acknowledged = 0;
|
|
15572
15661
|
const request = async (type, responseType, recordId, payload) => {
|
|
15662
|
+
assertOwner();
|
|
15573
15663
|
const requestId = randomUUID9();
|
|
15574
15664
|
const response = ws.waitForMessage(responseType, (value) => {
|
|
15575
15665
|
const frame = value;
|
|
@@ -15577,10 +15667,14 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15577
15667
|
}, 2e4);
|
|
15578
15668
|
void response.catch(() => {
|
|
15579
15669
|
});
|
|
15670
|
+
assertOwner();
|
|
15580
15671
|
await ws.sendAsync(type, { protocol_version: 1, record_id: recordId, request_id: requestId, ...payload });
|
|
15581
|
-
|
|
15672
|
+
const received = await response;
|
|
15673
|
+
assertOwner();
|
|
15674
|
+
return received.payload;
|
|
15582
15675
|
};
|
|
15583
15676
|
const storeWithReceipt = async (type, responseType, embedId, payload, recoveryRecordId, version2) => {
|
|
15677
|
+
assertOwner();
|
|
15584
15678
|
const requestId = randomUUID9();
|
|
15585
15679
|
const receipt = ws.waitForMessage(
|
|
15586
15680
|
responseType,
|
|
@@ -15589,8 +15683,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15589
15683
|
);
|
|
15590
15684
|
void receipt.catch(() => {
|
|
15591
15685
|
});
|
|
15686
|
+
assertOwner();
|
|
15592
15687
|
await ws.sendAsync(type, { ...payload, request_id: requestId, recovery_record_id: recoveryRecordId });
|
|
15593
|
-
|
|
15688
|
+
const received = await receipt;
|
|
15689
|
+
assertOwner();
|
|
15690
|
+
return received.payload;
|
|
15594
15691
|
};
|
|
15595
15692
|
const reconcileAcknowledgedEmbed = async (output, chatKey, sealedContent) => {
|
|
15596
15693
|
if (output.output_kind !== "embed" && output.output_kind !== "diff") {
|
|
@@ -15613,7 +15710,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15613
15710
|
throw new Error(`Recovery output ${output.record_id} lacks verified canonical acknowledgement.`);
|
|
15614
15711
|
}
|
|
15615
15712
|
const embedPath = `/v1/embeds/chats/${encodeURIComponent(output.target_chat_id)}/embeds/${encodeURIComponent(output.subject_id)}` + (teamId ? `?team_id=${encodeURIComponent(teamId)}` : "");
|
|
15616
|
-
const canonical = await
|
|
15713
|
+
const canonical = await http.get(embedPath, this.getCliRequestHeaders());
|
|
15617
15714
|
const row3 = canonical.data?.embed;
|
|
15618
15715
|
if (!canonical.ok || !row3 || row3.hashed_chat_id !== computeSHA256(output.target_chat_id) || typeof row3.encrypted_content !== "string" || typeof row3.encrypted_type !== "string") {
|
|
15619
15716
|
throw new Error(`Recovery output ${output.record_id} canonical embed is unavailable.`);
|
|
@@ -15638,7 +15735,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15638
15735
|
} else {
|
|
15639
15736
|
const query = new URLSearchParams({ capability: "bounded-v1", chat_id: output.target_chat_id });
|
|
15640
15737
|
if (teamId) query.set("team_id", teamId);
|
|
15641
|
-
const history = await
|
|
15738
|
+
const history = await http.get(
|
|
15642
15739
|
`/v1/embeds/${encodeURIComponent(output.subject_id)}/versions/${output.output_version}?${query}`,
|
|
15643
15740
|
this.getCliRequestHeaders()
|
|
15644
15741
|
);
|
|
@@ -15652,12 +15749,14 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15652
15749
|
if (typeof encryptedPatch === "string") await decryptWithAesGcmCombined(encryptedPatch, embedKey);
|
|
15653
15750
|
}
|
|
15654
15751
|
};
|
|
15655
|
-
|
|
15752
|
+
const rootKeys = /* @__PURE__ */ new Map();
|
|
15753
|
+
const prepareOne = async (output) => {
|
|
15754
|
+
assertOwner();
|
|
15656
15755
|
const identity3 = recoveryOutputIdentity(output);
|
|
15657
15756
|
const committed = dependencies.acknowledged.get(output.record_id);
|
|
15658
15757
|
if (committed !== void 0) {
|
|
15659
15758
|
if (committed !== identity3) throw new Error("Acknowledged recovery dependency changed identity.");
|
|
15660
|
-
|
|
15759
|
+
return null;
|
|
15661
15760
|
}
|
|
15662
15761
|
if (dependencies.visiting.has(output.record_id) || dependencies.visiting.size >= 32) {
|
|
15663
15762
|
throw new Error("Recovery embed dependency is cyclic or exceeds the bounded depth.");
|
|
@@ -15665,26 +15764,35 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15665
15764
|
if (output.root_hashed_team_id !== void 0 && output.root_hashed_team_id !== (teamId ? computeSHA256(teamId) : null)) {
|
|
15666
15765
|
throw new Error(`Recovery output ${output.record_id} Team key context is unavailable; output remains pending.`);
|
|
15667
15766
|
}
|
|
15668
|
-
let
|
|
15669
|
-
|
|
15670
|
-
|
|
15671
|
-
|
|
15672
|
-
|
|
15673
|
-
|
|
15674
|
-
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15767
|
+
let rootKeyPromise = rootKeys.get(output.root_chat_id);
|
|
15768
|
+
if (!rootKeyPromise) {
|
|
15769
|
+
rootKeyPromise = (async () => {
|
|
15770
|
+
let root = cache.chats.find((chat) => String(chat.details.id ?? "") === output.root_chat_id);
|
|
15771
|
+
let rootKey2 = root ? await this.resolveChatKey(cache, root, wrappingKey, teamId) : null;
|
|
15772
|
+
if (!rootKey2) {
|
|
15773
|
+
const response = await http.get(
|
|
15774
|
+
`/v1/chats/${encodeURIComponent(output.root_chat_id)}/wrappers/window` + (teamId ? `?team_id=${encodeURIComponent(teamId)}` : ""),
|
|
15775
|
+
this.getCliRequestHeaders()
|
|
15776
|
+
);
|
|
15777
|
+
if (!response.ok) throw new Error(`Recovery root key lookup failed (HTTP ${response.status}); output remains pending.`);
|
|
15778
|
+
const keyType = teamId ? "team" : "master";
|
|
15779
|
+
const wrapper = (response.data.wrappers ?? []).find((item) => item.key_type === keyType && typeof item.encrypted_chat_key === "string" && item.encrypted_chat_key);
|
|
15780
|
+
if (!wrapper) throw new Error(`Recovery root chat ${output.root_chat_id} key wrapper is unavailable.`);
|
|
15781
|
+
cache.chatKeyWrappers ??= [];
|
|
15782
|
+
cache.chatKeyWrappers.push(wrapper);
|
|
15783
|
+
if (!root) {
|
|
15784
|
+
root = { details: { id: output.root_chat_id, encrypted_chat_key: wrapper.encrypted_chat_key }, messages: [] };
|
|
15785
|
+
cache.chats.push(root);
|
|
15786
|
+
}
|
|
15787
|
+
rootKey2 = await this.resolveChatKey(cache, root, wrappingKey, teamId);
|
|
15788
|
+
}
|
|
15789
|
+
if (!rootKey2) throw new Error(`Recovery root chat ${output.root_chat_id} key is unavailable.`);
|
|
15790
|
+
return rootKey2;
|
|
15791
|
+
})();
|
|
15792
|
+
rootKeys.set(output.root_chat_id, rootKeyPromise);
|
|
15793
|
+
}
|
|
15794
|
+
const rootKey = await rootKeyPromise;
|
|
15795
|
+
assertOwner();
|
|
15688
15796
|
let ready2;
|
|
15689
15797
|
try {
|
|
15690
15798
|
ready2 = await request("recovery_output_get", "recovery_output_ready", output.record_id, {});
|
|
@@ -15693,7 +15801,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15693
15801
|
await reconcileAcknowledgedEmbed(output, rootKey);
|
|
15694
15802
|
dependencies.acknowledged.set(output.record_id, identity3);
|
|
15695
15803
|
acknowledged += 1;
|
|
15696
|
-
|
|
15804
|
+
return null;
|
|
15697
15805
|
}
|
|
15698
15806
|
for (const field3 of ["record_id", "root_chat_id", "target_chat_id", "turn_id", "subject_id", "output_kind", "output_version", "chat_key_version"]) {
|
|
15699
15807
|
if (ready2[field3] !== output[field3]) throw new Error(`Recovery output ${output.record_id} identity mismatch: ${field3}.`);
|
|
@@ -15729,6 +15837,27 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15729
15837
|
throw new Error("Recovery output plaintext identity mismatch.");
|
|
15730
15838
|
}
|
|
15731
15839
|
const content = recovered.content;
|
|
15840
|
+
assertOwner();
|
|
15841
|
+
if (typeof content.parent_embed_id === "string") {
|
|
15842
|
+
const candidates = (dependencies.bySubject.get(content.parent_embed_id) ?? []).filter((candidate) => candidate.output_kind === "embed");
|
|
15843
|
+
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))) {
|
|
15844
|
+
const parentPath = `/v1/embeds/chats/${encodeURIComponent(output.target_chat_id)}/embeds/${encodeURIComponent(content.parent_embed_id)}` + (teamId ? `?team_id=${encodeURIComponent(teamId)}` : "");
|
|
15845
|
+
const parent = await http.get(parentPath, this.getCliRequestHeaders());
|
|
15846
|
+
if (!parent.ok) throw new Error("Canonical recovery parent embed key scope was unavailable.");
|
|
15847
|
+
}
|
|
15848
|
+
}
|
|
15849
|
+
return { output, ready: ready2, content, rootKey, identity: identity3 };
|
|
15850
|
+
};
|
|
15851
|
+
const prepared = [];
|
|
15852
|
+
for (const output of ordered) prepared.push(await prepareOne(output));
|
|
15853
|
+
const persistOne = async ({ output, ready: ready2, content, rootKey, identity: identity3 }) => {
|
|
15854
|
+
assertOwner();
|
|
15855
|
+
const committed = dependencies.acknowledged.get(output.record_id);
|
|
15856
|
+
if (committed !== void 0) {
|
|
15857
|
+
if (committed !== identity3) throw new Error("Acknowledged recovery dependency changed identity.");
|
|
15858
|
+
return;
|
|
15859
|
+
}
|
|
15860
|
+
const messageRole = output.message_role ?? "assistant";
|
|
15732
15861
|
const childKey = rootKey;
|
|
15733
15862
|
const now = Math.floor(Date.now() / 1e3);
|
|
15734
15863
|
const encryptedChatKey = typeof ready2.encrypted_chat_key === "string" && ready2.encrypted_chat_key ? ready2.encrypted_chat_key : await encryptBytesWithAesGcm(childKey, wrappingKey);
|
|
@@ -15803,6 +15932,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15803
15932
|
}, 2e4);
|
|
15804
15933
|
void stored.catch(() => {
|
|
15805
15934
|
});
|
|
15935
|
+
assertOwner();
|
|
15806
15936
|
await ws.sendAsync("store_chat_compression_checkpoint", {
|
|
15807
15937
|
chat_id: output.target_chat_id,
|
|
15808
15938
|
checkpoint_id: output.subject_id,
|
|
@@ -15830,7 +15960,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15830
15960
|
} else {
|
|
15831
15961
|
if (content.embed_id !== output.subject_id) throw new Error("Recovery embed identity mismatch.");
|
|
15832
15962
|
const embedPath = `/v1/embeds/chats/${encodeURIComponent(output.target_chat_id)}/embeds/${encodeURIComponent(output.subject_id)}` + (teamId ? `?team_id=${encodeURIComponent(teamId)}` : "");
|
|
15833
|
-
let canonicalBefore = await
|
|
15963
|
+
let canonicalBefore = await http.get(
|
|
15834
15964
|
embedPath,
|
|
15835
15965
|
this.getCliRequestHeaders()
|
|
15836
15966
|
);
|
|
@@ -15929,7 +16059,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15929
16059
|
}
|
|
15930
16060
|
const query = new URLSearchParams({ capability: "bounded-v1", chat_id: output.target_chat_id });
|
|
15931
16061
|
if (teamId) query.set("team_id", teamId);
|
|
15932
|
-
const existing = await
|
|
16062
|
+
const existing = await http.get(
|
|
15933
16063
|
`/v1/embeds/${encodeURIComponent(output.subject_id)}/versions/${output.output_version}?${query}`,
|
|
15934
16064
|
this.getCliRequestHeaders()
|
|
15935
16065
|
);
|
|
@@ -15985,7 +16115,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15985
16115
|
} else {
|
|
15986
16116
|
const query = new URLSearchParams({ capability: "bounded-v1", chat_id: output.target_chat_id });
|
|
15987
16117
|
if (teamId) query.set("team_id", teamId);
|
|
15988
|
-
const history = await
|
|
16118
|
+
const history = await http.get(
|
|
15989
16119
|
`/v1/embeds/${encodeURIComponent(output.subject_id)}/versions/${output.output_version}?${query}`,
|
|
15990
16120
|
this.getCliRequestHeaders()
|
|
15991
16121
|
);
|
|
@@ -16106,7 +16236,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16106
16236
|
}
|
|
16107
16237
|
const keyPath = `/v1/embeds/chats/${encodeURIComponent(output.target_chat_id)}/embeds/${encodeURIComponent(keySubject)}` + (teamId ? `?team_id=${encodeURIComponent(teamId)}` : "");
|
|
16108
16238
|
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
|
|
16239
|
+
const keyPage = bootstrapRootKeys ? null : keySubject === output.subject_id && canonicalBefore.ok ? canonicalBefore : parentPage ?? await http.get(
|
|
16110
16240
|
keyPath,
|
|
16111
16241
|
this.getCliRequestHeaders()
|
|
16112
16242
|
);
|
|
@@ -16133,20 +16263,24 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16133
16263
|
}
|
|
16134
16264
|
].filter((key) => !existingTypes.has(key.key_type));
|
|
16135
16265
|
if (keys2.length > 0) {
|
|
16266
|
+
assertOwner();
|
|
16136
16267
|
const requestId = randomUUID9();
|
|
16137
16268
|
const receipt = ws.waitForMessage("store_embed_keys_confirmed", (value) => value.request_id === requestId, 3e4);
|
|
16138
16269
|
void receipt.catch(() => {
|
|
16139
16270
|
});
|
|
16271
|
+
assertOwner();
|
|
16140
16272
|
await ws.sendAsync("store_embed_keys", {
|
|
16141
16273
|
request_id: requestId,
|
|
16142
16274
|
recovery_record_id: output.record_id,
|
|
16143
16275
|
keys: keys2
|
|
16144
16276
|
});
|
|
16145
|
-
const
|
|
16277
|
+
const received = await receipt;
|
|
16278
|
+
assertOwner();
|
|
16279
|
+
const result = received.payload;
|
|
16146
16280
|
if (result.failed_count !== 0 || result.created_count !== keys2.length || result.requested_count !== keys2.length) {
|
|
16147
16281
|
throw new Error("Recovery embed key wrappers were not exactly confirmed.");
|
|
16148
16282
|
}
|
|
16149
|
-
const durable = await
|
|
16283
|
+
const durable = await http.get(
|
|
16150
16284
|
keyPath,
|
|
16151
16285
|
this.getCliRequestHeaders()
|
|
16152
16286
|
);
|
|
@@ -16156,7 +16290,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16156
16290
|
throw new Error("Recovery embed key wrappers are not both durable.");
|
|
16157
16291
|
}
|
|
16158
16292
|
}
|
|
16159
|
-
const canonicalAfter = await
|
|
16293
|
+
const canonicalAfter = await http.get(
|
|
16160
16294
|
embedPath,
|
|
16161
16295
|
this.getCliRequestHeaders()
|
|
16162
16296
|
);
|
|
@@ -16171,7 +16305,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16171
16305
|
} else {
|
|
16172
16306
|
const query = new URLSearchParams({ capability: "bounded-v1", chat_id: output.target_chat_id });
|
|
16173
16307
|
if (teamId) query.set("team_id", teamId);
|
|
16174
|
-
const history = await
|
|
16308
|
+
const history = await http.get(
|
|
16175
16309
|
`/v1/embeds/${encodeURIComponent(output.subject_id)}/versions/${output.output_version}?${query}`,
|
|
16176
16310
|
this.getCliRequestHeaders()
|
|
16177
16311
|
);
|
|
@@ -16195,7 +16329,14 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16195
16329
|
}
|
|
16196
16330
|
}
|
|
16197
16331
|
dependencies.acknowledged.set(output.record_id, identity3);
|
|
16332
|
+
assertOwner();
|
|
16198
16333
|
acknowledged += 1;
|
|
16334
|
+
};
|
|
16335
|
+
for (const kind of ["message", "embed", "diff", "summary", "checkpoint"]) {
|
|
16336
|
+
const group = prepared.filter((item) => item !== null && item.output.output_kind === kind);
|
|
16337
|
+
if (kind === "embed" || kind === "diff") {
|
|
16338
|
+
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));
|
|
16339
|
+
} else for (const item of group) await persistOne(item);
|
|
16199
16340
|
}
|
|
16200
16341
|
return acknowledged;
|
|
16201
16342
|
}
|
|
@@ -16321,1651 +16462,1555 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16321
16462
|
}
|
|
16322
16463
|
}
|
|
16323
16464
|
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
|
-
|
|
16465
|
+
this.cancelSendSocketRearm();
|
|
16466
|
+
this.foregroundSendCount++;
|
|
16467
|
+
try {
|
|
16468
|
+
const preWsStartedAt = performance.now();
|
|
16469
|
+
const traceSendTiming = (phase) => {
|
|
16470
|
+
if (process.env.OPENMATES_TRACE_SEND_TIMING === "1") {
|
|
16471
|
+
console.error(`[chat send timing] phase=${phase} elapsed_ms=${Math.round(performance.now() - preWsStartedAt)}`);
|
|
16472
|
+
}
|
|
16473
|
+
};
|
|
16474
|
+
traceSendTiming("send_begin");
|
|
16475
|
+
if (this.preparedSendSocket && !this.preparedSendSocket.opened) this.closePreparedSendSocket();
|
|
16476
|
+
const teamId = this.resolveTeamContext({ teamId: params.teamId, personal: params.personal });
|
|
16477
|
+
const teamSenderName = teamId ? await this.ownTeamSenderName(teamId) : "User";
|
|
16478
|
+
if (params.chatId && params.newChatId) {
|
|
16479
|
+
throw new Error("chatId and newChatId cannot be used together.");
|
|
16480
|
+
}
|
|
16481
|
+
if (params.newChatId && !CANONICAL_UUID_PATTERN.test(params.newChatId)) {
|
|
16482
|
+
throw new Error("newChatId must be a canonical UUID.");
|
|
16483
|
+
}
|
|
16484
|
+
if (params.incognito && params.projectId) {
|
|
16485
|
+
throw new Error("Project file execution requires a saved chat.");
|
|
16486
|
+
}
|
|
16487
|
+
const ownsPreparedId = Boolean(params.newChatId && this.preparedChatKey?.chatId === params.newChatId && this.preparedChatOwnerCurrent(this.preparedChatKey.owner, teamId));
|
|
16488
|
+
const sdkMintedChatId = params.chatId === void 0 && params.projectId === void 0 && (params.newChatId === void 0 || ownsPreparedId);
|
|
16489
|
+
let chatId;
|
|
16490
|
+
if (params.newChatId) {
|
|
16491
|
+
chatId = params.newChatId;
|
|
16492
|
+
} else if (!params.chatId) {
|
|
16493
|
+
chatId = randomUUID9();
|
|
16494
|
+
} else if (!CANONICAL_UUID_PATTERN.test(params.chatId)) {
|
|
16495
|
+
const resolved = await this.resolveFullChatId(params.chatId, { teamId });
|
|
16496
|
+
if (!resolved) {
|
|
16497
|
+
throw new Error(
|
|
16498
|
+
`Chat not found for '${params.chatId}'. Use a slug, full UUID, or the first 8 characters of an existing chat ID.`
|
|
16499
|
+
);
|
|
16500
|
+
}
|
|
16501
|
+
chatId = resolved;
|
|
16502
|
+
} else {
|
|
16503
|
+
chatId = params.chatId;
|
|
16504
|
+
}
|
|
16505
|
+
const historyAbort = new AbortController();
|
|
16506
|
+
let savedPrivacyHistoryRead = null;
|
|
16507
|
+
let historySnapshotOwner = null;
|
|
16508
|
+
const readSavedPrivacyHistory = () => {
|
|
16509
|
+
if (!savedPrivacyHistoryRead) {
|
|
16510
|
+
savedPrivacyHistoryRead = withHttpAbortSignal(historyAbort.signal, () => this.getChatMessages(
|
|
16511
|
+
chatId,
|
|
16512
|
+
{ personal: !teamId, teamId, freshChatOnly: true, signal: historyAbort.signal }
|
|
16513
|
+
)).then((history) => {
|
|
16514
|
+
historySnapshotOwner = this.preparedChatOwner(this.requireSession(), teamId);
|
|
16515
|
+
return history;
|
|
16516
|
+
});
|
|
16517
|
+
void savedPrivacyHistoryRead.catch(() => {
|
|
16518
|
+
});
|
|
16519
|
+
}
|
|
16520
|
+
return savedPrivacyHistoryRead;
|
|
16521
|
+
};
|
|
16522
|
+
const joinSavedPrivacyHistory = async () => {
|
|
16523
|
+
const history = await readSavedPrivacyHistory();
|
|
16524
|
+
if (!historySnapshotOwner || !this.preparedChatOwnerCurrent(historySnapshotOwner, teamId)) {
|
|
16525
|
+
throw new Error("Chat workspace changed during history preparation.");
|
|
16526
|
+
}
|
|
16527
|
+
return history;
|
|
16528
|
+
};
|
|
16529
|
+
try {
|
|
16530
|
+
let finalMessage = params.message;
|
|
16531
|
+
if (hasRememberMessageReference(finalMessage)) {
|
|
16532
|
+
const rememberedHistory = (params.messageHistory ?? []).map((message) => ({ id: message.message_id, content: message.content })).filter((message) => Boolean(message.id) && typeof message.content === "string");
|
|
16533
|
+
finalMessage = rewriteRememberMessageReferences(finalMessage, rememberedHistory);
|
|
16534
|
+
}
|
|
16535
|
+
if (params.chatId && !params.incognito && hasRememberMessageReference(finalMessage)) {
|
|
16536
|
+
const { messages } = await joinSavedPrivacyHistory();
|
|
16537
|
+
finalMessage = rewriteRememberMessageReferences(finalMessage, messages);
|
|
16538
|
+
}
|
|
16539
|
+
const shouldWaitForAi = shouldWaitForTeamAi(finalMessage, teamId);
|
|
16540
|
+
const explicitTasksAppSkill = messageExplicitlyRequestsTasksAppSkill(finalMessage);
|
|
16541
|
+
const taskUpdateJobsEnabled = params.taskUpdateJobs !== false && !explicitTasksAppSkill;
|
|
16542
|
+
const sendSocketOptions = {
|
|
16543
|
+
taskUpdateJobs: taskUpdateJobsEnabled,
|
|
16544
|
+
projectFileJobs: !params.incognito,
|
|
16545
|
+
remoteCommandJobs: !params.incognito,
|
|
16546
|
+
interactiveHuman: params.interactiveHuman === true && this.interactiveViewerAllowed && process.stdin.isTTY === true && process.stdout.isTTY === true
|
|
16547
|
+
};
|
|
16548
|
+
const independentKeyOwner = this.preparedChatOwner(this.requireSession(), teamId);
|
|
16549
|
+
const independentNewChatKey = !params.incognito && !params.chatId ? (async () => {
|
|
16550
|
+
if (ownsPreparedId && this.preparedChatKey?.chatId === chatId) {
|
|
16551
|
+
const prepared = await this.preparedChatKey.value;
|
|
16552
|
+
if (teamId) independentKeyOwner.teamKeyFingerprint = createHash14("sha256").update(bytesToBase64(prepared.wrappingKey)).digest("hex");
|
|
16553
|
+
return prepared;
|
|
16554
|
+
}
|
|
16555
|
+
const wrappingKey = await this.getChatWrappingKey(teamId, this.getMasterKeyBytes());
|
|
16556
|
+
if (teamId) independentKeyOwner.teamKeyFingerprint = createHash14("sha256").update(bytesToBase64(wrappingKey)).digest("hex");
|
|
16557
|
+
const key = new Uint8Array(randomBytes4(32));
|
|
16558
|
+
return { key, wrappingKey, encryptedKey: await encryptBytesWithAesGcm(key, wrappingKey) };
|
|
16559
|
+
})() : null;
|
|
16560
|
+
void independentNewChatKey?.catch(() => {
|
|
16561
|
+
});
|
|
16562
|
+
const independentRecoveryKeypair = independentNewChatKey?.then((prepared) => deriveChatCompletionRecoveryKeypair(
|
|
16563
|
+
Buffer.from(prepared.key).toString("base64url"),
|
|
16367
16564
|
chatId,
|
|
16368
|
-
|
|
16369
|
-
))
|
|
16370
|
-
|
|
16371
|
-
return history;
|
|
16565
|
+
1
|
|
16566
|
+
));
|
|
16567
|
+
void independentRecoveryKeypair?.catch(() => {
|
|
16372
16568
|
});
|
|
16373
|
-
|
|
16569
|
+
const privacyTurnId = params.incognito ? null : randomUUID9();
|
|
16570
|
+
let conditionalPrivacyReceipt = null;
|
|
16571
|
+
let compiledPrivacy;
|
|
16572
|
+
let memorySocket = null;
|
|
16573
|
+
let socketSnapshotAttempted = false;
|
|
16574
|
+
let socketSnapshotResultRows = null;
|
|
16575
|
+
let socketProjectCandidateRows = null;
|
|
16576
|
+
let verifiedProjectCandidates = null;
|
|
16577
|
+
let deferredProjectDirectory = false;
|
|
16578
|
+
let usePersonalRoutingDirectory = false;
|
|
16579
|
+
let projectCandidatesReadyFromSendStartMs = null;
|
|
16580
|
+
let socketMemoryReceipt = null;
|
|
16581
|
+
const socketMemoryOptionsKey = () => JSON.stringify([
|
|
16582
|
+
this.sendSocketOptionsKey(sendSocketOptions),
|
|
16583
|
+
params.projectId ?? null,
|
|
16584
|
+
params.teamId ?? null,
|
|
16585
|
+
params.personal === true,
|
|
16586
|
+
params.incognito === true,
|
|
16587
|
+
params.autoApproveMemories === true,
|
|
16588
|
+
params.piiDetection !== false,
|
|
16589
|
+
params.learningMode?.enabled ?? null,
|
|
16590
|
+
params.learningMode?.ageGroup ?? null
|
|
16591
|
+
]);
|
|
16592
|
+
if (!params.incognito && params.chatId) readSavedPrivacyHistory();
|
|
16593
|
+
const followupDiscoveryOwner = !params.incognito && params.chatId && !teamId && !params.projectId ? this.preparedChatOwner(this.requireSession(), teamId) : null;
|
|
16594
|
+
const followupDiscoveryRead = followupDiscoveryOwner ? withHttpAbortSignal(historyAbort.signal, async () => prefetchCliJevDiscovery(this, chatId, { personal: true, teamId })) : null;
|
|
16595
|
+
void followupDiscoveryRead?.catch(() => {
|
|
16374
16596
|
});
|
|
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);
|
|
16597
|
+
const memoryStartedAt = performance.now();
|
|
16598
|
+
let availableMemories = params.memorySnapshot ?? [];
|
|
16599
|
+
let memoryCountsLoaded = false;
|
|
16600
|
+
let memoryMetadataKeys = [];
|
|
16601
|
+
if (!params.incognito) {
|
|
16602
|
+
try {
|
|
16603
|
+
if (!teamId) {
|
|
16604
|
+
try {
|
|
16605
|
+
socketSnapshotAttempted = true;
|
|
16606
|
+
memorySocket = (sendSocketOptions.interactiveHuman ? await this.takePreparedSendSocket(sendSocketOptions, teamId) : null) ?? await this.openWsClient(sendSocketOptions);
|
|
16607
|
+
const ownerKey = JSON.stringify([
|
|
16608
|
+
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16609
|
+
authCookieFingerprint(this.http.getCookieMap())
|
|
16610
|
+
]);
|
|
16611
|
+
const conditionalSupported = await memorySocket.ws.waitForServerCapability("personal_send_context_snapshot_v2");
|
|
16612
|
+
const combinedSupported = conditionalSupported || await memorySocket.ws.waitForServerCapability("personal_send_context_snapshot_v1");
|
|
16613
|
+
const supported = combinedSupported || await memorySocket.ws.waitForServerCapability("personal_memory_snapshot_v1");
|
|
16614
|
+
if (supported) {
|
|
16615
|
+
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");
|
|
16616
|
+
deferredProjectDirectory = deferProjectDirectory;
|
|
16617
|
+
usePersonalRoutingDirectory = deferProjectDirectory && memorySocket.ws.supportsServerCapability("personal_project_routing_directory_v1");
|
|
16618
|
+
traceSendTiming("snapshot_request");
|
|
16619
|
+
const privacyOwnerKey = this.preparedPrivacyOwnerKey(null);
|
|
16620
|
+
const preferencesKey = JSON.stringify(await readPrivacyPreferences());
|
|
16621
|
+
const preparedBundle = conditionalSupported ? this.preparedPrivacy.get(privacyOwnerKey, preferencesKey) : null;
|
|
16622
|
+
const conditional = conditionalSupported ? await memorySocket.ws.requestPersonalSendContextSnapshotV2({
|
|
16623
|
+
request_id: randomUUID9(),
|
|
16624
|
+
purpose: "send",
|
|
16625
|
+
turn_id: privacyTurnId,
|
|
16626
|
+
...preparedBundle ? { known_generation: preparedBundle.generation } : {}
|
|
16627
|
+
}) : null;
|
|
16628
|
+
const combined = !conditional && combinedSupported && !deferProjectDirectory ? await memorySocket.ws.requestPersonalSendContextSnapshot(randomUUID9()) : null;
|
|
16629
|
+
const snapshot2 = conditional ? conditional.personal_memories ?? null : combined?.personal_memories ?? await memorySocket.ws.requestPersonalMemorySnapshot(randomUUID9());
|
|
16630
|
+
traceSendTiming("snapshot_ready");
|
|
16631
|
+
if (conditional || combined) {
|
|
16632
|
+
socketProjectCandidateRows = (conditional ?? combined).project_candidates;
|
|
16633
|
+
verifiedProjectCandidates = await decodePersonalProjectCandidates(this, (conditional ?? combined).project_candidates);
|
|
16634
|
+
projectCandidatesReadyFromSendStartMs = Math.round(performance.now() - preWsStartedAt);
|
|
16635
|
+
}
|
|
16636
|
+
if (conditional?.unchanged) {
|
|
16637
|
+
if (!preparedBundle || conditional.privacy_generation !== preparedBundle.generation) throw new Error("Unchanged privacy snapshot has no matching prepared bundle.");
|
|
16638
|
+
availableMemories = preparedBundle.memories;
|
|
16639
|
+
compiledPrivacy = preparedBundle.privacy;
|
|
16640
|
+
socketSnapshotResultRows = [];
|
|
16641
|
+
} else {
|
|
16642
|
+
if (!snapshot2) throw new Error("Full Personal privacy snapshot is required.");
|
|
16643
|
+
socketSnapshotResultRows = snapshot2.memories;
|
|
16644
|
+
availableMemories = await this.listMemoriesUnshared({ personal: true }, snapshot2);
|
|
16645
|
+
if (conditional) {
|
|
16646
|
+
this.preparedPrivacy.set(privacyOwnerKey, preferencesKey, conditional.privacy_generation, availableMemories);
|
|
16647
|
+
compiledPrivacy = this.preparedPrivacy.get(privacyOwnerKey, preferencesKey)?.privacy;
|
|
16648
|
+
}
|
|
16649
|
+
}
|
|
16501
16650
|
if (conditional) {
|
|
16502
|
-
|
|
16503
|
-
|
|
16651
|
+
if (!conditional.receipt_id || conditional.turn_id !== privacyTurnId) throw new Error("Conditional privacy snapshot has no valid send receipt.");
|
|
16652
|
+
conditionalPrivacyReceipt = {
|
|
16653
|
+
receiptId: conditional.receipt_id,
|
|
16654
|
+
generation: conditional.privacy_generation,
|
|
16655
|
+
preferencesKey,
|
|
16656
|
+
epoch: this.preparedPrivacyEpoch
|
|
16657
|
+
};
|
|
16504
16658
|
}
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16659
|
+
traceSendTiming("memory_decrypt_ready");
|
|
16660
|
+
if (!memorySocket.ws.isOpen() || JSON.stringify([
|
|
16661
|
+
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16662
|
+
authCookieFingerprint(this.http.getCookieMap())
|
|
16663
|
+
]) !== ownerKey) {
|
|
16664
|
+
throw new Error("Personal Memory snapshot owner or socket changed during preparation.");
|
|
16665
|
+
}
|
|
16666
|
+
socketMemoryReceipt = {
|
|
16667
|
+
ws: memorySocket.ws,
|
|
16668
|
+
snapshot: availableMemories,
|
|
16669
|
+
resultRows: socketSnapshotResultRows,
|
|
16670
|
+
projectRows: socketProjectCandidateRows,
|
|
16671
|
+
candidates: verifiedProjectCandidates,
|
|
16672
|
+
ownerKey,
|
|
16673
|
+
optionsKey: socketMemoryOptionsKey(),
|
|
16674
|
+
used: false
|
|
16513
16675
|
};
|
|
16676
|
+
} else {
|
|
16677
|
+
memorySocket.ws.close();
|
|
16678
|
+
memorySocket = null;
|
|
16679
|
+
socketSnapshotAttempted = false;
|
|
16514
16680
|
}
|
|
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();
|
|
16681
|
+
} catch (error) {
|
|
16682
|
+
memorySocket?.ws.close();
|
|
16534
16683
|
memorySocket = null;
|
|
16535
|
-
|
|
16684
|
+
throw error;
|
|
16536
16685
|
}
|
|
16537
|
-
} catch (error) {
|
|
16538
|
-
memorySocket?.ws.close();
|
|
16539
|
-
memorySocket = null;
|
|
16540
|
-
throw error;
|
|
16541
16686
|
}
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
|
|
16551
|
-
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
16687
|
+
if (!socketMemoryReceipt) availableMemories = teamId && params.memorySnapshot ? params.memorySnapshot : await this.listMemoriesUnshared({ teamId }, void 0, !teamId);
|
|
16688
|
+
memoryCountsLoaded = true;
|
|
16689
|
+
memoryMetadataKeys = [
|
|
16690
|
+
...new Set(
|
|
16691
|
+
availableMemories.filter((memory) => memory.app_id && memory.item_type).map((memory) => `${memory.app_id}-${memory.item_type}`)
|
|
16692
|
+
)
|
|
16693
|
+
];
|
|
16694
|
+
} catch (error) {
|
|
16695
|
+
if (socketSnapshotAttempted || !teamId) throw error;
|
|
16696
|
+
if (params.autoApproveMemories) {
|
|
16697
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16698
|
+
throw new Error(`Failed to load memories for auto-approval: ${message}`);
|
|
16699
|
+
}
|
|
16555
16700
|
}
|
|
16556
16701
|
}
|
|
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
16702
|
try {
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
}
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
|
|
16703
|
+
const snapshotAuthHandoff = this.personalMemoryAuthHandoff?.snapshot.deref() === availableMemories ? this.personalMemoryAuthHandoff.auth : null;
|
|
16704
|
+
this.personalMemoryAuthHandoff = null;
|
|
16705
|
+
this.pendingWhoAmIAuthHandoff = null;
|
|
16706
|
+
const personalSendAuthHandoff = !teamId && !params.incognito ? snapshotAuthHandoff : null;
|
|
16707
|
+
const memoryPrepMs = Math.round(performance.now() - memoryStartedAt);
|
|
16708
|
+
const privacyStartedAt = performance.now();
|
|
16709
|
+
traceSendTiming("privacy_begin");
|
|
16710
|
+
const savedPrivacyHistory = !params.incognito && params.chatId ? await joinSavedPrivacyHistory() : null;
|
|
16711
|
+
let piiMappings = [...savedPrivacyHistory?.messages.flatMap((message) => (message.piiMappings ?? []).map((mapping) => ({ ...mapping, type: mapping.type ?? "OTHER" }))) ?? [], ...params.piiMappings ?? []];
|
|
16712
|
+
try {
|
|
16713
|
+
if (params.piiDetection !== false) {
|
|
16714
|
+
const prepared = await prepareCliMessagePrivacy(finalMessage, availableMemories, piiMappings, { kind: "message" }, params.onPrivacyProgress, compiledPrivacy);
|
|
16715
|
+
finalMessage = prepared.message;
|
|
16716
|
+
piiMappings = prepared.mappings;
|
|
16717
|
+
for (const embed of params.preparedEmbeds ?? []) {
|
|
16718
|
+
if (!["code-code", "docs-doc", "sheets-sheet"].includes(embed.type)) continue;
|
|
16719
|
+
const decoded = toonDecode(embed.content);
|
|
16720
|
+
for (const field3 of ["code", "html", "table", "text"]) if (typeof decoded[field3] === "string") {
|
|
16721
|
+
const value = await prepareCliMessagePrivacy(decoded[field3], availableMemories, piiMappings, { kind: "document" }, params.onPrivacyProgress, compiledPrivacy);
|
|
16722
|
+
decoded[field3] = value.message;
|
|
16723
|
+
piiMappings = value.mappings;
|
|
16724
|
+
}
|
|
16725
|
+
embed.content = toonEncodeContent(decoded);
|
|
16726
|
+
const content = decoded.code ?? decoded.table ?? decoded.html ?? decoded.text;
|
|
16727
|
+
if (typeof content === "string") {
|
|
16728
|
+
embed.contentHash = createHash14("sha256").update(content).digest("hex");
|
|
16729
|
+
embed.textLengthChars = content.length;
|
|
16730
|
+
}
|
|
16586
16731
|
}
|
|
16587
16732
|
}
|
|
16733
|
+
params.onPrivacyPrepared?.(finalMessage);
|
|
16734
|
+
} catch (error) {
|
|
16735
|
+
memorySocket?.ws.close();
|
|
16736
|
+
throw error;
|
|
16588
16737
|
}
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
const suppliedContext = deferredProjectDirectory ? { ...params.jevContext, project_focus_candidates: void 0 } : params.jevContext;
|
|
16608
|
-
const contextStartedAt = Date.now();
|
|
16609
|
-
let publishRouting;
|
|
16610
|
-
const routingContext = new Promise((resolve20) => {
|
|
16611
|
-
publishRouting = resolve20;
|
|
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
|
-
}
|
|
16738
|
+
const privacyPrepMs = Math.round(performance.now() - privacyStartedAt);
|
|
16739
|
+
traceSendTiming("privacy_ready");
|
|
16740
|
+
const preWsMs = Math.round(performance.now() - preWsStartedAt);
|
|
16741
|
+
const contextSession = this.requireSession();
|
|
16742
|
+
const {
|
|
16743
|
+
apiUrl: contextApiUrl,
|
|
16744
|
+
hashedEmail: contextHashedEmail,
|
|
16745
|
+
sessionId: contextSessionId,
|
|
16746
|
+
createdAt: contextSessionCreatedAt,
|
|
16747
|
+
masterKeyExportedB64: contextMasterKey,
|
|
16748
|
+
activeTeamId: contextActiveTeamId
|
|
16749
|
+
} = contextSession;
|
|
16750
|
+
const contextScope = { teamId, personal: !teamId };
|
|
16751
|
+
const suppliedContext = deferredProjectDirectory ? { ...params.jevContext, project_focus_candidates: void 0 } : params.jevContext;
|
|
16752
|
+
const contextStartedAt = Date.now();
|
|
16753
|
+
let publishRouting;
|
|
16754
|
+
const routingContext = new Promise((resolve20) => {
|
|
16755
|
+
publishRouting = resolve20;
|
|
16665
16756
|
});
|
|
16666
|
-
|
|
16667
|
-
|
|
16757
|
+
let publishProjectRouting;
|
|
16758
|
+
let rejectProjectRouting;
|
|
16759
|
+
const projectRoutingContext = new Promise((resolve20, reject) => {
|
|
16760
|
+
publishProjectRouting = resolve20;
|
|
16761
|
+
rejectProjectRouting = reject;
|
|
16762
|
+
});
|
|
16763
|
+
void projectRoutingContext.catch(() => {
|
|
16764
|
+
});
|
|
16765
|
+
const publishProjectRoutingTimed = (context) => {
|
|
16766
|
+
projectCandidatesReadyFromSendStartMs ??= Math.round(performance.now() - preWsStartedAt);
|
|
16767
|
+
publishProjectRouting(deferredProjectDirectory ? { ...context, project_focus_candidates: context.project_focus_candidates?.filter((candidate) => candidate.auto_selection === true) } : context);
|
|
16768
|
+
};
|
|
16769
|
+
let recoveryMs = 0;
|
|
16770
|
+
const contextOwnerIsCurrent = () => {
|
|
16771
|
+
const current2 = this.session;
|
|
16772
|
+
return Boolean(current2 && current2.apiUrl === contextApiUrl && current2.hashedEmail === contextHashedEmail && current2.sessionId === contextSessionId && current2.createdAt === contextSessionCreatedAt && current2.masterKeyExportedB64 === contextMasterKey && current2.activeTeamId === contextActiveTeamId);
|
|
16773
|
+
};
|
|
16774
|
+
const earlyContextAbort = new AbortController();
|
|
16775
|
+
const earlyContext = !params.incognito && !params.chatId && !params.projectId ? withHttpAbortSignal(earlyContextAbort.signal, () => prepareCliJevContext(this, chatId, contextScope, suppliedContext, finalMessage, {
|
|
16776
|
+
sdkMintedChatId,
|
|
16777
|
+
onRouting: publishRouting,
|
|
16778
|
+
verifiedProjectCandidates: verifiedProjectCandidates ?? void 0,
|
|
16779
|
+
usePersonalRoutingDirectory,
|
|
16780
|
+
onProjectRouting: publishProjectRoutingTimed,
|
|
16781
|
+
onProjectRoutingError: rejectProjectRouting
|
|
16782
|
+
})).then(
|
|
16783
|
+
(value) => ({ ok: true, value, elapsedMs: Date.now() - contextStartedAt }),
|
|
16784
|
+
(error) => ({ ok: false, error, elapsedMs: Date.now() - contextStartedAt })
|
|
16785
|
+
) : null;
|
|
16786
|
+
const authSocketStartedAt = Date.now();
|
|
16787
|
+
let opened;
|
|
16788
|
+
try {
|
|
16789
|
+
opened = memorySocket ?? (sendSocketOptions.interactiveHuman ? await this.takePreparedSendSocket(sendSocketOptions, teamId) : null) ?? await this.openWsClient({ ...sendSocketOptions, personalSendAuthHandoff });
|
|
16790
|
+
} catch (error) {
|
|
16791
|
+
earlyContextAbort.abort();
|
|
16792
|
+
if (earlyContext) await earlyContext;
|
|
16793
|
+
throw error;
|
|
16668
16794
|
}
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
|
|
16675
|
-
let initialHeaderNotice = null;
|
|
16676
|
-
let initialAdmissionDurable = false;
|
|
16677
|
-
let initialHeaderStopped = false;
|
|
16678
|
-
let initialTitleScheduled;
|
|
16679
|
-
let initialIconScheduled;
|
|
16680
|
-
let persistOptionalHeader;
|
|
16681
|
-
let optionalMetadata = null;
|
|
16682
|
-
const socketReadyForSendAt = performance.now();
|
|
16683
|
-
let capabilitiesWaitMs = 0;
|
|
16684
|
-
let keyWaitMs = 0;
|
|
16685
|
-
let recoveryKeypairMs = 0;
|
|
16686
|
-
let encryptPayloadMs = 0;
|
|
16687
|
-
if (!params.incognito && !ownerId2) {
|
|
16688
|
-
earlyContextAbort.abort();
|
|
16689
|
-
ws.close();
|
|
16690
|
-
if (earlyContext) await earlyContext;
|
|
16691
|
-
throw new Error("Authenticated user identity is required for saved chat recovery.");
|
|
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 {
|
|
16795
|
+
const { ws, ownerId: ownerId2 } = opened;
|
|
16796
|
+
const consumeSocketMemoryReceipt = async () => {
|
|
16797
|
+
if (!socketMemoryReceipt) return;
|
|
16798
|
+
const receipt = socketMemoryReceipt;
|
|
16799
|
+
if (receipt.used || receipt.ws !== ws || receipt.snapshot !== availableMemories || receipt.resultRows !== socketSnapshotResultRows || receipt.projectRows !== socketProjectCandidateRows || receipt.candidates !== verifiedProjectCandidates || !ws.isOpen() || receipt.optionsKey !== socketMemoryOptionsKey()) {
|
|
16800
|
+
throw new Error("Personal Memory snapshot socket receipt is invalid.");
|
|
16704
16801
|
}
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
recoveryDiscoveryError = error;
|
|
16712
|
-
throw error;
|
|
16802
|
+
await this.withFreshStoredSession(async () => {
|
|
16803
|
+
if (!ws.isOpen() || JSON.stringify([
|
|
16804
|
+
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16805
|
+
authCookieFingerprint(this.http.getCookieMap())
|
|
16806
|
+
]) !== receipt.ownerKey) {
|
|
16807
|
+
throw new Error("Personal Memory snapshot owner changed before send.");
|
|
16713
16808
|
}
|
|
16714
|
-
);
|
|
16715
|
-
void recoveryDiscovery.catch(() => {
|
|
16716
16809
|
});
|
|
16717
|
-
if (
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
16723
|
-
|
|
16724
|
-
|
|
16725
|
-
|
|
16726
|
-
|
|
16727
|
-
let
|
|
16728
|
-
|
|
16729
|
-
|
|
16730
|
-
|
|
16810
|
+
if (conditionalPrivacyReceipt && (conditionalPrivacyReceipt.epoch !== this.preparedPrivacyEpoch || conditionalPrivacyReceipt.preferencesKey !== JSON.stringify(await readPrivacyPreferences()))) {
|
|
16811
|
+
throw new Error("Privacy preferences changed before send; prepare the turn again.");
|
|
16812
|
+
}
|
|
16813
|
+
receipt.used = true;
|
|
16814
|
+
};
|
|
16815
|
+
let metadataTransportOwned = false;
|
|
16816
|
+
let metadataHandoffReady = false;
|
|
16817
|
+
let metadataHandoffSuggestion = null;
|
|
16818
|
+
let initialHeaderWork = null;
|
|
16819
|
+
let initialHeaderNotice = null;
|
|
16820
|
+
let initialAdmissionDurable = false;
|
|
16821
|
+
let initialHeaderStopped = false;
|
|
16822
|
+
let initialTitleScheduled;
|
|
16823
|
+
let initialIconScheduled;
|
|
16824
|
+
let persistOptionalHeader;
|
|
16825
|
+
let optionalMetadata = null;
|
|
16826
|
+
const socketReadyForSendAt = performance.now();
|
|
16827
|
+
let capabilitiesWaitMs = 0;
|
|
16828
|
+
let keyWaitMs = 0;
|
|
16829
|
+
let recoveryKeypairMs = 0;
|
|
16830
|
+
let encryptPayloadMs = 0;
|
|
16831
|
+
if (!params.incognito && !ownerId2) {
|
|
16832
|
+
earlyContextAbort.abort();
|
|
16833
|
+
ws.close();
|
|
16834
|
+
if (earlyContext) await earlyContext;
|
|
16835
|
+
throw new Error("Authenticated user identity is required for saved chat recovery.");
|
|
16731
16836
|
}
|
|
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)
|
|
16837
|
+
const authSocketMs = Date.now() - authSocketStartedAt;
|
|
16838
|
+
const mayDeferRecovery = !params.incognito && sdkMintedChatId && !teamId && !params.projectId && shouldWaitForAi;
|
|
16839
|
+
let recoveryDiscovery = null;
|
|
16840
|
+
let recoveryDiscoveryError = null;
|
|
16841
|
+
let contextAckRejection = null;
|
|
16842
|
+
let removeContextAckWatcher = null;
|
|
16843
|
+
try {
|
|
16844
|
+
if (!params.incognito) {
|
|
16845
|
+
try {
|
|
16846
|
+
ws.send("chat_metadata_warmup", teamId ? { team_id: teamId } : {});
|
|
16847
|
+
} catch {
|
|
16848
|
+
}
|
|
16849
|
+
const recoveryStartedAt = Date.now();
|
|
16850
|
+
recoveryDiscovery = ws.waitForRecoveryOutputDiscovery().then(
|
|
16851
|
+
() => {
|
|
16852
|
+
recoveryMs = Date.now() - recoveryStartedAt;
|
|
16853
|
+
},
|
|
16854
|
+
(error) => {
|
|
16855
|
+
recoveryDiscoveryError = error;
|
|
16856
|
+
throw error;
|
|
16857
|
+
}
|
|
16780
16858
|
);
|
|
16781
|
-
|
|
16782
|
-
|
|
16783
|
-
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16859
|
+
void recoveryDiscovery.catch(() => {
|
|
16860
|
+
});
|
|
16861
|
+
if (mayDeferRecovery) {
|
|
16862
|
+
const capabilitiesWaitStartedAt = performance.now();
|
|
16863
|
+
await Promise.race([ws.waitForServerCapabilities(), recoveryDiscovery]);
|
|
16864
|
+
capabilitiesWaitMs = Math.round(performance.now() - capabilitiesWaitStartedAt);
|
|
16865
|
+
} else await recoveryDiscovery;
|
|
16866
|
+
}
|
|
16867
|
+
const messageId2 = randomUUID9();
|
|
16868
|
+
const wallCreatedAt = Math.floor(Date.now() / 1e3);
|
|
16869
|
+
const createdAt = !teamId && !params.incognito && params.chatId ? nextPersonalUserCreatedAt(wallCreatedAt, savedPrivacyHistory?.messages.map((row3) => row3.createdAt) ?? []) : wallCreatedAt;
|
|
16870
|
+
const isNewChat = !params.chatId;
|
|
16871
|
+
let messageHistoryForRequest = params.messageHistory;
|
|
16872
|
+
if (!params.incognito && !isNewChat && !messageHistoryForRequest) {
|
|
16873
|
+
const { messages } = savedPrivacyHistory;
|
|
16874
|
+
messageHistoryForRequest = buildSavedChatMessageHistory(messages, chatId);
|
|
16875
|
+
}
|
|
16876
|
+
ws.send("set_active_chat", { chat_id: chatId, ...teamId ? { team_id: teamId } : {} });
|
|
16877
|
+
const connectedAccountDirectory = buildConnectedAccountDirectoryPayload(
|
|
16878
|
+
params.connectedAccountDirectory
|
|
16879
|
+
);
|
|
16880
|
+
const connectedAccountTokenRefs = params.connectedAccountTokenRefInputs?.length ? await this.createTurnTokenRefs({
|
|
16881
|
+
chatId,
|
|
16882
|
+
messageId: messageId2,
|
|
16883
|
+
refs: params.connectedAccountTokenRefInputs,
|
|
16884
|
+
teamId
|
|
16885
|
+
}) : [];
|
|
16886
|
+
assertNoConnectedAccountSecretLeak(connectedAccountTokenRefs);
|
|
16887
|
+
let chatKeyBytes = null;
|
|
16888
|
+
let activeFocusId = null;
|
|
16889
|
+
let encryptedChatKey = null;
|
|
16890
|
+
let chatSlugLookupKey = null;
|
|
16891
|
+
let focusPhaseState = {};
|
|
16892
|
+
let baselineMessagesV = 0;
|
|
16893
|
+
let notificationTitle = null;
|
|
16894
|
+
let terminalExpectedMessagesV = 1;
|
|
16895
|
+
let savedTurnId = null;
|
|
16896
|
+
if (!params.incognito) {
|
|
16897
|
+
traceSendTiming("key_begin");
|
|
16898
|
+
const masterKey = this.getMasterKeyBytes();
|
|
16899
|
+
const preparedKey = isNewChat && ownsPreparedId && this.preparedChatKey?.chatId === chatId && this.preparedChatOwnerCurrent(this.preparedChatKey.owner, teamId) ? this.preparedChatKey : null;
|
|
16900
|
+
if (preparedKey) this.preparedChatKey = null;
|
|
16901
|
+
const keyWaitStartedAt = performance.now();
|
|
16902
|
+
const prepared = independentNewChatKey ? await independentNewChatKey : preparedKey ? await preparedKey.value : null;
|
|
16903
|
+
if (prepared && !this.preparedChatOwnerCurrent(independentKeyOwner, teamId)) throw new Error("Chat key preparation owner changed before send.");
|
|
16904
|
+
const wrappingKey = prepared?.wrappingKey ?? await this.getChatWrappingKey(teamId, masterKey);
|
|
16905
|
+
keyWaitMs = Math.round(performance.now() - keyWaitStartedAt);
|
|
16906
|
+
chatSlugLookupKey = wrappingKey;
|
|
16907
|
+
if (isNewChat && prepared) {
|
|
16908
|
+
chatKeyBytes = prepared.key;
|
|
16909
|
+
encryptedChatKey = prepared.encryptedKey;
|
|
16910
|
+
} else if (isNewChat) {
|
|
16911
|
+
chatKeyBytes = globalThis.crypto ? new Uint8Array(globalThis.crypto.getRandomValues(new Uint8Array(32))) : new Uint8Array(
|
|
16912
|
+
(await import("crypto")).webcrypto.getRandomValues(
|
|
16913
|
+
new Uint8Array(32)
|
|
16914
|
+
)
|
|
16915
|
+
);
|
|
16916
|
+
encryptedChatKey = await encryptBytesWithAesGcm(
|
|
16917
|
+
chatKeyBytes,
|
|
16918
|
+
wrappingKey
|
|
16919
|
+
);
|
|
16920
|
+
} else {
|
|
16921
|
+
const cache = (savedPrivacyHistory && this.scopedChatHistory.get(savedPrivacyHistory)) ?? loadSyncCache(teamId) ?? await this.ensureSynced(false, [], { teamId });
|
|
16922
|
+
const chat = cache.chats.find(
|
|
16923
|
+
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
16924
|
+
);
|
|
16925
|
+
if (chat) {
|
|
16926
|
+
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
16927
|
+
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
16928
|
+
if (encKey) {
|
|
16929
|
+
chatKeyBytes = await this.resolveChatKey(cache, chat, wrappingKey, teamId);
|
|
16930
|
+
encryptedChatKey = encKey;
|
|
16931
|
+
if (!chatKeyBytes) throw new Error("Could not decrypt the saved chat key.");
|
|
16932
|
+
if (typeof chat.details.encrypted_title === "string") {
|
|
16933
|
+
try {
|
|
16934
|
+
notificationTitle = await decryptWithAesGcmCombined(chat.details.encrypted_title, chatKeyBytes);
|
|
16935
|
+
} catch {
|
|
16936
|
+
}
|
|
16792
16937
|
}
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
16938
|
+
const encryptedPhaseState = chat.details.encrypted_focus_phase_state;
|
|
16939
|
+
if (typeof encryptedPhaseState === "string" && encryptedPhaseState) {
|
|
16940
|
+
const phaseText = await decryptWithAesGcmCombined(encryptedPhaseState, chatKeyBytes);
|
|
16941
|
+
if (!phaseText) throw new Error("Could not decrypt focus phase progress. Sync before sending again.");
|
|
16942
|
+
focusPhaseState = JSON.parse(phaseText);
|
|
16943
|
+
}
|
|
16944
|
+
const encryptedFocusId = chat.details.encrypted_active_focus_id;
|
|
16945
|
+
if (typeof encryptedFocusId === "string" && encryptedFocusId) {
|
|
16946
|
+
activeFocusId = await decryptWithAesGcmCombined(encryptedFocusId, chatKeyBytes);
|
|
16947
|
+
if (!activeFocusId) {
|
|
16948
|
+
throw new Error("Could not decrypt the chat's active focus. Sync before sending again.");
|
|
16949
|
+
}
|
|
16805
16950
|
}
|
|
16806
16951
|
}
|
|
16807
16952
|
}
|
|
16953
|
+
if (!chatKeyBytes || !encryptedChatKey) {
|
|
16954
|
+
throw new Error(`Encrypted chat key not found for chat '${chatId}'. Sync and try again.`);
|
|
16955
|
+
}
|
|
16808
16956
|
}
|
|
16809
|
-
|
|
16810
|
-
throw new Error(`Encrypted chat key not found for chat '${chatId}'. Sync and try again.`);
|
|
16811
|
-
}
|
|
16812
|
-
}
|
|
16813
|
-
traceSendTiming("key_ready");
|
|
16814
|
-
}
|
|
16815
|
-
let focusPersistence = Promise.resolve();
|
|
16816
|
-
let focusPersistenceError = null;
|
|
16817
|
-
const chatContextEvents = !params.incognito && chatKeyBytes ? registerChatContextEvents({
|
|
16818
|
-
ws,
|
|
16819
|
-
chatId,
|
|
16820
|
-
chatKey: chatKeyBytes,
|
|
16821
|
-
existingMessageIds: savedPrivacyHistory?.messages.map(canonicalSavedChatMessageId) ?? [],
|
|
16822
|
-
previousRulesSetKey: savedPrivacyHistory?.messages.filter((message) => message.role === "system").map((message) => parseChatContextContent(message.content)).filter((event) => event?.type === "rules_loaded").at(-1)?.set_key,
|
|
16823
|
-
onApplied: params.onChatContextApplied
|
|
16824
|
-
}) : null;
|
|
16825
|
-
let projectAuthoringAvailable = null;
|
|
16826
|
-
if (!params.incognito) ws.onMessageType("project_authoring_available", (value) => {
|
|
16827
|
-
if (!value || typeof value !== "object") return;
|
|
16828
|
-
const frame = value;
|
|
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;
|
|
16957
|
+
traceSendTiming("key_ready");
|
|
16840
16958
|
}
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
16959
|
+
let focusPersistence = Promise.resolve();
|
|
16960
|
+
let focusPersistenceError = null;
|
|
16961
|
+
const chatContextEvents = !params.incognito && chatKeyBytes ? registerChatContextEvents({
|
|
16962
|
+
ws,
|
|
16963
|
+
chatId,
|
|
16964
|
+
chatKey: chatKeyBytes,
|
|
16965
|
+
existingMessageIds: savedPrivacyHistory?.messages.map(canonicalSavedChatMessageId) ?? [],
|
|
16966
|
+
previousRulesSetKey: savedPrivacyHistory?.messages.filter((message) => message.role === "system").map((message) => parseChatContextContent(message.content)).filter((event) => event?.type === "rules_loaded").at(-1)?.set_key,
|
|
16967
|
+
onApplied: params.onChatContextApplied
|
|
16968
|
+
}) : null;
|
|
16969
|
+
let projectAuthoringAvailable = null;
|
|
16970
|
+
if (!params.incognito) ws.onMessageType("project_authoring_available", (value) => {
|
|
16971
|
+
if (!value || typeof value !== "object") return;
|
|
16972
|
+
const frame = value;
|
|
16973
|
+
if (frame.chat_id === chatId && frame.user_message_id === messageId2 && typeof frame.project_id === "string")
|
|
16974
|
+
projectAuthoringAvailable = { chat_id: chatId, project_id: frame.project_id, user_message_id: messageId2 };
|
|
16855
16975
|
});
|
|
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];
|
|
16976
|
+
const projectFocusCountdown = !params.incognito ? registerProjectFocusCountdown({
|
|
16977
|
+
client: this,
|
|
16978
|
+
ws,
|
|
16979
|
+
chatId,
|
|
16980
|
+
teamId,
|
|
16981
|
+
onPending: params.onProjectFocusPending,
|
|
16982
|
+
onError: (error) => {
|
|
16983
|
+
focusPersistenceError = error;
|
|
16867
16984
|
}
|
|
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", {
|
|
16985
|
+
}) : null;
|
|
16986
|
+
ws.onMessageType("focus_mode_activated", (event) => {
|
|
16987
|
+
if (event.chat_id !== chatId || !event.focus_id || !chatKeyBytes || params.incognito) return;
|
|
16988
|
+
const focusId = event.focus_id;
|
|
16989
|
+
if (!focusId.startsWith("project-")) delete focusPhaseState[focusId];
|
|
16990
|
+
const key = chatKeyBytes;
|
|
16991
|
+
focusPersistence = focusPersistence.then(async () => {
|
|
16992
|
+
const encryptedFocusId = await encryptWithAesGcmCombined(focusId, key);
|
|
16993
|
+
await ws.sendAsync("update_encrypted_active_focus_id", {
|
|
16887
16994
|
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
|
-
}
|
|
16995
|
+
encrypted_active_focus_id: encryptedFocusId
|
|
16895
16996
|
});
|
|
16896
|
-
}
|
|
16897
|
-
|
|
16898
|
-
|
|
16899
|
-
focusPersistenceError = error;
|
|
16997
|
+
}).catch((error) => {
|
|
16998
|
+
focusPersistenceError = error;
|
|
16999
|
+
});
|
|
16900
17000
|
});
|
|
16901
|
-
|
|
16902
|
-
|
|
16903
|
-
|
|
16904
|
-
|
|
16905
|
-
|
|
16906
|
-
|
|
16907
|
-
|
|
16908
|
-
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
17001
|
+
ws.onMessageType("focus_phases_updated", (event) => {
|
|
17002
|
+
if (event.chat_id !== chatId || !event.states || !chatKeyBytes || params.incognito) return;
|
|
17003
|
+
const key = chatKeyBytes;
|
|
17004
|
+
const states2 = event.states;
|
|
17005
|
+
focusPersistence = focusPersistence.then(async () => {
|
|
17006
|
+
for (const [focusId, phase] of Object.entries(states2)) {
|
|
17007
|
+
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) {
|
|
17008
|
+
throw new Error("Invalid focus phase state received");
|
|
17009
|
+
}
|
|
17010
|
+
if (focusPhaseState[focusId]?.run_id === phase.run_id && focusPhaseState[focusId].version > phase.version) states2[focusId] = focusPhaseState[focusId];
|
|
17011
|
+
}
|
|
17012
|
+
focusPhaseState = states2;
|
|
17013
|
+
const encryptedState = await encryptWithAesGcmCombined(JSON.stringify(states2), key);
|
|
17014
|
+
const persisted = ws.waitForMessage(
|
|
17015
|
+
"encrypted_metadata_stored",
|
|
17016
|
+
(payload) => payload.chat_id === chatId,
|
|
17017
|
+
2e4
|
|
17018
|
+
);
|
|
17019
|
+
await ws.sendAsync("encrypted_chat_metadata", {
|
|
17020
|
+
chat_id: chatId,
|
|
17021
|
+
...teamId ? { team_id: teamId } : {},
|
|
17022
|
+
encrypted_focus_phase_state: encryptedState
|
|
17023
|
+
});
|
|
17024
|
+
await persisted;
|
|
17025
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
17026
|
+
for (const phase of Object.values(states2)) for (const transition of phase.transitions) {
|
|
17027
|
+
if (transition.chat_id !== chatId || transition.focus_id !== phase.focus_id || !transition.event_id || eventIds.has(transition.event_id)) continue;
|
|
17028
|
+
eventIds.add(transition.event_id);
|
|
17029
|
+
const content = JSON.stringify(transition);
|
|
17030
|
+
await ws.sendAsync("chat_system_message_added", {
|
|
17031
|
+
chat_id: chatId,
|
|
17032
|
+
message: {
|
|
17033
|
+
message_id: transition.event_id,
|
|
17034
|
+
role: "system",
|
|
17035
|
+
status: "synced",
|
|
17036
|
+
encrypted_content: await encryptWithAesGcmCombined(content, key),
|
|
17037
|
+
created_at: transition.created_at
|
|
17038
|
+
}
|
|
17039
|
+
});
|
|
17040
|
+
}
|
|
17041
|
+
clearSyncCache(teamId);
|
|
17042
|
+
}).catch((error) => {
|
|
17043
|
+
focusPersistenceError = error;
|
|
17044
|
+
});
|
|
16914
17045
|
});
|
|
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;
|
|
17046
|
+
const clientCapabilities = taskUpdateJobsEnabled ? ["task_update_jobs"] : [];
|
|
17047
|
+
if (!params.incognito && chatKeyBytes) {
|
|
17048
|
+
registerCliProjectFileExecutor({
|
|
17049
|
+
client: this,
|
|
17050
|
+
ws,
|
|
17051
|
+
chatId,
|
|
17052
|
+
chatKey: chatKeyBytes,
|
|
17053
|
+
piiMappings,
|
|
17054
|
+
memories: availableMemories,
|
|
17055
|
+
requestApproval: params.onProjectWriteApproval,
|
|
17056
|
+
requestReadApproval: params.onProjectReadApproval,
|
|
17057
|
+
onHostedVersionCommitted: params.onHostedVersionCommitted
|
|
17058
|
+
});
|
|
17059
|
+
clientCapabilities.push("project_file_jobs");
|
|
17060
|
+
registerRemoteCommandOriginClient({ ws, client: this, chatId, onReview: params.onRemoteCommandReview, onEvent: params.onRemoteCommandEvent });
|
|
17061
|
+
clientCapabilities.push("remote_command_jobs");
|
|
16947
17062
|
}
|
|
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"}`);
|
|
17063
|
+
const messagePayload = {
|
|
17064
|
+
chat_id: chatId,
|
|
17065
|
+
...teamId ? { team_id: teamId } : {},
|
|
17066
|
+
client_capabilities: clientCapabilities,
|
|
17067
|
+
// Only decrypted current metadata restores focus; history is never authority.
|
|
17068
|
+
active_focus_id: activeFocusId,
|
|
17069
|
+
focus_phase_state: focusPhaseState,
|
|
17070
|
+
is_incognito: Boolean(params.incognito),
|
|
17071
|
+
message: {
|
|
17072
|
+
message_id: messageId2,
|
|
17073
|
+
chat_id: chatId,
|
|
17074
|
+
role: "user",
|
|
17075
|
+
sender_name: "User",
|
|
17076
|
+
status: "sent",
|
|
17077
|
+
content: finalMessage,
|
|
17078
|
+
created_at: createdAt,
|
|
17079
|
+
chat_has_title: Boolean(params.chatId)
|
|
16966
17080
|
}
|
|
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
17081
|
};
|
|
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
|
-
|
|
17082
|
+
const useCombinedAdmission = ws.supportsServerCapability?.("chat_turn_admit_v1") === true;
|
|
17083
|
+
const useDeferredContext = useCombinedAdmission && !params.chatId && !params.projectId && !teamId && ws.supportsServerCapability?.("chat_turn_context_v1") === true;
|
|
17084
|
+
const useSelectionDetailsV2 = useDeferredContext && sdkMintedChatId && ws.supportsServerCapability?.("chat_turn_context_selection_details_v2") === true;
|
|
17085
|
+
const contextRequestId = !params.incognito && shouldWaitForAi && useDeferredContext ? randomUUID9() : null;
|
|
17086
|
+
let pendingContext = null;
|
|
17087
|
+
if (!params.incognito && shouldWaitForAi) {
|
|
17088
|
+
if (contextRequestId) {
|
|
17089
|
+
messagePayload.client_context_pending = true;
|
|
17090
|
+
messagePayload.client_context_request_id = contextRequestId;
|
|
17091
|
+
}
|
|
17092
|
+
pendingContext = (async () => {
|
|
17093
|
+
const prefetchedDiscovery2 = followupDiscoveryRead ? await followupDiscoveryRead : void 0;
|
|
17094
|
+
if (followupDiscoveryOwner && !this.preparedChatOwnerCurrent(followupDiscoveryOwner, teamId)) {
|
|
17095
|
+
throw new Error("Chat workspace changed during context discovery.");
|
|
17096
|
+
}
|
|
17097
|
+
const prepared = earlyContext ? await earlyContext : { ok: true, value: await withHttpAbortSignal(earlyContextAbort.signal, () => prepareCliJevContext(this, chatId, contextScope, suppliedContext, finalMessage, {
|
|
17098
|
+
prefetchedDiscovery: prefetchedDiscovery2,
|
|
17099
|
+
sdkMintedChatId,
|
|
17100
|
+
onRouting: publishRouting,
|
|
17101
|
+
verifiedProjectCandidates: verifiedProjectCandidates ?? void 0,
|
|
17102
|
+
usePersonalRoutingDirectory,
|
|
17103
|
+
onProjectRouting: publishProjectRoutingTimed,
|
|
17104
|
+
onProjectRoutingError: rejectProjectRouting
|
|
17105
|
+
})), elapsedMs: Date.now() - contextStartedAt };
|
|
17106
|
+
if (!prepared.ok) throw prepared.error;
|
|
17107
|
+
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17108
|
+
if (process.env.OPENMATES_DEBUG === "1") {
|
|
17109
|
+
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"}`);
|
|
17110
|
+
}
|
|
17111
|
+
return deferredProjectDirectory ? { ...prepared.value, project_focus_candidates: prepared.value.project_focus_candidates?.filter((candidate) => candidate.auto_selection === true) } : prepared.value;
|
|
17112
|
+
})();
|
|
17113
|
+
void pendingContext.catch(() => {
|
|
17114
|
+
});
|
|
17031
17115
|
}
|
|
17032
|
-
if (
|
|
17033
|
-
|
|
17116
|
+
if (params.testMockMarker !== void 0) {
|
|
17117
|
+
const { capacityReplayMarker } = await import("./capacityReplayMarker-3NFJNDTZ.js");
|
|
17118
|
+
messagePayload.test_mock_marker = capacityReplayMarker(
|
|
17119
|
+
params.testMockMarker,
|
|
17120
|
+
this.apiUrl,
|
|
17121
|
+
process.env.OPENMATES_CAPACITY_CLIENT_REPLAY === "1"
|
|
17122
|
+
);
|
|
17034
17123
|
}
|
|
17035
|
-
if (
|
|
17036
|
-
|
|
17124
|
+
if (isNewChat && encryptedChatKey) {
|
|
17125
|
+
messagePayload.encrypted_chat_key = encryptedChatKey;
|
|
17037
17126
|
}
|
|
17038
|
-
|
|
17039
|
-
|
|
17040
|
-
throw new Error("Prepared message embed ID is invalid.");
|
|
17127
|
+
if (memoryMetadataKeys.length > 0) {
|
|
17128
|
+
messagePayload.app_settings_memories_metadata = memoryMetadataKeys;
|
|
17041
17129
|
}
|
|
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);
|
|
17130
|
+
if (connectedAccountDirectory) {
|
|
17131
|
+
messagePayload.connected_account_directory = connectedAccountDirectory;
|
|
17058
17132
|
}
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
17062
|
-
|
|
17063
|
-
|
|
17064
|
-
|
|
17065
|
-
|
|
17066
|
-
|
|
17067
|
-
|
|
17068
|
-
|
|
17133
|
+
if (connectedAccountTokenRefs.length > 0) {
|
|
17134
|
+
messagePayload.connected_account_token_refs = connectedAccountTokenRefs;
|
|
17135
|
+
}
|
|
17136
|
+
if (params.benchmarkMetadata) {
|
|
17137
|
+
messagePayload.benchmark_metadata = params.benchmarkMetadata;
|
|
17138
|
+
}
|
|
17139
|
+
if (params.learningMode) {
|
|
17140
|
+
messagePayload.learning_mode = {
|
|
17141
|
+
enabled: params.learningMode.enabled,
|
|
17142
|
+
age_group: params.learningMode.ageGroup ?? null
|
|
17143
|
+
};
|
|
17144
|
+
}
|
|
17145
|
+
if (params.incognito) {
|
|
17146
|
+
const providedHistory = (params.messageHistory ?? []).map(
|
|
17147
|
+
(historyMessage) => buildInferenceHistoryMessage(historyMessage, historyMessage.chat_id ?? chatId)
|
|
17148
|
+
);
|
|
17149
|
+
messagePayload.message_history = [...providedHistory, {
|
|
17150
|
+
message_id: messageId2,
|
|
17151
|
+
chat_id: chatId,
|
|
17152
|
+
role: "user",
|
|
17153
|
+
sender_name: "User",
|
|
17154
|
+
content: finalMessage,
|
|
17155
|
+
created_at: createdAt
|
|
17156
|
+
}];
|
|
17157
|
+
} else if (messageHistoryForRequest && messageHistoryForRequest.length > 0) {
|
|
17158
|
+
messagePayload.message_history = messageHistoryForRequest.map(
|
|
17159
|
+
(historyMessage) => buildInferenceHistoryMessage(historyMessage, chatId)
|
|
17069
17160
|
);
|
|
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
17161
|
}
|
|
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
|
|
17162
|
+
if (params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
17163
|
+
messagePayload.embeds = params.preparedEmbeds.map((embed) => ({
|
|
17164
|
+
embed_id: embed.embedId,
|
|
17165
|
+
type: embed.type,
|
|
17166
|
+
content: embed.content,
|
|
17167
|
+
status: embed.status,
|
|
17168
|
+
text_preview: embed.textPreview
|
|
17169
|
+
}));
|
|
17170
|
+
}
|
|
17171
|
+
const encryptedEmbeds = [...params.encryptedEmbeds ?? []];
|
|
17172
|
+
if (!params.incognito && params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
17173
|
+
if (!ownerId2) {
|
|
17174
|
+
throw new Error("Authenticated user identity is required to encrypt embeds.");
|
|
17175
|
+
}
|
|
17176
|
+
if (!chatKeyBytes) {
|
|
17177
|
+
throw new Error("Saved chat key is required to encrypt every prepared embed.");
|
|
17178
|
+
}
|
|
17179
|
+
if (new Set(params.preparedEmbeds.map((embed) => embed.embedId)).size !== params.preparedEmbeds.length) {
|
|
17180
|
+
throw new Error("Prepared message embed IDs must be unique.");
|
|
17181
|
+
}
|
|
17182
|
+
const preparedIds = params.preparedEmbeds.map((embed) => embed.embedId);
|
|
17183
|
+
if (preparedIds.some((id) => !id || id.length > 128)) {
|
|
17184
|
+
throw new Error("Prepared message embed ID is invalid.");
|
|
17185
|
+
}
|
|
17186
|
+
const canonicalReady = /* @__PURE__ */ new Set();
|
|
17187
|
+
for (let offset = 0; offset < preparedIds.length; offset += 20) {
|
|
17188
|
+
const selectedIds = preparedIds.slice(offset, offset + 20);
|
|
17189
|
+
if (Buffer.byteLength(JSON.stringify({ embed_ids: selectedIds }), "utf8") > 4096) {
|
|
17190
|
+
throw new Error("Message embed availability request exceeds its byte limit.");
|
|
17141
17191
|
}
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17192
|
+
const path2 = this.appendTeamQuery(
|
|
17193
|
+
`/v1/embeds/chats/${encodeURIComponent(chatId)}/references/availability`,
|
|
17194
|
+
{ teamId }
|
|
17195
|
+
);
|
|
17196
|
+
const availability = await this.http.post(path2, { embed_ids: selectedIds }, this.getCliRequestHeaders());
|
|
17197
|
+
if (!availability.ok) {
|
|
17198
|
+
throw new Error(`Message embed availability lookup failed with HTTP ${availability.status}.`);
|
|
17199
|
+
}
|
|
17200
|
+
const page = classifyMessageEmbedAvailability(selectedIds, availability.data.results);
|
|
17201
|
+
for (const id of page.ready) canonicalReady.add(id);
|
|
17202
|
+
}
|
|
17203
|
+
const masterKey = this.getMasterKeyBytes();
|
|
17204
|
+
for (const embed of params.preparedEmbeds) {
|
|
17205
|
+
if (canonicalReady.has(embed.embedId)) continue;
|
|
17206
|
+
const encrypted = await encryptEmbed(
|
|
17207
|
+
embed,
|
|
17208
|
+
masterKey,
|
|
17209
|
+
chatKeyBytes,
|
|
17210
|
+
chatId,
|
|
17211
|
+
messageId2,
|
|
17212
|
+
ownerId2
|
|
17213
|
+
);
|
|
17214
|
+
if (!encrypted?.encrypted_content || !encrypted.encrypted_type || encrypted.embed_keys.length !== 2) {
|
|
17215
|
+
throw new Error(`Prepared message embed ${embed.embedId} is missing required ciphertext or key wrappers.`);
|
|
17216
|
+
}
|
|
17217
|
+
encryptedEmbeds.push(encrypted);
|
|
17152
17218
|
}
|
|
17153
17219
|
}
|
|
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
|
-
} : {}
|
|
17220
|
+
if (!params.incognito && encryptedEmbeds.length > 0) {
|
|
17221
|
+
if (!ownerId2) throw new Error("Authenticated owner identity is required for encrypted message embeds.");
|
|
17222
|
+
assertCompleteEncryptedEmbedBundle(encryptedEmbeds, { chatId, messageId: messageId2, ownerId: ownerId2 });
|
|
17223
|
+
}
|
|
17224
|
+
if (encryptedEmbeds.length > 0) {
|
|
17225
|
+
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
17226
|
+
}
|
|
17227
|
+
let dispatchHandlersReady = () => {
|
|
17170
17228
|
};
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
|
|
17176
|
-
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17188
|
-
|
|
17189
|
-
|
|
17229
|
+
const handlersReady = new Promise((resolve20) => {
|
|
17230
|
+
dispatchHandlersReady = resolve20;
|
|
17231
|
+
});
|
|
17232
|
+
let admissionPayload = null;
|
|
17233
|
+
let precollectedResponse = params.precollectResponse && params.incognito && shouldWaitForAi ? ws.collectAiResponse(messageId2, chatId, { onStream: params.onStream, timeoutMs: params.responseTimeoutMs }) : null;
|
|
17234
|
+
if (!params.incognito && chatKeyBytes && encryptedChatKey) {
|
|
17235
|
+
traceSendTiming("preflight_begin");
|
|
17236
|
+
const protocolVersion = 1;
|
|
17237
|
+
const chatKeyVersion = 1;
|
|
17238
|
+
const turnId = privacyTurnId;
|
|
17239
|
+
savedTurnId = turnId;
|
|
17240
|
+
terminalExpectedMessagesV = baselineMessagesV + 1;
|
|
17241
|
+
const recoveryKeypairStartedAt = performance.now();
|
|
17242
|
+
const recoveryKeypair = independentRecoveryKeypair ? await independentRecoveryKeypair : await deriveChatCompletionRecoveryKeypair(
|
|
17243
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
17244
|
+
chatId,
|
|
17245
|
+
chatKeyVersion
|
|
17246
|
+
);
|
|
17247
|
+
recoveryKeypairMs = Math.round(performance.now() - recoveryKeypairStartedAt);
|
|
17248
|
+
const encryptPayloadStartedAt = performance.now();
|
|
17249
|
+
const [encryptedContent, encryptedSenderName, encryptedPiiMappings] = await Promise.all([
|
|
17250
|
+
encryptWithAesGcmCombined(finalMessage, chatKeyBytes),
|
|
17251
|
+
encryptWithAesGcmCombined(teamSenderName, chatKeyBytes),
|
|
17252
|
+
piiMappings.length ? encryptWithAesGcmCombined(JSON.stringify(piiMappings), chatKeyBytes) : null
|
|
17253
|
+
]);
|
|
17254
|
+
const encryptedUserMessage = {
|
|
17255
|
+
client_message_id: messageId2,
|
|
17256
|
+
chat_id: chatId,
|
|
17257
|
+
encrypted_content: encryptedContent,
|
|
17258
|
+
role: "user",
|
|
17190
17259
|
created_at: createdAt,
|
|
17191
17260
|
updated_at: createdAt
|
|
17192
17261
|
};
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17262
|
+
encryptedUserMessage.encrypted_sender_name = encryptedSenderName;
|
|
17263
|
+
if (encryptedPiiMappings) encryptedUserMessage.encrypted_pii_mappings = encryptedPiiMappings;
|
|
17264
|
+
Object.assign(messagePayload, {
|
|
17265
|
+
turn_id: turnId,
|
|
17266
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
17267
|
+
chat_key_version: chatKeyVersion
|
|
17268
|
+
});
|
|
17269
|
+
let preflightInferenceRequest = messagePayload;
|
|
17196
17270
|
if (teamId) {
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17271
|
+
const teamInferenceHistory = [
|
|
17272
|
+
...(messageHistoryForRequest ?? []).map((historyMessage) => ({
|
|
17273
|
+
role: historyMessage.role,
|
|
17274
|
+
content: historyMessage.content,
|
|
17275
|
+
category: historyMessage.category ?? null,
|
|
17276
|
+
sender_name: historyMessage.sender_name ?? historyMessage.role,
|
|
17277
|
+
created_at: historyMessage.created_at
|
|
17278
|
+
})),
|
|
17279
|
+
{
|
|
17280
|
+
role: "user",
|
|
17281
|
+
content: finalMessage,
|
|
17282
|
+
category: null,
|
|
17283
|
+
sender_name: teamSenderName,
|
|
17284
|
+
created_at: createdAt
|
|
17285
|
+
}
|
|
17286
|
+
];
|
|
17287
|
+
const teamMessageEnvelope = { ...encryptedUserMessage, message_id: messageId2 };
|
|
17288
|
+
delete messagePayload.message_history;
|
|
17289
|
+
messagePayload.message = teamMessageEnvelope;
|
|
17290
|
+
if (shouldWaitForAi) {
|
|
17291
|
+
messagePayload.team_ai_invocation = { history: teamInferenceHistory };
|
|
17292
|
+
preflightInferenceRequest = {
|
|
17293
|
+
...messagePayload,
|
|
17294
|
+
message_history: teamInferenceHistory
|
|
17295
|
+
};
|
|
17296
|
+
}
|
|
17297
|
+
}
|
|
17298
|
+
const preflightPayload = {
|
|
17299
|
+
protocol_version: protocolVersion,
|
|
17300
|
+
chat_id: chatId,
|
|
17301
|
+
...teamId ? { team_id: teamId } : {},
|
|
17302
|
+
turn_id: turnId,
|
|
17303
|
+
message_id: messageId2,
|
|
17304
|
+
chat_key_version: chatKeyVersion,
|
|
17305
|
+
encrypted_chat_key: encryptedChatKey,
|
|
17306
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
17307
|
+
expected_messages_v: baselineMessagesV,
|
|
17308
|
+
encrypted_user_message: encryptedUserMessage,
|
|
17309
|
+
inference_request: preflightInferenceRequest,
|
|
17310
|
+
...conditionalPrivacyReceipt ? {
|
|
17311
|
+
privacy_receipt_id: conditionalPrivacyReceipt.receiptId,
|
|
17312
|
+
privacy_generation: conditionalPrivacyReceipt.generation
|
|
17313
|
+
} : {}
|
|
17314
|
+
};
|
|
17315
|
+
if (isNewChat) {
|
|
17316
|
+
if (!chatSlugLookupKey) {
|
|
17317
|
+
throw new Error("Encrypted chat slug lookup key was not initialized.");
|
|
17318
|
+
}
|
|
17319
|
+
const initialTitle = teamId && !shouldWaitForAi ? teamChatTitleFromMessage(params.message) : "";
|
|
17320
|
+
const slugMetadata = await buildEncryptedObjectSlugMetadata({
|
|
17321
|
+
value: selectNewChatSlugValue({
|
|
17322
|
+
message: finalMessage,
|
|
17323
|
+
chatId,
|
|
17324
|
+
explicitSlug: params.slug
|
|
17325
|
+
}),
|
|
17326
|
+
encryptionKey: chatKeyBytes,
|
|
17327
|
+
lookupKey: chatSlugLookupKey
|
|
17203
17328
|
});
|
|
17329
|
+
preflightPayload.encrypted_chat_metadata = {
|
|
17330
|
+
encrypted_title: await encryptWithAesGcmCombined(initialTitle, chatKeyBytes),
|
|
17331
|
+
encrypted_slug: slugMetadata.encrypted_slug,
|
|
17332
|
+
slug_lookup_hash: slugMetadata.slug_lookup_hash,
|
|
17333
|
+
encrypted_chat_key: encryptedChatKey,
|
|
17334
|
+
created_at: createdAt,
|
|
17335
|
+
updated_at: createdAt
|
|
17336
|
+
};
|
|
17204
17337
|
}
|
|
17205
|
-
|
|
17206
|
-
|
|
17338
|
+
encryptPayloadMs = Math.round(performance.now() - encryptPayloadStartedAt);
|
|
17339
|
+
try {
|
|
17340
|
+
if (teamId) {
|
|
17341
|
+
await stageCliTeamNotificationPreview(ws, {
|
|
17342
|
+
teamId,
|
|
17343
|
+
chatId,
|
|
17344
|
+
messageId: messageId2,
|
|
17345
|
+
content: finalMessage,
|
|
17346
|
+
title: notificationTitle
|
|
17347
|
+
});
|
|
17348
|
+
}
|
|
17349
|
+
if (params.projectId) {
|
|
17350
|
+
await prepareCliProjectFocusForPreflight({
|
|
17351
|
+
ws,
|
|
17352
|
+
chatId,
|
|
17353
|
+
teamId,
|
|
17354
|
+
isNewChat,
|
|
17355
|
+
encryptedChatKey,
|
|
17356
|
+
createdAt,
|
|
17357
|
+
activateFocus: () => activateCliProjectFocus(this, params.projectId, chatId, teamId)
|
|
17358
|
+
});
|
|
17359
|
+
const focusedContext = await prepareCliJevContext(
|
|
17360
|
+
this,
|
|
17361
|
+
chatId,
|
|
17362
|
+
{ teamId, personal: !teamId },
|
|
17363
|
+
params.jevContext,
|
|
17364
|
+
finalMessage
|
|
17365
|
+
);
|
|
17366
|
+
pendingContext = Promise.resolve(focusedContext);
|
|
17367
|
+
}
|
|
17368
|
+
admissionPayload = preflightPayload;
|
|
17369
|
+
traceSendTiming("preflight_ready");
|
|
17370
|
+
} catch (error) {
|
|
17371
|
+
ws.close();
|
|
17372
|
+
throw error;
|
|
17373
|
+
}
|
|
17374
|
+
}
|
|
17375
|
+
const initialHeaderOwner = this.preparedChatOwner(this.requireSession(), teamId);
|
|
17376
|
+
const startInitialHeader = () => {
|
|
17377
|
+
if (!initialAdmissionDurable || !initialHeaderNotice || !chatKeyBytes || !isNewChat || params.incognito || !savedTurnId) return;
|
|
17378
|
+
const notice = { ...initialHeaderNotice }, key = chatKeyBytes, turn = savedTurnId;
|
|
17379
|
+
const titleChanged = !!notice.chatTitle && notice.chatTitle !== initialTitleScheduled;
|
|
17380
|
+
const iconChanged = !!notice.chatIcon && notice.chatIcon !== initialIconScheduled;
|
|
17381
|
+
if (!titleChanged && !iconChanged) return;
|
|
17382
|
+
initialTitleScheduled = notice.chatTitle ?? initialTitleScheduled;
|
|
17383
|
+
initialIconScheduled = notice.chatIcon ?? initialIconScheduled;
|
|
17384
|
+
const current2 = () => !initialHeaderStopped && ws.isOpen() && this.preparedChatOwnerCurrent(initialHeaderOwner, teamId) && this.activeSubmittedChatTurns.get(chatId) === turn;
|
|
17385
|
+
initialHeaderWork = (initialHeaderWork ?? Promise.resolve()).then(async () => {
|
|
17386
|
+
if (!current2()) return;
|
|
17387
|
+
if (titleChanged) await this.persistPostProcessingMetadata({
|
|
17207
17388
|
ws,
|
|
17208
17389
|
chatId,
|
|
17209
17390
|
teamId,
|
|
17210
|
-
|
|
17391
|
+
chatKeyBytes: key,
|
|
17211
17392
|
encryptedChatKey,
|
|
17212
|
-
|
|
17213
|
-
|
|
17393
|
+
followUpSuggestions: [],
|
|
17394
|
+
suggestedFollowupMessage: null,
|
|
17395
|
+
newChatSuggestions: [],
|
|
17396
|
+
chatSummary: null,
|
|
17397
|
+
chatTags: [],
|
|
17398
|
+
updatedChatTitle: notice.chatTitle ?? null,
|
|
17399
|
+
sourceTitleVersion: notice.sourceTitleVersion ?? null,
|
|
17400
|
+
sourceMetadataVersion: notice.sourceMetadataVersion ?? null,
|
|
17401
|
+
unversionedGeneratedTitle: notice.sourceTitleVersion === void 0 && notice.sourceMetadataVersion === void 0,
|
|
17402
|
+
ownerCurrent: current2
|
|
17214
17403
|
});
|
|
17215
|
-
|
|
17216
|
-
|
|
17217
|
-
|
|
17218
|
-
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17404
|
+
if (!current2()) return;
|
|
17405
|
+
if (iconChanged || notice.category) {
|
|
17406
|
+
const [encryptedIcon, encryptedCategory] = await Promise.all([
|
|
17407
|
+
notice.chatIcon ? encryptWithAesGcmCombined(notice.chatIcon, key) : null,
|
|
17408
|
+
notice.category ? encryptWithAesGcmCombined(notice.category, key) : null
|
|
17409
|
+
]);
|
|
17410
|
+
if (!current2()) return;
|
|
17411
|
+
const stored = ws.waitForMessage("encrypted_metadata_stored", (value) => value.chat_id === chatId, 2e4);
|
|
17412
|
+
void stored.catch(() => {
|
|
17413
|
+
});
|
|
17414
|
+
await ws.sendAsync("encrypted_chat_metadata", {
|
|
17415
|
+
chat_id: chatId,
|
|
17416
|
+
...teamId ? { team_id: teamId } : {},
|
|
17417
|
+
encrypted_chat_key: encryptedChatKey,
|
|
17418
|
+
...encryptedIcon ? { encrypted_icon: encryptedIcon } : {},
|
|
17419
|
+
...encryptedCategory ? { encrypted_chat_category: encryptedCategory } : {}
|
|
17420
|
+
});
|
|
17421
|
+
await stored;
|
|
17422
|
+
}
|
|
17423
|
+
if (current2()) clearSyncCache(teamId);
|
|
17424
|
+
}).catch(() => {
|
|
17425
|
+
});
|
|
17426
|
+
};
|
|
17427
|
+
const onTurnStream = (event) => {
|
|
17428
|
+
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)) {
|
|
17429
|
+
initialHeaderNotice = { ...initialHeaderNotice, ...event };
|
|
17430
|
+
startInitialHeader();
|
|
17223
17431
|
}
|
|
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({
|
|
17432
|
+
params.onStream?.(event);
|
|
17433
|
+
};
|
|
17434
|
+
if (!params.incognito && chatKeyBytes && savedTurnId) {
|
|
17435
|
+
const key = chatKeyBytes;
|
|
17436
|
+
const ownedTurnId = savedTurnId;
|
|
17437
|
+
this.activeSubmittedChatTurns.set(chatId, ownedTurnId);
|
|
17438
|
+
const owner2 = this.preparedChatOwner(this.requireSession(), teamId);
|
|
17439
|
+
const stopOwned = () => optionalMetadata?.stop();
|
|
17440
|
+
optionalMetadata = observeOwnedTurnMetadata({
|
|
17244
17441
|
ws,
|
|
17245
17442
|
chatId,
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
|
|
17257
|
-
|
|
17258
|
-
|
|
17443
|
+
turnId: savedTurnId,
|
|
17444
|
+
userMessageId: messageId2,
|
|
17445
|
+
closeAfterMetadata: true,
|
|
17446
|
+
ownerCurrent: () => this.preparedChatOwnerCurrent(owner2, teamId),
|
|
17447
|
+
persist: async (metadata) => {
|
|
17448
|
+
await this.persistPostProcessingMetadata({ ws, teamId, chatKeyBytes: key, encryptedChatKey, ...metadata });
|
|
17449
|
+
clearSyncCache(teamId);
|
|
17450
|
+
},
|
|
17451
|
+
onMetadata: params.onMetadata,
|
|
17452
|
+
onReleased: () => {
|
|
17453
|
+
this.ownedMetadataSubscriptions.delete(stopOwned);
|
|
17454
|
+
if (this.activeSubmittedChatTurns.get(chatId) === ownedTurnId) this.activeSubmittedChatTurns.delete(chatId);
|
|
17455
|
+
if (metadataTransportOwned) ws.close();
|
|
17456
|
+
}
|
|
17259
17457
|
});
|
|
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();
|
|
17458
|
+
while (this.ownedMetadataSubscriptions.size >= 8) this.ownedMetadataSubscriptions.values().next().value?.();
|
|
17459
|
+
this.ownedMetadataSubscriptions.add(stopOwned);
|
|
17287
17460
|
}
|
|
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
|
-
}
|
|
17461
|
+
if (!params.incognito && shouldWaitForAi) {
|
|
17462
|
+
precollectedResponse = ws.collectAiResponse(messageId2, chatId, {
|
|
17463
|
+
waitForPostProcessing: false,
|
|
17464
|
+
onStream: onTurnStream,
|
|
17465
|
+
onSubChatEvent: async (event) => {
|
|
17466
|
+
await handlersReady;
|
|
17467
|
+
await handleSubChatEvent(event);
|
|
17468
|
+
},
|
|
17469
|
+
onAppSettingsMemoriesRequest: async (event) => {
|
|
17470
|
+
await handlersReady;
|
|
17471
|
+
await handleAppSettingsMemoriesRequest(event);
|
|
17472
|
+
},
|
|
17473
|
+
timeoutMs: params.responseTimeoutMs,
|
|
17474
|
+
recoveryTurnId: savedTurnId,
|
|
17475
|
+
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
17476
|
+
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
17477
|
+
});
|
|
17478
|
+
}
|
|
17479
|
+
const queuedAdmission = !params.incognito && shouldWaitForAi ? ws.waitForMessage("message_queued", (payload) => {
|
|
17480
|
+
const event = payload;
|
|
17481
|
+
return event.chat_id === chatId && event.user_message_id === messageId2;
|
|
17482
|
+
}, params.responseTimeoutMs ?? 9e4) : null;
|
|
17483
|
+
if (queuedAdmission) void queuedAdmission.catch(() => {
|
|
17313
17484
|
});
|
|
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)
|
|
17485
|
+
const pausedHandoff = !params.incognito && shouldWaitForAi ? ws.waitForMessage("queued_handoff_paused", (payload) => {
|
|
17486
|
+
const event = payload;
|
|
17487
|
+
return event.chat_id === chatId && (event.message_id === messageId2 || event.message_id === void 0);
|
|
17488
|
+
}, params.responseTimeoutMs ?? 9e4) : null;
|
|
17489
|
+
if (pausedHandoff) void pausedHandoff.catch(() => {
|
|
17333
17490
|
});
|
|
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;
|
|
17491
|
+
const confirmed = ws.waitForMessage(
|
|
17492
|
+
"chat_message_confirmed",
|
|
17493
|
+
(payload) => {
|
|
17494
|
+
const eventPayload = payload;
|
|
17495
|
+
return eventPayload.chat_id === chatId && eventPayload.message_id === messageId2;
|
|
17496
|
+
},
|
|
17497
|
+
params.responseTimeoutMs ?? DEFAULT_CHAT_MESSAGE_CONFIRMATION_TIMEOUT_MS
|
|
17498
|
+
);
|
|
17499
|
+
let confirmedPayload;
|
|
17500
|
+
let admissionContinuationPhase = "before_dispatch";
|
|
17501
|
+
let admissionAckObserved = false;
|
|
17502
|
+
let routingFrameSent = false;
|
|
17503
|
+
let detailsFrameSent = false;
|
|
17504
|
+
const admissionContinuationStartedAt = performance.now();
|
|
17505
|
+
try {
|
|
17506
|
+
if (admissionPayload) {
|
|
17507
|
+
if (!useDeferredContext && recoveryDiscovery) await recoveryDiscovery;
|
|
17508
|
+
if (recoveryDiscoveryError) throw recoveryDiscoveryError;
|
|
17509
|
+
if (!contextRequestId && pendingContext) {
|
|
17510
|
+
const context = await pendingContext;
|
|
17511
|
+
Object.assign(messagePayload, context);
|
|
17512
|
+
Object.assign(admissionPayload.inference_request, context);
|
|
17434
17513
|
}
|
|
17435
17514
|
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17436
|
-
|
|
17437
|
-
|
|
17438
|
-
rejectContextAck = reject;
|
|
17439
|
-
});
|
|
17440
|
-
void contextAckRejection.catch(() => {
|
|
17515
|
+
const admissionAck = ws.waitForMessage("chat_turn_preflight_ack", (payload) => payload.turn_id === savedTurnId);
|
|
17516
|
+
void admissionAck.catch(() => {
|
|
17441
17517
|
});
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17518
|
+
const frameDispatchAt = performance.now();
|
|
17519
|
+
admissionContinuationPhase = "await_ack";
|
|
17520
|
+
await consumeSocketMemoryReceipt();
|
|
17521
|
+
await ws.sendAsync(useCombinedAdmission ? "chat_turn_admit" : "chat_turn_preflight", admissionPayload);
|
|
17522
|
+
traceSendTiming("admission_frame");
|
|
17523
|
+
if (process.env.OPENMATES_DEBUG === "1") {
|
|
17524
|
+
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)}`);
|
|
17525
|
+
}
|
|
17526
|
+
const ackPayload = (await admissionAck).payload;
|
|
17527
|
+
traceSendTiming("admission_ack");
|
|
17528
|
+
admissionAckObserved = true;
|
|
17529
|
+
if (typeof ackPayload.preflight_id !== "string" || !ackPayload.preflight_id) {
|
|
17530
|
+
throw new Error("Encrypted chat admission acknowledgement omitted preflight_id.");
|
|
17531
|
+
}
|
|
17532
|
+
if (typeof ackPayload.committed_messages_v === "number" && Number.isSafeInteger(ackPayload.committed_messages_v)) {
|
|
17533
|
+
terminalExpectedMessagesV = ackPayload.committed_messages_v;
|
|
17534
|
+
}
|
|
17535
|
+
initialAdmissionDurable = true;
|
|
17536
|
+
startInitialHeader();
|
|
17537
|
+
if (!useCombinedAdmission) {
|
|
17538
|
+
Object.assign(messagePayload, { protocol_version: admissionPayload.protocol_version, preflight_id: ackPayload.preflight_id });
|
|
17539
|
+
await ws.sendAsync("chat_message_added", messagePayload);
|
|
17540
|
+
}
|
|
17541
|
+
if (pendingContext && contextRequestId) {
|
|
17542
|
+
admissionContinuationPhase = "await_routing";
|
|
17543
|
+
const identity3 = {
|
|
17544
|
+
chat_id: chatId,
|
|
17545
|
+
turn_id: savedTurnId,
|
|
17546
|
+
message_id: messageId2,
|
|
17547
|
+
request_id: contextRequestId,
|
|
17548
|
+
...teamId ? { team_id: teamId } : {}
|
|
17549
|
+
};
|
|
17550
|
+
let failureNotice = null;
|
|
17551
|
+
const notifyContextFailure = (reason) => {
|
|
17552
|
+
if (failureNotice) return failureNotice;
|
|
17553
|
+
if (!ws.supportsServerCapability("chat_turn_context_failure_v1") || !ws.isOpen() || !contextOwnerIsCurrent() || !savedTurnId) return Promise.resolve();
|
|
17554
|
+
failureNotice = (async () => {
|
|
17555
|
+
const failureAck = ws.waitForMessage("chat_turn_context_failed_ack", (payload) => {
|
|
17556
|
+
const event = payload;
|
|
17557
|
+
return event.chat_id === chatId && event.turn_id === savedTurnId && event.message_id === messageId2 && event.request_id === contextRequestId;
|
|
17558
|
+
}, 5e3);
|
|
17559
|
+
void failureAck.catch(() => {
|
|
17560
|
+
});
|
|
17561
|
+
await ws.sendAsync("chat_turn_context_failed", { ...identity3, reason });
|
|
17562
|
+
const status = (await failureAck).payload;
|
|
17563
|
+
if (status.status !== "accepted" && status.status !== "duplicate") {
|
|
17564
|
+
throw new Error("Chat context failure receipt was rejected.");
|
|
17565
|
+
}
|
|
17566
|
+
})();
|
|
17567
|
+
return failureNotice;
|
|
17568
|
+
};
|
|
17569
|
+
let routing;
|
|
17570
|
+
try {
|
|
17571
|
+
routing = useSelectionDetailsV2 ? await projectRoutingContext : deferredProjectDirectory ? (await Promise.all([pendingContext, projectRoutingContext]))[0] : params.projectId ? await pendingContext : await Promise.race([routingContext, pendingContext]);
|
|
17572
|
+
} catch (error) {
|
|
17573
|
+
try {
|
|
17574
|
+
await notifyContextFailure(useSelectionDetailsV2 ? "project_discovery_failed" : "context_preparation_failed");
|
|
17575
|
+
} catch {
|
|
17576
|
+
}
|
|
17577
|
+
throw error;
|
|
17455
17578
|
}
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17579
|
+
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17580
|
+
let rejectContextAck;
|
|
17581
|
+
contextAckRejection = new Promise((_resolve, reject) => {
|
|
17582
|
+
rejectContextAck = reject;
|
|
17459
17583
|
});
|
|
17460
|
-
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
|
|
17465
|
-
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17470
|
-
|
|
17471
|
-
|
|
17584
|
+
void contextAckRejection.catch(() => {
|
|
17585
|
+
});
|
|
17586
|
+
let rejectedPhaseSeen = false;
|
|
17587
|
+
const attemptedContextPhases = /* @__PURE__ */ new Set();
|
|
17588
|
+
removeContextAckWatcher = ws.onMessageType("chat_turn_context_ack", (event) => {
|
|
17589
|
+
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;
|
|
17590
|
+
if (event.status === "accepted" || event.status === "duplicate") {
|
|
17591
|
+
traceSendTiming(event.phase === "routing" ? "routing_ack" : "details_ack");
|
|
17592
|
+
return;
|
|
17593
|
+
}
|
|
17594
|
+
if (event.status !== "rejected") return;
|
|
17595
|
+
rejectedPhaseSeen = true;
|
|
17596
|
+
if (!contextOwnerIsCurrent()) {
|
|
17597
|
+
rejectContextAck(new Error("Chat workspace changed while preparing context."));
|
|
17598
|
+
return;
|
|
17599
|
+
}
|
|
17600
|
+
void notifyContextFailure("context_preparation_failed").catch(() => {
|
|
17601
|
+
}).then(() => {
|
|
17602
|
+
rejectContextAck(new Error("Chat context delivery was rejected."));
|
|
17603
|
+
});
|
|
17604
|
+
});
|
|
17605
|
+
const { project_focus_candidates, related_task_candidates, project_focus_catalog, accepted_plan_context } = routing;
|
|
17606
|
+
traceSendTiming("routing_prepared");
|
|
17607
|
+
attemptedContextPhases.add("routing");
|
|
17608
|
+
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 } });
|
|
17609
|
+
routingFrameSent = true;
|
|
17610
|
+
traceSendTiming("routing_frame");
|
|
17611
|
+
admissionContinuationPhase = "await_details";
|
|
17612
|
+
let context;
|
|
17472
17613
|
try {
|
|
17473
|
-
await
|
|
17474
|
-
} catch {
|
|
17614
|
+
context = await Promise.race([pendingContext, contextAckRejection]);
|
|
17615
|
+
} catch (error) {
|
|
17616
|
+
try {
|
|
17617
|
+
await notifyContextFailure("context_preparation_failed");
|
|
17618
|
+
} catch {
|
|
17619
|
+
}
|
|
17620
|
+
throw error;
|
|
17475
17621
|
}
|
|
17476
|
-
throw
|
|
17622
|
+
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17623
|
+
traceSendTiming("details_prepared");
|
|
17624
|
+
attemptedContextPhases.add("details");
|
|
17625
|
+
await ws.sendAsync("chat_turn_context_ready", { ...identity3, phase: "details", context: {
|
|
17626
|
+
custom_memory_documents: context.custom_memory_documents ?? [],
|
|
17627
|
+
project_focus_documents: context.project_focus_documents ?? [],
|
|
17628
|
+
project_context_documents: context.project_context_documents ?? [],
|
|
17629
|
+
...useSelectionDetailsV2 ? {
|
|
17630
|
+
related_task_candidates: context.related_task_candidates ?? [],
|
|
17631
|
+
project_focus_catalog: context.project_focus_catalog ?? [],
|
|
17632
|
+
accepted_plan_context: context.accepted_plan_context ?? null,
|
|
17633
|
+
...connectedAccountDirectory ? { connected_account_directory: connectedAccountDirectory } : {}
|
|
17634
|
+
} : {}
|
|
17635
|
+
} });
|
|
17636
|
+
detailsFrameSent = true;
|
|
17637
|
+
traceSendTiming("details_frame");
|
|
17477
17638
|
}
|
|
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");
|
|
17639
|
+
admissionContinuationPhase = "await_confirmation";
|
|
17640
|
+
} else {
|
|
17641
|
+
await consumeSocketMemoryReceipt();
|
|
17642
|
+
await ws.sendAsync("chat_message_added", messagePayload);
|
|
17494
17643
|
}
|
|
17495
|
-
|
|
17496
|
-
}
|
|
17497
|
-
|
|
17498
|
-
|
|
17644
|
+
confirmedPayload = (await (contextAckRejection ? Promise.race([confirmed, contextAckRejection]) : confirmed)).payload;
|
|
17645
|
+
} catch (error) {
|
|
17646
|
+
if (process.env.OPENMATES_DEBUG === "1" && admissionPayload) {
|
|
17647
|
+
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)}`);
|
|
17648
|
+
}
|
|
17649
|
+
dispatchHandlersReady();
|
|
17650
|
+
void confirmed.catch(() => {
|
|
17651
|
+
});
|
|
17652
|
+
if (precollectedResponse) void precollectedResponse.catch(() => {
|
|
17653
|
+
});
|
|
17654
|
+
ws.close();
|
|
17655
|
+
throw error;
|
|
17499
17656
|
}
|
|
17500
|
-
|
|
17501
|
-
|
|
17502
|
-
if (process.env.OPENMATES_DEBUG === "1" && admissionPayload) {
|
|
17503
|
-
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)}`);
|
|
17657
|
+
if (!params.incognito && typeof confirmedPayload.new_messages_v === "number" && Number.isSafeInteger(confirmedPayload.new_messages_v)) {
|
|
17658
|
+
terminalExpectedMessagesV = confirmedPayload.new_messages_v;
|
|
17504
17659
|
}
|
|
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;
|
|
17539
|
-
}
|
|
17540
|
-
if (request.maxDirectSubChats !== null && request.existingSubChats !== null && request.existingSubChats + count > request.maxDirectSubChats) {
|
|
17541
|
-
return false;
|
|
17660
|
+
if (!params.incognito && (!teamId || shouldWaitForAi)) {
|
|
17661
|
+
clearSyncCache(teamId);
|
|
17542
17662
|
}
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
|
|
17555
|
-
|
|
17556
|
-
|
|
17557
|
-
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17663
|
+
let assistant = "";
|
|
17664
|
+
let assistantMessageId = null;
|
|
17665
|
+
let category = null;
|
|
17666
|
+
let modelName = null;
|
|
17667
|
+
let tokenUsage = null;
|
|
17668
|
+
let promptBudget = null;
|
|
17669
|
+
let followUpSuggestions = [];
|
|
17670
|
+
let suggestedFollowupMessage = null;
|
|
17671
|
+
let taskProposals = [];
|
|
17672
|
+
let taskUpdateProposals = [];
|
|
17673
|
+
let taskEvents = [];
|
|
17674
|
+
let pendingTaskUpdateJobs = [];
|
|
17675
|
+
let subChatEvents = [];
|
|
17676
|
+
const appSettingsMemoryRequests = [];
|
|
17677
|
+
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
17678
|
+
const isApprovalWithinServerLimits = (request) => {
|
|
17679
|
+
const count = request.subChats.length;
|
|
17680
|
+
if (count === 0) return false;
|
|
17681
|
+
if (request.remainingSubChats !== null && count > request.remainingSubChats) {
|
|
17682
|
+
return false;
|
|
17563
17683
|
}
|
|
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
|
|
17684
|
+
if (request.maxDirectSubChats !== null && request.existingSubChats !== null && request.existingSubChats + count > request.maxDirectSubChats) {
|
|
17685
|
+
return false;
|
|
17606
17686
|
}
|
|
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
|
|
17687
|
+
return true;
|
|
17688
|
+
};
|
|
17689
|
+
const isAutoApprovalWithinServerLimits = (request) => {
|
|
17690
|
+
if (!isApprovalWithinServerLimits(request)) return false;
|
|
17691
|
+
return request.maxAutoSubChats === null || request.subChats.length <= request.maxAutoSubChats;
|
|
17692
|
+
};
|
|
17693
|
+
const handleSubChatEvent = async (event) => {
|
|
17694
|
+
params.onSubChatEvent?.(event);
|
|
17695
|
+
if (event.type === "spawn_sub_chats") {
|
|
17696
|
+
if (params.incognito || !chatKeyBytes) return;
|
|
17697
|
+
const rawSubChats = event.payload.sub_chats;
|
|
17698
|
+
if (!Array.isArray(rawSubChats)) return;
|
|
17699
|
+
const metadataPayloads = await buildSubChatEncryptedMetadataPayloads({
|
|
17700
|
+
parentChatId: chatId,
|
|
17701
|
+
parentChatKey: chatKeyBytes,
|
|
17702
|
+
encryptedParentChatKey: encryptedChatKey,
|
|
17703
|
+
subChats: rawSubChats
|
|
17639
17704
|
});
|
|
17705
|
+
for (const metadataPayload of metadataPayloads) {
|
|
17706
|
+
await ws.sendAsync("encrypted_chat_metadata", metadataPayload);
|
|
17707
|
+
}
|
|
17708
|
+
return;
|
|
17640
17709
|
}
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
const
|
|
17648
|
-
|
|
17649
|
-
|
|
17710
|
+
if (event.type !== "sub_chat_confirmation_required") return;
|
|
17711
|
+
const payload = event.payload;
|
|
17712
|
+
const confirmationChatId = typeof payload.chat_id === "string" ? payload.chat_id : chatId;
|
|
17713
|
+
const taskId = typeof payload.task_id === "string" ? payload.task_id : null;
|
|
17714
|
+
const subChats = Array.isArray(payload.sub_chats) ? payload.sub_chats : [];
|
|
17715
|
+
if (!taskId) return;
|
|
17716
|
+
const request = {
|
|
17717
|
+
chatId: confirmationChatId,
|
|
17718
|
+
taskId,
|
|
17719
|
+
subChats,
|
|
17720
|
+
maxAutoSubChats: numberOrNull(payload.max_auto_sub_chats),
|
|
17721
|
+
maxDirectSubChats: numberOrNull(payload.max_direct_sub_chats),
|
|
17722
|
+
existingSubChats: numberOrNull(payload.existing_sub_chats),
|
|
17723
|
+
remainingSubChats: numberOrNull(payload.remaining_sub_chats)
|
|
17724
|
+
};
|
|
17725
|
+
const withinLimits = isApprovalWithinServerLimits(request);
|
|
17726
|
+
const approved = params.autoApproveSubChats ? isAutoApprovalWithinServerLimits(request) : withinLimits && params.onSubChatApprovalRequest ? await params.onSubChatApprovalRequest(request) : false;
|
|
17727
|
+
await ws.sendAsync(
|
|
17728
|
+
"sub_chat_confirmation",
|
|
17729
|
+
buildSubChatConfirmationPayload({
|
|
17730
|
+
chatId: request.chatId,
|
|
17731
|
+
taskId: request.taskId,
|
|
17732
|
+
approved,
|
|
17733
|
+
approveCount: approved ? request.subChats.length : void 0
|
|
17734
|
+
})
|
|
17650
17735
|
);
|
|
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
|
|
17736
|
+
};
|
|
17737
|
+
const persistSystemMessage = async (systemMessage) => {
|
|
17738
|
+
if (!chatKeyBytes) return;
|
|
17739
|
+
await ws.sendAsync("chat_system_message_added", {
|
|
17740
|
+
chat_id: chatId,
|
|
17741
|
+
message: {
|
|
17742
|
+
message_id: systemMessage.message_id,
|
|
17743
|
+
role: "system",
|
|
17744
|
+
encrypted_content: await encryptWithAesGcmCombined(
|
|
17745
|
+
systemMessage.content,
|
|
17746
|
+
chatKeyBytes
|
|
17747
|
+
),
|
|
17748
|
+
created_at: systemMessage.created_at,
|
|
17749
|
+
user_message_id: systemMessage.user_message_id
|
|
17750
|
+
}
|
|
17674
17751
|
});
|
|
17675
|
-
await
|
|
17752
|
+
await ws.waitForMessage(
|
|
17753
|
+
"system_message_confirmed",
|
|
17754
|
+
(payload) => {
|
|
17755
|
+
const p = payload;
|
|
17756
|
+
return p.message_id === systemMessage.message_id;
|
|
17757
|
+
},
|
|
17758
|
+
2e4
|
|
17759
|
+
);
|
|
17760
|
+
};
|
|
17761
|
+
const persistMemoryRequestSystemMessage = async (event) => {
|
|
17762
|
+
const requestId = event.requestId ?? `${messageId2}-memory-request`;
|
|
17763
|
+
await persistSystemMessage(buildAppSettingsMemoryRequestSystemMessage({
|
|
17676
17764
|
userMessageId: messageId2,
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17765
|
+
requestId,
|
|
17766
|
+
requestedKeys: event.requestedKeys,
|
|
17767
|
+
entryCounts: memoryCountsLoaded ? new Map(event.requestedKeys.map((key) => [key, availableMemories.filter((memory) => `${memory.app_id}-${memory.item_type}` === key).length])) : void 0,
|
|
17680
17768
|
createdAt: Math.floor(Date.now() / 1e3)
|
|
17681
17769
|
}));
|
|
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.");
|
|
17770
|
+
};
|
|
17771
|
+
const responseCategoriesFromMemories = (approvedMemories) => {
|
|
17772
|
+
const counts = /* @__PURE__ */ new Map();
|
|
17773
|
+
for (const memory of approvedMemories) {
|
|
17774
|
+
const key = `${memory.app_id}-${memory.item_key}`;
|
|
17775
|
+
const existing = counts.get(key);
|
|
17776
|
+
if (existing) {
|
|
17777
|
+
existing.entryCount += 1;
|
|
17778
|
+
} else {
|
|
17779
|
+
counts.set(key, {
|
|
17780
|
+
appId: memory.app_id,
|
|
17781
|
+
itemType: memory.item_key,
|
|
17782
|
+
entryCount: 1
|
|
17783
|
+
});
|
|
17778
17784
|
}
|
|
17779
|
-
|
|
17780
|
-
|
|
17781
|
-
|
|
17782
|
-
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17786
|
-
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
17785
|
+
}
|
|
17786
|
+
return [...counts.values()];
|
|
17787
|
+
};
|
|
17788
|
+
const handleAppSettingsMemoriesRequest = async (event) => {
|
|
17789
|
+
await persistMemoryRequestSystemMessage(event);
|
|
17790
|
+
if (params.autoApproveMemories) {
|
|
17791
|
+
const requested = new Set(event.requestedKeys);
|
|
17792
|
+
const unadvertisedKeys = event.requestedKeys.filter(
|
|
17793
|
+
(key) => !memoryMetadataKeys.includes(key)
|
|
17794
|
+
);
|
|
17795
|
+
if (unadvertisedKeys.length > 0) {
|
|
17796
|
+
throw new Error(
|
|
17797
|
+
`Refusing to auto-approve unadvertised memory categories: ${unadvertisedKeys.join(", ")}`
|
|
17798
|
+
);
|
|
17792
17799
|
}
|
|
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(() => {
|
|
17800
|
+
const approvedMemories = availableMemories.filter((memory) => requested.has(`${memory.app_id}-${memory.item_type}`)).map((memory) => ({
|
|
17801
|
+
app_id: memory.app_id,
|
|
17802
|
+
item_key: memory.item_type,
|
|
17803
|
+
content: memory.data
|
|
17804
|
+
}));
|
|
17805
|
+
const approvedKeys = [
|
|
17806
|
+
...new Set(approvedMemories.map((memory) => `${memory.app_id}-${memory.item_key}`))
|
|
17807
|
+
];
|
|
17808
|
+
const categories = responseCategoriesFromMemories(approvedMemories);
|
|
17809
|
+
appSettingsMemoryRequests.push({
|
|
17810
|
+
requestId: event.requestId,
|
|
17811
|
+
requestedKeys: event.requestedKeys,
|
|
17812
|
+
approvedKeys,
|
|
17813
|
+
entryCount: approvedMemories.length
|
|
17816
17814
|
});
|
|
17817
|
-
|
|
17818
|
-
|
|
17819
|
-
|
|
17820
|
-
void responsePromise.catch(() => {
|
|
17815
|
+
await ws.sendAsync("app_settings_memories_confirmed", {
|
|
17816
|
+
chat_id: event.chatId,
|
|
17817
|
+
app_settings_memories: approvedMemories
|
|
17821
17818
|
});
|
|
17822
|
-
|
|
17823
|
-
return {
|
|
17824
|
-
status: "queued",
|
|
17825
|
-
chatId,
|
|
17826
|
-
messageId: null,
|
|
17819
|
+
await persistSystemMessage(buildAppSettingsMemoryResponseSystemMessage({
|
|
17827
17820
|
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
|
-
};
|
|
17821
|
+
messageId: `${messageId2}-memory-response`,
|
|
17822
|
+
action: "included",
|
|
17823
|
+
categories,
|
|
17824
|
+
createdAt: Math.floor(Date.now() / 1e3)
|
|
17825
|
+
}));
|
|
17826
|
+
return;
|
|
17844
17827
|
}
|
|
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
|
-
};
|
|
17828
|
+
appSettingsMemoryRequests.push({
|
|
17829
|
+
requestId: event.requestId,
|
|
17830
|
+
requestedKeys: event.requestedKeys,
|
|
17831
|
+
approvedKeys: [],
|
|
17832
|
+
entryCount: 0
|
|
17833
|
+
});
|
|
17834
|
+
throw new Error(
|
|
17835
|
+
`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.`
|
|
17836
|
+
);
|
|
17837
|
+
};
|
|
17838
|
+
const persistedTaskEventIds = /* @__PURE__ */ new Set();
|
|
17839
|
+
const persistTaskEventSystemMessages = async (events) => {
|
|
17840
|
+
if (!chatKeyBytes || events.length === 0) return;
|
|
17841
|
+
for (const event of events) {
|
|
17842
|
+
if (persistedTaskEventIds.has(event.event_id)) continue;
|
|
17843
|
+
await this.persistEncryptedSystemMessage(
|
|
17844
|
+
ws,
|
|
17845
|
+
await buildTaskEventSystemMessage({
|
|
17846
|
+
chatKey: chatKeyBytes,
|
|
17847
|
+
userMessageId: messageId2,
|
|
17848
|
+
event
|
|
17849
|
+
}),
|
|
17850
|
+
chatId
|
|
17851
|
+
);
|
|
17852
|
+
persistedTaskEventIds.add(event.event_id);
|
|
17880
17853
|
}
|
|
17881
|
-
|
|
17882
|
-
|
|
17883
|
-
|
|
17854
|
+
};
|
|
17855
|
+
const streamOpts = {
|
|
17856
|
+
waitForPostProcessing: false,
|
|
17857
|
+
onStream: onTurnStream,
|
|
17858
|
+
onSubChatEvent: handleSubChatEvent,
|
|
17859
|
+
onAppSettingsMemoriesRequest: handleAppSettingsMemoriesRequest,
|
|
17860
|
+
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
17861
|
+
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
17862
|
+
};
|
|
17863
|
+
dispatchHandlersReady();
|
|
17864
|
+
if (!shouldWaitForAi) {
|
|
17865
|
+
ws.close();
|
|
17866
|
+
} else if (params.incognito) {
|
|
17867
|
+
try {
|
|
17868
|
+
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId2, chatId, {
|
|
17869
|
+
...streamOpts,
|
|
17870
|
+
timeoutMs: params.responseTimeoutMs,
|
|
17871
|
+
recoveryTurnId: savedTurnId
|
|
17872
|
+
}));
|
|
17873
|
+
assistantMessageId = resp.messageId;
|
|
17874
|
+
assistant = resp.content;
|
|
17875
|
+
category = resp.category;
|
|
17876
|
+
modelName = resp.modelName;
|
|
17877
|
+
tokenUsage = resp.tokenUsage;
|
|
17878
|
+
promptBudget = resp.promptBudget;
|
|
17879
|
+
taskEvents = resp.taskEvents;
|
|
17880
|
+
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
17881
|
+
subChatEvents = resp.subChatEvents;
|
|
17882
|
+
if (resp.status === "waiting_for_user") {
|
|
17883
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
17884
|
+
return {
|
|
17885
|
+
status: resp.status,
|
|
17886
|
+
chatId,
|
|
17887
|
+
messageId: assistantMessageId,
|
|
17888
|
+
assistant,
|
|
17889
|
+
category,
|
|
17890
|
+
modelName,
|
|
17891
|
+
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
17892
|
+
followUpSuggestions,
|
|
17893
|
+
taskProposals,
|
|
17894
|
+
taskUpdateProposals,
|
|
17895
|
+
taskEvents,
|
|
17896
|
+
pendingTaskUpdateJobs,
|
|
17897
|
+
subChatEvents,
|
|
17898
|
+
appSettingsMemoryRequests,
|
|
17899
|
+
tokenUsage,
|
|
17900
|
+
promptBudget
|
|
17901
|
+
};
|
|
17884
17902
|
}
|
|
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 {
|
|
17903
|
+
} finally {
|
|
17904
|
+
ws.close();
|
|
17905
|
+
}
|
|
17906
|
+
} else {
|
|
17907
|
+
try {
|
|
17908
|
+
const recoverCurrentTurnOutputs = async () => {
|
|
17909
|
+
if (recoveryDiscovery) {
|
|
17906
17910
|
try {
|
|
17907
|
-
await
|
|
17908
|
-
protocol_version: 1,
|
|
17909
|
-
job_id: recoveryJobId,
|
|
17910
|
-
request_id: requestId
|
|
17911
|
-
});
|
|
17911
|
+
await recoveryDiscovery;
|
|
17912
17912
|
} catch (error) {
|
|
17913
|
-
|
|
17914
|
-
|
|
17915
|
-
throw error;
|
|
17913
|
+
if (!useDeferredContext) throw error;
|
|
17914
|
+
return;
|
|
17916
17915
|
}
|
|
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
17916
|
}
|
|
17917
|
+
const pages = ws.drainAvailableRecoveryOutputPages();
|
|
17918
|
+
if (pages.length === 0) return;
|
|
17919
|
+
if (!ownerId2) throw new Error("Saved chat recovery output requires the owner identity.");
|
|
17920
|
+
if (pages.some((page) => page.some((output) => output.root_chat_id === chatId || output.target_chat_id === chatId)) && (!chatKeyBytes || !encryptedChatKey)) {
|
|
17921
|
+
throw new Error("Saved chat recovery output requires the root chat key.");
|
|
17922
|
+
}
|
|
17923
|
+
const cache = loadSyncCache(teamId) ?? {
|
|
17924
|
+
syncedAt: Date.now(),
|
|
17925
|
+
totalChatCount: 0,
|
|
17926
|
+
loadedChatCount: 0,
|
|
17927
|
+
chats: [],
|
|
17928
|
+
embeds: [],
|
|
17929
|
+
embedKeys: [],
|
|
17930
|
+
chatKeyWrappers: []
|
|
17931
|
+
};
|
|
17932
|
+
if (encryptedChatKey) {
|
|
17933
|
+
const root = cache.chats.find((chat) => String(chat.details.id ?? "") === chatId);
|
|
17934
|
+
if (root) root.details.encrypted_chat_key = encryptedChatKey;
|
|
17935
|
+
else cache.chats.push({ details: { id: chatId, encrypted_chat_key: encryptedChatKey }, messages: [] });
|
|
17936
|
+
}
|
|
17937
|
+
await this.replayCurrentTurnRecoveryOutputPages(ws, ownerId2, pages, cache, teamId, chatId);
|
|
17938
|
+
};
|
|
17939
|
+
const responsePromise = precollectedResponse ?? ws.collectAiResponse(messageId2, chatId, {
|
|
17940
|
+
...streamOpts,
|
|
17941
|
+
timeoutMs: params.responseTimeoutMs,
|
|
17942
|
+
recoveryTurnId: savedTurnId
|
|
17943
|
+
});
|
|
17944
|
+
const outcome = await Promise.race([
|
|
17945
|
+
responsePromise.then((response) => ({ kind: "response", response })),
|
|
17946
|
+
...contextAckRejection ? [contextAckRejection] : [],
|
|
17947
|
+
...queuedAdmission ? [queuedAdmission.then(
|
|
17948
|
+
(frame) => ({ kind: "queued", frame }),
|
|
17949
|
+
() => new Promise(() => {
|
|
17950
|
+
})
|
|
17951
|
+
)] : [],
|
|
17952
|
+
...pausedHandoff ? [pausedHandoff.then(
|
|
17953
|
+
(frame) => ({ kind: "paused", frame }),
|
|
17954
|
+
() => new Promise(() => {
|
|
17955
|
+
})
|
|
17956
|
+
)] : []
|
|
17957
|
+
]);
|
|
17958
|
+
if (outcome.kind === "paused") {
|
|
17959
|
+
void responsePromise.catch(() => {
|
|
17960
|
+
});
|
|
17961
|
+
throw new Error("Message saved, but AI handoff is paused. No assistant run started; check the chat before retrying.");
|
|
17932
17962
|
}
|
|
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
|
|
17963
|
+
if (outcome.kind === "queued") {
|
|
17964
|
+
void responsePromise.catch(() => {
|
|
17951
17965
|
});
|
|
17952
|
-
|
|
17953
|
-
|
|
17966
|
+
const frame = outcome.frame.payload;
|
|
17967
|
+
return {
|
|
17968
|
+
status: "queued",
|
|
17969
|
+
chatId,
|
|
17970
|
+
messageId: null,
|
|
17971
|
+
userMessageId: messageId2,
|
|
17972
|
+
queueMode: frame.queue_mode === "steer" ? "steer" : "follow_up",
|
|
17973
|
+
queueRevision: typeof frame.queue_revision === "number" ? frame.queue_revision : 1,
|
|
17974
|
+
assistant: "",
|
|
17975
|
+
category: null,
|
|
17976
|
+
modelName: null,
|
|
17977
|
+
mateName: null,
|
|
17978
|
+
followUpSuggestions: [],
|
|
17979
|
+
taskProposals: [],
|
|
17980
|
+
taskUpdateProposals: [],
|
|
17981
|
+
taskEvents: [],
|
|
17982
|
+
pendingTaskUpdateJobs: [],
|
|
17983
|
+
subChatEvents: [],
|
|
17984
|
+
appSettingsMemoryRequests: [],
|
|
17985
|
+
tokenUsage: null,
|
|
17986
|
+
promptBudget: null
|
|
17987
|
+
};
|
|
17988
|
+
}
|
|
17989
|
+
const resp = outcome.response;
|
|
17990
|
+
assistantMessageId = resp.messageId;
|
|
17991
|
+
assistant = resp.content;
|
|
17992
|
+
category = resp.category;
|
|
17993
|
+
modelName = resp.modelName;
|
|
17994
|
+
tokenUsage = resp.tokenUsage;
|
|
17995
|
+
promptBudget = resp.promptBudget;
|
|
17996
|
+
followUpSuggestions = resp.followUpSuggestions;
|
|
17997
|
+
suggestedFollowupMessage = resp.suggestedFollowupMessage;
|
|
17998
|
+
taskProposals = resp.taskProposals;
|
|
17999
|
+
taskUpdateProposals = resp.taskUpdateProposals;
|
|
18000
|
+
taskEvents = resp.taskEvents;
|
|
18001
|
+
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
18002
|
+
subChatEvents = resp.subChatEvents;
|
|
18003
|
+
if (resp.status === "waiting_for_user") {
|
|
17954
18004
|
await persistTaskEventSystemMessages(taskEvents);
|
|
17955
18005
|
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
18006
|
return {
|
|
17962
|
-
status:
|
|
18007
|
+
status: resp.status,
|
|
17963
18008
|
chatId,
|
|
17964
18009
|
messageId: assistantMessageId,
|
|
17965
18010
|
assistant,
|
|
17966
18011
|
category,
|
|
17967
18012
|
modelName,
|
|
17968
|
-
mateName:
|
|
18013
|
+
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
17969
18014
|
followUpSuggestions,
|
|
17970
18015
|
taskProposals,
|
|
17971
18016
|
taskUpdateProposals,
|
|
@@ -17977,234 +18022,338 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
17977
18022
|
promptBudget
|
|
17978
18023
|
};
|
|
17979
18024
|
}
|
|
17980
|
-
if (
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
17984
|
-
|
|
17985
|
-
|
|
17986
|
-
|
|
17987
|
-
|
|
17988
|
-
|
|
17989
|
-
|
|
17990
|
-
|
|
17991
|
-
|
|
17992
|
-
|
|
17993
|
-
|
|
17994
|
-
|
|
17995
|
-
|
|
17996
|
-
|
|
17997
|
-
|
|
17998
|
-
|
|
18025
|
+
if (chatKeyBytes) {
|
|
18026
|
+
if (!resp.recoveryJobId || !savedTurnId || !ownerId2) {
|
|
18027
|
+
throw new Error("Saved chat completion did not include recoverable terminal identity.");
|
|
18028
|
+
}
|
|
18029
|
+
const recoveryJobId = resp.recoveryJobId;
|
|
18030
|
+
let claimRetryDeadline = null;
|
|
18031
|
+
let claim;
|
|
18032
|
+
let claimRetryDelayMs = 500;
|
|
18033
|
+
while (true) {
|
|
18034
|
+
const requestId = randomUUID9();
|
|
18035
|
+
let matchingLeaseConflict = false;
|
|
18036
|
+
const stopWatchingErrors = ws.onMessageType("error", (payload) => {
|
|
18037
|
+
if (payload?.code === "lease_conflict" && payload.job_id === recoveryJobId && payload.request_id === requestId) {
|
|
18038
|
+
matchingLeaseConflict = true;
|
|
18039
|
+
}
|
|
18040
|
+
});
|
|
18041
|
+
const claimPromise = ws.waitForMessage(
|
|
18042
|
+
"recovery_job_claimed",
|
|
18043
|
+
(payload) => {
|
|
18044
|
+
const received = payload;
|
|
18045
|
+
return received.job_id === recoveryJobId && (received.request_id === void 0 || received.request_id === requestId);
|
|
18046
|
+
},
|
|
18047
|
+
claimRetryDeadline === null ? 2e4 : Math.min(2e4, Math.max(1, claimRetryDeadline - Date.now()))
|
|
18048
|
+
);
|
|
18049
|
+
try {
|
|
18050
|
+
try {
|
|
18051
|
+
await ws.sendAsync("recovery_job_claim", {
|
|
18052
|
+
protocol_version: 1,
|
|
18053
|
+
job_id: recoveryJobId,
|
|
18054
|
+
request_id: requestId
|
|
18055
|
+
});
|
|
18056
|
+
} catch (error) {
|
|
18057
|
+
void claimPromise.catch(() => {
|
|
18058
|
+
});
|
|
18059
|
+
throw error;
|
|
18060
|
+
}
|
|
18061
|
+
claim = (await claimPromise).payload;
|
|
18062
|
+
break;
|
|
18063
|
+
} catch (error) {
|
|
18064
|
+
if (!(error instanceof WebSocketProtocolError) || error.code !== "lease_conflict" || !matchingLeaseConflict) throw error;
|
|
18065
|
+
claimRetryDeadline ??= Date.now() + Math.min(
|
|
18066
|
+
1e4,
|
|
18067
|
+
Math.max(0, params.responseTimeoutMs ?? 9e4)
|
|
18068
|
+
);
|
|
18069
|
+
const remainingMs = claimRetryDeadline - Date.now();
|
|
18070
|
+
if (remainingMs <= claimRetryDelayMs) throw error;
|
|
18071
|
+
await new Promise((resolve20) => setTimeout(resolve20, claimRetryDelayMs));
|
|
18072
|
+
claimRetryDelayMs = Math.min(claimRetryDelayMs * 2, 2e3);
|
|
18073
|
+
} finally {
|
|
18074
|
+
stopWatchingErrors();
|
|
18075
|
+
}
|
|
18076
|
+
}
|
|
18077
|
+
const leaseToken = typeof claim.lease_token === "string" ? claim.lease_token : null;
|
|
18078
|
+
const leaseGeneration = typeof claim.lease_generation === "number" && Number.isSafeInteger(claim.lease_generation) ? claim.lease_generation : null;
|
|
18079
|
+
const assistantId = typeof claim.assistant_message_id === "string" ? claim.assistant_message_id : null;
|
|
18080
|
+
const keyVersion = typeof claim.chat_key_version === "number" && Number.isSafeInteger(claim.chat_key_version) ? claim.chat_key_version : null;
|
|
18081
|
+
const claimMatchesExpectedTerminal = assistantId && keyVersion === 1 && claim.chat_id === chatId && claim.turn_id === savedTurnId;
|
|
18082
|
+
if (claim.state === "TERMINAL" && claimMatchesExpectedTerminal) {
|
|
18083
|
+
assistant = resp.content;
|
|
18084
|
+
category = resp.category;
|
|
18085
|
+
modelName = resp.modelName;
|
|
18086
|
+
assistantMessageId = assistantId;
|
|
18087
|
+
await this.persistStreamedEmbeds({
|
|
18088
|
+
ws,
|
|
18089
|
+
embeds: resp.embeds,
|
|
18090
|
+
chatId,
|
|
18091
|
+
chatKeyBytes,
|
|
18092
|
+
fallbackMessageId: assistantId,
|
|
18093
|
+
ownerId: ownerId2,
|
|
18094
|
+
teamId
|
|
18095
|
+
});
|
|
18096
|
+
clearSyncCache(teamId);
|
|
18097
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
18098
|
+
await persistTaskEventSystemMessages(taskEvents);
|
|
18099
|
+
await recoverCurrentTurnOutputs();
|
|
18100
|
+
await projectFocusCountdown?.flush();
|
|
18101
|
+
await focusPersistence;
|
|
18102
|
+
await chatContextEvents?.flush();
|
|
18103
|
+
if (focusPersistenceError) throw focusPersistenceError;
|
|
18104
|
+
const mateName2 = category ? MATE_NAMES[category] ?? null : null;
|
|
18105
|
+
return {
|
|
18106
|
+
status: "completed",
|
|
18107
|
+
chatId,
|
|
18108
|
+
messageId: assistantMessageId,
|
|
18109
|
+
assistant,
|
|
18110
|
+
category,
|
|
18111
|
+
modelName,
|
|
18112
|
+
mateName: mateName2,
|
|
18113
|
+
followUpSuggestions,
|
|
18114
|
+
taskProposals,
|
|
18115
|
+
taskUpdateProposals,
|
|
18116
|
+
taskEvents,
|
|
18117
|
+
pendingTaskUpdateJobs,
|
|
18118
|
+
subChatEvents,
|
|
18119
|
+
appSettingsMemoryRequests,
|
|
18120
|
+
tokenUsage,
|
|
18121
|
+
promptBudget
|
|
18122
|
+
};
|
|
18123
|
+
}
|
|
18124
|
+
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") {
|
|
18125
|
+
throw new Error("Recovery job claim returned invalid lease or identity data.");
|
|
18126
|
+
}
|
|
18127
|
+
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
18128
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
17999
18129
|
chatId,
|
|
18000
|
-
turnId: savedTurnId,
|
|
18001
|
-
jobId: recoveryJobId,
|
|
18002
|
-
assistantMessageId: assistantId,
|
|
18003
18130
|
keyVersion
|
|
18131
|
+
);
|
|
18132
|
+
let envelope;
|
|
18133
|
+
try {
|
|
18134
|
+
envelope = JSON.parse(claim.sealed_payload);
|
|
18135
|
+
} catch {
|
|
18136
|
+
throw new Error("Recovery job contained an invalid sealed envelope.");
|
|
18004
18137
|
}
|
|
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
|
|
18138
|
+
const plaintext = await openChatCompletionRecoveryEnvelope(
|
|
18139
|
+
envelope,
|
|
18140
|
+
{
|
|
18141
|
+
recoveryPrivateKey: recoveryKeypair.privateKey,
|
|
18142
|
+
ownerId: ownerId2,
|
|
18143
|
+
chatId,
|
|
18144
|
+
turnId: savedTurnId,
|
|
18145
|
+
jobId: recoveryJobId,
|
|
18146
|
+
assistantMessageId: assistantId,
|
|
18147
|
+
keyVersion
|
|
18148
|
+
}
|
|
18149
|
+
);
|
|
18150
|
+
let recovered;
|
|
18151
|
+
try {
|
|
18152
|
+
recovered = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(plaintext));
|
|
18153
|
+
} catch {
|
|
18154
|
+
throw new Error("Recovery job plaintext was not valid UTF-8 JSON.");
|
|
18059
18155
|
}
|
|
18060
|
-
|
|
18061
|
-
|
|
18062
|
-
|
|
18063
|
-
"
|
|
18064
|
-
|
|
18065
|
-
|
|
18156
|
+
const expectedRecoveryFields = [
|
|
18157
|
+
"assistant_message_id",
|
|
18158
|
+
"category",
|
|
18159
|
+
"chat_id",
|
|
18160
|
+
"content",
|
|
18161
|
+
"job_id",
|
|
18162
|
+
"key_version",
|
|
18163
|
+
"model_name",
|
|
18164
|
+
"turn_id"
|
|
18165
|
+
];
|
|
18166
|
+
if (Object.keys(recovered).filter((field3) => ![
|
|
18167
|
+
"thinking_content",
|
|
18168
|
+
"thinking_signature",
|
|
18169
|
+
"has_thinking",
|
|
18170
|
+
"thinking_token_count",
|
|
18171
|
+
"thinking_duration_seconds"
|
|
18172
|
+
].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") {
|
|
18173
|
+
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
18174
|
+
}
|
|
18175
|
+
assistant = recovered.content;
|
|
18176
|
+
category = recovered.category;
|
|
18177
|
+
modelName = recovered.model_name;
|
|
18178
|
+
const completedAt = resp.recoveryCreatedAt ?? Math.floor(Date.now() / 1e3);
|
|
18179
|
+
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
18180
|
+
assistant,
|
|
18181
|
+
chatKeyBytes
|
|
18066
18182
|
);
|
|
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
|
-
}
|
|
18183
|
+
const encryptedSenderName = await encryptWithAesGcmCombined(category ? MATE_NAMES[category] ?? "Assistant" : "Assistant", chatKeyBytes);
|
|
18184
|
+
const encryptedCategory = recovered.category ? await encryptWithAesGcmCombined(recovered.category, chatKeyBytes) : void 0;
|
|
18185
|
+
const encryptedModelName = recovered.model_name ? await encryptWithAesGcmCombined(recovered.model_name, chatKeyBytes) : void 0;
|
|
18186
|
+
const terminalPersistPayload = {
|
|
18187
|
+
protocol_version: 1,
|
|
18188
|
+
job_id: recoveryJobId,
|
|
18189
|
+
lease_token: leaseToken,
|
|
18190
|
+
lease_generation: leaseGeneration,
|
|
18191
|
+
encrypted_assistant_message: {
|
|
18192
|
+
client_message_id: assistantId,
|
|
18193
|
+
chat_id: chatId,
|
|
18194
|
+
encrypted_content: encryptedAssistantContent,
|
|
18195
|
+
...await this.encryptThinkingFields(recovered, chatKeyBytes),
|
|
18196
|
+
encrypted_sender_name: encryptedSenderName,
|
|
18197
|
+
encrypted_category: encryptedCategory,
|
|
18198
|
+
encrypted_model_name: encryptedModelName,
|
|
18199
|
+
role: "assistant",
|
|
18200
|
+
user_message_id: messageId2,
|
|
18201
|
+
created_at: completedAt,
|
|
18202
|
+
updated_at: completedAt
|
|
18098
18203
|
}
|
|
18099
|
-
|
|
18100
|
-
|
|
18204
|
+
};
|
|
18205
|
+
const persistTerminalRecovery = async (expectedMessagesV) => {
|
|
18206
|
+
const persistedPromise = ws.waitForMessage(
|
|
18207
|
+
"recovery_job_persisted",
|
|
18208
|
+
(payload) => payload.job_id === recoveryJobId,
|
|
18209
|
+
2e4
|
|
18210
|
+
);
|
|
18211
|
+
await ws.sendAsync("recovery_job_persist", {
|
|
18212
|
+
...terminalPersistPayload,
|
|
18213
|
+
expected_messages_v: expectedMessagesV
|
|
18214
|
+
});
|
|
18215
|
+
await persistedPromise;
|
|
18216
|
+
};
|
|
18217
|
+
let terminalPersisted = false;
|
|
18218
|
+
try {
|
|
18219
|
+
await persistTerminalRecovery(terminalExpectedMessagesV);
|
|
18220
|
+
terminalPersisted = true;
|
|
18221
|
+
} catch (error) {
|
|
18222
|
+
if (error instanceof WebSocketProtocolError) {
|
|
18223
|
+
if (error.code === "version_conflict") {
|
|
18224
|
+
const latestCache = await this.ensureSynced(
|
|
18225
|
+
true,
|
|
18226
|
+
[chatId],
|
|
18227
|
+
{ teamId },
|
|
18228
|
+
false,
|
|
18229
|
+
void 0,
|
|
18230
|
+
void 0,
|
|
18231
|
+
{ skipTypedRecoveryOutputReplay: true }
|
|
18232
|
+
);
|
|
18233
|
+
const latestChat = latestCache.chats.find(
|
|
18234
|
+
(chat) => String(chat.details.id ?? "") === chatId
|
|
18235
|
+
);
|
|
18236
|
+
const latestMessagesV = typeof latestChat?.details.messages_v === "number" && Number.isSafeInteger(latestChat.details.messages_v) ? latestChat.details.messages_v : null;
|
|
18237
|
+
if (latestMessagesV !== null && latestMessagesV > terminalExpectedMessagesV) {
|
|
18238
|
+
await persistTerminalRecovery(latestMessagesV);
|
|
18239
|
+
terminalExpectedMessagesV = latestMessagesV;
|
|
18240
|
+
terminalPersisted = true;
|
|
18241
|
+
}
|
|
18242
|
+
}
|
|
18243
|
+
if (!terminalPersisted) {
|
|
18244
|
+
throw new Error(`${error.message} (${error.code})`);
|
|
18245
|
+
}
|
|
18246
|
+
} else {
|
|
18247
|
+
throw error;
|
|
18101
18248
|
}
|
|
18102
|
-
} else {
|
|
18103
|
-
throw error;
|
|
18104
18249
|
}
|
|
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({
|
|
18250
|
+
if (!terminalPersisted) {
|
|
18251
|
+
throw new Error("Encrypted completion recovery did not persist terminal state.");
|
|
18252
|
+
}
|
|
18253
|
+
assistantMessageId = assistantId;
|
|
18254
|
+
await this.persistStreamedEmbeds({
|
|
18123
18255
|
ws,
|
|
18124
|
-
|
|
18125
|
-
|
|
18126
|
-
|
|
18127
|
-
|
|
18128
|
-
|
|
18129
|
-
|
|
18130
|
-
requireActiveTurnEvent: true
|
|
18256
|
+
embeds: resp.embeds,
|
|
18257
|
+
chatId,
|
|
18258
|
+
chatKeyBytes,
|
|
18259
|
+
fallbackMessageId: assistantId,
|
|
18260
|
+
ownerId: ownerId2,
|
|
18261
|
+
teamId
|
|
18131
18262
|
});
|
|
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 {
|
|
18263
|
+
if (initialHeaderWork) persistOptionalHeader = () => initialHeaderWork;
|
|
18264
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
18265
|
+
if (taskUpdateJobsEnabled) {
|
|
18266
|
+
const persistedTaskJobIds = await this.persistPendingTaskUpdateJobs({
|
|
18267
|
+
ws,
|
|
18268
|
+
jobs: pendingTaskUpdateJobs,
|
|
18269
|
+
events: taskEvents,
|
|
18270
|
+
teamId,
|
|
18271
|
+
activeChatId: chatId,
|
|
18272
|
+
activeChatKeyBytes: chatKeyBytes,
|
|
18273
|
+
fallbackUserMessageId: messageId2,
|
|
18274
|
+
requireActiveTurnEvent: true
|
|
18275
|
+
});
|
|
18276
|
+
pendingTaskUpdateJobs = pendingTaskUpdateJobs.filter((job) => !persistedTaskJobIds.has(job.job_id));
|
|
18277
|
+
}
|
|
18278
|
+
await persistTaskEventSystemMessages(taskEvents);
|
|
18279
|
+
await recoverCurrentTurnOutputs();
|
|
18280
|
+
clearSyncCache(teamId);
|
|
18281
|
+
if (optionalMetadata) {
|
|
18282
|
+
metadataHandoffReady = true;
|
|
18283
|
+
metadataHandoffSuggestion = resp.suggestedFollowupMessage;
|
|
18156
18284
|
}
|
|
18157
18285
|
}
|
|
18158
|
-
await chatContextEvents?.flush();
|
|
18159
18286
|
} finally {
|
|
18160
|
-
|
|
18161
|
-
|
|
18162
|
-
|
|
18287
|
+
try {
|
|
18288
|
+
await projectFocusCountdown?.flush();
|
|
18289
|
+
await focusPersistence;
|
|
18290
|
+
if (projectAuthoringAvailable && assistant && chatContextEvents) {
|
|
18291
|
+
try {
|
|
18292
|
+
const history = boundedAuthoringHistory([
|
|
18293
|
+
...messageHistoryForRequest ?? [],
|
|
18294
|
+
{ role: "user", content: finalMessage },
|
|
18295
|
+
{ role: "assistant", content: assistant }
|
|
18296
|
+
]);
|
|
18297
|
+
const recommendations = await assessCliProjectAuthoring(this, projectAuthoringAvailable, history);
|
|
18298
|
+
for (const event of recommendations) chatContextEvents.persist(event);
|
|
18299
|
+
} catch {
|
|
18300
|
+
}
|
|
18301
|
+
}
|
|
18302
|
+
await chatContextEvents?.flush();
|
|
18303
|
+
} finally {
|
|
18304
|
+
projectFocusCountdown?.();
|
|
18305
|
+
chatContextEvents?.stop();
|
|
18306
|
+
if (!metadataHandoffReady) ws.close();
|
|
18307
|
+
}
|
|
18308
|
+
}
|
|
18309
|
+
if (focusPersistenceError) throw focusPersistenceError;
|
|
18310
|
+
if (metadataHandoffReady && optionalMetadata) {
|
|
18311
|
+
metadataTransportOwned = optionalMetadata.handOff(metadataHandoffSuggestion, persistOptionalHeader);
|
|
18312
|
+
if (!metadataTransportOwned) ws.close();
|
|
18163
18313
|
}
|
|
18164
18314
|
}
|
|
18165
|
-
|
|
18166
|
-
if (
|
|
18167
|
-
|
|
18168
|
-
|
|
18315
|
+
const mateName = category ? MATE_NAMES[category] ?? null : null;
|
|
18316
|
+
if (params.interactiveHuman && shouldWaitForAi && !params.incognito) this.rearmSendSocketWhenClosed(ws, teamId);
|
|
18317
|
+
return {
|
|
18318
|
+
status: "completed",
|
|
18319
|
+
chatId,
|
|
18320
|
+
messageId: assistantMessageId,
|
|
18321
|
+
userMessageId: messageId2,
|
|
18322
|
+
assistant,
|
|
18323
|
+
category,
|
|
18324
|
+
modelName,
|
|
18325
|
+
mateName,
|
|
18326
|
+
followUpSuggestions,
|
|
18327
|
+
suggestedFollowupMessage,
|
|
18328
|
+
taskProposals,
|
|
18329
|
+
taskUpdateProposals,
|
|
18330
|
+
taskEvents,
|
|
18331
|
+
pendingTaskUpdateJobs,
|
|
18332
|
+
subChatEvents,
|
|
18333
|
+
appSettingsMemoryRequests,
|
|
18334
|
+
tokenUsage,
|
|
18335
|
+
promptBudget
|
|
18336
|
+
};
|
|
18337
|
+
} finally {
|
|
18338
|
+
removeContextAckWatcher?.();
|
|
18339
|
+
earlyContextAbort.abort();
|
|
18340
|
+
if (!metadataTransportOwned) {
|
|
18341
|
+
initialHeaderStopped = true;
|
|
18342
|
+
optionalMetadata?.stop();
|
|
18343
|
+
ws.close();
|
|
18169
18344
|
}
|
|
18345
|
+
if (earlyContext) await earlyContext;
|
|
18170
18346
|
}
|
|
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;
|
|
18347
|
+
} catch (error) {
|
|
18348
|
+
memorySocket?.ws.close();
|
|
18349
|
+
throw error;
|
|
18201
18350
|
}
|
|
18202
|
-
}
|
|
18203
|
-
|
|
18204
|
-
throw error;
|
|
18351
|
+
} finally {
|
|
18352
|
+
historyAbort.abort();
|
|
18205
18353
|
}
|
|
18206
18354
|
} finally {
|
|
18207
|
-
|
|
18355
|
+
this.foregroundSendCount--;
|
|
18356
|
+
this.maybeRearmSendSocket();
|
|
18208
18357
|
}
|
|
18209
18358
|
}
|
|
18210
18359
|
async persistEncryptedSystemMessage(ws, systemMessage, targetChatId) {
|
|
@@ -18439,6 +18588,30 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18439
18588
|
return handledJobIds;
|
|
18440
18589
|
}
|
|
18441
18590
|
async persistStreamedEmbeds(params) {
|
|
18591
|
+
const assertOwner = this.captureRecoveryOwner();
|
|
18592
|
+
const ws = {
|
|
18593
|
+
waitForMessage: (...args) => {
|
|
18594
|
+
assertOwner();
|
|
18595
|
+
const result = params.ws.waitForMessage(...args).then((value) => {
|
|
18596
|
+
assertOwner();
|
|
18597
|
+
return value;
|
|
18598
|
+
});
|
|
18599
|
+
void result.catch(() => {
|
|
18600
|
+
});
|
|
18601
|
+
return result;
|
|
18602
|
+
},
|
|
18603
|
+
sendAsync: async (...args) => {
|
|
18604
|
+
assertOwner();
|
|
18605
|
+
await params.ws.sendAsync(...args);
|
|
18606
|
+
assertOwner();
|
|
18607
|
+
}
|
|
18608
|
+
};
|
|
18609
|
+
const http = { get: async (path2, headers) => {
|
|
18610
|
+
assertOwner();
|
|
18611
|
+
const result = await this.http.get(path2, headers);
|
|
18612
|
+
assertOwner();
|
|
18613
|
+
return result;
|
|
18614
|
+
} };
|
|
18442
18615
|
const finalized = new Map(
|
|
18443
18616
|
params.embeds.filter((embed) => {
|
|
18444
18617
|
const status = embed.status ?? "finished";
|
|
@@ -18449,7 +18622,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18449
18622
|
for (const parent of finalized.values()) {
|
|
18450
18623
|
for (const childId of parent.embed_ids ?? []) {
|
|
18451
18624
|
if (finalized.has(childId)) continue;
|
|
18452
|
-
const childResponse =
|
|
18625
|
+
const childResponse = ws.waitForMessage(
|
|
18453
18626
|
"send_embed_data",
|
|
18454
18627
|
(payload) => {
|
|
18455
18628
|
const frame = payload;
|
|
@@ -18457,7 +18630,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18457
18630
|
return nested.embed_id === childId;
|
|
18458
18631
|
}
|
|
18459
18632
|
);
|
|
18460
|
-
|
|
18633
|
+
assertOwner();
|
|
18634
|
+
await ws.sendAsync("request_embed", { embed_id: childId });
|
|
18461
18635
|
const envelope = (await childResponse).payload;
|
|
18462
18636
|
const child = envelope.payload ?? envelope;
|
|
18463
18637
|
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 +18645,6 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18471
18645
|
}
|
|
18472
18646
|
const masterKey = this.getMasterKeyBytes();
|
|
18473
18647
|
const parentKeys = /* @__PURE__ */ new Map();
|
|
18474
|
-
const processed = /* @__PURE__ */ new Set();
|
|
18475
18648
|
const cachedEmbeds = loadSyncCache();
|
|
18476
18649
|
const resolveCachedParentKey = async (parentId) => {
|
|
18477
18650
|
if (!cachedEmbeds) return void 0;
|
|
@@ -18489,6 +18662,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18489
18662
|
) ?? void 0;
|
|
18490
18663
|
};
|
|
18491
18664
|
const persistOne = async (embed, embedKey, isChild) => {
|
|
18665
|
+
assertOwner();
|
|
18492
18666
|
const now = Math.floor(Date.now() / 1e3);
|
|
18493
18667
|
const createdAt = normalizeUnixSeconds(embed.createdAt, now);
|
|
18494
18668
|
const updatedAt = normalizeUnixSeconds(embed.updatedAt, now);
|
|
@@ -18528,14 +18702,15 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18528
18702
|
}
|
|
18529
18703
|
] : [];
|
|
18530
18704
|
const storeRequestId = randomUUID9();
|
|
18531
|
-
const storeConfirmed =
|
|
18705
|
+
const storeConfirmed = ws.waitForMessage(
|
|
18532
18706
|
"store_embed_confirmed",
|
|
18533
18707
|
(payload) => matchesRecoveryEmbedReceipt(payload, storeRequestId, embed.embed_id),
|
|
18534
18708
|
3e4
|
|
18535
18709
|
);
|
|
18536
18710
|
void storeConfirmed.catch(() => {
|
|
18537
18711
|
});
|
|
18538
|
-
|
|
18712
|
+
assertOwner();
|
|
18713
|
+
await ws.sendAsync("store_embed", {
|
|
18539
18714
|
request_id: storeRequestId,
|
|
18540
18715
|
embed_id: embed.embed_id,
|
|
18541
18716
|
encrypted_type: encryptedType,
|
|
@@ -18566,10 +18741,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18566
18741
|
let storedHead = true;
|
|
18567
18742
|
try {
|
|
18568
18743
|
await storeConfirmed;
|
|
18744
|
+
assertOwner();
|
|
18569
18745
|
} catch (error) {
|
|
18570
18746
|
if (!(error instanceof WebSocketProtocolError) || error.code !== "embed_write_denied") throw error;
|
|
18571
18747
|
const query = params.teamId ? `?team_id=${encodeURIComponent(params.teamId)}` : "";
|
|
18572
|
-
const canonical = await
|
|
18748
|
+
const canonical = await http.get(
|
|
18573
18749
|
`/v1/embeds/chats/${encodeURIComponent(params.chatId)}/embeds/${encodeURIComponent(embed.embed_id)}${query}`,
|
|
18574
18750
|
this.getCliRequestHeaders()
|
|
18575
18751
|
);
|
|
@@ -18634,14 +18810,15 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18634
18810
|
const expectedEncryptedType = head.encrypted_type;
|
|
18635
18811
|
const expectedDigest = computeSHA256(expectedCiphertext);
|
|
18636
18812
|
const requestId = randomUUID9();
|
|
18637
|
-
const confirmed =
|
|
18813
|
+
const confirmed = ws.waitForMessage(
|
|
18638
18814
|
"store_embed_confirmed",
|
|
18639
18815
|
(payload) => matchesRecoveryEmbedReceipt(payload, requestId, embed.embed_id),
|
|
18640
18816
|
3e4
|
|
18641
18817
|
);
|
|
18642
18818
|
void confirmed.catch(() => {
|
|
18643
18819
|
});
|
|
18644
|
-
|
|
18820
|
+
assertOwner();
|
|
18821
|
+
await ws.sendAsync("store_embed", {
|
|
18645
18822
|
request_id: requestId,
|
|
18646
18823
|
embed_id: embed.embed_id,
|
|
18647
18824
|
chat_id: params.chatId,
|
|
@@ -18661,7 +18838,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18661
18838
|
if (receipt.canonical_source !== "head" || receipt.canonical_digest !== expectedDigest) {
|
|
18662
18839
|
throw new Error(`Embed ${embed.embed_id} metadata enrichment receipt does not match canonical head.`);
|
|
18663
18840
|
}
|
|
18664
|
-
const enriched = await
|
|
18841
|
+
const enriched = await http.get(
|
|
18665
18842
|
`/v1/embeds/chats/${encodeURIComponent(params.chatId)}/embeds/${encodeURIComponent(embed.embed_id)}${query}`,
|
|
18666
18843
|
this.getCliRequestHeaders()
|
|
18667
18844
|
);
|
|
@@ -18674,7 +18851,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18674
18851
|
}
|
|
18675
18852
|
if (storedHead && keys2.length > 0) {
|
|
18676
18853
|
const keysRequestId = randomUUID9();
|
|
18677
|
-
const keysConfirmed =
|
|
18854
|
+
const keysConfirmed = ws.waitForMessage(
|
|
18678
18855
|
"store_embed_keys_confirmed",
|
|
18679
18856
|
(payload) => {
|
|
18680
18857
|
const p = payload;
|
|
@@ -18682,8 +18859,10 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18682
18859
|
},
|
|
18683
18860
|
3e4
|
|
18684
18861
|
);
|
|
18685
|
-
|
|
18862
|
+
assertOwner();
|
|
18863
|
+
await ws.sendAsync("store_embed_keys", { request_id: keysRequestId, keys: keys2 });
|
|
18686
18864
|
const keysReceipt = (await keysConfirmed).payload;
|
|
18865
|
+
assertOwner();
|
|
18687
18866
|
if (keysReceipt.failed_count !== 0 || keysReceipt.created_count !== keys2.length) {
|
|
18688
18867
|
throw new Error(`Embed ${embed.embed_id} key wrappers were not durably stored.`);
|
|
18689
18868
|
}
|
|
@@ -18698,7 +18877,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18698
18877
|
if (row3.version_number === 1) {
|
|
18699
18878
|
const query = new URLSearchParams({ capability: "bounded-v1", chat_id: params.chatId });
|
|
18700
18879
|
if (params.teamId) query.set("team_id", params.teamId);
|
|
18701
|
-
const existing = await
|
|
18880
|
+
const existing = await http.get(
|
|
18702
18881
|
`/v1/embeds/${encodeURIComponent(embed.embed_id)}/versions/1?${query}`,
|
|
18703
18882
|
this.getCliRequestHeaders()
|
|
18704
18883
|
);
|
|
@@ -18721,7 +18900,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18721
18900
|
const encryptedPatch = typeof row3.patch === "string" ? await encryptWithAesGcmCombined(row3.patch, embedKey) : void 0;
|
|
18722
18901
|
if (!encryptedSnapshot && !encryptedPatch) continue;
|
|
18723
18902
|
const diffRequestId = randomUUID9();
|
|
18724
|
-
const diffConfirmed =
|
|
18903
|
+
const diffConfirmed = ws.waitForMessage(
|
|
18725
18904
|
"store_embed_diff_confirmed",
|
|
18726
18905
|
(payload) => {
|
|
18727
18906
|
const p = payload;
|
|
@@ -18731,7 +18910,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18731
18910
|
);
|
|
18732
18911
|
void diffConfirmed.catch(() => {
|
|
18733
18912
|
});
|
|
18734
|
-
|
|
18913
|
+
assertOwner();
|
|
18914
|
+
await ws.sendAsync("store_embed_diff", {
|
|
18735
18915
|
request_id: diffRequestId,
|
|
18736
18916
|
embed_id: row3.embed_id,
|
|
18737
18917
|
version_number: row3.version_number,
|
|
@@ -18741,6 +18921,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18741
18921
|
created_at: normalizeUnixSeconds(row3.created_at, now)
|
|
18742
18922
|
});
|
|
18743
18923
|
const diffReceipt = (await diffConfirmed).payload;
|
|
18924
|
+
assertOwner();
|
|
18744
18925
|
const expectedDigest = computeSHA256(JSON.stringify([encryptedSnapshot ?? null, encryptedPatch ?? null]));
|
|
18745
18926
|
if (diffReceipt.canonical_source !== "version_row" || diffReceipt.canonical_digest !== expectedDigest) {
|
|
18746
18927
|
throw new Error(`Embed ${embed.embed_id} v${row3.version_number} durable receipt did not match the stored source.`);
|
|
@@ -18748,34 +18929,19 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
18748
18929
|
}
|
|
18749
18930
|
}
|
|
18750
18931
|
};
|
|
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;
|
|
18932
|
+
await runRecoveryBatch([...finalized.values()], async (embed) => {
|
|
18933
|
+
assertOwner();
|
|
18770
18934
|
const parentId = embed.parent_embed_id || null;
|
|
18771
18935
|
const parentKey = parentId ? parentKeys.get(parentId) ?? await resolveCachedParentKey(parentId) : void 0;
|
|
18936
|
+
assertOwner();
|
|
18772
18937
|
if (parentId && !parentKey) {
|
|
18773
18938
|
throw new Error(`Cannot persist child embed ${embed.embed_id}: parent embed key ${parentId} is unavailable.`);
|
|
18774
18939
|
}
|
|
18775
18940
|
const embedKey = parentKey ?? await deriveEmbedKeyFromChatKey(params.chatKeyBytes, embed.embed_id);
|
|
18941
|
+
assertOwner();
|
|
18776
18942
|
await persistOne(embed, embedKey, Boolean(parentId));
|
|
18777
|
-
|
|
18778
|
-
}
|
|
18943
|
+
assertOwner();
|
|
18944
|
+
}, (embed, dependency) => embed.parent_embed_id === dependency.embed_id);
|
|
18779
18945
|
}
|
|
18780
18946
|
async persistPostProcessingMetadata(params) {
|
|
18781
18947
|
const encryptedFollowUps = params.followUpSuggestions.length > 0 ? await encryptWithAesGcmCombined(
|
|
@@ -23103,6 +23269,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
23103
23269
|
interactiveViewerAllowed = false;
|
|
23104
23270
|
interactiveViewerGeneration = 0;
|
|
23105
23271
|
beginInteractiveViewerSession() {
|
|
23272
|
+
this.cancelSendSocketRearm();
|
|
23106
23273
|
this.interactiveAdmissionEnabled = true;
|
|
23107
23274
|
this.interactiveAdmissionOwner = null;
|
|
23108
23275
|
this.interactiveAdmissionAuth = null;
|
|
@@ -23110,6 +23277,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
23110
23277
|
this.preparedSendSocketAttemptOwner = null;
|
|
23111
23278
|
}
|
|
23112
23279
|
endInteractiveViewerSession() {
|
|
23280
|
+
this.cancelSendSocketRearm();
|
|
23113
23281
|
this.interactiveAdmissionEnabled = false;
|
|
23114
23282
|
this.interactiveAdmissionOwner = null;
|
|
23115
23283
|
this.interactiveAdmissionAuth = null;
|
|
@@ -42590,6 +42758,20 @@ async function verifyCaddyCoreRoutes(apiUrl, origin) {
|
|
|
42590
42758
|
"Access-Control-Request-Headers": "content-type"
|
|
42591
42759
|
} });
|
|
42592
42760
|
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");
|
|
42761
|
+
let accountKeyPreflight;
|
|
42762
|
+
try {
|
|
42763
|
+
accountKeyPreflight = await request("/v1/account/chat-encryption-key", { method: "OPTIONS", headers: {
|
|
42764
|
+
Origin: origin,
|
|
42765
|
+
"Access-Control-Request-Method": "GET",
|
|
42766
|
+
"Access-Control-Request-Headers": "content-type"
|
|
42767
|
+
} });
|
|
42768
|
+
} catch {
|
|
42769
|
+
throw new Error("caddy_account_chat_key_preflight_failed");
|
|
42770
|
+
}
|
|
42771
|
+
const accountKeyMethods = accountKeyPreflight.headers.get("access-control-allow-methods")?.split(",").map((value) => value.trim().toUpperCase()) ?? [];
|
|
42772
|
+
if (accountKeyPreflight.status !== 200 || accountKeyPreflight.headers.get("access-control-allow-origin") !== origin || accountKeyPreflight.headers.get("access-control-allow-credentials") !== "true" || !accountKeyMethods.includes("GET")) {
|
|
42773
|
+
throw new Error("caddy_account_chat_key_preflight_failed");
|
|
42774
|
+
}
|
|
42593
42775
|
const availability = await request("/v1/features/availability");
|
|
42594
42776
|
if (!availability.ok) throw new Error("caddy_availability_route_failed");
|
|
42595
42777
|
const features = await availability.json();
|