openmates 0.27.0-alpha.78 → 0.27.0-alpha.79
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-XTVACTH6.js → chunk-RIVIWS3I.js} +1840 -1697
- package/dist/{cli-DJSSNn4S.d.ts → cli-CDXMwvV4.d.ts} +5 -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 +2 -2
|
@@ -6372,10 +6372,10 @@ function approvedCurrentPlan(plan) {
|
|
|
6372
6372
|
function linkedOpenTask(task, chatId, planId) {
|
|
6373
6373
|
return Boolean(task && task.primary_chat_id === chatId && task.plan_id === planId && OPEN_LINKED_TASK_STATES.has(task.status) && Number.isSafeInteger(task.version) && Number(task.version) >= 1);
|
|
6374
6374
|
}
|
|
6375
|
-
async function collectAcceptedCliPlanContext(client2, chatId, options, ownedTaskRecords = []) {
|
|
6375
|
+
async function collectAcceptedCliPlanContext(client2, chatId, options, ownedTaskRecords = [], plansRead) {
|
|
6376
6376
|
const candidates = /* @__PURE__ */ new Map();
|
|
6377
6377
|
try {
|
|
6378
|
-
const plans = await client2.listUserPlans({ ...options, chatId, activeOnly: false });
|
|
6378
|
+
const plans = await (plansRead ?? client2.listUserPlans({ ...options, chatId, activeOnly: false }));
|
|
6379
6379
|
for (const plan of plans.filter((plan2) => plan2.primary_chat_id === chatId && approvedCurrentPlan(plan2)).sort((a, b) => Number(b.updated_at ?? 0) - Number(a.updated_at ?? 0) || a.plan_id.localeCompare(b.plan_id))) candidates.set(plan.plan_id, []);
|
|
6380
6380
|
} catch {
|
|
6381
6381
|
}
|
|
@@ -6421,11 +6421,36 @@ async function collectAcceptedCliPlanContext(client2, chatId, options, ownedTask
|
|
|
6421
6421
|
}
|
|
6422
6422
|
return null;
|
|
6423
6423
|
}
|
|
6424
|
-
|
|
6424
|
+
function taskContextOwner(client2) {
|
|
6425
|
+
if (typeof client2.getSession !== "function") return () => {
|
|
6426
|
+
};
|
|
6427
|
+
const identity3 = () => {
|
|
6428
|
+
const session = client2.getSession();
|
|
6429
|
+
return createHash9("sha256").update(JSON.stringify([
|
|
6430
|
+
session.apiUrl,
|
|
6431
|
+
session.hashedEmail,
|
|
6432
|
+
session.sessionId,
|
|
6433
|
+
session.createdAt,
|
|
6434
|
+
session.masterKeyExportedB64,
|
|
6435
|
+
session.activeTeamId
|
|
6436
|
+
])).digest("hex");
|
|
6437
|
+
};
|
|
6438
|
+
const owner2 = identity3();
|
|
6439
|
+
return () => {
|
|
6440
|
+
try {
|
|
6441
|
+
if (identity3() === owner2) return;
|
|
6442
|
+
} catch {
|
|
6443
|
+
}
|
|
6444
|
+
throw new Error("Task context workspace changed");
|
|
6445
|
+
};
|
|
6446
|
+
}
|
|
6447
|
+
async function collectRelatedCliTaskContext(client2, chatId, options, records, nowSeconds3 = Date.now() / 1e3, requiredIds = /* @__PURE__ */ new Set()) {
|
|
6448
|
+
const assertTaskOwner = taskContextOwner(client2);
|
|
6425
6449
|
const owned = records.filter((record10) => typeof record10.task_id === "string" && record10.task_id.length > 0 && record10.task_id.length <= 128 && Number.isSafeInteger(record10.version) && Number(record10.version) >= 1);
|
|
6426
6450
|
const linked = owned.filter((record10) => record10.primary_chat_id === chatId && OPEN_LINKED_TASK_STATES.has(record10.status)).sort((a, b) => a.task_id.localeCompare(b.task_id));
|
|
6427
6451
|
const dependencies = /* @__PURE__ */ new Set();
|
|
6428
|
-
|
|
6452
|
+
const dependencySeeds = [...new Map([...linked, ...owned.filter((task) => requiredIds.has(task.task_id))].map((task) => [task.task_id, task])).values()].slice(0, 4);
|
|
6453
|
+
if (typeof client2.getTaskDependencies === "function") await Promise.all(dependencySeeds.map(async (task) => {
|
|
6429
6454
|
try {
|
|
6430
6455
|
const result = await client2.getTaskDependencies(task.task_id, options);
|
|
6431
6456
|
const ref = `task:${task.task_id}`;
|
|
@@ -6436,10 +6461,23 @@ async function collectRelatedCliTaskContext(client2, chatId, options, records, n
|
|
|
6436
6461
|
} catch {
|
|
6437
6462
|
}
|
|
6438
6463
|
}));
|
|
6464
|
+
assertTaskOwner();
|
|
6465
|
+
const presentIds = new Set(owned.map((task) => task.task_id));
|
|
6466
|
+
if (typeof client2.getUserTask === "function") {
|
|
6467
|
+
await Promise.all([...dependencies].filter((id) => !presentIds.has(id)).sort().slice(0, 24).map(async (id) => {
|
|
6468
|
+
try {
|
|
6469
|
+
const row3 = await client2.getUserTask(id, options);
|
|
6470
|
+
assertTaskOwner();
|
|
6471
|
+
if (row3?.task_id === id && Number.isSafeInteger(row3.version) && Number(row3.version) >= 1) owned.push(row3);
|
|
6472
|
+
} catch {
|
|
6473
|
+
}
|
|
6474
|
+
}));
|
|
6475
|
+
}
|
|
6476
|
+
assertTaskOwner();
|
|
6439
6477
|
const changedAt = (record10) => Number(record10.updated_at ?? record10.created_at ?? 0);
|
|
6440
6478
|
const priority = (record10) => {
|
|
6441
6479
|
if (record10.primary_chat_id === chatId && OPEN_LINKED_TASK_STATES.has(record10.status)) return 0;
|
|
6442
|
-
if (dependencies.has(record10.task_id)) return 1;
|
|
6480
|
+
if (dependencies.has(record10.task_id) || requiredIds.has(record10.task_id)) return 1;
|
|
6443
6481
|
if (record10.status === "in_progress") return 2;
|
|
6444
6482
|
if (["blocked", "done"].includes(record10.status) && changedAt(record10) >= nowSeconds3 - 1800) return 3;
|
|
6445
6483
|
return 4;
|
|
@@ -6462,9 +6500,12 @@ async function collectRelatedCliTaskContext(client2, chatId, options, records, n
|
|
|
6462
6500
|
} catch {
|
|
6463
6501
|
}
|
|
6464
6502
|
}
|
|
6503
|
+
assertTaskOwner();
|
|
6465
6504
|
return candidates;
|
|
6466
6505
|
}
|
|
6467
6506
|
async function prepareCliJevContext(client2, chatId, options, supplied = {}, text8, discovery = {}) {
|
|
6507
|
+
const assertTaskOwner = taskContextOwner(client2);
|
|
6508
|
+
const plansRead = discovery.sdkMintedChatId ? Promise.resolve([]) : typeof client2.listUserPlans === "function" ? client2.listUserPlans({ ...options, chatId, activeOnly: false }).catch(() => []) : Promise.resolve([]);
|
|
6468
6509
|
let candidates = [];
|
|
6469
6510
|
let active = {};
|
|
6470
6511
|
let tasks = [];
|
|
@@ -6485,18 +6526,39 @@ async function prepareCliJevContext(client2, chatId, options, supplied = {}, tex
|
|
|
6485
6526
|
discovery.onProjectRoutingError?.(error);
|
|
6486
6527
|
}),
|
|
6487
6528
|
(async () => {
|
|
6488
|
-
const
|
|
6489
|
-
|
|
6490
|
-
|
|
6529
|
+
const taskClient = client2;
|
|
6530
|
+
const [directory, linked] = await Promise.all([
|
|
6531
|
+
taskClient.listUserTaskDiscoveryPage?.(options).catch(() => []) ?? Promise.resolve([]),
|
|
6532
|
+
discovery.sdkMintedChatId ? Promise.resolve([]) : client2.listUserTasks({ ...options, chatId, limit: 100 }).catch(() => [])
|
|
6533
|
+
]);
|
|
6534
|
+
assertTaskOwner();
|
|
6535
|
+
const records = /* @__PURE__ */ new Map();
|
|
6536
|
+
for (const record10 of [...directory.slice(0, 100), ...linked]) records.set(record10.task_id, record10);
|
|
6537
|
+
const requiredIds = new Set([
|
|
6538
|
+
...(supplied.related_task_candidates ?? []).map((row3) => row3.task_id)
|
|
6539
|
+
].filter((id) => typeof id === "string" && id.length > 0 && id.length <= 128).slice(0, 24));
|
|
6540
|
+
if (typeof client2.getUserTask === "function") await Promise.all([...requiredIds].filter((id) => !records.has(id)).map(async (id) => {
|
|
6541
|
+
try {
|
|
6542
|
+
const record10 = await client2.getUserTask(id, options);
|
|
6543
|
+
assertTaskOwner();
|
|
6544
|
+
if (record10?.task_id === id) records.set(id, record10);
|
|
6545
|
+
} catch {
|
|
6546
|
+
}
|
|
6547
|
+
}));
|
|
6548
|
+
assertTaskOwner();
|
|
6549
|
+
ownedTaskRecords = [...records.values()];
|
|
6550
|
+
tasks = await collectRelatedCliTaskContext(client2, chatId, options, ownedTaskRecords, Date.now() / 1e3, requiredIds);
|
|
6491
6551
|
})().catch(() => {
|
|
6492
6552
|
})
|
|
6493
6553
|
]);
|
|
6494
|
-
|
|
6554
|
+
assertTaskOwner();
|
|
6555
|
+
const acceptedPlan = discovery.sdkMintedChatId ? null : await collectAcceptedCliPlanContext(client2, chatId, options, ownedTaskRecords, plansRead).catch(() => null);
|
|
6556
|
+
assertTaskOwner();
|
|
6495
6557
|
const catalog = await catalogReady;
|
|
6496
6558
|
discovery.onRouting?.({
|
|
6497
6559
|
accepted_plan_context: acceptedPlan,
|
|
6498
6560
|
project_focus_candidates: (discovery.verifiedProjectCandidates ?? supplied.project_focus_candidates ?? candidates).slice(0, 40),
|
|
6499
|
-
related_task_candidates:
|
|
6561
|
+
related_task_candidates: tasks.slice(0, 24),
|
|
6500
6562
|
project_focus_catalog: catalog.slice(0, 20)
|
|
6501
6563
|
});
|
|
6502
6564
|
await activeReady;
|
|
@@ -6518,10 +6580,11 @@ async function prepareCliJevContext(client2, chatId, options, supplied = {}, tex
|
|
|
6518
6580
|
documentIds.add(identity3);
|
|
6519
6581
|
customDocuments.push(rule);
|
|
6520
6582
|
}
|
|
6583
|
+
assertTaskOwner();
|
|
6521
6584
|
return {
|
|
6522
6585
|
accepted_plan_context: acceptedPlan,
|
|
6523
6586
|
project_focus_candidates: (discovery.verifiedProjectCandidates ?? supplied.project_focus_candidates ?? candidates).slice(0, 40),
|
|
6524
|
-
related_task_candidates:
|
|
6587
|
+
related_task_candidates: tasks.slice(0, 24),
|
|
6525
6588
|
custom_memory_documents: customDocuments,
|
|
6526
6589
|
project_focus_catalog: current2 ? (supplied.project_focus_catalog ?? loaded.project_focus_catalog ?? []).slice(0, 20) : [],
|
|
6527
6590
|
project_focus_documents: current2 ? (supplied.project_focus_documents ?? loaded.project_focus_documents ?? []).slice(0, 20) : [],
|
|
@@ -11254,6 +11317,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11254
11317
|
ownTeamDisplayNames = /* @__PURE__ */ new Map();
|
|
11255
11318
|
pendingWhoAmIAuthHandoff = null;
|
|
11256
11319
|
personalMemoryAuthHandoff = null;
|
|
11320
|
+
scopedChatHistory = /* @__PURE__ */ new WeakMap();
|
|
11257
11321
|
personalMemoryReadInFlight = null;
|
|
11258
11322
|
interactiveAdmissionEnabled = false;
|
|
11259
11323
|
interactiveAdmissionOwner = null;
|
|
@@ -14541,8 +14605,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
14541
14605
|
const accountCurrent = this.captureTeamAccountOwner().isCurrent;
|
|
14542
14606
|
const teamId = this.resolveTeamContext(options);
|
|
14543
14607
|
const masterKey = Buffer.from(this.getMasterKeyBytes());
|
|
14544
|
-
|
|
14545
|
-
|
|
14608
|
+
if (options.freshChatOnly && !CANONICAL_UUID_PATTERN.test(query)) {
|
|
14609
|
+
throw new Error("Fresh scoped history requires a full chat UUID.");
|
|
14610
|
+
}
|
|
14611
|
+
const localCache = options.preferCache || options.freshChatOnly ? loadSyncCache(teamId) : null;
|
|
14612
|
+
let cache = localCache ?? (options.preferCache || options.freshChatOnly ? {
|
|
14546
14613
|
syncedAt: 0,
|
|
14547
14614
|
totalChatCount: 0,
|
|
14548
14615
|
loadedChatCount: 0,
|
|
@@ -14560,7 +14627,28 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
14560
14627
|
const wrappingKey = localTeamKey ? base64ToBytes(localTeamKey) : await this.getChatWrappingKey(teamId, masterKey);
|
|
14561
14628
|
let found;
|
|
14562
14629
|
let fetchedMetadata = false;
|
|
14563
|
-
|
|
14630
|
+
if (options.freshChatOnly) {
|
|
14631
|
+
const response = await this.http.post(
|
|
14632
|
+
this.appendTeamQuery("/v1/chats/metadata/batch", { teamId }),
|
|
14633
|
+
{ chat_ids: [query] },
|
|
14634
|
+
this.getCliRequestHeaders()
|
|
14635
|
+
);
|
|
14636
|
+
current2();
|
|
14637
|
+
options.signal?.throwIfAborted();
|
|
14638
|
+
if (!response.ok) throw new Error(`Chat metadata failed with HTTP ${response.status}`);
|
|
14639
|
+
const details = response.data.chats.find((chat) => String(chat.id ?? "") === query);
|
|
14640
|
+
if (!details) throw new Error(`Chat '${query}' not found.`);
|
|
14641
|
+
found = { details, messages: [] };
|
|
14642
|
+
fetchedMetadata = true;
|
|
14643
|
+
cache = {
|
|
14644
|
+
...cache,
|
|
14645
|
+
chats: [...cache.chats.filter((chat) => String(chat.details.id ?? "") !== query), found],
|
|
14646
|
+
chatKeyWrappers: [
|
|
14647
|
+
...cache.chatKeyWrappers ?? [],
|
|
14648
|
+
...Array.isArray(details.chat_key_wrappers) ? details.chat_key_wrappers : []
|
|
14649
|
+
]
|
|
14650
|
+
};
|
|
14651
|
+
} else try {
|
|
14564
14652
|
found = await this.resolveCachedChatForQuery(query, cache, wrappingKey, teamId);
|
|
14565
14653
|
} catch (error) {
|
|
14566
14654
|
if (!options.preferCache || !(error instanceof Error) || !/^Chat '.*' not found\./.test(error.message)) throw error;
|
|
@@ -14582,7 +14670,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
14582
14670
|
] };
|
|
14583
14671
|
}
|
|
14584
14672
|
const foundChatId = String(found.details.id ?? "");
|
|
14585
|
-
if (!options.preferCache && foundChatId && getClientMessagesVersionForSync(found) === 0) {
|
|
14673
|
+
if (!options.preferCache && !options.freshChatOnly && foundChatId && getClientMessagesVersionForSync(found) === 0) {
|
|
14586
14674
|
cache = await this.ensureSynced(true, [foundChatId], options, true);
|
|
14587
14675
|
found = cache.chats.find((c) => String(c.details.id ?? "") === foundChatId) ?? found;
|
|
14588
14676
|
}
|
|
@@ -14668,7 +14756,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
14668
14756
|
const messages = coalesceMemoryRequestMessages(decoded);
|
|
14669
14757
|
current2();
|
|
14670
14758
|
if (options.preferCache && rawMessages !== found.messages) persistMessages(rawMessages);
|
|
14671
|
-
|
|
14759
|
+
const result = { chat: chatItem, messages, ...historyIncomplete ? { historyIncomplete: true } : {} };
|
|
14760
|
+
if (options.freshChatOnly) {
|
|
14761
|
+
this.scopedChatHistory.set(result, { ...cache, chats: cache.chats.map((chat) => chat === found ? { ...found, messages: rawMessages } : chat) });
|
|
14762
|
+
}
|
|
14763
|
+
return result;
|
|
14672
14764
|
}
|
|
14673
14765
|
/** Warm the latest recent windows in the background; only ciphertext reaches disk. */
|
|
14674
14766
|
async cacheRecentChatMessages(options = {}) {
|
|
@@ -16176,15 +16268,10 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16176
16268
|
async replayCurrentTurnRecoveryOutputPages(ws, ownerId2, pages, cache, teamId, chatId) {
|
|
16177
16269
|
const isActive = (output) => output.root_chat_id === chatId || output.target_chat_id === chatId;
|
|
16178
16270
|
const active = pages.map((page) => page.filter(isActive));
|
|
16179
|
-
const unrelated = pages.map((page) => page.filter((output) => !isActive(output)));
|
|
16180
16271
|
if (active.some((page) => page.length > 0)) {
|
|
16181
16272
|
await this.replayRecoveryOutputPages(ws, ownerId2, active, cache, teamId);
|
|
16182
16273
|
clearSyncCache(teamId);
|
|
16183
16274
|
}
|
|
16184
|
-
if (unrelated.some((page) => page.length > 0)) {
|
|
16185
|
-
await this.replayRecoveryOutputPages(ws, ownerId2, unrelated, cache, teamId, () => {
|
|
16186
|
-
});
|
|
16187
|
-
}
|
|
16188
16275
|
}
|
|
16189
16276
|
async sendMessage(params) {
|
|
16190
16277
|
const preWsStartedAt = performance.now();
|
|
@@ -16224,1579 +16311,1506 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
16224
16311
|
} else {
|
|
16225
16312
|
chatId = params.chatId;
|
|
16226
16313
|
}
|
|
16227
|
-
|
|
16228
|
-
|
|
16229
|
-
|
|
16230
|
-
|
|
16231
|
-
|
|
16232
|
-
|
|
16233
|
-
|
|
16234
|
-
|
|
16235
|
-
|
|
16236
|
-
|
|
16237
|
-
|
|
16238
|
-
|
|
16239
|
-
|
|
16240
|
-
|
|
16241
|
-
|
|
16242
|
-
|
|
16243
|
-
interactiveHuman: params.interactiveHuman === true && this.interactiveViewerAllowed && process.stdin.isTTY === true && process.stdout.isTTY === true
|
|
16314
|
+
const historyAbort = new AbortController();
|
|
16315
|
+
let savedPrivacyHistoryRead = null;
|
|
16316
|
+
let historySnapshotOwner = null;
|
|
16317
|
+
const readSavedPrivacyHistory = () => {
|
|
16318
|
+
if (!savedPrivacyHistoryRead) {
|
|
16319
|
+
savedPrivacyHistoryRead = withHttpAbortSignal(historyAbort.signal, () => this.getChatMessages(
|
|
16320
|
+
chatId,
|
|
16321
|
+
{ personal: !teamId, teamId, freshChatOnly: true, signal: historyAbort.signal }
|
|
16322
|
+
)).then((history) => {
|
|
16323
|
+
historySnapshotOwner = this.preparedChatOwner(this.requireSession(), teamId);
|
|
16324
|
+
return history;
|
|
16325
|
+
});
|
|
16326
|
+
void savedPrivacyHistoryRead.catch(() => {
|
|
16327
|
+
});
|
|
16328
|
+
}
|
|
16329
|
+
return savedPrivacyHistoryRead;
|
|
16244
16330
|
};
|
|
16245
|
-
const
|
|
16246
|
-
|
|
16247
|
-
if (
|
|
16248
|
-
|
|
16249
|
-
if (teamId) independentKeyOwner.teamKeyFingerprint = createHash14("sha256").update(bytesToBase64(prepared.wrappingKey)).digest("hex");
|
|
16250
|
-
return prepared;
|
|
16331
|
+
const joinSavedPrivacyHistory = async () => {
|
|
16332
|
+
const history = await readSavedPrivacyHistory();
|
|
16333
|
+
if (!historySnapshotOwner || !this.preparedChatOwnerCurrent(historySnapshotOwner, teamId)) {
|
|
16334
|
+
throw new Error("Chat workspace changed during history preparation.");
|
|
16251
16335
|
}
|
|
16252
|
-
|
|
16253
|
-
|
|
16254
|
-
|
|
16255
|
-
|
|
16256
|
-
|
|
16257
|
-
|
|
16258
|
-
|
|
16259
|
-
|
|
16260
|
-
|
|
16261
|
-
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16292
|
-
|
|
16293
|
-
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
|
|
16297
|
-
|
|
16298
|
-
|
|
16299
|
-
|
|
16300
|
-
|
|
16301
|
-
|
|
16302
|
-
|
|
16303
|
-
|
|
16304
|
-
|
|
16305
|
-
|
|
16306
|
-
|
|
16307
|
-
|
|
16308
|
-
|
|
16309
|
-
|
|
16310
|
-
|
|
16311
|
-
|
|
16312
|
-
|
|
16313
|
-
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16336
|
-
|
|
16336
|
+
return history;
|
|
16337
|
+
};
|
|
16338
|
+
try {
|
|
16339
|
+
let finalMessage = params.message;
|
|
16340
|
+
if (hasRememberMessageReference(finalMessage)) {
|
|
16341
|
+
const rememberedHistory = (params.messageHistory ?? []).map((message) => ({ id: message.message_id, content: message.content })).filter((message) => Boolean(message.id) && typeof message.content === "string");
|
|
16342
|
+
finalMessage = rewriteRememberMessageReferences(finalMessage, rememberedHistory);
|
|
16343
|
+
}
|
|
16344
|
+
if (params.chatId && !params.incognito && hasRememberMessageReference(finalMessage)) {
|
|
16345
|
+
const { messages } = await joinSavedPrivacyHistory();
|
|
16346
|
+
finalMessage = rewriteRememberMessageReferences(finalMessage, messages);
|
|
16347
|
+
}
|
|
16348
|
+
const shouldWaitForAi = shouldWaitForTeamAi(finalMessage, teamId);
|
|
16349
|
+
const explicitTasksAppSkill = messageExplicitlyRequestsTasksAppSkill(finalMessage);
|
|
16350
|
+
const taskUpdateJobsEnabled = params.taskUpdateJobs !== false && !explicitTasksAppSkill;
|
|
16351
|
+
const sendSocketOptions = {
|
|
16352
|
+
taskUpdateJobs: taskUpdateJobsEnabled,
|
|
16353
|
+
projectFileJobs: !params.incognito,
|
|
16354
|
+
remoteCommandJobs: !params.incognito,
|
|
16355
|
+
interactiveHuman: params.interactiveHuman === true && this.interactiveViewerAllowed && process.stdin.isTTY === true && process.stdout.isTTY === true
|
|
16356
|
+
};
|
|
16357
|
+
const independentKeyOwner = this.preparedChatOwner(this.requireSession(), teamId);
|
|
16358
|
+
const independentNewChatKey = !params.incognito && !params.chatId ? (async () => {
|
|
16359
|
+
if (ownsPreparedId && this.preparedChatKey?.chatId === chatId) {
|
|
16360
|
+
const prepared = await this.preparedChatKey.value;
|
|
16361
|
+
if (teamId) independentKeyOwner.teamKeyFingerprint = createHash14("sha256").update(bytesToBase64(prepared.wrappingKey)).digest("hex");
|
|
16362
|
+
return prepared;
|
|
16363
|
+
}
|
|
16364
|
+
const wrappingKey = await this.getChatWrappingKey(teamId, this.getMasterKeyBytes());
|
|
16365
|
+
if (teamId) independentKeyOwner.teamKeyFingerprint = createHash14("sha256").update(bytesToBase64(wrappingKey)).digest("hex");
|
|
16366
|
+
const key = new Uint8Array(randomBytes4(32));
|
|
16367
|
+
return { key, wrappingKey, encryptedKey: await encryptBytesWithAesGcm(key, wrappingKey) };
|
|
16368
|
+
})() : null;
|
|
16369
|
+
void independentNewChatKey?.catch(() => {
|
|
16370
|
+
});
|
|
16371
|
+
const independentRecoveryKeypair = independentNewChatKey?.then((prepared) => deriveChatCompletionRecoveryKeypair(
|
|
16372
|
+
Buffer.from(prepared.key).toString("base64url"),
|
|
16373
|
+
chatId,
|
|
16374
|
+
1
|
|
16375
|
+
));
|
|
16376
|
+
void independentRecoveryKeypair?.catch(() => {
|
|
16377
|
+
});
|
|
16378
|
+
const privacyTurnId = params.incognito ? null : randomUUID9();
|
|
16379
|
+
let conditionalPrivacyReceipt = null;
|
|
16380
|
+
let compiledPrivacy;
|
|
16381
|
+
let memorySocket = null;
|
|
16382
|
+
let socketSnapshotAttempted = false;
|
|
16383
|
+
let socketSnapshotResultRows = null;
|
|
16384
|
+
let socketProjectCandidateRows = null;
|
|
16385
|
+
let verifiedProjectCandidates = null;
|
|
16386
|
+
let deferredProjectDirectory = false;
|
|
16387
|
+
let usePersonalRoutingDirectory = false;
|
|
16388
|
+
let projectCandidatesReadyFromSendStartMs = null;
|
|
16389
|
+
let socketMemoryReceipt = null;
|
|
16390
|
+
const socketMemoryOptionsKey = () => JSON.stringify([
|
|
16391
|
+
this.sendSocketOptionsKey(sendSocketOptions),
|
|
16392
|
+
params.projectId ?? null,
|
|
16393
|
+
params.teamId ?? null,
|
|
16394
|
+
params.personal === true,
|
|
16395
|
+
params.incognito === true,
|
|
16396
|
+
params.autoApproveMemories === true,
|
|
16397
|
+
params.piiDetection !== false,
|
|
16398
|
+
params.learningMode?.enabled ?? null,
|
|
16399
|
+
params.learningMode?.ageGroup ?? null
|
|
16400
|
+
]);
|
|
16401
|
+
if (!params.incognito && params.chatId) readSavedPrivacyHistory();
|
|
16402
|
+
const memoryStartedAt = performance.now();
|
|
16403
|
+
let availableMemories = params.memorySnapshot ?? [];
|
|
16404
|
+
let memoryCountsLoaded = false;
|
|
16405
|
+
let memoryMetadataKeys = [];
|
|
16406
|
+
if (!params.incognito) {
|
|
16407
|
+
try {
|
|
16408
|
+
if (!teamId) {
|
|
16409
|
+
try {
|
|
16410
|
+
socketSnapshotAttempted = true;
|
|
16411
|
+
memorySocket = (sendSocketOptions.interactiveHuman ? await this.takePreparedSendSocket(sendSocketOptions, teamId) : null) ?? await this.openWsClient(sendSocketOptions);
|
|
16412
|
+
const ownerKey = JSON.stringify([
|
|
16413
|
+
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16414
|
+
authCookieFingerprint(this.http.getCookieMap())
|
|
16415
|
+
]);
|
|
16416
|
+
const conditionalSupported = await memorySocket.ws.waitForServerCapability("personal_send_context_snapshot_v2");
|
|
16417
|
+
const combinedSupported = conditionalSupported || await memorySocket.ws.waitForServerCapability("personal_send_context_snapshot_v1");
|
|
16418
|
+
const supported = combinedSupported || await memorySocket.ws.waitForServerCapability("personal_memory_snapshot_v1");
|
|
16419
|
+
if (supported) {
|
|
16420
|
+
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");
|
|
16421
|
+
deferredProjectDirectory = deferProjectDirectory;
|
|
16422
|
+
usePersonalRoutingDirectory = deferProjectDirectory && memorySocket.ws.supportsServerCapability("personal_project_routing_directory_v1");
|
|
16423
|
+
traceSendTiming("snapshot_request");
|
|
16424
|
+
const privacyOwnerKey = this.preparedPrivacyOwnerKey(null);
|
|
16425
|
+
const preferencesKey = JSON.stringify(await readPrivacyPreferences());
|
|
16426
|
+
const preparedBundle = conditionalSupported ? this.preparedPrivacy.get(privacyOwnerKey, preferencesKey) : null;
|
|
16427
|
+
const conditional = conditionalSupported ? await memorySocket.ws.requestPersonalSendContextSnapshotV2({
|
|
16428
|
+
request_id: randomUUID9(),
|
|
16429
|
+
purpose: "send",
|
|
16430
|
+
turn_id: privacyTurnId,
|
|
16431
|
+
...preparedBundle ? { known_generation: preparedBundle.generation } : {}
|
|
16432
|
+
}) : null;
|
|
16433
|
+
const combined = !conditional && combinedSupported && !deferProjectDirectory ? await memorySocket.ws.requestPersonalSendContextSnapshot(randomUUID9()) : null;
|
|
16434
|
+
const snapshot2 = conditional ? conditional.personal_memories ?? null : combined?.personal_memories ?? await memorySocket.ws.requestPersonalMemorySnapshot(randomUUID9());
|
|
16435
|
+
traceSendTiming("snapshot_ready");
|
|
16436
|
+
if (conditional || combined) {
|
|
16437
|
+
socketProjectCandidateRows = (conditional ?? combined).project_candidates;
|
|
16438
|
+
verifiedProjectCandidates = await decodePersonalProjectCandidates(this, (conditional ?? combined).project_candidates);
|
|
16439
|
+
projectCandidatesReadyFromSendStartMs = Math.round(performance.now() - preWsStartedAt);
|
|
16440
|
+
}
|
|
16441
|
+
if (conditional?.unchanged) {
|
|
16442
|
+
if (!preparedBundle || conditional.privacy_generation !== preparedBundle.generation) throw new Error("Unchanged privacy snapshot has no matching prepared bundle.");
|
|
16443
|
+
availableMemories = preparedBundle.memories;
|
|
16444
|
+
compiledPrivacy = preparedBundle.privacy;
|
|
16445
|
+
socketSnapshotResultRows = [];
|
|
16446
|
+
} else {
|
|
16447
|
+
if (!snapshot2) throw new Error("Full Personal privacy snapshot is required.");
|
|
16448
|
+
socketSnapshotResultRows = snapshot2.memories;
|
|
16449
|
+
availableMemories = await this.listMemoriesUnshared({ personal: true }, snapshot2);
|
|
16450
|
+
if (conditional) {
|
|
16451
|
+
this.preparedPrivacy.set(privacyOwnerKey, preferencesKey, conditional.privacy_generation, availableMemories);
|
|
16452
|
+
compiledPrivacy = this.preparedPrivacy.get(privacyOwnerKey, preferencesKey)?.privacy;
|
|
16453
|
+
}
|
|
16454
|
+
}
|
|
16337
16455
|
if (conditional) {
|
|
16338
|
-
|
|
16339
|
-
|
|
16456
|
+
if (!conditional.receipt_id || conditional.turn_id !== privacyTurnId) throw new Error("Conditional privacy snapshot has no valid send receipt.");
|
|
16457
|
+
conditionalPrivacyReceipt = {
|
|
16458
|
+
receiptId: conditional.receipt_id,
|
|
16459
|
+
generation: conditional.privacy_generation,
|
|
16460
|
+
preferencesKey,
|
|
16461
|
+
epoch: this.preparedPrivacyEpoch
|
|
16462
|
+
};
|
|
16340
16463
|
}
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
|
|
16345
|
-
|
|
16346
|
-
|
|
16347
|
-
|
|
16348
|
-
|
|
16464
|
+
traceSendTiming("memory_decrypt_ready");
|
|
16465
|
+
if (!memorySocket.ws.isOpen() || JSON.stringify([
|
|
16466
|
+
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16467
|
+
authCookieFingerprint(this.http.getCookieMap())
|
|
16468
|
+
]) !== ownerKey) {
|
|
16469
|
+
throw new Error("Personal Memory snapshot owner or socket changed during preparation.");
|
|
16470
|
+
}
|
|
16471
|
+
socketMemoryReceipt = {
|
|
16472
|
+
ws: memorySocket.ws,
|
|
16473
|
+
snapshot: availableMemories,
|
|
16474
|
+
resultRows: socketSnapshotResultRows,
|
|
16475
|
+
projectRows: socketProjectCandidateRows,
|
|
16476
|
+
candidates: verifiedProjectCandidates,
|
|
16477
|
+
ownerKey,
|
|
16478
|
+
optionsKey: socketMemoryOptionsKey(),
|
|
16479
|
+
used: false
|
|
16349
16480
|
};
|
|
16481
|
+
} else {
|
|
16482
|
+
memorySocket.ws.close();
|
|
16483
|
+
memorySocket = null;
|
|
16484
|
+
socketSnapshotAttempted = false;
|
|
16350
16485
|
}
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16354
|
-
authCookieFingerprint(this.http.getCookieMap())
|
|
16355
|
-
]) !== ownerKey) {
|
|
16356
|
-
throw new Error("Personal Memory snapshot owner or socket changed during preparation.");
|
|
16357
|
-
}
|
|
16358
|
-
socketMemoryReceipt = {
|
|
16359
|
-
ws: memorySocket.ws,
|
|
16360
|
-
snapshot: availableMemories,
|
|
16361
|
-
resultRows: socketSnapshotResultRows,
|
|
16362
|
-
projectRows: socketProjectCandidateRows,
|
|
16363
|
-
candidates: verifiedProjectCandidates,
|
|
16364
|
-
ownerKey,
|
|
16365
|
-
optionsKey: socketMemoryOptionsKey(),
|
|
16366
|
-
used: false
|
|
16367
|
-
};
|
|
16368
|
-
} else {
|
|
16369
|
-
memorySocket.ws.close();
|
|
16486
|
+
} catch (error) {
|
|
16487
|
+
memorySocket?.ws.close();
|
|
16370
16488
|
memorySocket = null;
|
|
16371
|
-
|
|
16489
|
+
throw error;
|
|
16372
16490
|
}
|
|
16373
|
-
} catch (error) {
|
|
16374
|
-
memorySocket?.ws.close();
|
|
16375
|
-
memorySocket = null;
|
|
16376
|
-
throw error;
|
|
16377
16491
|
}
|
|
16378
|
-
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16492
|
+
if (!socketMemoryReceipt) availableMemories = teamId && params.memorySnapshot ? params.memorySnapshot : await this.listMemoriesUnshared({ teamId }, void 0, !teamId);
|
|
16493
|
+
memoryCountsLoaded = true;
|
|
16494
|
+
memoryMetadataKeys = [
|
|
16495
|
+
...new Set(
|
|
16496
|
+
availableMemories.filter((memory) => memory.app_id && memory.item_type).map((memory) => `${memory.app_id}-${memory.item_type}`)
|
|
16497
|
+
)
|
|
16498
|
+
];
|
|
16499
|
+
} catch (error) {
|
|
16500
|
+
if (socketSnapshotAttempted || !teamId) throw error;
|
|
16501
|
+
if (params.autoApproveMemories) {
|
|
16502
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16503
|
+
throw new Error(`Failed to load memories for auto-approval: ${message}`);
|
|
16504
|
+
}
|
|
16391
16505
|
}
|
|
16392
16506
|
}
|
|
16393
|
-
}
|
|
16394
|
-
try {
|
|
16395
|
-
const snapshotAuthHandoff = this.personalMemoryAuthHandoff?.snapshot.deref() === availableMemories ? this.personalMemoryAuthHandoff.auth : null;
|
|
16396
|
-
this.personalMemoryAuthHandoff = null;
|
|
16397
|
-
this.pendingWhoAmIAuthHandoff = null;
|
|
16398
|
-
const personalSendAuthHandoff = !teamId && !params.incognito ? snapshotAuthHandoff : null;
|
|
16399
|
-
const memoryPrepMs = Math.round(performance.now() - memoryStartedAt);
|
|
16400
|
-
const privacyStartedAt = performance.now();
|
|
16401
|
-
traceSendTiming("privacy_begin");
|
|
16402
|
-
const savedPrivacyHistory = !params.incognito && params.chatId ? await this.getChatMessages(chatId, teamId ? { teamId } : { personal: true }) : null;
|
|
16403
|
-
let piiMappings = [...savedPrivacyHistory?.messages.flatMap((message) => (message.piiMappings ?? []).map((mapping) => ({ ...mapping, type: mapping.type ?? "OTHER" }))) ?? [], ...params.piiMappings ?? []];
|
|
16404
16507
|
try {
|
|
16405
|
-
|
|
16406
|
-
|
|
16407
|
-
|
|
16408
|
-
|
|
16409
|
-
|
|
16410
|
-
|
|
16411
|
-
|
|
16412
|
-
|
|
16413
|
-
|
|
16414
|
-
|
|
16415
|
-
|
|
16416
|
-
}
|
|
16417
|
-
|
|
16418
|
-
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
|
|
16508
|
+
const snapshotAuthHandoff = this.personalMemoryAuthHandoff?.snapshot.deref() === availableMemories ? this.personalMemoryAuthHandoff.auth : null;
|
|
16509
|
+
this.personalMemoryAuthHandoff = null;
|
|
16510
|
+
this.pendingWhoAmIAuthHandoff = null;
|
|
16511
|
+
const personalSendAuthHandoff = !teamId && !params.incognito ? snapshotAuthHandoff : null;
|
|
16512
|
+
const memoryPrepMs = Math.round(performance.now() - memoryStartedAt);
|
|
16513
|
+
const privacyStartedAt = performance.now();
|
|
16514
|
+
traceSendTiming("privacy_begin");
|
|
16515
|
+
const savedPrivacyHistory = !params.incognito && params.chatId ? await joinSavedPrivacyHistory() : null;
|
|
16516
|
+
let piiMappings = [...savedPrivacyHistory?.messages.flatMap((message) => (message.piiMappings ?? []).map((mapping) => ({ ...mapping, type: mapping.type ?? "OTHER" }))) ?? [], ...params.piiMappings ?? []];
|
|
16517
|
+
try {
|
|
16518
|
+
if (params.piiDetection !== false) {
|
|
16519
|
+
const prepared = await prepareCliMessagePrivacy(finalMessage, availableMemories, piiMappings, { kind: "message" }, params.onPrivacyProgress, compiledPrivacy);
|
|
16520
|
+
finalMessage = prepared.message;
|
|
16521
|
+
piiMappings = prepared.mappings;
|
|
16522
|
+
for (const embed of params.preparedEmbeds ?? []) {
|
|
16523
|
+
if (!["code-code", "docs-doc", "sheets-sheet"].includes(embed.type)) continue;
|
|
16524
|
+
const decoded = toonDecode(embed.content);
|
|
16525
|
+
for (const field3 of ["code", "html", "table", "text"]) if (typeof decoded[field3] === "string") {
|
|
16526
|
+
const value = await prepareCliMessagePrivacy(decoded[field3], availableMemories, piiMappings, { kind: "document" }, params.onPrivacyProgress, compiledPrivacy);
|
|
16527
|
+
decoded[field3] = value.message;
|
|
16528
|
+
piiMappings = value.mappings;
|
|
16529
|
+
}
|
|
16530
|
+
embed.content = toonEncodeContent(decoded);
|
|
16531
|
+
const content = decoded.code ?? decoded.table ?? decoded.html ?? decoded.text;
|
|
16532
|
+
if (typeof content === "string") {
|
|
16533
|
+
embed.contentHash = createHash14("sha256").update(content).digest("hex");
|
|
16534
|
+
embed.textLengthChars = content.length;
|
|
16535
|
+
}
|
|
16422
16536
|
}
|
|
16423
16537
|
}
|
|
16538
|
+
params.onPrivacyPrepared?.(finalMessage);
|
|
16539
|
+
} catch (error) {
|
|
16540
|
+
memorySocket?.ws.close();
|
|
16541
|
+
throw error;
|
|
16424
16542
|
}
|
|
16425
|
-
|
|
16426
|
-
|
|
16427
|
-
|
|
16428
|
-
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
|
|
16438
|
-
|
|
16439
|
-
|
|
16440
|
-
|
|
16441
|
-
|
|
16442
|
-
|
|
16443
|
-
|
|
16444
|
-
|
|
16445
|
-
|
|
16446
|
-
|
|
16447
|
-
|
|
16448
|
-
|
|
16449
|
-
let publishProjectRouting;
|
|
16450
|
-
let rejectProjectRouting;
|
|
16451
|
-
const projectRoutingContext = new Promise((resolve20, reject) => {
|
|
16452
|
-
publishProjectRouting = resolve20;
|
|
16453
|
-
rejectProjectRouting = reject;
|
|
16454
|
-
});
|
|
16455
|
-
void projectRoutingContext.catch(() => {
|
|
16456
|
-
});
|
|
16457
|
-
const publishProjectRoutingTimed = (context) => {
|
|
16458
|
-
projectCandidatesReadyFromSendStartMs ??= Math.round(performance.now() - preWsStartedAt);
|
|
16459
|
-
publishProjectRouting(deferredProjectDirectory ? { ...context, project_focus_candidates: context.project_focus_candidates?.filter((candidate) => candidate.auto_selection === true) } : context);
|
|
16460
|
-
};
|
|
16461
|
-
let recoveryMs = 0;
|
|
16462
|
-
const contextOwnerIsCurrent = () => {
|
|
16463
|
-
const current2 = this.session;
|
|
16464
|
-
return Boolean(current2 && current2.apiUrl === contextApiUrl && current2.hashedEmail === contextHashedEmail && current2.sessionId === contextSessionId && current2.createdAt === contextSessionCreatedAt && current2.masterKeyExportedB64 === contextMasterKey && current2.activeTeamId === contextActiveTeamId);
|
|
16465
|
-
};
|
|
16466
|
-
const earlyContextAbort = new AbortController();
|
|
16467
|
-
const earlyContext = !params.incognito && !params.chatId && !params.projectId ? withHttpAbortSignal(earlyContextAbort.signal, () => prepareCliJevContext(this, chatId, contextScope, suppliedContext, finalMessage, {
|
|
16468
|
-
sdkMintedChatId,
|
|
16469
|
-
onRouting: publishRouting,
|
|
16470
|
-
verifiedProjectCandidates: verifiedProjectCandidates ?? void 0,
|
|
16471
|
-
usePersonalRoutingDirectory,
|
|
16472
|
-
onProjectRouting: publishProjectRoutingTimed,
|
|
16473
|
-
onProjectRoutingError: rejectProjectRouting
|
|
16474
|
-
})).then(
|
|
16475
|
-
(value) => ({ ok: true, value, elapsedMs: Date.now() - contextStartedAt }),
|
|
16476
|
-
(error) => ({ ok: false, error, elapsedMs: Date.now() - contextStartedAt })
|
|
16477
|
-
) : null;
|
|
16478
|
-
const authSocketStartedAt = Date.now();
|
|
16479
|
-
let opened;
|
|
16480
|
-
try {
|
|
16481
|
-
opened = memorySocket ?? (sendSocketOptions.interactiveHuman ? await this.takePreparedSendSocket(sendSocketOptions, teamId) : null) ?? await this.openWsClient({ ...sendSocketOptions, personalSendAuthHandoff });
|
|
16482
|
-
} catch (error) {
|
|
16483
|
-
earlyContextAbort.abort();
|
|
16484
|
-
if (earlyContext) await earlyContext;
|
|
16485
|
-
throw error;
|
|
16486
|
-
}
|
|
16487
|
-
const { ws, ownerId: ownerId2 } = opened;
|
|
16488
|
-
const consumeSocketMemoryReceipt = async () => {
|
|
16489
|
-
if (!socketMemoryReceipt) return;
|
|
16490
|
-
const receipt = socketMemoryReceipt;
|
|
16491
|
-
if (receipt.used || receipt.ws !== ws || receipt.snapshot !== availableMemories || receipt.resultRows !== socketSnapshotResultRows || receipt.projectRows !== socketProjectCandidateRows || receipt.candidates !== verifiedProjectCandidates || !ws.isOpen() || receipt.optionsKey !== socketMemoryOptionsKey()) {
|
|
16492
|
-
throw new Error("Personal Memory snapshot socket receipt is invalid.");
|
|
16493
|
-
}
|
|
16494
|
-
await this.withFreshStoredSession(async () => {
|
|
16495
|
-
if (!ws.isOpen() || JSON.stringify([
|
|
16496
|
-
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16497
|
-
authCookieFingerprint(this.http.getCookieMap())
|
|
16498
|
-
]) !== receipt.ownerKey) {
|
|
16499
|
-
throw new Error("Personal Memory snapshot owner changed before send.");
|
|
16500
|
-
}
|
|
16543
|
+
const privacyPrepMs = Math.round(performance.now() - privacyStartedAt);
|
|
16544
|
+
traceSendTiming("privacy_ready");
|
|
16545
|
+
const preWsMs = Math.round(performance.now() - preWsStartedAt);
|
|
16546
|
+
const contextSession = this.requireSession();
|
|
16547
|
+
const {
|
|
16548
|
+
apiUrl: contextApiUrl,
|
|
16549
|
+
hashedEmail: contextHashedEmail,
|
|
16550
|
+
sessionId: contextSessionId,
|
|
16551
|
+
createdAt: contextSessionCreatedAt,
|
|
16552
|
+
masterKeyExportedB64: contextMasterKey,
|
|
16553
|
+
activeTeamId: contextActiveTeamId
|
|
16554
|
+
} = contextSession;
|
|
16555
|
+
const contextScope = { teamId, personal: !teamId };
|
|
16556
|
+
const suppliedContext = deferredProjectDirectory ? { ...params.jevContext, project_focus_candidates: void 0 } : params.jevContext;
|
|
16557
|
+
const contextStartedAt = Date.now();
|
|
16558
|
+
let publishRouting;
|
|
16559
|
+
const routingContext = new Promise((resolve20) => {
|
|
16560
|
+
publishRouting = resolve20;
|
|
16561
|
+
});
|
|
16562
|
+
let publishProjectRouting;
|
|
16563
|
+
let rejectProjectRouting;
|
|
16564
|
+
const projectRoutingContext = new Promise((resolve20, reject) => {
|
|
16565
|
+
publishProjectRouting = resolve20;
|
|
16566
|
+
rejectProjectRouting = reject;
|
|
16501
16567
|
});
|
|
16502
|
-
|
|
16503
|
-
|
|
16568
|
+
void projectRoutingContext.catch(() => {
|
|
16569
|
+
});
|
|
16570
|
+
const publishProjectRoutingTimed = (context) => {
|
|
16571
|
+
projectCandidatesReadyFromSendStartMs ??= Math.round(performance.now() - preWsStartedAt);
|
|
16572
|
+
publishProjectRouting(deferredProjectDirectory ? { ...context, project_focus_candidates: context.project_focus_candidates?.filter((candidate) => candidate.auto_selection === true) } : context);
|
|
16573
|
+
};
|
|
16574
|
+
let recoveryMs = 0;
|
|
16575
|
+
const contextOwnerIsCurrent = () => {
|
|
16576
|
+
const current2 = this.session;
|
|
16577
|
+
return Boolean(current2 && current2.apiUrl === contextApiUrl && current2.hashedEmail === contextHashedEmail && current2.sessionId === contextSessionId && current2.createdAt === contextSessionCreatedAt && current2.masterKeyExportedB64 === contextMasterKey && current2.activeTeamId === contextActiveTeamId);
|
|
16578
|
+
};
|
|
16579
|
+
const earlyContextAbort = new AbortController();
|
|
16580
|
+
const earlyContext = !params.incognito && !params.chatId && !params.projectId ? withHttpAbortSignal(earlyContextAbort.signal, () => prepareCliJevContext(this, chatId, contextScope, suppliedContext, finalMessage, {
|
|
16581
|
+
sdkMintedChatId,
|
|
16582
|
+
onRouting: publishRouting,
|
|
16583
|
+
verifiedProjectCandidates: verifiedProjectCandidates ?? void 0,
|
|
16584
|
+
usePersonalRoutingDirectory,
|
|
16585
|
+
onProjectRouting: publishProjectRoutingTimed,
|
|
16586
|
+
onProjectRoutingError: rejectProjectRouting
|
|
16587
|
+
})).then(
|
|
16588
|
+
(value) => ({ ok: true, value, elapsedMs: Date.now() - contextStartedAt }),
|
|
16589
|
+
(error) => ({ ok: false, error, elapsedMs: Date.now() - contextStartedAt })
|
|
16590
|
+
) : null;
|
|
16591
|
+
const authSocketStartedAt = Date.now();
|
|
16592
|
+
let opened;
|
|
16593
|
+
try {
|
|
16594
|
+
opened = memorySocket ?? (sendSocketOptions.interactiveHuman ? await this.takePreparedSendSocket(sendSocketOptions, teamId) : null) ?? await this.openWsClient({ ...sendSocketOptions, personalSendAuthHandoff });
|
|
16595
|
+
} catch (error) {
|
|
16596
|
+
earlyContextAbort.abort();
|
|
16597
|
+
if (earlyContext) await earlyContext;
|
|
16598
|
+
throw error;
|
|
16504
16599
|
}
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
let initialHeaderNotice = null;
|
|
16512
|
-
let initialAdmissionDurable = false;
|
|
16513
|
-
let initialHeaderStopped = false;
|
|
16514
|
-
let initialTitleScheduled;
|
|
16515
|
-
let initialIconScheduled;
|
|
16516
|
-
let persistOptionalHeader;
|
|
16517
|
-
let optionalMetadata = null;
|
|
16518
|
-
const socketReadyForSendAt = performance.now();
|
|
16519
|
-
let capabilitiesWaitMs = 0;
|
|
16520
|
-
let keyWaitMs = 0;
|
|
16521
|
-
let recoveryKeypairMs = 0;
|
|
16522
|
-
let encryptPayloadMs = 0;
|
|
16523
|
-
if (!params.incognito && !ownerId2) {
|
|
16524
|
-
earlyContextAbort.abort();
|
|
16525
|
-
ws.close();
|
|
16526
|
-
if (earlyContext) await earlyContext;
|
|
16527
|
-
throw new Error("Authenticated user identity is required for saved chat recovery.");
|
|
16528
|
-
}
|
|
16529
|
-
const authSocketMs = Date.now() - authSocketStartedAt;
|
|
16530
|
-
const mayDeferRecovery = !params.incognito && sdkMintedChatId && !teamId && !params.projectId && shouldWaitForAi;
|
|
16531
|
-
let recoveryDiscovery = null;
|
|
16532
|
-
let recoveryDiscoveryError = null;
|
|
16533
|
-
let contextAckRejection = null;
|
|
16534
|
-
let removeContextAckWatcher = null;
|
|
16535
|
-
try {
|
|
16536
|
-
if (!params.incognito) {
|
|
16537
|
-
try {
|
|
16538
|
-
ws.send("chat_metadata_warmup", teamId ? { team_id: teamId } : {});
|
|
16539
|
-
} catch {
|
|
16600
|
+
const { ws, ownerId: ownerId2 } = opened;
|
|
16601
|
+
const consumeSocketMemoryReceipt = async () => {
|
|
16602
|
+
if (!socketMemoryReceipt) return;
|
|
16603
|
+
const receipt = socketMemoryReceipt;
|
|
16604
|
+
if (receipt.used || receipt.ws !== ws || receipt.snapshot !== availableMemories || receipt.resultRows !== socketSnapshotResultRows || receipt.projectRows !== socketProjectCandidateRows || receipt.candidates !== verifiedProjectCandidates || !ws.isOpen() || receipt.optionsKey !== socketMemoryOptionsKey()) {
|
|
16605
|
+
throw new Error("Personal Memory snapshot socket receipt is invalid.");
|
|
16540
16606
|
}
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
|
|
16547
|
-
recoveryDiscoveryError = error;
|
|
16548
|
-
throw error;
|
|
16607
|
+
await this.withFreshStoredSession(async () => {
|
|
16608
|
+
if (!ws.isOpen() || JSON.stringify([
|
|
16609
|
+
this.sendSocketOwnerKey(this.requireSession(), teamId),
|
|
16610
|
+
authCookieFingerprint(this.http.getCookieMap())
|
|
16611
|
+
]) !== receipt.ownerKey) {
|
|
16612
|
+
throw new Error("Personal Memory snapshot owner changed before send.");
|
|
16549
16613
|
}
|
|
16550
|
-
);
|
|
16551
|
-
void recoveryDiscovery.catch(() => {
|
|
16552
16614
|
});
|
|
16553
|
-
if (
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16560
|
-
|
|
16561
|
-
|
|
16562
|
-
|
|
16563
|
-
let
|
|
16564
|
-
|
|
16565
|
-
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
const
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
let
|
|
16583
|
-
let
|
|
16584
|
-
let
|
|
16585
|
-
let
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
encryptedChatKey = prepared.encryptedKey;
|
|
16602
|
-
} else if (isNewChat) {
|
|
16603
|
-
chatKeyBytes = globalThis.crypto ? new Uint8Array(globalThis.crypto.getRandomValues(new Uint8Array(32))) : new Uint8Array(
|
|
16604
|
-
(await import("crypto")).webcrypto.getRandomValues(
|
|
16605
|
-
new Uint8Array(32)
|
|
16606
|
-
)
|
|
16607
|
-
);
|
|
16608
|
-
encryptedChatKey = await encryptBytesWithAesGcm(
|
|
16609
|
-
chatKeyBytes,
|
|
16610
|
-
wrappingKey
|
|
16611
|
-
);
|
|
16612
|
-
} else {
|
|
16613
|
-
const cache = loadSyncCache(teamId) ?? await this.ensureSynced(false, [], { teamId });
|
|
16614
|
-
const chat = cache.chats.find(
|
|
16615
|
-
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
16615
|
+
if (conditionalPrivacyReceipt && (conditionalPrivacyReceipt.epoch !== this.preparedPrivacyEpoch || conditionalPrivacyReceipt.preferencesKey !== JSON.stringify(await readPrivacyPreferences()))) {
|
|
16616
|
+
throw new Error("Privacy preferences changed before send; prepare the turn again.");
|
|
16617
|
+
}
|
|
16618
|
+
receipt.used = true;
|
|
16619
|
+
};
|
|
16620
|
+
let metadataTransportOwned = false;
|
|
16621
|
+
let metadataHandoffReady = false;
|
|
16622
|
+
let metadataHandoffSuggestion = null;
|
|
16623
|
+
let initialHeaderWork = null;
|
|
16624
|
+
let initialHeaderNotice = null;
|
|
16625
|
+
let initialAdmissionDurable = false;
|
|
16626
|
+
let initialHeaderStopped = false;
|
|
16627
|
+
let initialTitleScheduled;
|
|
16628
|
+
let initialIconScheduled;
|
|
16629
|
+
let persistOptionalHeader;
|
|
16630
|
+
let optionalMetadata = null;
|
|
16631
|
+
const socketReadyForSendAt = performance.now();
|
|
16632
|
+
let capabilitiesWaitMs = 0;
|
|
16633
|
+
let keyWaitMs = 0;
|
|
16634
|
+
let recoveryKeypairMs = 0;
|
|
16635
|
+
let encryptPayloadMs = 0;
|
|
16636
|
+
if (!params.incognito && !ownerId2) {
|
|
16637
|
+
earlyContextAbort.abort();
|
|
16638
|
+
ws.close();
|
|
16639
|
+
if (earlyContext) await earlyContext;
|
|
16640
|
+
throw new Error("Authenticated user identity is required for saved chat recovery.");
|
|
16641
|
+
}
|
|
16642
|
+
const authSocketMs = Date.now() - authSocketStartedAt;
|
|
16643
|
+
const mayDeferRecovery = !params.incognito && sdkMintedChatId && !teamId && !params.projectId && shouldWaitForAi;
|
|
16644
|
+
let recoveryDiscovery = null;
|
|
16645
|
+
let recoveryDiscoveryError = null;
|
|
16646
|
+
let contextAckRejection = null;
|
|
16647
|
+
let removeContextAckWatcher = null;
|
|
16648
|
+
try {
|
|
16649
|
+
if (!params.incognito) {
|
|
16650
|
+
try {
|
|
16651
|
+
ws.send("chat_metadata_warmup", teamId ? { team_id: teamId } : {});
|
|
16652
|
+
} catch {
|
|
16653
|
+
}
|
|
16654
|
+
const recoveryStartedAt = Date.now();
|
|
16655
|
+
recoveryDiscovery = ws.waitForRecoveryOutputDiscovery().then(
|
|
16656
|
+
() => {
|
|
16657
|
+
recoveryMs = Date.now() - recoveryStartedAt;
|
|
16658
|
+
},
|
|
16659
|
+
(error) => {
|
|
16660
|
+
recoveryDiscoveryError = error;
|
|
16661
|
+
throw error;
|
|
16662
|
+
}
|
|
16616
16663
|
);
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16622
|
-
|
|
16623
|
-
|
|
16624
|
-
|
|
16625
|
-
|
|
16626
|
-
|
|
16627
|
-
|
|
16664
|
+
void recoveryDiscovery.catch(() => {
|
|
16665
|
+
});
|
|
16666
|
+
if (mayDeferRecovery) {
|
|
16667
|
+
const capabilitiesWaitStartedAt = performance.now();
|
|
16668
|
+
await Promise.race([ws.waitForServerCapabilities(), recoveryDiscovery]);
|
|
16669
|
+
capabilitiesWaitMs = Math.round(performance.now() - capabilitiesWaitStartedAt);
|
|
16670
|
+
} else await recoveryDiscovery;
|
|
16671
|
+
}
|
|
16672
|
+
const messageId2 = randomUUID9();
|
|
16673
|
+
const wallCreatedAt = Math.floor(Date.now() / 1e3);
|
|
16674
|
+
const createdAt = !teamId && !params.incognito && params.chatId ? nextPersonalUserCreatedAt(wallCreatedAt, savedPrivacyHistory?.messages.map((row3) => row3.createdAt) ?? []) : wallCreatedAt;
|
|
16675
|
+
const isNewChat = !params.chatId;
|
|
16676
|
+
let messageHistoryForRequest = params.messageHistory;
|
|
16677
|
+
if (!params.incognito && !isNewChat && !messageHistoryForRequest) {
|
|
16678
|
+
const { messages } = savedPrivacyHistory;
|
|
16679
|
+
messageHistoryForRequest = buildSavedChatMessageHistory(messages, chatId);
|
|
16680
|
+
}
|
|
16681
|
+
ws.send("set_active_chat", { chat_id: chatId, ...teamId ? { team_id: teamId } : {} });
|
|
16682
|
+
const connectedAccountDirectory = buildConnectedAccountDirectoryPayload(
|
|
16683
|
+
params.connectedAccountDirectory
|
|
16684
|
+
);
|
|
16685
|
+
const connectedAccountTokenRefs = params.connectedAccountTokenRefInputs?.length ? await this.createTurnTokenRefs({
|
|
16686
|
+
chatId,
|
|
16687
|
+
messageId: messageId2,
|
|
16688
|
+
refs: params.connectedAccountTokenRefInputs,
|
|
16689
|
+
teamId
|
|
16690
|
+
}) : [];
|
|
16691
|
+
assertNoConnectedAccountSecretLeak(connectedAccountTokenRefs);
|
|
16692
|
+
let chatKeyBytes = null;
|
|
16693
|
+
let activeFocusId = null;
|
|
16694
|
+
let encryptedChatKey = null;
|
|
16695
|
+
let chatSlugLookupKey = null;
|
|
16696
|
+
let focusPhaseState = {};
|
|
16697
|
+
let baselineMessagesV = 0;
|
|
16698
|
+
let notificationTitle = null;
|
|
16699
|
+
let terminalExpectedMessagesV = 1;
|
|
16700
|
+
let savedTurnId = null;
|
|
16701
|
+
if (!params.incognito) {
|
|
16702
|
+
traceSendTiming("key_begin");
|
|
16703
|
+
const masterKey = this.getMasterKeyBytes();
|
|
16704
|
+
const preparedKey = isNewChat && ownsPreparedId && this.preparedChatKey?.chatId === chatId && this.preparedChatOwnerCurrent(this.preparedChatKey.owner, teamId) ? this.preparedChatKey : null;
|
|
16705
|
+
if (preparedKey) this.preparedChatKey = null;
|
|
16706
|
+
const keyWaitStartedAt = performance.now();
|
|
16707
|
+
const prepared = independentNewChatKey ? await independentNewChatKey : preparedKey ? await preparedKey.value : null;
|
|
16708
|
+
if (prepared && !this.preparedChatOwnerCurrent(independentKeyOwner, teamId)) throw new Error("Chat key preparation owner changed before send.");
|
|
16709
|
+
const wrappingKey = prepared?.wrappingKey ?? await this.getChatWrappingKey(teamId, masterKey);
|
|
16710
|
+
keyWaitMs = Math.round(performance.now() - keyWaitStartedAt);
|
|
16711
|
+
chatSlugLookupKey = wrappingKey;
|
|
16712
|
+
if (isNewChat && prepared) {
|
|
16713
|
+
chatKeyBytes = prepared.key;
|
|
16714
|
+
encryptedChatKey = prepared.encryptedKey;
|
|
16715
|
+
} else if (isNewChat) {
|
|
16716
|
+
chatKeyBytes = globalThis.crypto ? new Uint8Array(globalThis.crypto.getRandomValues(new Uint8Array(32))) : new Uint8Array(
|
|
16717
|
+
(await import("crypto")).webcrypto.getRandomValues(
|
|
16718
|
+
new Uint8Array(32)
|
|
16719
|
+
)
|
|
16720
|
+
);
|
|
16721
|
+
encryptedChatKey = await encryptBytesWithAesGcm(
|
|
16722
|
+
chatKeyBytes,
|
|
16723
|
+
wrappingKey
|
|
16724
|
+
);
|
|
16725
|
+
} else {
|
|
16726
|
+
const cache = (savedPrivacyHistory && this.scopedChatHistory.get(savedPrivacyHistory)) ?? loadSyncCache(teamId) ?? await this.ensureSynced(false, [], { teamId });
|
|
16727
|
+
const chat = cache.chats.find(
|
|
16728
|
+
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
16729
|
+
);
|
|
16730
|
+
if (chat) {
|
|
16731
|
+
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
16732
|
+
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
16733
|
+
if (encKey) {
|
|
16734
|
+
chatKeyBytes = await this.resolveChatKey(cache, chat, wrappingKey, teamId);
|
|
16735
|
+
encryptedChatKey = encKey;
|
|
16736
|
+
if (!chatKeyBytes) throw new Error("Could not decrypt the saved chat key.");
|
|
16737
|
+
if (typeof chat.details.encrypted_title === "string") {
|
|
16738
|
+
try {
|
|
16739
|
+
notificationTitle = await decryptWithAesGcmCombined(chat.details.encrypted_title, chatKeyBytes);
|
|
16740
|
+
} catch {
|
|
16741
|
+
}
|
|
16628
16742
|
}
|
|
16629
|
-
|
|
16630
|
-
|
|
16631
|
-
|
|
16632
|
-
|
|
16633
|
-
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16743
|
+
const encryptedPhaseState = chat.details.encrypted_focus_phase_state;
|
|
16744
|
+
if (typeof encryptedPhaseState === "string" && encryptedPhaseState) {
|
|
16745
|
+
const phaseText = await decryptWithAesGcmCombined(encryptedPhaseState, chatKeyBytes);
|
|
16746
|
+
if (!phaseText) throw new Error("Could not decrypt focus phase progress. Sync before sending again.");
|
|
16747
|
+
focusPhaseState = JSON.parse(phaseText);
|
|
16748
|
+
}
|
|
16749
|
+
const encryptedFocusId = chat.details.encrypted_active_focus_id;
|
|
16750
|
+
if (typeof encryptedFocusId === "string" && encryptedFocusId) {
|
|
16751
|
+
activeFocusId = await decryptWithAesGcmCombined(encryptedFocusId, chatKeyBytes);
|
|
16752
|
+
if (!activeFocusId) {
|
|
16753
|
+
throw new Error("Could not decrypt the chat's active focus. Sync before sending again.");
|
|
16754
|
+
}
|
|
16641
16755
|
}
|
|
16642
16756
|
}
|
|
16643
16757
|
}
|
|
16758
|
+
if (!chatKeyBytes || !encryptedChatKey) {
|
|
16759
|
+
throw new Error(`Encrypted chat key not found for chat '${chatId}'. Sync and try again.`);
|
|
16760
|
+
}
|
|
16644
16761
|
}
|
|
16645
|
-
|
|
16646
|
-
throw new Error(`Encrypted chat key not found for chat '${chatId}'. Sync and try again.`);
|
|
16647
|
-
}
|
|
16648
|
-
}
|
|
16649
|
-
traceSendTiming("key_ready");
|
|
16650
|
-
}
|
|
16651
|
-
let focusPersistence = Promise.resolve();
|
|
16652
|
-
let focusPersistenceError = null;
|
|
16653
|
-
const chatContextEvents = !params.incognito && chatKeyBytes ? registerChatContextEvents({
|
|
16654
|
-
ws,
|
|
16655
|
-
chatId,
|
|
16656
|
-
chatKey: chatKeyBytes,
|
|
16657
|
-
existingMessageIds: savedPrivacyHistory?.messages.map(canonicalSavedChatMessageId) ?? [],
|
|
16658
|
-
previousRulesSetKey: savedPrivacyHistory?.messages.filter((message) => message.role === "system").map((message) => parseChatContextContent(message.content)).filter((event) => event?.type === "rules_loaded").at(-1)?.set_key,
|
|
16659
|
-
onApplied: params.onChatContextApplied
|
|
16660
|
-
}) : null;
|
|
16661
|
-
let projectAuthoringAvailable = null;
|
|
16662
|
-
if (!params.incognito) ws.onMessageType("project_authoring_available", (value) => {
|
|
16663
|
-
if (!value || typeof value !== "object") return;
|
|
16664
|
-
const frame = value;
|
|
16665
|
-
if (frame.chat_id === chatId && frame.user_message_id === messageId2 && typeof frame.project_id === "string")
|
|
16666
|
-
projectAuthoringAvailable = { chat_id: chatId, project_id: frame.project_id, user_message_id: messageId2 };
|
|
16667
|
-
});
|
|
16668
|
-
const projectFocusCountdown = !params.incognito ? registerProjectFocusCountdown({
|
|
16669
|
-
client: this,
|
|
16670
|
-
ws,
|
|
16671
|
-
chatId,
|
|
16672
|
-
teamId,
|
|
16673
|
-
onPending: params.onProjectFocusPending,
|
|
16674
|
-
onError: (error) => {
|
|
16675
|
-
focusPersistenceError = error;
|
|
16762
|
+
traceSendTiming("key_ready");
|
|
16676
16763
|
}
|
|
16677
|
-
|
|
16678
|
-
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16764
|
+
let focusPersistence = Promise.resolve();
|
|
16765
|
+
let focusPersistenceError = null;
|
|
16766
|
+
const chatContextEvents = !params.incognito && chatKeyBytes ? registerChatContextEvents({
|
|
16767
|
+
ws,
|
|
16768
|
+
chatId,
|
|
16769
|
+
chatKey: chatKeyBytes,
|
|
16770
|
+
existingMessageIds: savedPrivacyHistory?.messages.map(canonicalSavedChatMessageId) ?? [],
|
|
16771
|
+
previousRulesSetKey: savedPrivacyHistory?.messages.filter((message) => message.role === "system").map((message) => parseChatContextContent(message.content)).filter((event) => event?.type === "rules_loaded").at(-1)?.set_key,
|
|
16772
|
+
onApplied: params.onChatContextApplied
|
|
16773
|
+
}) : null;
|
|
16774
|
+
let projectAuthoringAvailable = null;
|
|
16775
|
+
if (!params.incognito) ws.onMessageType("project_authoring_available", (value) => {
|
|
16776
|
+
if (!value || typeof value !== "object") return;
|
|
16777
|
+
const frame = value;
|
|
16778
|
+
if (frame.chat_id === chatId && frame.user_message_id === messageId2 && typeof frame.project_id === "string")
|
|
16779
|
+
projectAuthoringAvailable = { chat_id: chatId, project_id: frame.project_id, user_message_id: messageId2 };
|
|
16691
16780
|
});
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
|
|
16697
|
-
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
throw new Error("Invalid focus phase state received");
|
|
16701
|
-
}
|
|
16702
|
-
if (focusPhaseState[focusId]?.run_id === phase.run_id && focusPhaseState[focusId].version > phase.version) states2[focusId] = focusPhaseState[focusId];
|
|
16781
|
+
const projectFocusCountdown = !params.incognito ? registerProjectFocusCountdown({
|
|
16782
|
+
client: this,
|
|
16783
|
+
ws,
|
|
16784
|
+
chatId,
|
|
16785
|
+
teamId,
|
|
16786
|
+
onPending: params.onProjectFocusPending,
|
|
16787
|
+
onError: (error) => {
|
|
16788
|
+
focusPersistenceError = error;
|
|
16703
16789
|
}
|
|
16704
|
-
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
)
|
|
16711
|
-
|
|
16712
|
-
|
|
16713
|
-
...teamId ? { team_id: teamId } : {},
|
|
16714
|
-
encrypted_focus_phase_state: encryptedState
|
|
16715
|
-
});
|
|
16716
|
-
await persisted;
|
|
16717
|
-
const eventIds = /* @__PURE__ */ new Set();
|
|
16718
|
-
for (const phase of Object.values(states2)) for (const transition of phase.transitions) {
|
|
16719
|
-
if (transition.chat_id !== chatId || transition.focus_id !== phase.focus_id || !transition.event_id || eventIds.has(transition.event_id)) continue;
|
|
16720
|
-
eventIds.add(transition.event_id);
|
|
16721
|
-
const content = JSON.stringify(transition);
|
|
16722
|
-
await ws.sendAsync("chat_system_message_added", {
|
|
16790
|
+
}) : null;
|
|
16791
|
+
ws.onMessageType("focus_mode_activated", (event) => {
|
|
16792
|
+
if (event.chat_id !== chatId || !event.focus_id || !chatKeyBytes || params.incognito) return;
|
|
16793
|
+
const focusId = event.focus_id;
|
|
16794
|
+
if (!focusId.startsWith("project-")) delete focusPhaseState[focusId];
|
|
16795
|
+
const key = chatKeyBytes;
|
|
16796
|
+
focusPersistence = focusPersistence.then(async () => {
|
|
16797
|
+
const encryptedFocusId = await encryptWithAesGcmCombined(focusId, key);
|
|
16798
|
+
await ws.sendAsync("update_encrypted_active_focus_id", {
|
|
16723
16799
|
chat_id: chatId,
|
|
16724
|
-
|
|
16725
|
-
message_id: transition.event_id,
|
|
16726
|
-
role: "system",
|
|
16727
|
-
status: "synced",
|
|
16728
|
-
encrypted_content: await encryptWithAesGcmCombined(content, key),
|
|
16729
|
-
created_at: transition.created_at
|
|
16730
|
-
}
|
|
16800
|
+
encrypted_active_focus_id: encryptedFocusId
|
|
16731
16801
|
});
|
|
16732
|
-
}
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
focusPersistenceError = error;
|
|
16802
|
+
}).catch((error) => {
|
|
16803
|
+
focusPersistenceError = error;
|
|
16804
|
+
});
|
|
16736
16805
|
});
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
|
|
16743
|
-
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16806
|
+
ws.onMessageType("focus_phases_updated", (event) => {
|
|
16807
|
+
if (event.chat_id !== chatId || !event.states || !chatKeyBytes || params.incognito) return;
|
|
16808
|
+
const key = chatKeyBytes;
|
|
16809
|
+
const states2 = event.states;
|
|
16810
|
+
focusPersistence = focusPersistence.then(async () => {
|
|
16811
|
+
for (const [focusId, phase] of Object.entries(states2)) {
|
|
16812
|
+
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) {
|
|
16813
|
+
throw new Error("Invalid focus phase state received");
|
|
16814
|
+
}
|
|
16815
|
+
if (focusPhaseState[focusId]?.run_id === phase.run_id && focusPhaseState[focusId].version > phase.version) states2[focusId] = focusPhaseState[focusId];
|
|
16816
|
+
}
|
|
16817
|
+
focusPhaseState = states2;
|
|
16818
|
+
const encryptedState = await encryptWithAesGcmCombined(JSON.stringify(states2), key);
|
|
16819
|
+
const persisted = ws.waitForMessage(
|
|
16820
|
+
"encrypted_metadata_stored",
|
|
16821
|
+
(payload) => payload.chat_id === chatId,
|
|
16822
|
+
2e4
|
|
16823
|
+
);
|
|
16824
|
+
await ws.sendAsync("encrypted_chat_metadata", {
|
|
16825
|
+
chat_id: chatId,
|
|
16826
|
+
...teamId ? { team_id: teamId } : {},
|
|
16827
|
+
encrypted_focus_phase_state: encryptedState
|
|
16828
|
+
});
|
|
16829
|
+
await persisted;
|
|
16830
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
16831
|
+
for (const phase of Object.values(states2)) for (const transition of phase.transitions) {
|
|
16832
|
+
if (transition.chat_id !== chatId || transition.focus_id !== phase.focus_id || !transition.event_id || eventIds.has(transition.event_id)) continue;
|
|
16833
|
+
eventIds.add(transition.event_id);
|
|
16834
|
+
const content = JSON.stringify(transition);
|
|
16835
|
+
await ws.sendAsync("chat_system_message_added", {
|
|
16836
|
+
chat_id: chatId,
|
|
16837
|
+
message: {
|
|
16838
|
+
message_id: transition.event_id,
|
|
16839
|
+
role: "system",
|
|
16840
|
+
status: "synced",
|
|
16841
|
+
encrypted_content: await encryptWithAesGcmCombined(content, key),
|
|
16842
|
+
created_at: transition.created_at
|
|
16843
|
+
}
|
|
16844
|
+
});
|
|
16845
|
+
}
|
|
16846
|
+
clearSyncCache(teamId);
|
|
16847
|
+
}).catch((error) => {
|
|
16848
|
+
focusPersistenceError = error;
|
|
16849
|
+
});
|
|
16750
16850
|
});
|
|
16751
|
-
clientCapabilities
|
|
16752
|
-
|
|
16753
|
-
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
|
|
16759
|
-
|
|
16760
|
-
|
|
16761
|
-
|
|
16762
|
-
|
|
16763
|
-
|
|
16764
|
-
|
|
16765
|
-
|
|
16766
|
-
|
|
16767
|
-
sender_name: "User",
|
|
16768
|
-
status: "sent",
|
|
16769
|
-
content: finalMessage,
|
|
16770
|
-
created_at: createdAt,
|
|
16771
|
-
chat_has_title: Boolean(params.chatId)
|
|
16772
|
-
}
|
|
16773
|
-
};
|
|
16774
|
-
const useCombinedAdmission = ws.supportsServerCapability?.("chat_turn_admit_v1") === true;
|
|
16775
|
-
const useDeferredContext = useCombinedAdmission && !params.chatId && !params.projectId && !teamId && ws.supportsServerCapability?.("chat_turn_context_v1") === true;
|
|
16776
|
-
const useSelectionDetailsV2 = useDeferredContext && sdkMintedChatId && ws.supportsServerCapability?.("chat_turn_context_selection_details_v2") === true;
|
|
16777
|
-
const contextRequestId = !params.incognito && shouldWaitForAi && useDeferredContext ? randomUUID9() : null;
|
|
16778
|
-
let pendingContext = null;
|
|
16779
|
-
if (!params.incognito && shouldWaitForAi) {
|
|
16780
|
-
if (contextRequestId) {
|
|
16781
|
-
messagePayload.client_context_pending = true;
|
|
16782
|
-
messagePayload.client_context_request_id = contextRequestId;
|
|
16851
|
+
const clientCapabilities = taskUpdateJobsEnabled ? ["task_update_jobs"] : [];
|
|
16852
|
+
if (!params.incognito && chatKeyBytes) {
|
|
16853
|
+
registerCliProjectFileExecutor({
|
|
16854
|
+
client: this,
|
|
16855
|
+
ws,
|
|
16856
|
+
chatId,
|
|
16857
|
+
chatKey: chatKeyBytes,
|
|
16858
|
+
piiMappings,
|
|
16859
|
+
memories: availableMemories,
|
|
16860
|
+
requestApproval: params.onProjectWriteApproval,
|
|
16861
|
+
requestReadApproval: params.onProjectReadApproval,
|
|
16862
|
+
onHostedVersionCommitted: params.onHostedVersionCommitted
|
|
16863
|
+
});
|
|
16864
|
+
clientCapabilities.push("project_file_jobs");
|
|
16865
|
+
registerRemoteCommandOriginClient({ ws, client: this, chatId, onReview: params.onRemoteCommandReview, onEvent: params.onRemoteCommandEvent });
|
|
16866
|
+
clientCapabilities.push("remote_command_jobs");
|
|
16783
16867
|
}
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
16793
|
-
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
|
|
16868
|
+
const messagePayload = {
|
|
16869
|
+
chat_id: chatId,
|
|
16870
|
+
...teamId ? { team_id: teamId } : {},
|
|
16871
|
+
client_capabilities: clientCapabilities,
|
|
16872
|
+
// Only decrypted current metadata restores focus; history is never authority.
|
|
16873
|
+
active_focus_id: activeFocusId,
|
|
16874
|
+
focus_phase_state: focusPhaseState,
|
|
16875
|
+
is_incognito: Boolean(params.incognito),
|
|
16876
|
+
message: {
|
|
16877
|
+
message_id: messageId2,
|
|
16878
|
+
chat_id: chatId,
|
|
16879
|
+
role: "user",
|
|
16880
|
+
sender_name: "User",
|
|
16881
|
+
status: "sent",
|
|
16882
|
+
content: finalMessage,
|
|
16883
|
+
created_at: createdAt,
|
|
16884
|
+
chat_has_title: Boolean(params.chatId)
|
|
16797
16885
|
}
|
|
16798
|
-
return deferredProjectDirectory ? { ...prepared.value, project_focus_candidates: prepared.value.project_focus_candidates?.filter((candidate) => candidate.auto_selection === true) } : prepared.value;
|
|
16799
|
-
})();
|
|
16800
|
-
void pendingContext.catch(() => {
|
|
16801
|
-
});
|
|
16802
|
-
}
|
|
16803
|
-
if (params.testMockMarker !== void 0) {
|
|
16804
|
-
const { capacityReplayMarker } = await import("./capacityReplayMarker-3NFJNDTZ.js");
|
|
16805
|
-
messagePayload.test_mock_marker = capacityReplayMarker(
|
|
16806
|
-
params.testMockMarker,
|
|
16807
|
-
this.apiUrl,
|
|
16808
|
-
process.env.OPENMATES_CAPACITY_CLIENT_REPLAY === "1"
|
|
16809
|
-
);
|
|
16810
|
-
}
|
|
16811
|
-
if (isNewChat && encryptedChatKey) {
|
|
16812
|
-
messagePayload.encrypted_chat_key = encryptedChatKey;
|
|
16813
|
-
}
|
|
16814
|
-
if (memoryMetadataKeys.length > 0) {
|
|
16815
|
-
messagePayload.app_settings_memories_metadata = memoryMetadataKeys;
|
|
16816
|
-
}
|
|
16817
|
-
if (connectedAccountDirectory) {
|
|
16818
|
-
messagePayload.connected_account_directory = connectedAccountDirectory;
|
|
16819
|
-
}
|
|
16820
|
-
if (connectedAccountTokenRefs.length > 0) {
|
|
16821
|
-
messagePayload.connected_account_token_refs = connectedAccountTokenRefs;
|
|
16822
|
-
}
|
|
16823
|
-
if (params.benchmarkMetadata) {
|
|
16824
|
-
messagePayload.benchmark_metadata = params.benchmarkMetadata;
|
|
16825
|
-
}
|
|
16826
|
-
if (params.learningMode) {
|
|
16827
|
-
messagePayload.learning_mode = {
|
|
16828
|
-
enabled: params.learningMode.enabled,
|
|
16829
|
-
age_group: params.learningMode.ageGroup ?? null
|
|
16830
16886
|
};
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
const
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
16838
|
-
|
|
16839
|
-
|
|
16840
|
-
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16859
|
-
|
|
16860
|
-
if (
|
|
16861
|
-
|
|
16887
|
+
const useCombinedAdmission = ws.supportsServerCapability?.("chat_turn_admit_v1") === true;
|
|
16888
|
+
const useDeferredContext = useCombinedAdmission && !params.chatId && !params.projectId && !teamId && ws.supportsServerCapability?.("chat_turn_context_v1") === true;
|
|
16889
|
+
const useSelectionDetailsV2 = useDeferredContext && sdkMintedChatId && ws.supportsServerCapability?.("chat_turn_context_selection_details_v2") === true;
|
|
16890
|
+
const contextRequestId = !params.incognito && shouldWaitForAi && useDeferredContext ? randomUUID9() : null;
|
|
16891
|
+
let pendingContext = null;
|
|
16892
|
+
if (!params.incognito && shouldWaitForAi) {
|
|
16893
|
+
if (contextRequestId) {
|
|
16894
|
+
messagePayload.client_context_pending = true;
|
|
16895
|
+
messagePayload.client_context_request_id = contextRequestId;
|
|
16896
|
+
}
|
|
16897
|
+
pendingContext = (async () => {
|
|
16898
|
+
const prepared = earlyContext ? await earlyContext : { ok: true, value: await withHttpAbortSignal(earlyContextAbort.signal, () => prepareCliJevContext(this, chatId, contextScope, suppliedContext, finalMessage, {
|
|
16899
|
+
sdkMintedChatId,
|
|
16900
|
+
onRouting: publishRouting,
|
|
16901
|
+
verifiedProjectCandidates: verifiedProjectCandidates ?? void 0,
|
|
16902
|
+
usePersonalRoutingDirectory,
|
|
16903
|
+
onProjectRouting: publishProjectRoutingTimed,
|
|
16904
|
+
onProjectRoutingError: rejectProjectRouting
|
|
16905
|
+
})), elapsedMs: Date.now() - contextStartedAt };
|
|
16906
|
+
if (!prepared.ok) throw prepared.error;
|
|
16907
|
+
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
16908
|
+
if (process.env.OPENMATES_DEBUG === "1") {
|
|
16909
|
+
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"}`);
|
|
16910
|
+
}
|
|
16911
|
+
return deferredProjectDirectory ? { ...prepared.value, project_focus_candidates: prepared.value.project_focus_candidates?.filter((candidate) => candidate.auto_selection === true) } : prepared.value;
|
|
16912
|
+
})();
|
|
16913
|
+
void pendingContext.catch(() => {
|
|
16914
|
+
});
|
|
16915
|
+
}
|
|
16916
|
+
if (params.testMockMarker !== void 0) {
|
|
16917
|
+
const { capacityReplayMarker } = await import("./capacityReplayMarker-3NFJNDTZ.js");
|
|
16918
|
+
messagePayload.test_mock_marker = capacityReplayMarker(
|
|
16919
|
+
params.testMockMarker,
|
|
16920
|
+
this.apiUrl,
|
|
16921
|
+
process.env.OPENMATES_CAPACITY_CLIENT_REPLAY === "1"
|
|
16922
|
+
);
|
|
16862
16923
|
}
|
|
16863
|
-
if (
|
|
16864
|
-
|
|
16924
|
+
if (isNewChat && encryptedChatKey) {
|
|
16925
|
+
messagePayload.encrypted_chat_key = encryptedChatKey;
|
|
16865
16926
|
}
|
|
16866
|
-
if (
|
|
16867
|
-
|
|
16927
|
+
if (memoryMetadataKeys.length > 0) {
|
|
16928
|
+
messagePayload.app_settings_memories_metadata = memoryMetadataKeys;
|
|
16868
16929
|
}
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
throw new Error("Prepared message embed ID is invalid.");
|
|
16930
|
+
if (connectedAccountDirectory) {
|
|
16931
|
+
messagePayload.connected_account_directory = connectedAccountDirectory;
|
|
16872
16932
|
}
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
const selectedIds = preparedIds.slice(offset, offset + 20);
|
|
16876
|
-
if (Buffer.byteLength(JSON.stringify({ embed_ids: selectedIds }), "utf8") > 4096) {
|
|
16877
|
-
throw new Error("Message embed availability request exceeds its byte limit.");
|
|
16878
|
-
}
|
|
16879
|
-
const path2 = this.appendTeamQuery(
|
|
16880
|
-
`/v1/embeds/chats/${encodeURIComponent(chatId)}/references/availability`,
|
|
16881
|
-
{ teamId }
|
|
16882
|
-
);
|
|
16883
|
-
const availability = await this.http.post(path2, { embed_ids: selectedIds }, this.getCliRequestHeaders());
|
|
16884
|
-
if (!availability.ok) {
|
|
16885
|
-
throw new Error(`Message embed availability lookup failed with HTTP ${availability.status}.`);
|
|
16886
|
-
}
|
|
16887
|
-
const page = classifyMessageEmbedAvailability(selectedIds, availability.data.results);
|
|
16888
|
-
for (const id of page.ready) canonicalReady.add(id);
|
|
16933
|
+
if (connectedAccountTokenRefs.length > 0) {
|
|
16934
|
+
messagePayload.connected_account_token_refs = connectedAccountTokenRefs;
|
|
16889
16935
|
}
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
16893
|
-
|
|
16894
|
-
|
|
16895
|
-
|
|
16896
|
-
|
|
16897
|
-
|
|
16898
|
-
|
|
16899
|
-
|
|
16936
|
+
if (params.benchmarkMetadata) {
|
|
16937
|
+
messagePayload.benchmark_metadata = params.benchmarkMetadata;
|
|
16938
|
+
}
|
|
16939
|
+
if (params.learningMode) {
|
|
16940
|
+
messagePayload.learning_mode = {
|
|
16941
|
+
enabled: params.learningMode.enabled,
|
|
16942
|
+
age_group: params.learningMode.ageGroup ?? null
|
|
16943
|
+
};
|
|
16944
|
+
}
|
|
16945
|
+
if (params.incognito) {
|
|
16946
|
+
const providedHistory = (params.messageHistory ?? []).map(
|
|
16947
|
+
(historyMessage) => buildInferenceHistoryMessage(historyMessage, historyMessage.chat_id ?? chatId)
|
|
16948
|
+
);
|
|
16949
|
+
messagePayload.message_history = [...providedHistory, {
|
|
16950
|
+
message_id: messageId2,
|
|
16951
|
+
chat_id: chatId,
|
|
16952
|
+
role: "user",
|
|
16953
|
+
sender_name: "User",
|
|
16954
|
+
content: finalMessage,
|
|
16955
|
+
created_at: createdAt
|
|
16956
|
+
}];
|
|
16957
|
+
} else if (messageHistoryForRequest && messageHistoryForRequest.length > 0) {
|
|
16958
|
+
messagePayload.message_history = messageHistoryForRequest.map(
|
|
16959
|
+
(historyMessage) => buildInferenceHistoryMessage(historyMessage, chatId)
|
|
16900
16960
|
);
|
|
16901
|
-
if (!encrypted?.encrypted_content || !encrypted.encrypted_type || encrypted.embed_keys.length !== 2) {
|
|
16902
|
-
throw new Error(`Prepared message embed ${embed.embedId} is missing required ciphertext or key wrappers.`);
|
|
16903
|
-
}
|
|
16904
|
-
encryptedEmbeds.push(encrypted);
|
|
16905
16961
|
}
|
|
16906
|
-
|
|
16907
|
-
|
|
16908
|
-
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
|
|
16917
|
-
|
|
16918
|
-
|
|
16919
|
-
|
|
16920
|
-
|
|
16921
|
-
|
|
16922
|
-
|
|
16923
|
-
|
|
16924
|
-
|
|
16925
|
-
|
|
16926
|
-
|
|
16927
|
-
|
|
16928
|
-
|
|
16929
|
-
|
|
16930
|
-
|
|
16931
|
-
|
|
16932
|
-
|
|
16933
|
-
|
|
16934
|
-
|
|
16935
|
-
const encryptPayloadStartedAt = performance.now();
|
|
16936
|
-
const [encryptedContent, encryptedSenderName, encryptedPiiMappings] = await Promise.all([
|
|
16937
|
-
encryptWithAesGcmCombined(finalMessage, chatKeyBytes),
|
|
16938
|
-
encryptWithAesGcmCombined(teamSenderName, chatKeyBytes),
|
|
16939
|
-
piiMappings.length ? encryptWithAesGcmCombined(JSON.stringify(piiMappings), chatKeyBytes) : null
|
|
16940
|
-
]);
|
|
16941
|
-
const encryptedUserMessage = {
|
|
16942
|
-
client_message_id: messageId2,
|
|
16943
|
-
chat_id: chatId,
|
|
16944
|
-
encrypted_content: encryptedContent,
|
|
16945
|
-
role: "user",
|
|
16946
|
-
created_at: createdAt,
|
|
16947
|
-
updated_at: createdAt
|
|
16948
|
-
};
|
|
16949
|
-
encryptedUserMessage.encrypted_sender_name = encryptedSenderName;
|
|
16950
|
-
if (encryptedPiiMappings) encryptedUserMessage.encrypted_pii_mappings = encryptedPiiMappings;
|
|
16951
|
-
Object.assign(messagePayload, {
|
|
16952
|
-
turn_id: turnId,
|
|
16953
|
-
recovery_public_key: recoveryKeypair.publicKey,
|
|
16954
|
-
chat_key_version: chatKeyVersion
|
|
16955
|
-
});
|
|
16956
|
-
let preflightInferenceRequest = messagePayload;
|
|
16957
|
-
if (teamId) {
|
|
16958
|
-
const teamInferenceHistory = [
|
|
16959
|
-
...(messageHistoryForRequest ?? []).map((historyMessage) => ({
|
|
16960
|
-
role: historyMessage.role,
|
|
16961
|
-
content: historyMessage.content,
|
|
16962
|
-
category: historyMessage.category ?? null,
|
|
16963
|
-
sender_name: historyMessage.sender_name ?? historyMessage.role,
|
|
16964
|
-
created_at: historyMessage.created_at
|
|
16965
|
-
})),
|
|
16966
|
-
{
|
|
16967
|
-
role: "user",
|
|
16968
|
-
content: finalMessage,
|
|
16969
|
-
category: null,
|
|
16970
|
-
sender_name: teamSenderName,
|
|
16971
|
-
created_at: createdAt
|
|
16962
|
+
if (params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
16963
|
+
messagePayload.embeds = params.preparedEmbeds.map((embed) => ({
|
|
16964
|
+
embed_id: embed.embedId,
|
|
16965
|
+
type: embed.type,
|
|
16966
|
+
content: embed.content,
|
|
16967
|
+
status: embed.status,
|
|
16968
|
+
text_preview: embed.textPreview
|
|
16969
|
+
}));
|
|
16970
|
+
}
|
|
16971
|
+
const encryptedEmbeds = [...params.encryptedEmbeds ?? []];
|
|
16972
|
+
if (!params.incognito && params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
16973
|
+
if (!ownerId2) {
|
|
16974
|
+
throw new Error("Authenticated user identity is required to encrypt embeds.");
|
|
16975
|
+
}
|
|
16976
|
+
if (!chatKeyBytes) {
|
|
16977
|
+
throw new Error("Saved chat key is required to encrypt every prepared embed.");
|
|
16978
|
+
}
|
|
16979
|
+
if (new Set(params.preparedEmbeds.map((embed) => embed.embedId)).size !== params.preparedEmbeds.length) {
|
|
16980
|
+
throw new Error("Prepared message embed IDs must be unique.");
|
|
16981
|
+
}
|
|
16982
|
+
const preparedIds = params.preparedEmbeds.map((embed) => embed.embedId);
|
|
16983
|
+
if (preparedIds.some((id) => !id || id.length > 128)) {
|
|
16984
|
+
throw new Error("Prepared message embed ID is invalid.");
|
|
16985
|
+
}
|
|
16986
|
+
const canonicalReady = /* @__PURE__ */ new Set();
|
|
16987
|
+
for (let offset = 0; offset < preparedIds.length; offset += 20) {
|
|
16988
|
+
const selectedIds = preparedIds.slice(offset, offset + 20);
|
|
16989
|
+
if (Buffer.byteLength(JSON.stringify({ embed_ids: selectedIds }), "utf8") > 4096) {
|
|
16990
|
+
throw new Error("Message embed availability request exceeds its byte limit.");
|
|
16972
16991
|
}
|
|
16973
|
-
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
16977
|
-
|
|
16978
|
-
|
|
16979
|
-
|
|
16980
|
-
|
|
16981
|
-
|
|
16982
|
-
|
|
16992
|
+
const path2 = this.appendTeamQuery(
|
|
16993
|
+
`/v1/embeds/chats/${encodeURIComponent(chatId)}/references/availability`,
|
|
16994
|
+
{ teamId }
|
|
16995
|
+
);
|
|
16996
|
+
const availability = await this.http.post(path2, { embed_ids: selectedIds }, this.getCliRequestHeaders());
|
|
16997
|
+
if (!availability.ok) {
|
|
16998
|
+
throw new Error(`Message embed availability lookup failed with HTTP ${availability.status}.`);
|
|
16999
|
+
}
|
|
17000
|
+
const page = classifyMessageEmbedAvailability(selectedIds, availability.data.results);
|
|
17001
|
+
for (const id of page.ready) canonicalReady.add(id);
|
|
17002
|
+
}
|
|
17003
|
+
const masterKey = this.getMasterKeyBytes();
|
|
17004
|
+
for (const embed of params.preparedEmbeds) {
|
|
17005
|
+
if (canonicalReady.has(embed.embedId)) continue;
|
|
17006
|
+
const encrypted = await encryptEmbed(
|
|
17007
|
+
embed,
|
|
17008
|
+
masterKey,
|
|
17009
|
+
chatKeyBytes,
|
|
17010
|
+
chatId,
|
|
17011
|
+
messageId2,
|
|
17012
|
+
ownerId2
|
|
17013
|
+
);
|
|
17014
|
+
if (!encrypted?.encrypted_content || !encrypted.encrypted_type || encrypted.embed_keys.length !== 2) {
|
|
17015
|
+
throw new Error(`Prepared message embed ${embed.embedId} is missing required ciphertext or key wrappers.`);
|
|
17016
|
+
}
|
|
17017
|
+
encryptedEmbeds.push(encrypted);
|
|
16983
17018
|
}
|
|
16984
17019
|
}
|
|
16985
|
-
|
|
16986
|
-
|
|
16987
|
-
|
|
16988
|
-
|
|
16989
|
-
|
|
16990
|
-
|
|
16991
|
-
|
|
16992
|
-
|
|
16993
|
-
recovery_public_key: recoveryKeypair.publicKey,
|
|
16994
|
-
expected_messages_v: baselineMessagesV,
|
|
16995
|
-
encrypted_user_message: encryptedUserMessage,
|
|
16996
|
-
inference_request: preflightInferenceRequest,
|
|
16997
|
-
...conditionalPrivacyReceipt ? {
|
|
16998
|
-
privacy_receipt_id: conditionalPrivacyReceipt.receiptId,
|
|
16999
|
-
privacy_generation: conditionalPrivacyReceipt.generation
|
|
17000
|
-
} : {}
|
|
17020
|
+
if (!params.incognito && encryptedEmbeds.length > 0) {
|
|
17021
|
+
if (!ownerId2) throw new Error("Authenticated owner identity is required for encrypted message embeds.");
|
|
17022
|
+
assertCompleteEncryptedEmbedBundle(encryptedEmbeds, { chatId, messageId: messageId2, ownerId: ownerId2 });
|
|
17023
|
+
}
|
|
17024
|
+
if (encryptedEmbeds.length > 0) {
|
|
17025
|
+
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
17026
|
+
}
|
|
17027
|
+
let dispatchHandlersReady = () => {
|
|
17001
17028
|
};
|
|
17002
|
-
|
|
17003
|
-
|
|
17004
|
-
|
|
17005
|
-
|
|
17006
|
-
|
|
17007
|
-
|
|
17008
|
-
|
|
17009
|
-
|
|
17010
|
-
|
|
17011
|
-
|
|
17012
|
-
|
|
17013
|
-
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
17029
|
+
const handlersReady = new Promise((resolve20) => {
|
|
17030
|
+
dispatchHandlersReady = resolve20;
|
|
17031
|
+
});
|
|
17032
|
+
let admissionPayload = null;
|
|
17033
|
+
let precollectedResponse = params.precollectResponse && params.incognito && shouldWaitForAi ? ws.collectAiResponse(messageId2, chatId, { onStream: params.onStream, timeoutMs: params.responseTimeoutMs }) : null;
|
|
17034
|
+
if (!params.incognito && chatKeyBytes && encryptedChatKey) {
|
|
17035
|
+
traceSendTiming("preflight_begin");
|
|
17036
|
+
const protocolVersion = 1;
|
|
17037
|
+
const chatKeyVersion = 1;
|
|
17038
|
+
const turnId = privacyTurnId;
|
|
17039
|
+
savedTurnId = turnId;
|
|
17040
|
+
terminalExpectedMessagesV = baselineMessagesV + 1;
|
|
17041
|
+
const recoveryKeypairStartedAt = performance.now();
|
|
17042
|
+
const recoveryKeypair = independentRecoveryKeypair ? await independentRecoveryKeypair : await deriveChatCompletionRecoveryKeypair(
|
|
17043
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
17044
|
+
chatId,
|
|
17045
|
+
chatKeyVersion
|
|
17046
|
+
);
|
|
17047
|
+
recoveryKeypairMs = Math.round(performance.now() - recoveryKeypairStartedAt);
|
|
17048
|
+
const encryptPayloadStartedAt = performance.now();
|
|
17049
|
+
const [encryptedContent, encryptedSenderName, encryptedPiiMappings] = await Promise.all([
|
|
17050
|
+
encryptWithAesGcmCombined(finalMessage, chatKeyBytes),
|
|
17051
|
+
encryptWithAesGcmCombined(teamSenderName, chatKeyBytes),
|
|
17052
|
+
piiMappings.length ? encryptWithAesGcmCombined(JSON.stringify(piiMappings), chatKeyBytes) : null
|
|
17053
|
+
]);
|
|
17054
|
+
const encryptedUserMessage = {
|
|
17055
|
+
client_message_id: messageId2,
|
|
17056
|
+
chat_id: chatId,
|
|
17057
|
+
encrypted_content: encryptedContent,
|
|
17058
|
+
role: "user",
|
|
17021
17059
|
created_at: createdAt,
|
|
17022
17060
|
updated_at: createdAt
|
|
17023
17061
|
};
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17062
|
+
encryptedUserMessage.encrypted_sender_name = encryptedSenderName;
|
|
17063
|
+
if (encryptedPiiMappings) encryptedUserMessage.encrypted_pii_mappings = encryptedPiiMappings;
|
|
17064
|
+
Object.assign(messagePayload, {
|
|
17065
|
+
turn_id: turnId,
|
|
17066
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
17067
|
+
chat_key_version: chatKeyVersion
|
|
17068
|
+
});
|
|
17069
|
+
let preflightInferenceRequest = messagePayload;
|
|
17027
17070
|
if (teamId) {
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
|
|
17071
|
+
const teamInferenceHistory = [
|
|
17072
|
+
...(messageHistoryForRequest ?? []).map((historyMessage) => ({
|
|
17073
|
+
role: historyMessage.role,
|
|
17074
|
+
content: historyMessage.content,
|
|
17075
|
+
category: historyMessage.category ?? null,
|
|
17076
|
+
sender_name: historyMessage.sender_name ?? historyMessage.role,
|
|
17077
|
+
created_at: historyMessage.created_at
|
|
17078
|
+
})),
|
|
17079
|
+
{
|
|
17080
|
+
role: "user",
|
|
17081
|
+
content: finalMessage,
|
|
17082
|
+
category: null,
|
|
17083
|
+
sender_name: teamSenderName,
|
|
17084
|
+
created_at: createdAt
|
|
17085
|
+
}
|
|
17086
|
+
];
|
|
17087
|
+
const teamMessageEnvelope = { ...encryptedUserMessage, message_id: messageId2 };
|
|
17088
|
+
delete messagePayload.message_history;
|
|
17089
|
+
messagePayload.message = teamMessageEnvelope;
|
|
17090
|
+
if (shouldWaitForAi) {
|
|
17091
|
+
messagePayload.team_ai_invocation = { history: teamInferenceHistory };
|
|
17092
|
+
preflightInferenceRequest = {
|
|
17093
|
+
...messagePayload,
|
|
17094
|
+
message_history: teamInferenceHistory
|
|
17095
|
+
};
|
|
17096
|
+
}
|
|
17097
|
+
}
|
|
17098
|
+
const preflightPayload = {
|
|
17099
|
+
protocol_version: protocolVersion,
|
|
17100
|
+
chat_id: chatId,
|
|
17101
|
+
...teamId ? { team_id: teamId } : {},
|
|
17102
|
+
turn_id: turnId,
|
|
17103
|
+
message_id: messageId2,
|
|
17104
|
+
chat_key_version: chatKeyVersion,
|
|
17105
|
+
encrypted_chat_key: encryptedChatKey,
|
|
17106
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
17107
|
+
expected_messages_v: baselineMessagesV,
|
|
17108
|
+
encrypted_user_message: encryptedUserMessage,
|
|
17109
|
+
inference_request: preflightInferenceRequest,
|
|
17110
|
+
...conditionalPrivacyReceipt ? {
|
|
17111
|
+
privacy_receipt_id: conditionalPrivacyReceipt.receiptId,
|
|
17112
|
+
privacy_generation: conditionalPrivacyReceipt.generation
|
|
17113
|
+
} : {}
|
|
17114
|
+
};
|
|
17115
|
+
if (isNewChat) {
|
|
17116
|
+
if (!chatSlugLookupKey) {
|
|
17117
|
+
throw new Error("Encrypted chat slug lookup key was not initialized.");
|
|
17118
|
+
}
|
|
17119
|
+
const initialTitle = teamId && !shouldWaitForAi ? teamChatTitleFromMessage(params.message) : "";
|
|
17120
|
+
const slugMetadata = await buildEncryptedObjectSlugMetadata({
|
|
17121
|
+
value: selectNewChatSlugValue({
|
|
17122
|
+
message: finalMessage,
|
|
17123
|
+
chatId,
|
|
17124
|
+
explicitSlug: params.slug
|
|
17125
|
+
}),
|
|
17126
|
+
encryptionKey: chatKeyBytes,
|
|
17127
|
+
lookupKey: chatSlugLookupKey
|
|
17034
17128
|
});
|
|
17129
|
+
preflightPayload.encrypted_chat_metadata = {
|
|
17130
|
+
encrypted_title: await encryptWithAesGcmCombined(initialTitle, chatKeyBytes),
|
|
17131
|
+
encrypted_slug: slugMetadata.encrypted_slug,
|
|
17132
|
+
slug_lookup_hash: slugMetadata.slug_lookup_hash,
|
|
17133
|
+
encrypted_chat_key: encryptedChatKey,
|
|
17134
|
+
created_at: createdAt,
|
|
17135
|
+
updated_at: createdAt
|
|
17136
|
+
};
|
|
17035
17137
|
}
|
|
17036
|
-
|
|
17037
|
-
|
|
17138
|
+
encryptPayloadMs = Math.round(performance.now() - encryptPayloadStartedAt);
|
|
17139
|
+
try {
|
|
17140
|
+
if (teamId) {
|
|
17141
|
+
await stageCliTeamNotificationPreview(ws, {
|
|
17142
|
+
teamId,
|
|
17143
|
+
chatId,
|
|
17144
|
+
messageId: messageId2,
|
|
17145
|
+
content: finalMessage,
|
|
17146
|
+
title: notificationTitle
|
|
17147
|
+
});
|
|
17148
|
+
}
|
|
17149
|
+
if (params.projectId) {
|
|
17150
|
+
await prepareCliProjectFocusForPreflight({
|
|
17151
|
+
ws,
|
|
17152
|
+
chatId,
|
|
17153
|
+
teamId,
|
|
17154
|
+
isNewChat,
|
|
17155
|
+
encryptedChatKey,
|
|
17156
|
+
createdAt,
|
|
17157
|
+
activateFocus: () => activateCliProjectFocus(this, params.projectId, chatId, teamId)
|
|
17158
|
+
});
|
|
17159
|
+
const focusedContext = await prepareCliJevContext(
|
|
17160
|
+
this,
|
|
17161
|
+
chatId,
|
|
17162
|
+
{ teamId, personal: !teamId },
|
|
17163
|
+
params.jevContext,
|
|
17164
|
+
finalMessage
|
|
17165
|
+
);
|
|
17166
|
+
pendingContext = Promise.resolve(focusedContext);
|
|
17167
|
+
}
|
|
17168
|
+
admissionPayload = preflightPayload;
|
|
17169
|
+
traceSendTiming("preflight_ready");
|
|
17170
|
+
} catch (error) {
|
|
17171
|
+
ws.close();
|
|
17172
|
+
throw error;
|
|
17173
|
+
}
|
|
17174
|
+
}
|
|
17175
|
+
const initialHeaderOwner = this.preparedChatOwner(this.requireSession(), teamId);
|
|
17176
|
+
const startInitialHeader = () => {
|
|
17177
|
+
if (!initialAdmissionDurable || !initialHeaderNotice || !chatKeyBytes || !isNewChat || params.incognito || !savedTurnId) return;
|
|
17178
|
+
const notice = { ...initialHeaderNotice }, key = chatKeyBytes, turn = savedTurnId;
|
|
17179
|
+
const titleChanged = !!notice.chatTitle && notice.chatTitle !== initialTitleScheduled;
|
|
17180
|
+
const iconChanged = !!notice.chatIcon && notice.chatIcon !== initialIconScheduled;
|
|
17181
|
+
if (!titleChanged && !iconChanged) return;
|
|
17182
|
+
initialTitleScheduled = notice.chatTitle ?? initialTitleScheduled;
|
|
17183
|
+
initialIconScheduled = notice.chatIcon ?? initialIconScheduled;
|
|
17184
|
+
const current2 = () => !initialHeaderStopped && ws.isOpen() && this.preparedChatOwnerCurrent(initialHeaderOwner, teamId) && this.activeSubmittedChatTurns.get(chatId) === turn;
|
|
17185
|
+
initialHeaderWork = (initialHeaderWork ?? Promise.resolve()).then(async () => {
|
|
17186
|
+
if (!current2()) return;
|
|
17187
|
+
if (titleChanged) await this.persistPostProcessingMetadata({
|
|
17038
17188
|
ws,
|
|
17039
17189
|
chatId,
|
|
17040
17190
|
teamId,
|
|
17041
|
-
|
|
17191
|
+
chatKeyBytes: key,
|
|
17042
17192
|
encryptedChatKey,
|
|
17043
|
-
|
|
17044
|
-
|
|
17193
|
+
followUpSuggestions: [],
|
|
17194
|
+
suggestedFollowupMessage: null,
|
|
17195
|
+
newChatSuggestions: [],
|
|
17196
|
+
chatSummary: null,
|
|
17197
|
+
chatTags: [],
|
|
17198
|
+
updatedChatTitle: notice.chatTitle ?? null,
|
|
17199
|
+
sourceTitleVersion: notice.sourceTitleVersion ?? null,
|
|
17200
|
+
sourceMetadataVersion: notice.sourceMetadataVersion ?? null,
|
|
17201
|
+
unversionedGeneratedTitle: notice.sourceTitleVersion === void 0 && notice.sourceMetadataVersion === void 0,
|
|
17202
|
+
ownerCurrent: current2
|
|
17045
17203
|
});
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
|
|
17051
|
-
|
|
17052
|
-
|
|
17053
|
-
|
|
17204
|
+
if (!current2()) return;
|
|
17205
|
+
if (iconChanged || notice.category) {
|
|
17206
|
+
const [encryptedIcon, encryptedCategory] = await Promise.all([
|
|
17207
|
+
notice.chatIcon ? encryptWithAesGcmCombined(notice.chatIcon, key) : null,
|
|
17208
|
+
notice.category ? encryptWithAesGcmCombined(notice.category, key) : null
|
|
17209
|
+
]);
|
|
17210
|
+
if (!current2()) return;
|
|
17211
|
+
const stored = ws.waitForMessage("encrypted_metadata_stored", (value) => value.chat_id === chatId, 2e4);
|
|
17212
|
+
void stored.catch(() => {
|
|
17213
|
+
});
|
|
17214
|
+
await ws.sendAsync("encrypted_chat_metadata", {
|
|
17215
|
+
chat_id: chatId,
|
|
17216
|
+
...teamId ? { team_id: teamId } : {},
|
|
17217
|
+
encrypted_chat_key: encryptedChatKey,
|
|
17218
|
+
...encryptedIcon ? { encrypted_icon: encryptedIcon } : {},
|
|
17219
|
+
...encryptedCategory ? { encrypted_chat_category: encryptedCategory } : {}
|
|
17220
|
+
});
|
|
17221
|
+
await stored;
|
|
17222
|
+
}
|
|
17223
|
+
if (current2()) clearSyncCache(teamId);
|
|
17224
|
+
}).catch(() => {
|
|
17225
|
+
});
|
|
17226
|
+
};
|
|
17227
|
+
const onTurnStream = (event) => {
|
|
17228
|
+
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)) {
|
|
17229
|
+
initialHeaderNotice = { ...initialHeaderNotice, ...event };
|
|
17230
|
+
startInitialHeader();
|
|
17054
17231
|
}
|
|
17055
|
-
|
|
17056
|
-
|
|
17057
|
-
|
|
17058
|
-
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
17062
|
-
|
|
17063
|
-
|
|
17064
|
-
if (!initialAdmissionDurable || !initialHeaderNotice || !chatKeyBytes || !isNewChat || params.incognito || !savedTurnId) return;
|
|
17065
|
-
const notice = { ...initialHeaderNotice }, key = chatKeyBytes, turn = savedTurnId;
|
|
17066
|
-
const titleChanged = !!notice.chatTitle && notice.chatTitle !== initialTitleScheduled;
|
|
17067
|
-
const iconChanged = !!notice.chatIcon && notice.chatIcon !== initialIconScheduled;
|
|
17068
|
-
if (!titleChanged && !iconChanged) return;
|
|
17069
|
-
initialTitleScheduled = notice.chatTitle ?? initialTitleScheduled;
|
|
17070
|
-
initialIconScheduled = notice.chatIcon ?? initialIconScheduled;
|
|
17071
|
-
const current2 = () => !initialHeaderStopped && ws.isOpen() && this.preparedChatOwnerCurrent(initialHeaderOwner, teamId) && this.activeSubmittedChatTurns.get(chatId) === turn;
|
|
17072
|
-
initialHeaderWork = (initialHeaderWork ?? Promise.resolve()).then(async () => {
|
|
17073
|
-
if (!current2()) return;
|
|
17074
|
-
if (titleChanged) await this.persistPostProcessingMetadata({
|
|
17232
|
+
params.onStream?.(event);
|
|
17233
|
+
};
|
|
17234
|
+
if (!params.incognito && chatKeyBytes && savedTurnId) {
|
|
17235
|
+
const key = chatKeyBytes;
|
|
17236
|
+
const ownedTurnId = savedTurnId;
|
|
17237
|
+
this.activeSubmittedChatTurns.set(chatId, ownedTurnId);
|
|
17238
|
+
const owner2 = this.preparedChatOwner(this.requireSession(), teamId);
|
|
17239
|
+
const stopOwned = () => optionalMetadata?.stop();
|
|
17240
|
+
optionalMetadata = observeOwnedTurnMetadata({
|
|
17075
17241
|
ws,
|
|
17076
17242
|
chatId,
|
|
17077
|
-
|
|
17078
|
-
|
|
17079
|
-
|
|
17080
|
-
|
|
17081
|
-
|
|
17082
|
-
|
|
17083
|
-
|
|
17084
|
-
|
|
17085
|
-
|
|
17086
|
-
|
|
17087
|
-
|
|
17088
|
-
|
|
17089
|
-
|
|
17243
|
+
turnId: savedTurnId,
|
|
17244
|
+
userMessageId: messageId2,
|
|
17245
|
+
closeAfterMetadata: true,
|
|
17246
|
+
ownerCurrent: () => this.preparedChatOwnerCurrent(owner2, teamId),
|
|
17247
|
+
persist: async (metadata) => {
|
|
17248
|
+
await this.persistPostProcessingMetadata({ ws, teamId, chatKeyBytes: key, encryptedChatKey, ...metadata });
|
|
17249
|
+
clearSyncCache(teamId);
|
|
17250
|
+
},
|
|
17251
|
+
onMetadata: params.onMetadata,
|
|
17252
|
+
onReleased: () => {
|
|
17253
|
+
this.ownedMetadataSubscriptions.delete(stopOwned);
|
|
17254
|
+
if (this.activeSubmittedChatTurns.get(chatId) === ownedTurnId) this.activeSubmittedChatTurns.delete(chatId);
|
|
17255
|
+
if (metadataTransportOwned) ws.close();
|
|
17256
|
+
}
|
|
17090
17257
|
});
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
const [encryptedIcon, encryptedCategory] = await Promise.all([
|
|
17094
|
-
notice.chatIcon ? encryptWithAesGcmCombined(notice.chatIcon, key) : null,
|
|
17095
|
-
notice.category ? encryptWithAesGcmCombined(notice.category, key) : null
|
|
17096
|
-
]);
|
|
17097
|
-
if (!current2()) return;
|
|
17098
|
-
const stored = ws.waitForMessage("encrypted_metadata_stored", (value) => value.chat_id === chatId, 2e4);
|
|
17099
|
-
void stored.catch(() => {
|
|
17100
|
-
});
|
|
17101
|
-
await ws.sendAsync("encrypted_chat_metadata", {
|
|
17102
|
-
chat_id: chatId,
|
|
17103
|
-
...teamId ? { team_id: teamId } : {},
|
|
17104
|
-
encrypted_chat_key: encryptedChatKey,
|
|
17105
|
-
...encryptedIcon ? { encrypted_icon: encryptedIcon } : {},
|
|
17106
|
-
...encryptedCategory ? { encrypted_chat_category: encryptedCategory } : {}
|
|
17107
|
-
});
|
|
17108
|
-
await stored;
|
|
17109
|
-
}
|
|
17110
|
-
if (current2()) clearSyncCache(teamId);
|
|
17111
|
-
}).catch(() => {
|
|
17112
|
-
});
|
|
17113
|
-
};
|
|
17114
|
-
const onTurnStream = (event) => {
|
|
17115
|
-
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)) {
|
|
17116
|
-
initialHeaderNotice = { ...initialHeaderNotice, ...event };
|
|
17117
|
-
startInitialHeader();
|
|
17258
|
+
while (this.ownedMetadataSubscriptions.size >= 8) this.ownedMetadataSubscriptions.values().next().value?.();
|
|
17259
|
+
this.ownedMetadataSubscriptions.add(stopOwned);
|
|
17118
17260
|
}
|
|
17119
|
-
params.
|
|
17120
|
-
|
|
17121
|
-
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
if (metadataTransportOwned) ws.close();
|
|
17143
|
-
}
|
|
17261
|
+
if (!params.incognito && shouldWaitForAi) {
|
|
17262
|
+
precollectedResponse = ws.collectAiResponse(messageId2, chatId, {
|
|
17263
|
+
waitForPostProcessing: false,
|
|
17264
|
+
onStream: onTurnStream,
|
|
17265
|
+
onSubChatEvent: async (event) => {
|
|
17266
|
+
await handlersReady;
|
|
17267
|
+
await handleSubChatEvent(event);
|
|
17268
|
+
},
|
|
17269
|
+
onAppSettingsMemoriesRequest: async (event) => {
|
|
17270
|
+
await handlersReady;
|
|
17271
|
+
await handleAppSettingsMemoriesRequest(event);
|
|
17272
|
+
},
|
|
17273
|
+
timeoutMs: params.responseTimeoutMs,
|
|
17274
|
+
recoveryTurnId: savedTurnId,
|
|
17275
|
+
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
17276
|
+
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
17277
|
+
});
|
|
17278
|
+
}
|
|
17279
|
+
const queuedAdmission = !params.incognito && shouldWaitForAi ? ws.waitForMessage("message_queued", (payload) => {
|
|
17280
|
+
const event = payload;
|
|
17281
|
+
return event.chat_id === chatId && event.user_message_id === messageId2;
|
|
17282
|
+
}, params.responseTimeoutMs ?? 9e4) : null;
|
|
17283
|
+
if (queuedAdmission) void queuedAdmission.catch(() => {
|
|
17144
17284
|
});
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
waitForPostProcessing: false,
|
|
17151
|
-
onStream: onTurnStream,
|
|
17152
|
-
onSubChatEvent: async (event) => {
|
|
17153
|
-
await handlersReady;
|
|
17154
|
-
await handleSubChatEvent(event);
|
|
17155
|
-
},
|
|
17156
|
-
onAppSettingsMemoriesRequest: async (event) => {
|
|
17157
|
-
await handlersReady;
|
|
17158
|
-
await handleAppSettingsMemoriesRequest(event);
|
|
17159
|
-
},
|
|
17160
|
-
timeoutMs: params.responseTimeoutMs,
|
|
17161
|
-
recoveryTurnId: savedTurnId,
|
|
17162
|
-
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
17163
|
-
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
17285
|
+
const pausedHandoff = !params.incognito && shouldWaitForAi ? ws.waitForMessage("queued_handoff_paused", (payload) => {
|
|
17286
|
+
const event = payload;
|
|
17287
|
+
return event.chat_id === chatId && (event.message_id === messageId2 || event.message_id === void 0);
|
|
17288
|
+
}, params.responseTimeoutMs ?? 9e4) : null;
|
|
17289
|
+
if (pausedHandoff) void pausedHandoff.catch(() => {
|
|
17164
17290
|
});
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
|
|
17170
|
-
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
|
|
17176
|
-
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
let admissionContinuationPhase = "before_dispatch";
|
|
17188
|
-
let admissionAckObserved = false;
|
|
17189
|
-
let routingFrameSent = false;
|
|
17190
|
-
let detailsFrameSent = false;
|
|
17191
|
-
const admissionContinuationStartedAt = performance.now();
|
|
17192
|
-
try {
|
|
17193
|
-
if (admissionPayload) {
|
|
17194
|
-
if (!useDeferredContext && recoveryDiscovery) await recoveryDiscovery;
|
|
17195
|
-
if (recoveryDiscoveryError) throw recoveryDiscoveryError;
|
|
17196
|
-
if (!contextRequestId && pendingContext) {
|
|
17197
|
-
const context = await pendingContext;
|
|
17198
|
-
Object.assign(messagePayload, context);
|
|
17199
|
-
Object.assign(admissionPayload.inference_request, context);
|
|
17200
|
-
}
|
|
17201
|
-
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17202
|
-
const admissionAck = ws.waitForMessage("chat_turn_preflight_ack", (payload) => payload.turn_id === savedTurnId);
|
|
17203
|
-
void admissionAck.catch(() => {
|
|
17204
|
-
});
|
|
17205
|
-
const frameDispatchAt = performance.now();
|
|
17206
|
-
admissionContinuationPhase = "await_ack";
|
|
17207
|
-
await consumeSocketMemoryReceipt();
|
|
17208
|
-
await ws.sendAsync(useCombinedAdmission ? "chat_turn_admit" : "chat_turn_preflight", admissionPayload);
|
|
17209
|
-
traceSendTiming("admission_frame");
|
|
17210
|
-
if (process.env.OPENMATES_DEBUG === "1") {
|
|
17211
|
-
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)}`);
|
|
17212
|
-
}
|
|
17213
|
-
const ackPayload = (await admissionAck).payload;
|
|
17214
|
-
traceSendTiming("admission_ack");
|
|
17215
|
-
admissionAckObserved = true;
|
|
17216
|
-
if (typeof ackPayload.preflight_id !== "string" || !ackPayload.preflight_id) {
|
|
17217
|
-
throw new Error("Encrypted chat admission acknowledgement omitted preflight_id.");
|
|
17218
|
-
}
|
|
17219
|
-
if (typeof ackPayload.committed_messages_v === "number" && Number.isSafeInteger(ackPayload.committed_messages_v)) {
|
|
17220
|
-
terminalExpectedMessagesV = ackPayload.committed_messages_v;
|
|
17221
|
-
}
|
|
17222
|
-
initialAdmissionDurable = true;
|
|
17223
|
-
startInitialHeader();
|
|
17224
|
-
if (!useCombinedAdmission) {
|
|
17225
|
-
Object.assign(messagePayload, { protocol_version: admissionPayload.protocol_version, preflight_id: ackPayload.preflight_id });
|
|
17226
|
-
await ws.sendAsync("chat_message_added", messagePayload);
|
|
17227
|
-
}
|
|
17228
|
-
if (pendingContext && contextRequestId) {
|
|
17229
|
-
admissionContinuationPhase = "await_routing";
|
|
17230
|
-
const identity3 = {
|
|
17231
|
-
chat_id: chatId,
|
|
17232
|
-
turn_id: savedTurnId,
|
|
17233
|
-
message_id: messageId2,
|
|
17234
|
-
request_id: contextRequestId,
|
|
17235
|
-
...teamId ? { team_id: teamId } : {}
|
|
17236
|
-
};
|
|
17237
|
-
let failureNotice = null;
|
|
17238
|
-
const notifyContextFailure = (reason) => {
|
|
17239
|
-
if (failureNotice) return failureNotice;
|
|
17240
|
-
if (!ws.supportsServerCapability("chat_turn_context_failure_v1") || !ws.isOpen() || !contextOwnerIsCurrent() || !savedTurnId) return Promise.resolve();
|
|
17241
|
-
failureNotice = (async () => {
|
|
17242
|
-
const failureAck = ws.waitForMessage("chat_turn_context_failed_ack", (payload) => {
|
|
17243
|
-
const event = payload;
|
|
17244
|
-
return event.chat_id === chatId && event.turn_id === savedTurnId && event.message_id === messageId2 && event.request_id === contextRequestId;
|
|
17245
|
-
}, 5e3);
|
|
17246
|
-
void failureAck.catch(() => {
|
|
17247
|
-
});
|
|
17248
|
-
await ws.sendAsync("chat_turn_context_failed", { ...identity3, reason });
|
|
17249
|
-
const status = (await failureAck).payload;
|
|
17250
|
-
if (status.status !== "accepted" && status.status !== "duplicate") {
|
|
17251
|
-
throw new Error("Chat context failure receipt was rejected.");
|
|
17252
|
-
}
|
|
17253
|
-
})();
|
|
17254
|
-
return failureNotice;
|
|
17255
|
-
};
|
|
17256
|
-
let routing;
|
|
17257
|
-
try {
|
|
17258
|
-
routing = useSelectionDetailsV2 ? await projectRoutingContext : deferredProjectDirectory ? (await Promise.all([pendingContext, projectRoutingContext]))[0] : params.projectId ? await pendingContext : await Promise.race([routingContext, pendingContext]);
|
|
17259
|
-
} catch (error) {
|
|
17260
|
-
try {
|
|
17261
|
-
await notifyContextFailure(useSelectionDetailsV2 ? "project_discovery_failed" : "context_preparation_failed");
|
|
17262
|
-
} catch {
|
|
17263
|
-
}
|
|
17264
|
-
throw error;
|
|
17291
|
+
const confirmed = ws.waitForMessage(
|
|
17292
|
+
"chat_message_confirmed",
|
|
17293
|
+
(payload) => {
|
|
17294
|
+
const eventPayload = payload;
|
|
17295
|
+
return eventPayload.chat_id === chatId && eventPayload.message_id === messageId2;
|
|
17296
|
+
},
|
|
17297
|
+
params.responseTimeoutMs ?? DEFAULT_CHAT_MESSAGE_CONFIRMATION_TIMEOUT_MS
|
|
17298
|
+
);
|
|
17299
|
+
let confirmedPayload;
|
|
17300
|
+
let admissionContinuationPhase = "before_dispatch";
|
|
17301
|
+
let admissionAckObserved = false;
|
|
17302
|
+
let routingFrameSent = false;
|
|
17303
|
+
let detailsFrameSent = false;
|
|
17304
|
+
const admissionContinuationStartedAt = performance.now();
|
|
17305
|
+
try {
|
|
17306
|
+
if (admissionPayload) {
|
|
17307
|
+
if (!useDeferredContext && recoveryDiscovery) await recoveryDiscovery;
|
|
17308
|
+
if (recoveryDiscoveryError) throw recoveryDiscoveryError;
|
|
17309
|
+
if (!contextRequestId && pendingContext) {
|
|
17310
|
+
const context = await pendingContext;
|
|
17311
|
+
Object.assign(messagePayload, context);
|
|
17312
|
+
Object.assign(admissionPayload.inference_request, context);
|
|
17265
17313
|
}
|
|
17266
17314
|
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17267
|
-
|
|
17268
|
-
|
|
17269
|
-
rejectContextAck = reject;
|
|
17270
|
-
});
|
|
17271
|
-
void contextAckRejection.catch(() => {
|
|
17315
|
+
const admissionAck = ws.waitForMessage("chat_turn_preflight_ack", (payload) => payload.turn_id === savedTurnId);
|
|
17316
|
+
void admissionAck.catch(() => {
|
|
17272
17317
|
});
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
|
|
17280
|
-
|
|
17281
|
-
|
|
17282
|
-
|
|
17283
|
-
|
|
17284
|
-
|
|
17285
|
-
|
|
17318
|
+
const frameDispatchAt = performance.now();
|
|
17319
|
+
admissionContinuationPhase = "await_ack";
|
|
17320
|
+
await consumeSocketMemoryReceipt();
|
|
17321
|
+
await ws.sendAsync(useCombinedAdmission ? "chat_turn_admit" : "chat_turn_preflight", admissionPayload);
|
|
17322
|
+
traceSendTiming("admission_frame");
|
|
17323
|
+
if (process.env.OPENMATES_DEBUG === "1") {
|
|
17324
|
+
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)}`);
|
|
17325
|
+
}
|
|
17326
|
+
const ackPayload = (await admissionAck).payload;
|
|
17327
|
+
traceSendTiming("admission_ack");
|
|
17328
|
+
admissionAckObserved = true;
|
|
17329
|
+
if (typeof ackPayload.preflight_id !== "string" || !ackPayload.preflight_id) {
|
|
17330
|
+
throw new Error("Encrypted chat admission acknowledgement omitted preflight_id.");
|
|
17331
|
+
}
|
|
17332
|
+
if (typeof ackPayload.committed_messages_v === "number" && Number.isSafeInteger(ackPayload.committed_messages_v)) {
|
|
17333
|
+
terminalExpectedMessagesV = ackPayload.committed_messages_v;
|
|
17334
|
+
}
|
|
17335
|
+
initialAdmissionDurable = true;
|
|
17336
|
+
startInitialHeader();
|
|
17337
|
+
if (!useCombinedAdmission) {
|
|
17338
|
+
Object.assign(messagePayload, { protocol_version: admissionPayload.protocol_version, preflight_id: ackPayload.preflight_id });
|
|
17339
|
+
await ws.sendAsync("chat_message_added", messagePayload);
|
|
17340
|
+
}
|
|
17341
|
+
if (pendingContext && contextRequestId) {
|
|
17342
|
+
admissionContinuationPhase = "await_routing";
|
|
17343
|
+
const identity3 = {
|
|
17344
|
+
chat_id: chatId,
|
|
17345
|
+
turn_id: savedTurnId,
|
|
17346
|
+
message_id: messageId2,
|
|
17347
|
+
request_id: contextRequestId,
|
|
17348
|
+
...teamId ? { team_id: teamId } : {}
|
|
17349
|
+
};
|
|
17350
|
+
let failureNotice = null;
|
|
17351
|
+
const notifyContextFailure = (reason) => {
|
|
17352
|
+
if (failureNotice) return failureNotice;
|
|
17353
|
+
if (!ws.supportsServerCapability("chat_turn_context_failure_v1") || !ws.isOpen() || !contextOwnerIsCurrent() || !savedTurnId) return Promise.resolve();
|
|
17354
|
+
failureNotice = (async () => {
|
|
17355
|
+
const failureAck = ws.waitForMessage("chat_turn_context_failed_ack", (payload) => {
|
|
17356
|
+
const event = payload;
|
|
17357
|
+
return event.chat_id === chatId && event.turn_id === savedTurnId && event.message_id === messageId2 && event.request_id === contextRequestId;
|
|
17358
|
+
}, 5e3);
|
|
17359
|
+
void failureAck.catch(() => {
|
|
17360
|
+
});
|
|
17361
|
+
await ws.sendAsync("chat_turn_context_failed", { ...identity3, reason });
|
|
17362
|
+
const status = (await failureAck).payload;
|
|
17363
|
+
if (status.status !== "accepted" && status.status !== "duplicate") {
|
|
17364
|
+
throw new Error("Chat context failure receipt was rejected.");
|
|
17365
|
+
}
|
|
17366
|
+
})();
|
|
17367
|
+
return failureNotice;
|
|
17368
|
+
};
|
|
17369
|
+
let routing;
|
|
17370
|
+
try {
|
|
17371
|
+
routing = useSelectionDetailsV2 ? await projectRoutingContext : deferredProjectDirectory ? (await Promise.all([pendingContext, projectRoutingContext]))[0] : params.projectId ? await pendingContext : await Promise.race([routingContext, pendingContext]);
|
|
17372
|
+
} catch (error) {
|
|
17373
|
+
try {
|
|
17374
|
+
await notifyContextFailure(useSelectionDetailsV2 ? "project_discovery_failed" : "context_preparation_failed");
|
|
17375
|
+
} catch {
|
|
17376
|
+
}
|
|
17377
|
+
throw error;
|
|
17286
17378
|
}
|
|
17287
|
-
|
|
17288
|
-
|
|
17289
|
-
|
|
17379
|
+
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17380
|
+
let rejectContextAck;
|
|
17381
|
+
contextAckRejection = new Promise((_resolve, reject) => {
|
|
17382
|
+
rejectContextAck = reject;
|
|
17290
17383
|
});
|
|
17291
|
-
|
|
17292
|
-
|
|
17293
|
-
|
|
17294
|
-
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17384
|
+
void contextAckRejection.catch(() => {
|
|
17385
|
+
});
|
|
17386
|
+
let rejectedPhaseSeen = false;
|
|
17387
|
+
const attemptedContextPhases = /* @__PURE__ */ new Set();
|
|
17388
|
+
removeContextAckWatcher = ws.onMessageType("chat_turn_context_ack", (event) => {
|
|
17389
|
+
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;
|
|
17390
|
+
if (event.status === "accepted" || event.status === "duplicate") {
|
|
17391
|
+
traceSendTiming(event.phase === "routing" ? "routing_ack" : "details_ack");
|
|
17392
|
+
return;
|
|
17393
|
+
}
|
|
17394
|
+
if (event.status !== "rejected") return;
|
|
17395
|
+
rejectedPhaseSeen = true;
|
|
17396
|
+
if (!contextOwnerIsCurrent()) {
|
|
17397
|
+
rejectContextAck(new Error("Chat workspace changed while preparing context."));
|
|
17398
|
+
return;
|
|
17399
|
+
}
|
|
17400
|
+
void notifyContextFailure("context_preparation_failed").catch(() => {
|
|
17401
|
+
}).then(() => {
|
|
17402
|
+
rejectContextAck(new Error("Chat context delivery was rejected."));
|
|
17403
|
+
});
|
|
17404
|
+
});
|
|
17405
|
+
const { project_focus_candidates, related_task_candidates, project_focus_catalog, accepted_plan_context } = routing;
|
|
17406
|
+
traceSendTiming("routing_prepared");
|
|
17407
|
+
attemptedContextPhases.add("routing");
|
|
17408
|
+
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 } });
|
|
17409
|
+
routingFrameSent = true;
|
|
17410
|
+
traceSendTiming("routing_frame");
|
|
17411
|
+
admissionContinuationPhase = "await_details";
|
|
17412
|
+
let context;
|
|
17303
17413
|
try {
|
|
17304
|
-
await
|
|
17305
|
-
} catch {
|
|
17414
|
+
context = await Promise.race([pendingContext, contextAckRejection]);
|
|
17415
|
+
} catch (error) {
|
|
17416
|
+
try {
|
|
17417
|
+
await notifyContextFailure("context_preparation_failed");
|
|
17418
|
+
} catch {
|
|
17419
|
+
}
|
|
17420
|
+
throw error;
|
|
17306
17421
|
}
|
|
17307
|
-
throw
|
|
17422
|
+
if (!contextOwnerIsCurrent()) throw new Error("Chat workspace changed while preparing context.");
|
|
17423
|
+
traceSendTiming("details_prepared");
|
|
17424
|
+
attemptedContextPhases.add("details");
|
|
17425
|
+
await ws.sendAsync("chat_turn_context_ready", { ...identity3, phase: "details", context: {
|
|
17426
|
+
custom_memory_documents: context.custom_memory_documents ?? [],
|
|
17427
|
+
project_focus_documents: context.project_focus_documents ?? [],
|
|
17428
|
+
project_context_documents: context.project_context_documents ?? [],
|
|
17429
|
+
...useSelectionDetailsV2 ? {
|
|
17430
|
+
related_task_candidates: context.related_task_candidates ?? [],
|
|
17431
|
+
project_focus_catalog: context.project_focus_catalog ?? [],
|
|
17432
|
+
accepted_plan_context: context.accepted_plan_context ?? null,
|
|
17433
|
+
...connectedAccountDirectory ? { connected_account_directory: connectedAccountDirectory } : {}
|
|
17434
|
+
} : {}
|
|
17435
|
+
} });
|
|
17436
|
+
detailsFrameSent = true;
|
|
17437
|
+
traceSendTiming("details_frame");
|
|
17308
17438
|
}
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
await ws.sendAsync("
|
|
17313
|
-
custom_memory_documents: context.custom_memory_documents ?? [],
|
|
17314
|
-
project_focus_documents: context.project_focus_documents ?? [],
|
|
17315
|
-
project_context_documents: context.project_context_documents ?? [],
|
|
17316
|
-
...useSelectionDetailsV2 ? {
|
|
17317
|
-
related_task_candidates: context.related_task_candidates ?? [],
|
|
17318
|
-
project_focus_catalog: context.project_focus_catalog ?? [],
|
|
17319
|
-
accepted_plan_context: context.accepted_plan_context ?? null,
|
|
17320
|
-
...connectedAccountDirectory ? { connected_account_directory: connectedAccountDirectory } : {}
|
|
17321
|
-
} : {}
|
|
17322
|
-
} });
|
|
17323
|
-
detailsFrameSent = true;
|
|
17324
|
-
traceSendTiming("details_frame");
|
|
17439
|
+
admissionContinuationPhase = "await_confirmation";
|
|
17440
|
+
} else {
|
|
17441
|
+
await consumeSocketMemoryReceipt();
|
|
17442
|
+
await ws.sendAsync("chat_message_added", messagePayload);
|
|
17325
17443
|
}
|
|
17326
|
-
|
|
17327
|
-
}
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17444
|
+
confirmedPayload = (await (contextAckRejection ? Promise.race([confirmed, contextAckRejection]) : confirmed)).payload;
|
|
17445
|
+
} catch (error) {
|
|
17446
|
+
if (process.env.OPENMATES_DEBUG === "1" && admissionPayload) {
|
|
17447
|
+
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)}`);
|
|
17448
|
+
}
|
|
17449
|
+
dispatchHandlersReady();
|
|
17450
|
+
void confirmed.catch(() => {
|
|
17451
|
+
});
|
|
17452
|
+
if (precollectedResponse) void precollectedResponse.catch(() => {
|
|
17453
|
+
});
|
|
17454
|
+
ws.close();
|
|
17455
|
+
throw error;
|
|
17335
17456
|
}
|
|
17336
|
-
|
|
17337
|
-
|
|
17338
|
-
});
|
|
17339
|
-
if (precollectedResponse) void precollectedResponse.catch(() => {
|
|
17340
|
-
});
|
|
17341
|
-
ws.close();
|
|
17342
|
-
throw error;
|
|
17343
|
-
}
|
|
17344
|
-
if (!params.incognito && typeof confirmedPayload.new_messages_v === "number" && Number.isSafeInteger(confirmedPayload.new_messages_v)) {
|
|
17345
|
-
terminalExpectedMessagesV = confirmedPayload.new_messages_v;
|
|
17346
|
-
}
|
|
17347
|
-
if (!params.incognito && (!teamId || shouldWaitForAi)) {
|
|
17348
|
-
clearSyncCache(teamId);
|
|
17349
|
-
}
|
|
17350
|
-
let assistant = "";
|
|
17351
|
-
let assistantMessageId = null;
|
|
17352
|
-
let category = null;
|
|
17353
|
-
let modelName = null;
|
|
17354
|
-
let tokenUsage = null;
|
|
17355
|
-
let promptBudget = null;
|
|
17356
|
-
let followUpSuggestions = [];
|
|
17357
|
-
let suggestedFollowupMessage = null;
|
|
17358
|
-
let taskProposals = [];
|
|
17359
|
-
let taskUpdateProposals = [];
|
|
17360
|
-
let taskEvents = [];
|
|
17361
|
-
let pendingTaskUpdateJobs = [];
|
|
17362
|
-
let subChatEvents = [];
|
|
17363
|
-
const appSettingsMemoryRequests = [];
|
|
17364
|
-
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
17365
|
-
const isApprovalWithinServerLimits = (request) => {
|
|
17366
|
-
const count = request.subChats.length;
|
|
17367
|
-
if (count === 0) return false;
|
|
17368
|
-
if (request.remainingSubChats !== null && count > request.remainingSubChats) {
|
|
17369
|
-
return false;
|
|
17457
|
+
if (!params.incognito && typeof confirmedPayload.new_messages_v === "number" && Number.isSafeInteger(confirmedPayload.new_messages_v)) {
|
|
17458
|
+
terminalExpectedMessagesV = confirmedPayload.new_messages_v;
|
|
17370
17459
|
}
|
|
17371
|
-
if (
|
|
17372
|
-
|
|
17460
|
+
if (!params.incognito && (!teamId || shouldWaitForAi)) {
|
|
17461
|
+
clearSyncCache(teamId);
|
|
17373
17462
|
}
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
17381
|
-
|
|
17382
|
-
|
|
17383
|
-
|
|
17384
|
-
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
|
|
17390
|
-
|
|
17391
|
-
|
|
17392
|
-
|
|
17393
|
-
|
|
17463
|
+
let assistant = "";
|
|
17464
|
+
let assistantMessageId = null;
|
|
17465
|
+
let category = null;
|
|
17466
|
+
let modelName = null;
|
|
17467
|
+
let tokenUsage = null;
|
|
17468
|
+
let promptBudget = null;
|
|
17469
|
+
let followUpSuggestions = [];
|
|
17470
|
+
let suggestedFollowupMessage = null;
|
|
17471
|
+
let taskProposals = [];
|
|
17472
|
+
let taskUpdateProposals = [];
|
|
17473
|
+
let taskEvents = [];
|
|
17474
|
+
let pendingTaskUpdateJobs = [];
|
|
17475
|
+
let subChatEvents = [];
|
|
17476
|
+
const appSettingsMemoryRequests = [];
|
|
17477
|
+
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
17478
|
+
const isApprovalWithinServerLimits = (request) => {
|
|
17479
|
+
const count = request.subChats.length;
|
|
17480
|
+
if (count === 0) return false;
|
|
17481
|
+
if (request.remainingSubChats !== null && count > request.remainingSubChats) {
|
|
17482
|
+
return false;
|
|
17394
17483
|
}
|
|
17395
|
-
|
|
17396
|
-
|
|
17397
|
-
if (event.type !== "sub_chat_confirmation_required") return;
|
|
17398
|
-
const payload = event.payload;
|
|
17399
|
-
const confirmationChatId = typeof payload.chat_id === "string" ? payload.chat_id : chatId;
|
|
17400
|
-
const taskId = typeof payload.task_id === "string" ? payload.task_id : null;
|
|
17401
|
-
const subChats = Array.isArray(payload.sub_chats) ? payload.sub_chats : [];
|
|
17402
|
-
if (!taskId) return;
|
|
17403
|
-
const request = {
|
|
17404
|
-
chatId: confirmationChatId,
|
|
17405
|
-
taskId,
|
|
17406
|
-
subChats,
|
|
17407
|
-
maxAutoSubChats: numberOrNull(payload.max_auto_sub_chats),
|
|
17408
|
-
maxDirectSubChats: numberOrNull(payload.max_direct_sub_chats),
|
|
17409
|
-
existingSubChats: numberOrNull(payload.existing_sub_chats),
|
|
17410
|
-
remainingSubChats: numberOrNull(payload.remaining_sub_chats)
|
|
17411
|
-
};
|
|
17412
|
-
const withinLimits = isApprovalWithinServerLimits(request);
|
|
17413
|
-
const approved = params.autoApproveSubChats ? isAutoApprovalWithinServerLimits(request) : withinLimits && params.onSubChatApprovalRequest ? await params.onSubChatApprovalRequest(request) : false;
|
|
17414
|
-
await ws.sendAsync(
|
|
17415
|
-
"sub_chat_confirmation",
|
|
17416
|
-
buildSubChatConfirmationPayload({
|
|
17417
|
-
chatId: request.chatId,
|
|
17418
|
-
taskId: request.taskId,
|
|
17419
|
-
approved,
|
|
17420
|
-
approveCount: approved ? request.subChats.length : void 0
|
|
17421
|
-
})
|
|
17422
|
-
);
|
|
17423
|
-
};
|
|
17424
|
-
const persistSystemMessage = async (systemMessage) => {
|
|
17425
|
-
if (!chatKeyBytes) return;
|
|
17426
|
-
await ws.sendAsync("chat_system_message_added", {
|
|
17427
|
-
chat_id: chatId,
|
|
17428
|
-
message: {
|
|
17429
|
-
message_id: systemMessage.message_id,
|
|
17430
|
-
role: "system",
|
|
17431
|
-
encrypted_content: await encryptWithAesGcmCombined(
|
|
17432
|
-
systemMessage.content,
|
|
17433
|
-
chatKeyBytes
|
|
17434
|
-
),
|
|
17435
|
-
created_at: systemMessage.created_at,
|
|
17436
|
-
user_message_id: systemMessage.user_message_id
|
|
17484
|
+
if (request.maxDirectSubChats !== null && request.existingSubChats !== null && request.existingSubChats + count > request.maxDirectSubChats) {
|
|
17485
|
+
return false;
|
|
17437
17486
|
}
|
|
17438
|
-
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
(
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17455
|
-
createdAt: Math.floor(Date.now() / 1e3)
|
|
17456
|
-
}));
|
|
17457
|
-
};
|
|
17458
|
-
const responseCategoriesFromMemories = (approvedMemories) => {
|
|
17459
|
-
const counts = /* @__PURE__ */ new Map();
|
|
17460
|
-
for (const memory of approvedMemories) {
|
|
17461
|
-
const key = `${memory.app_id}-${memory.item_key}`;
|
|
17462
|
-
const existing = counts.get(key);
|
|
17463
|
-
if (existing) {
|
|
17464
|
-
existing.entryCount += 1;
|
|
17465
|
-
} else {
|
|
17466
|
-
counts.set(key, {
|
|
17467
|
-
appId: memory.app_id,
|
|
17468
|
-
itemType: memory.item_key,
|
|
17469
|
-
entryCount: 1
|
|
17487
|
+
return true;
|
|
17488
|
+
};
|
|
17489
|
+
const isAutoApprovalWithinServerLimits = (request) => {
|
|
17490
|
+
if (!isApprovalWithinServerLimits(request)) return false;
|
|
17491
|
+
return request.maxAutoSubChats === null || request.subChats.length <= request.maxAutoSubChats;
|
|
17492
|
+
};
|
|
17493
|
+
const handleSubChatEvent = async (event) => {
|
|
17494
|
+
params.onSubChatEvent?.(event);
|
|
17495
|
+
if (event.type === "spawn_sub_chats") {
|
|
17496
|
+
if (params.incognito || !chatKeyBytes) return;
|
|
17497
|
+
const rawSubChats = event.payload.sub_chats;
|
|
17498
|
+
if (!Array.isArray(rawSubChats)) return;
|
|
17499
|
+
const metadataPayloads = await buildSubChatEncryptedMetadataPayloads({
|
|
17500
|
+
parentChatId: chatId,
|
|
17501
|
+
parentChatKey: chatKeyBytes,
|
|
17502
|
+
encryptedParentChatKey: encryptedChatKey,
|
|
17503
|
+
subChats: rawSubChats
|
|
17470
17504
|
});
|
|
17505
|
+
for (const metadataPayload of metadataPayloads) {
|
|
17506
|
+
await ws.sendAsync("encrypted_chat_metadata", metadataPayload);
|
|
17507
|
+
}
|
|
17508
|
+
return;
|
|
17471
17509
|
}
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
const
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
|
|
17482
|
-
|
|
17483
|
-
|
|
17484
|
-
|
|
17485
|
-
)
|
|
17486
|
-
}
|
|
17487
|
-
const
|
|
17488
|
-
|
|
17489
|
-
|
|
17490
|
-
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
requestId: event.requestId,
|
|
17498
|
-
requestedKeys: event.requestedKeys,
|
|
17499
|
-
approvedKeys,
|
|
17500
|
-
entryCount: approvedMemories.length
|
|
17501
|
-
});
|
|
17502
|
-
await ws.sendAsync("app_settings_memories_confirmed", {
|
|
17503
|
-
chat_id: event.chatId,
|
|
17504
|
-
app_settings_memories: approvedMemories
|
|
17505
|
-
});
|
|
17506
|
-
await persistSystemMessage(buildAppSettingsMemoryResponseSystemMessage({
|
|
17507
|
-
userMessageId: messageId2,
|
|
17508
|
-
messageId: `${messageId2}-memory-response`,
|
|
17509
|
-
action: "included",
|
|
17510
|
-
categories,
|
|
17511
|
-
createdAt: Math.floor(Date.now() / 1e3)
|
|
17512
|
-
}));
|
|
17513
|
-
return;
|
|
17514
|
-
}
|
|
17515
|
-
appSettingsMemoryRequests.push({
|
|
17516
|
-
requestId: event.requestId,
|
|
17517
|
-
requestedKeys: event.requestedKeys,
|
|
17518
|
-
approvedKeys: [],
|
|
17519
|
-
entryCount: 0
|
|
17520
|
-
});
|
|
17521
|
-
throw new Error(
|
|
17522
|
-
`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.`
|
|
17523
|
-
);
|
|
17524
|
-
};
|
|
17525
|
-
const persistedTaskEventIds = /* @__PURE__ */ new Set();
|
|
17526
|
-
const persistTaskEventSystemMessages = async (events) => {
|
|
17527
|
-
if (!chatKeyBytes || events.length === 0) return;
|
|
17528
|
-
for (const event of events) {
|
|
17529
|
-
if (persistedTaskEventIds.has(event.event_id)) continue;
|
|
17530
|
-
await this.persistEncryptedSystemMessage(
|
|
17531
|
-
ws,
|
|
17532
|
-
await buildTaskEventSystemMessage({
|
|
17533
|
-
chatKey: chatKeyBytes,
|
|
17534
|
-
userMessageId: messageId2,
|
|
17535
|
-
event
|
|
17536
|
-
}),
|
|
17537
|
-
chatId
|
|
17510
|
+
if (event.type !== "sub_chat_confirmation_required") return;
|
|
17511
|
+
const payload = event.payload;
|
|
17512
|
+
const confirmationChatId = typeof payload.chat_id === "string" ? payload.chat_id : chatId;
|
|
17513
|
+
const taskId = typeof payload.task_id === "string" ? payload.task_id : null;
|
|
17514
|
+
const subChats = Array.isArray(payload.sub_chats) ? payload.sub_chats : [];
|
|
17515
|
+
if (!taskId) return;
|
|
17516
|
+
const request = {
|
|
17517
|
+
chatId: confirmationChatId,
|
|
17518
|
+
taskId,
|
|
17519
|
+
subChats,
|
|
17520
|
+
maxAutoSubChats: numberOrNull(payload.max_auto_sub_chats),
|
|
17521
|
+
maxDirectSubChats: numberOrNull(payload.max_direct_sub_chats),
|
|
17522
|
+
existingSubChats: numberOrNull(payload.existing_sub_chats),
|
|
17523
|
+
remainingSubChats: numberOrNull(payload.remaining_sub_chats)
|
|
17524
|
+
};
|
|
17525
|
+
const withinLimits = isApprovalWithinServerLimits(request);
|
|
17526
|
+
const approved = params.autoApproveSubChats ? isAutoApprovalWithinServerLimits(request) : withinLimits && params.onSubChatApprovalRequest ? await params.onSubChatApprovalRequest(request) : false;
|
|
17527
|
+
await ws.sendAsync(
|
|
17528
|
+
"sub_chat_confirmation",
|
|
17529
|
+
buildSubChatConfirmationPayload({
|
|
17530
|
+
chatId: request.chatId,
|
|
17531
|
+
taskId: request.taskId,
|
|
17532
|
+
approved,
|
|
17533
|
+
approveCount: approved ? request.subChats.length : void 0
|
|
17534
|
+
})
|
|
17538
17535
|
);
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
17542
|
-
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
} else if (params.incognito) {
|
|
17554
|
-
try {
|
|
17555
|
-
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId2, chatId, {
|
|
17556
|
-
...streamOpts,
|
|
17557
|
-
timeoutMs: params.responseTimeoutMs,
|
|
17558
|
-
recoveryTurnId: savedTurnId
|
|
17559
|
-
}));
|
|
17560
|
-
assistantMessageId = resp.messageId;
|
|
17561
|
-
assistant = resp.content;
|
|
17562
|
-
category = resp.category;
|
|
17563
|
-
modelName = resp.modelName;
|
|
17564
|
-
tokenUsage = resp.tokenUsage;
|
|
17565
|
-
promptBudget = resp.promptBudget;
|
|
17566
|
-
taskEvents = resp.taskEvents;
|
|
17567
|
-
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
17568
|
-
subChatEvents = resp.subChatEvents;
|
|
17569
|
-
if (resp.status === "waiting_for_user") {
|
|
17570
|
-
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
17571
|
-
return {
|
|
17572
|
-
status: resp.status,
|
|
17573
|
-
chatId,
|
|
17574
|
-
messageId: assistantMessageId,
|
|
17575
|
-
assistant,
|
|
17576
|
-
category,
|
|
17577
|
-
modelName,
|
|
17578
|
-
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
17579
|
-
followUpSuggestions,
|
|
17580
|
-
taskProposals,
|
|
17581
|
-
taskUpdateProposals,
|
|
17582
|
-
taskEvents,
|
|
17583
|
-
pendingTaskUpdateJobs,
|
|
17584
|
-
subChatEvents,
|
|
17585
|
-
appSettingsMemoryRequests,
|
|
17586
|
-
tokenUsage,
|
|
17587
|
-
promptBudget
|
|
17588
|
-
};
|
|
17589
|
-
}
|
|
17590
|
-
} finally {
|
|
17591
|
-
ws.close();
|
|
17592
|
-
}
|
|
17593
|
-
} else {
|
|
17594
|
-
try {
|
|
17595
|
-
const recoverCurrentTurnOutputs = async () => {
|
|
17596
|
-
if (recoveryDiscovery) {
|
|
17597
|
-
try {
|
|
17598
|
-
await recoveryDiscovery;
|
|
17599
|
-
} catch (error) {
|
|
17600
|
-
if (!useDeferredContext) throw error;
|
|
17601
|
-
return;
|
|
17602
|
-
}
|
|
17536
|
+
};
|
|
17537
|
+
const persistSystemMessage = async (systemMessage) => {
|
|
17538
|
+
if (!chatKeyBytes) return;
|
|
17539
|
+
await ws.sendAsync("chat_system_message_added", {
|
|
17540
|
+
chat_id: chatId,
|
|
17541
|
+
message: {
|
|
17542
|
+
message_id: systemMessage.message_id,
|
|
17543
|
+
role: "system",
|
|
17544
|
+
encrypted_content: await encryptWithAesGcmCombined(
|
|
17545
|
+
systemMessage.content,
|
|
17546
|
+
chatKeyBytes
|
|
17547
|
+
),
|
|
17548
|
+
created_at: systemMessage.created_at,
|
|
17549
|
+
user_message_id: systemMessage.user_message_id
|
|
17603
17550
|
}
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17551
|
+
});
|
|
17552
|
+
await ws.waitForMessage(
|
|
17553
|
+
"system_message_confirmed",
|
|
17554
|
+
(payload) => {
|
|
17555
|
+
const p = payload;
|
|
17556
|
+
return p.message_id === systemMessage.message_id;
|
|
17557
|
+
},
|
|
17558
|
+
2e4
|
|
17559
|
+
);
|
|
17560
|
+
};
|
|
17561
|
+
const persistMemoryRequestSystemMessage = async (event) => {
|
|
17562
|
+
const requestId = event.requestId ?? `${messageId2}-memory-request`;
|
|
17563
|
+
await persistSystemMessage(buildAppSettingsMemoryRequestSystemMessage({
|
|
17564
|
+
userMessageId: messageId2,
|
|
17565
|
+
requestId,
|
|
17566
|
+
requestedKeys: event.requestedKeys,
|
|
17567
|
+
entryCounts: memoryCountsLoaded ? new Map(event.requestedKeys.map((key) => [key, availableMemories.filter((memory) => `${memory.app_id}-${memory.item_type}` === key).length])) : void 0,
|
|
17568
|
+
createdAt: Math.floor(Date.now() / 1e3)
|
|
17569
|
+
}));
|
|
17570
|
+
};
|
|
17571
|
+
const responseCategoriesFromMemories = (approvedMemories) => {
|
|
17572
|
+
const counts = /* @__PURE__ */ new Map();
|
|
17573
|
+
for (const memory of approvedMemories) {
|
|
17574
|
+
const key = `${memory.app_id}-${memory.item_key}`;
|
|
17575
|
+
const existing = counts.get(key);
|
|
17576
|
+
if (existing) {
|
|
17577
|
+
existing.entryCount += 1;
|
|
17578
|
+
} else {
|
|
17579
|
+
counts.set(key, {
|
|
17580
|
+
appId: memory.app_id,
|
|
17581
|
+
itemType: memory.item_key,
|
|
17582
|
+
entryCount: 1
|
|
17583
|
+
});
|
|
17609
17584
|
}
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
|
|
17620
|
-
|
|
17621
|
-
|
|
17622
|
-
|
|
17585
|
+
}
|
|
17586
|
+
return [...counts.values()];
|
|
17587
|
+
};
|
|
17588
|
+
const handleAppSettingsMemoriesRequest = async (event) => {
|
|
17589
|
+
await persistMemoryRequestSystemMessage(event);
|
|
17590
|
+
if (params.autoApproveMemories) {
|
|
17591
|
+
const requested = new Set(event.requestedKeys);
|
|
17592
|
+
const unadvertisedKeys = event.requestedKeys.filter(
|
|
17593
|
+
(key) => !memoryMetadataKeys.includes(key)
|
|
17594
|
+
);
|
|
17595
|
+
if (unadvertisedKeys.length > 0) {
|
|
17596
|
+
throw new Error(
|
|
17597
|
+
`Refusing to auto-approve unadvertised memory categories: ${unadvertisedKeys.join(", ")}`
|
|
17598
|
+
);
|
|
17623
17599
|
}
|
|
17624
|
-
|
|
17625
|
-
|
|
17626
|
-
|
|
17627
|
-
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
)] : [],
|
|
17639
|
-
...pausedHandoff ? [pausedHandoff.then(
|
|
17640
|
-
(frame) => ({ kind: "paused", frame }),
|
|
17641
|
-
() => new Promise(() => {
|
|
17642
|
-
})
|
|
17643
|
-
)] : []
|
|
17644
|
-
]);
|
|
17645
|
-
if (outcome.kind === "paused") {
|
|
17646
|
-
void responsePromise.catch(() => {
|
|
17600
|
+
const approvedMemories = availableMemories.filter((memory) => requested.has(`${memory.app_id}-${memory.item_type}`)).map((memory) => ({
|
|
17601
|
+
app_id: memory.app_id,
|
|
17602
|
+
item_key: memory.item_type,
|
|
17603
|
+
content: memory.data
|
|
17604
|
+
}));
|
|
17605
|
+
const approvedKeys = [
|
|
17606
|
+
...new Set(approvedMemories.map((memory) => `${memory.app_id}-${memory.item_key}`))
|
|
17607
|
+
];
|
|
17608
|
+
const categories = responseCategoriesFromMemories(approvedMemories);
|
|
17609
|
+
appSettingsMemoryRequests.push({
|
|
17610
|
+
requestId: event.requestId,
|
|
17611
|
+
requestedKeys: event.requestedKeys,
|
|
17612
|
+
approvedKeys,
|
|
17613
|
+
entryCount: approvedMemories.length
|
|
17647
17614
|
});
|
|
17648
|
-
|
|
17649
|
-
|
|
17650
|
-
|
|
17651
|
-
void responsePromise.catch(() => {
|
|
17615
|
+
await ws.sendAsync("app_settings_memories_confirmed", {
|
|
17616
|
+
chat_id: event.chatId,
|
|
17617
|
+
app_settings_memories: approvedMemories
|
|
17652
17618
|
});
|
|
17653
|
-
|
|
17654
|
-
return {
|
|
17655
|
-
status: "queued",
|
|
17656
|
-
chatId,
|
|
17657
|
-
messageId: null,
|
|
17619
|
+
await persistSystemMessage(buildAppSettingsMemoryResponseSystemMessage({
|
|
17658
17620
|
userMessageId: messageId2,
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
followUpSuggestions: [],
|
|
17666
|
-
taskProposals: [],
|
|
17667
|
-
taskUpdateProposals: [],
|
|
17668
|
-
taskEvents: [],
|
|
17669
|
-
pendingTaskUpdateJobs: [],
|
|
17670
|
-
subChatEvents: [],
|
|
17671
|
-
appSettingsMemoryRequests: [],
|
|
17672
|
-
tokenUsage: null,
|
|
17673
|
-
promptBudget: null
|
|
17674
|
-
};
|
|
17621
|
+
messageId: `${messageId2}-memory-response`,
|
|
17622
|
+
action: "included",
|
|
17623
|
+
categories,
|
|
17624
|
+
createdAt: Math.floor(Date.now() / 1e3)
|
|
17625
|
+
}));
|
|
17626
|
+
return;
|
|
17675
17627
|
}
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17680
|
-
|
|
17681
|
-
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17686
|
-
|
|
17687
|
-
|
|
17688
|
-
|
|
17689
|
-
|
|
17690
|
-
|
|
17691
|
-
await
|
|
17692
|
-
|
|
17693
|
-
|
|
17694
|
-
|
|
17695
|
-
|
|
17696
|
-
|
|
17697
|
-
|
|
17698
|
-
|
|
17699
|
-
|
|
17700
|
-
|
|
17701
|
-
followUpSuggestions,
|
|
17702
|
-
taskProposals,
|
|
17703
|
-
taskUpdateProposals,
|
|
17704
|
-
taskEvents,
|
|
17705
|
-
pendingTaskUpdateJobs,
|
|
17706
|
-
subChatEvents,
|
|
17707
|
-
appSettingsMemoryRequests,
|
|
17708
|
-
tokenUsage,
|
|
17709
|
-
promptBudget
|
|
17710
|
-
};
|
|
17628
|
+
appSettingsMemoryRequests.push({
|
|
17629
|
+
requestId: event.requestId,
|
|
17630
|
+
requestedKeys: event.requestedKeys,
|
|
17631
|
+
approvedKeys: [],
|
|
17632
|
+
entryCount: 0
|
|
17633
|
+
});
|
|
17634
|
+
throw new Error(
|
|
17635
|
+
`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.`
|
|
17636
|
+
);
|
|
17637
|
+
};
|
|
17638
|
+
const persistedTaskEventIds = /* @__PURE__ */ new Set();
|
|
17639
|
+
const persistTaskEventSystemMessages = async (events) => {
|
|
17640
|
+
if (!chatKeyBytes || events.length === 0) return;
|
|
17641
|
+
for (const event of events) {
|
|
17642
|
+
if (persistedTaskEventIds.has(event.event_id)) continue;
|
|
17643
|
+
await this.persistEncryptedSystemMessage(
|
|
17644
|
+
ws,
|
|
17645
|
+
await buildTaskEventSystemMessage({
|
|
17646
|
+
chatKey: chatKeyBytes,
|
|
17647
|
+
userMessageId: messageId2,
|
|
17648
|
+
event
|
|
17649
|
+
}),
|
|
17650
|
+
chatId
|
|
17651
|
+
);
|
|
17652
|
+
persistedTaskEventIds.add(event.event_id);
|
|
17711
17653
|
}
|
|
17712
|
-
|
|
17713
|
-
|
|
17714
|
-
|
|
17654
|
+
};
|
|
17655
|
+
const streamOpts = {
|
|
17656
|
+
waitForPostProcessing: false,
|
|
17657
|
+
onStream: onTurnStream,
|
|
17658
|
+
onSubChatEvent: handleSubChatEvent,
|
|
17659
|
+
onAppSettingsMemoriesRequest: handleAppSettingsMemoriesRequest,
|
|
17660
|
+
projectWriteApprovalInteractive: Boolean(params.onProjectWriteApproval),
|
|
17661
|
+
remoteCommandReviewInteractive: Boolean(params.onRemoteCommandReview)
|
|
17662
|
+
};
|
|
17663
|
+
dispatchHandlersReady();
|
|
17664
|
+
if (!shouldWaitForAi) {
|
|
17665
|
+
ws.close();
|
|
17666
|
+
} else if (params.incognito) {
|
|
17667
|
+
try {
|
|
17668
|
+
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId2, chatId, {
|
|
17669
|
+
...streamOpts,
|
|
17670
|
+
timeoutMs: params.responseTimeoutMs,
|
|
17671
|
+
recoveryTurnId: savedTurnId
|
|
17672
|
+
}));
|
|
17673
|
+
assistantMessageId = resp.messageId;
|
|
17674
|
+
assistant = resp.content;
|
|
17675
|
+
category = resp.category;
|
|
17676
|
+
modelName = resp.modelName;
|
|
17677
|
+
tokenUsage = resp.tokenUsage;
|
|
17678
|
+
promptBudget = resp.promptBudget;
|
|
17679
|
+
taskEvents = resp.taskEvents;
|
|
17680
|
+
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
17681
|
+
subChatEvents = resp.subChatEvents;
|
|
17682
|
+
if (resp.status === "waiting_for_user") {
|
|
17683
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
17684
|
+
return {
|
|
17685
|
+
status: resp.status,
|
|
17686
|
+
chatId,
|
|
17687
|
+
messageId: assistantMessageId,
|
|
17688
|
+
assistant,
|
|
17689
|
+
category,
|
|
17690
|
+
modelName,
|
|
17691
|
+
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
17692
|
+
followUpSuggestions,
|
|
17693
|
+
taskProposals,
|
|
17694
|
+
taskUpdateProposals,
|
|
17695
|
+
taskEvents,
|
|
17696
|
+
pendingTaskUpdateJobs,
|
|
17697
|
+
subChatEvents,
|
|
17698
|
+
appSettingsMemoryRequests,
|
|
17699
|
+
tokenUsage,
|
|
17700
|
+
promptBudget
|
|
17701
|
+
};
|
|
17715
17702
|
}
|
|
17716
|
-
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
const stopWatchingErrors = ws.onMessageType("error", (payload) => {
|
|
17724
|
-
if (payload?.code === "lease_conflict" && payload.job_id === recoveryJobId && payload.request_id === requestId) {
|
|
17725
|
-
matchingLeaseConflict = true;
|
|
17726
|
-
}
|
|
17727
|
-
});
|
|
17728
|
-
const claimPromise = ws.waitForMessage(
|
|
17729
|
-
"recovery_job_claimed",
|
|
17730
|
-
(payload) => {
|
|
17731
|
-
const received = payload;
|
|
17732
|
-
return received.job_id === recoveryJobId && (received.request_id === void 0 || received.request_id === requestId);
|
|
17733
|
-
},
|
|
17734
|
-
claimRetryDeadline === null ? 2e4 : Math.min(2e4, Math.max(1, claimRetryDeadline - Date.now()))
|
|
17735
|
-
);
|
|
17736
|
-
try {
|
|
17703
|
+
} finally {
|
|
17704
|
+
ws.close();
|
|
17705
|
+
}
|
|
17706
|
+
} else {
|
|
17707
|
+
try {
|
|
17708
|
+
const recoverCurrentTurnOutputs = async () => {
|
|
17709
|
+
if (recoveryDiscovery) {
|
|
17737
17710
|
try {
|
|
17738
|
-
await
|
|
17739
|
-
protocol_version: 1,
|
|
17740
|
-
job_id: recoveryJobId,
|
|
17741
|
-
request_id: requestId
|
|
17742
|
-
});
|
|
17711
|
+
await recoveryDiscovery;
|
|
17743
17712
|
} catch (error) {
|
|
17744
|
-
|
|
17745
|
-
|
|
17746
|
-
throw error;
|
|
17713
|
+
if (!useDeferredContext) throw error;
|
|
17714
|
+
return;
|
|
17747
17715
|
}
|
|
17748
|
-
claim = (await claimPromise).payload;
|
|
17749
|
-
break;
|
|
17750
|
-
} catch (error) {
|
|
17751
|
-
if (!(error instanceof WebSocketProtocolError) || error.code !== "lease_conflict" || !matchingLeaseConflict) throw error;
|
|
17752
|
-
claimRetryDeadline ??= Date.now() + Math.min(
|
|
17753
|
-
1e4,
|
|
17754
|
-
Math.max(0, params.responseTimeoutMs ?? 9e4)
|
|
17755
|
-
);
|
|
17756
|
-
const remainingMs = claimRetryDeadline - Date.now();
|
|
17757
|
-
if (remainingMs <= claimRetryDelayMs) throw error;
|
|
17758
|
-
await new Promise((resolve20) => setTimeout(resolve20, claimRetryDelayMs));
|
|
17759
|
-
claimRetryDelayMs = Math.min(claimRetryDelayMs * 2, 2e3);
|
|
17760
|
-
} finally {
|
|
17761
|
-
stopWatchingErrors();
|
|
17762
17716
|
}
|
|
17717
|
+
const pages = ws.drainAvailableRecoveryOutputPages();
|
|
17718
|
+
if (pages.length === 0) return;
|
|
17719
|
+
if (!ownerId2) throw new Error("Saved chat recovery output requires the owner identity.");
|
|
17720
|
+
if (pages.some((page) => page.some((output) => output.root_chat_id === chatId || output.target_chat_id === chatId)) && (!chatKeyBytes || !encryptedChatKey)) {
|
|
17721
|
+
throw new Error("Saved chat recovery output requires the root chat key.");
|
|
17722
|
+
}
|
|
17723
|
+
const cache = loadSyncCache(teamId) ?? {
|
|
17724
|
+
syncedAt: Date.now(),
|
|
17725
|
+
totalChatCount: 0,
|
|
17726
|
+
loadedChatCount: 0,
|
|
17727
|
+
chats: [],
|
|
17728
|
+
embeds: [],
|
|
17729
|
+
embedKeys: [],
|
|
17730
|
+
chatKeyWrappers: []
|
|
17731
|
+
};
|
|
17732
|
+
if (encryptedChatKey) {
|
|
17733
|
+
const root = cache.chats.find((chat) => String(chat.details.id ?? "") === chatId);
|
|
17734
|
+
if (root) root.details.encrypted_chat_key = encryptedChatKey;
|
|
17735
|
+
else cache.chats.push({ details: { id: chatId, encrypted_chat_key: encryptedChatKey }, messages: [] });
|
|
17736
|
+
}
|
|
17737
|
+
await this.replayCurrentTurnRecoveryOutputPages(ws, ownerId2, pages, cache, teamId, chatId);
|
|
17738
|
+
};
|
|
17739
|
+
const responsePromise = precollectedResponse ?? ws.collectAiResponse(messageId2, chatId, {
|
|
17740
|
+
...streamOpts,
|
|
17741
|
+
timeoutMs: params.responseTimeoutMs,
|
|
17742
|
+
recoveryTurnId: savedTurnId
|
|
17743
|
+
});
|
|
17744
|
+
const outcome = await Promise.race([
|
|
17745
|
+
responsePromise.then((response) => ({ kind: "response", response })),
|
|
17746
|
+
...contextAckRejection ? [contextAckRejection] : [],
|
|
17747
|
+
...queuedAdmission ? [queuedAdmission.then(
|
|
17748
|
+
(frame) => ({ kind: "queued", frame }),
|
|
17749
|
+
() => new Promise(() => {
|
|
17750
|
+
})
|
|
17751
|
+
)] : [],
|
|
17752
|
+
...pausedHandoff ? [pausedHandoff.then(
|
|
17753
|
+
(frame) => ({ kind: "paused", frame }),
|
|
17754
|
+
() => new Promise(() => {
|
|
17755
|
+
})
|
|
17756
|
+
)] : []
|
|
17757
|
+
]);
|
|
17758
|
+
if (outcome.kind === "paused") {
|
|
17759
|
+
void responsePromise.catch(() => {
|
|
17760
|
+
});
|
|
17761
|
+
throw new Error("Message saved, but AI handoff is paused. No assistant run started; check the chat before retrying.");
|
|
17763
17762
|
}
|
|
17764
|
-
|
|
17765
|
-
|
|
17766
|
-
const assistantId = typeof claim.assistant_message_id === "string" ? claim.assistant_message_id : null;
|
|
17767
|
-
const keyVersion = typeof claim.chat_key_version === "number" && Number.isSafeInteger(claim.chat_key_version) ? claim.chat_key_version : null;
|
|
17768
|
-
const claimMatchesExpectedTerminal = assistantId && keyVersion === 1 && claim.chat_id === chatId && claim.turn_id === savedTurnId;
|
|
17769
|
-
if (claim.state === "TERMINAL" && claimMatchesExpectedTerminal) {
|
|
17770
|
-
assistant = resp.content;
|
|
17771
|
-
category = resp.category;
|
|
17772
|
-
modelName = resp.modelName;
|
|
17773
|
-
assistantMessageId = assistantId;
|
|
17774
|
-
await this.persistStreamedEmbeds({
|
|
17775
|
-
ws,
|
|
17776
|
-
embeds: resp.embeds,
|
|
17777
|
-
chatId,
|
|
17778
|
-
chatKeyBytes,
|
|
17779
|
-
fallbackMessageId: assistantId,
|
|
17780
|
-
ownerId: ownerId2,
|
|
17781
|
-
teamId
|
|
17763
|
+
if (outcome.kind === "queued") {
|
|
17764
|
+
void responsePromise.catch(() => {
|
|
17782
17765
|
});
|
|
17783
|
-
|
|
17784
|
-
|
|
17766
|
+
const frame = outcome.frame.payload;
|
|
17767
|
+
return {
|
|
17768
|
+
status: "queued",
|
|
17769
|
+
chatId,
|
|
17770
|
+
messageId: null,
|
|
17771
|
+
userMessageId: messageId2,
|
|
17772
|
+
queueMode: frame.queue_mode === "steer" ? "steer" : "follow_up",
|
|
17773
|
+
queueRevision: typeof frame.queue_revision === "number" ? frame.queue_revision : 1,
|
|
17774
|
+
assistant: "",
|
|
17775
|
+
category: null,
|
|
17776
|
+
modelName: null,
|
|
17777
|
+
mateName: null,
|
|
17778
|
+
followUpSuggestions: [],
|
|
17779
|
+
taskProposals: [],
|
|
17780
|
+
taskUpdateProposals: [],
|
|
17781
|
+
taskEvents: [],
|
|
17782
|
+
pendingTaskUpdateJobs: [],
|
|
17783
|
+
subChatEvents: [],
|
|
17784
|
+
appSettingsMemoryRequests: [],
|
|
17785
|
+
tokenUsage: null,
|
|
17786
|
+
promptBudget: null
|
|
17787
|
+
};
|
|
17788
|
+
}
|
|
17789
|
+
const resp = outcome.response;
|
|
17790
|
+
assistantMessageId = resp.messageId;
|
|
17791
|
+
assistant = resp.content;
|
|
17792
|
+
category = resp.category;
|
|
17793
|
+
modelName = resp.modelName;
|
|
17794
|
+
tokenUsage = resp.tokenUsage;
|
|
17795
|
+
promptBudget = resp.promptBudget;
|
|
17796
|
+
followUpSuggestions = resp.followUpSuggestions;
|
|
17797
|
+
suggestedFollowupMessage = resp.suggestedFollowupMessage;
|
|
17798
|
+
taskProposals = resp.taskProposals;
|
|
17799
|
+
taskUpdateProposals = resp.taskUpdateProposals;
|
|
17800
|
+
taskEvents = resp.taskEvents;
|
|
17801
|
+
pendingTaskUpdateJobs = taskUpdateJobsEnabled ? resp.pendingTaskUpdateJobs : [];
|
|
17802
|
+
subChatEvents = resp.subChatEvents;
|
|
17803
|
+
if (resp.status === "waiting_for_user") {
|
|
17785
17804
|
await persistTaskEventSystemMessages(taskEvents);
|
|
17786
17805
|
await recoverCurrentTurnOutputs();
|
|
17787
|
-
await projectFocusCountdown?.flush();
|
|
17788
|
-
await focusPersistence;
|
|
17789
|
-
await chatContextEvents?.flush();
|
|
17790
|
-
if (focusPersistenceError) throw focusPersistenceError;
|
|
17791
|
-
const mateName2 = category ? MATE_NAMES[category] ?? null : null;
|
|
17792
17806
|
return {
|
|
17793
|
-
status:
|
|
17807
|
+
status: resp.status,
|
|
17794
17808
|
chatId,
|
|
17795
17809
|
messageId: assistantMessageId,
|
|
17796
17810
|
assistant,
|
|
17797
17811
|
category,
|
|
17798
17812
|
modelName,
|
|
17799
|
-
mateName:
|
|
17813
|
+
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
17800
17814
|
followUpSuggestions,
|
|
17801
17815
|
taskProposals,
|
|
17802
17816
|
taskUpdateProposals,
|
|
@@ -17808,231 +17822,333 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
17808
17822
|
promptBudget
|
|
17809
17823
|
};
|
|
17810
17824
|
}
|
|
17811
|
-
if (
|
|
17812
|
-
|
|
17813
|
-
|
|
17814
|
-
|
|
17815
|
-
|
|
17816
|
-
|
|
17817
|
-
|
|
17818
|
-
|
|
17819
|
-
|
|
17820
|
-
|
|
17821
|
-
|
|
17822
|
-
|
|
17823
|
-
|
|
17824
|
-
|
|
17825
|
-
|
|
17826
|
-
|
|
17827
|
-
|
|
17828
|
-
|
|
17829
|
-
|
|
17825
|
+
if (chatKeyBytes) {
|
|
17826
|
+
if (!resp.recoveryJobId || !savedTurnId || !ownerId2) {
|
|
17827
|
+
throw new Error("Saved chat completion did not include recoverable terminal identity.");
|
|
17828
|
+
}
|
|
17829
|
+
const recoveryJobId = resp.recoveryJobId;
|
|
17830
|
+
let claimRetryDeadline = null;
|
|
17831
|
+
let claim;
|
|
17832
|
+
let claimRetryDelayMs = 500;
|
|
17833
|
+
while (true) {
|
|
17834
|
+
const requestId = randomUUID9();
|
|
17835
|
+
let matchingLeaseConflict = false;
|
|
17836
|
+
const stopWatchingErrors = ws.onMessageType("error", (payload) => {
|
|
17837
|
+
if (payload?.code === "lease_conflict" && payload.job_id === recoveryJobId && payload.request_id === requestId) {
|
|
17838
|
+
matchingLeaseConflict = true;
|
|
17839
|
+
}
|
|
17840
|
+
});
|
|
17841
|
+
const claimPromise = ws.waitForMessage(
|
|
17842
|
+
"recovery_job_claimed",
|
|
17843
|
+
(payload) => {
|
|
17844
|
+
const received = payload;
|
|
17845
|
+
return received.job_id === recoveryJobId && (received.request_id === void 0 || received.request_id === requestId);
|
|
17846
|
+
},
|
|
17847
|
+
claimRetryDeadline === null ? 2e4 : Math.min(2e4, Math.max(1, claimRetryDeadline - Date.now()))
|
|
17848
|
+
);
|
|
17849
|
+
try {
|
|
17850
|
+
try {
|
|
17851
|
+
await ws.sendAsync("recovery_job_claim", {
|
|
17852
|
+
protocol_version: 1,
|
|
17853
|
+
job_id: recoveryJobId,
|
|
17854
|
+
request_id: requestId
|
|
17855
|
+
});
|
|
17856
|
+
} catch (error) {
|
|
17857
|
+
void claimPromise.catch(() => {
|
|
17858
|
+
});
|
|
17859
|
+
throw error;
|
|
17860
|
+
}
|
|
17861
|
+
claim = (await claimPromise).payload;
|
|
17862
|
+
break;
|
|
17863
|
+
} catch (error) {
|
|
17864
|
+
if (!(error instanceof WebSocketProtocolError) || error.code !== "lease_conflict" || !matchingLeaseConflict) throw error;
|
|
17865
|
+
claimRetryDeadline ??= Date.now() + Math.min(
|
|
17866
|
+
1e4,
|
|
17867
|
+
Math.max(0, params.responseTimeoutMs ?? 9e4)
|
|
17868
|
+
);
|
|
17869
|
+
const remainingMs = claimRetryDeadline - Date.now();
|
|
17870
|
+
if (remainingMs <= claimRetryDelayMs) throw error;
|
|
17871
|
+
await new Promise((resolve20) => setTimeout(resolve20, claimRetryDelayMs));
|
|
17872
|
+
claimRetryDelayMs = Math.min(claimRetryDelayMs * 2, 2e3);
|
|
17873
|
+
} finally {
|
|
17874
|
+
stopWatchingErrors();
|
|
17875
|
+
}
|
|
17876
|
+
}
|
|
17877
|
+
const leaseToken = typeof claim.lease_token === "string" ? claim.lease_token : null;
|
|
17878
|
+
const leaseGeneration = typeof claim.lease_generation === "number" && Number.isSafeInteger(claim.lease_generation) ? claim.lease_generation : null;
|
|
17879
|
+
const assistantId = typeof claim.assistant_message_id === "string" ? claim.assistant_message_id : null;
|
|
17880
|
+
const keyVersion = typeof claim.chat_key_version === "number" && Number.isSafeInteger(claim.chat_key_version) ? claim.chat_key_version : null;
|
|
17881
|
+
const claimMatchesExpectedTerminal = assistantId && keyVersion === 1 && claim.chat_id === chatId && claim.turn_id === savedTurnId;
|
|
17882
|
+
if (claim.state === "TERMINAL" && claimMatchesExpectedTerminal) {
|
|
17883
|
+
assistant = resp.content;
|
|
17884
|
+
category = resp.category;
|
|
17885
|
+
modelName = resp.modelName;
|
|
17886
|
+
assistantMessageId = assistantId;
|
|
17887
|
+
await this.persistStreamedEmbeds({
|
|
17888
|
+
ws,
|
|
17889
|
+
embeds: resp.embeds,
|
|
17890
|
+
chatId,
|
|
17891
|
+
chatKeyBytes,
|
|
17892
|
+
fallbackMessageId: assistantId,
|
|
17893
|
+
ownerId: ownerId2,
|
|
17894
|
+
teamId
|
|
17895
|
+
});
|
|
17896
|
+
clearSyncCache(teamId);
|
|
17897
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
17898
|
+
await persistTaskEventSystemMessages(taskEvents);
|
|
17899
|
+
await recoverCurrentTurnOutputs();
|
|
17900
|
+
await projectFocusCountdown?.flush();
|
|
17901
|
+
await focusPersistence;
|
|
17902
|
+
await chatContextEvents?.flush();
|
|
17903
|
+
if (focusPersistenceError) throw focusPersistenceError;
|
|
17904
|
+
const mateName2 = category ? MATE_NAMES[category] ?? null : null;
|
|
17905
|
+
return {
|
|
17906
|
+
status: "completed",
|
|
17907
|
+
chatId,
|
|
17908
|
+
messageId: assistantMessageId,
|
|
17909
|
+
assistant,
|
|
17910
|
+
category,
|
|
17911
|
+
modelName,
|
|
17912
|
+
mateName: mateName2,
|
|
17913
|
+
followUpSuggestions,
|
|
17914
|
+
taskProposals,
|
|
17915
|
+
taskUpdateProposals,
|
|
17916
|
+
taskEvents,
|
|
17917
|
+
pendingTaskUpdateJobs,
|
|
17918
|
+
subChatEvents,
|
|
17919
|
+
appSettingsMemoryRequests,
|
|
17920
|
+
tokenUsage,
|
|
17921
|
+
promptBudget
|
|
17922
|
+
};
|
|
17923
|
+
}
|
|
17924
|
+
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") {
|
|
17925
|
+
throw new Error("Recovery job claim returned invalid lease or identity data.");
|
|
17926
|
+
}
|
|
17927
|
+
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
17928
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
17830
17929
|
chatId,
|
|
17831
|
-
turnId: savedTurnId,
|
|
17832
|
-
jobId: recoveryJobId,
|
|
17833
|
-
assistantMessageId: assistantId,
|
|
17834
17930
|
keyVersion
|
|
17931
|
+
);
|
|
17932
|
+
let envelope;
|
|
17933
|
+
try {
|
|
17934
|
+
envelope = JSON.parse(claim.sealed_payload);
|
|
17935
|
+
} catch {
|
|
17936
|
+
throw new Error("Recovery job contained an invalid sealed envelope.");
|
|
17835
17937
|
}
|
|
17836
|
-
|
|
17837
|
-
|
|
17838
|
-
|
|
17839
|
-
|
|
17840
|
-
|
|
17841
|
-
|
|
17842
|
-
|
|
17843
|
-
|
|
17844
|
-
|
|
17845
|
-
|
|
17846
|
-
|
|
17847
|
-
|
|
17848
|
-
|
|
17849
|
-
|
|
17850
|
-
|
|
17851
|
-
|
|
17852
|
-
|
|
17853
|
-
if (Object.keys(recovered).filter((field3) => ![
|
|
17854
|
-
"thinking_content",
|
|
17855
|
-
"thinking_signature",
|
|
17856
|
-
"has_thinking",
|
|
17857
|
-
"thinking_token_count",
|
|
17858
|
-
"thinking_duration_seconds"
|
|
17859
|
-
].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") {
|
|
17860
|
-
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
17861
|
-
}
|
|
17862
|
-
assistant = recovered.content;
|
|
17863
|
-
category = recovered.category;
|
|
17864
|
-
modelName = recovered.model_name;
|
|
17865
|
-
const completedAt = resp.recoveryCreatedAt ?? Math.floor(Date.now() / 1e3);
|
|
17866
|
-
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
17867
|
-
assistant,
|
|
17868
|
-
chatKeyBytes
|
|
17869
|
-
);
|
|
17870
|
-
const encryptedSenderName = await encryptWithAesGcmCombined(category ? MATE_NAMES[category] ?? "Assistant" : "Assistant", chatKeyBytes);
|
|
17871
|
-
const encryptedCategory = recovered.category ? await encryptWithAesGcmCombined(recovered.category, chatKeyBytes) : void 0;
|
|
17872
|
-
const encryptedModelName = recovered.model_name ? await encryptWithAesGcmCombined(recovered.model_name, chatKeyBytes) : void 0;
|
|
17873
|
-
const terminalPersistPayload = {
|
|
17874
|
-
protocol_version: 1,
|
|
17875
|
-
job_id: recoveryJobId,
|
|
17876
|
-
lease_token: leaseToken,
|
|
17877
|
-
lease_generation: leaseGeneration,
|
|
17878
|
-
encrypted_assistant_message: {
|
|
17879
|
-
client_message_id: assistantId,
|
|
17880
|
-
chat_id: chatId,
|
|
17881
|
-
encrypted_content: encryptedAssistantContent,
|
|
17882
|
-
...await this.encryptThinkingFields(recovered, chatKeyBytes),
|
|
17883
|
-
encrypted_sender_name: encryptedSenderName,
|
|
17884
|
-
encrypted_category: encryptedCategory,
|
|
17885
|
-
encrypted_model_name: encryptedModelName,
|
|
17886
|
-
role: "assistant",
|
|
17887
|
-
user_message_id: messageId2,
|
|
17888
|
-
created_at: completedAt,
|
|
17889
|
-
updated_at: completedAt
|
|
17938
|
+
const plaintext = await openChatCompletionRecoveryEnvelope(
|
|
17939
|
+
envelope,
|
|
17940
|
+
{
|
|
17941
|
+
recoveryPrivateKey: recoveryKeypair.privateKey,
|
|
17942
|
+
ownerId: ownerId2,
|
|
17943
|
+
chatId,
|
|
17944
|
+
turnId: savedTurnId,
|
|
17945
|
+
jobId: recoveryJobId,
|
|
17946
|
+
assistantMessageId: assistantId,
|
|
17947
|
+
keyVersion
|
|
17948
|
+
}
|
|
17949
|
+
);
|
|
17950
|
+
let recovered;
|
|
17951
|
+
try {
|
|
17952
|
+
recovered = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(plaintext));
|
|
17953
|
+
} catch {
|
|
17954
|
+
throw new Error("Recovery job plaintext was not valid UTF-8 JSON.");
|
|
17890
17955
|
}
|
|
17891
|
-
|
|
17892
|
-
|
|
17893
|
-
|
|
17894
|
-
"
|
|
17895
|
-
|
|
17896
|
-
|
|
17956
|
+
const expectedRecoveryFields = [
|
|
17957
|
+
"assistant_message_id",
|
|
17958
|
+
"category",
|
|
17959
|
+
"chat_id",
|
|
17960
|
+
"content",
|
|
17961
|
+
"job_id",
|
|
17962
|
+
"key_version",
|
|
17963
|
+
"model_name",
|
|
17964
|
+
"turn_id"
|
|
17965
|
+
];
|
|
17966
|
+
if (Object.keys(recovered).filter((field3) => ![
|
|
17967
|
+
"thinking_content",
|
|
17968
|
+
"thinking_signature",
|
|
17969
|
+
"has_thinking",
|
|
17970
|
+
"thinking_token_count",
|
|
17971
|
+
"thinking_duration_seconds"
|
|
17972
|
+
].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") {
|
|
17973
|
+
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
17974
|
+
}
|
|
17975
|
+
assistant = recovered.content;
|
|
17976
|
+
category = recovered.category;
|
|
17977
|
+
modelName = recovered.model_name;
|
|
17978
|
+
const completedAt = resp.recoveryCreatedAt ?? Math.floor(Date.now() / 1e3);
|
|
17979
|
+
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
17980
|
+
assistant,
|
|
17981
|
+
chatKeyBytes
|
|
17897
17982
|
);
|
|
17898
|
-
await
|
|
17899
|
-
|
|
17900
|
-
|
|
17901
|
-
|
|
17902
|
-
|
|
17903
|
-
|
|
17904
|
-
|
|
17905
|
-
|
|
17906
|
-
|
|
17907
|
-
|
|
17908
|
-
|
|
17909
|
-
|
|
17910
|
-
|
|
17911
|
-
|
|
17912
|
-
|
|
17913
|
-
|
|
17914
|
-
|
|
17915
|
-
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
{ skipTypedRecoveryOutputReplay: true }
|
|
17919
|
-
);
|
|
17920
|
-
const latestChat = latestCache.chats.find(
|
|
17921
|
-
(chat) => String(chat.details.id ?? "") === chatId
|
|
17922
|
-
);
|
|
17923
|
-
const latestMessagesV = typeof latestChat?.details.messages_v === "number" && Number.isSafeInteger(latestChat.details.messages_v) ? latestChat.details.messages_v : null;
|
|
17924
|
-
if (latestMessagesV !== null && latestMessagesV > terminalExpectedMessagesV) {
|
|
17925
|
-
await persistTerminalRecovery(latestMessagesV);
|
|
17926
|
-
terminalExpectedMessagesV = latestMessagesV;
|
|
17927
|
-
terminalPersisted = true;
|
|
17928
|
-
}
|
|
17983
|
+
const encryptedSenderName = await encryptWithAesGcmCombined(category ? MATE_NAMES[category] ?? "Assistant" : "Assistant", chatKeyBytes);
|
|
17984
|
+
const encryptedCategory = recovered.category ? await encryptWithAesGcmCombined(recovered.category, chatKeyBytes) : void 0;
|
|
17985
|
+
const encryptedModelName = recovered.model_name ? await encryptWithAesGcmCombined(recovered.model_name, chatKeyBytes) : void 0;
|
|
17986
|
+
const terminalPersistPayload = {
|
|
17987
|
+
protocol_version: 1,
|
|
17988
|
+
job_id: recoveryJobId,
|
|
17989
|
+
lease_token: leaseToken,
|
|
17990
|
+
lease_generation: leaseGeneration,
|
|
17991
|
+
encrypted_assistant_message: {
|
|
17992
|
+
client_message_id: assistantId,
|
|
17993
|
+
chat_id: chatId,
|
|
17994
|
+
encrypted_content: encryptedAssistantContent,
|
|
17995
|
+
...await this.encryptThinkingFields(recovered, chatKeyBytes),
|
|
17996
|
+
encrypted_sender_name: encryptedSenderName,
|
|
17997
|
+
encrypted_category: encryptedCategory,
|
|
17998
|
+
encrypted_model_name: encryptedModelName,
|
|
17999
|
+
role: "assistant",
|
|
18000
|
+
user_message_id: messageId2,
|
|
18001
|
+
created_at: completedAt,
|
|
18002
|
+
updated_at: completedAt
|
|
17929
18003
|
}
|
|
17930
|
-
|
|
17931
|
-
|
|
18004
|
+
};
|
|
18005
|
+
const persistTerminalRecovery = async (expectedMessagesV) => {
|
|
18006
|
+
const persistedPromise = ws.waitForMessage(
|
|
18007
|
+
"recovery_job_persisted",
|
|
18008
|
+
(payload) => payload.job_id === recoveryJobId,
|
|
18009
|
+
2e4
|
|
18010
|
+
);
|
|
18011
|
+
await ws.sendAsync("recovery_job_persist", {
|
|
18012
|
+
...terminalPersistPayload,
|
|
18013
|
+
expected_messages_v: expectedMessagesV
|
|
18014
|
+
});
|
|
18015
|
+
await persistedPromise;
|
|
18016
|
+
};
|
|
18017
|
+
let terminalPersisted = false;
|
|
18018
|
+
try {
|
|
18019
|
+
await persistTerminalRecovery(terminalExpectedMessagesV);
|
|
18020
|
+
terminalPersisted = true;
|
|
18021
|
+
} catch (error) {
|
|
18022
|
+
if (error instanceof WebSocketProtocolError) {
|
|
18023
|
+
if (error.code === "version_conflict") {
|
|
18024
|
+
const latestCache = await this.ensureSynced(
|
|
18025
|
+
true,
|
|
18026
|
+
[chatId],
|
|
18027
|
+
{ teamId },
|
|
18028
|
+
false,
|
|
18029
|
+
void 0,
|
|
18030
|
+
void 0,
|
|
18031
|
+
{ skipTypedRecoveryOutputReplay: true }
|
|
18032
|
+
);
|
|
18033
|
+
const latestChat = latestCache.chats.find(
|
|
18034
|
+
(chat) => String(chat.details.id ?? "") === chatId
|
|
18035
|
+
);
|
|
18036
|
+
const latestMessagesV = typeof latestChat?.details.messages_v === "number" && Number.isSafeInteger(latestChat.details.messages_v) ? latestChat.details.messages_v : null;
|
|
18037
|
+
if (latestMessagesV !== null && latestMessagesV > terminalExpectedMessagesV) {
|
|
18038
|
+
await persistTerminalRecovery(latestMessagesV);
|
|
18039
|
+
terminalExpectedMessagesV = latestMessagesV;
|
|
18040
|
+
terminalPersisted = true;
|
|
18041
|
+
}
|
|
18042
|
+
}
|
|
18043
|
+
if (!terminalPersisted) {
|
|
18044
|
+
throw new Error(`${error.message} (${error.code})`);
|
|
18045
|
+
}
|
|
18046
|
+
} else {
|
|
18047
|
+
throw error;
|
|
17932
18048
|
}
|
|
17933
|
-
} else {
|
|
17934
|
-
throw error;
|
|
17935
18049
|
}
|
|
17936
|
-
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
17941
|
-
await this.persistStreamedEmbeds({
|
|
17942
|
-
ws,
|
|
17943
|
-
embeds: resp.embeds,
|
|
17944
|
-
chatId,
|
|
17945
|
-
chatKeyBytes,
|
|
17946
|
-
fallbackMessageId: assistantId,
|
|
17947
|
-
ownerId: ownerId2,
|
|
17948
|
-
teamId
|
|
17949
|
-
});
|
|
17950
|
-
if (initialHeaderWork) persistOptionalHeader = () => initialHeaderWork;
|
|
17951
|
-
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
17952
|
-
if (taskUpdateJobsEnabled) {
|
|
17953
|
-
const persistedTaskJobIds = await this.persistPendingTaskUpdateJobs({
|
|
18050
|
+
if (!terminalPersisted) {
|
|
18051
|
+
throw new Error("Encrypted completion recovery did not persist terminal state.");
|
|
18052
|
+
}
|
|
18053
|
+
assistantMessageId = assistantId;
|
|
18054
|
+
await this.persistStreamedEmbeds({
|
|
17954
18055
|
ws,
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
requireActiveTurnEvent: true
|
|
18056
|
+
embeds: resp.embeds,
|
|
18057
|
+
chatId,
|
|
18058
|
+
chatKeyBytes,
|
|
18059
|
+
fallbackMessageId: assistantId,
|
|
18060
|
+
ownerId: ownerId2,
|
|
18061
|
+
teamId
|
|
17962
18062
|
});
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
17968
|
-
|
|
17969
|
-
|
|
17970
|
-
|
|
17971
|
-
|
|
17972
|
-
|
|
17973
|
-
|
|
17974
|
-
|
|
17975
|
-
|
|
17976
|
-
|
|
17977
|
-
|
|
17978
|
-
|
|
17979
|
-
|
|
17980
|
-
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
17984
|
-
const recommendations = await assessCliProjectAuthoring(this, projectAuthoringAvailable, history);
|
|
17985
|
-
for (const event of recommendations) chatContextEvents.persist(event);
|
|
17986
|
-
} catch {
|
|
18063
|
+
if (initialHeaderWork) persistOptionalHeader = () => initialHeaderWork;
|
|
18064
|
+
await persistCompressionCheckpoints(ws, chatKeyBytes, resp.compressionCheckpoints);
|
|
18065
|
+
if (taskUpdateJobsEnabled) {
|
|
18066
|
+
const persistedTaskJobIds = await this.persistPendingTaskUpdateJobs({
|
|
18067
|
+
ws,
|
|
18068
|
+
jobs: pendingTaskUpdateJobs,
|
|
18069
|
+
events: taskEvents,
|
|
18070
|
+
teamId,
|
|
18071
|
+
activeChatId: chatId,
|
|
18072
|
+
activeChatKeyBytes: chatKeyBytes,
|
|
18073
|
+
fallbackUserMessageId: messageId2,
|
|
18074
|
+
requireActiveTurnEvent: true
|
|
18075
|
+
});
|
|
18076
|
+
pendingTaskUpdateJobs = pendingTaskUpdateJobs.filter((job) => !persistedTaskJobIds.has(job.job_id));
|
|
18077
|
+
}
|
|
18078
|
+
await persistTaskEventSystemMessages(taskEvents);
|
|
18079
|
+
await recoverCurrentTurnOutputs();
|
|
18080
|
+
clearSyncCache(teamId);
|
|
18081
|
+
if (optionalMetadata) {
|
|
18082
|
+
metadataHandoffReady = true;
|
|
18083
|
+
metadataHandoffSuggestion = resp.suggestedFollowupMessage;
|
|
17987
18084
|
}
|
|
17988
18085
|
}
|
|
17989
|
-
await chatContextEvents?.flush();
|
|
17990
18086
|
} finally {
|
|
17991
|
-
|
|
17992
|
-
|
|
17993
|
-
|
|
18087
|
+
try {
|
|
18088
|
+
await projectFocusCountdown?.flush();
|
|
18089
|
+
await focusPersistence;
|
|
18090
|
+
if (projectAuthoringAvailable && assistant && chatContextEvents) {
|
|
18091
|
+
try {
|
|
18092
|
+
const history = boundedAuthoringHistory([
|
|
18093
|
+
...messageHistoryForRequest ?? [],
|
|
18094
|
+
{ role: "user", content: finalMessage },
|
|
18095
|
+
{ role: "assistant", content: assistant }
|
|
18096
|
+
]);
|
|
18097
|
+
const recommendations = await assessCliProjectAuthoring(this, projectAuthoringAvailable, history);
|
|
18098
|
+
for (const event of recommendations) chatContextEvents.persist(event);
|
|
18099
|
+
} catch {
|
|
18100
|
+
}
|
|
18101
|
+
}
|
|
18102
|
+
await chatContextEvents?.flush();
|
|
18103
|
+
} finally {
|
|
18104
|
+
projectFocusCountdown?.();
|
|
18105
|
+
chatContextEvents?.stop();
|
|
18106
|
+
if (!metadataHandoffReady) ws.close();
|
|
18107
|
+
}
|
|
18108
|
+
}
|
|
18109
|
+
if (focusPersistenceError) throw focusPersistenceError;
|
|
18110
|
+
if (metadataHandoffReady && optionalMetadata) {
|
|
18111
|
+
metadataTransportOwned = optionalMetadata.handOff(metadataHandoffSuggestion, persistOptionalHeader);
|
|
18112
|
+
if (!metadataTransportOwned) ws.close();
|
|
17994
18113
|
}
|
|
17995
18114
|
}
|
|
17996
|
-
|
|
17997
|
-
|
|
17998
|
-
|
|
17999
|
-
|
|
18115
|
+
const mateName = category ? MATE_NAMES[category] ?? null : null;
|
|
18116
|
+
return {
|
|
18117
|
+
status: "completed",
|
|
18118
|
+
chatId,
|
|
18119
|
+
messageId: assistantMessageId,
|
|
18120
|
+
userMessageId: messageId2,
|
|
18121
|
+
assistant,
|
|
18122
|
+
category,
|
|
18123
|
+
modelName,
|
|
18124
|
+
mateName,
|
|
18125
|
+
followUpSuggestions,
|
|
18126
|
+
suggestedFollowupMessage,
|
|
18127
|
+
taskProposals,
|
|
18128
|
+
taskUpdateProposals,
|
|
18129
|
+
taskEvents,
|
|
18130
|
+
pendingTaskUpdateJobs,
|
|
18131
|
+
subChatEvents,
|
|
18132
|
+
appSettingsMemoryRequests,
|
|
18133
|
+
tokenUsage,
|
|
18134
|
+
promptBudget
|
|
18135
|
+
};
|
|
18136
|
+
} finally {
|
|
18137
|
+
removeContextAckWatcher?.();
|
|
18138
|
+
earlyContextAbort.abort();
|
|
18139
|
+
if (!metadataTransportOwned) {
|
|
18140
|
+
initialHeaderStopped = true;
|
|
18141
|
+
optionalMetadata?.stop();
|
|
18142
|
+
ws.close();
|
|
18000
18143
|
}
|
|
18144
|
+
if (earlyContext) await earlyContext;
|
|
18001
18145
|
}
|
|
18002
|
-
|
|
18003
|
-
|
|
18004
|
-
|
|
18005
|
-
chatId,
|
|
18006
|
-
messageId: assistantMessageId,
|
|
18007
|
-
userMessageId: messageId2,
|
|
18008
|
-
assistant,
|
|
18009
|
-
category,
|
|
18010
|
-
modelName,
|
|
18011
|
-
mateName,
|
|
18012
|
-
followUpSuggestions,
|
|
18013
|
-
suggestedFollowupMessage,
|
|
18014
|
-
taskProposals,
|
|
18015
|
-
taskUpdateProposals,
|
|
18016
|
-
taskEvents,
|
|
18017
|
-
pendingTaskUpdateJobs,
|
|
18018
|
-
subChatEvents,
|
|
18019
|
-
appSettingsMemoryRequests,
|
|
18020
|
-
tokenUsage,
|
|
18021
|
-
promptBudget
|
|
18022
|
-
};
|
|
18023
|
-
} finally {
|
|
18024
|
-
removeContextAckWatcher?.();
|
|
18025
|
-
earlyContextAbort.abort();
|
|
18026
|
-
if (!metadataTransportOwned) {
|
|
18027
|
-
initialHeaderStopped = true;
|
|
18028
|
-
optionalMetadata?.stop();
|
|
18029
|
-
ws.close();
|
|
18030
|
-
}
|
|
18031
|
-
if (earlyContext) await earlyContext;
|
|
18146
|
+
} catch (error) {
|
|
18147
|
+
memorySocket?.ws.close();
|
|
18148
|
+
throw error;
|
|
18032
18149
|
}
|
|
18033
|
-
}
|
|
18034
|
-
|
|
18035
|
-
throw error;
|
|
18150
|
+
} finally {
|
|
18151
|
+
historyAbort.abort();
|
|
18036
18152
|
}
|
|
18037
18153
|
}
|
|
18038
18154
|
async persistEncryptedSystemMessage(ws, systemMessage, targetChatId) {
|
|
@@ -20337,6 +20453,25 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
20337
20453
|
// -------------------------------------------------------------------------
|
|
20338
20454
|
// User tasks
|
|
20339
20455
|
// -------------------------------------------------------------------------
|
|
20456
|
+
/** One fresh authorized discovery page; never an exhaustive Task inventory. */
|
|
20457
|
+
async listUserTaskDiscoveryPage(options = {}) {
|
|
20458
|
+
const owner2 = this.captureTeamAccountOwner();
|
|
20459
|
+
const activeTeamId = this.getActiveTeamId();
|
|
20460
|
+
const teamId = this.resolveTeamContext(options);
|
|
20461
|
+
const params = new URLSearchParams({ paginate: "true", limit: "100" });
|
|
20462
|
+
if (teamId) params.set("team_id", teamId);
|
|
20463
|
+
const response = await this.http.get(
|
|
20464
|
+
`/v1/user-tasks?${params}`,
|
|
20465
|
+
this.getCliRequestHeaders()
|
|
20466
|
+
);
|
|
20467
|
+
if (!owner2.isCurrent() || this.getActiveTeamId() !== activeTeamId || this.resolveTeamContext(options) !== teamId) throw new Error("Task context workspace changed");
|
|
20468
|
+
if (!response.ok) throw new Error(`Task discovery failed with HTTP ${response.status}`);
|
|
20469
|
+
const rows2 = response.data?.tasks;
|
|
20470
|
+
if (!Array.isArray(rows2) || rows2.length > 100 || rows2.some((row3) => !row3 || typeof row3.task_id !== "string" || !row3.task_id || row3.task_id.length > 128)) {
|
|
20471
|
+
throw new Error("Invalid bounded Task discovery page");
|
|
20472
|
+
}
|
|
20473
|
+
return rows2.filter((row3) => row3.source !== "workflow_run");
|
|
20474
|
+
}
|
|
20340
20475
|
async listUserTasks(filters = {}, options = {}) {
|
|
20341
20476
|
const session = this.requireSession();
|
|
20342
20477
|
const owner2 = [
|
|
@@ -22908,6 +23043,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
22908
23043
|
}
|
|
22909
23044
|
interactiveViewerChatId = null;
|
|
22910
23045
|
interactiveViewerSocket = null;
|
|
23046
|
+
interactiveViewerOpenController = null;
|
|
22911
23047
|
interactiveViewerAllowed = false;
|
|
22912
23048
|
interactiveViewerGeneration = 0;
|
|
22913
23049
|
beginInteractiveViewerSession() {
|
|
@@ -22935,8 +23071,10 @@ Required: ${schema.required.join(", ")}`
|
|
|
22935
23071
|
this.clearInteractiveChatViewer();
|
|
22936
23072
|
this.interactiveViewerChatId = chatId;
|
|
22937
23073
|
const generation = this.interactiveViewerGeneration;
|
|
23074
|
+
const controller = new AbortController();
|
|
23075
|
+
this.interactiveViewerOpenController = controller;
|
|
22938
23076
|
try {
|
|
22939
|
-
const { ws } = await this.openWsClient({ taskUpdateJobs: false, interactiveHuman: true });
|
|
23077
|
+
const { ws } = await this.openWsClient({ taskUpdateJobs: false, interactiveHuman: true, signal: controller.signal });
|
|
22940
23078
|
if (!this.interactiveViewerAllowed || this.interactiveViewerGeneration !== generation || this.interactiveViewerChatId !== chatId) {
|
|
22941
23079
|
ws.close();
|
|
22942
23080
|
return;
|
|
@@ -22958,9 +23096,14 @@ Required: ${schema.required.join(", ")}`
|
|
|
22958
23096
|
if (this.interactiveViewerGeneration === generation && this.interactiveViewerChatId === chatId) void this.setInteractiveChatViewer(chatId);
|
|
22959
23097
|
}, 1e3);
|
|
22960
23098
|
retry.unref?.();
|
|
23099
|
+
} finally {
|
|
23100
|
+
if (this.interactiveViewerOpenController === controller) this.interactiveViewerOpenController = null;
|
|
22961
23101
|
}
|
|
22962
23102
|
}
|
|
22963
23103
|
clearInteractiveChatViewer() {
|
|
23104
|
+
const controller = this.interactiveViewerOpenController;
|
|
23105
|
+
this.interactiveViewerOpenController = null;
|
|
23106
|
+
controller?.abort();
|
|
22964
23107
|
this.interactiveViewerGeneration = (this.interactiveViewerGeneration ?? 0) + 1;
|
|
22965
23108
|
this.interactiveViewerChatId = null;
|
|
22966
23109
|
const ws = this.interactiveViewerSocket;
|