openmates 0.14.8-alpha.0 → 0.14.8-alpha.10
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-7BIVPFKJ.js → chunk-WEU7CSZD.js} +925 -271
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +83 -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";
|
|
@@ -1190,6 +1190,8 @@ var SUB_CHAT_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1190
1190
|
var SUB_CHAT_PARENT_STATUS_MESSAGE = "I've started the sub-chats and will continue once they finish.";
|
|
1191
1191
|
var SUB_CHAT_COMPLETION_TIMEOUT_MS = 10 * 6e4;
|
|
1192
1192
|
var CLIENT_UPDATE_REQUIRED_GUIDANCE = "OpenMates CLI update required. Run `openmates upgrade` and retry.";
|
|
1193
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set(["backlog", "todo", "in_progress", "blocked", "done"]);
|
|
1194
|
+
var TASK_ASSIGNEES = /* @__PURE__ */ new Set(["ai", "user"]);
|
|
1193
1195
|
var WebSocketProtocolError = class extends Error {
|
|
1194
1196
|
code;
|
|
1195
1197
|
constructor(code, message) {
|
|
@@ -1212,14 +1214,121 @@ function websocketProtocolError(envelope) {
|
|
|
1212
1214
|
}
|
|
1213
1215
|
return null;
|
|
1214
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 parseTaskProposals(value) {
|
|
1243
|
+
if (!Array.isArray(value)) return [];
|
|
1244
|
+
return value.flatMap((item) => {
|
|
1245
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
1246
|
+
const raw = item;
|
|
1247
|
+
if (typeof raw.title !== "string" || raw.title.trim().length === 0) return [];
|
|
1248
|
+
const proposal = { title: raw.title };
|
|
1249
|
+
if (typeof raw.description === "string" || raw.description === null) proposal.description = raw.description;
|
|
1250
|
+
if (typeof raw.status === "string" && TASK_STATUSES.has(raw.status)) proposal.status = raw.status;
|
|
1251
|
+
if (typeof raw.assignee_type === "string" && TASK_ASSIGNEES.has(raw.assignee_type)) proposal.assignee_type = raw.assignee_type;
|
|
1252
|
+
return [proposal];
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
function parseTaskUpdateProposals(value) {
|
|
1256
|
+
if (!Array.isArray(value)) return [];
|
|
1257
|
+
return value.flatMap((item) => {
|
|
1258
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
1259
|
+
const raw = item;
|
|
1260
|
+
if (typeof raw.task_id !== "string" || raw.task_id.trim().length === 0) return [];
|
|
1261
|
+
const proposal = { task_id: raw.task_id };
|
|
1262
|
+
if (typeof raw.title === "string" || raw.title === null) proposal.title = raw.title;
|
|
1263
|
+
if (typeof raw.description === "string" || raw.description === null) proposal.description = raw.description;
|
|
1264
|
+
if (typeof raw.status === "string" && TASK_STATUSES.has(raw.status) || raw.status === null) proposal.status = raw.status;
|
|
1265
|
+
if (typeof raw.assignee_type === "string" && TASK_ASSIGNEES.has(raw.assignee_type) || raw.assignee_type === null) proposal.assignee_type = raw.assignee_type;
|
|
1266
|
+
return [proposal];
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
function parseTaskEvent(value) {
|
|
1270
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1271
|
+
const raw = value;
|
|
1272
|
+
if (typeof raw.event_id !== "string" || typeof raw.chat_id !== "string") return null;
|
|
1273
|
+
if (typeof raw.task_id !== "string" || typeof raw.event_type !== "string") return null;
|
|
1274
|
+
const event = {
|
|
1275
|
+
event_id: raw.event_id,
|
|
1276
|
+
chat_id: raw.chat_id,
|
|
1277
|
+
task_id: raw.task_id,
|
|
1278
|
+
event_type: raw.event_type
|
|
1279
|
+
};
|
|
1280
|
+
if (typeof raw.short_id === "string" || raw.short_id === null) event.short_id = raw.short_id;
|
|
1281
|
+
if (typeof raw.title === "string" || raw.title === null) event.title = raw.title;
|
|
1282
|
+
if (typeof raw.status === "string" || raw.status === null) event.status = raw.status;
|
|
1283
|
+
if (typeof raw.reason === "string" || raw.reason === null) event.reason = raw.reason;
|
|
1284
|
+
if (typeof raw.created_at === "number" || raw.created_at === null) event.created_at = raw.created_at;
|
|
1285
|
+
if (typeof raw.task_update_job_id === "string" || raw.task_update_job_id === null) event.task_update_job_id = raw.task_update_job_id;
|
|
1286
|
+
return event;
|
|
1287
|
+
}
|
|
1288
|
+
function parsePendingTaskUpdateJobs(value) {
|
|
1289
|
+
if (!Array.isArray(value)) return [];
|
|
1290
|
+
return value.flatMap((item) => {
|
|
1291
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
1292
|
+
const raw = item;
|
|
1293
|
+
if (typeof raw.job_id !== "string" || typeof raw.task_id !== "string") return [];
|
|
1294
|
+
if (typeof raw.revision !== "number" || typeof raw.task_key_version !== "number" || typeof raw.expires_at !== "number") return [];
|
|
1295
|
+
const job = {
|
|
1296
|
+
job_id: raw.job_id,
|
|
1297
|
+
task_id: raw.task_id,
|
|
1298
|
+
revision: raw.revision,
|
|
1299
|
+
task_key_version: raw.task_key_version,
|
|
1300
|
+
expires_at: raw.expires_at
|
|
1301
|
+
};
|
|
1302
|
+
if (typeof raw.chat_id === "string" || raw.chat_id === null) job.chat_id = raw.chat_id;
|
|
1303
|
+
return [job];
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
function parseAvailableRecoveryJobs(value) {
|
|
1307
|
+
if (!Array.isArray(value)) return [];
|
|
1308
|
+
return value.flatMap((item) => {
|
|
1309
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
1310
|
+
const raw = item;
|
|
1311
|
+
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 [];
|
|
1312
|
+
return [{
|
|
1313
|
+
job_id: raw.job_id,
|
|
1314
|
+
chat_id: raw.chat_id,
|
|
1315
|
+
turn_id: raw.turn_id,
|
|
1316
|
+
assistant_message_id: raw.assistant_message_id,
|
|
1317
|
+
chat_key_version: raw.chat_key_version
|
|
1318
|
+
}];
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1215
1321
|
var OpenMatesWsClient = class {
|
|
1216
1322
|
socket;
|
|
1323
|
+
passiveTaskUpdateJobs = /* @__PURE__ */ new Map();
|
|
1324
|
+
activeResponseCollectors = 0;
|
|
1217
1325
|
constructor(options) {
|
|
1218
1326
|
const wsBase = options.apiUrl.replace(/^http/, "ws").replace(/\/$/, "");
|
|
1219
1327
|
const token = options.wsToken || options.refreshToken || "";
|
|
1220
1328
|
const query = new URLSearchParams({
|
|
1221
1329
|
sessionId: options.sessionId,
|
|
1222
|
-
token
|
|
1330
|
+
token,
|
|
1331
|
+
client_capabilities: "task_update_jobs"
|
|
1223
1332
|
});
|
|
1224
1333
|
const wsHeaders = {};
|
|
1225
1334
|
if (options.userAgent) {
|
|
@@ -1234,6 +1343,10 @@ var OpenMatesWsClient = class {
|
|
|
1234
1343
|
this.socket = new WebSocket(`${wsBase}/v1/ws?${query.toString()}`, {
|
|
1235
1344
|
headers: wsHeaders
|
|
1236
1345
|
});
|
|
1346
|
+
this.socket.on("message", (rawData) => {
|
|
1347
|
+
if (this.activeResponseCollectors > 0) return;
|
|
1348
|
+
this.bufferPassiveTaskUpdateJobs(rawData);
|
|
1349
|
+
});
|
|
1237
1350
|
}
|
|
1238
1351
|
async open(timeoutMs = 1e4) {
|
|
1239
1352
|
await new Promise((resolve7, reject) => {
|
|
@@ -1279,6 +1392,22 @@ var OpenMatesWsClient = class {
|
|
|
1279
1392
|
});
|
|
1280
1393
|
});
|
|
1281
1394
|
}
|
|
1395
|
+
bufferPassiveTaskUpdateJobs(rawData) {
|
|
1396
|
+
try {
|
|
1397
|
+
const parsed = JSON.parse(rawData.toString());
|
|
1398
|
+
if (parsed.type !== "task_update_jobs_available") return;
|
|
1399
|
+
const payload = parsed.payload ?? {};
|
|
1400
|
+
for (const job of parsePendingTaskUpdateJobs(payload.jobs)) {
|
|
1401
|
+
this.passiveTaskUpdateJobs.set(job.job_id, job);
|
|
1402
|
+
}
|
|
1403
|
+
} catch {
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
drainPassiveTaskUpdateJobs() {
|
|
1407
|
+
const jobs = [...this.passiveTaskUpdateJobs.values()];
|
|
1408
|
+
this.passiveTaskUpdateJobs.clear();
|
|
1409
|
+
return jobs;
|
|
1410
|
+
}
|
|
1282
1411
|
waitForMessage(expectedType, predicate, timeoutMs = 2e4) {
|
|
1283
1412
|
return new Promise((resolve7, reject) => {
|
|
1284
1413
|
const seenTypes = /* @__PURE__ */ new Set();
|
|
@@ -1412,6 +1541,16 @@ var OpenMatesWsClient = class {
|
|
|
1412
1541
|
let recoveryJobId = null;
|
|
1413
1542
|
let followUpSuggestions = [];
|
|
1414
1543
|
let newChatSuggestions = [];
|
|
1544
|
+
let taskProposals = [];
|
|
1545
|
+
let taskUpdateProposals = [];
|
|
1546
|
+
const taskEvents = [];
|
|
1547
|
+
this.activeResponseCollectors += 1;
|
|
1548
|
+
let pendingTaskUpdateJobs = this.drainPassiveTaskUpdateJobs();
|
|
1549
|
+
const mergePendingTaskUpdateJobs = (jobs) => {
|
|
1550
|
+
const byId = new Map(pendingTaskUpdateJobs.map((job) => [job.job_id, job]));
|
|
1551
|
+
for (const job of jobs) byId.set(job.job_id, job);
|
|
1552
|
+
pendingTaskUpdateJobs = [...byId.values()];
|
|
1553
|
+
};
|
|
1415
1554
|
const subChatEvents = [];
|
|
1416
1555
|
const pendingSubChatHandlers = /* @__PURE__ */ new Set();
|
|
1417
1556
|
const pendingMemoryRequestHandlers = /* @__PURE__ */ new Set();
|
|
@@ -1472,6 +1611,10 @@ var OpenMatesWsClient = class {
|
|
|
1472
1611
|
modelName,
|
|
1473
1612
|
followUpSuggestions,
|
|
1474
1613
|
newChatSuggestions,
|
|
1614
|
+
taskProposals,
|
|
1615
|
+
taskUpdateProposals,
|
|
1616
|
+
taskEvents,
|
|
1617
|
+
pendingTaskUpdateJobs,
|
|
1475
1618
|
embeds: [...embeds.values()],
|
|
1476
1619
|
subChatEvents,
|
|
1477
1620
|
recoveryJobId
|
|
@@ -1479,6 +1622,7 @@ var OpenMatesWsClient = class {
|
|
|
1479
1622
|
return;
|
|
1480
1623
|
}
|
|
1481
1624
|
if (!aiResponseDone || !postProcessingDone) return;
|
|
1625
|
+
if (options?.recoveryTurnId && !recoveryJobId) return;
|
|
1482
1626
|
if (pendingSubChatHandlers.size > 0) return;
|
|
1483
1627
|
if (pendingMemoryRequestHandlers.size > 0) return;
|
|
1484
1628
|
if (processingEmbedIds.size > 0 && !asyncEmbedTimer) {
|
|
@@ -1493,6 +1637,10 @@ var OpenMatesWsClient = class {
|
|
|
1493
1637
|
modelName,
|
|
1494
1638
|
followUpSuggestions,
|
|
1495
1639
|
newChatSuggestions,
|
|
1640
|
+
taskProposals,
|
|
1641
|
+
taskUpdateProposals,
|
|
1642
|
+
taskEvents,
|
|
1643
|
+
pendingTaskUpdateJobs,
|
|
1496
1644
|
embeds: [...embeds.values()],
|
|
1497
1645
|
subChatEvents,
|
|
1498
1646
|
recoveryJobId
|
|
@@ -1511,6 +1659,10 @@ var OpenMatesWsClient = class {
|
|
|
1511
1659
|
modelName,
|
|
1512
1660
|
followUpSuggestions,
|
|
1513
1661
|
newChatSuggestions,
|
|
1662
|
+
taskProposals,
|
|
1663
|
+
taskUpdateProposals,
|
|
1664
|
+
taskEvents,
|
|
1665
|
+
pendingTaskUpdateJobs,
|
|
1514
1666
|
embeds: [...embeds.values()],
|
|
1515
1667
|
subChatEvents,
|
|
1516
1668
|
recoveryJobId
|
|
@@ -1587,6 +1739,9 @@ var OpenMatesWsClient = class {
|
|
|
1587
1739
|
const type = parsed.type;
|
|
1588
1740
|
const protocolError = websocketProtocolError(parsed);
|
|
1589
1741
|
if (protocolError) {
|
|
1742
|
+
if (!errorFrameBelongsToAiResponse(parsed, userMessageId, chatId, options?.recoveryTurnId)) {
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1590
1745
|
cleanup();
|
|
1591
1746
|
reject(protocolError);
|
|
1592
1747
|
return;
|
|
@@ -1617,6 +1772,29 @@ var OpenMatesWsClient = class {
|
|
|
1617
1772
|
}
|
|
1618
1773
|
return;
|
|
1619
1774
|
}
|
|
1775
|
+
if (type === "task_event") {
|
|
1776
|
+
const event = parseTaskEvent(p);
|
|
1777
|
+
if (!event || event.chat_id !== chatId) return;
|
|
1778
|
+
taskEvents.push(event);
|
|
1779
|
+
maybeResolve();
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
if (type === "task_update_jobs_available") {
|
|
1783
|
+
mergePendingTaskUpdateJobs(parsePendingTaskUpdateJobs(p.jobs));
|
|
1784
|
+
maybeResolve();
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
if (type === "recovery_jobs_available") {
|
|
1788
|
+
const job = parseAvailableRecoveryJobs(p.jobs).find(
|
|
1789
|
+
(candidate) => candidate.chat_id === chatId && (!options?.recoveryTurnId || candidate.turn_id === options.recoveryTurnId)
|
|
1790
|
+
);
|
|
1791
|
+
if (!job) return;
|
|
1792
|
+
recoveryJobId = job.job_id;
|
|
1793
|
+
messageId = job.assistant_message_id;
|
|
1794
|
+
if (aiResponseDone) maybeResolve();
|
|
1795
|
+
else scheduleResolve(latestContent);
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1620
1798
|
if (type === "ai_message_update") {
|
|
1621
1799
|
const msgId = p.user_message_id ?? p.userMessageId;
|
|
1622
1800
|
if (msgId !== userMessageId && p.chat_id !== chatId) return;
|
|
@@ -1695,6 +1873,8 @@ var OpenMatesWsClient = class {
|
|
|
1695
1873
|
(s) => typeof s === "string" && s.length > 0
|
|
1696
1874
|
);
|
|
1697
1875
|
}
|
|
1876
|
+
taskProposals = parseTaskProposals(p.task_proposals);
|
|
1877
|
+
taskUpdateProposals = parseTaskUpdateProposals(p.task_update_proposals);
|
|
1698
1878
|
if (aiResponseDone) {
|
|
1699
1879
|
if (postProcessingTimer) {
|
|
1700
1880
|
clearTimeout(postProcessingTimer);
|
|
@@ -1723,6 +1903,10 @@ var OpenMatesWsClient = class {
|
|
|
1723
1903
|
modelName,
|
|
1724
1904
|
followUpSuggestions,
|
|
1725
1905
|
newChatSuggestions,
|
|
1906
|
+
taskProposals,
|
|
1907
|
+
taskUpdateProposals,
|
|
1908
|
+
taskEvents,
|
|
1909
|
+
pendingTaskUpdateJobs,
|
|
1726
1910
|
embeds: [...embeds.values()],
|
|
1727
1911
|
subChatEvents,
|
|
1728
1912
|
recoveryJobId
|
|
@@ -1745,6 +1929,7 @@ var OpenMatesWsClient = class {
|
|
|
1745
1929
|
this.socket.off("message", onMessage);
|
|
1746
1930
|
this.socket.off("error", onError);
|
|
1747
1931
|
this.socket.off("close", onClose);
|
|
1932
|
+
this.activeResponseCollectors = Math.max(0, this.activeResponseCollectors - 1);
|
|
1748
1933
|
};
|
|
1749
1934
|
this.socket.on("message", onMessage);
|
|
1750
1935
|
this.socket.on("error", onError);
|
|
@@ -2473,6 +2658,227 @@ function toArrayBuffer3(input) {
|
|
|
2473
2658
|
return output;
|
|
2474
2659
|
}
|
|
2475
2660
|
|
|
2661
|
+
// src/tasksCli.ts
|
|
2662
|
+
import { createHash as createHash5, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
|
|
2663
|
+
var TASK_STATUSES2 = ["backlog", "todo", "in_progress", "blocked", "done"];
|
|
2664
|
+
var DEFAULT_STANDALONE_PREFIX = "TASK";
|
|
2665
|
+
function normalizeTaskStatus(value) {
|
|
2666
|
+
if (value === void 0) return void 0;
|
|
2667
|
+
if (TASK_STATUSES2.includes(value)) return value;
|
|
2668
|
+
throw new Error(`Unknown task status '${value}'. Expected one of: ${TASK_STATUSES2.join(", ")}`);
|
|
2669
|
+
}
|
|
2670
|
+
function parseAssignee(value) {
|
|
2671
|
+
if (!value || value === "user") return { assigneeType: "user", assigneeHash: null };
|
|
2672
|
+
if (["ai", "openmates", "OpenMates"].includes(value)) return { assigneeType: "ai", assigneeHash: null };
|
|
2673
|
+
return { assigneeType: "user", assigneeHash: value };
|
|
2674
|
+
}
|
|
2675
|
+
function splitCsvFlag(value) {
|
|
2676
|
+
if (typeof value !== "string") return [];
|
|
2677
|
+
return value.split(/[,\n]/).map((item) => item.trim()).filter(Boolean);
|
|
2678
|
+
}
|
|
2679
|
+
function parseDueAt(value) {
|
|
2680
|
+
if (value === void 0) return void 0;
|
|
2681
|
+
if (value === false || value === true) throw new Error("--due requires a timestamp or date value.");
|
|
2682
|
+
const numeric = Number(value);
|
|
2683
|
+
if (Number.isFinite(numeric) && numeric > 0) return Math.floor(numeric);
|
|
2684
|
+
const parsed = Date.parse(value);
|
|
2685
|
+
if (Number.isNaN(parsed)) throw new Error(`Invalid --due value '${value}'.`);
|
|
2686
|
+
return Math.floor(parsed / 1e3);
|
|
2687
|
+
}
|
|
2688
|
+
async function buildCreateUserTaskInput(masterKey, input) {
|
|
2689
|
+
const taskKey = randomBytes2(32);
|
|
2690
|
+
const encryptedTaskKey = await encryptBytesWithAesGcm(taskKey, masterKey);
|
|
2691
|
+
const timestamp = nowSeconds();
|
|
2692
|
+
const assignee = parseAssignee(input.assign);
|
|
2693
|
+
const linkedProjectIds = input.projectIds ?? [];
|
|
2694
|
+
const status = input.status ?? (assignee.assigneeType === "ai" && !input.dueAt ? "in_progress" : "todo");
|
|
2695
|
+
return {
|
|
2696
|
+
task_id: randomUUIDCompat(),
|
|
2697
|
+
short_id: void 0,
|
|
2698
|
+
version: 1,
|
|
2699
|
+
encrypted_task_key: encryptedTaskKey,
|
|
2700
|
+
encrypted_title: await encryptWithAesGcmCombined(input.title, taskKey),
|
|
2701
|
+
encrypted_description: await encryptWithAesGcmCombined(input.description ?? "", taskKey),
|
|
2702
|
+
encrypted_tags: await encryptWithAesGcmCombined("[]", taskKey),
|
|
2703
|
+
encrypted_linked_project_ids: await encryptWithAesGcmCombined(JSON.stringify(linkedProjectIds), taskKey),
|
|
2704
|
+
status,
|
|
2705
|
+
assignee_type: assignee.assigneeType,
|
|
2706
|
+
assignee_hash: assignee.assigneeHash,
|
|
2707
|
+
primary_chat_id: input.chatId ?? null,
|
|
2708
|
+
linked_project_ids: linkedProjectIds,
|
|
2709
|
+
plan_id: input.planId ?? null,
|
|
2710
|
+
due_at: input.dueAt ?? null,
|
|
2711
|
+
priority: 0,
|
|
2712
|
+
position: timestamp,
|
|
2713
|
+
created_at: timestamp,
|
|
2714
|
+
updated_at: timestamp
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
async function buildUpdateUserTaskInput(task, masterKey, input) {
|
|
2718
|
+
const taskKey = await taskKeyFromRecord(task.encrypted, masterKey);
|
|
2719
|
+
const patch = { version: task.version, updated_at: nowSeconds() };
|
|
2720
|
+
if (input.title !== void 0) patch.encrypted_title = await encryptWithAesGcmCombined(input.title, taskKey);
|
|
2721
|
+
if (input.description !== void 0) patch.encrypted_description = await encryptWithAesGcmCombined(input.description, taskKey);
|
|
2722
|
+
if (input.status !== void 0) patch.status = input.status;
|
|
2723
|
+
if (input.assign !== void 0) {
|
|
2724
|
+
const assignee = parseAssignee(input.assign);
|
|
2725
|
+
patch.assignee_type = assignee.assigneeType;
|
|
2726
|
+
patch.assignee_hash = assignee.assigneeHash;
|
|
2727
|
+
}
|
|
2728
|
+
if (input.chatId !== void 0) patch.primary_chat_id = input.chatId;
|
|
2729
|
+
if (input.projectIds !== void 0) {
|
|
2730
|
+
patch.linked_project_ids = input.projectIds;
|
|
2731
|
+
patch.encrypted_linked_project_ids = await encryptWithAesGcmCombined(JSON.stringify(input.projectIds), taskKey);
|
|
2732
|
+
}
|
|
2733
|
+
if (input.planId !== void 0) patch.plan_id = input.planId;
|
|
2734
|
+
return patch;
|
|
2735
|
+
}
|
|
2736
|
+
async function decryptUserTask(record, masterKey) {
|
|
2737
|
+
if (typeof record.version !== "number") throw new Error(`Task ${record.task_id} is missing version.`);
|
|
2738
|
+
const taskKey = await taskKeyFromRecord(record, masterKey);
|
|
2739
|
+
const tags = parseStringArray(await decryptOptional(record.encrypted_tags, taskKey));
|
|
2740
|
+
const linkedProjectIds = parseStringArray(await decryptOptional(record.encrypted_linked_project_ids, taskKey));
|
|
2741
|
+
return {
|
|
2742
|
+
taskId: record.task_id,
|
|
2743
|
+
shortId: record.short_id || deriveShortId(record),
|
|
2744
|
+
title: await decryptOptional(record.encrypted_title, taskKey) || "(untitled task)",
|
|
2745
|
+
description: await decryptOptional(record.encrypted_description, taskKey),
|
|
2746
|
+
tags,
|
|
2747
|
+
latestInstruction: await decryptOptional(record.encrypted_latest_instruction, taskKey),
|
|
2748
|
+
status: record.status,
|
|
2749
|
+
assigneeType: record.assignee_type,
|
|
2750
|
+
assigneeHash: record.assignee_hash ?? null,
|
|
2751
|
+
primaryChatId: record.primary_chat_id ?? null,
|
|
2752
|
+
linkedProjectIds: linkedProjectIds.length > 0 ? linkedProjectIds : record.linked_project_ids ?? [],
|
|
2753
|
+
planId: record.plan_id ?? null,
|
|
2754
|
+
dueAt: record.due_at ?? null,
|
|
2755
|
+
priority: record.priority ?? 0,
|
|
2756
|
+
position: record.position ?? 0,
|
|
2757
|
+
queueState: record.queue_state ?? "none",
|
|
2758
|
+
blockedReasonCode: record.blocked_reason_code ?? null,
|
|
2759
|
+
aiExecutionState: record.ai_execution_state ?? null,
|
|
2760
|
+
version: record.version,
|
|
2761
|
+
encrypted: record
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
async function decryptUserTasks(records, masterKey) {
|
|
2765
|
+
const output = [];
|
|
2766
|
+
for (const record of records) output.push(await decryptUserTask(record, masterKey));
|
|
2767
|
+
return output;
|
|
2768
|
+
}
|
|
2769
|
+
function findTask(tasks, id) {
|
|
2770
|
+
const taskIdMatch = tasks.find((candidate) => candidate.taskId === id);
|
|
2771
|
+
if (taskIdMatch) return taskIdMatch;
|
|
2772
|
+
const shortIdMatches = tasks.filter((candidate) => candidate.shortId === id);
|
|
2773
|
+
if (shortIdMatches.length > 1) throw new Error(`Task '${id}' is ambiguous in the current task list. Use the full task ID.`);
|
|
2774
|
+
const task = shortIdMatches[0];
|
|
2775
|
+
if (!task) throw new Error(`Task '${id}' was not found in the current task list.`);
|
|
2776
|
+
return task;
|
|
2777
|
+
}
|
|
2778
|
+
function renderTaskList(tasks) {
|
|
2779
|
+
if (tasks.length === 0) return "No tasks found.";
|
|
2780
|
+
const lines = ["Tasks", "ID Status Assignee Queue Title"];
|
|
2781
|
+
for (const task of tasks) {
|
|
2782
|
+
lines.push(`${pad(task.shortId, 9)} ${pad(task.status, 12)} ${pad(assigneeLabel(task), 11)} ${pad(task.queueState, 11)} ${task.title}`);
|
|
2783
|
+
}
|
|
2784
|
+
return lines.join("\n");
|
|
2785
|
+
}
|
|
2786
|
+
function renderTaskDetail(task) {
|
|
2787
|
+
const lines = [
|
|
2788
|
+
`Task ${task.shortId}`,
|
|
2789
|
+
`Title: ${task.title}`,
|
|
2790
|
+
`Status: ${task.status}`,
|
|
2791
|
+
`Assignee: ${assigneeLabel(task)}`,
|
|
2792
|
+
`Queue: ${task.queueState}`,
|
|
2793
|
+
`Task ID: ${task.taskId}`
|
|
2794
|
+
];
|
|
2795
|
+
if (task.description) lines.push(`Description: ${task.description}`);
|
|
2796
|
+
if (task.primaryChatId) lines.push(`Chat: ${task.primaryChatId}`);
|
|
2797
|
+
if (task.linkedProjectIds.length > 0) lines.push(`Projects: ${task.linkedProjectIds.join(", ")}`);
|
|
2798
|
+
if (task.planId) lines.push(`Plan: ${task.planId}`);
|
|
2799
|
+
if (task.blockedReasonCode) lines.push(`Blocked reason: ${task.blockedReasonCode}`);
|
|
2800
|
+
if (task.aiExecutionState) lines.push(`AI state: ${task.aiExecutionState}`);
|
|
2801
|
+
return lines.join("\n");
|
|
2802
|
+
}
|
|
2803
|
+
function renderTaskBoard(tasks, width = process.stdout.columns || 100) {
|
|
2804
|
+
const columns = TASK_STATUSES2.map((status) => ({ status, tasks: tasks.filter((task) => task.status === status).sort(compareTasks) }));
|
|
2805
|
+
if (width < 96) {
|
|
2806
|
+
const lines2 = ["OpenMates Tasks Board"];
|
|
2807
|
+
for (const column of columns) {
|
|
2808
|
+
lines2.push("", `${columnTitle(column.status)} (${column.tasks.length})`, "-".repeat(24));
|
|
2809
|
+
lines2.push(...boardColumnLines(column.tasks, 8));
|
|
2810
|
+
}
|
|
2811
|
+
return lines2.join("\n");
|
|
2812
|
+
}
|
|
2813
|
+
const perColumn = 6;
|
|
2814
|
+
const columnWidth = 22;
|
|
2815
|
+
const lines = ["OpenMates Tasks Board", ""];
|
|
2816
|
+
lines.push(columns.map((column) => pad(`${columnTitle(column.status)} (${column.tasks.length})`, columnWidth)).join(" "));
|
|
2817
|
+
lines.push(columns.map(() => "-".repeat(columnWidth)).join(" "));
|
|
2818
|
+
const renderedColumns = columns.map((column) => boardColumnLines(column.tasks, perColumn).map((line) => truncate(line, columnWidth)));
|
|
2819
|
+
const maxRows = Math.max(...renderedColumns.map((column) => column.length));
|
|
2820
|
+
for (let row = 0; row < maxRows; row += 1) {
|
|
2821
|
+
lines.push(renderedColumns.map((column) => pad(column[row] ?? "", columnWidth)).join(" "));
|
|
2822
|
+
}
|
|
2823
|
+
return lines.join("\n");
|
|
2824
|
+
}
|
|
2825
|
+
function boardColumnLines(tasks, limit) {
|
|
2826
|
+
if (tasks.length === 0) return ["No tasks here."];
|
|
2827
|
+
const visible = tasks.slice(0, limit).flatMap((task) => [
|
|
2828
|
+
`[${task.shortId}] ${task.title}`,
|
|
2829
|
+
` ${assigneeLabel(task)} ${task.queueState === "none" ? "" : task.queueState}`.trimEnd()
|
|
2830
|
+
]);
|
|
2831
|
+
if (tasks.length > limit) visible.push(`... ${tasks.length - limit} more`);
|
|
2832
|
+
return visible;
|
|
2833
|
+
}
|
|
2834
|
+
function compareTasks(a, b) {
|
|
2835
|
+
return a.position - b.position || a.title.localeCompare(b.title);
|
|
2836
|
+
}
|
|
2837
|
+
function columnTitle(status) {
|
|
2838
|
+
if (status === "in_progress") return "In progress";
|
|
2839
|
+
return status.slice(0, 1).toUpperCase() + status.slice(1).replace("_", " ");
|
|
2840
|
+
}
|
|
2841
|
+
function assigneeLabel(task) {
|
|
2842
|
+
return task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
2843
|
+
}
|
|
2844
|
+
async function taskKeyFromRecord(record, masterKey) {
|
|
2845
|
+
if (!record.encrypted_task_key) throw new Error(`Task ${record.task_id} is missing encrypted task key.`);
|
|
2846
|
+
const taskKey = await decryptBytesWithAesGcm(record.encrypted_task_key, masterKey);
|
|
2847
|
+
if (!taskKey) throw new Error(`Failed to decrypt task key for ${record.task_id}.`);
|
|
2848
|
+
return taskKey;
|
|
2849
|
+
}
|
|
2850
|
+
async function decryptOptional(value, key) {
|
|
2851
|
+
if (!value) return "";
|
|
2852
|
+
return await decryptWithAesGcmCombined(value, key) ?? "";
|
|
2853
|
+
}
|
|
2854
|
+
function parseStringArray(value) {
|
|
2855
|
+
if (!value) return [];
|
|
2856
|
+
try {
|
|
2857
|
+
const parsed = JSON.parse(value);
|
|
2858
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
2859
|
+
} catch {
|
|
2860
|
+
return [];
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
function deriveShortId(record) {
|
|
2864
|
+
const prefix = record.short_id_prefix || DEFAULT_STANDALONE_PREFIX;
|
|
2865
|
+
const source = record.task_id || `${record.created_at ?? ""}-${record.position ?? ""}`;
|
|
2866
|
+
const digest = createHash5("sha256").update(source).digest("hex").slice(0, 4).toUpperCase();
|
|
2867
|
+
return `${prefix}-${parseInt(digest, 16) % 1e4}`;
|
|
2868
|
+
}
|
|
2869
|
+
function nowSeconds() {
|
|
2870
|
+
return Math.floor(Date.now() / 1e3);
|
|
2871
|
+
}
|
|
2872
|
+
function randomUUIDCompat() {
|
|
2873
|
+
return randomUUID3();
|
|
2874
|
+
}
|
|
2875
|
+
function pad(value, length) {
|
|
2876
|
+
return truncate(value, length).padEnd(length);
|
|
2877
|
+
}
|
|
2878
|
+
function truncate(value, length) {
|
|
2879
|
+
return value.length <= length ? value : `${value.slice(0, Math.max(0, length - 3))}...`;
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2476
2882
|
// src/client.ts
|
|
2477
2883
|
function normalizeUnixSeconds(value, fallback) {
|
|
2478
2884
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
@@ -2640,6 +3046,83 @@ function buildAppSettingsMemoryResponseSystemMessage(params) {
|
|
|
2640
3046
|
user_message_id: params.userMessageId
|
|
2641
3047
|
};
|
|
2642
3048
|
}
|
|
3049
|
+
async function buildTaskEventSystemMessage(params) {
|
|
3050
|
+
const content = formatTaskEventSystemContent(params.event);
|
|
3051
|
+
const message = {
|
|
3052
|
+
message_id: `task-event-${params.event.event_id}`,
|
|
3053
|
+
role: "system",
|
|
3054
|
+
encrypted_content: await encryptWithAesGcmCombined(content, params.chatKey),
|
|
3055
|
+
created_at: params.event.created_at ?? Math.floor(Date.now() / 1e3),
|
|
3056
|
+
user_message_id: params.userMessageId
|
|
3057
|
+
};
|
|
3058
|
+
if (params.event.task_update_job_id) message.task_update_job_id = params.event.task_update_job_id;
|
|
3059
|
+
return message;
|
|
3060
|
+
}
|
|
3061
|
+
function taskUpdateJobBelongsToActiveTurn(job, activeChatId, taskEvents) {
|
|
3062
|
+
void activeChatId;
|
|
3063
|
+
return taskEvents.some((event) => event.task_update_job_id === job.job_id);
|
|
3064
|
+
}
|
|
3065
|
+
function buildTaskUpdateJobPersistPayload(params) {
|
|
3066
|
+
const encryptedTaskPayload = pruneAbsentTaskPersistFields(params.encryptedTaskPayload);
|
|
3067
|
+
assertTaskPersistPayloadEncrypted(encryptedTaskPayload);
|
|
3068
|
+
return {
|
|
3069
|
+
protocol_version: 1,
|
|
3070
|
+
job_id: params.jobId,
|
|
3071
|
+
lease_token: params.leaseToken,
|
|
3072
|
+
lease_generation: params.leaseGeneration,
|
|
3073
|
+
expected_task_version: params.expectedTaskVersion,
|
|
3074
|
+
encrypted_task_payload: encryptedTaskPayload,
|
|
3075
|
+
encrypted_task_event_message: params.encryptedTaskEventMessage ?? null
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
3078
|
+
function pruneAbsentTaskPersistFields(payload) {
|
|
3079
|
+
return Object.fromEntries(
|
|
3080
|
+
Object.entries(payload).filter(([, value]) => value !== void 0 && value !== null)
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
function formatTaskEventSystemContent(event) {
|
|
3084
|
+
const taskLabel = event.short_id || event.task_id;
|
|
3085
|
+
const title = event.title ? ` "${event.title}"` : "";
|
|
3086
|
+
const status = event.status ? ` (${event.status})` : "";
|
|
3087
|
+
const reason = event.reason ? `: ${event.reason}` : "";
|
|
3088
|
+
switch (event.event_type) {
|
|
3089
|
+
case "created":
|
|
3090
|
+
return `${taskLabel} created${title}${status}`;
|
|
3091
|
+
case "updated":
|
|
3092
|
+
return `${taskLabel} updated${title}${status}`;
|
|
3093
|
+
case "blocked":
|
|
3094
|
+
return `${taskLabel} blocked${reason}`;
|
|
3095
|
+
case "completed":
|
|
3096
|
+
return `${taskLabel} completed${title}`;
|
|
3097
|
+
case "unblocked":
|
|
3098
|
+
return `${taskLabel} unblocked`;
|
|
3099
|
+
default:
|
|
3100
|
+
return `${taskLabel} ${event.event_type}${title}${status}${reason}`;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
function assertTaskPersistPayloadEncrypted(payload) {
|
|
3104
|
+
const allowedSafeKeys = /* @__PURE__ */ new Set([
|
|
3105
|
+
"assignee_hash",
|
|
3106
|
+
"assignee_type",
|
|
3107
|
+
"blocked_reason_code",
|
|
3108
|
+
"created_at",
|
|
3109
|
+
"key_wrappers",
|
|
3110
|
+
"linked_project_ids",
|
|
3111
|
+
"position",
|
|
3112
|
+
"primary_chat_id",
|
|
3113
|
+
"priority",
|
|
3114
|
+
"status",
|
|
3115
|
+
"task_id",
|
|
3116
|
+
"updated_at",
|
|
3117
|
+
"version"
|
|
3118
|
+
]);
|
|
3119
|
+
for (const key of Object.keys(payload)) {
|
|
3120
|
+
if (key.startsWith("encrypted_") || allowedSafeKeys.has(key)) {
|
|
3121
|
+
continue;
|
|
3122
|
+
}
|
|
3123
|
+
throw new Error("Task update job payload contains plaintext or unsupported field");
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
2643
3126
|
async function buildSubChatEncryptedMetadataPayloads(params) {
|
|
2644
3127
|
const createdAt = params.createdAt ?? Math.floor(Date.now() / 1e3);
|
|
2645
3128
|
const payloads = [];
|
|
@@ -3313,11 +3796,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3313
3796
|
}
|
|
3314
3797
|
let anonymousId = loadAnonymousId();
|
|
3315
3798
|
if (!anonymousId) {
|
|
3316
|
-
anonymousId =
|
|
3799
|
+
anonymousId = randomUUID4();
|
|
3317
3800
|
saveAnonymousId(anonymousId);
|
|
3318
3801
|
}
|
|
3319
|
-
const chatId = `anonymous-${
|
|
3320
|
-
const messageId = `anonymous-message-${
|
|
3802
|
+
const chatId = `anonymous-${randomUUID4()}`;
|
|
3803
|
+
const messageId = `anonymous-message-${randomUUID4()}`;
|
|
3321
3804
|
const requestBody = {
|
|
3322
3805
|
anonymous_id: anonymousId,
|
|
3323
3806
|
client_chat_id: chatId,
|
|
@@ -3352,6 +3835,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3352
3835
|
modelName: response.data.modelName ?? null,
|
|
3353
3836
|
mateName: null,
|
|
3354
3837
|
followUpSuggestions: response.data.followUpSuggestions ?? [],
|
|
3838
|
+
taskProposals: [],
|
|
3839
|
+
taskUpdateProposals: [],
|
|
3355
3840
|
subChatEvents: [],
|
|
3356
3841
|
appSettingsMemoryRequests: []
|
|
3357
3842
|
};
|
|
@@ -3509,7 +3994,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3509
3994
|
pin: pin.trim().toUpperCase(),
|
|
3510
3995
|
token
|
|
3511
3996
|
});
|
|
3512
|
-
const sessionId =
|
|
3997
|
+
const sessionId = randomUUID4();
|
|
3513
3998
|
const login = await this.http.post(
|
|
3514
3999
|
"/v1/auth/login",
|
|
3515
4000
|
{
|
|
@@ -3684,7 +4169,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3684
4169
|
}
|
|
3685
4170
|
const session = {
|
|
3686
4171
|
apiUrl: this.apiUrl,
|
|
3687
|
-
sessionId:
|
|
4172
|
+
sessionId: randomUUID4(),
|
|
3688
4173
|
wsToken: null,
|
|
3689
4174
|
cookies: this.http.getCookieMap(),
|
|
3690
4175
|
masterKeyExportedB64: material.masterKeyB64,
|
|
@@ -3844,7 +4329,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3844
4329
|
if (!markdown) throw new Error("Draft markdown must not be empty.");
|
|
3845
4330
|
const masterKey = this.getMasterKeyBytes();
|
|
3846
4331
|
const encrypted = await this.saveEncryptedDraft({
|
|
3847
|
-
chatId: params.chatId ??
|
|
4332
|
+
chatId: params.chatId ?? randomUUID4(),
|
|
3848
4333
|
encryptedDraftMd: await encryptWithAesGcmCombined(markdown, masterKey),
|
|
3849
4334
|
encryptedDraftPreview: params.preview ? await encryptWithAesGcmCombined(params.preview, masterKey) : null
|
|
3850
4335
|
});
|
|
@@ -4400,7 +4885,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4400
4885
|
async sendMessage(params) {
|
|
4401
4886
|
let chatId;
|
|
4402
4887
|
if (!params.chatId) {
|
|
4403
|
-
chatId =
|
|
4888
|
+
chatId = randomUUID4();
|
|
4404
4889
|
} else if (params.chatId.length < 36) {
|
|
4405
4890
|
const resolved = await this.resolveFullChatId(params.chatId);
|
|
4406
4891
|
if (!resolved) {
|
|
@@ -4434,7 +4919,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4434
4919
|
ws.close();
|
|
4435
4920
|
throw new Error("Authenticated user identity is required for saved chat recovery.");
|
|
4436
4921
|
}
|
|
4437
|
-
const messageId =
|
|
4922
|
+
const messageId = randomUUID4();
|
|
4438
4923
|
const createdAt = Math.floor(Date.now() / 1e3);
|
|
4439
4924
|
const isNewChat = !params.chatId;
|
|
4440
4925
|
ws.send("set_active_chat", { chat_id: chatId });
|
|
@@ -4451,6 +4936,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4451
4936
|
let chatKeyBytes = null;
|
|
4452
4937
|
let encryptedChatKey = null;
|
|
4453
4938
|
let baselineMessagesV = 0;
|
|
4939
|
+
let terminalExpectedMessagesV = 1;
|
|
4454
4940
|
let savedTurnId = null;
|
|
4455
4941
|
if (!params.incognito) {
|
|
4456
4942
|
const masterKey = this.getMasterKeyBytes();
|
|
@@ -4484,6 +4970,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4484
4970
|
}
|
|
4485
4971
|
const messagePayload = {
|
|
4486
4972
|
chat_id: chatId,
|
|
4973
|
+
client_capabilities: ["task_update_jobs"],
|
|
4487
4974
|
is_incognito: Boolean(params.incognito),
|
|
4488
4975
|
message: {
|
|
4489
4976
|
message_id: messageId,
|
|
@@ -4567,12 +5054,13 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4567
5054
|
if (encryptedEmbeds.length > 0) {
|
|
4568
5055
|
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
4569
5056
|
}
|
|
4570
|
-
let precollectedResponse = params.precollectResponse && params.incognito ? ws.collectAiResponse(messageId, chatId, { onStream: params.onStream }) : null;
|
|
5057
|
+
let precollectedResponse = params.precollectResponse && params.incognito ? ws.collectAiResponse(messageId, chatId, { onStream: params.onStream, timeoutMs: params.responseTimeoutMs }) : null;
|
|
4571
5058
|
if (!params.incognito && chatKeyBytes && encryptedChatKey) {
|
|
4572
5059
|
const protocolVersion = 1;
|
|
4573
5060
|
const chatKeyVersion = 1;
|
|
4574
|
-
const turnId =
|
|
5061
|
+
const turnId = randomUUID4();
|
|
4575
5062
|
savedTurnId = turnId;
|
|
5063
|
+
terminalExpectedMessagesV = baselineMessagesV + 1;
|
|
4576
5064
|
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
4577
5065
|
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
4578
5066
|
chatId,
|
|
@@ -4638,10 +5126,15 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4638
5126
|
protocol_version: protocolVersion,
|
|
4639
5127
|
preflight_id: ackPayload.preflight_id
|
|
4640
5128
|
});
|
|
5129
|
+
if (typeof ackPayload.committed_messages_v === "number" && Number.isSafeInteger(ackPayload.committed_messages_v)) {
|
|
5130
|
+
terminalExpectedMessagesV = ackPayload.committed_messages_v;
|
|
5131
|
+
}
|
|
4641
5132
|
}
|
|
4642
5133
|
if (params.precollectResponse && !params.incognito) {
|
|
4643
5134
|
precollectedResponse = ws.collectAiResponse(messageId, chatId, {
|
|
4644
|
-
onStream: params.onStream
|
|
5135
|
+
onStream: params.onStream,
|
|
5136
|
+
timeoutMs: params.responseTimeoutMs,
|
|
5137
|
+
recoveryTurnId: savedTurnId
|
|
4645
5138
|
});
|
|
4646
5139
|
}
|
|
4647
5140
|
const confirmed = ws.waitForMessage(
|
|
@@ -4653,12 +5146,19 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4653
5146
|
2e4
|
|
4654
5147
|
);
|
|
4655
5148
|
await ws.sendAsync("chat_message_added", messagePayload);
|
|
4656
|
-
await confirmed;
|
|
5149
|
+
const confirmedPayload = (await confirmed).payload;
|
|
5150
|
+
if (typeof confirmedPayload.new_messages_v === "number" && Number.isSafeInteger(confirmedPayload.new_messages_v)) {
|
|
5151
|
+
terminalExpectedMessagesV = confirmedPayload.new_messages_v + 1;
|
|
5152
|
+
}
|
|
4657
5153
|
let assistant = "";
|
|
4658
5154
|
let assistantMessageId = null;
|
|
4659
5155
|
let category = null;
|
|
4660
5156
|
let modelName = null;
|
|
4661
5157
|
let followUpSuggestions = [];
|
|
5158
|
+
let taskProposals = [];
|
|
5159
|
+
let taskUpdateProposals = [];
|
|
5160
|
+
let taskEvents = [];
|
|
5161
|
+
let pendingTaskUpdateJobs = [];
|
|
4662
5162
|
let subChatEvents = [];
|
|
4663
5163
|
const appSettingsMemoryRequests = [];
|
|
4664
5164
|
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
@@ -4821,6 +5321,225 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4821
5321
|
`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.`
|
|
4822
5322
|
);
|
|
4823
5323
|
};
|
|
5324
|
+
const persistEncryptedSystemMessage = async (systemMessage, targetChatId = chatId) => {
|
|
5325
|
+
await ws.sendAsync("chat_system_message_added", {
|
|
5326
|
+
chat_id: targetChatId,
|
|
5327
|
+
message: systemMessage
|
|
5328
|
+
});
|
|
5329
|
+
await ws.waitForMessage(
|
|
5330
|
+
"system_message_confirmed",
|
|
5331
|
+
(payload) => payload.message_id === systemMessage.message_id,
|
|
5332
|
+
2e4
|
|
5333
|
+
);
|
|
5334
|
+
};
|
|
5335
|
+
const persistedTaskEventIds = /* @__PURE__ */ new Set();
|
|
5336
|
+
const persistTaskEventSystemMessages = async (events) => {
|
|
5337
|
+
if (!chatKeyBytes || events.length === 0) return;
|
|
5338
|
+
for (const event of events) {
|
|
5339
|
+
if (persistedTaskEventIds.has(event.event_id)) continue;
|
|
5340
|
+
await persistEncryptedSystemMessage(await buildTaskEventSystemMessage({
|
|
5341
|
+
chatKey: chatKeyBytes,
|
|
5342
|
+
userMessageId: messageId,
|
|
5343
|
+
event
|
|
5344
|
+
}));
|
|
5345
|
+
persistedTaskEventIds.add(event.event_id);
|
|
5346
|
+
}
|
|
5347
|
+
};
|
|
5348
|
+
const persistPendingTaskUpdateJobs = async (jobs, events) => {
|
|
5349
|
+
const handledJobIds = /* @__PURE__ */ new Set();
|
|
5350
|
+
if (jobs.length === 0) return handledJobIds;
|
|
5351
|
+
const masterKey = this.getMasterKeyBytes();
|
|
5352
|
+
let decryptedTasksCache = null;
|
|
5353
|
+
const eventByJobId = new Map(events.map((event) => [event.task_update_job_id, event]));
|
|
5354
|
+
const buildTaskKeyWrappersForChat = async (task, targetChatId, createdAt2) => {
|
|
5355
|
+
const encryptedTaskKey = task.encrypted.encrypted_task_key;
|
|
5356
|
+
if (!encryptedTaskKey) throw new Error(`Task ${task.taskId} is missing encrypted task key.`);
|
|
5357
|
+
const taskKey = await decryptBytesWithAesGcm(encryptedTaskKey, masterKey);
|
|
5358
|
+
if (!taskKey) throw new Error(`Failed to decrypt task key for ${task.taskId}.`);
|
|
5359
|
+
const targetChatKey = await resolveChatKey(targetChatId);
|
|
5360
|
+
const existingProjectWrappers = await listProjectTaskKeyWrappers(task.taskId, createdAt2);
|
|
5361
|
+
const linkedProjectIds = task.linkedProjectIds ?? [];
|
|
5362
|
+
if (linkedProjectIds.length > existingProjectWrappers.length) {
|
|
5363
|
+
throw new Error(`Task ${task.taskId} is missing project key wrappers required for move persistence.`);
|
|
5364
|
+
}
|
|
5365
|
+
return [
|
|
5366
|
+
{
|
|
5367
|
+
key_type: "master",
|
|
5368
|
+
encrypted_task_key: await encryptBytesWithAesGcm(taskKey, masterKey),
|
|
5369
|
+
created_at: createdAt2
|
|
5370
|
+
},
|
|
5371
|
+
{
|
|
5372
|
+
key_type: "chat",
|
|
5373
|
+
hashed_chat_id: computeSHA256(targetChatId),
|
|
5374
|
+
encrypted_task_key: await encryptBytesWithAesGcm(taskKey, targetChatKey),
|
|
5375
|
+
created_at: createdAt2
|
|
5376
|
+
},
|
|
5377
|
+
...existingProjectWrappers
|
|
5378
|
+
];
|
|
5379
|
+
};
|
|
5380
|
+
const listProjectTaskKeyWrappers = async (taskId, createdAt2) => {
|
|
5381
|
+
const response = await this.http.get(
|
|
5382
|
+
`/v1/user-tasks/${encodeURIComponent(taskId)}/key-wrappers`,
|
|
5383
|
+
this.getCliRequestHeaders()
|
|
5384
|
+
);
|
|
5385
|
+
if (!response.ok) {
|
|
5386
|
+
throw new Error(`User task key wrapper list failed with HTTP ${response.status}`);
|
|
5387
|
+
}
|
|
5388
|
+
return (response.data.key_wrappers ?? []).filter((wrapper) => wrapper.key_type === "project").map((wrapper) => ({
|
|
5389
|
+
key_type: "project",
|
|
5390
|
+
hashed_project_id: wrapper.hashed_project_id,
|
|
5391
|
+
encrypted_task_key: wrapper.encrypted_task_key,
|
|
5392
|
+
created_at: typeof wrapper.created_at === "number" ? wrapper.created_at : createdAt2,
|
|
5393
|
+
expires_at: wrapper.expires_at ?? null
|
|
5394
|
+
}));
|
|
5395
|
+
};
|
|
5396
|
+
const resolveChatKey = async (targetChatId) => {
|
|
5397
|
+
if (targetChatId === chatId && chatKeyBytes) return chatKeyBytes;
|
|
5398
|
+
const cache = loadSyncCache() ?? await this.ensureSynced();
|
|
5399
|
+
const targetChat = cache.chats.find((chat) => String(chat.details.id ?? "") === targetChatId);
|
|
5400
|
+
const encryptedTargetChatKey = typeof targetChat?.details.encrypted_chat_key === "string" ? targetChat.details.encrypted_chat_key : null;
|
|
5401
|
+
if (!encryptedTargetChatKey) {
|
|
5402
|
+
throw new Error(`Encrypted chat key not found for task move target '${targetChatId}'. Sync and try again.`);
|
|
5403
|
+
}
|
|
5404
|
+
const targetChatKey = await decryptBytesWithAesGcm(encryptedTargetChatKey, masterKey);
|
|
5405
|
+
if (!targetChatKey) {
|
|
5406
|
+
throw new Error(`Failed to decrypt chat key for task move target '${targetChatId}'.`);
|
|
5407
|
+
}
|
|
5408
|
+
return targetChatKey;
|
|
5409
|
+
};
|
|
5410
|
+
const taskEventTypeForOperation = (operation) => {
|
|
5411
|
+
switch (operation) {
|
|
5412
|
+
case "create":
|
|
5413
|
+
return "created";
|
|
5414
|
+
case "move":
|
|
5415
|
+
return "moved";
|
|
5416
|
+
case "update":
|
|
5417
|
+
return "updated";
|
|
5418
|
+
default:
|
|
5419
|
+
return operation || "updated";
|
|
5420
|
+
}
|
|
5421
|
+
};
|
|
5422
|
+
for (const job of jobs) {
|
|
5423
|
+
if (!taskUpdateJobBelongsToActiveTurn(job, chatId, events)) {
|
|
5424
|
+
handledJobIds.add(job.job_id);
|
|
5425
|
+
continue;
|
|
5426
|
+
}
|
|
5427
|
+
const claimPromise = ws.waitForMessage(
|
|
5428
|
+
"task_update_job_claimed",
|
|
5429
|
+
(payload) => payload.job_id === job.job_id,
|
|
5430
|
+
2e4
|
|
5431
|
+
);
|
|
5432
|
+
await ws.sendAsync("task_update_job_claim", {
|
|
5433
|
+
protocol_version: 1,
|
|
5434
|
+
job_id: job.job_id
|
|
5435
|
+
});
|
|
5436
|
+
const claim = (await claimPromise).payload;
|
|
5437
|
+
const privatePatch = claim.private_patch ?? {};
|
|
5438
|
+
const safeMetadata = claim.safe_metadata ?? {};
|
|
5439
|
+
const sourceChatId = claim.chat_id ?? job.chat_id ?? chatId;
|
|
5440
|
+
const sourceChatKey = await resolveChatKey(sourceChatId);
|
|
5441
|
+
const event = eventByJobId.get(job.job_id) ?? {
|
|
5442
|
+
event_id: `task-event-${job.job_id}`,
|
|
5443
|
+
chat_id: sourceChatId,
|
|
5444
|
+
task_id: claim.task_id,
|
|
5445
|
+
event_type: taskEventTypeForOperation(claim.operation),
|
|
5446
|
+
title: typeof privatePatch.title === "string" ? privatePatch.title : null,
|
|
5447
|
+
status: typeof safeMetadata.status === "string" ? safeMetadata.status : null,
|
|
5448
|
+
created_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : Math.floor(Date.now() / 1e3),
|
|
5449
|
+
task_update_job_id: job.job_id
|
|
5450
|
+
};
|
|
5451
|
+
const eventMessage = await buildTaskEventSystemMessage({
|
|
5452
|
+
chatKey: sourceChatKey,
|
|
5453
|
+
userMessageId: claim.message_id ?? messageId,
|
|
5454
|
+
event
|
|
5455
|
+
});
|
|
5456
|
+
const confirmTaskEventPersisted = async () => {
|
|
5457
|
+
const confirmedPromise = ws.waitForMessage(
|
|
5458
|
+
"task_update_job_event_confirmed",
|
|
5459
|
+
(payload) => payload.job_id === job.job_id,
|
|
5460
|
+
2e4
|
|
5461
|
+
);
|
|
5462
|
+
await ws.sendAsync("task_update_job_event_confirmed", {
|
|
5463
|
+
protocol_version: 1,
|
|
5464
|
+
job_id: job.job_id,
|
|
5465
|
+
event_system_message_id: eventMessage.message_id
|
|
5466
|
+
});
|
|
5467
|
+
await confirmedPromise;
|
|
5468
|
+
};
|
|
5469
|
+
if (claim.state === "TASK_PERSISTED") {
|
|
5470
|
+
await persistEncryptedSystemMessage(eventMessage, sourceChatId);
|
|
5471
|
+
persistedTaskEventIds.add(event.event_id);
|
|
5472
|
+
await confirmTaskEventPersisted();
|
|
5473
|
+
handledJobIds.add(job.job_id);
|
|
5474
|
+
continue;
|
|
5475
|
+
}
|
|
5476
|
+
if (!claim.lease_token || !Number.isSafeInteger(claim.lease_generation)) {
|
|
5477
|
+
throw new Error("Task update job claim returned an invalid lease.");
|
|
5478
|
+
}
|
|
5479
|
+
let encryptedTaskPayload;
|
|
5480
|
+
if (claim.operation === "create") {
|
|
5481
|
+
const input = await buildCreateUserTaskInput(masterKey, {
|
|
5482
|
+
title: typeof privatePatch.title === "string" ? privatePatch.title : "Untitled task",
|
|
5483
|
+
description: typeof privatePatch.description === "string" ? privatePatch.description : "",
|
|
5484
|
+
status: typeof safeMetadata.status === "string" ? safeMetadata.status : "todo",
|
|
5485
|
+
assign: typeof safeMetadata.assignee_type === "string" ? safeMetadata.assignee_type : "user",
|
|
5486
|
+
chatId: typeof safeMetadata.primary_chat_id === "string" ? safeMetadata.primary_chat_id : claim.chat_id ?? chatId
|
|
5487
|
+
});
|
|
5488
|
+
encryptedTaskPayload = {
|
|
5489
|
+
...input,
|
|
5490
|
+
task_id: claim.task_id,
|
|
5491
|
+
position: typeof safeMetadata.position === "number" ? safeMetadata.position : input.position,
|
|
5492
|
+
created_at: typeof safeMetadata.created_at === "number" ? safeMetadata.created_at : input.created_at,
|
|
5493
|
+
updated_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : input.updated_at
|
|
5494
|
+
};
|
|
5495
|
+
} else {
|
|
5496
|
+
if (!decryptedTasksCache) {
|
|
5497
|
+
decryptedTasksCache = await decryptUserTasks(await this.listUserTasks(), masterKey);
|
|
5498
|
+
}
|
|
5499
|
+
const task = findTask(decryptedTasksCache, claim.task_id);
|
|
5500
|
+
const patch = await buildUpdateUserTaskInput(task, masterKey, {
|
|
5501
|
+
title: typeof privatePatch.title === "string" ? privatePatch.title : void 0,
|
|
5502
|
+
description: typeof privatePatch.description === "string" ? privatePatch.description : void 0,
|
|
5503
|
+
status: typeof safeMetadata.status === "string" ? safeMetadata.status : void 0,
|
|
5504
|
+
assign: typeof safeMetadata.assignee_type === "string" ? safeMetadata.assignee_type : void 0,
|
|
5505
|
+
chatId: typeof safeMetadata.primary_chat_id === "string" ? safeMetadata.primary_chat_id : void 0
|
|
5506
|
+
});
|
|
5507
|
+
encryptedTaskPayload = {
|
|
5508
|
+
...patch,
|
|
5509
|
+
version: claim.expected_task_version,
|
|
5510
|
+
updated_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : patch.updated_at
|
|
5511
|
+
};
|
|
5512
|
+
if (typeof safeMetadata.primary_chat_id === "string") {
|
|
5513
|
+
encryptedTaskPayload.key_wrappers = await buildTaskKeyWrappersForChat(
|
|
5514
|
+
task,
|
|
5515
|
+
safeMetadata.primary_chat_id,
|
|
5516
|
+
typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : Math.floor(Date.now() / 1e3)
|
|
5517
|
+
);
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5520
|
+
const encryptedEventMessage = eventMessage.encrypted_content;
|
|
5521
|
+
const persistedPromise = ws.waitForMessage(
|
|
5522
|
+
"task_update_job_persisted",
|
|
5523
|
+
(payload) => payload.job_id === job.job_id,
|
|
5524
|
+
2e4
|
|
5525
|
+
);
|
|
5526
|
+
await ws.sendAsync("task_update_job_persist", buildTaskUpdateJobPersistPayload({
|
|
5527
|
+
jobId: job.job_id,
|
|
5528
|
+
leaseToken: claim.lease_token,
|
|
5529
|
+
leaseGeneration: claim.lease_generation,
|
|
5530
|
+
expectedTaskVersion: claim.expected_task_version,
|
|
5531
|
+
encryptedTaskPayload,
|
|
5532
|
+
encryptedTaskEventMessage: encryptedEventMessage
|
|
5533
|
+
}));
|
|
5534
|
+
await persistedPromise;
|
|
5535
|
+
await persistEncryptedSystemMessage(eventMessage, sourceChatId);
|
|
5536
|
+
persistedTaskEventIds.add(event.event_id);
|
|
5537
|
+
await confirmTaskEventPersisted();
|
|
5538
|
+
handledJobIds.add(job.job_id);
|
|
5539
|
+
}
|
|
5540
|
+
clearSyncCache();
|
|
5541
|
+
return handledJobIds;
|
|
5542
|
+
};
|
|
4824
5543
|
const streamOpts = {
|
|
4825
5544
|
onStream: params.onStream,
|
|
4826
5545
|
onSubChatEvent: handleSubChatEvent,
|
|
@@ -4828,11 +5547,17 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4828
5547
|
};
|
|
4829
5548
|
if (params.incognito) {
|
|
4830
5549
|
try {
|
|
4831
|
-
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId,
|
|
5550
|
+
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId, {
|
|
5551
|
+
...streamOpts,
|
|
5552
|
+
timeoutMs: params.responseTimeoutMs,
|
|
5553
|
+
recoveryTurnId: savedTurnId
|
|
5554
|
+
}));
|
|
4832
5555
|
assistantMessageId = resp.messageId;
|
|
4833
5556
|
assistant = resp.content;
|
|
4834
5557
|
category = resp.category;
|
|
4835
5558
|
modelName = resp.modelName;
|
|
5559
|
+
taskEvents = resp.taskEvents;
|
|
5560
|
+
pendingTaskUpdateJobs = resp.pendingTaskUpdateJobs;
|
|
4836
5561
|
subChatEvents = resp.subChatEvents;
|
|
4837
5562
|
if (resp.status === "waiting_for_user") {
|
|
4838
5563
|
return {
|
|
@@ -4844,6 +5569,10 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4844
5569
|
modelName,
|
|
4845
5570
|
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
4846
5571
|
followUpSuggestions,
|
|
5572
|
+
taskProposals,
|
|
5573
|
+
taskUpdateProposals,
|
|
5574
|
+
taskEvents,
|
|
5575
|
+
pendingTaskUpdateJobs,
|
|
4847
5576
|
subChatEvents,
|
|
4848
5577
|
appSettingsMemoryRequests
|
|
4849
5578
|
};
|
|
@@ -4853,12 +5582,19 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4853
5582
|
}
|
|
4854
5583
|
} else {
|
|
4855
5584
|
try {
|
|
4856
|
-
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId,
|
|
5585
|
+
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId, {
|
|
5586
|
+
...streamOpts,
|
|
5587
|
+
timeoutMs: params.responseTimeoutMs
|
|
5588
|
+
}));
|
|
4857
5589
|
assistantMessageId = resp.messageId;
|
|
4858
5590
|
assistant = resp.content;
|
|
4859
5591
|
category = resp.category;
|
|
4860
5592
|
modelName = resp.modelName;
|
|
4861
5593
|
followUpSuggestions = resp.followUpSuggestions;
|
|
5594
|
+
taskProposals = resp.taskProposals;
|
|
5595
|
+
taskUpdateProposals = resp.taskUpdateProposals;
|
|
5596
|
+
taskEvents = resp.taskEvents;
|
|
5597
|
+
pendingTaskUpdateJobs = resp.pendingTaskUpdateJobs;
|
|
4862
5598
|
subChatEvents = resp.subChatEvents;
|
|
4863
5599
|
if (resp.status === "waiting_for_user") {
|
|
4864
5600
|
return {
|
|
@@ -4870,6 +5606,10 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4870
5606
|
modelName,
|
|
4871
5607
|
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
4872
5608
|
followUpSuggestions,
|
|
5609
|
+
taskProposals,
|
|
5610
|
+
taskUpdateProposals,
|
|
5611
|
+
taskEvents,
|
|
5612
|
+
pendingTaskUpdateJobs,
|
|
4873
5613
|
subChatEvents,
|
|
4874
5614
|
appSettingsMemoryRequests
|
|
4875
5615
|
};
|
|
@@ -4935,12 +5675,15 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4935
5675
|
"model_name",
|
|
4936
5676
|
"turn_id"
|
|
4937
5677
|
];
|
|
4938
|
-
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"
|
|
5678
|
+
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") {
|
|
4939
5679
|
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
4940
5680
|
}
|
|
5681
|
+
assistant = recovered.content;
|
|
5682
|
+
category = recovered.category;
|
|
5683
|
+
modelName = recovered.model_name;
|
|
4941
5684
|
const completedAt = Math.floor(Date.now() / 1e3);
|
|
4942
5685
|
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
4943
|
-
|
|
5686
|
+
assistant,
|
|
4944
5687
|
chatKeyBytes
|
|
4945
5688
|
);
|
|
4946
5689
|
const encryptedSenderName = await encryptWithAesGcmCombined("Assistant", chatKeyBytes);
|
|
@@ -4956,7 +5699,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4956
5699
|
job_id: recoveryJobId,
|
|
4957
5700
|
lease_token: leaseToken,
|
|
4958
5701
|
lease_generation: leaseGeneration,
|
|
4959
|
-
expected_messages_v:
|
|
5702
|
+
expected_messages_v: terminalExpectedMessagesV,
|
|
4960
5703
|
encrypted_assistant_message: {
|
|
4961
5704
|
client_message_id: assistantId,
|
|
4962
5705
|
chat_id: chatId,
|
|
@@ -4987,6 +5730,9 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4987
5730
|
newChatSuggestions: resp.newChatSuggestions,
|
|
4988
5731
|
encryptedChatKey
|
|
4989
5732
|
});
|
|
5733
|
+
const persistedTaskJobIds = await persistPendingTaskUpdateJobs(pendingTaskUpdateJobs, taskEvents);
|
|
5734
|
+
pendingTaskUpdateJobs = pendingTaskUpdateJobs.filter((job) => !persistedTaskJobIds.has(job.job_id));
|
|
5735
|
+
await persistTaskEventSystemMessages(taskEvents);
|
|
4990
5736
|
clearSyncCache();
|
|
4991
5737
|
}
|
|
4992
5738
|
} finally {
|
|
@@ -5003,6 +5749,10 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5003
5749
|
modelName,
|
|
5004
5750
|
mateName,
|
|
5005
5751
|
followUpSuggestions,
|
|
5752
|
+
taskProposals,
|
|
5753
|
+
taskUpdateProposals,
|
|
5754
|
+
taskEvents,
|
|
5755
|
+
pendingTaskUpdateJobs,
|
|
5006
5756
|
subChatEvents,
|
|
5007
5757
|
appSettingsMemoryRequests
|
|
5008
5758
|
};
|
|
@@ -5935,6 +6685,22 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5935
6685
|
}
|
|
5936
6686
|
return response.data.task;
|
|
5937
6687
|
}
|
|
6688
|
+
async extractUserTaskProposals(input) {
|
|
6689
|
+
const response = await this.http.post(
|
|
6690
|
+
"/v1/user-tasks/extract",
|
|
6691
|
+
{
|
|
6692
|
+
corrected_text: input.correctedText,
|
|
6693
|
+
mode: input.mode ?? "create",
|
|
6694
|
+
context_chat_id: input.contextChatId ?? null,
|
|
6695
|
+
project_ids: input.projectIds ?? []
|
|
6696
|
+
},
|
|
6697
|
+
this.getCliRequestHeaders()
|
|
6698
|
+
);
|
|
6699
|
+
if (!response.ok || !Array.isArray(response.data.proposed_tasks)) {
|
|
6700
|
+
throw new Error(`Failed to extract task proposals (HTTP ${response.status})`);
|
|
6701
|
+
}
|
|
6702
|
+
return response.data.proposed_tasks;
|
|
6703
|
+
}
|
|
5938
6704
|
async updateUserTask(taskId, input) {
|
|
5939
6705
|
this.requireSession();
|
|
5940
6706
|
const response = await this.http.patch(
|
|
@@ -5947,7 +6713,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5947
6713
|
}
|
|
5948
6714
|
return response.data.task;
|
|
5949
6715
|
}
|
|
5950
|
-
async startUserTaskWithAI(taskId, input
|
|
6716
|
+
async startUserTaskWithAI(taskId, input) {
|
|
5951
6717
|
this.requireSession();
|
|
5952
6718
|
const response = await this.http.post(
|
|
5953
6719
|
`/v1/user-tasks/${encodeURIComponent(taskId)}/start-ai`,
|
|
@@ -5959,10 +6725,10 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5959
6725
|
}
|
|
5960
6726
|
return response.data.task;
|
|
5961
6727
|
}
|
|
5962
|
-
async deleteUserTask(taskId) {
|
|
6728
|
+
async deleteUserTask(taskId, version) {
|
|
5963
6729
|
this.requireSession();
|
|
5964
6730
|
const response = await this.http.delete(
|
|
5965
|
-
`/v1/user-tasks/${encodeURIComponent(taskId)}`,
|
|
6731
|
+
`/v1/user-tasks/${encodeURIComponent(taskId)}?version=${encodeURIComponent(String(version))}`,
|
|
5966
6732
|
void 0,
|
|
5967
6733
|
this.getCliRequestHeaders()
|
|
5968
6734
|
);
|
|
@@ -5971,16 +6737,16 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5971
6737
|
}
|
|
5972
6738
|
return response.data;
|
|
5973
6739
|
}
|
|
5974
|
-
async completeUserTask(taskId, input
|
|
6740
|
+
async completeUserTask(taskId, input) {
|
|
5975
6741
|
return this.postUserTaskAction(taskId, "complete", input);
|
|
5976
6742
|
}
|
|
5977
|
-
async blockUserTask(taskId, input
|
|
6743
|
+
async blockUserTask(taskId, input) {
|
|
5978
6744
|
return this.postUserTaskAction(taskId, "block", input);
|
|
5979
6745
|
}
|
|
5980
|
-
async unblockUserTask(taskId, input
|
|
6746
|
+
async unblockUserTask(taskId, input) {
|
|
5981
6747
|
return this.postUserTaskAction(taskId, "unblock", input);
|
|
5982
6748
|
}
|
|
5983
|
-
async skipUserTask(taskId, input
|
|
6749
|
+
async skipUserTask(taskId, input) {
|
|
5984
6750
|
return this.postUserTaskAction(taskId, "skip", input);
|
|
5985
6751
|
}
|
|
5986
6752
|
async reorderUserTasks(input) {
|
|
@@ -5995,7 +6761,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5995
6761
|
}
|
|
5996
6762
|
return response.data.tasks ?? [];
|
|
5997
6763
|
}
|
|
5998
|
-
async postUserTaskAction(taskId, action, input
|
|
6764
|
+
async postUserTaskAction(taskId, action, input) {
|
|
5999
6765
|
this.requireSession();
|
|
6000
6766
|
const response = await this.http.post(
|
|
6001
6767
|
`/v1/user-tasks/${encodeURIComponent(taskId)}/${encodeURIComponent(action)}`,
|
|
@@ -6753,7 +7519,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
6753
7519
|
);
|
|
6754
7520
|
}
|
|
6755
7521
|
}
|
|
6756
|
-
const entryId = params.entryId ??
|
|
7522
|
+
const entryId = params.entryId ?? randomUUID4();
|
|
6757
7523
|
const now = Math.floor(Date.now() / 1e3);
|
|
6758
7524
|
const hashedKey = hashItemKey(params.appId, params.itemType);
|
|
6759
7525
|
const plaintextPayload = {
|
|
@@ -7228,7 +7994,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
7228
7994
|
makeWsClient(session) {
|
|
7229
7995
|
return new OpenMatesWsClient({
|
|
7230
7996
|
apiUrl: session.apiUrl,
|
|
7231
|
-
sessionId:
|
|
7997
|
+
sessionId: randomUUID4(),
|
|
7232
7998
|
wsToken: session.wsToken,
|
|
7233
7999
|
refreshToken: session.cookies.auth_refresh_token ?? null,
|
|
7234
8000
|
// Same User-Agent as login so OS-based device fingerprint hash matches.
|
|
@@ -8411,7 +9177,7 @@ var OutputRedactor = class {
|
|
|
8411
9177
|
import { readFileSync as readFileSync4, statSync, existsSync as existsSync4 } from "fs";
|
|
8412
9178
|
import { basename, extname, resolve as resolve2 } from "path";
|
|
8413
9179
|
import { homedir as homedir4 } from "os";
|
|
8414
|
-
import { createHash as
|
|
9180
|
+
import { createHash as createHash6 } from "crypto";
|
|
8415
9181
|
var MAX_PER_FILE_SIZE = 100 * 1024 * 1024;
|
|
8416
9182
|
var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8417
9183
|
".pem",
|
|
@@ -8721,7 +9487,7 @@ function processCodeFile(filePath, filename, redactor) {
|
|
|
8721
9487
|
line_count: lineCount
|
|
8722
9488
|
});
|
|
8723
9489
|
const textPreview = `${filename} (${language}, ${lineCount} lines)`;
|
|
8724
|
-
const contentHash =
|
|
9490
|
+
const contentHash = createHash6("sha256").update(content).digest("hex");
|
|
8725
9491
|
const embed = {
|
|
8726
9492
|
embedId,
|
|
8727
9493
|
embedRef,
|
|
@@ -10237,7 +11003,7 @@ function formatTs(ts) {
|
|
|
10237
11003
|
|
|
10238
11004
|
// src/server.ts
|
|
10239
11005
|
import { execFileSync as execFileSync2, execSync, spawn as nodeSpawn, spawnSync } from "child_process";
|
|
10240
|
-
import { createHash as
|
|
11006
|
+
import { createHash as createHash7, randomBytes as randomBytes3 } from "crypto";
|
|
10241
11007
|
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";
|
|
10242
11008
|
import { createInterface as createInterface2 } from "readline";
|
|
10243
11009
|
import { createInterface as createPromptInterface } from "readline/promises";
|
|
@@ -10855,10 +11621,10 @@ function resolveTargetImageTag(flags, currentTag, packageVersion) {
|
|
|
10855
11621
|
return { tag: getDefaultImageTagForVersion(packageVersion) };
|
|
10856
11622
|
}
|
|
10857
11623
|
function randomHex(bytes) {
|
|
10858
|
-
return
|
|
11624
|
+
return randomBytes3(bytes).toString("hex");
|
|
10859
11625
|
}
|
|
10860
11626
|
function generateInviteCode() {
|
|
10861
|
-
const digits = Array.from(
|
|
11627
|
+
const digits = Array.from(randomBytes3(12), (byte) => String(byte % 10)).join("");
|
|
10862
11628
|
return `${digits.slice(0, 4)}-${digits.slice(4, 8)}-${digits.slice(8, 12)}`;
|
|
10863
11629
|
}
|
|
10864
11630
|
function getEnvVar(content, name) {
|
|
@@ -10902,7 +11668,7 @@ function packagedCaddyTemplatePath(role) {
|
|
|
10902
11668
|
}
|
|
10903
11669
|
function fileHash(path) {
|
|
10904
11670
|
if (!existsSync5(path)) return null;
|
|
10905
|
-
return
|
|
11671
|
+
return createHash7("sha256").update(readFileSync5(path)).digest("hex");
|
|
10906
11672
|
}
|
|
10907
11673
|
async function loadSelfHostComposeTemplate(templateRef, role) {
|
|
10908
11674
|
const templateDir = process.env.OPENMATES_SELFHOST_TEMPLATE_DIR;
|
|
@@ -11288,7 +12054,7 @@ function formatSecretPreflight(preflight) {
|
|
|
11288
12054
|
return parts.join("; ");
|
|
11289
12055
|
}
|
|
11290
12056
|
function hashFile(path) {
|
|
11291
|
-
const hash =
|
|
12057
|
+
const hash = createHash7("sha256");
|
|
11292
12058
|
const fd = openSync(path, "r");
|
|
11293
12059
|
const buffer = Buffer.allocUnsafe(CHECKSUM_BUFFER_BYTES);
|
|
11294
12060
|
try {
|
|
@@ -29309,7 +30075,10 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
29309
30075
|
},
|
|
29310
30076
|
models3d: {
|
|
29311
30077
|
generate: {
|
|
29312
|
-
text: "Generate 3D model"
|
|
30078
|
+
text: "Generate 3D model",
|
|
30079
|
+
description: {
|
|
30080
|
+
text: "Create a 3D model from a text prompt or reference images."
|
|
30081
|
+
}
|
|
29313
30082
|
}
|
|
29314
30083
|
},
|
|
29315
30084
|
music: {
|
|
@@ -30164,6 +30933,12 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
30164
30933
|
text: "Create structured idea maps and brainstorm topic trees."
|
|
30165
30934
|
}
|
|
30166
30935
|
},
|
|
30936
|
+
models3d: {
|
|
30937
|
+
text: "3D Models",
|
|
30938
|
+
description: {
|
|
30939
|
+
text: "Generate 3D models from prompts or reference images."
|
|
30940
|
+
}
|
|
30941
|
+
},
|
|
30167
30942
|
math: {
|
|
30168
30943
|
text: "Math",
|
|
30169
30944
|
description: {
|
|
@@ -46676,7 +47451,7 @@ function buildAssistantFeedbackDecision(rating) {
|
|
|
46676
47451
|
}
|
|
46677
47452
|
|
|
46678
47453
|
// src/benchmark.ts
|
|
46679
|
-
import { randomUUID as
|
|
47454
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
46680
47455
|
import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
46681
47456
|
import { tmpdir } from "os";
|
|
46682
47457
|
import { dirname as dirname2, join as join4, resolve as resolve5 } from "path";
|
|
@@ -46976,7 +47751,7 @@ async function handleBenchmark(client, subcommand, rest, flags) {
|
|
|
46976
47751
|
const caseIds = parseCaseIds(flags.case);
|
|
46977
47752
|
const dryRun = flags["dry-run"] === true;
|
|
46978
47753
|
const output = typeof flags.output === "string" ? flags.output : void 0;
|
|
46979
|
-
const runId = typeof flags["run-id"] === "string" ? flags["run-id"] :
|
|
47754
|
+
const runId = typeof flags["run-id"] === "string" ? flags["run-id"] : randomUUID5();
|
|
46980
47755
|
const imagePath = typeof flags.image === "string" ? resolve5(flags.image) : defaultImageFixturePath();
|
|
46981
47756
|
if (!dryRun && flags["confirm-spend-credits"] !== true) {
|
|
46982
47757
|
throw new Error(
|
|
@@ -47386,7 +48161,7 @@ function buildLongContextHistory() {
|
|
|
47386
48161
|
}
|
|
47387
48162
|
function appendHistory(history, role, content) {
|
|
47388
48163
|
history.push({
|
|
47389
|
-
message_id:
|
|
48164
|
+
message_id: randomUUID5(),
|
|
47390
48165
|
role,
|
|
47391
48166
|
sender_name: role === "user" ? "User" : "Assistant",
|
|
47392
48167
|
content,
|
|
@@ -47921,7 +48696,7 @@ function renderBody(state, width) {
|
|
|
47921
48696
|
case "tasks":
|
|
47922
48697
|
return renderTasks(state, width);
|
|
47923
48698
|
case "task":
|
|
47924
|
-
return
|
|
48699
|
+
return renderTaskDetail2(state, width);
|
|
47925
48700
|
case "start":
|
|
47926
48701
|
default:
|
|
47927
48702
|
return renderStart(width);
|
|
@@ -47994,7 +48769,7 @@ function renderTasks(state, width) {
|
|
|
47994
48769
|
}
|
|
47995
48770
|
return lines.flatMap((line) => wrap(line, width));
|
|
47996
48771
|
}
|
|
47997
|
-
function
|
|
48772
|
+
function renderTaskDetail2(state, width) {
|
|
47998
48773
|
const task = state.activeTask;
|
|
47999
48774
|
if (!task) return renderTasks(state, width);
|
|
48000
48775
|
const assignee = task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
@@ -48311,221 +49086,6 @@ function boxed(line, width) {
|
|
|
48311
49086
|
return `\u2502 ${line.padEnd(width)} \u2502`;
|
|
48312
49087
|
}
|
|
48313
49088
|
|
|
48314
|
-
// src/tasksCli.ts
|
|
48315
|
-
import { createHash as createHash7, randomBytes as randomBytes3, randomUUID as randomUUID5 } from "crypto";
|
|
48316
|
-
var TASK_STATUSES = ["backlog", "todo", "in_progress", "blocked", "done"];
|
|
48317
|
-
var DEFAULT_STANDALONE_PREFIX = "TASK";
|
|
48318
|
-
function normalizeTaskStatus(value) {
|
|
48319
|
-
if (value === void 0) return void 0;
|
|
48320
|
-
if (TASK_STATUSES.includes(value)) return value;
|
|
48321
|
-
throw new Error(`Unknown task status '${value}'. Expected one of: ${TASK_STATUSES.join(", ")}`);
|
|
48322
|
-
}
|
|
48323
|
-
function parseAssignee(value) {
|
|
48324
|
-
if (!value || value === "user") return { assigneeType: "user", assigneeHash: null };
|
|
48325
|
-
if (["ai", "openmates", "OpenMates"].includes(value)) return { assigneeType: "ai", assigneeHash: null };
|
|
48326
|
-
return { assigneeType: "user", assigneeHash: value };
|
|
48327
|
-
}
|
|
48328
|
-
function splitCsvFlag(value) {
|
|
48329
|
-
if (typeof value !== "string") return [];
|
|
48330
|
-
return value.split(/[,\n]/).map((item) => item.trim()).filter(Boolean);
|
|
48331
|
-
}
|
|
48332
|
-
function parseDueAt(value) {
|
|
48333
|
-
if (value === void 0) return void 0;
|
|
48334
|
-
if (value === false || value === true) throw new Error("--due requires a timestamp or date value.");
|
|
48335
|
-
const numeric = Number(value);
|
|
48336
|
-
if (Number.isFinite(numeric) && numeric > 0) return Math.floor(numeric);
|
|
48337
|
-
const parsed = Date.parse(value);
|
|
48338
|
-
if (Number.isNaN(parsed)) throw new Error(`Invalid --due value '${value}'.`);
|
|
48339
|
-
return Math.floor(parsed / 1e3);
|
|
48340
|
-
}
|
|
48341
|
-
async function buildCreateUserTaskInput(masterKey, input) {
|
|
48342
|
-
const taskKey = randomBytes3(32);
|
|
48343
|
-
const encryptedTaskKey = await encryptBytesWithAesGcm(taskKey, masterKey);
|
|
48344
|
-
const timestamp = nowSeconds();
|
|
48345
|
-
const assignee = parseAssignee(input.assign);
|
|
48346
|
-
const linkedProjectIds = input.projectIds ?? [];
|
|
48347
|
-
const status = input.status ?? (assignee.assigneeType === "ai" && !input.dueAt ? "in_progress" : "todo");
|
|
48348
|
-
return {
|
|
48349
|
-
task_id: randomUUIDCompat(),
|
|
48350
|
-
short_id: void 0,
|
|
48351
|
-
encrypted_task_key: encryptedTaskKey,
|
|
48352
|
-
encrypted_title: await encryptWithAesGcmCombined(input.title, taskKey),
|
|
48353
|
-
encrypted_description: await encryptWithAesGcmCombined(input.description ?? "", taskKey),
|
|
48354
|
-
encrypted_tags: await encryptWithAesGcmCombined("[]", taskKey),
|
|
48355
|
-
encrypted_linked_project_ids: await encryptWithAesGcmCombined(JSON.stringify(linkedProjectIds), taskKey),
|
|
48356
|
-
status,
|
|
48357
|
-
assignee_type: assignee.assigneeType,
|
|
48358
|
-
assignee_hash: assignee.assigneeHash,
|
|
48359
|
-
primary_chat_id: input.chatId ?? null,
|
|
48360
|
-
linked_project_ids: linkedProjectIds,
|
|
48361
|
-
plan_id: input.planId ?? null,
|
|
48362
|
-
due_at: input.dueAt ?? null,
|
|
48363
|
-
priority: 0,
|
|
48364
|
-
position: timestamp,
|
|
48365
|
-
created_at: timestamp,
|
|
48366
|
-
updated_at: timestamp
|
|
48367
|
-
};
|
|
48368
|
-
}
|
|
48369
|
-
async function buildUpdateUserTaskInput(task, masterKey, input) {
|
|
48370
|
-
const taskKey = await taskKeyFromRecord(task.encrypted, masterKey);
|
|
48371
|
-
const patch = { version: task.version, updated_at: nowSeconds() };
|
|
48372
|
-
if (input.title !== void 0) patch.encrypted_title = await encryptWithAesGcmCombined(input.title, taskKey);
|
|
48373
|
-
if (input.description !== void 0) patch.encrypted_description = await encryptWithAesGcmCombined(input.description, taskKey);
|
|
48374
|
-
if (input.status !== void 0) patch.status = input.status;
|
|
48375
|
-
if (input.assign !== void 0) {
|
|
48376
|
-
const assignee = parseAssignee(input.assign);
|
|
48377
|
-
patch.assignee_type = assignee.assigneeType;
|
|
48378
|
-
patch.assignee_hash = assignee.assigneeHash;
|
|
48379
|
-
}
|
|
48380
|
-
if (input.chatId !== void 0) patch.primary_chat_id = input.chatId;
|
|
48381
|
-
if (input.projectIds !== void 0) {
|
|
48382
|
-
patch.linked_project_ids = input.projectIds;
|
|
48383
|
-
patch.encrypted_linked_project_ids = await encryptWithAesGcmCombined(JSON.stringify(input.projectIds), taskKey);
|
|
48384
|
-
}
|
|
48385
|
-
if (input.planId !== void 0) patch.plan_id = input.planId;
|
|
48386
|
-
return patch;
|
|
48387
|
-
}
|
|
48388
|
-
async function decryptUserTask(record, masterKey) {
|
|
48389
|
-
const taskKey = await taskKeyFromRecord(record, masterKey);
|
|
48390
|
-
const tags = parseStringArray(await decryptOptional(record.encrypted_tags, taskKey));
|
|
48391
|
-
const linkedProjectIds = parseStringArray(await decryptOptional(record.encrypted_linked_project_ids, taskKey));
|
|
48392
|
-
return {
|
|
48393
|
-
taskId: record.task_id,
|
|
48394
|
-
shortId: record.short_id || deriveShortId(record),
|
|
48395
|
-
title: await decryptOptional(record.encrypted_title, taskKey) || "(untitled task)",
|
|
48396
|
-
description: await decryptOptional(record.encrypted_description, taskKey),
|
|
48397
|
-
tags,
|
|
48398
|
-
latestInstruction: await decryptOptional(record.encrypted_latest_instruction, taskKey),
|
|
48399
|
-
status: record.status,
|
|
48400
|
-
assigneeType: record.assignee_type,
|
|
48401
|
-
assigneeHash: record.assignee_hash ?? null,
|
|
48402
|
-
primaryChatId: record.primary_chat_id ?? null,
|
|
48403
|
-
linkedProjectIds: linkedProjectIds.length > 0 ? linkedProjectIds : record.linked_project_ids ?? [],
|
|
48404
|
-
planId: record.plan_id ?? null,
|
|
48405
|
-
dueAt: record.due_at ?? null,
|
|
48406
|
-
priority: record.priority ?? 0,
|
|
48407
|
-
position: record.position ?? 0,
|
|
48408
|
-
queueState: record.queue_state ?? "none",
|
|
48409
|
-
blockedReasonCode: record.blocked_reason_code ?? null,
|
|
48410
|
-
aiExecutionState: record.ai_execution_state ?? null,
|
|
48411
|
-
version: record.version ?? 1,
|
|
48412
|
-
encrypted: record
|
|
48413
|
-
};
|
|
48414
|
-
}
|
|
48415
|
-
async function decryptUserTasks(records, masterKey) {
|
|
48416
|
-
const output = [];
|
|
48417
|
-
for (const record of records) output.push(await decryptUserTask(record, masterKey));
|
|
48418
|
-
return output;
|
|
48419
|
-
}
|
|
48420
|
-
function findTask(tasks, id) {
|
|
48421
|
-
const task = tasks.find((candidate) => candidate.taskId === id || candidate.shortId === id);
|
|
48422
|
-
if (!task) throw new Error(`Task '${id}' was not found in the current task list.`);
|
|
48423
|
-
return task;
|
|
48424
|
-
}
|
|
48425
|
-
function renderTaskList(tasks) {
|
|
48426
|
-
if (tasks.length === 0) return "No tasks found.";
|
|
48427
|
-
const lines = ["Tasks", "ID Status Assignee Queue Title"];
|
|
48428
|
-
for (const task of tasks) {
|
|
48429
|
-
lines.push(`${pad(task.shortId, 9)} ${pad(task.status, 12)} ${pad(assigneeLabel(task), 11)} ${pad(task.queueState, 11)} ${task.title}`);
|
|
48430
|
-
}
|
|
48431
|
-
return lines.join("\n");
|
|
48432
|
-
}
|
|
48433
|
-
function renderTaskDetail2(task) {
|
|
48434
|
-
const lines = [
|
|
48435
|
-
`Task ${task.shortId}`,
|
|
48436
|
-
`Title: ${task.title}`,
|
|
48437
|
-
`Status: ${task.status}`,
|
|
48438
|
-
`Assignee: ${assigneeLabel(task)}`,
|
|
48439
|
-
`Queue: ${task.queueState}`,
|
|
48440
|
-
`Task ID: ${task.taskId}`
|
|
48441
|
-
];
|
|
48442
|
-
if (task.description) lines.push(`Description: ${task.description}`);
|
|
48443
|
-
if (task.primaryChatId) lines.push(`Chat: ${task.primaryChatId}`);
|
|
48444
|
-
if (task.linkedProjectIds.length > 0) lines.push(`Projects: ${task.linkedProjectIds.join(", ")}`);
|
|
48445
|
-
if (task.planId) lines.push(`Plan: ${task.planId}`);
|
|
48446
|
-
if (task.blockedReasonCode) lines.push(`Blocked reason: ${task.blockedReasonCode}`);
|
|
48447
|
-
if (task.aiExecutionState) lines.push(`AI state: ${task.aiExecutionState}`);
|
|
48448
|
-
return lines.join("\n");
|
|
48449
|
-
}
|
|
48450
|
-
function renderTaskBoard(tasks, width = process.stdout.columns || 100) {
|
|
48451
|
-
const columns = TASK_STATUSES.map((status) => ({ status, tasks: tasks.filter((task) => task.status === status).sort(compareTasks) }));
|
|
48452
|
-
if (width < 96) {
|
|
48453
|
-
const lines2 = ["OpenMates Tasks Board"];
|
|
48454
|
-
for (const column of columns) {
|
|
48455
|
-
lines2.push("", `${columnTitle(column.status)} (${column.tasks.length})`, "-".repeat(24));
|
|
48456
|
-
lines2.push(...boardColumnLines(column.tasks, 8));
|
|
48457
|
-
}
|
|
48458
|
-
return lines2.join("\n");
|
|
48459
|
-
}
|
|
48460
|
-
const perColumn = 6;
|
|
48461
|
-
const columnWidth = 22;
|
|
48462
|
-
const lines = ["OpenMates Tasks Board", ""];
|
|
48463
|
-
lines.push(columns.map((column) => pad(`${columnTitle(column.status)} (${column.tasks.length})`, columnWidth)).join(" "));
|
|
48464
|
-
lines.push(columns.map(() => "-".repeat(columnWidth)).join(" "));
|
|
48465
|
-
const renderedColumns = columns.map((column) => boardColumnLines(column.tasks, perColumn).map((line) => truncate(line, columnWidth)));
|
|
48466
|
-
const maxRows = Math.max(...renderedColumns.map((column) => column.length));
|
|
48467
|
-
for (let row = 0; row < maxRows; row += 1) {
|
|
48468
|
-
lines.push(renderedColumns.map((column) => pad(column[row] ?? "", columnWidth)).join(" "));
|
|
48469
|
-
}
|
|
48470
|
-
return lines.join("\n");
|
|
48471
|
-
}
|
|
48472
|
-
function boardColumnLines(tasks, limit) {
|
|
48473
|
-
if (tasks.length === 0) return ["No tasks here."];
|
|
48474
|
-
const visible = tasks.slice(0, limit).flatMap((task) => [
|
|
48475
|
-
`[${task.shortId}] ${task.title}`,
|
|
48476
|
-
` ${assigneeLabel(task)} ${task.queueState === "none" ? "" : task.queueState}`.trimEnd()
|
|
48477
|
-
]);
|
|
48478
|
-
if (tasks.length > limit) visible.push(`... ${tasks.length - limit} more`);
|
|
48479
|
-
return visible;
|
|
48480
|
-
}
|
|
48481
|
-
function compareTasks(a, b) {
|
|
48482
|
-
return a.position - b.position || a.title.localeCompare(b.title);
|
|
48483
|
-
}
|
|
48484
|
-
function columnTitle(status) {
|
|
48485
|
-
if (status === "in_progress") return "In progress";
|
|
48486
|
-
return status.slice(0, 1).toUpperCase() + status.slice(1).replace("_", " ");
|
|
48487
|
-
}
|
|
48488
|
-
function assigneeLabel(task) {
|
|
48489
|
-
return task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
48490
|
-
}
|
|
48491
|
-
async function taskKeyFromRecord(record, masterKey) {
|
|
48492
|
-
if (!record.encrypted_task_key) throw new Error(`Task ${record.task_id} is missing encrypted task key.`);
|
|
48493
|
-
const taskKey = await decryptBytesWithAesGcm(record.encrypted_task_key, masterKey);
|
|
48494
|
-
if (!taskKey) throw new Error(`Failed to decrypt task key for ${record.task_id}.`);
|
|
48495
|
-
return taskKey;
|
|
48496
|
-
}
|
|
48497
|
-
async function decryptOptional(value, key) {
|
|
48498
|
-
if (!value) return "";
|
|
48499
|
-
return await decryptWithAesGcmCombined(value, key) ?? "";
|
|
48500
|
-
}
|
|
48501
|
-
function parseStringArray(value) {
|
|
48502
|
-
if (!value) return [];
|
|
48503
|
-
try {
|
|
48504
|
-
const parsed = JSON.parse(value);
|
|
48505
|
-
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
48506
|
-
} catch {
|
|
48507
|
-
return [];
|
|
48508
|
-
}
|
|
48509
|
-
}
|
|
48510
|
-
function deriveShortId(record) {
|
|
48511
|
-
const prefix = record.short_id_prefix || DEFAULT_STANDALONE_PREFIX;
|
|
48512
|
-
const source = record.task_id || `${record.created_at ?? ""}-${record.position ?? ""}`;
|
|
48513
|
-
const digest = createHash7("sha256").update(source).digest("hex").slice(0, 4).toUpperCase();
|
|
48514
|
-
return `${prefix}-${parseInt(digest, 16) % 1e4}`;
|
|
48515
|
-
}
|
|
48516
|
-
function nowSeconds() {
|
|
48517
|
-
return Math.floor(Date.now() / 1e3);
|
|
48518
|
-
}
|
|
48519
|
-
function randomUUIDCompat() {
|
|
48520
|
-
return randomUUID5();
|
|
48521
|
-
}
|
|
48522
|
-
function pad(value, length) {
|
|
48523
|
-
return truncate(value, length).padEnd(length);
|
|
48524
|
-
}
|
|
48525
|
-
function truncate(value, length) {
|
|
48526
|
-
return value.length <= length ? value : `${value.slice(0, Math.max(0, length - 3))}...`;
|
|
48527
|
-
}
|
|
48528
|
-
|
|
48529
49089
|
// src/tui.ts
|
|
48530
49090
|
function defaultModeForStreams(input, output) {
|
|
48531
49091
|
return input.isTTY === true && output.isTTY === true ? "tui" : "quickstart";
|
|
@@ -48959,7 +49519,7 @@ async function handleActiveTaskAction(params) {
|
|
|
48959
49519
|
render();
|
|
48960
49520
|
return;
|
|
48961
49521
|
}
|
|
48962
|
-
await client.deleteUserTask(task.taskId);
|
|
49522
|
+
await client.deleteUserTask(task.taskId, task.version);
|
|
48963
49523
|
state.tasks = state.tasks.filter((candidate) => candidate.taskId !== task.taskId);
|
|
48964
49524
|
state.activeTask = null;
|
|
48965
49525
|
state.selectedIndex = Math.min(state.selectedIndex, Math.max(0, state.tasks.length - 1));
|
|
@@ -48976,7 +49536,7 @@ async function handleActiveTaskAction(params) {
|
|
|
48976
49536
|
render();
|
|
48977
49537
|
return;
|
|
48978
49538
|
}
|
|
48979
|
-
const updated2 = await client.reorderUserTasks({ moves: [{ task_id: task.taskId, position }] });
|
|
49539
|
+
const updated2 = await client.reorderUserTasks({ moves: [{ task_id: task.taskId, version: task.version, position }] });
|
|
48980
49540
|
const [decrypted2] = await decryptUserTasks(updated2, client.getMasterKeyBytes());
|
|
48981
49541
|
if (decrypted2) {
|
|
48982
49542
|
replaceTask(state, decrypted2);
|
|
@@ -50068,7 +50628,7 @@ async function handleTasks(client, subcommand, rest, flags) {
|
|
|
50068
50628
|
if (subcommand === "delete") {
|
|
50069
50629
|
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, "delete");
|
|
50070
50630
|
if (flags.confirm !== true) throw new Error("Deleting a task requires --confirm.");
|
|
50071
|
-
const result = await client.deleteUserTask(task.taskId);
|
|
50631
|
+
const result = await client.deleteUserTask(task.taskId, task.version);
|
|
50072
50632
|
if (flags.json === true) printJson2(result);
|
|
50073
50633
|
else console.log(`Task deleted: ${task.shortId}`);
|
|
50074
50634
|
return;
|
|
@@ -50099,7 +50659,7 @@ async function handleTasks(client, subcommand, rest, flags) {
|
|
|
50099
50659
|
}
|
|
50100
50660
|
if (subcommand === "reorder") {
|
|
50101
50661
|
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, "reorder");
|
|
50102
|
-
const move = { task_id: task.taskId };
|
|
50662
|
+
const move = { task_id: task.taskId, version: task.version };
|
|
50103
50663
|
if (typeof flags.before === "string") move.before_task_id = (await resolveTask(client, masterKey, flags.before, scope)).taskId;
|
|
50104
50664
|
if (typeof flags.after === "string") move.after_task_id = (await resolveTask(client, masterKey, flags.after, scope)).taskId;
|
|
50105
50665
|
if (typeof flags.status === "string") move.status = normalizeTaskStatus(flags.status);
|
|
@@ -50134,7 +50694,7 @@ async function requiredResolvedTask(client, masterKey, id, scope, action) {
|
|
|
50134
50694
|
}
|
|
50135
50695
|
function printTaskOutput(task, flags) {
|
|
50136
50696
|
if (flags.json === true) printJson2({ task: taskToJson(task) });
|
|
50137
|
-
else console.log(
|
|
50697
|
+
else console.log(renderTaskDetail(task));
|
|
50138
50698
|
}
|
|
50139
50699
|
function taskToJson(task) {
|
|
50140
50700
|
return {
|
|
@@ -50239,6 +50799,10 @@ function parsePositiveIntegerFlag(value, flagName) {
|
|
|
50239
50799
|
}
|
|
50240
50800
|
return parsed;
|
|
50241
50801
|
}
|
|
50802
|
+
function parseResponseTimeoutMs(flags) {
|
|
50803
|
+
const seconds = parsePositiveIntegerFlag(flags["response-timeout-seconds"], "--response-timeout-seconds");
|
|
50804
|
+
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
50805
|
+
}
|
|
50242
50806
|
function parseRemoteAccessSourceType(value) {
|
|
50243
50807
|
if (value === void 0) return "local_folder";
|
|
50244
50808
|
if (value === "local_folder" || value === "local_git_repository") {
|
|
@@ -50380,7 +50944,9 @@ async function handleChats(client, subcommand, rest, flags, redactor) {
|
|
|
50380
50944
|
json: flags.json === true,
|
|
50381
50945
|
autoApproveSubChats: flags["auto-approve"] === true,
|
|
50382
50946
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50947
|
+
acceptTaskProposals: flags["accept-task-proposals"] === true,
|
|
50383
50948
|
piiDetection: flags["no-pii-detection"] !== true,
|
|
50949
|
+
responseTimeoutMs: parseResponseTimeoutMs(flags),
|
|
50384
50950
|
anonymousLearningMode: client.hasSession() ? void 0 : parseAnonymousLearningModeFlags(flags)
|
|
50385
50951
|
},
|
|
50386
50952
|
redactor
|
|
@@ -50455,7 +51021,9 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
50455
51021
|
json: flags.json === true,
|
|
50456
51022
|
autoApproveSubChats: flags["auto-approve"] === true,
|
|
50457
51023
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50458
|
-
|
|
51024
|
+
acceptTaskProposals: flags["accept-task-proposals"] === true,
|
|
51025
|
+
piiDetection: flags["no-pii-detection"] !== true,
|
|
51026
|
+
responseTimeoutMs: parseResponseTimeoutMs(flags)
|
|
50459
51027
|
},
|
|
50460
51028
|
redactor
|
|
50461
51029
|
);
|
|
@@ -50483,7 +51051,9 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
50483
51051
|
json: flags.json === true,
|
|
50484
51052
|
autoApproveSubChats: flags["auto-approve"] === true,
|
|
50485
51053
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50486
|
-
|
|
51054
|
+
acceptTaskProposals: flags["accept-task-proposals"] === true,
|
|
51055
|
+
piiDetection: flags["no-pii-detection"] !== true,
|
|
51056
|
+
responseTimeoutMs: parseResponseTimeoutMs(flags)
|
|
50487
51057
|
},
|
|
50488
51058
|
redactor
|
|
50489
51059
|
);
|
|
@@ -50504,7 +51074,8 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
50504
51074
|
json: flags.json === true,
|
|
50505
51075
|
autoApproveSubChats: flags["auto-approve"] === true,
|
|
50506
51076
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50507
|
-
piiDetection: flags["no-pii-detection"] !== true
|
|
51077
|
+
piiDetection: flags["no-pii-detection"] !== true,
|
|
51078
|
+
responseTimeoutMs: parseResponseTimeoutMs(flags)
|
|
50508
51079
|
},
|
|
50509
51080
|
redactor
|
|
50510
51081
|
);
|
|
@@ -53891,7 +54462,7 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
53891
54462
|
process.stdout.write(`${result2.assistant}
|
|
53892
54463
|
`);
|
|
53893
54464
|
}
|
|
53894
|
-
return result2;
|
|
54465
|
+
return { ...result2, taskEvents: [], pendingTaskUpdateJobs: [], acceptedTaskProposals: [] };
|
|
53895
54466
|
}
|
|
53896
54467
|
const urlResult = prepareUrlEmbeds(finalMessage);
|
|
53897
54468
|
finalMessage = urlResult.message;
|
|
@@ -53906,6 +54477,7 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
53906
54477
|
onSubChatApprovalRequest,
|
|
53907
54478
|
autoApproveSubChats: params.autoApproveSubChats,
|
|
53908
54479
|
autoApproveMemories: params.autoApproveMemories,
|
|
54480
|
+
responseTimeoutMs: params.responseTimeoutMs,
|
|
53909
54481
|
learningMode,
|
|
53910
54482
|
preparedEmbeds: preparedEmbeds.length > 0 ? preparedEmbeds : void 0,
|
|
53911
54483
|
piiMappings: piiResult.mappings.map((mapping) => ({
|
|
@@ -53915,6 +54487,9 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
53915
54487
|
}))
|
|
53916
54488
|
});
|
|
53917
54489
|
clearTyping();
|
|
54490
|
+
const acceptedTaskProposals = params.acceptTaskProposals === true && result.status === "completed" ? await acceptChatTaskProposals(client, result.chatId, result.taskProposals, `${finalMessage}
|
|
54491
|
+
|
|
54492
|
+
${result.assistant}`) : [];
|
|
53918
54493
|
if (result.status === "waiting_for_user") {
|
|
53919
54494
|
const question = parseInteractiveQuestionBlock(result.assistant);
|
|
53920
54495
|
if (params.json && question) {
|
|
@@ -53930,7 +54505,7 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
53930
54505
|
"\x1B[33mOpenMates needs more input to continue.\x1B[0m\nContinue in the web app, or rerun with --json to inspect the structured waiting state.\n"
|
|
53931
54506
|
);
|
|
53932
54507
|
}
|
|
53933
|
-
return result;
|
|
54508
|
+
return { ...result, acceptedTaskProposals: [] };
|
|
53934
54509
|
}
|
|
53935
54510
|
if (!params.json) {
|
|
53936
54511
|
if (!headerPrinted) {
|
|
@@ -53994,13 +54569,87 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
53994
54569
|
process.stdout.write(`${SEP}
|
|
53995
54570
|
`);
|
|
53996
54571
|
}
|
|
54572
|
+
if (result.taskEvents.length > 0) {
|
|
54573
|
+
process.stdout.write(`\x1B[2mTask updates:\x1B[0m
|
|
54574
|
+
`);
|
|
54575
|
+
for (const event of result.taskEvents) {
|
|
54576
|
+
process.stdout.write(` \x1B[2m- ${formatTaskEvent(event)}\x1B[0m
|
|
54577
|
+
`);
|
|
54578
|
+
}
|
|
54579
|
+
process.stdout.write(`${SEP}
|
|
54580
|
+
`);
|
|
54581
|
+
}
|
|
54582
|
+
if (result.pendingTaskUpdateJobs.length > 0) {
|
|
54583
|
+
const count = result.pendingTaskUpdateJobs.length;
|
|
54584
|
+
process.stdout.write(
|
|
54585
|
+
`\x1B[2mPending encrypted task update${count === 1 ? "" : "s"}: ${count}\x1B[0m
|
|
54586
|
+
${SEP}
|
|
54587
|
+
`
|
|
54588
|
+
);
|
|
54589
|
+
}
|
|
53997
54590
|
process.stdout.write(
|
|
53998
54591
|
`\x1B[2mContinue: openmates chats send --chat ${shortId} "your message"\x1B[0m
|
|
53999
54592
|
\x1B[2mHistory: openmates chats show ${shortId}\x1B[0m
|
|
54000
54593
|
`
|
|
54001
54594
|
);
|
|
54002
54595
|
}
|
|
54003
|
-
return result;
|
|
54596
|
+
return { ...result, acceptedTaskProposals };
|
|
54597
|
+
}
|
|
54598
|
+
function formatTaskEvent(event) {
|
|
54599
|
+
const taskLabel = event.short_id || event.task_id;
|
|
54600
|
+
const title = event.title ? ` "${event.title}"` : "";
|
|
54601
|
+
const status = event.status ? ` (${event.status})` : "";
|
|
54602
|
+
const reason = event.reason ? `: ${event.reason}` : "";
|
|
54603
|
+
switch (event.event_type) {
|
|
54604
|
+
case "created":
|
|
54605
|
+
return `${taskLabel} created${title}${status}`;
|
|
54606
|
+
case "updated":
|
|
54607
|
+
return `${taskLabel} updated${title}${status}`;
|
|
54608
|
+
case "blocked":
|
|
54609
|
+
return `${taskLabel} blocked${reason}`;
|
|
54610
|
+
case "completed":
|
|
54611
|
+
return `${taskLabel} completed${title}`;
|
|
54612
|
+
case "unblocked":
|
|
54613
|
+
return `${taskLabel} unblocked`;
|
|
54614
|
+
default:
|
|
54615
|
+
return `${taskLabel} ${event.event_type}${title}${status}${reason}`;
|
|
54616
|
+
}
|
|
54617
|
+
}
|
|
54618
|
+
async function acceptChatTaskProposals(client, chatId, proposals, fallbackText) {
|
|
54619
|
+
let proposalsToAccept = proposals;
|
|
54620
|
+
if (proposalsToAccept.length === 0 && fallbackText.trim()) {
|
|
54621
|
+
const extractionText = buildTaskProposalFallbackText(fallbackText);
|
|
54622
|
+
proposalsToAccept = await client.extractUserTaskProposals({
|
|
54623
|
+
correctedText: extractionText,
|
|
54624
|
+
contextChatId: chatId
|
|
54625
|
+
});
|
|
54626
|
+
}
|
|
54627
|
+
if (proposalsToAccept.length === 0) return [];
|
|
54628
|
+
const masterKey = client.getMasterKeyBytes();
|
|
54629
|
+
const accepted = [];
|
|
54630
|
+
for (const proposal of proposalsToAccept) {
|
|
54631
|
+
const input = await buildCreateUserTaskInput(masterKey, {
|
|
54632
|
+
title: proposal.title,
|
|
54633
|
+
description: proposal.description ?? "",
|
|
54634
|
+
status: proposal.status,
|
|
54635
|
+
assign: proposal.assignee_type ?? "user",
|
|
54636
|
+
chatId
|
|
54637
|
+
});
|
|
54638
|
+
const created = await client.createUserTask(input);
|
|
54639
|
+
const decrypted = await decryptUserTask(created, masterKey);
|
|
54640
|
+
accepted.push(taskToJson(decrypted));
|
|
54641
|
+
}
|
|
54642
|
+
return accepted;
|
|
54643
|
+
}
|
|
54644
|
+
function buildTaskProposalFallbackText(text) {
|
|
54645
|
+
const seen = /* @__PURE__ */ new Set();
|
|
54646
|
+
const bulletLines = text.split("\n").map((line) => line.trim()).filter((line) => /^[-*•]\s+/.test(line)).map((line) => line.replace(/^([-*•]\s*)\[[ xX]\]\s*/, "$1")).filter((line) => {
|
|
54647
|
+
const key = line.replace(/^[-*•]\s+/, "").trim().toLowerCase();
|
|
54648
|
+
if (seen.has(key)) return false;
|
|
54649
|
+
seen.add(key);
|
|
54650
|
+
return true;
|
|
54651
|
+
});
|
|
54652
|
+
return bulletLines.length > 0 ? bulletLines.join("\n") : text;
|
|
54004
54653
|
}
|
|
54005
54654
|
function printIncognitoNoHistoryNotice(json) {
|
|
54006
54655
|
const message = "Incognito chats are not stored. There is no incognito history to show or clear.";
|
|
@@ -55401,10 +56050,10 @@ function printChatsHelp() {
|
|
|
55401
56050
|
openmates chats show <chat-id> [--raw] [--json]
|
|
55402
56051
|
openmates chats open [<n|example-id|slug>] [--json]
|
|
55403
56052
|
openmates chats search <query> [--json]
|
|
55404
|
-
openmates chats new <message> [--json] [--learning-mode --age-group <group>] [--auto-approve] [--auto-approve-memories] [--no-pii-detection]
|
|
55405
|
-
openmates chats send [--chat <id>] [--incognito] <message> [--json] [--auto-approve] [--auto-approve-memories] [--no-pii-detection]
|
|
56053
|
+
openmates chats new <message> [--json] [--learning-mode --age-group <group>] [--auto-approve] [--auto-approve-memories] [--accept-task-proposals] [--no-pii-detection]
|
|
56054
|
+
openmates chats send [--chat <id>] [--incognito] <message> [--json] [--auto-approve] [--auto-approve-memories] [--accept-task-proposals] [--no-pii-detection]
|
|
55406
56055
|
openmates chats send --chat <id> --followup <n> [--json] [--auto-approve] [--auto-approve-memories]
|
|
55407
|
-
openmates chats answer-interactive --chat <id> --question-json '<json>' --answer-json '<json>' [--json]
|
|
56056
|
+
openmates chats answer-interactive --chat <id> --question-json '<json>' --answer-json '<json>' [--json] [--accept-task-proposals]
|
|
55408
56057
|
openmates chats download <chat-id> [--output <path>] [--zip] [--json]
|
|
55409
56058
|
openmates chats delete <id1> [id2] [id3] ... [--yes]
|
|
55410
56059
|
openmates chats share [<chat-id>] [--expires <seconds>] [--password <pwd>] [--json]
|
|
@@ -55447,6 +56096,11 @@ Options for 'new', 'send', and 'incognito':
|
|
|
55447
56096
|
--no-pii-detection Send the message exactly as typed. By default, the CLI
|
|
55448
56097
|
replaces detected PII with placeholders before send.
|
|
55449
56098
|
|
|
56099
|
+
Saved-chat task options for 'new', 'send', and 'answer-interactive':
|
|
56100
|
+
--accept-task-proposals Explicitly save assistant task proposals as encrypted
|
|
56101
|
+
Tasks V1 records scoped to the chat. Without this,
|
|
56102
|
+
proposals remain review-only, like the web app card.
|
|
56103
|
+
|
|
55450
56104
|
Guest-only options for logged-out 'new':
|
|
55451
56105
|
--learning-mode Opt anonymous chat into request-scoped Learning Mode.
|
|
55452
56106
|
--age-group <group> Required with --learning-mode: under_10, 10_12,
|