openmates 0.14.8-alpha.1 → 0.14.8-alpha.11
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-RR6H47VR.js → chunk-6URDLYLM.js} +810 -265
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +52 -12
- package/dist/index.js +2 -2
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-AXNRPVLE.js";
|
|
5
5
|
|
|
6
6
|
// src/client.ts
|
|
7
|
-
import { randomUUID as
|
|
7
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
8
8
|
import { arch, platform as platform2, release } from "os";
|
|
9
9
|
import { createInterface } from "readline/promises";
|
|
10
10
|
import { stdin, stdout } from "process";
|
|
@@ -1214,6 +1214,42 @@ function websocketProtocolError(envelope) {
|
|
|
1214
1214
|
}
|
|
1215
1215
|
return null;
|
|
1216
1216
|
}
|
|
1217
|
+
function errorFrameBelongsToAiResponse(envelope, userMessageId, chatId, recoveryTurnId) {
|
|
1218
|
+
if (envelope.type === "client_update_required") return true;
|
|
1219
|
+
if (envelope.type !== "error") return true;
|
|
1220
|
+
const payload = envelope.payload && typeof envelope.payload === "object" ? envelope.payload : {};
|
|
1221
|
+
let scoped = false;
|
|
1222
|
+
const errorChatId = typeof payload.chat_id === "string" ? payload.chat_id : null;
|
|
1223
|
+
if (errorChatId) {
|
|
1224
|
+
scoped = true;
|
|
1225
|
+
if (errorChatId !== chatId) return false;
|
|
1226
|
+
}
|
|
1227
|
+
const errorMessageId = typeof payload.user_message_id === "string" ? payload.user_message_id : typeof payload.userMessageId === "string" ? payload.userMessageId : typeof payload.message_id === "string" ? payload.message_id : null;
|
|
1228
|
+
if (errorMessageId) {
|
|
1229
|
+
scoped = true;
|
|
1230
|
+
if (errorMessageId !== userMessageId) return false;
|
|
1231
|
+
}
|
|
1232
|
+
const errorTurnId = typeof payload.turn_id === "string" ? payload.turn_id : null;
|
|
1233
|
+
if (errorTurnId) {
|
|
1234
|
+
scoped = true;
|
|
1235
|
+
if (recoveryTurnId !== errorTurnId) return false;
|
|
1236
|
+
}
|
|
1237
|
+
if (typeof payload.job_id === "string") {
|
|
1238
|
+
scoped = true;
|
|
1239
|
+
}
|
|
1240
|
+
return !scoped || Boolean(errorChatId || errorMessageId || errorTurnId);
|
|
1241
|
+
}
|
|
1242
|
+
function scopedErrorMissesPredicate(envelope, predicate) {
|
|
1243
|
+
if (envelope.type !== "error" || !predicate) return false;
|
|
1244
|
+
const payload = envelope.payload && typeof envelope.payload === "object" ? envelope.payload : {};
|
|
1245
|
+
const hasScope = typeof payload.turn_id === "string" || typeof payload.chat_id === "string" || typeof payload.message_id === "string" || typeof payload.user_message_id === "string" || typeof payload.userMessageId === "string" || typeof payload.job_id === "string";
|
|
1246
|
+
if (!hasScope) return false;
|
|
1247
|
+
try {
|
|
1248
|
+
return !predicate(payload);
|
|
1249
|
+
} catch {
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1217
1253
|
function parseTaskProposals(value) {
|
|
1218
1254
|
if (!Array.isArray(value)) return [];
|
|
1219
1255
|
return value.flatMap((item) => {
|
|
@@ -1241,14 +1277,69 @@ function parseTaskUpdateProposals(value) {
|
|
|
1241
1277
|
return [proposal];
|
|
1242
1278
|
});
|
|
1243
1279
|
}
|
|
1280
|
+
function parseTaskEvent(value) {
|
|
1281
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1282
|
+
const raw = value;
|
|
1283
|
+
if (typeof raw.event_id !== "string" || typeof raw.chat_id !== "string") return null;
|
|
1284
|
+
if (typeof raw.task_id !== "string" || typeof raw.event_type !== "string") return null;
|
|
1285
|
+
const event = {
|
|
1286
|
+
event_id: raw.event_id,
|
|
1287
|
+
chat_id: raw.chat_id,
|
|
1288
|
+
task_id: raw.task_id,
|
|
1289
|
+
event_type: raw.event_type
|
|
1290
|
+
};
|
|
1291
|
+
if (typeof raw.short_id === "string" || raw.short_id === null) event.short_id = raw.short_id;
|
|
1292
|
+
if (typeof raw.title === "string" || raw.title === null) event.title = raw.title;
|
|
1293
|
+
if (typeof raw.status === "string" || raw.status === null) event.status = raw.status;
|
|
1294
|
+
if (typeof raw.reason === "string" || raw.reason === null) event.reason = raw.reason;
|
|
1295
|
+
if (typeof raw.created_at === "number" || raw.created_at === null) event.created_at = raw.created_at;
|
|
1296
|
+
if (typeof raw.task_update_job_id === "string" || raw.task_update_job_id === null) event.task_update_job_id = raw.task_update_job_id;
|
|
1297
|
+
return event;
|
|
1298
|
+
}
|
|
1299
|
+
function parsePendingTaskUpdateJobs(value) {
|
|
1300
|
+
if (!Array.isArray(value)) return [];
|
|
1301
|
+
return value.flatMap((item) => {
|
|
1302
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
1303
|
+
const raw = item;
|
|
1304
|
+
if (typeof raw.job_id !== "string" || typeof raw.task_id !== "string") return [];
|
|
1305
|
+
if (typeof raw.revision !== "number" || typeof raw.task_key_version !== "number" || typeof raw.expires_at !== "number") return [];
|
|
1306
|
+
const job = {
|
|
1307
|
+
job_id: raw.job_id,
|
|
1308
|
+
task_id: raw.task_id,
|
|
1309
|
+
revision: raw.revision,
|
|
1310
|
+
task_key_version: raw.task_key_version,
|
|
1311
|
+
expires_at: raw.expires_at
|
|
1312
|
+
};
|
|
1313
|
+
if (typeof raw.chat_id === "string" || raw.chat_id === null) job.chat_id = raw.chat_id;
|
|
1314
|
+
return [job];
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
function parseAvailableRecoveryJobs(value) {
|
|
1318
|
+
if (!Array.isArray(value)) return [];
|
|
1319
|
+
return value.flatMap((item) => {
|
|
1320
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
1321
|
+
const raw = item;
|
|
1322
|
+
if (typeof raw.job_id !== "string" || typeof raw.chat_id !== "string" || typeof raw.turn_id !== "string" || typeof raw.assistant_message_id !== "string" || typeof raw.chat_key_version !== "number") return [];
|
|
1323
|
+
return [{
|
|
1324
|
+
job_id: raw.job_id,
|
|
1325
|
+
chat_id: raw.chat_id,
|
|
1326
|
+
turn_id: raw.turn_id,
|
|
1327
|
+
assistant_message_id: raw.assistant_message_id,
|
|
1328
|
+
chat_key_version: raw.chat_key_version
|
|
1329
|
+
}];
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1244
1332
|
var OpenMatesWsClient = class {
|
|
1245
1333
|
socket;
|
|
1334
|
+
passiveTaskUpdateJobs = /* @__PURE__ */ new Map();
|
|
1335
|
+
activeResponseCollectors = 0;
|
|
1246
1336
|
constructor(options) {
|
|
1247
1337
|
const wsBase = options.apiUrl.replace(/^http/, "ws").replace(/\/$/, "");
|
|
1248
1338
|
const token = options.wsToken || options.refreshToken || "";
|
|
1249
1339
|
const query = new URLSearchParams({
|
|
1250
1340
|
sessionId: options.sessionId,
|
|
1251
|
-
token
|
|
1341
|
+
token,
|
|
1342
|
+
client_capabilities: "task_update_jobs"
|
|
1252
1343
|
});
|
|
1253
1344
|
const wsHeaders = {};
|
|
1254
1345
|
if (options.userAgent) {
|
|
@@ -1263,6 +1354,10 @@ var OpenMatesWsClient = class {
|
|
|
1263
1354
|
this.socket = new WebSocket(`${wsBase}/v1/ws?${query.toString()}`, {
|
|
1264
1355
|
headers: wsHeaders
|
|
1265
1356
|
});
|
|
1357
|
+
this.socket.on("message", (rawData) => {
|
|
1358
|
+
if (this.activeResponseCollectors > 0) return;
|
|
1359
|
+
this.bufferPassiveTaskUpdateJobs(rawData);
|
|
1360
|
+
});
|
|
1266
1361
|
}
|
|
1267
1362
|
async open(timeoutMs = 1e4) {
|
|
1268
1363
|
await new Promise((resolve7, reject) => {
|
|
@@ -1308,6 +1403,22 @@ var OpenMatesWsClient = class {
|
|
|
1308
1403
|
});
|
|
1309
1404
|
});
|
|
1310
1405
|
}
|
|
1406
|
+
bufferPassiveTaskUpdateJobs(rawData) {
|
|
1407
|
+
try {
|
|
1408
|
+
const parsed = JSON.parse(rawData.toString());
|
|
1409
|
+
if (parsed.type !== "task_update_jobs_available") return;
|
|
1410
|
+
const payload = parsed.payload ?? {};
|
|
1411
|
+
for (const job of parsePendingTaskUpdateJobs(payload.jobs)) {
|
|
1412
|
+
this.passiveTaskUpdateJobs.set(job.job_id, job);
|
|
1413
|
+
}
|
|
1414
|
+
} catch {
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
drainPassiveTaskUpdateJobs() {
|
|
1418
|
+
const jobs = [...this.passiveTaskUpdateJobs.values()];
|
|
1419
|
+
this.passiveTaskUpdateJobs.clear();
|
|
1420
|
+
return jobs;
|
|
1421
|
+
}
|
|
1311
1422
|
waitForMessage(expectedType, predicate, timeoutMs = 2e4) {
|
|
1312
1423
|
return new Promise((resolve7, reject) => {
|
|
1313
1424
|
const seenTypes = /* @__PURE__ */ new Set();
|
|
@@ -1318,6 +1429,7 @@ var OpenMatesWsClient = class {
|
|
|
1318
1429
|
if (typeof parsed.type === "string") seenTypes.add(parsed.type);
|
|
1319
1430
|
const protocolError = websocketProtocolError(parsed);
|
|
1320
1431
|
if (protocolError) {
|
|
1432
|
+
if (scopedErrorMissesPredicate(parsed, predicate)) return;
|
|
1321
1433
|
cleanup();
|
|
1322
1434
|
reject(protocolError);
|
|
1323
1435
|
return;
|
|
@@ -1443,6 +1555,14 @@ var OpenMatesWsClient = class {
|
|
|
1443
1555
|
let newChatSuggestions = [];
|
|
1444
1556
|
let taskProposals = [];
|
|
1445
1557
|
let taskUpdateProposals = [];
|
|
1558
|
+
const taskEvents = [];
|
|
1559
|
+
this.activeResponseCollectors += 1;
|
|
1560
|
+
let pendingTaskUpdateJobs = this.drainPassiveTaskUpdateJobs();
|
|
1561
|
+
const mergePendingTaskUpdateJobs = (jobs) => {
|
|
1562
|
+
const byId = new Map(pendingTaskUpdateJobs.map((job) => [job.job_id, job]));
|
|
1563
|
+
for (const job of jobs) byId.set(job.job_id, job);
|
|
1564
|
+
pendingTaskUpdateJobs = [...byId.values()];
|
|
1565
|
+
};
|
|
1446
1566
|
const subChatEvents = [];
|
|
1447
1567
|
const pendingSubChatHandlers = /* @__PURE__ */ new Set();
|
|
1448
1568
|
const pendingMemoryRequestHandlers = /* @__PURE__ */ new Set();
|
|
@@ -1505,6 +1625,8 @@ var OpenMatesWsClient = class {
|
|
|
1505
1625
|
newChatSuggestions,
|
|
1506
1626
|
taskProposals,
|
|
1507
1627
|
taskUpdateProposals,
|
|
1628
|
+
taskEvents,
|
|
1629
|
+
pendingTaskUpdateJobs,
|
|
1508
1630
|
embeds: [...embeds.values()],
|
|
1509
1631
|
subChatEvents,
|
|
1510
1632
|
recoveryJobId
|
|
@@ -1512,6 +1634,7 @@ var OpenMatesWsClient = class {
|
|
|
1512
1634
|
return;
|
|
1513
1635
|
}
|
|
1514
1636
|
if (!aiResponseDone || !postProcessingDone) return;
|
|
1637
|
+
if (options?.recoveryTurnId && !recoveryJobId) return;
|
|
1515
1638
|
if (pendingSubChatHandlers.size > 0) return;
|
|
1516
1639
|
if (pendingMemoryRequestHandlers.size > 0) return;
|
|
1517
1640
|
if (processingEmbedIds.size > 0 && !asyncEmbedTimer) {
|
|
@@ -1528,6 +1651,8 @@ var OpenMatesWsClient = class {
|
|
|
1528
1651
|
newChatSuggestions,
|
|
1529
1652
|
taskProposals,
|
|
1530
1653
|
taskUpdateProposals,
|
|
1654
|
+
taskEvents,
|
|
1655
|
+
pendingTaskUpdateJobs,
|
|
1531
1656
|
embeds: [...embeds.values()],
|
|
1532
1657
|
subChatEvents,
|
|
1533
1658
|
recoveryJobId
|
|
@@ -1548,6 +1673,8 @@ var OpenMatesWsClient = class {
|
|
|
1548
1673
|
newChatSuggestions,
|
|
1549
1674
|
taskProposals,
|
|
1550
1675
|
taskUpdateProposals,
|
|
1676
|
+
taskEvents,
|
|
1677
|
+
pendingTaskUpdateJobs,
|
|
1551
1678
|
embeds: [...embeds.values()],
|
|
1552
1679
|
subChatEvents,
|
|
1553
1680
|
recoveryJobId
|
|
@@ -1624,6 +1751,9 @@ var OpenMatesWsClient = class {
|
|
|
1624
1751
|
const type = parsed.type;
|
|
1625
1752
|
const protocolError = websocketProtocolError(parsed);
|
|
1626
1753
|
if (protocolError) {
|
|
1754
|
+
if (!errorFrameBelongsToAiResponse(parsed, userMessageId, chatId, options?.recoveryTurnId)) {
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1627
1757
|
cleanup();
|
|
1628
1758
|
reject(protocolError);
|
|
1629
1759
|
return;
|
|
@@ -1654,6 +1784,29 @@ var OpenMatesWsClient = class {
|
|
|
1654
1784
|
}
|
|
1655
1785
|
return;
|
|
1656
1786
|
}
|
|
1787
|
+
if (type === "task_event") {
|
|
1788
|
+
const event = parseTaskEvent(p);
|
|
1789
|
+
if (!event || event.chat_id !== chatId) return;
|
|
1790
|
+
taskEvents.push(event);
|
|
1791
|
+
maybeResolve();
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
if (type === "task_update_jobs_available") {
|
|
1795
|
+
mergePendingTaskUpdateJobs(parsePendingTaskUpdateJobs(p.jobs));
|
|
1796
|
+
maybeResolve();
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
if (type === "recovery_jobs_available") {
|
|
1800
|
+
const job = parseAvailableRecoveryJobs(p.jobs).find(
|
|
1801
|
+
(candidate) => candidate.chat_id === chatId && (!options?.recoveryTurnId || candidate.turn_id === options.recoveryTurnId)
|
|
1802
|
+
);
|
|
1803
|
+
if (!job) return;
|
|
1804
|
+
recoveryJobId = job.job_id;
|
|
1805
|
+
messageId = job.assistant_message_id;
|
|
1806
|
+
if (aiResponseDone) maybeResolve();
|
|
1807
|
+
else scheduleResolve(latestContent);
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1657
1810
|
if (type === "ai_message_update") {
|
|
1658
1811
|
const msgId = p.user_message_id ?? p.userMessageId;
|
|
1659
1812
|
if (msgId !== userMessageId && p.chat_id !== chatId) return;
|
|
@@ -1764,6 +1917,8 @@ var OpenMatesWsClient = class {
|
|
|
1764
1917
|
newChatSuggestions,
|
|
1765
1918
|
taskProposals,
|
|
1766
1919
|
taskUpdateProposals,
|
|
1920
|
+
taskEvents,
|
|
1921
|
+
pendingTaskUpdateJobs,
|
|
1767
1922
|
embeds: [...embeds.values()],
|
|
1768
1923
|
subChatEvents,
|
|
1769
1924
|
recoveryJobId
|
|
@@ -1786,6 +1941,7 @@ var OpenMatesWsClient = class {
|
|
|
1786
1941
|
this.socket.off("message", onMessage);
|
|
1787
1942
|
this.socket.off("error", onError);
|
|
1788
1943
|
this.socket.off("close", onClose);
|
|
1944
|
+
this.activeResponseCollectors = Math.max(0, this.activeResponseCollectors - 1);
|
|
1789
1945
|
};
|
|
1790
1946
|
this.socket.on("message", onMessage);
|
|
1791
1947
|
this.socket.on("error", onError);
|
|
@@ -2514,6 +2670,227 @@ function toArrayBuffer3(input) {
|
|
|
2514
2670
|
return output;
|
|
2515
2671
|
}
|
|
2516
2672
|
|
|
2673
|
+
// src/tasksCli.ts
|
|
2674
|
+
import { createHash as createHash5, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
|
|
2675
|
+
var TASK_STATUSES2 = ["backlog", "todo", "in_progress", "blocked", "done"];
|
|
2676
|
+
var DEFAULT_STANDALONE_PREFIX = "TASK";
|
|
2677
|
+
function normalizeTaskStatus(value) {
|
|
2678
|
+
if (value === void 0) return void 0;
|
|
2679
|
+
if (TASK_STATUSES2.includes(value)) return value;
|
|
2680
|
+
throw new Error(`Unknown task status '${value}'. Expected one of: ${TASK_STATUSES2.join(", ")}`);
|
|
2681
|
+
}
|
|
2682
|
+
function parseAssignee(value) {
|
|
2683
|
+
if (!value || value === "user") return { assigneeType: "user", assigneeHash: null };
|
|
2684
|
+
if (["ai", "openmates", "OpenMates"].includes(value)) return { assigneeType: "ai", assigneeHash: null };
|
|
2685
|
+
return { assigneeType: "user", assigneeHash: value };
|
|
2686
|
+
}
|
|
2687
|
+
function splitCsvFlag(value) {
|
|
2688
|
+
if (typeof value !== "string") return [];
|
|
2689
|
+
return value.split(/[,\n]/).map((item) => item.trim()).filter(Boolean);
|
|
2690
|
+
}
|
|
2691
|
+
function parseDueAt(value) {
|
|
2692
|
+
if (value === void 0) return void 0;
|
|
2693
|
+
if (value === false || value === true) throw new Error("--due requires a timestamp or date value.");
|
|
2694
|
+
const numeric = Number(value);
|
|
2695
|
+
if (Number.isFinite(numeric) && numeric > 0) return Math.floor(numeric);
|
|
2696
|
+
const parsed = Date.parse(value);
|
|
2697
|
+
if (Number.isNaN(parsed)) throw new Error(`Invalid --due value '${value}'.`);
|
|
2698
|
+
return Math.floor(parsed / 1e3);
|
|
2699
|
+
}
|
|
2700
|
+
async function buildCreateUserTaskInput(masterKey, input) {
|
|
2701
|
+
const taskKey = randomBytes2(32);
|
|
2702
|
+
const encryptedTaskKey = await encryptBytesWithAesGcm(taskKey, masterKey);
|
|
2703
|
+
const timestamp = nowSeconds();
|
|
2704
|
+
const assignee = parseAssignee(input.assign);
|
|
2705
|
+
const linkedProjectIds = input.projectIds ?? [];
|
|
2706
|
+
const status = input.status ?? (assignee.assigneeType === "ai" && !input.dueAt ? "in_progress" : "todo");
|
|
2707
|
+
return {
|
|
2708
|
+
task_id: randomUUIDCompat(),
|
|
2709
|
+
short_id: void 0,
|
|
2710
|
+
version: 1,
|
|
2711
|
+
encrypted_task_key: encryptedTaskKey,
|
|
2712
|
+
encrypted_title: await encryptWithAesGcmCombined(input.title, taskKey),
|
|
2713
|
+
encrypted_description: await encryptWithAesGcmCombined(input.description ?? "", taskKey),
|
|
2714
|
+
encrypted_tags: await encryptWithAesGcmCombined("[]", taskKey),
|
|
2715
|
+
encrypted_linked_project_ids: await encryptWithAesGcmCombined(JSON.stringify(linkedProjectIds), taskKey),
|
|
2716
|
+
status,
|
|
2717
|
+
assignee_type: assignee.assigneeType,
|
|
2718
|
+
assignee_hash: assignee.assigneeHash,
|
|
2719
|
+
primary_chat_id: input.chatId ?? null,
|
|
2720
|
+
linked_project_ids: linkedProjectIds,
|
|
2721
|
+
plan_id: input.planId ?? null,
|
|
2722
|
+
due_at: input.dueAt ?? null,
|
|
2723
|
+
priority: 0,
|
|
2724
|
+
position: timestamp,
|
|
2725
|
+
created_at: timestamp,
|
|
2726
|
+
updated_at: timestamp
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
async function buildUpdateUserTaskInput(task, masterKey, input) {
|
|
2730
|
+
const taskKey = await taskKeyFromRecord(task.encrypted, masterKey);
|
|
2731
|
+
const patch = { version: task.version, updated_at: nowSeconds() };
|
|
2732
|
+
if (input.title !== void 0) patch.encrypted_title = await encryptWithAesGcmCombined(input.title, taskKey);
|
|
2733
|
+
if (input.description !== void 0) patch.encrypted_description = await encryptWithAesGcmCombined(input.description, taskKey);
|
|
2734
|
+
if (input.status !== void 0) patch.status = input.status;
|
|
2735
|
+
if (input.assign !== void 0) {
|
|
2736
|
+
const assignee = parseAssignee(input.assign);
|
|
2737
|
+
patch.assignee_type = assignee.assigneeType;
|
|
2738
|
+
patch.assignee_hash = assignee.assigneeHash;
|
|
2739
|
+
}
|
|
2740
|
+
if (input.chatId !== void 0) patch.primary_chat_id = input.chatId;
|
|
2741
|
+
if (input.projectIds !== void 0) {
|
|
2742
|
+
patch.linked_project_ids = input.projectIds;
|
|
2743
|
+
patch.encrypted_linked_project_ids = await encryptWithAesGcmCombined(JSON.stringify(input.projectIds), taskKey);
|
|
2744
|
+
}
|
|
2745
|
+
if (input.planId !== void 0) patch.plan_id = input.planId;
|
|
2746
|
+
return patch;
|
|
2747
|
+
}
|
|
2748
|
+
async function decryptUserTask(record, masterKey) {
|
|
2749
|
+
if (typeof record.version !== "number") throw new Error(`Task ${record.task_id} is missing version.`);
|
|
2750
|
+
const taskKey = await taskKeyFromRecord(record, masterKey);
|
|
2751
|
+
const tags = parseStringArray(await decryptOptional(record.encrypted_tags, taskKey));
|
|
2752
|
+
const linkedProjectIds = parseStringArray(await decryptOptional(record.encrypted_linked_project_ids, taskKey));
|
|
2753
|
+
return {
|
|
2754
|
+
taskId: record.task_id,
|
|
2755
|
+
shortId: record.short_id || deriveShortId(record),
|
|
2756
|
+
title: await decryptOptional(record.encrypted_title, taskKey) || "(untitled task)",
|
|
2757
|
+
description: await decryptOptional(record.encrypted_description, taskKey),
|
|
2758
|
+
tags,
|
|
2759
|
+
latestInstruction: await decryptOptional(record.encrypted_latest_instruction, taskKey),
|
|
2760
|
+
status: record.status,
|
|
2761
|
+
assigneeType: record.assignee_type,
|
|
2762
|
+
assigneeHash: record.assignee_hash ?? null,
|
|
2763
|
+
primaryChatId: record.primary_chat_id ?? null,
|
|
2764
|
+
linkedProjectIds: linkedProjectIds.length > 0 ? linkedProjectIds : record.linked_project_ids ?? [],
|
|
2765
|
+
planId: record.plan_id ?? null,
|
|
2766
|
+
dueAt: record.due_at ?? null,
|
|
2767
|
+
priority: record.priority ?? 0,
|
|
2768
|
+
position: record.position ?? 0,
|
|
2769
|
+
queueState: record.queue_state ?? "none",
|
|
2770
|
+
blockedReasonCode: record.blocked_reason_code ?? null,
|
|
2771
|
+
aiExecutionState: record.ai_execution_state ?? null,
|
|
2772
|
+
version: record.version,
|
|
2773
|
+
encrypted: record
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
async function decryptUserTasks(records, masterKey) {
|
|
2777
|
+
const output = [];
|
|
2778
|
+
for (const record of records) output.push(await decryptUserTask(record, masterKey));
|
|
2779
|
+
return output;
|
|
2780
|
+
}
|
|
2781
|
+
function findTask(tasks, id) {
|
|
2782
|
+
const taskIdMatch = tasks.find((candidate) => candidate.taskId === id);
|
|
2783
|
+
if (taskIdMatch) return taskIdMatch;
|
|
2784
|
+
const shortIdMatches = tasks.filter((candidate) => candidate.shortId === id);
|
|
2785
|
+
if (shortIdMatches.length > 1) throw new Error(`Task '${id}' is ambiguous in the current task list. Use the full task ID.`);
|
|
2786
|
+
const task = shortIdMatches[0];
|
|
2787
|
+
if (!task) throw new Error(`Task '${id}' was not found in the current task list.`);
|
|
2788
|
+
return task;
|
|
2789
|
+
}
|
|
2790
|
+
function renderTaskList(tasks) {
|
|
2791
|
+
if (tasks.length === 0) return "No tasks found.";
|
|
2792
|
+
const lines = ["Tasks", "ID Status Assignee Queue Title"];
|
|
2793
|
+
for (const task of tasks) {
|
|
2794
|
+
lines.push(`${pad(task.shortId, 9)} ${pad(task.status, 12)} ${pad(assigneeLabel(task), 11)} ${pad(task.queueState, 11)} ${task.title}`);
|
|
2795
|
+
}
|
|
2796
|
+
return lines.join("\n");
|
|
2797
|
+
}
|
|
2798
|
+
function renderTaskDetail(task) {
|
|
2799
|
+
const lines = [
|
|
2800
|
+
`Task ${task.shortId}`,
|
|
2801
|
+
`Title: ${task.title}`,
|
|
2802
|
+
`Status: ${task.status}`,
|
|
2803
|
+
`Assignee: ${assigneeLabel(task)}`,
|
|
2804
|
+
`Queue: ${task.queueState}`,
|
|
2805
|
+
`Task ID: ${task.taskId}`
|
|
2806
|
+
];
|
|
2807
|
+
if (task.description) lines.push(`Description: ${task.description}`);
|
|
2808
|
+
if (task.primaryChatId) lines.push(`Chat: ${task.primaryChatId}`);
|
|
2809
|
+
if (task.linkedProjectIds.length > 0) lines.push(`Projects: ${task.linkedProjectIds.join(", ")}`);
|
|
2810
|
+
if (task.planId) lines.push(`Plan: ${task.planId}`);
|
|
2811
|
+
if (task.blockedReasonCode) lines.push(`Blocked reason: ${task.blockedReasonCode}`);
|
|
2812
|
+
if (task.aiExecutionState) lines.push(`AI state: ${task.aiExecutionState}`);
|
|
2813
|
+
return lines.join("\n");
|
|
2814
|
+
}
|
|
2815
|
+
function renderTaskBoard(tasks, width = process.stdout.columns || 100) {
|
|
2816
|
+
const columns = TASK_STATUSES2.map((status) => ({ status, tasks: tasks.filter((task) => task.status === status).sort(compareTasks) }));
|
|
2817
|
+
if (width < 96) {
|
|
2818
|
+
const lines2 = ["OpenMates Tasks Board"];
|
|
2819
|
+
for (const column of columns) {
|
|
2820
|
+
lines2.push("", `${columnTitle(column.status)} (${column.tasks.length})`, "-".repeat(24));
|
|
2821
|
+
lines2.push(...boardColumnLines(column.tasks, 8));
|
|
2822
|
+
}
|
|
2823
|
+
return lines2.join("\n");
|
|
2824
|
+
}
|
|
2825
|
+
const perColumn = 6;
|
|
2826
|
+
const columnWidth = 22;
|
|
2827
|
+
const lines = ["OpenMates Tasks Board", ""];
|
|
2828
|
+
lines.push(columns.map((column) => pad(`${columnTitle(column.status)} (${column.tasks.length})`, columnWidth)).join(" "));
|
|
2829
|
+
lines.push(columns.map(() => "-".repeat(columnWidth)).join(" "));
|
|
2830
|
+
const renderedColumns = columns.map((column) => boardColumnLines(column.tasks, perColumn).map((line) => truncate(line, columnWidth)));
|
|
2831
|
+
const maxRows = Math.max(...renderedColumns.map((column) => column.length));
|
|
2832
|
+
for (let row = 0; row < maxRows; row += 1) {
|
|
2833
|
+
lines.push(renderedColumns.map((column) => pad(column[row] ?? "", columnWidth)).join(" "));
|
|
2834
|
+
}
|
|
2835
|
+
return lines.join("\n");
|
|
2836
|
+
}
|
|
2837
|
+
function boardColumnLines(tasks, limit) {
|
|
2838
|
+
if (tasks.length === 0) return ["No tasks here."];
|
|
2839
|
+
const visible = tasks.slice(0, limit).flatMap((task) => [
|
|
2840
|
+
`[${task.shortId}] ${task.title}`,
|
|
2841
|
+
` ${assigneeLabel(task)} ${task.queueState === "none" ? "" : task.queueState}`.trimEnd()
|
|
2842
|
+
]);
|
|
2843
|
+
if (tasks.length > limit) visible.push(`... ${tasks.length - limit} more`);
|
|
2844
|
+
return visible;
|
|
2845
|
+
}
|
|
2846
|
+
function compareTasks(a, b) {
|
|
2847
|
+
return a.position - b.position || a.title.localeCompare(b.title);
|
|
2848
|
+
}
|
|
2849
|
+
function columnTitle(status) {
|
|
2850
|
+
if (status === "in_progress") return "In progress";
|
|
2851
|
+
return status.slice(0, 1).toUpperCase() + status.slice(1).replace("_", " ");
|
|
2852
|
+
}
|
|
2853
|
+
function assigneeLabel(task) {
|
|
2854
|
+
return task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
2855
|
+
}
|
|
2856
|
+
async function taskKeyFromRecord(record, masterKey) {
|
|
2857
|
+
if (!record.encrypted_task_key) throw new Error(`Task ${record.task_id} is missing encrypted task key.`);
|
|
2858
|
+
const taskKey = await decryptBytesWithAesGcm(record.encrypted_task_key, masterKey);
|
|
2859
|
+
if (!taskKey) throw new Error(`Failed to decrypt task key for ${record.task_id}.`);
|
|
2860
|
+
return taskKey;
|
|
2861
|
+
}
|
|
2862
|
+
async function decryptOptional(value, key) {
|
|
2863
|
+
if (!value) return "";
|
|
2864
|
+
return await decryptWithAesGcmCombined(value, key) ?? "";
|
|
2865
|
+
}
|
|
2866
|
+
function parseStringArray(value) {
|
|
2867
|
+
if (!value) return [];
|
|
2868
|
+
try {
|
|
2869
|
+
const parsed = JSON.parse(value);
|
|
2870
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
2871
|
+
} catch {
|
|
2872
|
+
return [];
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
function deriveShortId(record) {
|
|
2876
|
+
const prefix = record.short_id_prefix || DEFAULT_STANDALONE_PREFIX;
|
|
2877
|
+
const source = record.task_id || `${record.created_at ?? ""}-${record.position ?? ""}`;
|
|
2878
|
+
const digest = createHash5("sha256").update(source).digest("hex").slice(0, 4).toUpperCase();
|
|
2879
|
+
return `${prefix}-${parseInt(digest, 16) % 1e4}`;
|
|
2880
|
+
}
|
|
2881
|
+
function nowSeconds() {
|
|
2882
|
+
return Math.floor(Date.now() / 1e3);
|
|
2883
|
+
}
|
|
2884
|
+
function randomUUIDCompat() {
|
|
2885
|
+
return randomUUID3();
|
|
2886
|
+
}
|
|
2887
|
+
function pad(value, length) {
|
|
2888
|
+
return truncate(value, length).padEnd(length);
|
|
2889
|
+
}
|
|
2890
|
+
function truncate(value, length) {
|
|
2891
|
+
return value.length <= length ? value : `${value.slice(0, Math.max(0, length - 3))}...`;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2517
2894
|
// src/client.ts
|
|
2518
2895
|
function normalizeUnixSeconds(value, fallback) {
|
|
2519
2896
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
@@ -2681,6 +3058,83 @@ function buildAppSettingsMemoryResponseSystemMessage(params) {
|
|
|
2681
3058
|
user_message_id: params.userMessageId
|
|
2682
3059
|
};
|
|
2683
3060
|
}
|
|
3061
|
+
async function buildTaskEventSystemMessage(params) {
|
|
3062
|
+
const content = formatTaskEventSystemContent(params.event);
|
|
3063
|
+
const message = {
|
|
3064
|
+
message_id: `task-event-${params.event.event_id}`,
|
|
3065
|
+
role: "system",
|
|
3066
|
+
encrypted_content: await encryptWithAesGcmCombined(content, params.chatKey),
|
|
3067
|
+
created_at: params.event.created_at ?? Math.floor(Date.now() / 1e3),
|
|
3068
|
+
user_message_id: params.userMessageId
|
|
3069
|
+
};
|
|
3070
|
+
if (params.event.task_update_job_id) message.task_update_job_id = params.event.task_update_job_id;
|
|
3071
|
+
return message;
|
|
3072
|
+
}
|
|
3073
|
+
function taskUpdateJobBelongsToActiveTurn(job, activeChatId, taskEvents) {
|
|
3074
|
+
void activeChatId;
|
|
3075
|
+
return taskEvents.some((event) => event.task_update_job_id === job.job_id);
|
|
3076
|
+
}
|
|
3077
|
+
function buildTaskUpdateJobPersistPayload(params) {
|
|
3078
|
+
const encryptedTaskPayload = pruneAbsentTaskPersistFields(params.encryptedTaskPayload);
|
|
3079
|
+
assertTaskPersistPayloadEncrypted(encryptedTaskPayload);
|
|
3080
|
+
return {
|
|
3081
|
+
protocol_version: 1,
|
|
3082
|
+
job_id: params.jobId,
|
|
3083
|
+
lease_token: params.leaseToken,
|
|
3084
|
+
lease_generation: params.leaseGeneration,
|
|
3085
|
+
expected_task_version: params.expectedTaskVersion,
|
|
3086
|
+
encrypted_task_payload: encryptedTaskPayload,
|
|
3087
|
+
encrypted_task_event_message: params.encryptedTaskEventMessage ?? null
|
|
3088
|
+
};
|
|
3089
|
+
}
|
|
3090
|
+
function pruneAbsentTaskPersistFields(payload) {
|
|
3091
|
+
return Object.fromEntries(
|
|
3092
|
+
Object.entries(payload).filter(([, value]) => value !== void 0 && value !== null)
|
|
3093
|
+
);
|
|
3094
|
+
}
|
|
3095
|
+
function formatTaskEventSystemContent(event) {
|
|
3096
|
+
const taskLabel = event.short_id || event.task_id;
|
|
3097
|
+
const title = event.title ? ` "${event.title}"` : "";
|
|
3098
|
+
const status = event.status ? ` (${event.status})` : "";
|
|
3099
|
+
const reason = event.reason ? `: ${event.reason}` : "";
|
|
3100
|
+
switch (event.event_type) {
|
|
3101
|
+
case "created":
|
|
3102
|
+
return `${taskLabel} created${title}${status}`;
|
|
3103
|
+
case "updated":
|
|
3104
|
+
return `${taskLabel} updated${title}${status}`;
|
|
3105
|
+
case "blocked":
|
|
3106
|
+
return `${taskLabel} blocked${reason}`;
|
|
3107
|
+
case "completed":
|
|
3108
|
+
return `${taskLabel} completed${title}`;
|
|
3109
|
+
case "unblocked":
|
|
3110
|
+
return `${taskLabel} unblocked`;
|
|
3111
|
+
default:
|
|
3112
|
+
return `${taskLabel} ${event.event_type}${title}${status}${reason}`;
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
function assertTaskPersistPayloadEncrypted(payload) {
|
|
3116
|
+
const allowedSafeKeys = /* @__PURE__ */ new Set([
|
|
3117
|
+
"assignee_hash",
|
|
3118
|
+
"assignee_type",
|
|
3119
|
+
"blocked_reason_code",
|
|
3120
|
+
"created_at",
|
|
3121
|
+
"key_wrappers",
|
|
3122
|
+
"linked_project_ids",
|
|
3123
|
+
"position",
|
|
3124
|
+
"primary_chat_id",
|
|
3125
|
+
"priority",
|
|
3126
|
+
"status",
|
|
3127
|
+
"task_id",
|
|
3128
|
+
"updated_at",
|
|
3129
|
+
"version"
|
|
3130
|
+
]);
|
|
3131
|
+
for (const key of Object.keys(payload)) {
|
|
3132
|
+
if (key.startsWith("encrypted_") || allowedSafeKeys.has(key)) {
|
|
3133
|
+
continue;
|
|
3134
|
+
}
|
|
3135
|
+
throw new Error("Task update job payload contains plaintext or unsupported field");
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
2684
3138
|
async function buildSubChatEncryptedMetadataPayloads(params) {
|
|
2685
3139
|
const createdAt = params.createdAt ?? Math.floor(Date.now() / 1e3);
|
|
2686
3140
|
const payloads = [];
|
|
@@ -3354,11 +3808,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3354
3808
|
}
|
|
3355
3809
|
let anonymousId = loadAnonymousId();
|
|
3356
3810
|
if (!anonymousId) {
|
|
3357
|
-
anonymousId =
|
|
3811
|
+
anonymousId = randomUUID4();
|
|
3358
3812
|
saveAnonymousId(anonymousId);
|
|
3359
3813
|
}
|
|
3360
|
-
const chatId = `anonymous-${
|
|
3361
|
-
const messageId = `anonymous-message-${
|
|
3814
|
+
const chatId = `anonymous-${randomUUID4()}`;
|
|
3815
|
+
const messageId = `anonymous-message-${randomUUID4()}`;
|
|
3362
3816
|
const requestBody = {
|
|
3363
3817
|
anonymous_id: anonymousId,
|
|
3364
3818
|
client_chat_id: chatId,
|
|
@@ -3552,7 +4006,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3552
4006
|
pin: pin.trim().toUpperCase(),
|
|
3553
4007
|
token
|
|
3554
4008
|
});
|
|
3555
|
-
const sessionId =
|
|
4009
|
+
const sessionId = randomUUID4();
|
|
3556
4010
|
const login = await this.http.post(
|
|
3557
4011
|
"/v1/auth/login",
|
|
3558
4012
|
{
|
|
@@ -3727,7 +4181,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3727
4181
|
}
|
|
3728
4182
|
const session = {
|
|
3729
4183
|
apiUrl: this.apiUrl,
|
|
3730
|
-
sessionId:
|
|
4184
|
+
sessionId: randomUUID4(),
|
|
3731
4185
|
wsToken: null,
|
|
3732
4186
|
cookies: this.http.getCookieMap(),
|
|
3733
4187
|
masterKeyExportedB64: material.masterKeyB64,
|
|
@@ -3887,7 +4341,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3887
4341
|
if (!markdown) throw new Error("Draft markdown must not be empty.");
|
|
3888
4342
|
const masterKey = this.getMasterKeyBytes();
|
|
3889
4343
|
const encrypted = await this.saveEncryptedDraft({
|
|
3890
|
-
chatId: params.chatId ??
|
|
4344
|
+
chatId: params.chatId ?? randomUUID4(),
|
|
3891
4345
|
encryptedDraftMd: await encryptWithAesGcmCombined(markdown, masterKey),
|
|
3892
4346
|
encryptedDraftPreview: params.preview ? await encryptWithAesGcmCombined(params.preview, masterKey) : null
|
|
3893
4347
|
});
|
|
@@ -4443,7 +4897,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4443
4897
|
async sendMessage(params) {
|
|
4444
4898
|
let chatId;
|
|
4445
4899
|
if (!params.chatId) {
|
|
4446
|
-
chatId =
|
|
4900
|
+
chatId = randomUUID4();
|
|
4447
4901
|
} else if (params.chatId.length < 36) {
|
|
4448
4902
|
const resolved = await this.resolveFullChatId(params.chatId);
|
|
4449
4903
|
if (!resolved) {
|
|
@@ -4477,7 +4931,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4477
4931
|
ws.close();
|
|
4478
4932
|
throw new Error("Authenticated user identity is required for saved chat recovery.");
|
|
4479
4933
|
}
|
|
4480
|
-
const messageId =
|
|
4934
|
+
const messageId = randomUUID4();
|
|
4481
4935
|
const createdAt = Math.floor(Date.now() / 1e3);
|
|
4482
4936
|
const isNewChat = !params.chatId;
|
|
4483
4937
|
ws.send("set_active_chat", { chat_id: chatId });
|
|
@@ -4528,6 +4982,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4528
4982
|
}
|
|
4529
4983
|
const messagePayload = {
|
|
4530
4984
|
chat_id: chatId,
|
|
4985
|
+
client_capabilities: ["task_update_jobs"],
|
|
4531
4986
|
is_incognito: Boolean(params.incognito),
|
|
4532
4987
|
message: {
|
|
4533
4988
|
message_id: messageId,
|
|
@@ -4611,11 +5066,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4611
5066
|
if (encryptedEmbeds.length > 0) {
|
|
4612
5067
|
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
4613
5068
|
}
|
|
4614
|
-
let precollectedResponse = params.precollectResponse && params.incognito ? ws.collectAiResponse(messageId, chatId, { onStream: params.onStream }) : null;
|
|
5069
|
+
let precollectedResponse = params.precollectResponse && params.incognito ? ws.collectAiResponse(messageId, chatId, { onStream: params.onStream, timeoutMs: params.responseTimeoutMs }) : null;
|
|
4615
5070
|
if (!params.incognito && chatKeyBytes && encryptedChatKey) {
|
|
4616
5071
|
const protocolVersion = 1;
|
|
4617
5072
|
const chatKeyVersion = 1;
|
|
4618
|
-
const turnId =
|
|
5073
|
+
const turnId = randomUUID4();
|
|
4619
5074
|
savedTurnId = turnId;
|
|
4620
5075
|
terminalExpectedMessagesV = baselineMessagesV + 1;
|
|
4621
5076
|
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
@@ -4666,7 +5121,10 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4666
5121
|
updated_at: createdAt
|
|
4667
5122
|
};
|
|
4668
5123
|
}
|
|
4669
|
-
const preflightAck = ws.waitForMessage(
|
|
5124
|
+
const preflightAck = ws.waitForMessage(
|
|
5125
|
+
"chat_turn_preflight_ack",
|
|
5126
|
+
(payload) => payload.turn_id === turnId
|
|
5127
|
+
);
|
|
4670
5128
|
let ackPayload;
|
|
4671
5129
|
try {
|
|
4672
5130
|
await ws.sendAsync("chat_turn_preflight", preflightPayload);
|
|
@@ -4689,7 +5147,9 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4689
5147
|
}
|
|
4690
5148
|
if (params.precollectResponse && !params.incognito) {
|
|
4691
5149
|
precollectedResponse = ws.collectAiResponse(messageId, chatId, {
|
|
4692
|
-
onStream: params.onStream
|
|
5150
|
+
onStream: params.onStream,
|
|
5151
|
+
timeoutMs: params.responseTimeoutMs,
|
|
5152
|
+
recoveryTurnId: savedTurnId
|
|
4693
5153
|
});
|
|
4694
5154
|
}
|
|
4695
5155
|
const confirmed = ws.waitForMessage(
|
|
@@ -4712,6 +5172,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4712
5172
|
let followUpSuggestions = [];
|
|
4713
5173
|
let taskProposals = [];
|
|
4714
5174
|
let taskUpdateProposals = [];
|
|
5175
|
+
let taskEvents = [];
|
|
5176
|
+
let pendingTaskUpdateJobs = [];
|
|
4715
5177
|
let subChatEvents = [];
|
|
4716
5178
|
const appSettingsMemoryRequests = [];
|
|
4717
5179
|
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
@@ -4874,6 +5336,225 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4874
5336
|
`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.`
|
|
4875
5337
|
);
|
|
4876
5338
|
};
|
|
5339
|
+
const persistEncryptedSystemMessage = async (systemMessage, targetChatId = chatId) => {
|
|
5340
|
+
await ws.sendAsync("chat_system_message_added", {
|
|
5341
|
+
chat_id: targetChatId,
|
|
5342
|
+
message: systemMessage
|
|
5343
|
+
});
|
|
5344
|
+
await ws.waitForMessage(
|
|
5345
|
+
"system_message_confirmed",
|
|
5346
|
+
(payload) => payload.message_id === systemMessage.message_id,
|
|
5347
|
+
2e4
|
|
5348
|
+
);
|
|
5349
|
+
};
|
|
5350
|
+
const persistedTaskEventIds = /* @__PURE__ */ new Set();
|
|
5351
|
+
const persistTaskEventSystemMessages = async (events) => {
|
|
5352
|
+
if (!chatKeyBytes || events.length === 0) return;
|
|
5353
|
+
for (const event of events) {
|
|
5354
|
+
if (persistedTaskEventIds.has(event.event_id)) continue;
|
|
5355
|
+
await persistEncryptedSystemMessage(await buildTaskEventSystemMessage({
|
|
5356
|
+
chatKey: chatKeyBytes,
|
|
5357
|
+
userMessageId: messageId,
|
|
5358
|
+
event
|
|
5359
|
+
}));
|
|
5360
|
+
persistedTaskEventIds.add(event.event_id);
|
|
5361
|
+
}
|
|
5362
|
+
};
|
|
5363
|
+
const persistPendingTaskUpdateJobs = async (jobs, events) => {
|
|
5364
|
+
const handledJobIds = /* @__PURE__ */ new Set();
|
|
5365
|
+
if (jobs.length === 0) return handledJobIds;
|
|
5366
|
+
const masterKey = this.getMasterKeyBytes();
|
|
5367
|
+
let decryptedTasksCache = null;
|
|
5368
|
+
const eventByJobId = new Map(events.map((event) => [event.task_update_job_id, event]));
|
|
5369
|
+
const buildTaskKeyWrappersForChat = async (task, targetChatId, createdAt2) => {
|
|
5370
|
+
const encryptedTaskKey = task.encrypted.encrypted_task_key;
|
|
5371
|
+
if (!encryptedTaskKey) throw new Error(`Task ${task.taskId} is missing encrypted task key.`);
|
|
5372
|
+
const taskKey = await decryptBytesWithAesGcm(encryptedTaskKey, masterKey);
|
|
5373
|
+
if (!taskKey) throw new Error(`Failed to decrypt task key for ${task.taskId}.`);
|
|
5374
|
+
const targetChatKey = await resolveChatKey(targetChatId);
|
|
5375
|
+
const existingProjectWrappers = await listProjectTaskKeyWrappers(task.taskId, createdAt2);
|
|
5376
|
+
const linkedProjectIds = task.linkedProjectIds ?? [];
|
|
5377
|
+
if (linkedProjectIds.length > existingProjectWrappers.length) {
|
|
5378
|
+
throw new Error(`Task ${task.taskId} is missing project key wrappers required for move persistence.`);
|
|
5379
|
+
}
|
|
5380
|
+
return [
|
|
5381
|
+
{
|
|
5382
|
+
key_type: "master",
|
|
5383
|
+
encrypted_task_key: await encryptBytesWithAesGcm(taskKey, masterKey),
|
|
5384
|
+
created_at: createdAt2
|
|
5385
|
+
},
|
|
5386
|
+
{
|
|
5387
|
+
key_type: "chat",
|
|
5388
|
+
hashed_chat_id: computeSHA256(targetChatId),
|
|
5389
|
+
encrypted_task_key: await encryptBytesWithAesGcm(taskKey, targetChatKey),
|
|
5390
|
+
created_at: createdAt2
|
|
5391
|
+
},
|
|
5392
|
+
...existingProjectWrappers
|
|
5393
|
+
];
|
|
5394
|
+
};
|
|
5395
|
+
const listProjectTaskKeyWrappers = async (taskId, createdAt2) => {
|
|
5396
|
+
const response = await this.http.get(
|
|
5397
|
+
`/v1/user-tasks/${encodeURIComponent(taskId)}/key-wrappers`,
|
|
5398
|
+
this.getCliRequestHeaders()
|
|
5399
|
+
);
|
|
5400
|
+
if (!response.ok) {
|
|
5401
|
+
throw new Error(`User task key wrapper list failed with HTTP ${response.status}`);
|
|
5402
|
+
}
|
|
5403
|
+
return (response.data.key_wrappers ?? []).filter((wrapper) => wrapper.key_type === "project").map((wrapper) => ({
|
|
5404
|
+
key_type: "project",
|
|
5405
|
+
hashed_project_id: wrapper.hashed_project_id,
|
|
5406
|
+
encrypted_task_key: wrapper.encrypted_task_key,
|
|
5407
|
+
created_at: typeof wrapper.created_at === "number" ? wrapper.created_at : createdAt2,
|
|
5408
|
+
expires_at: wrapper.expires_at ?? null
|
|
5409
|
+
}));
|
|
5410
|
+
};
|
|
5411
|
+
const resolveChatKey = async (targetChatId) => {
|
|
5412
|
+
if (targetChatId === chatId && chatKeyBytes) return chatKeyBytes;
|
|
5413
|
+
const cache = loadSyncCache() ?? await this.ensureSynced();
|
|
5414
|
+
const targetChat = cache.chats.find((chat) => String(chat.details.id ?? "") === targetChatId);
|
|
5415
|
+
const encryptedTargetChatKey = typeof targetChat?.details.encrypted_chat_key === "string" ? targetChat.details.encrypted_chat_key : null;
|
|
5416
|
+
if (!encryptedTargetChatKey) {
|
|
5417
|
+
throw new Error(`Encrypted chat key not found for task move target '${targetChatId}'. Sync and try again.`);
|
|
5418
|
+
}
|
|
5419
|
+
const targetChatKey = await decryptBytesWithAesGcm(encryptedTargetChatKey, masterKey);
|
|
5420
|
+
if (!targetChatKey) {
|
|
5421
|
+
throw new Error(`Failed to decrypt chat key for task move target '${targetChatId}'.`);
|
|
5422
|
+
}
|
|
5423
|
+
return targetChatKey;
|
|
5424
|
+
};
|
|
5425
|
+
const taskEventTypeForOperation = (operation) => {
|
|
5426
|
+
switch (operation) {
|
|
5427
|
+
case "create":
|
|
5428
|
+
return "created";
|
|
5429
|
+
case "move":
|
|
5430
|
+
return "moved";
|
|
5431
|
+
case "update":
|
|
5432
|
+
return "updated";
|
|
5433
|
+
default:
|
|
5434
|
+
return operation || "updated";
|
|
5435
|
+
}
|
|
5436
|
+
};
|
|
5437
|
+
for (const job of jobs) {
|
|
5438
|
+
if (!taskUpdateJobBelongsToActiveTurn(job, chatId, events)) {
|
|
5439
|
+
handledJobIds.add(job.job_id);
|
|
5440
|
+
continue;
|
|
5441
|
+
}
|
|
5442
|
+
const claimPromise = ws.waitForMessage(
|
|
5443
|
+
"task_update_job_claimed",
|
|
5444
|
+
(payload) => payload.job_id === job.job_id,
|
|
5445
|
+
2e4
|
|
5446
|
+
);
|
|
5447
|
+
await ws.sendAsync("task_update_job_claim", {
|
|
5448
|
+
protocol_version: 1,
|
|
5449
|
+
job_id: job.job_id
|
|
5450
|
+
});
|
|
5451
|
+
const claim = (await claimPromise).payload;
|
|
5452
|
+
const privatePatch = claim.private_patch ?? {};
|
|
5453
|
+
const safeMetadata = claim.safe_metadata ?? {};
|
|
5454
|
+
const sourceChatId = claim.chat_id ?? job.chat_id ?? chatId;
|
|
5455
|
+
const sourceChatKey = await resolveChatKey(sourceChatId);
|
|
5456
|
+
const event = eventByJobId.get(job.job_id) ?? {
|
|
5457
|
+
event_id: `task-event-${job.job_id}`,
|
|
5458
|
+
chat_id: sourceChatId,
|
|
5459
|
+
task_id: claim.task_id,
|
|
5460
|
+
event_type: taskEventTypeForOperation(claim.operation),
|
|
5461
|
+
title: typeof privatePatch.title === "string" ? privatePatch.title : null,
|
|
5462
|
+
status: typeof safeMetadata.status === "string" ? safeMetadata.status : null,
|
|
5463
|
+
created_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : Math.floor(Date.now() / 1e3),
|
|
5464
|
+
task_update_job_id: job.job_id
|
|
5465
|
+
};
|
|
5466
|
+
const eventMessage = await buildTaskEventSystemMessage({
|
|
5467
|
+
chatKey: sourceChatKey,
|
|
5468
|
+
userMessageId: claim.message_id ?? messageId,
|
|
5469
|
+
event
|
|
5470
|
+
});
|
|
5471
|
+
const confirmTaskEventPersisted = async () => {
|
|
5472
|
+
const confirmedPromise = ws.waitForMessage(
|
|
5473
|
+
"task_update_job_event_confirmed",
|
|
5474
|
+
(payload) => payload.job_id === job.job_id,
|
|
5475
|
+
2e4
|
|
5476
|
+
);
|
|
5477
|
+
await ws.sendAsync("task_update_job_event_confirmed", {
|
|
5478
|
+
protocol_version: 1,
|
|
5479
|
+
job_id: job.job_id,
|
|
5480
|
+
event_system_message_id: eventMessage.message_id
|
|
5481
|
+
});
|
|
5482
|
+
await confirmedPromise;
|
|
5483
|
+
};
|
|
5484
|
+
if (claim.state === "TASK_PERSISTED") {
|
|
5485
|
+
await persistEncryptedSystemMessage(eventMessage, sourceChatId);
|
|
5486
|
+
persistedTaskEventIds.add(event.event_id);
|
|
5487
|
+
await confirmTaskEventPersisted();
|
|
5488
|
+
handledJobIds.add(job.job_id);
|
|
5489
|
+
continue;
|
|
5490
|
+
}
|
|
5491
|
+
if (!claim.lease_token || !Number.isSafeInteger(claim.lease_generation)) {
|
|
5492
|
+
throw new Error("Task update job claim returned an invalid lease.");
|
|
5493
|
+
}
|
|
5494
|
+
let encryptedTaskPayload;
|
|
5495
|
+
if (claim.operation === "create") {
|
|
5496
|
+
const input = await buildCreateUserTaskInput(masterKey, {
|
|
5497
|
+
title: typeof privatePatch.title === "string" ? privatePatch.title : "Untitled task",
|
|
5498
|
+
description: typeof privatePatch.description === "string" ? privatePatch.description : "",
|
|
5499
|
+
status: typeof safeMetadata.status === "string" ? safeMetadata.status : "todo",
|
|
5500
|
+
assign: typeof safeMetadata.assignee_type === "string" ? safeMetadata.assignee_type : "user",
|
|
5501
|
+
chatId: typeof safeMetadata.primary_chat_id === "string" ? safeMetadata.primary_chat_id : claim.chat_id ?? chatId
|
|
5502
|
+
});
|
|
5503
|
+
encryptedTaskPayload = {
|
|
5504
|
+
...input,
|
|
5505
|
+
task_id: claim.task_id,
|
|
5506
|
+
position: typeof safeMetadata.position === "number" ? safeMetadata.position : input.position,
|
|
5507
|
+
created_at: typeof safeMetadata.created_at === "number" ? safeMetadata.created_at : input.created_at,
|
|
5508
|
+
updated_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : input.updated_at
|
|
5509
|
+
};
|
|
5510
|
+
} else {
|
|
5511
|
+
if (!decryptedTasksCache) {
|
|
5512
|
+
decryptedTasksCache = await decryptUserTasks(await this.listUserTasks(), masterKey);
|
|
5513
|
+
}
|
|
5514
|
+
const task = findTask(decryptedTasksCache, claim.task_id);
|
|
5515
|
+
const patch = await buildUpdateUserTaskInput(task, masterKey, {
|
|
5516
|
+
title: typeof privatePatch.title === "string" ? privatePatch.title : void 0,
|
|
5517
|
+
description: typeof privatePatch.description === "string" ? privatePatch.description : void 0,
|
|
5518
|
+
status: typeof safeMetadata.status === "string" ? safeMetadata.status : void 0,
|
|
5519
|
+
assign: typeof safeMetadata.assignee_type === "string" ? safeMetadata.assignee_type : void 0,
|
|
5520
|
+
chatId: typeof safeMetadata.primary_chat_id === "string" ? safeMetadata.primary_chat_id : void 0
|
|
5521
|
+
});
|
|
5522
|
+
encryptedTaskPayload = {
|
|
5523
|
+
...patch,
|
|
5524
|
+
version: claim.expected_task_version,
|
|
5525
|
+
updated_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : patch.updated_at
|
|
5526
|
+
};
|
|
5527
|
+
if (typeof safeMetadata.primary_chat_id === "string") {
|
|
5528
|
+
encryptedTaskPayload.key_wrappers = await buildTaskKeyWrappersForChat(
|
|
5529
|
+
task,
|
|
5530
|
+
safeMetadata.primary_chat_id,
|
|
5531
|
+
typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : Math.floor(Date.now() / 1e3)
|
|
5532
|
+
);
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
const encryptedEventMessage = eventMessage.encrypted_content;
|
|
5536
|
+
const persistedPromise = ws.waitForMessage(
|
|
5537
|
+
"task_update_job_persisted",
|
|
5538
|
+
(payload) => payload.job_id === job.job_id,
|
|
5539
|
+
2e4
|
|
5540
|
+
);
|
|
5541
|
+
await ws.sendAsync("task_update_job_persist", buildTaskUpdateJobPersistPayload({
|
|
5542
|
+
jobId: job.job_id,
|
|
5543
|
+
leaseToken: claim.lease_token,
|
|
5544
|
+
leaseGeneration: claim.lease_generation,
|
|
5545
|
+
expectedTaskVersion: claim.expected_task_version,
|
|
5546
|
+
encryptedTaskPayload,
|
|
5547
|
+
encryptedTaskEventMessage: encryptedEventMessage
|
|
5548
|
+
}));
|
|
5549
|
+
await persistedPromise;
|
|
5550
|
+
await persistEncryptedSystemMessage(eventMessage, sourceChatId);
|
|
5551
|
+
persistedTaskEventIds.add(event.event_id);
|
|
5552
|
+
await confirmTaskEventPersisted();
|
|
5553
|
+
handledJobIds.add(job.job_id);
|
|
5554
|
+
}
|
|
5555
|
+
clearSyncCache();
|
|
5556
|
+
return handledJobIds;
|
|
5557
|
+
};
|
|
4877
5558
|
const streamOpts = {
|
|
4878
5559
|
onStream: params.onStream,
|
|
4879
5560
|
onSubChatEvent: handleSubChatEvent,
|
|
@@ -4881,11 +5562,17 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4881
5562
|
};
|
|
4882
5563
|
if (params.incognito) {
|
|
4883
5564
|
try {
|
|
4884
|
-
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId,
|
|
5565
|
+
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId, {
|
|
5566
|
+
...streamOpts,
|
|
5567
|
+
timeoutMs: params.responseTimeoutMs,
|
|
5568
|
+
recoveryTurnId: savedTurnId
|
|
5569
|
+
}));
|
|
4885
5570
|
assistantMessageId = resp.messageId;
|
|
4886
5571
|
assistant = resp.content;
|
|
4887
5572
|
category = resp.category;
|
|
4888
5573
|
modelName = resp.modelName;
|
|
5574
|
+
taskEvents = resp.taskEvents;
|
|
5575
|
+
pendingTaskUpdateJobs = resp.pendingTaskUpdateJobs;
|
|
4889
5576
|
subChatEvents = resp.subChatEvents;
|
|
4890
5577
|
if (resp.status === "waiting_for_user") {
|
|
4891
5578
|
return {
|
|
@@ -4899,6 +5586,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4899
5586
|
followUpSuggestions,
|
|
4900
5587
|
taskProposals,
|
|
4901
5588
|
taskUpdateProposals,
|
|
5589
|
+
taskEvents,
|
|
5590
|
+
pendingTaskUpdateJobs,
|
|
4902
5591
|
subChatEvents,
|
|
4903
5592
|
appSettingsMemoryRequests
|
|
4904
5593
|
};
|
|
@@ -4908,7 +5597,10 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4908
5597
|
}
|
|
4909
5598
|
} else {
|
|
4910
5599
|
try {
|
|
4911
|
-
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId,
|
|
5600
|
+
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId, {
|
|
5601
|
+
...streamOpts,
|
|
5602
|
+
timeoutMs: params.responseTimeoutMs
|
|
5603
|
+
}));
|
|
4912
5604
|
assistantMessageId = resp.messageId;
|
|
4913
5605
|
assistant = resp.content;
|
|
4914
5606
|
category = resp.category;
|
|
@@ -4916,6 +5608,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4916
5608
|
followUpSuggestions = resp.followUpSuggestions;
|
|
4917
5609
|
taskProposals = resp.taskProposals;
|
|
4918
5610
|
taskUpdateProposals = resp.taskUpdateProposals;
|
|
5611
|
+
taskEvents = resp.taskEvents;
|
|
5612
|
+
pendingTaskUpdateJobs = resp.pendingTaskUpdateJobs;
|
|
4919
5613
|
subChatEvents = resp.subChatEvents;
|
|
4920
5614
|
if (resp.status === "waiting_for_user") {
|
|
4921
5615
|
return {
|
|
@@ -4929,6 +5623,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4929
5623
|
followUpSuggestions,
|
|
4930
5624
|
taskProposals,
|
|
4931
5625
|
taskUpdateProposals,
|
|
5626
|
+
taskEvents,
|
|
5627
|
+
pendingTaskUpdateJobs,
|
|
4932
5628
|
subChatEvents,
|
|
4933
5629
|
appSettingsMemoryRequests
|
|
4934
5630
|
};
|
|
@@ -4994,12 +5690,15 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4994
5690
|
"model_name",
|
|
4995
5691
|
"turn_id"
|
|
4996
5692
|
];
|
|
4997
|
-
if (Object.keys(recovered).sort().join(",") !== expectedRecoveryFields.join(",") || recovered.assistant_message_id !== assistantId || recovered.chat_id !== chatId || recovered.turn_id !== savedTurnId || recovered.job_id !== recoveryJobId || recovered.key_version !== keyVersion || typeof recovered.content !== "string" || recovered.category !== null && typeof recovered.category !== "string" || recovered.model_name !== null && typeof recovered.model_name !== "string"
|
|
5693
|
+
if (Object.keys(recovered).sort().join(",") !== expectedRecoveryFields.join(",") || recovered.assistant_message_id !== assistantId || recovered.chat_id !== chatId || recovered.turn_id !== savedTurnId || recovered.job_id !== recoveryJobId || recovered.key_version !== keyVersion || typeof recovered.content !== "string" || recovered.category !== null && typeof recovered.category !== "string" || recovered.model_name !== null && typeof recovered.model_name !== "string") {
|
|
4998
5694
|
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
4999
5695
|
}
|
|
5696
|
+
assistant = recovered.content;
|
|
5697
|
+
category = recovered.category;
|
|
5698
|
+
modelName = recovered.model_name;
|
|
5000
5699
|
const completedAt = Math.floor(Date.now() / 1e3);
|
|
5001
5700
|
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
5002
|
-
|
|
5701
|
+
assistant,
|
|
5003
5702
|
chatKeyBytes
|
|
5004
5703
|
);
|
|
5005
5704
|
const encryptedSenderName = await encryptWithAesGcmCombined("Assistant", chatKeyBytes);
|
|
@@ -5046,6 +5745,9 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5046
5745
|
newChatSuggestions: resp.newChatSuggestions,
|
|
5047
5746
|
encryptedChatKey
|
|
5048
5747
|
});
|
|
5748
|
+
const persistedTaskJobIds = await persistPendingTaskUpdateJobs(pendingTaskUpdateJobs, taskEvents);
|
|
5749
|
+
pendingTaskUpdateJobs = pendingTaskUpdateJobs.filter((job) => !persistedTaskJobIds.has(job.job_id));
|
|
5750
|
+
await persistTaskEventSystemMessages(taskEvents);
|
|
5049
5751
|
clearSyncCache();
|
|
5050
5752
|
}
|
|
5051
5753
|
} finally {
|
|
@@ -5064,6 +5766,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5064
5766
|
followUpSuggestions,
|
|
5065
5767
|
taskProposals,
|
|
5066
5768
|
taskUpdateProposals,
|
|
5769
|
+
taskEvents,
|
|
5770
|
+
pendingTaskUpdateJobs,
|
|
5067
5771
|
subChatEvents,
|
|
5068
5772
|
appSettingsMemoryRequests
|
|
5069
5773
|
};
|
|
@@ -6024,7 +6728,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
6024
6728
|
}
|
|
6025
6729
|
return response.data.task;
|
|
6026
6730
|
}
|
|
6027
|
-
async startUserTaskWithAI(taskId, input
|
|
6731
|
+
async startUserTaskWithAI(taskId, input) {
|
|
6028
6732
|
this.requireSession();
|
|
6029
6733
|
const response = await this.http.post(
|
|
6030
6734
|
`/v1/user-tasks/${encodeURIComponent(taskId)}/start-ai`,
|
|
@@ -6036,10 +6740,10 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
6036
6740
|
}
|
|
6037
6741
|
return response.data.task;
|
|
6038
6742
|
}
|
|
6039
|
-
async deleteUserTask(taskId) {
|
|
6743
|
+
async deleteUserTask(taskId, version) {
|
|
6040
6744
|
this.requireSession();
|
|
6041
6745
|
const response = await this.http.delete(
|
|
6042
|
-
`/v1/user-tasks/${encodeURIComponent(taskId)}`,
|
|
6746
|
+
`/v1/user-tasks/${encodeURIComponent(taskId)}?version=${encodeURIComponent(String(version))}`,
|
|
6043
6747
|
void 0,
|
|
6044
6748
|
this.getCliRequestHeaders()
|
|
6045
6749
|
);
|
|
@@ -6048,16 +6752,16 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
6048
6752
|
}
|
|
6049
6753
|
return response.data;
|
|
6050
6754
|
}
|
|
6051
|
-
async completeUserTask(taskId, input
|
|
6755
|
+
async completeUserTask(taskId, input) {
|
|
6052
6756
|
return this.postUserTaskAction(taskId, "complete", input);
|
|
6053
6757
|
}
|
|
6054
|
-
async blockUserTask(taskId, input
|
|
6758
|
+
async blockUserTask(taskId, input) {
|
|
6055
6759
|
return this.postUserTaskAction(taskId, "block", input);
|
|
6056
6760
|
}
|
|
6057
|
-
async unblockUserTask(taskId, input
|
|
6761
|
+
async unblockUserTask(taskId, input) {
|
|
6058
6762
|
return this.postUserTaskAction(taskId, "unblock", input);
|
|
6059
6763
|
}
|
|
6060
|
-
async skipUserTask(taskId, input
|
|
6764
|
+
async skipUserTask(taskId, input) {
|
|
6061
6765
|
return this.postUserTaskAction(taskId, "skip", input);
|
|
6062
6766
|
}
|
|
6063
6767
|
async reorderUserTasks(input) {
|
|
@@ -6072,7 +6776,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
6072
6776
|
}
|
|
6073
6777
|
return response.data.tasks ?? [];
|
|
6074
6778
|
}
|
|
6075
|
-
async postUserTaskAction(taskId, action, input
|
|
6779
|
+
async postUserTaskAction(taskId, action, input) {
|
|
6076
6780
|
this.requireSession();
|
|
6077
6781
|
const response = await this.http.post(
|
|
6078
6782
|
`/v1/user-tasks/${encodeURIComponent(taskId)}/${encodeURIComponent(action)}`,
|
|
@@ -6830,7 +7534,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
6830
7534
|
);
|
|
6831
7535
|
}
|
|
6832
7536
|
}
|
|
6833
|
-
const entryId = params.entryId ??
|
|
7537
|
+
const entryId = params.entryId ?? randomUUID4();
|
|
6834
7538
|
const now = Math.floor(Date.now() / 1e3);
|
|
6835
7539
|
const hashedKey = hashItemKey(params.appId, params.itemType);
|
|
6836
7540
|
const plaintextPayload = {
|
|
@@ -7305,7 +8009,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
7305
8009
|
makeWsClient(session) {
|
|
7306
8010
|
return new OpenMatesWsClient({
|
|
7307
8011
|
apiUrl: session.apiUrl,
|
|
7308
|
-
sessionId:
|
|
8012
|
+
sessionId: randomUUID4(),
|
|
7309
8013
|
wsToken: session.wsToken,
|
|
7310
8014
|
refreshToken: session.cookies.auth_refresh_token ?? null,
|
|
7311
8015
|
// Same User-Agent as login so OS-based device fingerprint hash matches.
|
|
@@ -8488,7 +9192,7 @@ var OutputRedactor = class {
|
|
|
8488
9192
|
import { readFileSync as readFileSync4, statSync, existsSync as existsSync4 } from "fs";
|
|
8489
9193
|
import { basename, extname, resolve as resolve2 } from "path";
|
|
8490
9194
|
import { homedir as homedir4 } from "os";
|
|
8491
|
-
import { createHash as
|
|
9195
|
+
import { createHash as createHash6 } from "crypto";
|
|
8492
9196
|
var MAX_PER_FILE_SIZE = 100 * 1024 * 1024;
|
|
8493
9197
|
var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8494
9198
|
".pem",
|
|
@@ -8798,7 +9502,7 @@ function processCodeFile(filePath, filename, redactor) {
|
|
|
8798
9502
|
line_count: lineCount
|
|
8799
9503
|
});
|
|
8800
9504
|
const textPreview = `${filename} (${language}, ${lineCount} lines)`;
|
|
8801
|
-
const contentHash =
|
|
9505
|
+
const contentHash = createHash6("sha256").update(content).digest("hex");
|
|
8802
9506
|
const embed = {
|
|
8803
9507
|
embedId,
|
|
8804
9508
|
embedRef,
|
|
@@ -10314,7 +11018,7 @@ function formatTs(ts) {
|
|
|
10314
11018
|
|
|
10315
11019
|
// src/server.ts
|
|
10316
11020
|
import { execFileSync as execFileSync2, execSync, spawn as nodeSpawn, spawnSync } from "child_process";
|
|
10317
|
-
import { createHash as
|
|
11021
|
+
import { createHash as createHash7, randomBytes as randomBytes3 } from "crypto";
|
|
10318
11022
|
import { chmodSync as chmodSync2, closeSync, copyFileSync, cpSync, existsSync as existsSync5, mkdirSync as mkdirSync3, mkdtempSync, openSync, readFileSync as readFileSync5, readSync, readdirSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
10319
11023
|
import { createInterface as createInterface2 } from "readline";
|
|
10320
11024
|
import { createInterface as createPromptInterface } from "readline/promises";
|
|
@@ -10932,10 +11636,10 @@ function resolveTargetImageTag(flags, currentTag, packageVersion) {
|
|
|
10932
11636
|
return { tag: getDefaultImageTagForVersion(packageVersion) };
|
|
10933
11637
|
}
|
|
10934
11638
|
function randomHex(bytes) {
|
|
10935
|
-
return
|
|
11639
|
+
return randomBytes3(bytes).toString("hex");
|
|
10936
11640
|
}
|
|
10937
11641
|
function generateInviteCode() {
|
|
10938
|
-
const digits = Array.from(
|
|
11642
|
+
const digits = Array.from(randomBytes3(12), (byte) => String(byte % 10)).join("");
|
|
10939
11643
|
return `${digits.slice(0, 4)}-${digits.slice(4, 8)}-${digits.slice(8, 12)}`;
|
|
10940
11644
|
}
|
|
10941
11645
|
function getEnvVar(content, name) {
|
|
@@ -10979,7 +11683,7 @@ function packagedCaddyTemplatePath(role) {
|
|
|
10979
11683
|
}
|
|
10980
11684
|
function fileHash(path) {
|
|
10981
11685
|
if (!existsSync5(path)) return null;
|
|
10982
|
-
return
|
|
11686
|
+
return createHash7("sha256").update(readFileSync5(path)).digest("hex");
|
|
10983
11687
|
}
|
|
10984
11688
|
async function loadSelfHostComposeTemplate(templateRef, role) {
|
|
10985
11689
|
const templateDir = process.env.OPENMATES_SELFHOST_TEMPLATE_DIR;
|
|
@@ -11365,7 +12069,7 @@ function formatSecretPreflight(preflight) {
|
|
|
11365
12069
|
return parts.join("; ");
|
|
11366
12070
|
}
|
|
11367
12071
|
function hashFile(path) {
|
|
11368
|
-
const hash =
|
|
12072
|
+
const hash = createHash7("sha256");
|
|
11369
12073
|
const fd = openSync(path, "r");
|
|
11370
12074
|
const buffer = Buffer.allocUnsafe(CHECKSUM_BUFFER_BYTES);
|
|
11371
12075
|
try {
|
|
@@ -29386,7 +30090,10 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
29386
30090
|
},
|
|
29387
30091
|
models3d: {
|
|
29388
30092
|
generate: {
|
|
29389
|
-
text: "Generate 3D model"
|
|
30093
|
+
text: "Generate 3D model",
|
|
30094
|
+
description: {
|
|
30095
|
+
text: "Create a 3D model from a text prompt or reference images."
|
|
30096
|
+
}
|
|
29390
30097
|
}
|
|
29391
30098
|
},
|
|
29392
30099
|
music: {
|
|
@@ -30241,6 +30948,12 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
30241
30948
|
text: "Create structured idea maps and brainstorm topic trees."
|
|
30242
30949
|
}
|
|
30243
30950
|
},
|
|
30951
|
+
models3d: {
|
|
30952
|
+
text: "3D Models",
|
|
30953
|
+
description: {
|
|
30954
|
+
text: "Generate 3D models from prompts or reference images."
|
|
30955
|
+
}
|
|
30956
|
+
},
|
|
30244
30957
|
math: {
|
|
30245
30958
|
text: "Math",
|
|
30246
30959
|
description: {
|
|
@@ -46753,7 +47466,7 @@ function buildAssistantFeedbackDecision(rating) {
|
|
|
46753
47466
|
}
|
|
46754
47467
|
|
|
46755
47468
|
// src/benchmark.ts
|
|
46756
|
-
import { randomUUID as
|
|
47469
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
46757
47470
|
import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
46758
47471
|
import { tmpdir } from "os";
|
|
46759
47472
|
import { dirname as dirname2, join as join4, resolve as resolve5 } from "path";
|
|
@@ -47053,7 +47766,7 @@ async function handleBenchmark(client, subcommand, rest, flags) {
|
|
|
47053
47766
|
const caseIds = parseCaseIds(flags.case);
|
|
47054
47767
|
const dryRun = flags["dry-run"] === true;
|
|
47055
47768
|
const output = typeof flags.output === "string" ? flags.output : void 0;
|
|
47056
|
-
const runId = typeof flags["run-id"] === "string" ? flags["run-id"] :
|
|
47769
|
+
const runId = typeof flags["run-id"] === "string" ? flags["run-id"] : randomUUID5();
|
|
47057
47770
|
const imagePath = typeof flags.image === "string" ? resolve5(flags.image) : defaultImageFixturePath();
|
|
47058
47771
|
if (!dryRun && flags["confirm-spend-credits"] !== true) {
|
|
47059
47772
|
throw new Error(
|
|
@@ -47463,7 +48176,7 @@ function buildLongContextHistory() {
|
|
|
47463
48176
|
}
|
|
47464
48177
|
function appendHistory(history, role, content) {
|
|
47465
48178
|
history.push({
|
|
47466
|
-
message_id:
|
|
48179
|
+
message_id: randomUUID5(),
|
|
47467
48180
|
role,
|
|
47468
48181
|
sender_name: role === "user" ? "User" : "Assistant",
|
|
47469
48182
|
content,
|
|
@@ -47998,7 +48711,7 @@ function renderBody(state, width) {
|
|
|
47998
48711
|
case "tasks":
|
|
47999
48712
|
return renderTasks(state, width);
|
|
48000
48713
|
case "task":
|
|
48001
|
-
return
|
|
48714
|
+
return renderTaskDetail2(state, width);
|
|
48002
48715
|
case "start":
|
|
48003
48716
|
default:
|
|
48004
48717
|
return renderStart(width);
|
|
@@ -48071,7 +48784,7 @@ function renderTasks(state, width) {
|
|
|
48071
48784
|
}
|
|
48072
48785
|
return lines.flatMap((line) => wrap(line, width));
|
|
48073
48786
|
}
|
|
48074
|
-
function
|
|
48787
|
+
function renderTaskDetail2(state, width) {
|
|
48075
48788
|
const task = state.activeTask;
|
|
48076
48789
|
if (!task) return renderTasks(state, width);
|
|
48077
48790
|
const assignee = task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
@@ -48388,221 +49101,6 @@ function boxed(line, width) {
|
|
|
48388
49101
|
return `\u2502 ${line.padEnd(width)} \u2502`;
|
|
48389
49102
|
}
|
|
48390
49103
|
|
|
48391
|
-
// src/tasksCli.ts
|
|
48392
|
-
import { createHash as createHash7, randomBytes as randomBytes3, randomUUID as randomUUID5 } from "crypto";
|
|
48393
|
-
var TASK_STATUSES2 = ["backlog", "todo", "in_progress", "blocked", "done"];
|
|
48394
|
-
var DEFAULT_STANDALONE_PREFIX = "TASK";
|
|
48395
|
-
function normalizeTaskStatus(value) {
|
|
48396
|
-
if (value === void 0) return void 0;
|
|
48397
|
-
if (TASK_STATUSES2.includes(value)) return value;
|
|
48398
|
-
throw new Error(`Unknown task status '${value}'. Expected one of: ${TASK_STATUSES2.join(", ")}`);
|
|
48399
|
-
}
|
|
48400
|
-
function parseAssignee(value) {
|
|
48401
|
-
if (!value || value === "user") return { assigneeType: "user", assigneeHash: null };
|
|
48402
|
-
if (["ai", "openmates", "OpenMates"].includes(value)) return { assigneeType: "ai", assigneeHash: null };
|
|
48403
|
-
return { assigneeType: "user", assigneeHash: value };
|
|
48404
|
-
}
|
|
48405
|
-
function splitCsvFlag(value) {
|
|
48406
|
-
if (typeof value !== "string") return [];
|
|
48407
|
-
return value.split(/[,\n]/).map((item) => item.trim()).filter(Boolean);
|
|
48408
|
-
}
|
|
48409
|
-
function parseDueAt(value) {
|
|
48410
|
-
if (value === void 0) return void 0;
|
|
48411
|
-
if (value === false || value === true) throw new Error("--due requires a timestamp or date value.");
|
|
48412
|
-
const numeric = Number(value);
|
|
48413
|
-
if (Number.isFinite(numeric) && numeric > 0) return Math.floor(numeric);
|
|
48414
|
-
const parsed = Date.parse(value);
|
|
48415
|
-
if (Number.isNaN(parsed)) throw new Error(`Invalid --due value '${value}'.`);
|
|
48416
|
-
return Math.floor(parsed / 1e3);
|
|
48417
|
-
}
|
|
48418
|
-
async function buildCreateUserTaskInput(masterKey, input) {
|
|
48419
|
-
const taskKey = randomBytes3(32);
|
|
48420
|
-
const encryptedTaskKey = await encryptBytesWithAesGcm(taskKey, masterKey);
|
|
48421
|
-
const timestamp = nowSeconds();
|
|
48422
|
-
const assignee = parseAssignee(input.assign);
|
|
48423
|
-
const linkedProjectIds = input.projectIds ?? [];
|
|
48424
|
-
const status = input.status ?? (assignee.assigneeType === "ai" && !input.dueAt ? "in_progress" : "todo");
|
|
48425
|
-
return {
|
|
48426
|
-
task_id: randomUUIDCompat(),
|
|
48427
|
-
short_id: void 0,
|
|
48428
|
-
encrypted_task_key: encryptedTaskKey,
|
|
48429
|
-
encrypted_title: await encryptWithAesGcmCombined(input.title, taskKey),
|
|
48430
|
-
encrypted_description: await encryptWithAesGcmCombined(input.description ?? "", taskKey),
|
|
48431
|
-
encrypted_tags: await encryptWithAesGcmCombined("[]", taskKey),
|
|
48432
|
-
encrypted_linked_project_ids: await encryptWithAesGcmCombined(JSON.stringify(linkedProjectIds), taskKey),
|
|
48433
|
-
status,
|
|
48434
|
-
assignee_type: assignee.assigneeType,
|
|
48435
|
-
assignee_hash: assignee.assigneeHash,
|
|
48436
|
-
primary_chat_id: input.chatId ?? null,
|
|
48437
|
-
linked_project_ids: linkedProjectIds,
|
|
48438
|
-
plan_id: input.planId ?? null,
|
|
48439
|
-
due_at: input.dueAt ?? null,
|
|
48440
|
-
priority: 0,
|
|
48441
|
-
position: timestamp,
|
|
48442
|
-
created_at: timestamp,
|
|
48443
|
-
updated_at: timestamp
|
|
48444
|
-
};
|
|
48445
|
-
}
|
|
48446
|
-
async function buildUpdateUserTaskInput(task, masterKey, input) {
|
|
48447
|
-
const taskKey = await taskKeyFromRecord(task.encrypted, masterKey);
|
|
48448
|
-
const patch = { version: task.version, updated_at: nowSeconds() };
|
|
48449
|
-
if (input.title !== void 0) patch.encrypted_title = await encryptWithAesGcmCombined(input.title, taskKey);
|
|
48450
|
-
if (input.description !== void 0) patch.encrypted_description = await encryptWithAesGcmCombined(input.description, taskKey);
|
|
48451
|
-
if (input.status !== void 0) patch.status = input.status;
|
|
48452
|
-
if (input.assign !== void 0) {
|
|
48453
|
-
const assignee = parseAssignee(input.assign);
|
|
48454
|
-
patch.assignee_type = assignee.assigneeType;
|
|
48455
|
-
patch.assignee_hash = assignee.assigneeHash;
|
|
48456
|
-
}
|
|
48457
|
-
if (input.chatId !== void 0) patch.primary_chat_id = input.chatId;
|
|
48458
|
-
if (input.projectIds !== void 0) {
|
|
48459
|
-
patch.linked_project_ids = input.projectIds;
|
|
48460
|
-
patch.encrypted_linked_project_ids = await encryptWithAesGcmCombined(JSON.stringify(input.projectIds), taskKey);
|
|
48461
|
-
}
|
|
48462
|
-
if (input.planId !== void 0) patch.plan_id = input.planId;
|
|
48463
|
-
return patch;
|
|
48464
|
-
}
|
|
48465
|
-
async function decryptUserTask(record, masterKey) {
|
|
48466
|
-
const taskKey = await taskKeyFromRecord(record, masterKey);
|
|
48467
|
-
const tags = parseStringArray(await decryptOptional(record.encrypted_tags, taskKey));
|
|
48468
|
-
const linkedProjectIds = parseStringArray(await decryptOptional(record.encrypted_linked_project_ids, taskKey));
|
|
48469
|
-
return {
|
|
48470
|
-
taskId: record.task_id,
|
|
48471
|
-
shortId: record.short_id || deriveShortId(record),
|
|
48472
|
-
title: await decryptOptional(record.encrypted_title, taskKey) || "(untitled task)",
|
|
48473
|
-
description: await decryptOptional(record.encrypted_description, taskKey),
|
|
48474
|
-
tags,
|
|
48475
|
-
latestInstruction: await decryptOptional(record.encrypted_latest_instruction, taskKey),
|
|
48476
|
-
status: record.status,
|
|
48477
|
-
assigneeType: record.assignee_type,
|
|
48478
|
-
assigneeHash: record.assignee_hash ?? null,
|
|
48479
|
-
primaryChatId: record.primary_chat_id ?? null,
|
|
48480
|
-
linkedProjectIds: linkedProjectIds.length > 0 ? linkedProjectIds : record.linked_project_ids ?? [],
|
|
48481
|
-
planId: record.plan_id ?? null,
|
|
48482
|
-
dueAt: record.due_at ?? null,
|
|
48483
|
-
priority: record.priority ?? 0,
|
|
48484
|
-
position: record.position ?? 0,
|
|
48485
|
-
queueState: record.queue_state ?? "none",
|
|
48486
|
-
blockedReasonCode: record.blocked_reason_code ?? null,
|
|
48487
|
-
aiExecutionState: record.ai_execution_state ?? null,
|
|
48488
|
-
version: record.version ?? 1,
|
|
48489
|
-
encrypted: record
|
|
48490
|
-
};
|
|
48491
|
-
}
|
|
48492
|
-
async function decryptUserTasks(records, masterKey) {
|
|
48493
|
-
const output = [];
|
|
48494
|
-
for (const record of records) output.push(await decryptUserTask(record, masterKey));
|
|
48495
|
-
return output;
|
|
48496
|
-
}
|
|
48497
|
-
function findTask(tasks, id) {
|
|
48498
|
-
const task = tasks.find((candidate) => candidate.taskId === id || candidate.shortId === id);
|
|
48499
|
-
if (!task) throw new Error(`Task '${id}' was not found in the current task list.`);
|
|
48500
|
-
return task;
|
|
48501
|
-
}
|
|
48502
|
-
function renderTaskList(tasks) {
|
|
48503
|
-
if (tasks.length === 0) return "No tasks found.";
|
|
48504
|
-
const lines = ["Tasks", "ID Status Assignee Queue Title"];
|
|
48505
|
-
for (const task of tasks) {
|
|
48506
|
-
lines.push(`${pad(task.shortId, 9)} ${pad(task.status, 12)} ${pad(assigneeLabel(task), 11)} ${pad(task.queueState, 11)} ${task.title}`);
|
|
48507
|
-
}
|
|
48508
|
-
return lines.join("\n");
|
|
48509
|
-
}
|
|
48510
|
-
function renderTaskDetail2(task) {
|
|
48511
|
-
const lines = [
|
|
48512
|
-
`Task ${task.shortId}`,
|
|
48513
|
-
`Title: ${task.title}`,
|
|
48514
|
-
`Status: ${task.status}`,
|
|
48515
|
-
`Assignee: ${assigneeLabel(task)}`,
|
|
48516
|
-
`Queue: ${task.queueState}`,
|
|
48517
|
-
`Task ID: ${task.taskId}`
|
|
48518
|
-
];
|
|
48519
|
-
if (task.description) lines.push(`Description: ${task.description}`);
|
|
48520
|
-
if (task.primaryChatId) lines.push(`Chat: ${task.primaryChatId}`);
|
|
48521
|
-
if (task.linkedProjectIds.length > 0) lines.push(`Projects: ${task.linkedProjectIds.join(", ")}`);
|
|
48522
|
-
if (task.planId) lines.push(`Plan: ${task.planId}`);
|
|
48523
|
-
if (task.blockedReasonCode) lines.push(`Blocked reason: ${task.blockedReasonCode}`);
|
|
48524
|
-
if (task.aiExecutionState) lines.push(`AI state: ${task.aiExecutionState}`);
|
|
48525
|
-
return lines.join("\n");
|
|
48526
|
-
}
|
|
48527
|
-
function renderTaskBoard(tasks, width = process.stdout.columns || 100) {
|
|
48528
|
-
const columns = TASK_STATUSES2.map((status) => ({ status, tasks: tasks.filter((task) => task.status === status).sort(compareTasks) }));
|
|
48529
|
-
if (width < 96) {
|
|
48530
|
-
const lines2 = ["OpenMates Tasks Board"];
|
|
48531
|
-
for (const column of columns) {
|
|
48532
|
-
lines2.push("", `${columnTitle(column.status)} (${column.tasks.length})`, "-".repeat(24));
|
|
48533
|
-
lines2.push(...boardColumnLines(column.tasks, 8));
|
|
48534
|
-
}
|
|
48535
|
-
return lines2.join("\n");
|
|
48536
|
-
}
|
|
48537
|
-
const perColumn = 6;
|
|
48538
|
-
const columnWidth = 22;
|
|
48539
|
-
const lines = ["OpenMates Tasks Board", ""];
|
|
48540
|
-
lines.push(columns.map((column) => pad(`${columnTitle(column.status)} (${column.tasks.length})`, columnWidth)).join(" "));
|
|
48541
|
-
lines.push(columns.map(() => "-".repeat(columnWidth)).join(" "));
|
|
48542
|
-
const renderedColumns = columns.map((column) => boardColumnLines(column.tasks, perColumn).map((line) => truncate(line, columnWidth)));
|
|
48543
|
-
const maxRows = Math.max(...renderedColumns.map((column) => column.length));
|
|
48544
|
-
for (let row = 0; row < maxRows; row += 1) {
|
|
48545
|
-
lines.push(renderedColumns.map((column) => pad(column[row] ?? "", columnWidth)).join(" "));
|
|
48546
|
-
}
|
|
48547
|
-
return lines.join("\n");
|
|
48548
|
-
}
|
|
48549
|
-
function boardColumnLines(tasks, limit) {
|
|
48550
|
-
if (tasks.length === 0) return ["No tasks here."];
|
|
48551
|
-
const visible = tasks.slice(0, limit).flatMap((task) => [
|
|
48552
|
-
`[${task.shortId}] ${task.title}`,
|
|
48553
|
-
` ${assigneeLabel(task)} ${task.queueState === "none" ? "" : task.queueState}`.trimEnd()
|
|
48554
|
-
]);
|
|
48555
|
-
if (tasks.length > limit) visible.push(`... ${tasks.length - limit} more`);
|
|
48556
|
-
return visible;
|
|
48557
|
-
}
|
|
48558
|
-
function compareTasks(a, b) {
|
|
48559
|
-
return a.position - b.position || a.title.localeCompare(b.title);
|
|
48560
|
-
}
|
|
48561
|
-
function columnTitle(status) {
|
|
48562
|
-
if (status === "in_progress") return "In progress";
|
|
48563
|
-
return status.slice(0, 1).toUpperCase() + status.slice(1).replace("_", " ");
|
|
48564
|
-
}
|
|
48565
|
-
function assigneeLabel(task) {
|
|
48566
|
-
return task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
48567
|
-
}
|
|
48568
|
-
async function taskKeyFromRecord(record, masterKey) {
|
|
48569
|
-
if (!record.encrypted_task_key) throw new Error(`Task ${record.task_id} is missing encrypted task key.`);
|
|
48570
|
-
const taskKey = await decryptBytesWithAesGcm(record.encrypted_task_key, masterKey);
|
|
48571
|
-
if (!taskKey) throw new Error(`Failed to decrypt task key for ${record.task_id}.`);
|
|
48572
|
-
return taskKey;
|
|
48573
|
-
}
|
|
48574
|
-
async function decryptOptional(value, key) {
|
|
48575
|
-
if (!value) return "";
|
|
48576
|
-
return await decryptWithAesGcmCombined(value, key) ?? "";
|
|
48577
|
-
}
|
|
48578
|
-
function parseStringArray(value) {
|
|
48579
|
-
if (!value) return [];
|
|
48580
|
-
try {
|
|
48581
|
-
const parsed = JSON.parse(value);
|
|
48582
|
-
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
48583
|
-
} catch {
|
|
48584
|
-
return [];
|
|
48585
|
-
}
|
|
48586
|
-
}
|
|
48587
|
-
function deriveShortId(record) {
|
|
48588
|
-
const prefix = record.short_id_prefix || DEFAULT_STANDALONE_PREFIX;
|
|
48589
|
-
const source = record.task_id || `${record.created_at ?? ""}-${record.position ?? ""}`;
|
|
48590
|
-
const digest = createHash7("sha256").update(source).digest("hex").slice(0, 4).toUpperCase();
|
|
48591
|
-
return `${prefix}-${parseInt(digest, 16) % 1e4}`;
|
|
48592
|
-
}
|
|
48593
|
-
function nowSeconds() {
|
|
48594
|
-
return Math.floor(Date.now() / 1e3);
|
|
48595
|
-
}
|
|
48596
|
-
function randomUUIDCompat() {
|
|
48597
|
-
return randomUUID5();
|
|
48598
|
-
}
|
|
48599
|
-
function pad(value, length) {
|
|
48600
|
-
return truncate(value, length).padEnd(length);
|
|
48601
|
-
}
|
|
48602
|
-
function truncate(value, length) {
|
|
48603
|
-
return value.length <= length ? value : `${value.slice(0, Math.max(0, length - 3))}...`;
|
|
48604
|
-
}
|
|
48605
|
-
|
|
48606
49104
|
// src/tui.ts
|
|
48607
49105
|
function defaultModeForStreams(input, output) {
|
|
48608
49106
|
return input.isTTY === true && output.isTTY === true ? "tui" : "quickstart";
|
|
@@ -49036,7 +49534,7 @@ async function handleActiveTaskAction(params) {
|
|
|
49036
49534
|
render();
|
|
49037
49535
|
return;
|
|
49038
49536
|
}
|
|
49039
|
-
await client.deleteUserTask(task.taskId);
|
|
49537
|
+
await client.deleteUserTask(task.taskId, task.version);
|
|
49040
49538
|
state.tasks = state.tasks.filter((candidate) => candidate.taskId !== task.taskId);
|
|
49041
49539
|
state.activeTask = null;
|
|
49042
49540
|
state.selectedIndex = Math.min(state.selectedIndex, Math.max(0, state.tasks.length - 1));
|
|
@@ -49053,7 +49551,7 @@ async function handleActiveTaskAction(params) {
|
|
|
49053
49551
|
render();
|
|
49054
49552
|
return;
|
|
49055
49553
|
}
|
|
49056
|
-
const updated2 = await client.reorderUserTasks({ moves: [{ task_id: task.taskId, position }] });
|
|
49554
|
+
const updated2 = await client.reorderUserTasks({ moves: [{ task_id: task.taskId, version: task.version, position }] });
|
|
49057
49555
|
const [decrypted2] = await decryptUserTasks(updated2, client.getMasterKeyBytes());
|
|
49058
49556
|
if (decrypted2) {
|
|
49059
49557
|
replaceTask(state, decrypted2);
|
|
@@ -50145,7 +50643,7 @@ async function handleTasks(client, subcommand, rest, flags) {
|
|
|
50145
50643
|
if (subcommand === "delete") {
|
|
50146
50644
|
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, "delete");
|
|
50147
50645
|
if (flags.confirm !== true) throw new Error("Deleting a task requires --confirm.");
|
|
50148
|
-
const result = await client.deleteUserTask(task.taskId);
|
|
50646
|
+
const result = await client.deleteUserTask(task.taskId, task.version);
|
|
50149
50647
|
if (flags.json === true) printJson2(result);
|
|
50150
50648
|
else console.log(`Task deleted: ${task.shortId}`);
|
|
50151
50649
|
return;
|
|
@@ -50176,7 +50674,7 @@ async function handleTasks(client, subcommand, rest, flags) {
|
|
|
50176
50674
|
}
|
|
50177
50675
|
if (subcommand === "reorder") {
|
|
50178
50676
|
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, "reorder");
|
|
50179
|
-
const move = { task_id: task.taskId };
|
|
50677
|
+
const move = { task_id: task.taskId, version: task.version };
|
|
50180
50678
|
if (typeof flags.before === "string") move.before_task_id = (await resolveTask(client, masterKey, flags.before, scope)).taskId;
|
|
50181
50679
|
if (typeof flags.after === "string") move.after_task_id = (await resolveTask(client, masterKey, flags.after, scope)).taskId;
|
|
50182
50680
|
if (typeof flags.status === "string") move.status = normalizeTaskStatus(flags.status);
|
|
@@ -50211,7 +50709,7 @@ async function requiredResolvedTask(client, masterKey, id, scope, action) {
|
|
|
50211
50709
|
}
|
|
50212
50710
|
function printTaskOutput(task, flags) {
|
|
50213
50711
|
if (flags.json === true) printJson2({ task: taskToJson(task) });
|
|
50214
|
-
else console.log(
|
|
50712
|
+
else console.log(renderTaskDetail(task));
|
|
50215
50713
|
}
|
|
50216
50714
|
function taskToJson(task) {
|
|
50217
50715
|
return {
|
|
@@ -50316,6 +50814,10 @@ function parsePositiveIntegerFlag(value, flagName) {
|
|
|
50316
50814
|
}
|
|
50317
50815
|
return parsed;
|
|
50318
50816
|
}
|
|
50817
|
+
function parseResponseTimeoutMs(flags) {
|
|
50818
|
+
const seconds = parsePositiveIntegerFlag(flags["response-timeout-seconds"], "--response-timeout-seconds");
|
|
50819
|
+
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
50820
|
+
}
|
|
50319
50821
|
function parseRemoteAccessSourceType(value) {
|
|
50320
50822
|
if (value === void 0) return "local_folder";
|
|
50321
50823
|
if (value === "local_folder" || value === "local_git_repository") {
|
|
@@ -50459,6 +50961,7 @@ async function handleChats(client, subcommand, rest, flags, redactor) {
|
|
|
50459
50961
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50460
50962
|
acceptTaskProposals: flags["accept-task-proposals"] === true,
|
|
50461
50963
|
piiDetection: flags["no-pii-detection"] !== true,
|
|
50964
|
+
responseTimeoutMs: parseResponseTimeoutMs(flags),
|
|
50462
50965
|
anonymousLearningMode: client.hasSession() ? void 0 : parseAnonymousLearningModeFlags(flags)
|
|
50463
50966
|
},
|
|
50464
50967
|
redactor
|
|
@@ -50534,7 +51037,8 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
50534
51037
|
autoApproveSubChats: flags["auto-approve"] === true,
|
|
50535
51038
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50536
51039
|
acceptTaskProposals: flags["accept-task-proposals"] === true,
|
|
50537
|
-
piiDetection: flags["no-pii-detection"] !== true
|
|
51040
|
+
piiDetection: flags["no-pii-detection"] !== true,
|
|
51041
|
+
responseTimeoutMs: parseResponseTimeoutMs(flags)
|
|
50538
51042
|
},
|
|
50539
51043
|
redactor
|
|
50540
51044
|
);
|
|
@@ -50563,7 +51067,8 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
50563
51067
|
autoApproveSubChats: flags["auto-approve"] === true,
|
|
50564
51068
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50565
51069
|
acceptTaskProposals: flags["accept-task-proposals"] === true,
|
|
50566
|
-
piiDetection: flags["no-pii-detection"] !== true
|
|
51070
|
+
piiDetection: flags["no-pii-detection"] !== true,
|
|
51071
|
+
responseTimeoutMs: parseResponseTimeoutMs(flags)
|
|
50567
51072
|
},
|
|
50568
51073
|
redactor
|
|
50569
51074
|
);
|
|
@@ -50584,7 +51089,8 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
50584
51089
|
json: flags.json === true,
|
|
50585
51090
|
autoApproveSubChats: flags["auto-approve"] === true,
|
|
50586
51091
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50587
|
-
piiDetection: flags["no-pii-detection"] !== true
|
|
51092
|
+
piiDetection: flags["no-pii-detection"] !== true,
|
|
51093
|
+
responseTimeoutMs: parseResponseTimeoutMs(flags)
|
|
50588
51094
|
},
|
|
50589
51095
|
redactor
|
|
50590
51096
|
);
|
|
@@ -53971,7 +54477,7 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
53971
54477
|
process.stdout.write(`${result2.assistant}
|
|
53972
54478
|
`);
|
|
53973
54479
|
}
|
|
53974
|
-
return { ...result2, acceptedTaskProposals: [] };
|
|
54480
|
+
return { ...result2, taskEvents: [], pendingTaskUpdateJobs: [], acceptedTaskProposals: [] };
|
|
53975
54481
|
}
|
|
53976
54482
|
const urlResult = prepareUrlEmbeds(finalMessage);
|
|
53977
54483
|
finalMessage = urlResult.message;
|
|
@@ -53986,6 +54492,7 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
53986
54492
|
onSubChatApprovalRequest,
|
|
53987
54493
|
autoApproveSubChats: params.autoApproveSubChats,
|
|
53988
54494
|
autoApproveMemories: params.autoApproveMemories,
|
|
54495
|
+
responseTimeoutMs: params.responseTimeoutMs,
|
|
53989
54496
|
learningMode,
|
|
53990
54497
|
preparedEmbeds: preparedEmbeds.length > 0 ? preparedEmbeds : void 0,
|
|
53991
54498
|
piiMappings: piiResult.mappings.map((mapping) => ({
|
|
@@ -54077,6 +54584,24 @@ ${result.assistant}`) : [];
|
|
|
54077
54584
|
process.stdout.write(`${SEP}
|
|
54078
54585
|
`);
|
|
54079
54586
|
}
|
|
54587
|
+
if (result.taskEvents.length > 0) {
|
|
54588
|
+
process.stdout.write(`\x1B[2mTask updates:\x1B[0m
|
|
54589
|
+
`);
|
|
54590
|
+
for (const event of result.taskEvents) {
|
|
54591
|
+
process.stdout.write(` \x1B[2m- ${formatTaskEvent(event)}\x1B[0m
|
|
54592
|
+
`);
|
|
54593
|
+
}
|
|
54594
|
+
process.stdout.write(`${SEP}
|
|
54595
|
+
`);
|
|
54596
|
+
}
|
|
54597
|
+
if (result.pendingTaskUpdateJobs.length > 0) {
|
|
54598
|
+
const count = result.pendingTaskUpdateJobs.length;
|
|
54599
|
+
process.stdout.write(
|
|
54600
|
+
`\x1B[2mPending encrypted task update${count === 1 ? "" : "s"}: ${count}\x1B[0m
|
|
54601
|
+
${SEP}
|
|
54602
|
+
`
|
|
54603
|
+
);
|
|
54604
|
+
}
|
|
54080
54605
|
process.stdout.write(
|
|
54081
54606
|
`\x1B[2mContinue: openmates chats send --chat ${shortId} "your message"\x1B[0m
|
|
54082
54607
|
\x1B[2mHistory: openmates chats show ${shortId}\x1B[0m
|
|
@@ -54085,6 +54610,26 @@ ${result.assistant}`) : [];
|
|
|
54085
54610
|
}
|
|
54086
54611
|
return { ...result, acceptedTaskProposals };
|
|
54087
54612
|
}
|
|
54613
|
+
function formatTaskEvent(event) {
|
|
54614
|
+
const taskLabel = event.short_id || event.task_id;
|
|
54615
|
+
const title = event.title ? ` "${event.title}"` : "";
|
|
54616
|
+
const status = event.status ? ` (${event.status})` : "";
|
|
54617
|
+
const reason = event.reason ? `: ${event.reason}` : "";
|
|
54618
|
+
switch (event.event_type) {
|
|
54619
|
+
case "created":
|
|
54620
|
+
return `${taskLabel} created${title}${status}`;
|
|
54621
|
+
case "updated":
|
|
54622
|
+
return `${taskLabel} updated${title}${status}`;
|
|
54623
|
+
case "blocked":
|
|
54624
|
+
return `${taskLabel} blocked${reason}`;
|
|
54625
|
+
case "completed":
|
|
54626
|
+
return `${taskLabel} completed${title}`;
|
|
54627
|
+
case "unblocked":
|
|
54628
|
+
return `${taskLabel} unblocked`;
|
|
54629
|
+
default:
|
|
54630
|
+
return `${taskLabel} ${event.event_type}${title}${status}${reason}`;
|
|
54631
|
+
}
|
|
54632
|
+
}
|
|
54088
54633
|
async function acceptChatTaskProposals(client, chatId, proposals, fallbackText) {
|
|
54089
54634
|
let proposalsToAccept = proposals;
|
|
54090
54635
|
if (proposalsToAccept.length === 0 && fallbackText.trim()) {
|