openmates 0.14.7 → 0.14.8-alpha.1
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-U2RKIE56.js → chunk-RR6H47VR.js} +2560 -206
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +257 -6
- package/dist/index.js +392 -15
- package/package.json +7 -3
- package/templates/core/docker-compose.selfhost.yml +9 -7
|
@@ -27,6 +27,12 @@ var CIPHERTEXT_HEADER_LENGTH = 2 + FINGERPRINT_LENGTH;
|
|
|
27
27
|
var API_KEY_PREFIX = "sk-api-";
|
|
28
28
|
var API_KEY_RANDOM_LENGTH = 32;
|
|
29
29
|
var API_KEY_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
30
|
+
var CHAT_RECOVERY_PROTOCOL_VERSION = 1;
|
|
31
|
+
var CHAT_RECOVERY_MAX_PAYLOAD_BYTES = 16 * 1024 * 1024;
|
|
32
|
+
var CHAT_RECOVERY_KEY_BYTES = 32;
|
|
33
|
+
var CHAT_RECOVERY_NONCE_BYTES = 12;
|
|
34
|
+
var CHAT_RECOVERY_AAD_PREFIX = new TextEncoder().encode("OMCR1");
|
|
35
|
+
var CHAT_RECOVERY_UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
30
36
|
function base64ToBytes(input) {
|
|
31
37
|
return new Uint8Array(Buffer.from(input, "base64"));
|
|
32
38
|
}
|
|
@@ -38,6 +44,144 @@ function toArrayBuffer(input) {
|
|
|
38
44
|
new Uint8Array(output).set(input);
|
|
39
45
|
return output;
|
|
40
46
|
}
|
|
47
|
+
function bytesToBase64Url(input) {
|
|
48
|
+
return Buffer.from(input).toString("base64url");
|
|
49
|
+
}
|
|
50
|
+
function base64UrlToBytes(input, field, expectedLength) {
|
|
51
|
+
if (!input || input.includes("=")) {
|
|
52
|
+
throw new Error(`${field} must be non-empty unpadded base64url`);
|
|
53
|
+
}
|
|
54
|
+
const decoded = new Uint8Array(Buffer.from(input, "base64url"));
|
|
55
|
+
if (bytesToBase64Url(decoded) !== input) {
|
|
56
|
+
throw new Error(`${field} must be canonical base64url`);
|
|
57
|
+
}
|
|
58
|
+
if (expectedLength !== void 0 && decoded.length !== expectedLength) {
|
|
59
|
+
throw new Error(`${field} must decode to ${expectedLength} bytes`);
|
|
60
|
+
}
|
|
61
|
+
return decoded;
|
|
62
|
+
}
|
|
63
|
+
function uint32(value, field) {
|
|
64
|
+
if (!Number.isInteger(value) || value < 0 || value > 4294967295) {
|
|
65
|
+
throw new Error(`${field} must be an unsigned 32-bit integer`);
|
|
66
|
+
}
|
|
67
|
+
const encoded = new Uint8Array(4);
|
|
68
|
+
new DataView(encoded.buffer).setUint32(0, value, false);
|
|
69
|
+
return encoded;
|
|
70
|
+
}
|
|
71
|
+
function recoveryKeyVersion(value) {
|
|
72
|
+
if (value === 0) {
|
|
73
|
+
throw new Error("key_version must be greater than zero");
|
|
74
|
+
}
|
|
75
|
+
return uint32(value, "key_version");
|
|
76
|
+
}
|
|
77
|
+
function canonicalUuid(value, field) {
|
|
78
|
+
if (!CHAT_RECOVERY_UUID_PATTERN.test(value)) {
|
|
79
|
+
throw new Error(`${field} must be a canonical lowercase UUID`);
|
|
80
|
+
}
|
|
81
|
+
return new TextEncoder().encode(value);
|
|
82
|
+
}
|
|
83
|
+
function concatBytes(...values) {
|
|
84
|
+
const output = new Uint8Array(values.reduce((total, value) => total + value.length, 0));
|
|
85
|
+
let offset = 0;
|
|
86
|
+
for (const value of values) {
|
|
87
|
+
output.set(value, offset);
|
|
88
|
+
offset += value.length;
|
|
89
|
+
}
|
|
90
|
+
return output;
|
|
91
|
+
}
|
|
92
|
+
function lengthPrefix(value) {
|
|
93
|
+
return concatBytes(uint32(value.length, "length"), value);
|
|
94
|
+
}
|
|
95
|
+
async function sha256(input) {
|
|
96
|
+
return new Uint8Array(await cryptoApi.subtle.digest("SHA-256", toArrayBuffer(input)));
|
|
97
|
+
}
|
|
98
|
+
async function hkdfSha256(input, salt, info) {
|
|
99
|
+
const key = await cryptoApi.subtle.importKey("raw", toArrayBuffer(input), "HKDF", false, ["deriveBits"]);
|
|
100
|
+
const bits = await cryptoApi.subtle.deriveBits(
|
|
101
|
+
{
|
|
102
|
+
name: "HKDF",
|
|
103
|
+
hash: "SHA-256",
|
|
104
|
+
salt: toArrayBuffer(salt),
|
|
105
|
+
info: toArrayBuffer(info)
|
|
106
|
+
},
|
|
107
|
+
key,
|
|
108
|
+
256
|
|
109
|
+
);
|
|
110
|
+
return new Uint8Array(bits);
|
|
111
|
+
}
|
|
112
|
+
async function deriveChatCompletionRecoveryKeypair(chatKey, chatId, keyVersion) {
|
|
113
|
+
const rawChatKey = base64UrlToBytes(chatKey, "chat_key", CHAT_RECOVERY_KEY_BYTES);
|
|
114
|
+
const salt = await sha256(new TextEncoder().encode("openmates:chat-recovery:v1"));
|
|
115
|
+
const info = concatBytes(lengthPrefix(canonicalUuid(chatId, "chat_id")), recoveryKeyVersion(keyVersion));
|
|
116
|
+
const privateKey = await hkdfSha256(rawChatKey, salt, info);
|
|
117
|
+
return {
|
|
118
|
+
privateKey: bytesToBase64Url(privateKey),
|
|
119
|
+
publicKey: bytesToBase64Url(nacl.scalarMult.base(privateKey))
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function buildRecoveryAssociatedData(values) {
|
|
123
|
+
const associatedData = concatBytes(
|
|
124
|
+
CHAT_RECOVERY_AAD_PREFIX,
|
|
125
|
+
lengthPrefix(canonicalUuid(values.owner_id, "owner_id")),
|
|
126
|
+
lengthPrefix(canonicalUuid(values.chat_id, "chat_id")),
|
|
127
|
+
lengthPrefix(canonicalUuid(values.turn_id, "turn_id")),
|
|
128
|
+
lengthPrefix(canonicalUuid(values.job_id, "job_id")),
|
|
129
|
+
lengthPrefix(canonicalUuid(values.assistant_message_id, "assistant_message_id")),
|
|
130
|
+
recoveryKeyVersion(values.key_version)
|
|
131
|
+
);
|
|
132
|
+
return bytesToBase64Url(associatedData);
|
|
133
|
+
}
|
|
134
|
+
function recoveryAssociatedData(identity) {
|
|
135
|
+
return base64UrlToBytes(
|
|
136
|
+
buildRecoveryAssociatedData({
|
|
137
|
+
owner_id: identity.ownerId,
|
|
138
|
+
chat_id: identity.chatId,
|
|
139
|
+
turn_id: identity.turnId,
|
|
140
|
+
job_id: identity.jobId,
|
|
141
|
+
assistant_message_id: identity.assistantMessageId,
|
|
142
|
+
key_version: identity.keyVersion
|
|
143
|
+
}),
|
|
144
|
+
"associated_data"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
async function recoveryEnvelopeKey(sharedSecret, associatedData) {
|
|
148
|
+
if (sharedSecret.every((value) => value === 0)) {
|
|
149
|
+
throw new Error("X25519 shared secret must not be all zero");
|
|
150
|
+
}
|
|
151
|
+
const salt = await sha256(new TextEncoder().encode("openmates:chat-recovery-envelope:v1"));
|
|
152
|
+
return hkdfSha256(sharedSecret, salt, await sha256(associatedData));
|
|
153
|
+
}
|
|
154
|
+
async function openChatCompletionRecoveryEnvelope(envelope, options) {
|
|
155
|
+
if (!envelope || Object.keys(envelope).sort().join(",") !== "ciphertext,epk,nonce,v" || envelope.v !== CHAT_RECOVERY_PROTOCOL_VERSION) {
|
|
156
|
+
throw new Error("invalid recovery envelope fields or version");
|
|
157
|
+
}
|
|
158
|
+
const recoveryPrivateKey = base64UrlToBytes(
|
|
159
|
+
options.recoveryPrivateKey,
|
|
160
|
+
"recovery_private_key",
|
|
161
|
+
CHAT_RECOVERY_KEY_BYTES
|
|
162
|
+
);
|
|
163
|
+
const ephemeralPublicKey = base64UrlToBytes(envelope.epk, "epk", CHAT_RECOVERY_KEY_BYTES);
|
|
164
|
+
const nonce = base64UrlToBytes(envelope.nonce, "nonce", CHAT_RECOVERY_NONCE_BYTES);
|
|
165
|
+
const ciphertext = base64UrlToBytes(envelope.ciphertext, "ciphertext");
|
|
166
|
+
if (ciphertext.length < 16 || ciphertext.length - 16 > CHAT_RECOVERY_MAX_PAYLOAD_BYTES) {
|
|
167
|
+
throw new Error("ciphertext payload size is invalid");
|
|
168
|
+
}
|
|
169
|
+
const associatedData = recoveryAssociatedData(options);
|
|
170
|
+
const sharedSecret = nacl.scalarMult(recoveryPrivateKey, ephemeralPublicKey);
|
|
171
|
+
const envelopeKeyBytes = await recoveryEnvelopeKey(sharedSecret, associatedData);
|
|
172
|
+
const envelopeKey = await cryptoApi.subtle.importKey(
|
|
173
|
+
"raw",
|
|
174
|
+
toArrayBuffer(envelopeKeyBytes),
|
|
175
|
+
{ name: "AES-GCM" },
|
|
176
|
+
false,
|
|
177
|
+
["decrypt"]
|
|
178
|
+
);
|
|
179
|
+
return new Uint8Array(await cryptoApi.subtle.decrypt(
|
|
180
|
+
{ name: "AES-GCM", iv: toArrayBuffer(nonce), additionalData: toArrayBuffer(associatedData) },
|
|
181
|
+
envelopeKey,
|
|
182
|
+
toArrayBuffer(ciphertext)
|
|
183
|
+
));
|
|
184
|
+
}
|
|
41
185
|
function generateSalt(length = EMAIL_SALT_LENGTH) {
|
|
42
186
|
return cryptoApi.getRandomValues(new Uint8Array(length));
|
|
43
187
|
}
|
|
@@ -54,9 +198,9 @@ function generateSecureRecoveryKey(length = 24) {
|
|
|
54
198
|
const chars = requiredSets[index];
|
|
55
199
|
result[index] = chars.charAt(randomByte % chars.length);
|
|
56
200
|
}
|
|
57
|
-
const
|
|
201
|
+
const randomBytes4 = cryptoApi.getRandomValues(new Uint8Array(length));
|
|
58
202
|
for (let index = requiredSets.length; index < length; index += 1) {
|
|
59
|
-
result[index] = allChars.charAt(
|
|
203
|
+
result[index] = allChars.charAt(randomBytes4[index] % allChars.length);
|
|
60
204
|
}
|
|
61
205
|
for (let index = result.length - 1; index > 0; index -= 1) {
|
|
62
206
|
const randomByte = cryptoApi.getRandomValues(new Uint8Array(1))[0];
|
|
@@ -409,6 +553,9 @@ var OpenMatesHttpClient = class {
|
|
|
409
553
|
async patch(path, body, headers = {}) {
|
|
410
554
|
return this.request("PATCH", path, body, headers);
|
|
411
555
|
}
|
|
556
|
+
async put(path, body, headers = {}) {
|
|
557
|
+
return this.request("PUT", path, body, headers);
|
|
558
|
+
}
|
|
412
559
|
async getBinary(path, headers = {}) {
|
|
413
560
|
const url = `${this.apiUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
414
561
|
const requestHeaders = {
|
|
@@ -1042,6 +1189,58 @@ var SUB_CHAT_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1042
1189
|
]);
|
|
1043
1190
|
var SUB_CHAT_PARENT_STATUS_MESSAGE = "I've started the sub-chats and will continue once they finish.";
|
|
1044
1191
|
var SUB_CHAT_COMPLETION_TIMEOUT_MS = 10 * 6e4;
|
|
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"]);
|
|
1195
|
+
var WebSocketProtocolError = class extends Error {
|
|
1196
|
+
code;
|
|
1197
|
+
constructor(code, message) {
|
|
1198
|
+
super(message);
|
|
1199
|
+
this.name = "WebSocketProtocolError";
|
|
1200
|
+
this.code = code;
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
function websocketProtocolError(envelope) {
|
|
1204
|
+
const payload = envelope.payload && typeof envelope.payload === "object" ? envelope.payload : {};
|
|
1205
|
+
const code = envelope.type === "client_update_required" ? envelope.type : envelope.type === "error" && typeof payload.code === "string" ? payload.code : null;
|
|
1206
|
+
if (code === "client_update_required") {
|
|
1207
|
+
return new WebSocketProtocolError(code, CLIENT_UPDATE_REQUIRED_GUIDANCE);
|
|
1208
|
+
}
|
|
1209
|
+
if (envelope.type === "error") {
|
|
1210
|
+
return new WebSocketProtocolError(
|
|
1211
|
+
code ?? "websocket_error",
|
|
1212
|
+
typeof payload.message === "string" ? payload.message : "Unknown WebSocket error"
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
function parseTaskProposals(value) {
|
|
1218
|
+
if (!Array.isArray(value)) return [];
|
|
1219
|
+
return value.flatMap((item) => {
|
|
1220
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
1221
|
+
const raw = item;
|
|
1222
|
+
if (typeof raw.title !== "string" || raw.title.trim().length === 0) return [];
|
|
1223
|
+
const proposal = { title: raw.title };
|
|
1224
|
+
if (typeof raw.description === "string" || raw.description === null) proposal.description = raw.description;
|
|
1225
|
+
if (typeof raw.status === "string" && TASK_STATUSES.has(raw.status)) proposal.status = raw.status;
|
|
1226
|
+
if (typeof raw.assignee_type === "string" && TASK_ASSIGNEES.has(raw.assignee_type)) proposal.assignee_type = raw.assignee_type;
|
|
1227
|
+
return [proposal];
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
function parseTaskUpdateProposals(value) {
|
|
1231
|
+
if (!Array.isArray(value)) return [];
|
|
1232
|
+
return value.flatMap((item) => {
|
|
1233
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
1234
|
+
const raw = item;
|
|
1235
|
+
if (typeof raw.task_id !== "string" || raw.task_id.trim().length === 0) return [];
|
|
1236
|
+
const proposal = { task_id: raw.task_id };
|
|
1237
|
+
if (typeof raw.title === "string" || raw.title === null) proposal.title = raw.title;
|
|
1238
|
+
if (typeof raw.description === "string" || raw.description === null) proposal.description = raw.description;
|
|
1239
|
+
if (typeof raw.status === "string" && TASK_STATUSES.has(raw.status) || raw.status === null) proposal.status = raw.status;
|
|
1240
|
+
if (typeof raw.assignee_type === "string" && TASK_ASSIGNEES.has(raw.assignee_type) || raw.assignee_type === null) proposal.assignee_type = raw.assignee_type;
|
|
1241
|
+
return [proposal];
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1045
1244
|
var OpenMatesWsClient = class {
|
|
1046
1245
|
socket;
|
|
1047
1246
|
constructor(options) {
|
|
@@ -1111,13 +1310,23 @@ var OpenMatesWsClient = class {
|
|
|
1111
1310
|
}
|
|
1112
1311
|
waitForMessage(expectedType, predicate, timeoutMs = 2e4) {
|
|
1113
1312
|
return new Promise((resolve7, reject) => {
|
|
1313
|
+
const seenTypes = /* @__PURE__ */ new Set();
|
|
1314
|
+
let predicateMisses = 0;
|
|
1114
1315
|
const onMessage = (rawData) => {
|
|
1115
1316
|
try {
|
|
1116
1317
|
const parsed = JSON.parse(rawData.toString());
|
|
1318
|
+
if (typeof parsed.type === "string") seenTypes.add(parsed.type);
|
|
1319
|
+
const protocolError = websocketProtocolError(parsed);
|
|
1320
|
+
if (protocolError) {
|
|
1321
|
+
cleanup();
|
|
1322
|
+
reject(protocolError);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1117
1325
|
if (parsed.type !== expectedType) {
|
|
1118
1326
|
return;
|
|
1119
1327
|
}
|
|
1120
1328
|
if (predicate && !predicate(parsed.payload)) {
|
|
1329
|
+
predicateMisses += 1;
|
|
1121
1330
|
return;
|
|
1122
1331
|
}
|
|
1123
1332
|
cleanup();
|
|
@@ -1135,7 +1344,12 @@ var OpenMatesWsClient = class {
|
|
|
1135
1344
|
};
|
|
1136
1345
|
const timeout = setTimeout(() => {
|
|
1137
1346
|
cleanup();
|
|
1138
|
-
|
|
1347
|
+
const observed = [...seenTypes].sort().join(", ") || "none";
|
|
1348
|
+
reject(
|
|
1349
|
+
new Error(
|
|
1350
|
+
`Timeout waiting for '${expectedType}' (seen types: ${observed}; predicate misses: ${predicateMisses})`
|
|
1351
|
+
)
|
|
1352
|
+
);
|
|
1139
1353
|
}, timeoutMs);
|
|
1140
1354
|
const cleanup = () => {
|
|
1141
1355
|
clearTimeout(timeout);
|
|
@@ -1159,6 +1373,12 @@ var OpenMatesWsClient = class {
|
|
|
1159
1373
|
const onMessage = (rawData) => {
|
|
1160
1374
|
try {
|
|
1161
1375
|
const parsed = JSON.parse(rawData.toString());
|
|
1376
|
+
const protocolError = websocketProtocolError(parsed);
|
|
1377
|
+
if (protocolError) {
|
|
1378
|
+
cleanup();
|
|
1379
|
+
reject(protocolError);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1162
1382
|
if (parsed.type === terminatorType) {
|
|
1163
1383
|
cleanup();
|
|
1164
1384
|
resolve7(collected);
|
|
@@ -1218,8 +1438,11 @@ var OpenMatesWsClient = class {
|
|
|
1218
1438
|
let taskId = null;
|
|
1219
1439
|
let category = null;
|
|
1220
1440
|
let modelName = null;
|
|
1441
|
+
let recoveryJobId = null;
|
|
1221
1442
|
let followUpSuggestions = [];
|
|
1222
1443
|
let newChatSuggestions = [];
|
|
1444
|
+
let taskProposals = [];
|
|
1445
|
+
let taskUpdateProposals = [];
|
|
1223
1446
|
const subChatEvents = [];
|
|
1224
1447
|
const pendingSubChatHandlers = /* @__PURE__ */ new Set();
|
|
1225
1448
|
const pendingMemoryRequestHandlers = /* @__PURE__ */ new Set();
|
|
@@ -1248,6 +1471,8 @@ var OpenMatesWsClient = class {
|
|
|
1248
1471
|
if (typeof p.category === "string" && p.category) category = p.category;
|
|
1249
1472
|
if (typeof p.model_name === "string" && p.model_name)
|
|
1250
1473
|
modelName = p.model_name;
|
|
1474
|
+
if (typeof p.recovery_job_id === "string" && p.recovery_job_id && p.recovery_protocol_version === 1)
|
|
1475
|
+
recoveryJobId = p.recovery_job_id;
|
|
1251
1476
|
};
|
|
1252
1477
|
const extractMessageContent = (message) => {
|
|
1253
1478
|
if (typeof message.content === "string") return message.content;
|
|
@@ -1278,8 +1503,11 @@ var OpenMatesWsClient = class {
|
|
|
1278
1503
|
modelName,
|
|
1279
1504
|
followUpSuggestions,
|
|
1280
1505
|
newChatSuggestions,
|
|
1506
|
+
taskProposals,
|
|
1507
|
+
taskUpdateProposals,
|
|
1281
1508
|
embeds: [...embeds.values()],
|
|
1282
|
-
subChatEvents
|
|
1509
|
+
subChatEvents,
|
|
1510
|
+
recoveryJobId
|
|
1283
1511
|
});
|
|
1284
1512
|
return;
|
|
1285
1513
|
}
|
|
@@ -1298,8 +1526,11 @@ var OpenMatesWsClient = class {
|
|
|
1298
1526
|
modelName,
|
|
1299
1527
|
followUpSuggestions,
|
|
1300
1528
|
newChatSuggestions,
|
|
1529
|
+
taskProposals,
|
|
1530
|
+
taskUpdateProposals,
|
|
1301
1531
|
embeds: [...embeds.values()],
|
|
1302
|
-
subChatEvents
|
|
1532
|
+
subChatEvents,
|
|
1533
|
+
recoveryJobId
|
|
1303
1534
|
});
|
|
1304
1535
|
}, asyncEmbedWaitMs);
|
|
1305
1536
|
return;
|
|
@@ -1315,8 +1546,11 @@ var OpenMatesWsClient = class {
|
|
|
1315
1546
|
modelName,
|
|
1316
1547
|
followUpSuggestions,
|
|
1317
1548
|
newChatSuggestions,
|
|
1549
|
+
taskProposals,
|
|
1550
|
+
taskUpdateProposals,
|
|
1318
1551
|
embeds: [...embeds.values()],
|
|
1319
|
-
subChatEvents
|
|
1552
|
+
subChatEvents,
|
|
1553
|
+
recoveryJobId
|
|
1320
1554
|
});
|
|
1321
1555
|
};
|
|
1322
1556
|
const handleSubChatEvent = (type, p) => {
|
|
@@ -1388,13 +1622,10 @@ var OpenMatesWsClient = class {
|
|
|
1388
1622
|
const parsed = JSON.parse(rawData.toString());
|
|
1389
1623
|
const p = parsed.payload ?? {};
|
|
1390
1624
|
const type = parsed.type;
|
|
1391
|
-
|
|
1625
|
+
const protocolError = websocketProtocolError(parsed);
|
|
1626
|
+
if (protocolError) {
|
|
1392
1627
|
cleanup();
|
|
1393
|
-
reject(
|
|
1394
|
-
new Error(
|
|
1395
|
-
typeof p.message === "string" ? p.message : "Unknown chat error"
|
|
1396
|
-
)
|
|
1397
|
-
);
|
|
1628
|
+
reject(protocolError);
|
|
1398
1629
|
return;
|
|
1399
1630
|
}
|
|
1400
1631
|
if (SUB_CHAT_EVENT_TYPES.has(type)) {
|
|
@@ -1501,6 +1732,8 @@ var OpenMatesWsClient = class {
|
|
|
1501
1732
|
(s) => typeof s === "string" && s.length > 0
|
|
1502
1733
|
);
|
|
1503
1734
|
}
|
|
1735
|
+
taskProposals = parseTaskProposals(p.task_proposals);
|
|
1736
|
+
taskUpdateProposals = parseTaskUpdateProposals(p.task_update_proposals);
|
|
1504
1737
|
if (aiResponseDone) {
|
|
1505
1738
|
if (postProcessingTimer) {
|
|
1506
1739
|
clearTimeout(postProcessingTimer);
|
|
@@ -1529,8 +1762,11 @@ var OpenMatesWsClient = class {
|
|
|
1529
1762
|
modelName,
|
|
1530
1763
|
followUpSuggestions,
|
|
1531
1764
|
newChatSuggestions,
|
|
1765
|
+
taskProposals,
|
|
1766
|
+
taskUpdateProposals,
|
|
1532
1767
|
embeds: [...embeds.values()],
|
|
1533
|
-
subChatEvents
|
|
1768
|
+
subChatEvents,
|
|
1769
|
+
recoveryJobId
|
|
1534
1770
|
});
|
|
1535
1771
|
return;
|
|
1536
1772
|
}
|
|
@@ -1957,7 +2193,7 @@ async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId
|
|
|
1957
2193
|
const hashedMessageId = computeSHA256(messageId);
|
|
1958
2194
|
const hashedUserId = computeSHA256(userId);
|
|
1959
2195
|
const wrappedWithMaster = await wrapKey(embedKey, masterKey);
|
|
1960
|
-
const
|
|
2196
|
+
const nowSeconds2 = Math.floor(Date.now() / 1e3);
|
|
1961
2197
|
const embedKeys = [
|
|
1962
2198
|
{
|
|
1963
2199
|
hashed_embed_id: hashedEmbedId,
|
|
@@ -1965,7 +2201,7 @@ async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId
|
|
|
1965
2201
|
hashed_chat_id: null,
|
|
1966
2202
|
encrypted_embed_key: wrappedWithMaster,
|
|
1967
2203
|
hashed_user_id: hashedUserId,
|
|
1968
|
-
created_at:
|
|
2204
|
+
created_at: nowSeconds2
|
|
1969
2205
|
}
|
|
1970
2206
|
];
|
|
1971
2207
|
if (chatKey) {
|
|
@@ -1976,7 +2212,7 @@ async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId
|
|
|
1976
2212
|
hashed_chat_id: hashedChatId,
|
|
1977
2213
|
encrypted_embed_key: wrappedWithChat,
|
|
1978
2214
|
hashed_user_id: hashedUserId,
|
|
1979
|
-
created_at:
|
|
2215
|
+
created_at: nowSeconds2
|
|
1980
2216
|
});
|
|
1981
2217
|
}
|
|
1982
2218
|
return {
|
|
@@ -1991,8 +2227,8 @@ async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId
|
|
|
1991
2227
|
file_path: embed.filePath,
|
|
1992
2228
|
content_hash: embed.contentHash,
|
|
1993
2229
|
text_length_chars: embed.textLengthChars,
|
|
1994
|
-
created_at:
|
|
1995
|
-
updated_at:
|
|
2230
|
+
created_at: nowSeconds2,
|
|
2231
|
+
updated_at: nowSeconds2,
|
|
1996
2232
|
embed_keys: embedKeys
|
|
1997
2233
|
};
|
|
1998
2234
|
} catch (error) {
|
|
@@ -2137,11 +2373,11 @@ async function decryptConnectedAccountCliTransferPayload(encryptedPayload, passc
|
|
|
2137
2373
|
throw new Error("Unsupported connected account import payload format.");
|
|
2138
2374
|
}
|
|
2139
2375
|
try {
|
|
2140
|
-
const key = await deriveTransferKey(passcode,
|
|
2376
|
+
const key = await deriveTransferKey(passcode, base64UrlToBytes2(envelope.kdf.salt), envelope.kdf.iterations);
|
|
2141
2377
|
const plaintext = await webcrypto5.subtle.decrypt(
|
|
2142
|
-
{ name: "AES-GCM", iv: toArrayBuffer3(
|
|
2378
|
+
{ name: "AES-GCM", iv: toArrayBuffer3(base64UrlToBytes2(envelope.cipher.iv)) },
|
|
2143
2379
|
key,
|
|
2144
|
-
toArrayBuffer3(
|
|
2380
|
+
toArrayBuffer3(base64UrlToBytes2(envelope.cipher.text))
|
|
2145
2381
|
);
|
|
2146
2382
|
return validateTransferPayload(JSON.parse(new TextDecoder().decode(plaintext)));
|
|
2147
2383
|
} catch (error) {
|
|
@@ -2189,7 +2425,7 @@ async function buildEncryptedConnectedAccountImportRow(params) {
|
|
|
2189
2425
|
}
|
|
2190
2426
|
function parseEnvelope(encodedEnvelope) {
|
|
2191
2427
|
try {
|
|
2192
|
-
return JSON.parse(new TextDecoder().decode(
|
|
2428
|
+
return JSON.parse(new TextDecoder().decode(base64UrlToBytes2(encodedEnvelope)));
|
|
2193
2429
|
} catch {
|
|
2194
2430
|
throw new Error("Connected account import payload is malformed.");
|
|
2195
2431
|
}
|
|
@@ -2269,7 +2505,7 @@ function defaultProviderLabel(providerId) {
|
|
|
2269
2505
|
function sha256Hex2(value) {
|
|
2270
2506
|
return createHash4("sha256").update(value).digest("hex");
|
|
2271
2507
|
}
|
|
2272
|
-
function
|
|
2508
|
+
function base64UrlToBytes2(value) {
|
|
2273
2509
|
return base64ToBytes(value.replace(/-/g, "+").replace(/_/g, "/"));
|
|
2274
2510
|
}
|
|
2275
2511
|
function toArrayBuffer3(input) {
|
|
@@ -2863,6 +3099,15 @@ var MEMORY_TYPE_REGISTRY = {
|
|
|
2863
3099
|
properties: { url: { type: "string" }, notes: { type: "string" } }
|
|
2864
3100
|
}
|
|
2865
3101
|
};
|
|
3102
|
+
function reconcileAuthoritativeChats(chats, evidence) {
|
|
3103
|
+
const deletedIds = new Set(evidence.deleted_chat_ids ?? []);
|
|
3104
|
+
const authoritativeIds = evidence.authoritative && evidence.authoritative_chat_ids ? new Set(evidence.authoritative_chat_ids) : null;
|
|
3105
|
+
if (!authoritativeIds && deletedIds.size === 0) return chats;
|
|
3106
|
+
return chats.filter((chat) => {
|
|
3107
|
+
const chatId = String(chat.details.id ?? "");
|
|
3108
|
+
return (!authoritativeIds || authoritativeIds.has(chatId)) && !deletedIds.has(chatId);
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
2866
3111
|
var MATE_NAMES = {
|
|
2867
3112
|
software_development: "Sophia",
|
|
2868
3113
|
business_development: "Burton",
|
|
@@ -3148,6 +3393,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3148
3393
|
modelName: response.data.modelName ?? null,
|
|
3149
3394
|
mateName: null,
|
|
3150
3395
|
followUpSuggestions: response.data.followUpSuggestions ?? [],
|
|
3396
|
+
taskProposals: [],
|
|
3397
|
+
taskUpdateProposals: [],
|
|
3151
3398
|
subChatEvents: [],
|
|
3152
3399
|
appSettingsMemoryRequests: []
|
|
3153
3400
|
};
|
|
@@ -3635,6 +3882,157 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3635
3882
|
hasMore: offset + limit < total
|
|
3636
3883
|
};
|
|
3637
3884
|
}
|
|
3885
|
+
async saveDraft(params) {
|
|
3886
|
+
const markdown = params.markdown.trim();
|
|
3887
|
+
if (!markdown) throw new Error("Draft markdown must not be empty.");
|
|
3888
|
+
const masterKey = this.getMasterKeyBytes();
|
|
3889
|
+
const encrypted = await this.saveEncryptedDraft({
|
|
3890
|
+
chatId: params.chatId ?? randomUUID3(),
|
|
3891
|
+
encryptedDraftMd: await encryptWithAesGcmCombined(markdown, masterKey),
|
|
3892
|
+
encryptedDraftPreview: params.preview ? await encryptWithAesGcmCombined(params.preview, masterKey) : null
|
|
3893
|
+
});
|
|
3894
|
+
return { ...encrypted, markdown, preview: params.preview ?? null };
|
|
3895
|
+
}
|
|
3896
|
+
async saveEncryptedDraft(params) {
|
|
3897
|
+
const { ws } = await this.openWsClient();
|
|
3898
|
+
try {
|
|
3899
|
+
const receipt = ws.waitForMessage(
|
|
3900
|
+
"draft_update_receipt",
|
|
3901
|
+
(payload) => payload.chat_id === params.chatId
|
|
3902
|
+
);
|
|
3903
|
+
await ws.sendAsync("update_draft", {
|
|
3904
|
+
chat_id: params.chatId,
|
|
3905
|
+
encrypted_draft_md: params.encryptedDraftMd,
|
|
3906
|
+
encrypted_draft_preview: params.encryptedDraftPreview ?? null
|
|
3907
|
+
});
|
|
3908
|
+
const response = await receipt;
|
|
3909
|
+
const draftV = Number(response.payload.draft_v ?? 0);
|
|
3910
|
+
this.storeEncryptedDraft({
|
|
3911
|
+
chatId: params.chatId,
|
|
3912
|
+
encryptedDraftMd: params.encryptedDraftMd,
|
|
3913
|
+
encryptedDraftPreview: params.encryptedDraftPreview ?? null,
|
|
3914
|
+
draftV
|
|
3915
|
+
});
|
|
3916
|
+
return {
|
|
3917
|
+
chatId: params.chatId,
|
|
3918
|
+
encryptedDraftMd: params.encryptedDraftMd,
|
|
3919
|
+
encryptedDraftPreview: params.encryptedDraftPreview ?? null,
|
|
3920
|
+
draftV
|
|
3921
|
+
};
|
|
3922
|
+
} finally {
|
|
3923
|
+
ws.close();
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
async listDrafts(forceRefresh = false) {
|
|
3927
|
+
if (forceRefresh) await this.reconcileDraftVersions();
|
|
3928
|
+
const cache = await this.ensureSynced(forceRefresh);
|
|
3929
|
+
const drafts = [];
|
|
3930
|
+
for (const chat of cache.chats) {
|
|
3931
|
+
const draft = await this.decryptCachedDraft(chat);
|
|
3932
|
+
if (draft) drafts.push(draft);
|
|
3933
|
+
}
|
|
3934
|
+
return drafts;
|
|
3935
|
+
}
|
|
3936
|
+
async getDraft(chatId, forceRefresh = false) {
|
|
3937
|
+
if (forceRefresh) await this.reconcileDraftVersions();
|
|
3938
|
+
const cache = await this.ensureSynced(forceRefresh);
|
|
3939
|
+
const chat = cache.chats.find((entry) => String(entry.details.id ?? "") === chatId);
|
|
3940
|
+
return chat ? this.decryptCachedDraft(chat) : null;
|
|
3941
|
+
}
|
|
3942
|
+
async clearDraft(chatId) {
|
|
3943
|
+
const { ws } = await this.openWsClient();
|
|
3944
|
+
try {
|
|
3945
|
+
const receipt = ws.waitForMessage(
|
|
3946
|
+
"draft_delete_receipt",
|
|
3947
|
+
(payload) => payload.chat_id === chatId
|
|
3948
|
+
);
|
|
3949
|
+
await ws.sendAsync("delete_draft", { chat_id: chatId });
|
|
3950
|
+
await receipt;
|
|
3951
|
+
} finally {
|
|
3952
|
+
ws.close();
|
|
3953
|
+
}
|
|
3954
|
+
const cache = loadSyncCache();
|
|
3955
|
+
if (!cache) return;
|
|
3956
|
+
const chat = cache.chats.find((entry) => String(entry.details.id ?? "") === chatId);
|
|
3957
|
+
if (chat) {
|
|
3958
|
+
delete chat.details.encrypted_draft_md;
|
|
3959
|
+
delete chat.details.encrypted_draft_preview;
|
|
3960
|
+
chat.details.draft_v = 0;
|
|
3961
|
+
if (chat.messages.length === 0 && !chat.details.encrypted_chat_key) {
|
|
3962
|
+
cache.chats = cache.chats.filter((entry) => entry !== chat);
|
|
3963
|
+
}
|
|
3964
|
+
saveSyncCache(cache);
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
async reconcileDraftVersions() {
|
|
3968
|
+
const cache = loadSyncCache();
|
|
3969
|
+
const drafts = (cache?.chats ?? []).filter(
|
|
3970
|
+
(chat) => typeof chat.details.encrypted_draft_md === "string"
|
|
3971
|
+
);
|
|
3972
|
+
if (drafts.length === 0) return {};
|
|
3973
|
+
const { ws } = await this.openWsClient();
|
|
3974
|
+
try {
|
|
3975
|
+
const response = ws.waitForMessage("draft_versions_response");
|
|
3976
|
+
await ws.sendAsync("get_draft_versions", {
|
|
3977
|
+
chats: drafts.map((chat) => ({
|
|
3978
|
+
chat_id: String(chat.details.id),
|
|
3979
|
+
client_draft_v: Number(chat.details.draft_v ?? 0)
|
|
3980
|
+
}))
|
|
3981
|
+
});
|
|
3982
|
+
const frame = await response;
|
|
3983
|
+
const versions = frame.payload.versions ?? {};
|
|
3984
|
+
for (const chat of drafts) {
|
|
3985
|
+
const chatId = String(chat.details.id);
|
|
3986
|
+
if (versions[chatId] === 0) {
|
|
3987
|
+
delete chat.details.encrypted_draft_md;
|
|
3988
|
+
delete chat.details.encrypted_draft_preview;
|
|
3989
|
+
chat.details.draft_v = 0;
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
if (cache) saveSyncCache(cache);
|
|
3993
|
+
return versions;
|
|
3994
|
+
} finally {
|
|
3995
|
+
ws.close();
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
storeEncryptedDraft(draft) {
|
|
3999
|
+
const cache = loadSyncCache() ?? {
|
|
4000
|
+
syncedAt: 0,
|
|
4001
|
+
totalChatCount: 0,
|
|
4002
|
+
loadedChatCount: 0,
|
|
4003
|
+
chats: [],
|
|
4004
|
+
embeds: [],
|
|
4005
|
+
embedKeys: []
|
|
4006
|
+
};
|
|
4007
|
+
let chat = cache.chats.find((entry) => String(entry.details.id ?? "") === draft.chatId);
|
|
4008
|
+
if (!chat) {
|
|
4009
|
+
chat = { details: { id: draft.chatId }, messages: [] };
|
|
4010
|
+
cache.chats.unshift(chat);
|
|
4011
|
+
}
|
|
4012
|
+
chat.details.encrypted_draft_md = draft.encryptedDraftMd;
|
|
4013
|
+
chat.details.encrypted_draft_preview = draft.encryptedDraftPreview;
|
|
4014
|
+
chat.details.draft_v = draft.draftV;
|
|
4015
|
+
cache.syncedAt = Date.now();
|
|
4016
|
+
cache.loadedChatCount = cache.chats.length;
|
|
4017
|
+
saveSyncCache(cache);
|
|
4018
|
+
}
|
|
4019
|
+
async decryptCachedDraft(chat) {
|
|
4020
|
+
const encryptedDraftMd = chat.details.encrypted_draft_md;
|
|
4021
|
+
if (typeof encryptedDraftMd !== "string") return null;
|
|
4022
|
+
const encryptedPreview = typeof chat.details.encrypted_draft_preview === "string" ? chat.details.encrypted_draft_preview : null;
|
|
4023
|
+
const masterKey = this.getMasterKeyBytes();
|
|
4024
|
+
const markdown = await decryptWithAesGcmCombined(encryptedDraftMd, masterKey);
|
|
4025
|
+
if (markdown === null) throw new Error("Failed to decrypt draft markdown.");
|
|
4026
|
+
const preview = encryptedPreview ? await decryptWithAesGcmCombined(encryptedPreview, masterKey) : markdown.slice(0, 160);
|
|
4027
|
+
return {
|
|
4028
|
+
chatId: String(chat.details.id ?? ""),
|
|
4029
|
+
encryptedDraftMd,
|
|
4030
|
+
encryptedDraftPreview: encryptedPreview,
|
|
4031
|
+
draftV: Number(chat.details.draft_v ?? 0),
|
|
4032
|
+
markdown,
|
|
4033
|
+
preview
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
3638
4036
|
async searchChats(query) {
|
|
3639
4037
|
const cache = await this.ensureSynced();
|
|
3640
4038
|
const masterKey = this.getMasterKeyBytes();
|
|
@@ -3727,8 +4125,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3727
4125
|
);
|
|
3728
4126
|
let msgEmbedIds = [];
|
|
3729
4127
|
if (clientMsgId && cache.embeds.length > 0) {
|
|
3730
|
-
const { createHash:
|
|
3731
|
-
const hashed =
|
|
4128
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
4129
|
+
const hashed = createHash8("sha256").update(clientMsgId).digest("hex");
|
|
3732
4130
|
msgEmbedIds = cache.embeds.filter(
|
|
3733
4131
|
(e) => e.hashed_message_id === hashed && // Only include parent embeds (no parent_embed_id).
|
|
3734
4132
|
// Child embeds inherit the parent's key and are loaded
|
|
@@ -3775,8 +4173,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3775
4173
|
);
|
|
3776
4174
|
}
|
|
3777
4175
|
const embedId = String(embed.embed_id ?? embed.id ?? "");
|
|
3778
|
-
const { createHash:
|
|
3779
|
-
const hashedEmbedId =
|
|
4176
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
4177
|
+
const hashedEmbedId = createHash8("sha256").update(embedId).digest("hex");
|
|
3780
4178
|
const embedKeyBytes = await this.resolveEmbedKey(
|
|
3781
4179
|
cache,
|
|
3782
4180
|
masterKey,
|
|
@@ -3884,7 +4282,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3884
4282
|
async resolveEmbedKey(cache, masterKey, embed, embedId, hashedEmbedId, visited = /* @__PURE__ */ new Set()) {
|
|
3885
4283
|
if (visited.has(embedId)) return null;
|
|
3886
4284
|
visited.add(embedId);
|
|
3887
|
-
const { createHash:
|
|
4285
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
3888
4286
|
const masterKeyEntry = cache.embedKeys.find(
|
|
3889
4287
|
(ek) => ek.hashed_embed_id === hashedEmbedId && String(ek.key_type) === "master"
|
|
3890
4288
|
);
|
|
@@ -3901,7 +4299,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3901
4299
|
if (chatKeyEntry && typeof chatKeyEntry.encrypted_embed_key === "string") {
|
|
3902
4300
|
const hashedChatId = String(chatKeyEntry.hashed_chat_id ?? "");
|
|
3903
4301
|
const owningChat = cache.chats.find((c) => {
|
|
3904
|
-
const chatHash =
|
|
4302
|
+
const chatHash = createHash8("sha256").update(String(c.details.id ?? "")).digest("hex");
|
|
3905
4303
|
return chatHash === hashedChatId;
|
|
3906
4304
|
});
|
|
3907
4305
|
if (owningChat) {
|
|
@@ -3930,7 +4328,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3930
4328
|
const parentFullId = String(
|
|
3931
4329
|
parentEmbed2.embed_id ?? parentEmbed2.id ?? ""
|
|
3932
4330
|
);
|
|
3933
|
-
const parentHashedId =
|
|
4331
|
+
const parentHashedId = createHash8("sha256").update(parentFullId).digest("hex");
|
|
3934
4332
|
const parentKey = await this.resolveEmbedKey(
|
|
3935
4333
|
cache,
|
|
3936
4334
|
masterKey,
|
|
@@ -4074,7 +4472,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4074
4472
|
}
|
|
4075
4473
|
}
|
|
4076
4474
|
}
|
|
4077
|
-
const { ws, session } = await this.openWsClient();
|
|
4475
|
+
const { ws, session, ownerId } = await this.openWsClient();
|
|
4476
|
+
if (!params.incognito && !ownerId) {
|
|
4477
|
+
ws.close();
|
|
4478
|
+
throw new Error("Authenticated user identity is required for saved chat recovery.");
|
|
4479
|
+
}
|
|
4078
4480
|
const messageId = randomUUID3();
|
|
4079
4481
|
const createdAt = Math.floor(Date.now() / 1e3);
|
|
4080
4482
|
const isNewChat = !params.chatId;
|
|
@@ -4089,6 +4491,41 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4089
4491
|
}) : [];
|
|
4090
4492
|
assertNoConnectedAccountSecretLeak(connectedAccountTokenRefs);
|
|
4091
4493
|
const piiMappings = params.piiMappings ?? [];
|
|
4494
|
+
let chatKeyBytes = null;
|
|
4495
|
+
let encryptedChatKey = null;
|
|
4496
|
+
let baselineMessagesV = 0;
|
|
4497
|
+
let terminalExpectedMessagesV = 1;
|
|
4498
|
+
let savedTurnId = null;
|
|
4499
|
+
if (!params.incognito) {
|
|
4500
|
+
const masterKey = this.getMasterKeyBytes();
|
|
4501
|
+
if (isNewChat) {
|
|
4502
|
+
chatKeyBytes = globalThis.crypto ? new Uint8Array(globalThis.crypto.getRandomValues(new Uint8Array(32))) : new Uint8Array(
|
|
4503
|
+
(await import("crypto")).webcrypto.getRandomValues(
|
|
4504
|
+
new Uint8Array(32)
|
|
4505
|
+
)
|
|
4506
|
+
);
|
|
4507
|
+
encryptedChatKey = await encryptBytesWithAesGcm(
|
|
4508
|
+
chatKeyBytes,
|
|
4509
|
+
masterKey
|
|
4510
|
+
);
|
|
4511
|
+
} else {
|
|
4512
|
+
const cache = loadSyncCache() ?? await this.ensureSynced();
|
|
4513
|
+
const chat = cache.chats.find(
|
|
4514
|
+
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
4515
|
+
);
|
|
4516
|
+
if (chat) {
|
|
4517
|
+
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
4518
|
+
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
4519
|
+
if (encKey) {
|
|
4520
|
+
chatKeyBytes = await decryptBytesWithAesGcm(encKey, masterKey);
|
|
4521
|
+
encryptedChatKey = encKey;
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
if (!chatKeyBytes || !encryptedChatKey) {
|
|
4525
|
+
throw new Error(`Encrypted chat key not found for chat '${chatId}'. Sync and try again.`);
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4092
4529
|
const messagePayload = {
|
|
4093
4530
|
chat_id: chatId,
|
|
4094
4531
|
is_incognito: Boolean(params.incognito),
|
|
@@ -4103,6 +4540,9 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4103
4540
|
chat_has_title: Boolean(params.chatId)
|
|
4104
4541
|
}
|
|
4105
4542
|
};
|
|
4543
|
+
if (isNewChat && encryptedChatKey) {
|
|
4544
|
+
messagePayload.encrypted_chat_key = encryptedChatKey;
|
|
4545
|
+
}
|
|
4106
4546
|
if (memoryMetadataKeys.length > 0) {
|
|
4107
4547
|
messagePayload.app_settings_memories_metadata = memoryMetadataKeys;
|
|
4108
4548
|
}
|
|
@@ -4144,37 +4584,6 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4144
4584
|
created_at: historyMessage.created_at
|
|
4145
4585
|
}));
|
|
4146
4586
|
}
|
|
4147
|
-
let chatKeyBytes = null;
|
|
4148
|
-
let encryptedChatKey = null;
|
|
4149
|
-
let baselineMessagesV = 0;
|
|
4150
|
-
if (!params.incognito) {
|
|
4151
|
-
const masterKey = this.getMasterKeyBytes();
|
|
4152
|
-
if (isNewChat) {
|
|
4153
|
-
chatKeyBytes = globalThis.crypto ? new Uint8Array(globalThis.crypto.getRandomValues(new Uint8Array(32))) : new Uint8Array(
|
|
4154
|
-
(await import("crypto")).webcrypto.getRandomValues(
|
|
4155
|
-
new Uint8Array(32)
|
|
4156
|
-
)
|
|
4157
|
-
);
|
|
4158
|
-
encryptedChatKey = await encryptBytesWithAesGcm(
|
|
4159
|
-
chatKeyBytes,
|
|
4160
|
-
masterKey
|
|
4161
|
-
);
|
|
4162
|
-
messagePayload.encrypted_chat_key = encryptedChatKey;
|
|
4163
|
-
} else {
|
|
4164
|
-
const cache = loadSyncCache() ?? await this.ensureSynced();
|
|
4165
|
-
const chat = cache.chats.find(
|
|
4166
|
-
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
4167
|
-
);
|
|
4168
|
-
if (chat) {
|
|
4169
|
-
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
4170
|
-
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
4171
|
-
if (encKey) {
|
|
4172
|
-
chatKeyBytes = await decryptBytesWithAesGcm(encKey, masterKey);
|
|
4173
|
-
encryptedChatKey = encKey;
|
|
4174
|
-
}
|
|
4175
|
-
}
|
|
4176
|
-
}
|
|
4177
|
-
}
|
|
4178
4587
|
if (params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
4179
4588
|
messagePayload.embeds = params.preparedEmbeds.map((embed) => ({
|
|
4180
4589
|
embed_id: embed.embedId,
|
|
@@ -4202,52 +4611,107 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4202
4611
|
if (encryptedEmbeds.length > 0) {
|
|
4203
4612
|
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
4204
4613
|
}
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4614
|
+
let precollectedResponse = params.precollectResponse && params.incognito ? ws.collectAiResponse(messageId, chatId, { onStream: params.onStream }) : null;
|
|
4615
|
+
if (!params.incognito && chatKeyBytes && encryptedChatKey) {
|
|
4616
|
+
const protocolVersion = 1;
|
|
4617
|
+
const chatKeyVersion = 1;
|
|
4618
|
+
const turnId = randomUUID3();
|
|
4619
|
+
savedTurnId = turnId;
|
|
4620
|
+
terminalExpectedMessagesV = baselineMessagesV + 1;
|
|
4621
|
+
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
4622
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
4623
|
+
chatId,
|
|
4624
|
+
chatKeyVersion
|
|
4625
|
+
);
|
|
4217
4626
|
const encryptedContent = await encryptWithAesGcmCombined(
|
|
4218
4627
|
params.message,
|
|
4219
4628
|
chatKeyBytes
|
|
4220
4629
|
);
|
|
4221
|
-
const
|
|
4222
|
-
|
|
4223
|
-
chatKeyBytes
|
|
4224
|
-
);
|
|
4225
|
-
const metadataPayload = {
|
|
4630
|
+
const encryptedUserMessage = {
|
|
4631
|
+
client_message_id: messageId,
|
|
4226
4632
|
chat_id: chatId,
|
|
4227
|
-
message_id: messageId,
|
|
4228
4633
|
encrypted_content: encryptedContent,
|
|
4229
|
-
|
|
4634
|
+
role: "user",
|
|
4230
4635
|
created_at: createdAt,
|
|
4231
|
-
|
|
4232
|
-
versions: {
|
|
4233
|
-
messages_v: 0,
|
|
4234
|
-
title_v: 0,
|
|
4235
|
-
last_edited_overall_timestamp: createdAt
|
|
4236
|
-
}
|
|
4636
|
+
updated_at: createdAt
|
|
4237
4637
|
};
|
|
4238
4638
|
if (piiMappings.length > 0) {
|
|
4239
|
-
|
|
4639
|
+
encryptedUserMessage.encrypted_pii_mappings = await encryptWithAesGcmCombined(
|
|
4240
4640
|
JSON.stringify(piiMappings),
|
|
4241
4641
|
chatKeyBytes
|
|
4242
4642
|
);
|
|
4243
4643
|
}
|
|
4244
|
-
|
|
4644
|
+
Object.assign(messagePayload, {
|
|
4645
|
+
turn_id: turnId,
|
|
4646
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
4647
|
+
chat_key_version: chatKeyVersion
|
|
4648
|
+
});
|
|
4649
|
+
const preflightPayload = {
|
|
4650
|
+
protocol_version: protocolVersion,
|
|
4651
|
+
chat_id: chatId,
|
|
4652
|
+
turn_id: turnId,
|
|
4653
|
+
message_id: messageId,
|
|
4654
|
+
chat_key_version: chatKeyVersion,
|
|
4655
|
+
encrypted_chat_key: encryptedChatKey,
|
|
4656
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
4657
|
+
expected_messages_v: baselineMessagesV,
|
|
4658
|
+
encrypted_user_message: encryptedUserMessage,
|
|
4659
|
+
inference_request: messagePayload
|
|
4660
|
+
};
|
|
4661
|
+
if (isNewChat) {
|
|
4662
|
+
preflightPayload.encrypted_chat_metadata = {
|
|
4663
|
+
encrypted_title: await encryptWithAesGcmCombined("", chatKeyBytes),
|
|
4664
|
+
encrypted_chat_key: encryptedChatKey,
|
|
4665
|
+
created_at: createdAt,
|
|
4666
|
+
updated_at: createdAt
|
|
4667
|
+
};
|
|
4668
|
+
}
|
|
4669
|
+
const preflightAck = ws.waitForMessage("chat_turn_preflight_ack");
|
|
4670
|
+
let ackPayload;
|
|
4671
|
+
try {
|
|
4672
|
+
await ws.sendAsync("chat_turn_preflight", preflightPayload);
|
|
4673
|
+
ackPayload = (await preflightAck).payload;
|
|
4674
|
+
} catch (error) {
|
|
4675
|
+
ws.close();
|
|
4676
|
+
throw error;
|
|
4677
|
+
}
|
|
4678
|
+
if (typeof ackPayload.preflight_id !== "string" || !ackPayload.preflight_id) {
|
|
4679
|
+
ws.close();
|
|
4680
|
+
throw new Error("Encrypted chat preflight acknowledgement omitted preflight_id.");
|
|
4681
|
+
}
|
|
4682
|
+
Object.assign(messagePayload, {
|
|
4683
|
+
protocol_version: protocolVersion,
|
|
4684
|
+
preflight_id: ackPayload.preflight_id
|
|
4685
|
+
});
|
|
4686
|
+
if (typeof ackPayload.committed_messages_v === "number" && Number.isSafeInteger(ackPayload.committed_messages_v)) {
|
|
4687
|
+
terminalExpectedMessagesV = ackPayload.committed_messages_v;
|
|
4688
|
+
}
|
|
4689
|
+
}
|
|
4690
|
+
if (params.precollectResponse && !params.incognito) {
|
|
4691
|
+
precollectedResponse = ws.collectAiResponse(messageId, chatId, {
|
|
4692
|
+
onStream: params.onStream
|
|
4693
|
+
});
|
|
4694
|
+
}
|
|
4695
|
+
const confirmed = ws.waitForMessage(
|
|
4696
|
+
"chat_message_confirmed",
|
|
4697
|
+
(payload) => {
|
|
4698
|
+
const eventPayload = payload;
|
|
4699
|
+
return eventPayload.chat_id === chatId && eventPayload.message_id === messageId;
|
|
4700
|
+
},
|
|
4701
|
+
2e4
|
|
4702
|
+
);
|
|
4703
|
+
await ws.sendAsync("chat_message_added", messagePayload);
|
|
4704
|
+
const confirmedPayload = (await confirmed).payload;
|
|
4705
|
+
if (typeof confirmedPayload.new_messages_v === "number" && Number.isSafeInteger(confirmedPayload.new_messages_v)) {
|
|
4706
|
+
terminalExpectedMessagesV = confirmedPayload.new_messages_v + 1;
|
|
4245
4707
|
}
|
|
4246
4708
|
let assistant = "";
|
|
4247
4709
|
let assistantMessageId = null;
|
|
4248
4710
|
let category = null;
|
|
4249
4711
|
let modelName = null;
|
|
4250
4712
|
let followUpSuggestions = [];
|
|
4713
|
+
let taskProposals = [];
|
|
4714
|
+
let taskUpdateProposals = [];
|
|
4251
4715
|
let subChatEvents = [];
|
|
4252
4716
|
const appSettingsMemoryRequests = [];
|
|
4253
4717
|
const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
@@ -4433,6 +4897,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4433
4897
|
modelName,
|
|
4434
4898
|
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
4435
4899
|
followUpSuggestions,
|
|
4900
|
+
taskProposals,
|
|
4901
|
+
taskUpdateProposals,
|
|
4436
4902
|
subChatEvents,
|
|
4437
4903
|
appSettingsMemoryRequests
|
|
4438
4904
|
};
|
|
@@ -4442,12 +4908,14 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4442
4908
|
}
|
|
4443
4909
|
} else {
|
|
4444
4910
|
try {
|
|
4445
|
-
const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
|
|
4911
|
+
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId, streamOpts));
|
|
4446
4912
|
assistantMessageId = resp.messageId;
|
|
4447
4913
|
assistant = resp.content;
|
|
4448
4914
|
category = resp.category;
|
|
4449
4915
|
modelName = resp.modelName;
|
|
4450
4916
|
followUpSuggestions = resp.followUpSuggestions;
|
|
4917
|
+
taskProposals = resp.taskProposals;
|
|
4918
|
+
taskUpdateProposals = resp.taskUpdateProposals;
|
|
4451
4919
|
subChatEvents = resp.subChatEvents;
|
|
4452
4920
|
if (resp.status === "waiting_for_user") {
|
|
4453
4921
|
return {
|
|
@@ -4459,51 +4927,116 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4459
4927
|
modelName,
|
|
4460
4928
|
mateName: category ? MATE_NAMES[category] ?? null : null,
|
|
4461
4929
|
followUpSuggestions,
|
|
4930
|
+
taskProposals,
|
|
4931
|
+
taskUpdateProposals,
|
|
4462
4932
|
subChatEvents,
|
|
4463
4933
|
appSettingsMemoryRequests
|
|
4464
4934
|
};
|
|
4465
4935
|
}
|
|
4466
|
-
if (chatKeyBytes
|
|
4936
|
+
if (chatKeyBytes) {
|
|
4937
|
+
if (!resp.recoveryJobId || !savedTurnId || !ownerId) {
|
|
4938
|
+
throw new Error("Saved chat completion did not include recoverable terminal identity.");
|
|
4939
|
+
}
|
|
4940
|
+
const recoveryJobId = resp.recoveryJobId;
|
|
4941
|
+
const claimPromise = ws.waitForMessage(
|
|
4942
|
+
"recovery_job_claimed",
|
|
4943
|
+
(payload) => payload.job_id === recoveryJobId,
|
|
4944
|
+
2e4
|
|
4945
|
+
);
|
|
4946
|
+
await ws.sendAsync("recovery_job_claim", {
|
|
4947
|
+
protocol_version: 1,
|
|
4948
|
+
job_id: recoveryJobId
|
|
4949
|
+
});
|
|
4950
|
+
const claim = (await claimPromise).payload;
|
|
4951
|
+
const leaseToken = typeof claim.lease_token === "string" ? claim.lease_token : null;
|
|
4952
|
+
const leaseGeneration = typeof claim.lease_generation === "number" && Number.isSafeInteger(claim.lease_generation) ? claim.lease_generation : null;
|
|
4953
|
+
const assistantId = typeof claim.assistant_message_id === "string" ? claim.assistant_message_id : null;
|
|
4954
|
+
const keyVersion = typeof claim.chat_key_version === "number" && Number.isSafeInteger(claim.chat_key_version) ? claim.chat_key_version : null;
|
|
4955
|
+
if (claim.state !== "LEASED" || !leaseToken || leaseGeneration === null || leaseGeneration < 1 || !assistantId || keyVersion === null || keyVersion !== 1 || claim.chat_id !== chatId || claim.turn_id !== savedTurnId || typeof claim.sealed_payload !== "string") {
|
|
4956
|
+
throw new Error("Recovery job claim returned invalid lease or identity data.");
|
|
4957
|
+
}
|
|
4958
|
+
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
4959
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
4960
|
+
chatId,
|
|
4961
|
+
keyVersion
|
|
4962
|
+
);
|
|
4963
|
+
let envelope;
|
|
4964
|
+
try {
|
|
4965
|
+
envelope = JSON.parse(claim.sealed_payload);
|
|
4966
|
+
} catch {
|
|
4967
|
+
throw new Error("Recovery job contained an invalid sealed envelope.");
|
|
4968
|
+
}
|
|
4969
|
+
const plaintext = await openChatCompletionRecoveryEnvelope(
|
|
4970
|
+
envelope,
|
|
4971
|
+
{
|
|
4972
|
+
recoveryPrivateKey: recoveryKeypair.privateKey,
|
|
4973
|
+
ownerId,
|
|
4974
|
+
chatId,
|
|
4975
|
+
turnId: savedTurnId,
|
|
4976
|
+
jobId: recoveryJobId,
|
|
4977
|
+
assistantMessageId: assistantId,
|
|
4978
|
+
keyVersion
|
|
4979
|
+
}
|
|
4980
|
+
);
|
|
4981
|
+
let recovered;
|
|
4982
|
+
try {
|
|
4983
|
+
recovered = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(plaintext));
|
|
4984
|
+
} catch {
|
|
4985
|
+
throw new Error("Recovery job plaintext was not valid UTF-8 JSON.");
|
|
4986
|
+
}
|
|
4987
|
+
const expectedRecoveryFields = [
|
|
4988
|
+
"assistant_message_id",
|
|
4989
|
+
"category",
|
|
4990
|
+
"chat_id",
|
|
4991
|
+
"content",
|
|
4992
|
+
"job_id",
|
|
4993
|
+
"key_version",
|
|
4994
|
+
"model_name",
|
|
4995
|
+
"turn_id"
|
|
4996
|
+
];
|
|
4997
|
+
if (Object.keys(recovered).sort().join(",") !== expectedRecoveryFields.join(",") || recovered.assistant_message_id !== assistantId || recovered.chat_id !== chatId || recovered.turn_id !== savedTurnId || recovered.job_id !== recoveryJobId || recovered.key_version !== keyVersion || typeof recovered.content !== "string" || recovered.category !== null && typeof recovered.category !== "string" || recovered.model_name !== null && typeof recovered.model_name !== "string" || recovered.content !== assistant || recovered.category !== category || recovered.model_name !== modelName) {
|
|
4998
|
+
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
4999
|
+
}
|
|
4467
5000
|
const completedAt = Math.floor(Date.now() / 1e3);
|
|
4468
5001
|
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
4469
|
-
|
|
5002
|
+
recovered.content,
|
|
4470
5003
|
chatKeyBytes
|
|
4471
5004
|
);
|
|
4472
|
-
const
|
|
4473
|
-
const
|
|
4474
|
-
const
|
|
4475
|
-
ws.
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
5005
|
+
const encryptedSenderName = await encryptWithAesGcmCombined("Assistant", chatKeyBytes);
|
|
5006
|
+
const encryptedCategory = recovered.category ? await encryptWithAesGcmCombined(recovered.category, chatKeyBytes) : void 0;
|
|
5007
|
+
const encryptedModelName = recovered.model_name ? await encryptWithAesGcmCombined(recovered.model_name, chatKeyBytes) : void 0;
|
|
5008
|
+
const persistedPromise = ws.waitForMessage(
|
|
5009
|
+
"recovery_job_persisted",
|
|
5010
|
+
(payload) => payload.job_id === recoveryJobId,
|
|
5011
|
+
2e4
|
|
5012
|
+
);
|
|
5013
|
+
await ws.sendAsync("recovery_job_persist", {
|
|
5014
|
+
protocol_version: 1,
|
|
5015
|
+
job_id: recoveryJobId,
|
|
5016
|
+
lease_token: leaseToken,
|
|
5017
|
+
lease_generation: leaseGeneration,
|
|
5018
|
+
expected_messages_v: terminalExpectedMessagesV,
|
|
5019
|
+
encrypted_assistant_message: {
|
|
5020
|
+
client_message_id: assistantId,
|
|
4479
5021
|
chat_id: chatId,
|
|
4480
|
-
role: "assistant",
|
|
4481
|
-
created_at: completedAt,
|
|
4482
|
-
status: "synced",
|
|
4483
|
-
user_message_id: messageId,
|
|
4484
5022
|
encrypted_content: encryptedAssistantContent,
|
|
5023
|
+
encrypted_sender_name: encryptedSenderName,
|
|
4485
5024
|
encrypted_category: encryptedCategory,
|
|
4486
|
-
encrypted_model_name: encryptedModelName
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
5025
|
+
encrypted_model_name: encryptedModelName,
|
|
5026
|
+
role: "assistant",
|
|
5027
|
+
user_message_id: messageId,
|
|
5028
|
+
created_at: completedAt,
|
|
5029
|
+
updated_at: completedAt
|
|
4491
5030
|
}
|
|
4492
5031
|
});
|
|
4493
|
-
await
|
|
4494
|
-
|
|
4495
|
-
(payload) => {
|
|
4496
|
-
const p = payload;
|
|
4497
|
-
return p.message_id === persistedAssistantMessageId;
|
|
4498
|
-
},
|
|
4499
|
-
2e4
|
|
4500
|
-
);
|
|
5032
|
+
await persistedPromise;
|
|
5033
|
+
assistantMessageId = assistantId;
|
|
4501
5034
|
await this.persistStreamedEmbeds({
|
|
4502
5035
|
ws,
|
|
4503
5036
|
embeds: resp.embeds,
|
|
4504
5037
|
chatId,
|
|
4505
5038
|
chatKeyBytes,
|
|
4506
|
-
fallbackMessageId:
|
|
5039
|
+
fallbackMessageId: assistantId
|
|
4507
5040
|
});
|
|
4508
5041
|
await this.persistPostProcessingMetadata({
|
|
4509
5042
|
ws,
|
|
@@ -4529,6 +5062,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4529
5062
|
modelName,
|
|
4530
5063
|
mateName,
|
|
4531
5064
|
followUpSuggestions,
|
|
5065
|
+
taskProposals,
|
|
5066
|
+
taskUpdateProposals,
|
|
4532
5067
|
subChatEvents,
|
|
4533
5068
|
appSettingsMemoryRequests
|
|
4534
5069
|
};
|
|
@@ -5041,6 +5576,30 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5041
5576
|
}
|
|
5042
5577
|
return response.data.workflow;
|
|
5043
5578
|
}
|
|
5579
|
+
async validateWorkflowYaml(source) {
|
|
5580
|
+
this.requireSession();
|
|
5581
|
+
const response = await this.http.post("/v1/workflows/validate", { source }, this.getCliRequestHeaders());
|
|
5582
|
+
if (!response.ok || !response.data.validation) {
|
|
5583
|
+
throw new Error(`Workflow YAML validation failed with HTTP ${response.status}`);
|
|
5584
|
+
}
|
|
5585
|
+
return response.data.validation;
|
|
5586
|
+
}
|
|
5587
|
+
async createWorkflowYaml(source) {
|
|
5588
|
+
this.requireSession();
|
|
5589
|
+
const response = await this.http.post("/v1/workflows/yaml", { source }, this.getCliRequestHeaders());
|
|
5590
|
+
if (!response.ok || !response.data.workflow || !response.data.validation) {
|
|
5591
|
+
throw new Error(`Workflow YAML create failed with HTTP ${response.status}`);
|
|
5592
|
+
}
|
|
5593
|
+
return { workflow: response.data.workflow, validation: response.data.validation };
|
|
5594
|
+
}
|
|
5595
|
+
async updateWorkflowYaml(workflowId, source) {
|
|
5596
|
+
this.requireSession();
|
|
5597
|
+
const createLike = await this.http.post(`/v1/workflows/${encodeURIComponent(workflowId)}/yaml`, { source }, this.getCliRequestHeaders());
|
|
5598
|
+
if (!createLike.ok || !createLike.data.workflow || !createLike.data.validation) {
|
|
5599
|
+
throw new Error(`Workflow YAML update failed with HTTP ${createLike.status}`);
|
|
5600
|
+
}
|
|
5601
|
+
return { workflow: createLike.data.workflow, validation: createLike.data.validation };
|
|
5602
|
+
}
|
|
5044
5603
|
async getWorkflow(workflowId) {
|
|
5045
5604
|
this.requireSession();
|
|
5046
5605
|
const response = await this.http.get(
|
|
@@ -5099,12 +5658,15 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5099
5658
|
async disableWorkflow(workflowId) {
|
|
5100
5659
|
return this.setWorkflowEnabled(workflowId, false);
|
|
5101
5660
|
}
|
|
5102
|
-
async runWorkflow(workflowId, params
|
|
5661
|
+
async runWorkflow(workflowId, params) {
|
|
5103
5662
|
this.requireSession();
|
|
5663
|
+
if (!params.idempotencyKey.trim()) {
|
|
5664
|
+
throw new Error("Workflow run requires a stable idempotencyKey");
|
|
5665
|
+
}
|
|
5104
5666
|
const response = await this.http.post(
|
|
5105
5667
|
`/v1/workflows/${encodeURIComponent(workflowId)}/run`,
|
|
5106
5668
|
{ mode: params.mode ?? "manual", input: params.input ?? {} },
|
|
5107
|
-
this.getCliRequestHeaders()
|
|
5669
|
+
{ ...this.getCliRequestHeaders(), "Idempotency-Key": params.idempotencyKey }
|
|
5108
5670
|
);
|
|
5109
5671
|
if (!response.ok || !response.data.run) {
|
|
5110
5672
|
throw new Error(`Workflow run failed with HTTP ${response.status}`);
|
|
@@ -5133,6 +5695,42 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5133
5695
|
}
|
|
5134
5696
|
return response.data.run;
|
|
5135
5697
|
}
|
|
5698
|
+
async cancelWorkflowRun(workflowId, runId) {
|
|
5699
|
+
this.requireSession();
|
|
5700
|
+
const response = await this.http.post(
|
|
5701
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/runs/${encodeURIComponent(runId)}/cancel`,
|
|
5702
|
+
{},
|
|
5703
|
+
this.getCliRequestHeaders()
|
|
5704
|
+
);
|
|
5705
|
+
if (!response.ok || response.data.status !== "cancellation_requested" && response.data.status !== "cancelled") {
|
|
5706
|
+
throw new Error(`Workflow run cancellation failed with HTTP ${response.status}`);
|
|
5707
|
+
}
|
|
5708
|
+
return response.data;
|
|
5709
|
+
}
|
|
5710
|
+
async testWorkflowStep(workflowId, stepId, params = {}) {
|
|
5711
|
+
this.requireSession();
|
|
5712
|
+
const response = await this.http.post(
|
|
5713
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/steps/${encodeURIComponent(stepId)}/test`,
|
|
5714
|
+
{ input: params.input ?? {}, confirmed: params.confirmed === true },
|
|
5715
|
+
this.getCliRequestHeaders()
|
|
5716
|
+
);
|
|
5717
|
+
if (!response.ok || !response.data.run) {
|
|
5718
|
+
throw new Error(`Workflow step test failed with HTTP ${response.status}`);
|
|
5719
|
+
}
|
|
5720
|
+
return response.data.run;
|
|
5721
|
+
}
|
|
5722
|
+
async respondToWorkflowRun(workflowId, runId, stepId, input) {
|
|
5723
|
+
this.requireSession();
|
|
5724
|
+
const response = await this.http.post(
|
|
5725
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/runs/${encodeURIComponent(runId)}/respond`,
|
|
5726
|
+
{ step_id: stepId, input },
|
|
5727
|
+
this.getCliRequestHeaders()
|
|
5728
|
+
);
|
|
5729
|
+
if (!response.ok || !response.data.run) {
|
|
5730
|
+
throw new Error(`Workflow response failed with HTTP ${response.status}`);
|
|
5731
|
+
}
|
|
5732
|
+
return response.data.run;
|
|
5733
|
+
}
|
|
5136
5734
|
async listWorkflowCapabilities() {
|
|
5137
5735
|
this.requireSession();
|
|
5138
5736
|
const response = await this.http.get(
|
|
@@ -5144,6 +5742,114 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5144
5742
|
}
|
|
5145
5743
|
return response.data.capabilities ?? [];
|
|
5146
5744
|
}
|
|
5745
|
+
async upsertWorkflowTemplateProjection(workflowId, params) {
|
|
5746
|
+
this.requireSession();
|
|
5747
|
+
const response = await this.http.put(
|
|
5748
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/template-projection`,
|
|
5749
|
+
{
|
|
5750
|
+
template_id: params.templateId,
|
|
5751
|
+
source_version: params.sourceVersion,
|
|
5752
|
+
ciphertext: params.ciphertext,
|
|
5753
|
+
ciphertext_checksum: params.ciphertextChecksum,
|
|
5754
|
+
owner_wrapped_key: params.ownerWrappedKey,
|
|
5755
|
+
projection_schema_version: params.projectionSchemaVersion
|
|
5756
|
+
},
|
|
5757
|
+
this.getCliRequestHeaders()
|
|
5758
|
+
);
|
|
5759
|
+
if (!response.ok) {
|
|
5760
|
+
throw new Error(`Workflow template projection upsert failed with HTTP ${response.status}`);
|
|
5761
|
+
}
|
|
5762
|
+
return response.data;
|
|
5763
|
+
}
|
|
5764
|
+
async getPublicWorkflowTemplateProjection(templateId) {
|
|
5765
|
+
const response = await this.http.get(
|
|
5766
|
+
`/v1/workflows/template-projections/${encodeURIComponent(templateId)}`,
|
|
5767
|
+
this.getCliRequestHeaders()
|
|
5768
|
+
);
|
|
5769
|
+
if (!response.ok) {
|
|
5770
|
+
throw new Error(`Workflow template projection retrieval failed with HTTP ${response.status}`);
|
|
5771
|
+
}
|
|
5772
|
+
return response.data;
|
|
5773
|
+
}
|
|
5774
|
+
async revokeWorkflowTemplateProjection(workflowId) {
|
|
5775
|
+
this.requireSession();
|
|
5776
|
+
const response = await this.http.post(
|
|
5777
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/template-projection/revoke`,
|
|
5778
|
+
{},
|
|
5779
|
+
this.getCliRequestHeaders()
|
|
5780
|
+
);
|
|
5781
|
+
if (!response.ok) {
|
|
5782
|
+
throw new Error(`Workflow template projection revoke failed with HTTP ${response.status}`);
|
|
5783
|
+
}
|
|
5784
|
+
return response.data;
|
|
5785
|
+
}
|
|
5786
|
+
async unrevokeWorkflowTemplateProjection(workflowId) {
|
|
5787
|
+
this.requireSession();
|
|
5788
|
+
const response = await this.http.post(
|
|
5789
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/template-projection/unrevoke`,
|
|
5790
|
+
{},
|
|
5791
|
+
this.getCliRequestHeaders()
|
|
5792
|
+
);
|
|
5793
|
+
if (!response.ok) {
|
|
5794
|
+
throw new Error(`Workflow template projection unrevoke failed with HTTP ${response.status}`);
|
|
5795
|
+
}
|
|
5796
|
+
return response.data;
|
|
5797
|
+
}
|
|
5798
|
+
async completeImportedWorkflowBinding(workflowId, params) {
|
|
5799
|
+
this.requireSession();
|
|
5800
|
+
const response = await this.http.post(
|
|
5801
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/binding-requirements/complete`,
|
|
5802
|
+
{ type: params.type, node_id: params.nodeId },
|
|
5803
|
+
this.getCliRequestHeaders()
|
|
5804
|
+
);
|
|
5805
|
+
if (!response.ok) {
|
|
5806
|
+
throw new Error(`Workflow imported binding completion failed with HTTP ${response.status}`);
|
|
5807
|
+
}
|
|
5808
|
+
return response.data;
|
|
5809
|
+
}
|
|
5810
|
+
async createWorkflowTemplateShortUrl(params) {
|
|
5811
|
+
this.requireSession();
|
|
5812
|
+
const response = await this.http.post(
|
|
5813
|
+
"/v1/share/short-url",
|
|
5814
|
+
{
|
|
5815
|
+
token: params.token,
|
|
5816
|
+
encrypted_url: params.encryptedUrl,
|
|
5817
|
+
content_type: "workflow_template",
|
|
5818
|
+
content_id: params.templateId,
|
|
5819
|
+
password_protected: params.passwordProtected ?? false,
|
|
5820
|
+
...params.ttlSeconds !== void 0 ? { ttl_seconds: params.ttlSeconds } : {}
|
|
5821
|
+
},
|
|
5822
|
+
this.getCliRequestHeaders()
|
|
5823
|
+
);
|
|
5824
|
+
if (!response.ok) {
|
|
5825
|
+
throw new Error(`Workflow template short URL create failed with HTTP ${response.status}`);
|
|
5826
|
+
}
|
|
5827
|
+
return response.data;
|
|
5828
|
+
}
|
|
5829
|
+
async revokeShortUrl(token) {
|
|
5830
|
+
this.requireSession();
|
|
5831
|
+
const response = await this.http.delete(
|
|
5832
|
+
`/v1/share/short-url/${encodeURIComponent(token)}`,
|
|
5833
|
+
void 0,
|
|
5834
|
+
this.getCliRequestHeaders()
|
|
5835
|
+
);
|
|
5836
|
+
if (!response.ok) {
|
|
5837
|
+
throw new Error(`Short URL revoke failed with HTTP ${response.status}`);
|
|
5838
|
+
}
|
|
5839
|
+
return response.data;
|
|
5840
|
+
}
|
|
5841
|
+
async importWorkflowTemplate(payload) {
|
|
5842
|
+
this.requireSession();
|
|
5843
|
+
const response = await this.http.post(
|
|
5844
|
+
"/v1/workflows/template-import",
|
|
5845
|
+
payload,
|
|
5846
|
+
this.getCliRequestHeaders()
|
|
5847
|
+
);
|
|
5848
|
+
if (!response.ok || !response.data.workflow) {
|
|
5849
|
+
throw new Error(`Workflow template import failed with HTTP ${response.status}`);
|
|
5850
|
+
}
|
|
5851
|
+
return response.data.workflow;
|
|
5852
|
+
}
|
|
5147
5853
|
async startWorkflowInput(params) {
|
|
5148
5854
|
this.requireSession();
|
|
5149
5855
|
const response = await this.http.post(
|
|
@@ -5290,6 +5996,22 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5290
5996
|
}
|
|
5291
5997
|
return response.data.task;
|
|
5292
5998
|
}
|
|
5999
|
+
async extractUserTaskProposals(input) {
|
|
6000
|
+
const response = await this.http.post(
|
|
6001
|
+
"/v1/user-tasks/extract",
|
|
6002
|
+
{
|
|
6003
|
+
corrected_text: input.correctedText,
|
|
6004
|
+
mode: input.mode ?? "create",
|
|
6005
|
+
context_chat_id: input.contextChatId ?? null,
|
|
6006
|
+
project_ids: input.projectIds ?? []
|
|
6007
|
+
},
|
|
6008
|
+
this.getCliRequestHeaders()
|
|
6009
|
+
);
|
|
6010
|
+
if (!response.ok || !Array.isArray(response.data.proposed_tasks)) {
|
|
6011
|
+
throw new Error(`Failed to extract task proposals (HTTP ${response.status})`);
|
|
6012
|
+
}
|
|
6013
|
+
return response.data.proposed_tasks;
|
|
6014
|
+
}
|
|
5293
6015
|
async updateUserTask(taskId, input) {
|
|
5294
6016
|
this.requireSession();
|
|
5295
6017
|
const response = await this.http.patch(
|
|
@@ -5314,6 +6036,54 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5314
6036
|
}
|
|
5315
6037
|
return response.data.task;
|
|
5316
6038
|
}
|
|
6039
|
+
async deleteUserTask(taskId) {
|
|
6040
|
+
this.requireSession();
|
|
6041
|
+
const response = await this.http.delete(
|
|
6042
|
+
`/v1/user-tasks/${encodeURIComponent(taskId)}`,
|
|
6043
|
+
void 0,
|
|
6044
|
+
this.getCliRequestHeaders()
|
|
6045
|
+
);
|
|
6046
|
+
if (!response.ok) {
|
|
6047
|
+
throw new Error(`User task delete failed with HTTP ${response.status}`);
|
|
6048
|
+
}
|
|
6049
|
+
return response.data;
|
|
6050
|
+
}
|
|
6051
|
+
async completeUserTask(taskId, input = {}) {
|
|
6052
|
+
return this.postUserTaskAction(taskId, "complete", input);
|
|
6053
|
+
}
|
|
6054
|
+
async blockUserTask(taskId, input = {}) {
|
|
6055
|
+
return this.postUserTaskAction(taskId, "block", input);
|
|
6056
|
+
}
|
|
6057
|
+
async unblockUserTask(taskId, input = {}) {
|
|
6058
|
+
return this.postUserTaskAction(taskId, "unblock", input);
|
|
6059
|
+
}
|
|
6060
|
+
async skipUserTask(taskId, input = {}) {
|
|
6061
|
+
return this.postUserTaskAction(taskId, "skip", input);
|
|
6062
|
+
}
|
|
6063
|
+
async reorderUserTasks(input) {
|
|
6064
|
+
this.requireSession();
|
|
6065
|
+
const response = await this.http.post(
|
|
6066
|
+
"/v1/user-tasks/reorder",
|
|
6067
|
+
input,
|
|
6068
|
+
this.getCliRequestHeaders()
|
|
6069
|
+
);
|
|
6070
|
+
if (!response.ok) {
|
|
6071
|
+
throw new Error(`User task reorder failed with HTTP ${response.status}`);
|
|
6072
|
+
}
|
|
6073
|
+
return response.data.tasks ?? [];
|
|
6074
|
+
}
|
|
6075
|
+
async postUserTaskAction(taskId, action, input = {}) {
|
|
6076
|
+
this.requireSession();
|
|
6077
|
+
const response = await this.http.post(
|
|
6078
|
+
`/v1/user-tasks/${encodeURIComponent(taskId)}/${encodeURIComponent(action)}`,
|
|
6079
|
+
input,
|
|
6080
|
+
this.getCliRequestHeaders()
|
|
6081
|
+
);
|
|
6082
|
+
if (!response.ok || !response.data.task) {
|
|
6083
|
+
throw new Error(`User task ${action} failed with HTTP ${response.status}`);
|
|
6084
|
+
}
|
|
6085
|
+
return response.data.task;
|
|
6086
|
+
}
|
|
5317
6087
|
// -------------------------------------------------------------------------
|
|
5318
6088
|
// User plans
|
|
5319
6089
|
// -------------------------------------------------------------------------
|
|
@@ -6194,7 +6964,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
6194
6964
|
const session = this.requireSession();
|
|
6195
6965
|
const masterKey = base64ToBytes(session.masterKeyExportedB64);
|
|
6196
6966
|
const cache = await this.ensureSynced();
|
|
6197
|
-
const { createHash:
|
|
6967
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
6198
6968
|
const embed = cache.embeds.find(
|
|
6199
6969
|
(e) => String(e.embed_id ?? "").startsWith(embedIdOrShort) || String(e.id ?? "").startsWith(embedIdOrShort)
|
|
6200
6970
|
);
|
|
@@ -6202,7 +6972,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
6202
6972
|
throw new Error(`Embed '${embedIdOrShort}' not found in local cache.`);
|
|
6203
6973
|
}
|
|
6204
6974
|
const embedId = String(embed.embed_id ?? embed.id ?? "");
|
|
6205
|
-
const hashedEmbedId =
|
|
6975
|
+
const hashedEmbedId = createHash8("sha256").update(embedId).digest("hex");
|
|
6206
6976
|
const embedKeyBytes = await this.resolveEmbedKey(
|
|
6207
6977
|
cache,
|
|
6208
6978
|
masterKey,
|
|
@@ -6262,8 +7032,8 @@ Required: ${schema.required.join(", ")}`
|
|
|
6262
7032
|
if (!embed) {
|
|
6263
7033
|
throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
|
|
6264
7034
|
}
|
|
6265
|
-
const { createHash:
|
|
6266
|
-
const hashedEmbedId =
|
|
7035
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
7036
|
+
const hashedEmbedId = createHash8("sha256").update(embedId).digest("hex");
|
|
6267
7037
|
const embedKey = await this.resolveEmbedKey(
|
|
6268
7038
|
cache,
|
|
6269
7039
|
masterKey,
|
|
@@ -6357,8 +7127,8 @@ Required: ${schema.required.join(", ")}`
|
|
|
6357
7127
|
if (!embed) {
|
|
6358
7128
|
throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
|
|
6359
7129
|
}
|
|
6360
|
-
const { createHash:
|
|
6361
|
-
const hashedEmbedId =
|
|
7130
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
7131
|
+
const hashedEmbedId = createHash8("sha256").update(embedId).digest("hex");
|
|
6362
7132
|
const embedKey = await this.resolveEmbedKey(
|
|
6363
7133
|
cache,
|
|
6364
7134
|
masterKey,
|
|
@@ -6550,11 +7320,11 @@ Required: ${schema.required.join(", ")}`
|
|
|
6550
7320
|
* so every WebSocket usage gets a fresh HMAC token automatically.
|
|
6551
7321
|
*/
|
|
6552
7322
|
async openWsClient() {
|
|
6553
|
-
await this.refreshWsToken();
|
|
7323
|
+
const ownerId = await this.refreshWsToken();
|
|
6554
7324
|
const session = this.requireSession();
|
|
6555
7325
|
const ws = this.makeWsClient(session);
|
|
6556
7326
|
await ws.open();
|
|
6557
|
-
return { ws, session };
|
|
7327
|
+
return { ws, session, ownerId };
|
|
6558
7328
|
}
|
|
6559
7329
|
/**
|
|
6560
7330
|
* Refresh the ws_token by calling /auth/session.
|
|
@@ -6571,7 +7341,9 @@ Required: ${schema.required.join(", ")}`
|
|
|
6571
7341
|
}
|
|
6572
7342
|
session.cookies = this.http.getCookieMap();
|
|
6573
7343
|
saveSession(session);
|
|
7344
|
+
return typeof res.data.user?.id === "string" ? res.data.user.id : typeof res.data.user?.user_id === "string" ? res.data.user.user_id : null;
|
|
6574
7345
|
} catch {
|
|
7346
|
+
return null;
|
|
6575
7347
|
}
|
|
6576
7348
|
}
|
|
6577
7349
|
/**
|
|
@@ -6616,6 +7388,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
6616
7388
|
const embedKeys = [];
|
|
6617
7389
|
let newChatSuggestions = [];
|
|
6618
7390
|
let totalChatCount = 0;
|
|
7391
|
+
let reconciliation = { authoritative: false };
|
|
6619
7392
|
const pendingAIResponses = [];
|
|
6620
7393
|
try {
|
|
6621
7394
|
ws.send("phased_sync_request", {
|
|
@@ -6642,6 +7415,11 @@ Required: ${schema.required.join(", ")}`
|
|
|
6642
7415
|
} else if (frame.type === "phase_2_last_20_chats_ready") {
|
|
6643
7416
|
const p = frame.payload;
|
|
6644
7417
|
totalChatCount = p.total_chat_count ?? 0;
|
|
7418
|
+
reconciliation = {
|
|
7419
|
+
authoritative: p.authoritative === true,
|
|
7420
|
+
authoritative_chat_ids: p.authoritative_chat_ids,
|
|
7421
|
+
deleted_chat_ids: p.deleted_chat_ids
|
|
7422
|
+
};
|
|
6645
7423
|
for (const wrapper of p.chats ?? []) {
|
|
6646
7424
|
const details = wrapper.chat_details;
|
|
6647
7425
|
if (!details || typeof details.id !== "string") continue;
|
|
@@ -6721,12 +7499,21 @@ Required: ${schema.required.join(", ")}`
|
|
|
6721
7499
|
}
|
|
6722
7500
|
}
|
|
6723
7501
|
}
|
|
7502
|
+
const reconciledChats = reconcileAuthoritativeChats(chats, reconciliation);
|
|
7503
|
+
chats.splice(0, chats.length, ...reconciledChats);
|
|
7504
|
+
if (reconciliation.deleted_chat_ids?.length) {
|
|
7505
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
7506
|
+
const deletedHashes = new Set(
|
|
7507
|
+
reconciliation.deleted_chat_ids.map((id) => createHash8("sha256").update(id).digest("hex"))
|
|
7508
|
+
);
|
|
7509
|
+
const keptEmbeds = embeds.filter((embed) => !deletedHashes.has(String(embed.hashed_chat_id ?? "")));
|
|
7510
|
+
const keptKeys = embedKeys.filter((key) => !deletedHashes.has(String(key.hashed_chat_id ?? "")));
|
|
7511
|
+
embeds.splice(0, embeds.length, ...keptEmbeds);
|
|
7512
|
+
embedKeys.splice(0, embedKeys.length, ...keptKeys);
|
|
7513
|
+
}
|
|
6724
7514
|
chats.sort(
|
|
6725
7515
|
(a, b) => (typeof b.details.last_edited_overall_timestamp === "number" ? b.details.last_edited_overall_timestamp : 0) - (typeof a.details.last_edited_overall_timestamp === "number" ? a.details.last_edited_overall_timestamp : 0)
|
|
6726
7516
|
);
|
|
6727
|
-
if (totalChatCount > 0 && chats.length > totalChatCount) {
|
|
6728
|
-
chats.length = totalChatCount;
|
|
6729
|
-
}
|
|
6730
7517
|
try {
|
|
6731
7518
|
await this.persistPendingAIResponsesFromSync(ws, chats, pendingAIResponses);
|
|
6732
7519
|
} finally {
|
|
@@ -6979,10 +7766,10 @@ function printLogo() {
|
|
|
6979
7766
|
|
|
6980
7767
|
// src/cli.ts
|
|
6981
7768
|
import { createInterface as createInterface4 } from "readline/promises";
|
|
6982
|
-
import { realpathSync, writeFileSync as writeFileSync6 } from "fs";
|
|
7769
|
+
import { readFileSync as readFileSync9, realpathSync, writeFileSync as writeFileSync6 } from "fs";
|
|
6983
7770
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6984
7771
|
import { basename as basename3, dirname as dirname3 } from "path";
|
|
6985
|
-
import { randomUUID as
|
|
7772
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
6986
7773
|
import WebSocket2 from "ws";
|
|
6987
7774
|
|
|
6988
7775
|
// ../secret-scanner/src/registry.ts
|
|
@@ -9534,6 +10321,22 @@ import { createInterface as createPromptInterface } from "readline/promises";
|
|
|
9534
10321
|
import { homedir as homedir5 } from "os";
|
|
9535
10322
|
import { dirname, join as join3, resolve as resolve3 } from "path";
|
|
9536
10323
|
|
|
10324
|
+
// src/branding.ts
|
|
10325
|
+
var OPENMATES_WORDMARK = "OPENMATES";
|
|
10326
|
+
var OPENMATES_ASCII_LARGE = [
|
|
10327
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588",
|
|
10328
|
+
"\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ",
|
|
10329
|
+
"\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588",
|
|
10330
|
+
"\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
10331
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588"
|
|
10332
|
+
];
|
|
10333
|
+
function openMatesAsciiLogo(width = 100) {
|
|
10334
|
+
return width >= 86 ? [...OPENMATES_ASCII_LARGE, OPENMATES_WORDMARK] : [OPENMATES_WORDMARK];
|
|
10335
|
+
}
|
|
10336
|
+
function renderOpenMatesAsciiLogo(width = 100) {
|
|
10337
|
+
return openMatesAsciiLogo(width).join("\n");
|
|
10338
|
+
}
|
|
10339
|
+
|
|
9537
10340
|
// src/support.ts
|
|
9538
10341
|
var SUPPORT_URL = "https://openmates.org/#settings/support";
|
|
9539
10342
|
var SUPPORT_TITLE = "Support OpenMates development";
|
|
@@ -10446,6 +11249,38 @@ function missingRequiredEnvKeys(installPath, role) {
|
|
|
10446
11249
|
const env = readEnvMap(installPath);
|
|
10447
11250
|
return requiredRuntimeEnvKeys(role).filter((key) => !env[key]);
|
|
10448
11251
|
}
|
|
11252
|
+
var LONG_RUNNING_SERVER_COMMANDS = /* @__PURE__ */ new Set([
|
|
11253
|
+
"install",
|
|
11254
|
+
"start",
|
|
11255
|
+
"stop",
|
|
11256
|
+
"restart",
|
|
11257
|
+
"update",
|
|
11258
|
+
"preflight",
|
|
11259
|
+
"caddy",
|
|
11260
|
+
"backup",
|
|
11261
|
+
"restore",
|
|
11262
|
+
"reset",
|
|
11263
|
+
"make-admin",
|
|
11264
|
+
"ai",
|
|
11265
|
+
"uninstall"
|
|
11266
|
+
]);
|
|
11267
|
+
function shouldPrintServerCommandStart(subcommand, rest, flags) {
|
|
11268
|
+
if (flags.json === true || !LONG_RUNNING_SERVER_COMMANDS.has(subcommand)) return false;
|
|
11269
|
+
if (subcommand === "backup" && rest[0] === "list") return false;
|
|
11270
|
+
if (subcommand === "update" && rest[0] === "status") return false;
|
|
11271
|
+
if (subcommand === "ai" && rest[0] === "models" && (rest[1] === "list" || rest[1] === "remove")) return false;
|
|
11272
|
+
if (subcommand === "caddy" && (rest[0] === "status" || rest[0] === "diff")) return false;
|
|
11273
|
+
return true;
|
|
11274
|
+
}
|
|
11275
|
+
function printServerCommandStart(subcommand, rest, flags) {
|
|
11276
|
+
const command = ["server", subcommand, ...rest].join(" ");
|
|
11277
|
+
console.log(renderOpenMatesAsciiLogo());
|
|
11278
|
+
console.log("");
|
|
11279
|
+
console.log(`Running OpenMates ${command}...`);
|
|
11280
|
+
if (typeof flags.path === "string") console.log(`Path: ${resolve3(flags.path)}`);
|
|
11281
|
+
if (typeof flags.role === "string") console.log(`Role: ${flags.role}`);
|
|
11282
|
+
console.log("");
|
|
11283
|
+
}
|
|
10449
11284
|
function vaultCheckContainerForRole(role) {
|
|
10450
11285
|
if (role === "core") return "api";
|
|
10451
11286
|
if (role === "upload") return "app-uploads";
|
|
@@ -11966,6 +12801,9 @@ async function handleServer(subcommand, rest, flags) {
|
|
|
11966
12801
|
printServerHelp();
|
|
11967
12802
|
return;
|
|
11968
12803
|
}
|
|
12804
|
+
if (shouldPrintServerCommandStart(subcommand, rest, flags)) {
|
|
12805
|
+
printServerCommandStart(subcommand, rest, flags);
|
|
12806
|
+
}
|
|
11969
12807
|
switch (subcommand) {
|
|
11970
12808
|
case "status":
|
|
11971
12809
|
return serverStatus(flags);
|
|
@@ -28546,6 +29384,11 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
28546
29384
|
}
|
|
28547
29385
|
}
|
|
28548
29386
|
},
|
|
29387
|
+
models3d: {
|
|
29388
|
+
generate: {
|
|
29389
|
+
text: "Generate 3D model"
|
|
29390
|
+
}
|
|
29391
|
+
},
|
|
28549
29392
|
music: {
|
|
28550
29393
|
generate: {
|
|
28551
29394
|
text: "Generate",
|
|
@@ -30885,6 +31728,27 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
30885
31728
|
save: {
|
|
30886
31729
|
text: "Save"
|
|
30887
31730
|
},
|
|
31731
|
+
detail_title_hint: {
|
|
31732
|
+
text: "Enter to save \xB7 Escape to cancel"
|
|
31733
|
+
},
|
|
31734
|
+
detail_description_hint: {
|
|
31735
|
+
text: "Ctrl/Command+Enter to save \xB7 Escape to cancel"
|
|
31736
|
+
},
|
|
31737
|
+
detail_save_error: {
|
|
31738
|
+
text: "Your changes were not saved."
|
|
31739
|
+
},
|
|
31740
|
+
detail_loading: {
|
|
31741
|
+
text: "Loading {item}\u2026"
|
|
31742
|
+
},
|
|
31743
|
+
detail_load_error: {
|
|
31744
|
+
text: "{item} could not be loaded."
|
|
31745
|
+
},
|
|
31746
|
+
detail_untitled: {
|
|
31747
|
+
text: "Untitled {item}"
|
|
31748
|
+
},
|
|
31749
|
+
detail_items_count: {
|
|
31750
|
+
text: "{count} items"
|
|
31751
|
+
},
|
|
30888
31752
|
settings: {
|
|
30889
31753
|
text: "Settings"
|
|
30890
31754
|
},
|
|
@@ -36515,6 +37379,22 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
36515
37379
|
},
|
|
36516
37380
|
privacy: {
|
|
36517
37381
|
providers: {
|
|
37382
|
+
model_generation: {
|
|
37383
|
+
heading: {
|
|
37384
|
+
text: "3D model generation (only when you use a 3D model skill)"
|
|
37385
|
+
},
|
|
37386
|
+
description: {
|
|
37387
|
+
text: "This provider is only used when you invoke a 3D model generation skill. If you never use the 3D models app, none of your data is shared with it."
|
|
37388
|
+
},
|
|
37389
|
+
hi3d: {
|
|
37390
|
+
heading: {
|
|
37391
|
+
text: "Hi3D (3D model generation)"
|
|
37392
|
+
},
|
|
37393
|
+
description: {
|
|
37394
|
+
text: "Hi3D receives reference images you supply or images generated from your text prompt, plus generation settings. Its Terms license Inputs and Outputs for AI model improvement and training. Hi3D publishes no exact API retention period or per-task deletion endpoint; its policy provides account and personal-data deletion rights. OpenMates requires your confirmation before first use."
|
|
37395
|
+
}
|
|
37396
|
+
}
|
|
37397
|
+
},
|
|
36518
37398
|
fitness: {
|
|
36519
37399
|
heading: {
|
|
36520
37400
|
text: "Fitness search (only when you use the fitness skill)"
|
|
@@ -40605,6 +41485,42 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
40605
41485
|
description: {
|
|
40606
41486
|
text: "Manage your credits, purchases, and what you used credits for."
|
|
40607
41487
|
},
|
|
41488
|
+
apple_support: {
|
|
41489
|
+
text: "Support OpenMates"
|
|
41490
|
+
},
|
|
41491
|
+
apple_usage_details: {
|
|
41492
|
+
text: "View usage details"
|
|
41493
|
+
},
|
|
41494
|
+
apple_purchased_gift_cards: {
|
|
41495
|
+
text: "Purchased"
|
|
41496
|
+
},
|
|
41497
|
+
apple_no_gift_cards: {
|
|
41498
|
+
text: "No gift cards yet"
|
|
41499
|
+
},
|
|
41500
|
+
apple_gift_card_purchase_unavailable: {
|
|
41501
|
+
text: "Gift card purchase is unavailable until your account email is fully synchronized."
|
|
41502
|
+
},
|
|
41503
|
+
apple_support_amount: {
|
|
41504
|
+
text: "Amount in EUR"
|
|
41505
|
+
},
|
|
41506
|
+
apple_create_bank_transfer: {
|
|
41507
|
+
text: "Create bank transfer"
|
|
41508
|
+
},
|
|
41509
|
+
apple_invalid_support_amount: {
|
|
41510
|
+
text: "Enter a valid amount and make sure your account has an email address."
|
|
41511
|
+
},
|
|
41512
|
+
apple_fulfillment_delayed: {
|
|
41513
|
+
text: "Your purchase is awaiting fulfillment. It remains retryable and will not be marked complete yet."
|
|
41514
|
+
},
|
|
41515
|
+
apple_product_not_found: {
|
|
41516
|
+
text: "This product is not available in the App Store."
|
|
41517
|
+
},
|
|
41518
|
+
apple_credits_count: {
|
|
41519
|
+
text: "{credits} credits"
|
|
41520
|
+
},
|
|
41521
|
+
apple_bank_transfer_reference: {
|
|
41522
|
+
text: "Bank transfer reference: {reference}"
|
|
41523
|
+
},
|
|
40608
41524
|
text: "Billing & Usage",
|
|
40609
41525
|
title: {
|
|
40610
41526
|
text: "Billing & Credits"
|
|
@@ -41385,6 +42301,12 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
41385
42301
|
learning_mode_disabled: {
|
|
41386
42302
|
text: "Learning Mode disabled."
|
|
41387
42303
|
},
|
|
42304
|
+
learning_mode_attempts_remaining: {
|
|
42305
|
+
text: "{count} attempts remaining."
|
|
42306
|
+
},
|
|
42307
|
+
learning_mode_locked: {
|
|
42308
|
+
text: "Deactivation is locked for 24 hours after too many incorrect attempts."
|
|
42309
|
+
},
|
|
41388
42310
|
learning_mode_age_group_prompt: {
|
|
41389
42311
|
text: "Learner age group: under_10, 10_12, 13_15, 16_18, or adult"
|
|
41390
42312
|
},
|
|
@@ -42629,6 +43551,73 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
42629
43551
|
},
|
|
42630
43552
|
share_debug_logs_admin_notice: {
|
|
42631
43553
|
text: "You are an admin. Therefore your debug logs are automatically shared already. Regular users can turn on/off debug logs sharing here."
|
|
43554
|
+
},
|
|
43555
|
+
native: {
|
|
43556
|
+
save_error: {
|
|
43557
|
+
text: "Could not save this privacy setting. Please try again."
|
|
43558
|
+
},
|
|
43559
|
+
master_key_unavailable: {
|
|
43560
|
+
text: "Your encryption key is unavailable. Sign in again to manage this setting."
|
|
43561
|
+
},
|
|
43562
|
+
debug_session: {
|
|
43563
|
+
description: {
|
|
43564
|
+
text: "Temporarily share sanitized native diagnostics with support. Message content, encryption keys, credentials, file names, and personal data are never shared."
|
|
43565
|
+
},
|
|
43566
|
+
duration: {
|
|
43567
|
+
text: "Duration"
|
|
43568
|
+
},
|
|
43569
|
+
start: {
|
|
43570
|
+
text: "Start sharing debug logs"
|
|
43571
|
+
},
|
|
43572
|
+
activating: {
|
|
43573
|
+
text: "Activating..."
|
|
43574
|
+
},
|
|
43575
|
+
active: {
|
|
43576
|
+
text: "Active debug session"
|
|
43577
|
+
},
|
|
43578
|
+
id: {
|
|
43579
|
+
text: "Your Debug ID"
|
|
43580
|
+
},
|
|
43581
|
+
share_hint: {
|
|
43582
|
+
text: "Share this ID with the support team so they can find your sanitized logs."
|
|
43583
|
+
},
|
|
43584
|
+
stop: {
|
|
43585
|
+
text: "Stop sharing"
|
|
43586
|
+
},
|
|
43587
|
+
stopping: {
|
|
43588
|
+
text: "Stopping..."
|
|
43589
|
+
},
|
|
43590
|
+
no_expiry: {
|
|
43591
|
+
text: "Active until manually stopped"
|
|
43592
|
+
},
|
|
43593
|
+
error: {
|
|
43594
|
+
text: "Could not update the debug sharing session. Please try again."
|
|
43595
|
+
},
|
|
43596
|
+
duration_5m: {
|
|
43597
|
+
text: "5 minutes"
|
|
43598
|
+
},
|
|
43599
|
+
duration_1h: {
|
|
43600
|
+
text: "1 hour"
|
|
43601
|
+
},
|
|
43602
|
+
duration_3d: {
|
|
43603
|
+
text: "3 days"
|
|
43604
|
+
},
|
|
43605
|
+
duration_7d: {
|
|
43606
|
+
text: "7 days"
|
|
43607
|
+
},
|
|
43608
|
+
duration_none: {
|
|
43609
|
+
text: "No time limit"
|
|
43610
|
+
},
|
|
43611
|
+
minutes_remaining: {
|
|
43612
|
+
text: "{count}m remaining"
|
|
43613
|
+
},
|
|
43614
|
+
hours_remaining: {
|
|
43615
|
+
text: "{count}h remaining"
|
|
43616
|
+
},
|
|
43617
|
+
days_remaining: {
|
|
43618
|
+
text: "{count}d remaining"
|
|
43619
|
+
}
|
|
43620
|
+
}
|
|
42632
43621
|
}
|
|
42633
43622
|
},
|
|
42634
43623
|
terms_and_conditions: {
|
|
@@ -44439,6 +45428,21 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
44439
45428
|
pair_server_use_custom: {
|
|
44440
45429
|
text: "Use custom"
|
|
44441
45430
|
},
|
|
45431
|
+
pair_self_hosted_edition: {
|
|
45432
|
+
text: "Connect self-hosted edition"
|
|
45433
|
+
},
|
|
45434
|
+
pair_self_hosted_placeholder: {
|
|
45435
|
+
text: "Server URL"
|
|
45436
|
+
},
|
|
45437
|
+
pair_self_hosted_connect: {
|
|
45438
|
+
text: "Connect"
|
|
45439
|
+
},
|
|
45440
|
+
pair_self_hosted_invalid_url: {
|
|
45441
|
+
text: "Enter a valid HTTPS server URL."
|
|
45442
|
+
},
|
|
45443
|
+
pair_use_production: {
|
|
45444
|
+
text: "Use OpenMates.org"
|
|
45445
|
+
},
|
|
44442
45446
|
pair_connect_apple_watch_title: {
|
|
44443
45447
|
text: "Connect Apple Watch"
|
|
44444
45448
|
},
|
|
@@ -46868,22 +47872,6 @@ var TuiTerminal = class {
|
|
|
46868
47872
|
}
|
|
46869
47873
|
};
|
|
46870
47874
|
|
|
46871
|
-
// src/branding.ts
|
|
46872
|
-
var OPENMATES_WORDMARK = "OPENMATES";
|
|
46873
|
-
var OPENMATES_ASCII_LARGE = [
|
|
46874
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588",
|
|
46875
|
-
"\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ",
|
|
46876
|
-
"\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588",
|
|
46877
|
-
"\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
46878
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588"
|
|
46879
|
-
];
|
|
46880
|
-
function openMatesAsciiLogo(width = 100) {
|
|
46881
|
-
return width >= 86 ? [...OPENMATES_ASCII_LARGE, OPENMATES_WORDMARK] : [OPENMATES_WORDMARK];
|
|
46882
|
-
}
|
|
46883
|
-
function renderOpenMatesAsciiLogo(width = 100) {
|
|
46884
|
-
return openMatesAsciiLogo(width).join("\n");
|
|
46885
|
-
}
|
|
46886
|
-
|
|
46887
47875
|
// src/tuiRenderer.ts
|
|
46888
47876
|
var MIN_WIDTH = 48;
|
|
46889
47877
|
var CONTENT_PREVIEW_LINES = 12;
|
|
@@ -46910,6 +47898,17 @@ function createInitialTuiState() {
|
|
|
46910
47898
|
selectedInterests: [],
|
|
46911
47899
|
examples: [],
|
|
46912
47900
|
activeExample: null,
|
|
47901
|
+
workflows: [],
|
|
47902
|
+
activeWorkflow: null,
|
|
47903
|
+
workflowRuns: [],
|
|
47904
|
+
tasks: [],
|
|
47905
|
+
activeTask: null,
|
|
47906
|
+
workflowTab: "graph",
|
|
47907
|
+
selectedWorkflowNodeIndex: 0,
|
|
47908
|
+
selectedWorkflowRunIndex: 0,
|
|
47909
|
+
expandedWorkflowNodeId: null,
|
|
47910
|
+
expandedWorkflowRunNodeId: null,
|
|
47911
|
+
workflowEdit: null,
|
|
46913
47912
|
messages: [],
|
|
46914
47913
|
status: null,
|
|
46915
47914
|
isBusy: false
|
|
@@ -46943,6 +47942,7 @@ Files:
|
|
|
46943
47942
|
|
|
46944
47943
|
More:
|
|
46945
47944
|
openmates apps list
|
|
47945
|
+
openmates workflows list
|
|
46946
47946
|
openmates mentions list
|
|
46947
47947
|
openmates embeds show <embed-id>
|
|
46948
47948
|
openmates help
|
|
@@ -46965,7 +47965,7 @@ function renderTuiFrame(state, width, height) {
|
|
|
46965
47965
|
const contentHeight = safeHeight - 4;
|
|
46966
47966
|
const body = renderBody(state, innerWidth);
|
|
46967
47967
|
const visible = sliceForScroll(body, contentHeight, state.scrollOffset, state.screen === "chat");
|
|
46968
|
-
const inputLine = state.screen === "interests" || state.screen === "examples" ? renderHintLine(state, innerWidth) : `> ${state.input || inputPlaceholder(state)}`;
|
|
47968
|
+
const inputLine = state.screen === "interests" || state.screen === "examples" || state.screen === "workflows" || state.screen === "workflow" || state.screen === "tasks" || state.screen === "task" ? renderHintLine(state, innerWidth) : `> ${state.input || inputPlaceholder(state)}`;
|
|
46969
47969
|
const lines = [
|
|
46970
47970
|
header2,
|
|
46971
47971
|
...padLines(visible, contentHeight, innerWidth).map((line) => boxed(line, innerWidth)),
|
|
@@ -46991,6 +47991,14 @@ function renderBody(state, width) {
|
|
|
46991
47991
|
return renderStatus(state, width);
|
|
46992
47992
|
case "embed":
|
|
46993
47993
|
return renderStatus(state, width);
|
|
47994
|
+
case "workflows":
|
|
47995
|
+
return renderWorkflows(state, width);
|
|
47996
|
+
case "workflow":
|
|
47997
|
+
return renderWorkflowDetail(state, width);
|
|
47998
|
+
case "tasks":
|
|
47999
|
+
return renderTasks(state, width);
|
|
48000
|
+
case "task":
|
|
48001
|
+
return renderTaskDetail(state, width);
|
|
46994
48002
|
case "start":
|
|
46995
48003
|
default:
|
|
46996
48004
|
return renderStart(width);
|
|
@@ -47012,7 +48020,7 @@ function renderStart(width) {
|
|
|
47012
48020
|
"Type @./notes.md, @~/Downloads/report.pdf, or @src/app.ts in your message.",
|
|
47013
48021
|
"Images, PDFs, audio, and code files are attached as encrypted embeds when you are signed in.",
|
|
47014
48022
|
"",
|
|
47015
|
-
"Shortcuts: /help /login /signup /examples /exit"
|
|
48023
|
+
"Shortcuts: /help /login /signup /examples /tasks /exit"
|
|
47016
48024
|
].flatMap((line) => wrap(line, width));
|
|
47017
48025
|
}
|
|
47018
48026
|
function renderHelp(width) {
|
|
@@ -47032,6 +48040,9 @@ function renderHelp(width) {
|
|
|
47032
48040
|
"",
|
|
47033
48041
|
"Commands",
|
|
47034
48042
|
" /examples Choose example chats",
|
|
48043
|
+
" /workflows List and run saved workflows",
|
|
48044
|
+
" /workflow <id> Open a workflow by ID",
|
|
48045
|
+
" /tasks Open your task workspace",
|
|
47035
48046
|
" /login Pair-auth login",
|
|
47036
48047
|
" /signup Leave TUI and run guided signup",
|
|
47037
48048
|
" /embed <id> Open an embed detail view",
|
|
@@ -47040,6 +48051,150 @@ function renderHelp(width) {
|
|
|
47040
48051
|
"Outside TUI: openmates --help, openmates chats --help, openmates apps --help"
|
|
47041
48052
|
].flatMap((line) => wrap(line, width));
|
|
47042
48053
|
}
|
|
48054
|
+
function renderTasks(state, width) {
|
|
48055
|
+
const lines = ["Tasks", ""];
|
|
48056
|
+
if (state.tasks.length === 0) {
|
|
48057
|
+
lines.push("No tasks found.", "Create one outside TUI with: openmates tasks create --title <title>");
|
|
48058
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
48059
|
+
}
|
|
48060
|
+
const visibleCount = Math.max(1, CONTENT_PREVIEW_LINES);
|
|
48061
|
+
const start = Math.max(0, Math.min(state.selectedIndex, state.tasks.length - visibleCount));
|
|
48062
|
+
for (let i = 0; i < state.tasks.slice(start, start + visibleCount).length; i += 1) {
|
|
48063
|
+
const absoluteIndex = start + i;
|
|
48064
|
+
const task = state.tasks[absoluteIndex];
|
|
48065
|
+
const cursor = absoluteIndex === state.selectedIndex ? ">" : " ";
|
|
48066
|
+
const assignee = task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
48067
|
+
lines.push(`${cursor} ${task.shortId} ${task.status} ${assignee} ${task.title}`);
|
|
48068
|
+
if (task.queueState !== "none") lines.push(` queue: ${task.queueState}`);
|
|
48069
|
+
if (task.description) lines.push(` ${task.description}`);
|
|
48070
|
+
lines.push("");
|
|
48071
|
+
}
|
|
48072
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
48073
|
+
}
|
|
48074
|
+
function renderTaskDetail(state, width) {
|
|
48075
|
+
const task = state.activeTask;
|
|
48076
|
+
if (!task) return renderTasks(state, width);
|
|
48077
|
+
const assignee = task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
48078
|
+
const lines = [
|
|
48079
|
+
`Task: ${task.shortId}`,
|
|
48080
|
+
`Title: ${task.title}`,
|
|
48081
|
+
`Status: ${task.status}`,
|
|
48082
|
+
`Assignee: ${assignee}`,
|
|
48083
|
+
`Queue: ${task.queueState}`,
|
|
48084
|
+
`ID: ${task.taskId}`,
|
|
48085
|
+
task.description ? `Description: ${task.description}` : null,
|
|
48086
|
+
task.primaryChatId ? `Chat: ${task.primaryChatId}` : null,
|
|
48087
|
+
task.linkedProjectIds.length > 0 ? `Projects: ${task.linkedProjectIds.join(", ")}` : null,
|
|
48088
|
+
task.blockedReasonCode ? `Blocked reason: ${task.blockedReasonCode}` : null,
|
|
48089
|
+
task.aiExecutionState ? `AI state: ${task.aiExecutionState}` : null,
|
|
48090
|
+
"",
|
|
48091
|
+
"Actions: c create, e edit, x delete, r reorder, s start, d done, b block, u unblock, k skip, Esc back"
|
|
48092
|
+
].filter((line) => line !== null);
|
|
48093
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
48094
|
+
}
|
|
48095
|
+
function renderWorkflows(state, width) {
|
|
48096
|
+
const lines = ["Workflows", ""];
|
|
48097
|
+
if (state.workflows.length === 0) {
|
|
48098
|
+
lines.push("No workflows found.", "Create one outside TUI with: openmates workflows create --file workflow.yml");
|
|
48099
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
48100
|
+
}
|
|
48101
|
+
const visibleCount = Math.max(1, CONTENT_PREVIEW_LINES);
|
|
48102
|
+
const start = Math.max(0, Math.min(state.selectedIndex, state.workflows.length - visibleCount));
|
|
48103
|
+
for (let i = 0; i < state.workflows.slice(start, start + visibleCount).length; i += 1) {
|
|
48104
|
+
const absoluteIndex = start + i;
|
|
48105
|
+
const workflow = state.workflows[absoluteIndex];
|
|
48106
|
+
const cursor = absoluteIndex === state.selectedIndex ? ">" : " ";
|
|
48107
|
+
const status = workflow.enabled ? "enabled" : "disabled";
|
|
48108
|
+
const lastRun = workflow.last_run_status ? ` last: ${workflow.last_run_status}` : "";
|
|
48109
|
+
lines.push(`${cursor} ${workflow.title} (${status})${lastRun}`);
|
|
48110
|
+
lines.push(` ${workflow.id}`);
|
|
48111
|
+
if (workflow.trigger_summary) lines.push(` ${workflow.trigger_summary}`);
|
|
48112
|
+
lines.push("");
|
|
48113
|
+
}
|
|
48114
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
48115
|
+
}
|
|
48116
|
+
function renderWorkflowDetail(state, width) {
|
|
48117
|
+
const workflow = state.activeWorkflow;
|
|
48118
|
+
if (!workflow) return renderWorkflows(state, width);
|
|
48119
|
+
const graphNodes = workflow.graph?.nodes ?? [];
|
|
48120
|
+
const selectedRun = state.workflowRuns[state.selectedWorkflowRunIndex] ?? null;
|
|
48121
|
+
const lines = [
|
|
48122
|
+
`Workflow: ${workflow.title}`,
|
|
48123
|
+
`ID: ${workflow.id}`,
|
|
48124
|
+
`Status: ${workflow.enabled ? "enabled" : "disabled"}`,
|
|
48125
|
+
workflow.trigger_summary ? `Trigger: ${workflow.trigger_summary}` : null,
|
|
48126
|
+
workflow.next_run_at ? `Next run: ${formatTimestamp(workflow.next_run_at)}` : null,
|
|
48127
|
+
workflow.last_run_status ? `Last run: ${workflow.last_run_status}` : null,
|
|
48128
|
+
"",
|
|
48129
|
+
state.workflowTab === "graph" ? "[Graph] Runs" : "Graph [Runs]",
|
|
48130
|
+
""
|
|
48131
|
+
].filter((line) => line !== null);
|
|
48132
|
+
if (state.workflowTab === "runs") {
|
|
48133
|
+
lines.push(...renderRunSelector(state, width), "");
|
|
48134
|
+
if (selectedRun) {
|
|
48135
|
+
lines.push(`Run graph: ${selectedRun.id} (${selectedRun.status})`);
|
|
48136
|
+
lines.push(...renderWorkflowGraph({ nodes: graphNodes, state, width, run: selectedRun }));
|
|
48137
|
+
} else {
|
|
48138
|
+
lines.push("No runs yet.");
|
|
48139
|
+
}
|
|
48140
|
+
} else {
|
|
48141
|
+
lines.push("Graph");
|
|
48142
|
+
lines.push(...renderWorkflowGraph({ nodes: graphNodes, state, width, run: null }));
|
|
48143
|
+
if (state.workflowEdit) lines.push("", `Editing ${state.workflowEdit.field}: ${state.workflowEdit.value}`);
|
|
48144
|
+
}
|
|
48145
|
+
if (state.status) lines.push("", state.status);
|
|
48146
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
48147
|
+
}
|
|
48148
|
+
function renderRunSelector(state, width) {
|
|
48149
|
+
if (state.workflowRuns.length === 0) return ["Runs", "No runs yet."];
|
|
48150
|
+
const lines = ["Runs"];
|
|
48151
|
+
const visibleRuns = state.workflowRuns.slice(0, 5);
|
|
48152
|
+
for (let index = 0; index < visibleRuns.length; index += 1) {
|
|
48153
|
+
const run = visibleRuns[index];
|
|
48154
|
+
const cursor = index === state.selectedWorkflowRunIndex ? ">" : " ";
|
|
48155
|
+
lines.push(`${cursor} ${run.id} ${run.status} ${formatTimestamp(run.started_at)}`);
|
|
48156
|
+
}
|
|
48157
|
+
return lines.map((line) => truncateVisible(line, width));
|
|
48158
|
+
}
|
|
48159
|
+
function renderWorkflowGraph(params) {
|
|
48160
|
+
const { nodes, state, run } = params;
|
|
48161
|
+
if (nodes.length === 0) return ["No graph nodes available."];
|
|
48162
|
+
const lines = [];
|
|
48163
|
+
const selectedIndex = state.workflowTab === "runs" ? selectedRunGraphNodeIndex(nodes, state) : state.selectedWorkflowNodeIndex;
|
|
48164
|
+
const expandedId = state.workflowTab === "runs" ? state.expandedWorkflowRunNodeId : state.expandedWorkflowNodeId;
|
|
48165
|
+
const nodeRunsById = new Map((run?.node_runs ?? []).map((nodeRun) => [nodeRun.node_id, nodeRun]));
|
|
48166
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
48167
|
+
const node = nodes[index];
|
|
48168
|
+
const nodeRun = nodeRunsById.get(node.id) ?? null;
|
|
48169
|
+
const cursor = index === selectedIndex ? ">" : " ";
|
|
48170
|
+
const status = nodeRun ? ` [${nodeRun.status}]` : "";
|
|
48171
|
+
lines.push(`${cursor} [${nodeTypeLabel(node.type)}] ${node.title ?? cardSummary(node)}${status}`);
|
|
48172
|
+
if (nodeRun?.output_summary) lines.push(` output: ${summarizeObject(nodeRun.output_summary)}`);
|
|
48173
|
+
if (nodeRun?.error_summary) lines.push(` error: ${nodeRun.error_summary}`);
|
|
48174
|
+
if (expandedId === node.id) {
|
|
48175
|
+
lines.push(...renderExpandedNode(node, nodeRun));
|
|
48176
|
+
}
|
|
48177
|
+
if (index < nodes.length - 1) lines.push(" |");
|
|
48178
|
+
}
|
|
48179
|
+
return lines;
|
|
48180
|
+
}
|
|
48181
|
+
function selectedRunGraphNodeIndex(nodes, state) {
|
|
48182
|
+
const run = state.workflowRuns[state.selectedWorkflowRunIndex];
|
|
48183
|
+
const nodeRun = run?.node_runs?.find((candidate) => candidate.node_id === state.expandedWorkflowRunNodeId);
|
|
48184
|
+
if (!nodeRun) return Math.min(state.selectedWorkflowNodeIndex, Math.max(0, nodes.length - 1));
|
|
48185
|
+
return Math.max(0, nodes.findIndex((node) => node.id === nodeRun.node_id));
|
|
48186
|
+
}
|
|
48187
|
+
function renderExpandedNode(node, nodeRun) {
|
|
48188
|
+
const lines = [
|
|
48189
|
+
` id: ${node.id}`,
|
|
48190
|
+
` type: ${node.type}`
|
|
48191
|
+
];
|
|
48192
|
+
if (node.config && Object.keys(node.config).length > 0) lines.push(` config: ${summarizeObject(node.config)}`);
|
|
48193
|
+
if (node.input_mapping && Object.keys(node.input_mapping).length > 0) lines.push(` input: ${summarizeObject(node.input_mapping)}`);
|
|
48194
|
+
if (nodeRun?.input_summary) lines.push(` run input: ${summarizeObject(nodeRun.input_summary)}`);
|
|
48195
|
+
if (nodeRun?.output_summary) lines.push(` run output: ${summarizeObject(nodeRun.output_summary)}`);
|
|
48196
|
+
return lines;
|
|
48197
|
+
}
|
|
47043
48198
|
function renderInterests(state, width) {
|
|
47044
48199
|
const lines = [
|
|
47045
48200
|
"Private local personalization",
|
|
@@ -47134,13 +48289,55 @@ function renderMessageContent(content, width) {
|
|
|
47134
48289
|
}
|
|
47135
48290
|
function renderHintLine(state, width) {
|
|
47136
48291
|
if (state.screen === "interests") return truncateVisible("\u2191/\u2193 move Space select Enter continue Esc back", width);
|
|
48292
|
+
if (state.screen === "tasks") return truncateVisible("\u2191/\u2193 choose Enter open c create Esc back", width);
|
|
48293
|
+
if (state.screen === "task") return truncateVisible("c create e edit x delete r reorder s start d done b block u unblock k skip", width);
|
|
48294
|
+
if (state.screen === "workflows") return truncateVisible("\u2191/\u2193 choose Enter open Esc back", width);
|
|
48295
|
+
if (state.screen === "workflow" && state.workflowEdit) return truncateVisible("Enter save title Esc cancel edit", width);
|
|
48296
|
+
if (state.screen === "workflow") return truncateVisible("g graph r runs \u2191/\u2193 select Enter expand e title E config x run u refresh c cancel", width);
|
|
47137
48297
|
return truncateVisible("\u2191/\u2193 choose Enter open /search filter Esc back", width);
|
|
47138
48298
|
}
|
|
47139
48299
|
function inputPlaceholder(state) {
|
|
47140
48300
|
if (state.screen === "example") return "Continue from this example, or ask your own question...";
|
|
47141
48301
|
if (state.screen === "chat") return "Ask a follow-up, use @file, or type /help";
|
|
48302
|
+
if (state.screen === "workflow" || state.screen === "workflows" || state.screen === "tasks" || state.screen === "task") return "Use shortcuts below, or type /help";
|
|
47142
48303
|
return "Ask anything...";
|
|
47143
48304
|
}
|
|
48305
|
+
function nodeTypeLabel(type) {
|
|
48306
|
+
switch (type) {
|
|
48307
|
+
case "manual_trigger":
|
|
48308
|
+
return "manual trigger";
|
|
48309
|
+
case "schedule_trigger":
|
|
48310
|
+
return "schedule";
|
|
48311
|
+
case "app_skill_action":
|
|
48312
|
+
return "app skill";
|
|
48313
|
+
case "send_notification":
|
|
48314
|
+
return "notification";
|
|
48315
|
+
case "ask_user":
|
|
48316
|
+
return "ask user";
|
|
48317
|
+
default:
|
|
48318
|
+
return type.replaceAll("_", " ");
|
|
48319
|
+
}
|
|
48320
|
+
}
|
|
48321
|
+
function cardSummary(node) {
|
|
48322
|
+
const config = node.config ?? {};
|
|
48323
|
+
if (node.type === "app_skill_action") {
|
|
48324
|
+
const app = typeof config.app === "string" ? config.app : "app";
|
|
48325
|
+
const skill = typeof config.skill === "string" ? config.skill : "skill";
|
|
48326
|
+
return `${app}.${skill}`;
|
|
48327
|
+
}
|
|
48328
|
+
if (node.type === "decision") return "If condition";
|
|
48329
|
+
if (node.type === "send_notification") return "Send notification";
|
|
48330
|
+
if (node.type === "ask_user") return "Ask for user input";
|
|
48331
|
+
return node.id;
|
|
48332
|
+
}
|
|
48333
|
+
function formatTimestamp(value) {
|
|
48334
|
+
if (!value) return "-";
|
|
48335
|
+
return new Date(value * 1e3).toISOString().replace("T", " ").slice(0, 16);
|
|
48336
|
+
}
|
|
48337
|
+
function summarizeObject(value) {
|
|
48338
|
+
const entries = Object.entries(value).slice(0, 3).map(([key, item]) => `${key}=${String(item)}`);
|
|
48339
|
+
return entries.join(", ") || "object";
|
|
48340
|
+
}
|
|
47144
48341
|
function interestScore(haystack, interest) {
|
|
47145
48342
|
let score = haystack.includes(interest) ? 10 : 0;
|
|
47146
48343
|
for (const token of interest.split(/\s+|&/).filter((part) => part.length > 2)) {
|
|
@@ -47191,6 +48388,221 @@ function boxed(line, width) {
|
|
|
47191
48388
|
return `\u2502 ${line.padEnd(width)} \u2502`;
|
|
47192
48389
|
}
|
|
47193
48390
|
|
|
48391
|
+
// src/tasksCli.ts
|
|
48392
|
+
import { createHash as createHash7, randomBytes as randomBytes3, randomUUID as randomUUID5 } from "crypto";
|
|
48393
|
+
var TASK_STATUSES2 = ["backlog", "todo", "in_progress", "blocked", "done"];
|
|
48394
|
+
var DEFAULT_STANDALONE_PREFIX = "TASK";
|
|
48395
|
+
function normalizeTaskStatus(value) {
|
|
48396
|
+
if (value === void 0) return void 0;
|
|
48397
|
+
if (TASK_STATUSES2.includes(value)) return value;
|
|
48398
|
+
throw new Error(`Unknown task status '${value}'. Expected one of: ${TASK_STATUSES2.join(", ")}`);
|
|
48399
|
+
}
|
|
48400
|
+
function parseAssignee(value) {
|
|
48401
|
+
if (!value || value === "user") return { assigneeType: "user", assigneeHash: null };
|
|
48402
|
+
if (["ai", "openmates", "OpenMates"].includes(value)) return { assigneeType: "ai", assigneeHash: null };
|
|
48403
|
+
return { assigneeType: "user", assigneeHash: value };
|
|
48404
|
+
}
|
|
48405
|
+
function splitCsvFlag(value) {
|
|
48406
|
+
if (typeof value !== "string") return [];
|
|
48407
|
+
return value.split(/[,\n]/).map((item) => item.trim()).filter(Boolean);
|
|
48408
|
+
}
|
|
48409
|
+
function parseDueAt(value) {
|
|
48410
|
+
if (value === void 0) return void 0;
|
|
48411
|
+
if (value === false || value === true) throw new Error("--due requires a timestamp or date value.");
|
|
48412
|
+
const numeric = Number(value);
|
|
48413
|
+
if (Number.isFinite(numeric) && numeric > 0) return Math.floor(numeric);
|
|
48414
|
+
const parsed = Date.parse(value);
|
|
48415
|
+
if (Number.isNaN(parsed)) throw new Error(`Invalid --due value '${value}'.`);
|
|
48416
|
+
return Math.floor(parsed / 1e3);
|
|
48417
|
+
}
|
|
48418
|
+
async function buildCreateUserTaskInput(masterKey, input) {
|
|
48419
|
+
const taskKey = randomBytes3(32);
|
|
48420
|
+
const encryptedTaskKey = await encryptBytesWithAesGcm(taskKey, masterKey);
|
|
48421
|
+
const timestamp = nowSeconds();
|
|
48422
|
+
const assignee = parseAssignee(input.assign);
|
|
48423
|
+
const linkedProjectIds = input.projectIds ?? [];
|
|
48424
|
+
const status = input.status ?? (assignee.assigneeType === "ai" && !input.dueAt ? "in_progress" : "todo");
|
|
48425
|
+
return {
|
|
48426
|
+
task_id: randomUUIDCompat(),
|
|
48427
|
+
short_id: void 0,
|
|
48428
|
+
encrypted_task_key: encryptedTaskKey,
|
|
48429
|
+
encrypted_title: await encryptWithAesGcmCombined(input.title, taskKey),
|
|
48430
|
+
encrypted_description: await encryptWithAesGcmCombined(input.description ?? "", taskKey),
|
|
48431
|
+
encrypted_tags: await encryptWithAesGcmCombined("[]", taskKey),
|
|
48432
|
+
encrypted_linked_project_ids: await encryptWithAesGcmCombined(JSON.stringify(linkedProjectIds), taskKey),
|
|
48433
|
+
status,
|
|
48434
|
+
assignee_type: assignee.assigneeType,
|
|
48435
|
+
assignee_hash: assignee.assigneeHash,
|
|
48436
|
+
primary_chat_id: input.chatId ?? null,
|
|
48437
|
+
linked_project_ids: linkedProjectIds,
|
|
48438
|
+
plan_id: input.planId ?? null,
|
|
48439
|
+
due_at: input.dueAt ?? null,
|
|
48440
|
+
priority: 0,
|
|
48441
|
+
position: timestamp,
|
|
48442
|
+
created_at: timestamp,
|
|
48443
|
+
updated_at: timestamp
|
|
48444
|
+
};
|
|
48445
|
+
}
|
|
48446
|
+
async function buildUpdateUserTaskInput(task, masterKey, input) {
|
|
48447
|
+
const taskKey = await taskKeyFromRecord(task.encrypted, masterKey);
|
|
48448
|
+
const patch = { version: task.version, updated_at: nowSeconds() };
|
|
48449
|
+
if (input.title !== void 0) patch.encrypted_title = await encryptWithAesGcmCombined(input.title, taskKey);
|
|
48450
|
+
if (input.description !== void 0) patch.encrypted_description = await encryptWithAesGcmCombined(input.description, taskKey);
|
|
48451
|
+
if (input.status !== void 0) patch.status = input.status;
|
|
48452
|
+
if (input.assign !== void 0) {
|
|
48453
|
+
const assignee = parseAssignee(input.assign);
|
|
48454
|
+
patch.assignee_type = assignee.assigneeType;
|
|
48455
|
+
patch.assignee_hash = assignee.assigneeHash;
|
|
48456
|
+
}
|
|
48457
|
+
if (input.chatId !== void 0) patch.primary_chat_id = input.chatId;
|
|
48458
|
+
if (input.projectIds !== void 0) {
|
|
48459
|
+
patch.linked_project_ids = input.projectIds;
|
|
48460
|
+
patch.encrypted_linked_project_ids = await encryptWithAesGcmCombined(JSON.stringify(input.projectIds), taskKey);
|
|
48461
|
+
}
|
|
48462
|
+
if (input.planId !== void 0) patch.plan_id = input.planId;
|
|
48463
|
+
return patch;
|
|
48464
|
+
}
|
|
48465
|
+
async function decryptUserTask(record, masterKey) {
|
|
48466
|
+
const taskKey = await taskKeyFromRecord(record, masterKey);
|
|
48467
|
+
const tags = parseStringArray(await decryptOptional(record.encrypted_tags, taskKey));
|
|
48468
|
+
const linkedProjectIds = parseStringArray(await decryptOptional(record.encrypted_linked_project_ids, taskKey));
|
|
48469
|
+
return {
|
|
48470
|
+
taskId: record.task_id,
|
|
48471
|
+
shortId: record.short_id || deriveShortId(record),
|
|
48472
|
+
title: await decryptOptional(record.encrypted_title, taskKey) || "(untitled task)",
|
|
48473
|
+
description: await decryptOptional(record.encrypted_description, taskKey),
|
|
48474
|
+
tags,
|
|
48475
|
+
latestInstruction: await decryptOptional(record.encrypted_latest_instruction, taskKey),
|
|
48476
|
+
status: record.status,
|
|
48477
|
+
assigneeType: record.assignee_type,
|
|
48478
|
+
assigneeHash: record.assignee_hash ?? null,
|
|
48479
|
+
primaryChatId: record.primary_chat_id ?? null,
|
|
48480
|
+
linkedProjectIds: linkedProjectIds.length > 0 ? linkedProjectIds : record.linked_project_ids ?? [],
|
|
48481
|
+
planId: record.plan_id ?? null,
|
|
48482
|
+
dueAt: record.due_at ?? null,
|
|
48483
|
+
priority: record.priority ?? 0,
|
|
48484
|
+
position: record.position ?? 0,
|
|
48485
|
+
queueState: record.queue_state ?? "none",
|
|
48486
|
+
blockedReasonCode: record.blocked_reason_code ?? null,
|
|
48487
|
+
aiExecutionState: record.ai_execution_state ?? null,
|
|
48488
|
+
version: record.version ?? 1,
|
|
48489
|
+
encrypted: record
|
|
48490
|
+
};
|
|
48491
|
+
}
|
|
48492
|
+
async function decryptUserTasks(records, masterKey) {
|
|
48493
|
+
const output = [];
|
|
48494
|
+
for (const record of records) output.push(await decryptUserTask(record, masterKey));
|
|
48495
|
+
return output;
|
|
48496
|
+
}
|
|
48497
|
+
function findTask(tasks, id) {
|
|
48498
|
+
const task = tasks.find((candidate) => candidate.taskId === id || candidate.shortId === id);
|
|
48499
|
+
if (!task) throw new Error(`Task '${id}' was not found in the current task list.`);
|
|
48500
|
+
return task;
|
|
48501
|
+
}
|
|
48502
|
+
function renderTaskList(tasks) {
|
|
48503
|
+
if (tasks.length === 0) return "No tasks found.";
|
|
48504
|
+
const lines = ["Tasks", "ID Status Assignee Queue Title"];
|
|
48505
|
+
for (const task of tasks) {
|
|
48506
|
+
lines.push(`${pad(task.shortId, 9)} ${pad(task.status, 12)} ${pad(assigneeLabel(task), 11)} ${pad(task.queueState, 11)} ${task.title}`);
|
|
48507
|
+
}
|
|
48508
|
+
return lines.join("\n");
|
|
48509
|
+
}
|
|
48510
|
+
function renderTaskDetail2(task) {
|
|
48511
|
+
const lines = [
|
|
48512
|
+
`Task ${task.shortId}`,
|
|
48513
|
+
`Title: ${task.title}`,
|
|
48514
|
+
`Status: ${task.status}`,
|
|
48515
|
+
`Assignee: ${assigneeLabel(task)}`,
|
|
48516
|
+
`Queue: ${task.queueState}`,
|
|
48517
|
+
`Task ID: ${task.taskId}`
|
|
48518
|
+
];
|
|
48519
|
+
if (task.description) lines.push(`Description: ${task.description}`);
|
|
48520
|
+
if (task.primaryChatId) lines.push(`Chat: ${task.primaryChatId}`);
|
|
48521
|
+
if (task.linkedProjectIds.length > 0) lines.push(`Projects: ${task.linkedProjectIds.join(", ")}`);
|
|
48522
|
+
if (task.planId) lines.push(`Plan: ${task.planId}`);
|
|
48523
|
+
if (task.blockedReasonCode) lines.push(`Blocked reason: ${task.blockedReasonCode}`);
|
|
48524
|
+
if (task.aiExecutionState) lines.push(`AI state: ${task.aiExecutionState}`);
|
|
48525
|
+
return lines.join("\n");
|
|
48526
|
+
}
|
|
48527
|
+
function renderTaskBoard(tasks, width = process.stdout.columns || 100) {
|
|
48528
|
+
const columns = TASK_STATUSES2.map((status) => ({ status, tasks: tasks.filter((task) => task.status === status).sort(compareTasks) }));
|
|
48529
|
+
if (width < 96) {
|
|
48530
|
+
const lines2 = ["OpenMates Tasks Board"];
|
|
48531
|
+
for (const column of columns) {
|
|
48532
|
+
lines2.push("", `${columnTitle(column.status)} (${column.tasks.length})`, "-".repeat(24));
|
|
48533
|
+
lines2.push(...boardColumnLines(column.tasks, 8));
|
|
48534
|
+
}
|
|
48535
|
+
return lines2.join("\n");
|
|
48536
|
+
}
|
|
48537
|
+
const perColumn = 6;
|
|
48538
|
+
const columnWidth = 22;
|
|
48539
|
+
const lines = ["OpenMates Tasks Board", ""];
|
|
48540
|
+
lines.push(columns.map((column) => pad(`${columnTitle(column.status)} (${column.tasks.length})`, columnWidth)).join(" "));
|
|
48541
|
+
lines.push(columns.map(() => "-".repeat(columnWidth)).join(" "));
|
|
48542
|
+
const renderedColumns = columns.map((column) => boardColumnLines(column.tasks, perColumn).map((line) => truncate(line, columnWidth)));
|
|
48543
|
+
const maxRows = Math.max(...renderedColumns.map((column) => column.length));
|
|
48544
|
+
for (let row = 0; row < maxRows; row += 1) {
|
|
48545
|
+
lines.push(renderedColumns.map((column) => pad(column[row] ?? "", columnWidth)).join(" "));
|
|
48546
|
+
}
|
|
48547
|
+
return lines.join("\n");
|
|
48548
|
+
}
|
|
48549
|
+
function boardColumnLines(tasks, limit) {
|
|
48550
|
+
if (tasks.length === 0) return ["No tasks here."];
|
|
48551
|
+
const visible = tasks.slice(0, limit).flatMap((task) => [
|
|
48552
|
+
`[${task.shortId}] ${task.title}`,
|
|
48553
|
+
` ${assigneeLabel(task)} ${task.queueState === "none" ? "" : task.queueState}`.trimEnd()
|
|
48554
|
+
]);
|
|
48555
|
+
if (tasks.length > limit) visible.push(`... ${tasks.length - limit} more`);
|
|
48556
|
+
return visible;
|
|
48557
|
+
}
|
|
48558
|
+
function compareTasks(a, b) {
|
|
48559
|
+
return a.position - b.position || a.title.localeCompare(b.title);
|
|
48560
|
+
}
|
|
48561
|
+
function columnTitle(status) {
|
|
48562
|
+
if (status === "in_progress") return "In progress";
|
|
48563
|
+
return status.slice(0, 1).toUpperCase() + status.slice(1).replace("_", " ");
|
|
48564
|
+
}
|
|
48565
|
+
function assigneeLabel(task) {
|
|
48566
|
+
return task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
48567
|
+
}
|
|
48568
|
+
async function taskKeyFromRecord(record, masterKey) {
|
|
48569
|
+
if (!record.encrypted_task_key) throw new Error(`Task ${record.task_id} is missing encrypted task key.`);
|
|
48570
|
+
const taskKey = await decryptBytesWithAesGcm(record.encrypted_task_key, masterKey);
|
|
48571
|
+
if (!taskKey) throw new Error(`Failed to decrypt task key for ${record.task_id}.`);
|
|
48572
|
+
return taskKey;
|
|
48573
|
+
}
|
|
48574
|
+
async function decryptOptional(value, key) {
|
|
48575
|
+
if (!value) return "";
|
|
48576
|
+
return await decryptWithAesGcmCombined(value, key) ?? "";
|
|
48577
|
+
}
|
|
48578
|
+
function parseStringArray(value) {
|
|
48579
|
+
if (!value) return [];
|
|
48580
|
+
try {
|
|
48581
|
+
const parsed = JSON.parse(value);
|
|
48582
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
48583
|
+
} catch {
|
|
48584
|
+
return [];
|
|
48585
|
+
}
|
|
48586
|
+
}
|
|
48587
|
+
function deriveShortId(record) {
|
|
48588
|
+
const prefix = record.short_id_prefix || DEFAULT_STANDALONE_PREFIX;
|
|
48589
|
+
const source = record.task_id || `${record.created_at ?? ""}-${record.position ?? ""}`;
|
|
48590
|
+
const digest = createHash7("sha256").update(source).digest("hex").slice(0, 4).toUpperCase();
|
|
48591
|
+
return `${prefix}-${parseInt(digest, 16) % 1e4}`;
|
|
48592
|
+
}
|
|
48593
|
+
function nowSeconds() {
|
|
48594
|
+
return Math.floor(Date.now() / 1e3);
|
|
48595
|
+
}
|
|
48596
|
+
function randomUUIDCompat() {
|
|
48597
|
+
return randomUUID5();
|
|
48598
|
+
}
|
|
48599
|
+
function pad(value, length) {
|
|
48600
|
+
return truncate(value, length).padEnd(length);
|
|
48601
|
+
}
|
|
48602
|
+
function truncate(value, length) {
|
|
48603
|
+
return value.length <= length ? value : `${value.slice(0, Math.max(0, length - 3))}...`;
|
|
48604
|
+
}
|
|
48605
|
+
|
|
47194
48606
|
// src/tui.ts
|
|
47195
48607
|
function defaultModeForStreams(input, output) {
|
|
47196
48608
|
return input.isTTY === true && output.isTTY === true ? "tui" : "quickstart";
|
|
@@ -47233,11 +48645,78 @@ async function handleKey(params) {
|
|
|
47233
48645
|
return;
|
|
47234
48646
|
}
|
|
47235
48647
|
if (key.name === "escape") {
|
|
47236
|
-
state.
|
|
48648
|
+
if (state.workflowEdit) {
|
|
48649
|
+
state.workflowEdit = null;
|
|
48650
|
+
render();
|
|
48651
|
+
return;
|
|
48652
|
+
}
|
|
48653
|
+
state.screen = state.screen === "workflow" ? "workflows" : state.screen === "task" ? "tasks" : "start";
|
|
47237
48654
|
state.input = "";
|
|
47238
48655
|
render();
|
|
47239
48656
|
return;
|
|
47240
48657
|
}
|
|
48658
|
+
if (state.screen === "workflow" && state.workflowEdit) {
|
|
48659
|
+
if (key.name === "return") {
|
|
48660
|
+
await saveWorkflowNodeTitle({ state, client, render });
|
|
48661
|
+
return;
|
|
48662
|
+
}
|
|
48663
|
+
if (key.name === "backspace") {
|
|
48664
|
+
state.workflowEdit.value = state.workflowEdit.value.slice(0, -1);
|
|
48665
|
+
render();
|
|
48666
|
+
return;
|
|
48667
|
+
}
|
|
48668
|
+
if (!key.ctrl && !key.meta && chunk && chunk >= " ") {
|
|
48669
|
+
state.workflowEdit.value += chunk;
|
|
48670
|
+
render();
|
|
48671
|
+
}
|
|
48672
|
+
return;
|
|
48673
|
+
}
|
|
48674
|
+
if (state.screen === "workflow" && !state.input && !key.ctrl && !key.meta) {
|
|
48675
|
+
if (chunk === "g") {
|
|
48676
|
+
state.workflowTab = "graph";
|
|
48677
|
+
render();
|
|
48678
|
+
return;
|
|
48679
|
+
}
|
|
48680
|
+
if (chunk === "r") {
|
|
48681
|
+
state.workflowTab = "runs";
|
|
48682
|
+
render();
|
|
48683
|
+
return;
|
|
48684
|
+
}
|
|
48685
|
+
if (chunk === "x") {
|
|
48686
|
+
await runActiveWorkflow({ state, client, render });
|
|
48687
|
+
return;
|
|
48688
|
+
}
|
|
48689
|
+
if (chunk === "u") {
|
|
48690
|
+
await refreshActiveWorkflowRuns({ state, client, render });
|
|
48691
|
+
return;
|
|
48692
|
+
}
|
|
48693
|
+
if (chunk === "c") {
|
|
48694
|
+
await cancelLatestWorkflowRun({ state, client, render });
|
|
48695
|
+
return;
|
|
48696
|
+
}
|
|
48697
|
+
if (chunk === "e") {
|
|
48698
|
+
startWorkflowNodeEdit(state, "title");
|
|
48699
|
+
render();
|
|
48700
|
+
return;
|
|
48701
|
+
}
|
|
48702
|
+
if (chunk === "E") {
|
|
48703
|
+
startWorkflowNodeEdit(state, "config");
|
|
48704
|
+
render();
|
|
48705
|
+
return;
|
|
48706
|
+
}
|
|
48707
|
+
}
|
|
48708
|
+
if ((state.screen === "tasks" || state.screen === "task") && !state.input && !key.ctrl && !key.meta) {
|
|
48709
|
+
if (chunk === "c") {
|
|
48710
|
+
await createTaskFromTui({ state, client, terminal, render });
|
|
48711
|
+
return;
|
|
48712
|
+
}
|
|
48713
|
+
}
|
|
48714
|
+
if (state.screen === "task" && !state.input && !key.ctrl && !key.meta) {
|
|
48715
|
+
if (["s", "d", "b", "u", "k", "e", "x", "r"].includes(chunk)) {
|
|
48716
|
+
await handleActiveTaskAction({ state, client, terminal, actionKey: chunk, render });
|
|
48717
|
+
return;
|
|
48718
|
+
}
|
|
48719
|
+
}
|
|
47241
48720
|
if (key.name === "up") {
|
|
47242
48721
|
moveSelectionOrScroll(state, -1);
|
|
47243
48722
|
render();
|
|
@@ -47282,6 +48761,9 @@ async function handleKey(params) {
|
|
|
47282
48761
|
await handleEnter({ state, client, terminal, render, finish });
|
|
47283
48762
|
return;
|
|
47284
48763
|
}
|
|
48764
|
+
if (state.screen === "workflow" || state.screen === "workflows" || state.screen === "tasks" || state.screen === "task") {
|
|
48765
|
+
return;
|
|
48766
|
+
}
|
|
47285
48767
|
if (!key.ctrl && !key.meta && chunk && chunk >= " ") {
|
|
47286
48768
|
state.input += chunk;
|
|
47287
48769
|
render();
|
|
@@ -47300,6 +48782,23 @@ async function handleEnter(params) {
|
|
|
47300
48782
|
render();
|
|
47301
48783
|
return;
|
|
47302
48784
|
}
|
|
48785
|
+
if (state.screen === "workflows") {
|
|
48786
|
+
const selected = state.workflows[state.selectedIndex];
|
|
48787
|
+
if (selected) await openWorkflowDetail({ state, client, workflow: selected, render });
|
|
48788
|
+
render();
|
|
48789
|
+
return;
|
|
48790
|
+
}
|
|
48791
|
+
if (state.screen === "tasks") {
|
|
48792
|
+
const selected = state.tasks[state.selectedIndex];
|
|
48793
|
+
if (selected) openTaskDetail(state, selected);
|
|
48794
|
+
render();
|
|
48795
|
+
return;
|
|
48796
|
+
}
|
|
48797
|
+
if (state.screen === "workflow") {
|
|
48798
|
+
toggleSelectedWorkflowNode(state);
|
|
48799
|
+
render();
|
|
48800
|
+
return;
|
|
48801
|
+
}
|
|
47303
48802
|
const text = state.input.trim();
|
|
47304
48803
|
if (!text) return;
|
|
47305
48804
|
state.input = "";
|
|
@@ -47326,6 +48825,22 @@ async function handleCommand(params) {
|
|
|
47326
48825
|
render();
|
|
47327
48826
|
return;
|
|
47328
48827
|
}
|
|
48828
|
+
if (name === "/workflows") {
|
|
48829
|
+
await openWorkflowList({ state, client, render });
|
|
48830
|
+
return;
|
|
48831
|
+
}
|
|
48832
|
+
if (name === "/tasks") {
|
|
48833
|
+
await openTaskList({ state, client, render });
|
|
48834
|
+
return;
|
|
48835
|
+
}
|
|
48836
|
+
if (name === "/workflow") {
|
|
48837
|
+
if (!arg) {
|
|
48838
|
+
await openWorkflowList({ state, client, render });
|
|
48839
|
+
return;
|
|
48840
|
+
}
|
|
48841
|
+
await openWorkflowById({ state, client, workflowId: arg, render });
|
|
48842
|
+
return;
|
|
48843
|
+
}
|
|
47329
48844
|
if (name === "/signup") {
|
|
47330
48845
|
finish({ action: "signup" });
|
|
47331
48846
|
return;
|
|
@@ -47429,8 +48944,368 @@ function moveSelectionOrScroll(state, direction) {
|
|
|
47429
48944
|
state.selectedIndex = clamp(state.selectedIndex + direction, 0, Math.max(0, state.examples.length - 1));
|
|
47430
48945
|
return;
|
|
47431
48946
|
}
|
|
48947
|
+
if (state.screen === "workflows") {
|
|
48948
|
+
state.selectedIndex = clamp(state.selectedIndex + direction, 0, Math.max(0, state.workflows.length - 1));
|
|
48949
|
+
return;
|
|
48950
|
+
}
|
|
48951
|
+
if (state.screen === "tasks") {
|
|
48952
|
+
state.selectedIndex = clamp(state.selectedIndex + direction, 0, Math.max(0, state.tasks.length - 1));
|
|
48953
|
+
return;
|
|
48954
|
+
}
|
|
48955
|
+
if (state.screen === "workflow") {
|
|
48956
|
+
if (state.workflowTab === "runs") {
|
|
48957
|
+
state.selectedWorkflowRunIndex = clamp(state.selectedWorkflowRunIndex + direction, 0, Math.max(0, state.workflowRuns.length - 1));
|
|
48958
|
+
const selectedRun = state.workflowRuns[state.selectedWorkflowRunIndex];
|
|
48959
|
+
state.selectedWorkflowNodeIndex = firstRunNodeIndex(state, selectedRun);
|
|
48960
|
+
return;
|
|
48961
|
+
}
|
|
48962
|
+
const nodeCount = state.activeWorkflow?.graph.nodes.length ?? 0;
|
|
48963
|
+
state.selectedWorkflowNodeIndex = clamp(state.selectedWorkflowNodeIndex + direction, 0, Math.max(0, nodeCount - 1));
|
|
48964
|
+
return;
|
|
48965
|
+
}
|
|
47432
48966
|
state.scrollOffset = Math.max(0, state.scrollOffset - direction);
|
|
47433
48967
|
}
|
|
48968
|
+
function toggleSelectedWorkflowNode(state) {
|
|
48969
|
+
const workflow = state.activeWorkflow;
|
|
48970
|
+
if (!workflow) return;
|
|
48971
|
+
const node = workflow.graph.nodes[state.selectedWorkflowNodeIndex];
|
|
48972
|
+
if (!node) return;
|
|
48973
|
+
if (state.workflowTab === "runs") {
|
|
48974
|
+
state.expandedWorkflowRunNodeId = state.expandedWorkflowRunNodeId === node.id ? null : node.id;
|
|
48975
|
+
} else {
|
|
48976
|
+
state.expandedWorkflowNodeId = state.expandedWorkflowNodeId === node.id ? null : node.id;
|
|
48977
|
+
}
|
|
48978
|
+
}
|
|
48979
|
+
function firstRunNodeIndex(state, run) {
|
|
48980
|
+
const workflow = state.activeWorkflow;
|
|
48981
|
+
const firstNodeRun = run?.node_runs?.[0];
|
|
48982
|
+
if (!workflow || !firstNodeRun) return 0;
|
|
48983
|
+
return Math.max(0, workflow.graph.nodes.findIndex((node) => node.id === firstNodeRun.node_id));
|
|
48984
|
+
}
|
|
48985
|
+
async function openTaskList(params) {
|
|
48986
|
+
const { state, client, render } = params;
|
|
48987
|
+
state.screen = "status";
|
|
48988
|
+
state.status = "Loading tasks...";
|
|
48989
|
+
render();
|
|
48990
|
+
try {
|
|
48991
|
+
state.tasks = await decryptUserTasks(await client.listUserTasks(), client.getMasterKeyBytes());
|
|
48992
|
+
state.selectedIndex = 0;
|
|
48993
|
+
state.scrollOffset = 0;
|
|
48994
|
+
state.activeTask = null;
|
|
48995
|
+
state.status = null;
|
|
48996
|
+
state.screen = "tasks";
|
|
48997
|
+
} catch (error) {
|
|
48998
|
+
state.status = taskError(error, "Could not load tasks. Use /login first if you are not signed in.");
|
|
48999
|
+
state.screen = "status";
|
|
49000
|
+
}
|
|
49001
|
+
render();
|
|
49002
|
+
}
|
|
49003
|
+
function openTaskDetail(state, task) {
|
|
49004
|
+
state.activeTask = task;
|
|
49005
|
+
state.screen = "task";
|
|
49006
|
+
state.scrollOffset = 0;
|
|
49007
|
+
}
|
|
49008
|
+
async function handleActiveTaskAction(params) {
|
|
49009
|
+
const { state, client, terminal, actionKey, render } = params;
|
|
49010
|
+
const task = state.activeTask;
|
|
49011
|
+
if (!task) return;
|
|
49012
|
+
state.status = "Updating task...";
|
|
49013
|
+
render();
|
|
49014
|
+
try {
|
|
49015
|
+
if (actionKey === "e") {
|
|
49016
|
+
const title = await promptLine(terminal, `New title for ${task.shortId}: `);
|
|
49017
|
+
if (!title.trim()) {
|
|
49018
|
+
state.status = "Edit cancelled.";
|
|
49019
|
+
render();
|
|
49020
|
+
return;
|
|
49021
|
+
}
|
|
49022
|
+
const patch = await buildUpdateUserTaskInput(task, client.getMasterKeyBytes(), { title: title.trim() });
|
|
49023
|
+
const updated2 = await client.updateUserTask(task.taskId, patch);
|
|
49024
|
+
const decrypted2 = await decryptUserTask(updated2, client.getMasterKeyBytes());
|
|
49025
|
+
replaceTask(state, decrypted2);
|
|
49026
|
+
state.activeTask = decrypted2;
|
|
49027
|
+
state.status = null;
|
|
49028
|
+
state.screen = "task";
|
|
49029
|
+
render();
|
|
49030
|
+
return;
|
|
49031
|
+
}
|
|
49032
|
+
if (actionKey === "x") {
|
|
49033
|
+
const confirmation = await promptLine(terminal, `Type DELETE to delete ${task.shortId}: `);
|
|
49034
|
+
if (confirmation !== "DELETE") {
|
|
49035
|
+
state.status = "Delete cancelled.";
|
|
49036
|
+
render();
|
|
49037
|
+
return;
|
|
49038
|
+
}
|
|
49039
|
+
await client.deleteUserTask(task.taskId);
|
|
49040
|
+
state.tasks = state.tasks.filter((candidate) => candidate.taskId !== task.taskId);
|
|
49041
|
+
state.activeTask = null;
|
|
49042
|
+
state.selectedIndex = Math.min(state.selectedIndex, Math.max(0, state.tasks.length - 1));
|
|
49043
|
+
state.status = `Deleted ${task.shortId}.`;
|
|
49044
|
+
state.screen = "tasks";
|
|
49045
|
+
render();
|
|
49046
|
+
return;
|
|
49047
|
+
}
|
|
49048
|
+
if (actionKey === "r") {
|
|
49049
|
+
const positionText = await promptLine(terminal, `New numeric position for ${task.shortId}: `);
|
|
49050
|
+
const position = Number(positionText.trim());
|
|
49051
|
+
if (!Number.isFinite(position)) {
|
|
49052
|
+
state.status = "Reorder cancelled: position must be a number.";
|
|
49053
|
+
render();
|
|
49054
|
+
return;
|
|
49055
|
+
}
|
|
49056
|
+
const updated2 = await client.reorderUserTasks({ moves: [{ task_id: task.taskId, position }] });
|
|
49057
|
+
const [decrypted2] = await decryptUserTasks(updated2, client.getMasterKeyBytes());
|
|
49058
|
+
if (decrypted2) {
|
|
49059
|
+
replaceTask(state, decrypted2);
|
|
49060
|
+
state.activeTask = decrypted2;
|
|
49061
|
+
}
|
|
49062
|
+
state.status = null;
|
|
49063
|
+
state.screen = "task";
|
|
49064
|
+
render();
|
|
49065
|
+
return;
|
|
49066
|
+
}
|
|
49067
|
+
const payload = actionKey === "b" ? { version: task.version, blocked_reason_code: "needs_user_input" } : { version: task.version };
|
|
49068
|
+
const updated = actionKey === "s" ? await client.startUserTaskWithAI(task.taskId, {
|
|
49069
|
+
version: task.version,
|
|
49070
|
+
primary_chat_id: task.primaryChatId ?? void 0,
|
|
49071
|
+
linked_project_ids: task.linkedProjectIds,
|
|
49072
|
+
plaintext_title: task.title,
|
|
49073
|
+
plaintext_description: task.description,
|
|
49074
|
+
plaintext_latest_instruction: task.latestInstruction
|
|
49075
|
+
}) : actionKey === "d" ? await client.completeUserTask(task.taskId, payload) : actionKey === "b" ? await client.blockUserTask(task.taskId, payload) : actionKey === "u" ? await client.unblockUserTask(task.taskId, payload) : await client.skipUserTask(task.taskId, payload);
|
|
49076
|
+
const decrypted = await decryptUserTask(updated, client.getMasterKeyBytes());
|
|
49077
|
+
replaceTask(state, decrypted);
|
|
49078
|
+
state.activeTask = decrypted;
|
|
49079
|
+
state.status = null;
|
|
49080
|
+
state.screen = "task";
|
|
49081
|
+
} catch (error) {
|
|
49082
|
+
state.status = taskError(error, "Could not update task.");
|
|
49083
|
+
state.screen = "status";
|
|
49084
|
+
}
|
|
49085
|
+
render();
|
|
49086
|
+
}
|
|
49087
|
+
async function createTaskFromTui(params) {
|
|
49088
|
+
const { state, client, terminal, render } = params;
|
|
49089
|
+
state.status = "Creating task...";
|
|
49090
|
+
render();
|
|
49091
|
+
try {
|
|
49092
|
+
const title = await promptLine(terminal, "Task title: ");
|
|
49093
|
+
if (!title.trim()) {
|
|
49094
|
+
state.status = "Create cancelled.";
|
|
49095
|
+
render();
|
|
49096
|
+
return;
|
|
49097
|
+
}
|
|
49098
|
+
const description = await promptLine(terminal, "Description (optional): ");
|
|
49099
|
+
const input = await buildCreateUserTaskInput(client.getMasterKeyBytes(), {
|
|
49100
|
+
title: title.trim(),
|
|
49101
|
+
description: description.trim(),
|
|
49102
|
+
assign: "user"
|
|
49103
|
+
});
|
|
49104
|
+
const created = await client.createUserTask(input);
|
|
49105
|
+
const decrypted = await decryptUserTask(created, client.getMasterKeyBytes());
|
|
49106
|
+
replaceTask(state, decrypted);
|
|
49107
|
+
state.activeTask = decrypted;
|
|
49108
|
+
state.selectedIndex = Math.max(0, state.tasks.findIndex((task) => task.taskId === decrypted.taskId));
|
|
49109
|
+
state.status = null;
|
|
49110
|
+
state.screen = "task";
|
|
49111
|
+
} catch (error) {
|
|
49112
|
+
state.status = taskError(error, "Could not create task.");
|
|
49113
|
+
state.screen = "status";
|
|
49114
|
+
}
|
|
49115
|
+
render();
|
|
49116
|
+
}
|
|
49117
|
+
async function promptLine(terminal, prompt) {
|
|
49118
|
+
return terminal.suspend(async () => {
|
|
49119
|
+
const rl = createInterface3({ input: nodeStdin, output: nodeStdout });
|
|
49120
|
+
try {
|
|
49121
|
+
return await rl.question(prompt);
|
|
49122
|
+
} finally {
|
|
49123
|
+
rl.close();
|
|
49124
|
+
}
|
|
49125
|
+
});
|
|
49126
|
+
}
|
|
49127
|
+
function replaceTask(state, task) {
|
|
49128
|
+
const index = state.tasks.findIndex((candidate) => candidate.taskId === task.taskId);
|
|
49129
|
+
if (index >= 0) state.tasks[index] = task;
|
|
49130
|
+
else state.tasks.unshift(task);
|
|
49131
|
+
}
|
|
49132
|
+
function taskError(error, fallback) {
|
|
49133
|
+
return error instanceof Error ? `${fallback} ${error.message}` : fallback;
|
|
49134
|
+
}
|
|
49135
|
+
async function openWorkflowList(params) {
|
|
49136
|
+
const { state, client, render } = params;
|
|
49137
|
+
state.screen = "status";
|
|
49138
|
+
state.status = "Loading workflows...";
|
|
49139
|
+
render();
|
|
49140
|
+
try {
|
|
49141
|
+
state.workflows = await client.listWorkflows();
|
|
49142
|
+
state.selectedIndex = 0;
|
|
49143
|
+
state.scrollOffset = 0;
|
|
49144
|
+
state.status = null;
|
|
49145
|
+
state.screen = "workflows";
|
|
49146
|
+
} catch (error) {
|
|
49147
|
+
state.status = workflowError(error, "Could not load workflows. Use /login first if you are not signed in.");
|
|
49148
|
+
state.screen = "status";
|
|
49149
|
+
}
|
|
49150
|
+
render();
|
|
49151
|
+
}
|
|
49152
|
+
async function openWorkflowById(params) {
|
|
49153
|
+
const { state, client, workflowId, render } = params;
|
|
49154
|
+
state.screen = "status";
|
|
49155
|
+
state.status = `Loading workflow ${workflowId}...`;
|
|
49156
|
+
render();
|
|
49157
|
+
try {
|
|
49158
|
+
const workflow = await client.getWorkflow(workflowId);
|
|
49159
|
+
await openWorkflowDetail({ state, client, workflow, render });
|
|
49160
|
+
} catch (error) {
|
|
49161
|
+
state.status = workflowError(error, `Could not load workflow ${workflowId}.`);
|
|
49162
|
+
state.screen = "status";
|
|
49163
|
+
render();
|
|
49164
|
+
}
|
|
49165
|
+
}
|
|
49166
|
+
async function openWorkflowDetail(params) {
|
|
49167
|
+
const { state, client, workflow, render } = params;
|
|
49168
|
+
state.status = `Loading workflow ${workflow.id}...`;
|
|
49169
|
+
render();
|
|
49170
|
+
let detail;
|
|
49171
|
+
try {
|
|
49172
|
+
detail = await client.getWorkflow(workflow.id);
|
|
49173
|
+
} catch (error) {
|
|
49174
|
+
state.status = workflowError(error, `Could not load workflow ${workflow.id}.`);
|
|
49175
|
+
state.screen = "status";
|
|
49176
|
+
render();
|
|
49177
|
+
return;
|
|
49178
|
+
}
|
|
49179
|
+
state.activeWorkflow = detail;
|
|
49180
|
+
state.workflowRuns = [];
|
|
49181
|
+
state.workflowTab = "graph";
|
|
49182
|
+
state.selectedWorkflowNodeIndex = 0;
|
|
49183
|
+
state.selectedWorkflowRunIndex = 0;
|
|
49184
|
+
state.expandedWorkflowNodeId = null;
|
|
49185
|
+
state.expandedWorkflowRunNodeId = null;
|
|
49186
|
+
state.workflowEdit = null;
|
|
49187
|
+
state.screen = "workflow";
|
|
49188
|
+
state.scrollOffset = 0;
|
|
49189
|
+
state.status = null;
|
|
49190
|
+
render();
|
|
49191
|
+
await refreshActiveWorkflowRuns({ state, client, render });
|
|
49192
|
+
}
|
|
49193
|
+
async function refreshActiveWorkflowRuns(params) {
|
|
49194
|
+
const { state, client, render } = params;
|
|
49195
|
+
const workflow = state.activeWorkflow;
|
|
49196
|
+
if (!workflow) return;
|
|
49197
|
+
state.status = "Refreshing workflow runs...";
|
|
49198
|
+
render();
|
|
49199
|
+
try {
|
|
49200
|
+
state.workflowRuns = await client.listWorkflowRuns(workflow.id);
|
|
49201
|
+
state.selectedWorkflowRunIndex = clamp(state.selectedWorkflowRunIndex, 0, Math.max(0, state.workflowRuns.length - 1));
|
|
49202
|
+
state.status = null;
|
|
49203
|
+
state.screen = "workflow";
|
|
49204
|
+
} catch (error) {
|
|
49205
|
+
state.status = workflowError(error, "Could not refresh workflow runs.");
|
|
49206
|
+
}
|
|
49207
|
+
render();
|
|
49208
|
+
}
|
|
49209
|
+
async function saveWorkflowNodeTitle(params) {
|
|
49210
|
+
const { state, client, render } = params;
|
|
49211
|
+
const workflow = state.activeWorkflow;
|
|
49212
|
+
const edit = state.workflowEdit;
|
|
49213
|
+
if (!workflow || !edit) return;
|
|
49214
|
+
let parsedConfig = null;
|
|
49215
|
+
if (edit.field === "config") {
|
|
49216
|
+
try {
|
|
49217
|
+
const parsed = JSON.parse(edit.value || "{}");
|
|
49218
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Config must be a JSON object");
|
|
49219
|
+
parsedConfig = parsed;
|
|
49220
|
+
} catch (error) {
|
|
49221
|
+
state.status = workflowError(error, "Invalid config JSON.");
|
|
49222
|
+
render();
|
|
49223
|
+
return;
|
|
49224
|
+
}
|
|
49225
|
+
}
|
|
49226
|
+
const graph = {
|
|
49227
|
+
...workflow.graph,
|
|
49228
|
+
nodes: workflow.graph.nodes.map((node) => {
|
|
49229
|
+
if (node.id !== edit.nodeId) return node;
|
|
49230
|
+
if (edit.field === "config") return { ...node, config: parsedConfig ?? {} };
|
|
49231
|
+
return { ...node, title: edit.value.trim() || null };
|
|
49232
|
+
})
|
|
49233
|
+
};
|
|
49234
|
+
state.status = `Saving node ${edit.field}...`;
|
|
49235
|
+
render();
|
|
49236
|
+
try {
|
|
49237
|
+
state.activeWorkflow = await client.updateWorkflow(workflow.id, { graph });
|
|
49238
|
+
state.workflowEdit = null;
|
|
49239
|
+
state.status = `Saved node ${edit.field}.`;
|
|
49240
|
+
} catch (error) {
|
|
49241
|
+
state.status = workflowError(error, "Could not save node title.");
|
|
49242
|
+
}
|
|
49243
|
+
render();
|
|
49244
|
+
}
|
|
49245
|
+
function startWorkflowNodeEdit(state, field) {
|
|
49246
|
+
const workflow = state.activeWorkflow;
|
|
49247
|
+
if (!workflow || state.workflowTab !== "graph") {
|
|
49248
|
+
state.status = "Switch to the Graph tab before editing node details.";
|
|
49249
|
+
return;
|
|
49250
|
+
}
|
|
49251
|
+
const node = workflow.graph.nodes[state.selectedWorkflowNodeIndex];
|
|
49252
|
+
if (!node) return;
|
|
49253
|
+
state.workflowEdit = {
|
|
49254
|
+
nodeId: node.id,
|
|
49255
|
+
field,
|
|
49256
|
+
value: field === "config" ? JSON.stringify(node.config ?? {}) : node.title ?? ""
|
|
49257
|
+
};
|
|
49258
|
+
state.expandedWorkflowNodeId = node.id;
|
|
49259
|
+
state.status = `Editing ${field} for ${node.id}.`;
|
|
49260
|
+
}
|
|
49261
|
+
async function runActiveWorkflow(params) {
|
|
49262
|
+
const { state, client, render } = params;
|
|
49263
|
+
const workflow = state.activeWorkflow;
|
|
49264
|
+
if (!workflow) return;
|
|
49265
|
+
if (!workflow.enabled) {
|
|
49266
|
+
state.status = "Enable this workflow outside TUI before running it.";
|
|
49267
|
+
render();
|
|
49268
|
+
return;
|
|
49269
|
+
}
|
|
49270
|
+
state.status = "Starting workflow run...";
|
|
49271
|
+
render();
|
|
49272
|
+
try {
|
|
49273
|
+
const run = await client.runWorkflow(workflow.id, {
|
|
49274
|
+
idempotencyKey: `tui-${workflow.id}-${Date.now()}`,
|
|
49275
|
+
mode: "manual",
|
|
49276
|
+
input: {}
|
|
49277
|
+
});
|
|
49278
|
+
state.workflowRuns = [run, ...state.workflowRuns.filter((candidate) => candidate.id !== run.id)];
|
|
49279
|
+
state.status = `Started run ${run.id}. Press u to refresh.`;
|
|
49280
|
+
} catch (error) {
|
|
49281
|
+
state.status = workflowError(error, "Could not start workflow run.");
|
|
49282
|
+
}
|
|
49283
|
+
render();
|
|
49284
|
+
}
|
|
49285
|
+
async function cancelLatestWorkflowRun(params) {
|
|
49286
|
+
const { state, client, render } = params;
|
|
49287
|
+
const workflow = state.activeWorkflow;
|
|
49288
|
+
const run = state.workflowRuns.find((candidate) => ["queued", "running", "waiting"].includes(candidate.status));
|
|
49289
|
+
if (!workflow || !run) {
|
|
49290
|
+
state.status = "No active workflow run to cancel.";
|
|
49291
|
+
render();
|
|
49292
|
+
return;
|
|
49293
|
+
}
|
|
49294
|
+
state.status = `Cancelling run ${run.id}...`;
|
|
49295
|
+
render();
|
|
49296
|
+
try {
|
|
49297
|
+
const result = await client.cancelWorkflowRun(workflow.id, run.id);
|
|
49298
|
+
state.status = `Run ${result.run_id} ${result.status}.`;
|
|
49299
|
+
await refreshActiveWorkflowRuns({ state, client, render });
|
|
49300
|
+
} catch (error) {
|
|
49301
|
+
state.status = workflowError(error, "Could not cancel workflow run.");
|
|
49302
|
+
render();
|
|
49303
|
+
}
|
|
49304
|
+
}
|
|
49305
|
+
function workflowError(error, fallback) {
|
|
49306
|
+
const details = error instanceof Error ? error.message : String(error);
|
|
49307
|
+
return `${fallback} ${details}`;
|
|
49308
|
+
}
|
|
47434
49309
|
function toggleInterest(state) {
|
|
47435
49310
|
const interest = TUI_INTERESTS[state.selectedIndex];
|
|
47436
49311
|
if (!interest) return;
|
|
@@ -47928,6 +49803,10 @@ async function main() {
|
|
|
47928
49803
|
printChatsHelp();
|
|
47929
49804
|
return;
|
|
47930
49805
|
}
|
|
49806
|
+
if (command === "drafts") {
|
|
49807
|
+
printDraftsHelp();
|
|
49808
|
+
return;
|
|
49809
|
+
}
|
|
47931
49810
|
if (command === "apps") {
|
|
47932
49811
|
printAppsHelp();
|
|
47933
49812
|
return;
|
|
@@ -47940,6 +49819,10 @@ async function main() {
|
|
|
47940
49819
|
printWorkflowsHelp();
|
|
47941
49820
|
return;
|
|
47942
49821
|
}
|
|
49822
|
+
if (command === "tasks") {
|
|
49823
|
+
printTasksHelp();
|
|
49824
|
+
return;
|
|
49825
|
+
}
|
|
47943
49826
|
if (command === "connected-accounts") {
|
|
47944
49827
|
printConnectedAccountsHelp();
|
|
47945
49828
|
return;
|
|
@@ -48050,6 +49933,14 @@ async function main() {
|
|
|
48050
49933
|
await handleChats(client, subcommand, rest, parsed.flags, redactor);
|
|
48051
49934
|
return;
|
|
48052
49935
|
}
|
|
49936
|
+
if (command === "tasks") {
|
|
49937
|
+
await handleTasks(client, subcommand, rest, parsed.flags);
|
|
49938
|
+
return;
|
|
49939
|
+
}
|
|
49940
|
+
if (command === "drafts") {
|
|
49941
|
+
await handleDrafts(client, subcommand, rest, parsed.flags);
|
|
49942
|
+
return;
|
|
49943
|
+
}
|
|
48053
49944
|
if (command === "apps") {
|
|
48054
49945
|
await handleApps(client, subcommand, rest, parsed.flags);
|
|
48055
49946
|
return;
|
|
@@ -48187,6 +50078,172 @@ function handleCliVersion(flags) {
|
|
|
48187
50078
|
}
|
|
48188
50079
|
console.log("OpenMates CLI is up to date.");
|
|
48189
50080
|
}
|
|
50081
|
+
async function handleTasks(client, subcommand, rest, flags) {
|
|
50082
|
+
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
50083
|
+
printTasksHelp();
|
|
50084
|
+
return;
|
|
50085
|
+
}
|
|
50086
|
+
const masterKey = client.getMasterKeyBytes();
|
|
50087
|
+
const scope = taskScopeFromFlags(flags);
|
|
50088
|
+
if (subcommand === "list" || subcommand === "status") {
|
|
50089
|
+
if (subcommand === "status" && rest[0]) {
|
|
50090
|
+
const task = await resolveTask(client, masterKey, rest[0], scope);
|
|
50091
|
+
printTaskOutput(task, flags);
|
|
50092
|
+
return;
|
|
50093
|
+
}
|
|
50094
|
+
const tasks = await loadTasks(client, masterKey, scope);
|
|
50095
|
+
if (flags.json === true) printJson2({ tasks: tasks.map(taskToJson) });
|
|
50096
|
+
else console.log(renderTaskList(tasks));
|
|
50097
|
+
return;
|
|
50098
|
+
}
|
|
50099
|
+
if (subcommand === "board") {
|
|
50100
|
+
const tasks = await loadTasks(client, masterKey, scope);
|
|
50101
|
+
if (flags.json === true) printJson2({ tasks: tasks.map(taskToJson) });
|
|
50102
|
+
else console.log(renderTaskBoard(tasks));
|
|
50103
|
+
return;
|
|
50104
|
+
}
|
|
50105
|
+
if (subcommand === "show") {
|
|
50106
|
+
const id = rest[0];
|
|
50107
|
+
if (!id) throw new Error("Missing task ID. Usage: openmates tasks show <task-id>");
|
|
50108
|
+
const task = await resolveTask(client, masterKey, id, scope);
|
|
50109
|
+
printTaskOutput(task, flags);
|
|
50110
|
+
return;
|
|
50111
|
+
}
|
|
50112
|
+
if (subcommand === "create") {
|
|
50113
|
+
const title = taskTitleFromFlagsOrRest(flags, rest);
|
|
50114
|
+
const input = await buildCreateUserTaskInput(masterKey, {
|
|
50115
|
+
title,
|
|
50116
|
+
description: typeof flags.description === "string" ? flags.description : "",
|
|
50117
|
+
status: normalizeTaskStatus(typeof flags.status === "string" ? flags.status : void 0),
|
|
50118
|
+
assign: taskAssignFlag(flags),
|
|
50119
|
+
chatId: typeof flags.chat === "string" ? flags.chat : null,
|
|
50120
|
+
projectIds: splitCsvFlag(flags.project ?? flags.projects),
|
|
50121
|
+
planId: typeof flags.plan === "string" ? flags.plan : null,
|
|
50122
|
+
dueAt: parseDueAt(flags.due)
|
|
50123
|
+
});
|
|
50124
|
+
const created = await client.createUserTask(input);
|
|
50125
|
+
printTaskOutput(await decryptUserTask(created, masterKey), flags);
|
|
50126
|
+
return;
|
|
50127
|
+
}
|
|
50128
|
+
if (subcommand === "edit") {
|
|
50129
|
+
const id = rest[0];
|
|
50130
|
+
if (!id) throw new Error("Missing task ID. Usage: openmates tasks edit <task-id> [--title ...]");
|
|
50131
|
+
const task = await resolveTask(client, masterKey, id, scope);
|
|
50132
|
+
const patch = await buildUpdateUserTaskInput(task, masterKey, {
|
|
50133
|
+
title: typeof flags.title === "string" ? flags.title : void 0,
|
|
50134
|
+
description: typeof flags.description === "string" ? flags.description : void 0,
|
|
50135
|
+
status: normalizeTaskStatus(typeof flags.status === "string" ? flags.status : void 0),
|
|
50136
|
+
assign: taskAssignFlag(flags),
|
|
50137
|
+
chatId: flags.chat === true ? null : typeof flags.chat === "string" ? flags.chat : void 0,
|
|
50138
|
+
projectIds: flags.project || flags.projects ? splitCsvFlag(flags.project ?? flags.projects) : void 0,
|
|
50139
|
+
planId: flags.plan === true ? null : typeof flags.plan === "string" ? flags.plan : void 0
|
|
50140
|
+
});
|
|
50141
|
+
const updated = await client.updateUserTask(task.taskId, patch);
|
|
50142
|
+
printTaskOutput(await decryptUserTask(updated, masterKey), flags);
|
|
50143
|
+
return;
|
|
50144
|
+
}
|
|
50145
|
+
if (subcommand === "delete") {
|
|
50146
|
+
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, "delete");
|
|
50147
|
+
if (flags.confirm !== true) throw new Error("Deleting a task requires --confirm.");
|
|
50148
|
+
const result = await client.deleteUserTask(task.taskId);
|
|
50149
|
+
if (flags.json === true) printJson2(result);
|
|
50150
|
+
else console.log(`Task deleted: ${task.shortId}`);
|
|
50151
|
+
return;
|
|
50152
|
+
}
|
|
50153
|
+
if (subcommand === "start") {
|
|
50154
|
+
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, "start");
|
|
50155
|
+
const started = await client.startUserTaskWithAI(task.taskId, {
|
|
50156
|
+
version: task.version,
|
|
50157
|
+
primary_chat_id: task.primaryChatId ?? void 0,
|
|
50158
|
+
linked_project_ids: task.linkedProjectIds,
|
|
50159
|
+
plaintext_title: task.title,
|
|
50160
|
+
plaintext_description: task.description,
|
|
50161
|
+
plaintext_latest_instruction: task.latestInstruction
|
|
50162
|
+
});
|
|
50163
|
+
printTaskOutput(await decryptUserTask(started, masterKey), flags);
|
|
50164
|
+
return;
|
|
50165
|
+
}
|
|
50166
|
+
if (["block", "unblock", "skip", "done"].includes(subcommand)) {
|
|
50167
|
+
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, subcommand);
|
|
50168
|
+
const payload = { version: task.version };
|
|
50169
|
+
if (subcommand === "block") {
|
|
50170
|
+
if (typeof flags.reason !== "string") throw new Error("Blocking a task requires --reason <code>.");
|
|
50171
|
+
payload.blocked_reason_code = flags.reason;
|
|
50172
|
+
}
|
|
50173
|
+
const updated = subcommand === "block" ? await client.blockUserTask(task.taskId, payload) : subcommand === "unblock" ? await client.unblockUserTask(task.taskId, payload) : subcommand === "skip" ? await client.skipUserTask(task.taskId, payload) : await client.completeUserTask(task.taskId, payload);
|
|
50174
|
+
printTaskOutput(await decryptUserTask(updated, masterKey), flags);
|
|
50175
|
+
return;
|
|
50176
|
+
}
|
|
50177
|
+
if (subcommand === "reorder") {
|
|
50178
|
+
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, "reorder");
|
|
50179
|
+
const move = { task_id: task.taskId };
|
|
50180
|
+
if (typeof flags.before === "string") move.before_task_id = (await resolveTask(client, masterKey, flags.before, scope)).taskId;
|
|
50181
|
+
if (typeof flags.after === "string") move.after_task_id = (await resolveTask(client, masterKey, flags.after, scope)).taskId;
|
|
50182
|
+
if (typeof flags.status === "string") move.status = normalizeTaskStatus(flags.status);
|
|
50183
|
+
if (typeof flags.position === "string") move.position = Number(flags.position);
|
|
50184
|
+
const updated = await client.reorderUserTasks({ moves: [move] });
|
|
50185
|
+
const decrypted = await decryptUserTasks(updated, masterKey);
|
|
50186
|
+
if (flags.json === true) printJson2({ tasks: decrypted.map(taskToJson) });
|
|
50187
|
+
else console.log(`Task reordered: ${task.shortId}`);
|
|
50188
|
+
return;
|
|
50189
|
+
}
|
|
50190
|
+
throw new Error(`Unknown tasks command '${subcommand}'. Run 'openmates tasks --help'.`);
|
|
50191
|
+
}
|
|
50192
|
+
function taskScopeFromFlags(flags) {
|
|
50193
|
+
return {
|
|
50194
|
+
status: normalizeTaskStatus(typeof flags.status === "string" ? flags.status : void 0),
|
|
50195
|
+
chatId: typeof flags.chat === "string" ? flags.chat : void 0,
|
|
50196
|
+
projectId: typeof flags.project === "string" ? flags.project : void 0,
|
|
50197
|
+
planId: typeof flags.plan === "string" ? flags.plan : void 0
|
|
50198
|
+
};
|
|
50199
|
+
}
|
|
50200
|
+
async function loadTasks(client, masterKey, scope) {
|
|
50201
|
+
const records = await client.listUserTasks({ status: scope.status, chatId: scope.chatId, projectId: scope.projectId });
|
|
50202
|
+
const tasks = await decryptUserTasks(records, masterKey);
|
|
50203
|
+
return scope.planId ? tasks.filter((task) => task.planId === scope.planId) : tasks;
|
|
50204
|
+
}
|
|
50205
|
+
async function resolveTask(client, masterKey, id, scope) {
|
|
50206
|
+
return findTask(await loadTasks(client, masterKey, { ...scope, status: void 0 }), id);
|
|
50207
|
+
}
|
|
50208
|
+
async function requiredResolvedTask(client, masterKey, id, scope, action) {
|
|
50209
|
+
if (!id) throw new Error(`Missing task ID. Usage: openmates tasks ${action} <task-id>`);
|
|
50210
|
+
return resolveTask(client, masterKey, id, scope);
|
|
50211
|
+
}
|
|
50212
|
+
function printTaskOutput(task, flags) {
|
|
50213
|
+
if (flags.json === true) printJson2({ task: taskToJson(task) });
|
|
50214
|
+
else console.log(renderTaskDetail2(task));
|
|
50215
|
+
}
|
|
50216
|
+
function taskToJson(task) {
|
|
50217
|
+
return {
|
|
50218
|
+
task_id: task.taskId,
|
|
50219
|
+
short_id: task.shortId,
|
|
50220
|
+
title: task.title,
|
|
50221
|
+
description: task.description,
|
|
50222
|
+
tags: task.tags,
|
|
50223
|
+
latest_instruction: task.latestInstruction,
|
|
50224
|
+
status: task.status,
|
|
50225
|
+
assignee_type: task.assigneeType,
|
|
50226
|
+
assignee_hash: task.assigneeHash,
|
|
50227
|
+
primary_chat_id: task.primaryChatId,
|
|
50228
|
+
linked_project_ids: task.linkedProjectIds,
|
|
50229
|
+
plan_id: task.planId,
|
|
50230
|
+
due_at: task.dueAt,
|
|
50231
|
+
priority: task.priority,
|
|
50232
|
+
position: task.position,
|
|
50233
|
+
queue_state: task.queueState,
|
|
50234
|
+
blocked_reason_code: task.blockedReasonCode,
|
|
50235
|
+
ai_execution_state: task.aiExecutionState,
|
|
50236
|
+
version: task.version
|
|
50237
|
+
};
|
|
50238
|
+
}
|
|
50239
|
+
function taskTitleFromFlagsOrRest(flags, rest) {
|
|
50240
|
+
const title = typeof flags.title === "string" ? flags.title : rest.join(" ").trim();
|
|
50241
|
+
if (!title) throw new Error("Missing task title. Usage: openmates tasks create --title <title>");
|
|
50242
|
+
return title;
|
|
50243
|
+
}
|
|
50244
|
+
function taskAssignFlag(flags) {
|
|
50245
|
+
return typeof flags.assign === "string" ? flags.assign : typeof flags.assignee === "string" ? flags.assignee : void 0;
|
|
50246
|
+
}
|
|
48190
50247
|
async function handleRemoteAccess(client, subcommand, rest, flags) {
|
|
48191
50248
|
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
48192
50249
|
printRemoteAccessHelp();
|
|
@@ -48197,7 +50254,7 @@ async function handleRemoteAccess(client, subcommand, rest, flags) {
|
|
|
48197
50254
|
if (!rootPath) {
|
|
48198
50255
|
throw new Error("Missing source path. Usage: openmates remote-access start --path <folder>");
|
|
48199
50256
|
}
|
|
48200
|
-
const sourceId = typeof flags["source-id"] === "string" ? flags["source-id"] :
|
|
50257
|
+
const sourceId = typeof flags["source-id"] === "string" ? flags["source-id"] : randomUUID6();
|
|
48201
50258
|
const projectId = typeof flags.project === "string" ? flags.project : void 0;
|
|
48202
50259
|
validateRemoteSourceRegistrationFlags(projectId, flags);
|
|
48203
50260
|
const sourceType = parseRemoteAccessSourceType(flags.type);
|
|
@@ -48303,11 +50360,57 @@ function parseJsonFlag(value, flagName) {
|
|
|
48303
50360
|
throw new Error(`Invalid JSON for ${flagName}: ${message}`);
|
|
48304
50361
|
}
|
|
48305
50362
|
}
|
|
50363
|
+
async function handleDrafts(client, subcommand, rest, flags) {
|
|
50364
|
+
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
50365
|
+
printDraftsHelp();
|
|
50366
|
+
return;
|
|
50367
|
+
}
|
|
50368
|
+
if (subcommand === "create" || subcommand === "update") {
|
|
50369
|
+
const chatId = subcommand === "update" ? rest[0] : typeof flags.chat === "string" ? flags.chat : void 0;
|
|
50370
|
+
const markdown = subcommand === "update" ? rest.slice(1).join(" ").trim() : rest.join(" ").trim();
|
|
50371
|
+
if (subcommand === "update" && !chatId) throw new Error("Missing chat ID for draft update.");
|
|
50372
|
+
if (!markdown) throw new Error(`Missing draft text for draft ${subcommand}.`);
|
|
50373
|
+
const draft = await client.saveDraft({
|
|
50374
|
+
chatId,
|
|
50375
|
+
markdown,
|
|
50376
|
+
preview: typeof flags.preview === "string" ? flags.preview : markdown.slice(0, 160)
|
|
50377
|
+
});
|
|
50378
|
+
printJson2(draft);
|
|
50379
|
+
return;
|
|
50380
|
+
}
|
|
50381
|
+
if (subcommand === "list") {
|
|
50382
|
+
const drafts = await client.listDrafts(flags.refresh === true);
|
|
50383
|
+
printJson2({ drafts });
|
|
50384
|
+
return;
|
|
50385
|
+
}
|
|
50386
|
+
if (subcommand === "get") {
|
|
50387
|
+
const chatId = rest[0];
|
|
50388
|
+
if (!chatId) throw new Error("Missing chat ID for draft get.");
|
|
50389
|
+
printJson2({ draft: await client.getDraft(chatId, flags.refresh === true) });
|
|
50390
|
+
return;
|
|
50391
|
+
}
|
|
50392
|
+
if (subcommand === "clear") {
|
|
50393
|
+
const chatId = rest[0];
|
|
50394
|
+
if (!chatId) throw new Error("Missing chat ID for draft clear.");
|
|
50395
|
+
await client.clearDraft(chatId);
|
|
50396
|
+
printJson2({ success: true, chat_id: chatId });
|
|
50397
|
+
return;
|
|
50398
|
+
}
|
|
50399
|
+
if (subcommand === "sync") {
|
|
50400
|
+
printJson2({ versions: await client.reconcileDraftVersions() });
|
|
50401
|
+
return;
|
|
50402
|
+
}
|
|
50403
|
+
throw new Error(`Unknown drafts subcommand '${subcommand}'.`);
|
|
50404
|
+
}
|
|
48306
50405
|
async function handleChats(client, subcommand, rest, flags, redactor) {
|
|
48307
50406
|
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
48308
50407
|
printChatsHelp();
|
|
48309
50408
|
return;
|
|
48310
50409
|
}
|
|
50410
|
+
if (rest[0] === "tasks") {
|
|
50411
|
+
await handleTasks(client, rest[1], rest.slice(2), { ...flags, chat: subcommand });
|
|
50412
|
+
return;
|
|
50413
|
+
}
|
|
48311
50414
|
if (subcommand === "list") {
|
|
48312
50415
|
const limit = typeof flags.limit === "string" ? parseInt(flags.limit, 10) : 10;
|
|
48313
50416
|
const page = typeof flags.page === "string" ? parseInt(flags.page, 10) : 1;
|
|
@@ -48354,6 +50457,7 @@ async function handleChats(client, subcommand, rest, flags, redactor) {
|
|
|
48354
50457
|
json: flags.json === true,
|
|
48355
50458
|
autoApproveSubChats: flags["auto-approve"] === true,
|
|
48356
50459
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50460
|
+
acceptTaskProposals: flags["accept-task-proposals"] === true,
|
|
48357
50461
|
piiDetection: flags["no-pii-detection"] !== true,
|
|
48358
50462
|
anonymousLearningMode: client.hasSession() ? void 0 : parseAnonymousLearningModeFlags(flags)
|
|
48359
50463
|
},
|
|
@@ -48429,6 +50533,7 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
48429
50533
|
json: flags.json === true,
|
|
48430
50534
|
autoApproveSubChats: flags["auto-approve"] === true,
|
|
48431
50535
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50536
|
+
acceptTaskProposals: flags["accept-task-proposals"] === true,
|
|
48432
50537
|
piiDetection: flags["no-pii-detection"] !== true
|
|
48433
50538
|
},
|
|
48434
50539
|
redactor
|
|
@@ -48457,6 +50562,7 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
|
|
|
48457
50562
|
json: flags.json === true,
|
|
48458
50563
|
autoApproveSubChats: flags["auto-approve"] === true,
|
|
48459
50564
|
autoApproveMemories: flags["auto-approve-memories"] === true,
|
|
50565
|
+
acceptTaskProposals: flags["accept-task-proposals"] === true,
|
|
48460
50566
|
piiDetection: flags["no-pii-detection"] !== true
|
|
48461
50567
|
},
|
|
48462
50568
|
redactor
|
|
@@ -48990,7 +51096,37 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
48990
51096
|
}
|
|
48991
51097
|
return;
|
|
48992
51098
|
}
|
|
51099
|
+
if (subcommand === "validate") {
|
|
51100
|
+
const file = typeof flags.file === "string" ? flags.file : "";
|
|
51101
|
+
if (!file) throw new Error("Missing --file. Example: openmates workflows validate --file workflow.yml");
|
|
51102
|
+
const validation = await client.validateWorkflowYaml(readFileSync9(file, "utf8"));
|
|
51103
|
+
if (flags.json === true) {
|
|
51104
|
+
printJson2(validation);
|
|
51105
|
+
} else {
|
|
51106
|
+
kv("Draft valid", validation.draft_valid ? "yes" : "no");
|
|
51107
|
+
kv("Ready to enable", validation.enable_ready ? "yes" : "no");
|
|
51108
|
+
for (const diagnostic of validation.diagnostics) {
|
|
51109
|
+
console.log(` - ${String(diagnostic.path ?? "$")}: ${String(diagnostic.message ?? diagnostic.code ?? "invalid")}`);
|
|
51110
|
+
}
|
|
51111
|
+
}
|
|
51112
|
+
if (!validation.draft_valid) process.exitCode = 1;
|
|
51113
|
+
return;
|
|
51114
|
+
}
|
|
48993
51115
|
if (subcommand === "create") {
|
|
51116
|
+
const yamlFile = typeof flags.file === "string" ? flags.file : "";
|
|
51117
|
+
if (yamlFile) {
|
|
51118
|
+
const result = await client.createWorkflowYaml(readFileSync9(yamlFile, "utf8"));
|
|
51119
|
+
if (flags.json === true) {
|
|
51120
|
+
printJson2(result);
|
|
51121
|
+
} else {
|
|
51122
|
+
printWorkflowDetail(result.workflow);
|
|
51123
|
+
kv("Ready to enable", result.validation.enable_ready ? "yes" : "no");
|
|
51124
|
+
for (const diagnostic of result.validation.diagnostics) {
|
|
51125
|
+
console.log(` - ${String(diagnostic.path ?? "$")}: ${String(diagnostic.message ?? diagnostic.code ?? "input required")}`);
|
|
51126
|
+
}
|
|
51127
|
+
}
|
|
51128
|
+
return;
|
|
51129
|
+
}
|
|
48994
51130
|
const title = typeof flags.title === "string" ? flags.title.trim() : "";
|
|
48995
51131
|
const graphJson = typeof flags.graph === "string" ? flags.graph : "";
|
|
48996
51132
|
if (!title) throw new Error("Missing --title for workflow create.");
|
|
@@ -49008,6 +51144,22 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
49008
51144
|
}
|
|
49009
51145
|
return;
|
|
49010
51146
|
}
|
|
51147
|
+
if (subcommand === "update") {
|
|
51148
|
+
const workflowId = rest[0];
|
|
51149
|
+
const yamlFile = typeof flags.file === "string" ? flags.file : "";
|
|
51150
|
+
if (!workflowId || !yamlFile) throw new Error("Missing workflow ID or --file. Example: openmates workflows update <id> --file workflow.yml");
|
|
51151
|
+
const result = await client.updateWorkflowYaml(workflowId, readFileSync9(yamlFile, "utf8"));
|
|
51152
|
+
if (flags.json === true) {
|
|
51153
|
+
printJson2(result);
|
|
51154
|
+
} else {
|
|
51155
|
+
printWorkflowDetail(result.workflow);
|
|
51156
|
+
kv("Ready to enable", result.validation.enable_ready ? "yes" : "no");
|
|
51157
|
+
for (const diagnostic of result.validation.diagnostics) {
|
|
51158
|
+
console.log(` - ${String(diagnostic.path ?? "$")}: ${String(diagnostic.message ?? diagnostic.code ?? "input required")}`);
|
|
51159
|
+
}
|
|
51160
|
+
}
|
|
51161
|
+
return;
|
|
51162
|
+
}
|
|
49011
51163
|
if (subcommand === "input") {
|
|
49012
51164
|
const text = typeof flags.text === "string" ? flags.text : rest.join(" ").trim();
|
|
49013
51165
|
if (!text) throw new Error('Missing workflow input text. Example: openmates workflows input "alert me if it rains"');
|
|
@@ -49114,9 +51266,11 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
49114
51266
|
if (subcommand === "run") {
|
|
49115
51267
|
const workflowId = rest[0];
|
|
49116
51268
|
if (!workflowId) throw new Error("Missing workflow ID. Example: openmates workflows run <id>");
|
|
51269
|
+
const idempotencyKey = typeof flags["idempotency-key"] === "string" ? flags["idempotency-key"] : "";
|
|
51270
|
+
if (!idempotencyKey) throw new Error("Missing --idempotency-key. Reuse this stable key when retrying the same workflow run.");
|
|
49117
51271
|
const mode = flags.mode === "test" ? "test" : "manual";
|
|
49118
51272
|
const input = typeof flags.input === "string" ? parseJsonFlag(flags.input, "--input") : {};
|
|
49119
|
-
const run = await client.runWorkflow(workflowId, { mode, input });
|
|
51273
|
+
const run = await client.runWorkflow(workflowId, { idempotencyKey, mode, input });
|
|
49120
51274
|
if (flags.json === true) {
|
|
49121
51275
|
printJson2(run);
|
|
49122
51276
|
} else {
|
|
@@ -49147,6 +51301,58 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
49147
51301
|
}
|
|
49148
51302
|
return;
|
|
49149
51303
|
}
|
|
51304
|
+
if (subcommand === "run-cancel") {
|
|
51305
|
+
const workflowId = rest[0];
|
|
51306
|
+
const runId = rest[1];
|
|
51307
|
+
if (!workflowId || !runId) throw new Error("Missing workflow/run ID. Example: openmates workflows run-cancel <workflow-id> <run-id>");
|
|
51308
|
+
const result = await client.cancelWorkflowRun(workflowId, runId);
|
|
51309
|
+
if (flags.json === true) {
|
|
51310
|
+
printJson2(result);
|
|
51311
|
+
} else {
|
|
51312
|
+
kv("Status", result.status);
|
|
51313
|
+
}
|
|
51314
|
+
return;
|
|
51315
|
+
}
|
|
51316
|
+
if (subcommand === "step-test") {
|
|
51317
|
+
const workflowId = rest[0];
|
|
51318
|
+
const stepId = rest[1];
|
|
51319
|
+
if (!workflowId || !stepId) throw new Error("Missing workflow/step ID. Example: openmates workflows step-test <workflow-id> <step-id> --yes");
|
|
51320
|
+
const input = typeof flags.input === "string" ? parseJsonFlag(flags.input, "--input") : {};
|
|
51321
|
+
const run = await client.testWorkflowStep(workflowId, stepId, { input, confirmed: flags.yes === true });
|
|
51322
|
+
if (flags.json === true) {
|
|
51323
|
+
printJson2(run);
|
|
51324
|
+
} else {
|
|
51325
|
+
printWorkflowRun(run);
|
|
51326
|
+
}
|
|
51327
|
+
return;
|
|
51328
|
+
}
|
|
51329
|
+
if (subcommand === "respond") {
|
|
51330
|
+
const workflowId = rest[0];
|
|
51331
|
+
const runId = rest[1];
|
|
51332
|
+
const stepId = rest[2];
|
|
51333
|
+
if (!workflowId || !runId || !stepId) throw new Error(`Missing workflow/run/step ID. Example: openmates workflows respond <workflow-id> <run-id> <step-id> --input '{"answer":"Berlin"}'`);
|
|
51334
|
+
const input = typeof flags.input === "string" ? parseJsonFlag(flags.input, "--input") : {};
|
|
51335
|
+
const run = await client.respondToWorkflowRun(workflowId, runId, stepId, input);
|
|
51336
|
+
if (flags.json === true) {
|
|
51337
|
+
printJson2(run);
|
|
51338
|
+
} else {
|
|
51339
|
+
printWorkflowRun(run);
|
|
51340
|
+
}
|
|
51341
|
+
return;
|
|
51342
|
+
}
|
|
51343
|
+
if (subcommand === "help-app") {
|
|
51344
|
+
const capabilityId = rest[0];
|
|
51345
|
+
if (!capabilityId) throw new Error("Missing app skill. Example: openmates workflows help-app weather.forecast");
|
|
51346
|
+
const capabilities = await client.listWorkflowCapabilities();
|
|
51347
|
+
const capability = capabilities.find((item) => item.id === capabilityId || item.id === capabilityId.replace(":", "."));
|
|
51348
|
+
if (!capability) throw new Error(`Workflow capability not found: ${capabilityId}`);
|
|
51349
|
+
if (flags.json === true) {
|
|
51350
|
+
printJson2(capability);
|
|
51351
|
+
} else {
|
|
51352
|
+
printWorkflowCapabilityHelp(capability);
|
|
51353
|
+
}
|
|
51354
|
+
return;
|
|
51355
|
+
}
|
|
49150
51356
|
throw new Error(`Unknown workflows command '${subcommand}'. Run 'openmates workflows --help'.`);
|
|
49151
51357
|
}
|
|
49152
51358
|
function printWorkflowList(workflows) {
|
|
@@ -49216,6 +51422,30 @@ function printWorkflowCapabilities(capabilities) {
|
|
|
49216
51422
|
kv(capability.id, `${capability.title} \xB7 ${state}`, 24);
|
|
49217
51423
|
}
|
|
49218
51424
|
}
|
|
51425
|
+
function printWorkflowCapabilityHelp(capability) {
|
|
51426
|
+
header(`${capability.title}
|
|
51427
|
+
`);
|
|
51428
|
+
kv("ID", capability.id);
|
|
51429
|
+
kv("Type", capability.type);
|
|
51430
|
+
kv("Enabled", capability.enabled ? "yes" : "no");
|
|
51431
|
+
if (capability.reason) kv("Reason", capability.reason);
|
|
51432
|
+
const metadata = capability.metadata ?? {};
|
|
51433
|
+
const workflow = metadata.workflow;
|
|
51434
|
+
if (workflow) {
|
|
51435
|
+
kv("Effect", String(workflow.effect ?? "unknown"));
|
|
51436
|
+
kv("Execution", String(workflow.execution_mode ?? "unknown"));
|
|
51437
|
+
kv("Approval", String(workflow.approval ?? "unknown"));
|
|
51438
|
+
kv("Unattended", workflow.unattended === true ? "yes" : "no");
|
|
51439
|
+
}
|
|
51440
|
+
if (metadata.input_schema) {
|
|
51441
|
+
console.log("\nInput schema:");
|
|
51442
|
+
console.log(JSON.stringify(metadata.input_schema, null, 2));
|
|
51443
|
+
}
|
|
51444
|
+
if (workflow?.test_example_input) {
|
|
51445
|
+
console.log("\nTest example:");
|
|
51446
|
+
console.log(JSON.stringify(workflow.test_example_input, null, 2));
|
|
51447
|
+
}
|
|
51448
|
+
}
|
|
49219
51449
|
async function handleApps(client, subcommand, rest, flags) {
|
|
49220
51450
|
const apiKey = resolveApiKey(flags) ?? void 0;
|
|
49221
51451
|
if (!subcommand || subcommand === "help") {
|
|
@@ -49452,7 +51682,7 @@ Run with --help for full parameter details:
|
|
|
49452
51682
|
}
|
|
49453
51683
|
}
|
|
49454
51684
|
try {
|
|
49455
|
-
const inputData = buildSkillInput(flags, inlineTokens, schemaParams);
|
|
51685
|
+
const inputData = app === "models3d" && skill === "generate" ? await buildModels3DGenerateInput(client, flags, inlineTokens, schemaParams) : buildSkillInput(flags, inlineTokens, schemaParams);
|
|
49456
51686
|
const data = await client.runSkill({ app, skill, inputData, apiKey });
|
|
49457
51687
|
if (flags.json === true) {
|
|
49458
51688
|
printJson2(data);
|
|
@@ -49657,6 +51887,27 @@ function buildSkillInput(flags, inlineTokens, schemaParams) {
|
|
|
49657
51887
|
}
|
|
49658
51888
|
return {};
|
|
49659
51889
|
}
|
|
51890
|
+
async function buildModels3DGenerateInput(client, flags, inlineTokens, schemaParams) {
|
|
51891
|
+
const imagePath = typeof flags.image === "string" ? flags.image : void 0;
|
|
51892
|
+
if (!imagePath) return buildSkillInput(flags, inlineTokens, schemaParams);
|
|
51893
|
+
if (typeof flags.input === "string" || inlineTokens.length > 0) {
|
|
51894
|
+
throw new Error("Use either a text prompt or --image <path> for 3D generation, not both.");
|
|
51895
|
+
}
|
|
51896
|
+
if (!client.hasSession()) {
|
|
51897
|
+
throw new Error("Image-based 3D generation requires `openmates login` to upload the reference image.");
|
|
51898
|
+
}
|
|
51899
|
+
const processed = processFiles([imagePath], null);
|
|
51900
|
+
if (processed.blocked.length > 0 || processed.errors.length > 0 || processed.embeds.length !== 1) {
|
|
51901
|
+
const reason = [...processed.blocked, ...processed.errors].map((entry) => entry.error).join("; ") || "no image embed produced";
|
|
51902
|
+
throw new Error(`Failed to prepare 3D reference image: ${reason}`);
|
|
51903
|
+
}
|
|
51904
|
+
const fileEmbed = processed.embeds[0];
|
|
51905
|
+
if (!fileEmbed.requiresUpload || !fileEmbed.localPath || fileEmbed.embed.type !== "image") {
|
|
51906
|
+
throw new Error("3D generation requires a supported image file.");
|
|
51907
|
+
}
|
|
51908
|
+
const uploadResult = await uploadFile(fileEmbed.localPath, client.getSession());
|
|
51909
|
+
return { image_embed_refs: [uploadResult.embed_id] };
|
|
51910
|
+
}
|
|
49660
51911
|
function getEffectiveRequiredParams(schemaParams) {
|
|
49661
51912
|
const required = schemaParams.filter((p) => p.required);
|
|
49662
51913
|
if (required.length > 0) return required;
|
|
@@ -50201,7 +52452,7 @@ async function confirmOrExit(question) {
|
|
|
50201
52452
|
process.exit(0);
|
|
50202
52453
|
}
|
|
50203
52454
|
}
|
|
50204
|
-
async function
|
|
52455
|
+
async function promptLine2(question) {
|
|
50205
52456
|
const rl = await import("readline");
|
|
50206
52457
|
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
50207
52458
|
const answer = await new Promise((resolve7) => iface.question(question, resolve7));
|
|
@@ -50210,7 +52461,7 @@ async function promptLine(question) {
|
|
|
50210
52461
|
}
|
|
50211
52462
|
async function promptSecret(question) {
|
|
50212
52463
|
if (!process.stdin.isTTY) {
|
|
50213
|
-
return
|
|
52464
|
+
return promptLine2(question);
|
|
50214
52465
|
}
|
|
50215
52466
|
return new Promise((resolve7) => {
|
|
50216
52467
|
const stdin2 = process.stdin;
|
|
@@ -50297,8 +52548,8 @@ async function writeSecretFile(filePath, content, force = false) {
|
|
|
50297
52548
|
return filePath;
|
|
50298
52549
|
}
|
|
50299
52550
|
async function generateProvisioningPassword() {
|
|
50300
|
-
const { randomBytes:
|
|
50301
|
-
return `OM-${
|
|
52551
|
+
const { randomBytes: randomBytes4 } = await import("crypto");
|
|
52552
|
+
return `OM-${randomBytes4(18).toString("base64url")}-aA2#`;
|
|
50302
52553
|
}
|
|
50303
52554
|
function decodeBase32(input) {
|
|
50304
52555
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
@@ -50333,7 +52584,7 @@ async function runSecuritySetup(client, flags, options = {}) {
|
|
|
50333
52584
|
result.otpSecret = setup.secret ?? null;
|
|
50334
52585
|
if (setup.otpauth_url) client.renderTotpQrCode(setup.otpauth_url);
|
|
50335
52586
|
if (setup.secret) console.log(`Manual TOTP secret: ${setup.secret}`);
|
|
50336
|
-
const code = typeof process.env.OPENMATES_CLI_SIGNUP_TOTP_CODE === "string" ? process.env.OPENMATES_CLI_SIGNUP_TOTP_CODE : options.autoGenerateTotp && setup.secret ? await generateTotpCode(setup.secret) : await
|
|
52587
|
+
const code = typeof process.env.OPENMATES_CLI_SIGNUP_TOTP_CODE === "string" ? process.env.OPENMATES_CLI_SIGNUP_TOTP_CODE : options.autoGenerateTotp && setup.secret ? await generateTotpCode(setup.secret) : await promptLine2("Enter current 2FA code: ");
|
|
50337
52588
|
await client.verifyTotpSetup(code);
|
|
50338
52589
|
const provider = typeof flags.provider === "string" ? flags.provider : "Authenticator app";
|
|
50339
52590
|
await client.setTotpProvider(provider);
|
|
@@ -50373,8 +52624,8 @@ async function handleSignup(client, flags, options = {}) {
|
|
|
50373
52624
|
if (flags.password !== void 0) {
|
|
50374
52625
|
throw new Error("Passwords must be entered through hidden prompts, not command-line flags.");
|
|
50375
52626
|
}
|
|
50376
|
-
const email = typeof flags.email === "string" ? flags.email : await
|
|
50377
|
-
const username = typeof flags.username === "string" ? flags.username : await
|
|
52627
|
+
const email = typeof flags.email === "string" ? flags.email : await promptLine2("Email: ");
|
|
52628
|
+
const username = typeof flags.username === "string" ? flags.username : await promptLine2("Username: ");
|
|
50378
52629
|
const inviteCode = typeof flags["invite-code"] === "string" ? flags["invite-code"] : "";
|
|
50379
52630
|
const language = typeof flags.language === "string" ? flags.language : "en";
|
|
50380
52631
|
const password = process.env.OPENMATES_CLI_SIGNUP_PASSWORD ?? await promptSecret("Password: ");
|
|
@@ -50383,7 +52634,7 @@ async function handleSignup(client, flags, options = {}) {
|
|
|
50383
52634
|
if (password !== confirmPassword) throw new Error("Passwords do not match.");
|
|
50384
52635
|
}
|
|
50385
52636
|
await client.requestSignupEmailCode({ email, inviteCode, language });
|
|
50386
|
-
const emailCode = process.env.OPENMATES_CLI_SIGNUP_EMAIL_CODE ?? await
|
|
52637
|
+
const emailCode = process.env.OPENMATES_CLI_SIGNUP_EMAIL_CODE ?? await promptLine2("Email verification code: ");
|
|
50387
52638
|
await client.verifySignupEmailCode({ email, username, inviteCode, code: emailCode, language });
|
|
50388
52639
|
const signup = await client.setupPasswordAccount({ email, username, password, inviteCode, language });
|
|
50389
52640
|
const security = await runSecuritySetup(client, flags, options);
|
|
@@ -50425,13 +52676,15 @@ async function handleE2E(client, subcommand, rest, flags) {
|
|
|
50425
52676
|
throw new Error("E2E provisioning refuses production API URLs.");
|
|
50426
52677
|
}
|
|
50427
52678
|
if (flags.password !== void 0) throw new Error("Use generated passwords or OPENMATES_CLI_SIGNUP_PASSWORD, not --password.");
|
|
52679
|
+
const inviteCode = process.env.OPENMATES_CLI_SIGNUP_INVITE_CODE;
|
|
52680
|
+
if (!inviteCode) throw new Error("OPENMATES_CLI_SIGNUP_INVITE_CODE is required for E2E provisioning.");
|
|
50428
52681
|
const slot = parseRequiredNumber(flags.slot, "--slot");
|
|
50429
|
-
if (![15, 16, 17, 18, 19, 20].includes(slot)) {
|
|
50430
|
-
throw new Error("Only reserved slots
|
|
52682
|
+
if (![14, 15, 16, 17, 18, 19, 20].includes(slot)) {
|
|
52683
|
+
throw new Error("Only reserved slots 14-20 are supported.");
|
|
50431
52684
|
}
|
|
50432
52685
|
const artifact = typeof flags.artifact === "string" ? flags.artifact : `test-results/credential-updates/slot-${slot}.env`;
|
|
50433
52686
|
const domain = typeof flags.domain === "string" ? flags.domain : process.env.OPENMATES_CLI_E2E_EMAIL_DOMAIN;
|
|
50434
|
-
const email = typeof flags.email === "string" ? flags.email : domain ? `cli-e2e-slot-${slot}-${Date.now()}@${domain}` : await
|
|
52687
|
+
const email = typeof flags.email === "string" ? flags.email : domain ? `cli-e2e-slot-${slot}-${Date.now()}@${domain}` : await promptLine2("E2E account email: ");
|
|
50435
52688
|
const username = typeof flags.username === "string" ? flags.username : `cli_e2e_slot_${slot}_${Date.now()}`;
|
|
50436
52689
|
const generatedPassword = process.env.OPENMATES_CLI_SIGNUP_PASSWORD ?? await generateProvisioningPassword();
|
|
50437
52690
|
const originalSignupPassword = process.env.OPENMATES_CLI_SIGNUP_PASSWORD;
|
|
@@ -50442,6 +52695,7 @@ async function handleE2E(client, subcommand, rest, flags) {
|
|
|
50442
52695
|
...flags,
|
|
50443
52696
|
email,
|
|
50444
52697
|
username,
|
|
52698
|
+
"invite-code": inviteCode,
|
|
50445
52699
|
yes: true,
|
|
50446
52700
|
json: false,
|
|
50447
52701
|
"backup-codes-output": typeof flags["backup-codes-output"] === "string" ? flags["backup-codes-output"] : `${artifact}.backup-codes`,
|
|
@@ -50636,10 +52890,10 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
50636
52890
|
await confirmOrExit("Delete this account and all associated data? [y/N] ");
|
|
50637
52891
|
}
|
|
50638
52892
|
await client.requestDeleteAccountEmailCode();
|
|
50639
|
-
const emailCode = await
|
|
52893
|
+
const emailCode = await promptLine2("Enter email verification code: ");
|
|
50640
52894
|
await client.verifyDeleteAccountEmailCode(emailCode);
|
|
50641
52895
|
const authMethods = await client.getAuthMethodsStatus();
|
|
50642
|
-
const totpCode = authMethods.has_2fa ? await
|
|
52896
|
+
const totpCode = authMethods.has_2fa ? await promptLine2("Enter 2FA code: ") : void 0;
|
|
50643
52897
|
const result = await client.deleteAccountWithCliVerification(totpCode);
|
|
50644
52898
|
if (flags.json === true) printJson2(result);
|
|
50645
52899
|
else console.log("\x1B[32m\u2713\x1B[0m Account deletion requested and local CLI session cleared");
|
|
@@ -51358,11 +53612,11 @@ function ansiMateBlock(category, mateName) {
|
|
|
51358
53612
|
const [r, g, b] = CATEGORY_ANSI_COLORS[category];
|
|
51359
53613
|
return `\x1B[48;2;${r};${g};${b}m\x1B[97m ${label} \x1B[0m`;
|
|
51360
53614
|
}
|
|
51361
|
-
function
|
|
53615
|
+
function formatTimestamp2(ts) {
|
|
51362
53616
|
if (!ts) return "\u2014";
|
|
51363
53617
|
const d = new Date(ts * 1e3);
|
|
51364
|
-
const
|
|
51365
|
-
return `${d.getFullYear()}-${
|
|
53618
|
+
const pad2 = (n) => String(n).padStart(2, "0");
|
|
53619
|
+
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
51366
53620
|
}
|
|
51367
53621
|
function printChatsTable(result) {
|
|
51368
53622
|
const { chats, total, page, limit, hasMore } = result;
|
|
@@ -51381,7 +53635,7 @@ function printChatsTable(result) {
|
|
|
51381
53635
|
);
|
|
51382
53636
|
for (const chat of chats) {
|
|
51383
53637
|
const block = ansiColorBlock(chat.category);
|
|
51384
|
-
const time =
|
|
53638
|
+
const time = formatTimestamp2(chat.updatedAt);
|
|
51385
53639
|
const title = chat.title ?? "(no title)";
|
|
51386
53640
|
const idStr = chat.shortId;
|
|
51387
53641
|
const sourceLabel = chat.source === "example" ? " \x1B[33mEXAMPLE CHAT\x1B[0m" : "";
|
|
@@ -51717,7 +53971,7 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
51717
53971
|
process.stdout.write(`${result2.assistant}
|
|
51718
53972
|
`);
|
|
51719
53973
|
}
|
|
51720
|
-
return result2;
|
|
53974
|
+
return { ...result2, acceptedTaskProposals: [] };
|
|
51721
53975
|
}
|
|
51722
53976
|
const urlResult = prepareUrlEmbeds(finalMessage);
|
|
51723
53977
|
finalMessage = urlResult.message;
|
|
@@ -51741,6 +53995,9 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
51741
53995
|
}))
|
|
51742
53996
|
});
|
|
51743
53997
|
clearTyping();
|
|
53998
|
+
const acceptedTaskProposals = params.acceptTaskProposals === true && result.status === "completed" ? await acceptChatTaskProposals(client, result.chatId, result.taskProposals, `${finalMessage}
|
|
53999
|
+
|
|
54000
|
+
${result.assistant}`) : [];
|
|
51744
54001
|
if (result.status === "waiting_for_user") {
|
|
51745
54002
|
const question = parseInteractiveQuestionBlock(result.assistant);
|
|
51746
54003
|
if (params.json && question) {
|
|
@@ -51756,7 +54013,7 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
51756
54013
|
"\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"
|
|
51757
54014
|
);
|
|
51758
54015
|
}
|
|
51759
|
-
return result;
|
|
54016
|
+
return { ...result, acceptedTaskProposals: [] };
|
|
51760
54017
|
}
|
|
51761
54018
|
if (!params.json) {
|
|
51762
54019
|
if (!headerPrinted) {
|
|
@@ -51826,7 +54083,43 @@ async function sendMessageStreaming(client, params, redactor) {
|
|
|
51826
54083
|
`
|
|
51827
54084
|
);
|
|
51828
54085
|
}
|
|
51829
|
-
return result;
|
|
54086
|
+
return { ...result, acceptedTaskProposals };
|
|
54087
|
+
}
|
|
54088
|
+
async function acceptChatTaskProposals(client, chatId, proposals, fallbackText) {
|
|
54089
|
+
let proposalsToAccept = proposals;
|
|
54090
|
+
if (proposalsToAccept.length === 0 && fallbackText.trim()) {
|
|
54091
|
+
const extractionText = buildTaskProposalFallbackText(fallbackText);
|
|
54092
|
+
proposalsToAccept = await client.extractUserTaskProposals({
|
|
54093
|
+
correctedText: extractionText,
|
|
54094
|
+
contextChatId: chatId
|
|
54095
|
+
});
|
|
54096
|
+
}
|
|
54097
|
+
if (proposalsToAccept.length === 0) return [];
|
|
54098
|
+
const masterKey = client.getMasterKeyBytes();
|
|
54099
|
+
const accepted = [];
|
|
54100
|
+
for (const proposal of proposalsToAccept) {
|
|
54101
|
+
const input = await buildCreateUserTaskInput(masterKey, {
|
|
54102
|
+
title: proposal.title,
|
|
54103
|
+
description: proposal.description ?? "",
|
|
54104
|
+
status: proposal.status,
|
|
54105
|
+
assign: proposal.assignee_type ?? "user",
|
|
54106
|
+
chatId
|
|
54107
|
+
});
|
|
54108
|
+
const created = await client.createUserTask(input);
|
|
54109
|
+
const decrypted = await decryptUserTask(created, masterKey);
|
|
54110
|
+
accepted.push(taskToJson(decrypted));
|
|
54111
|
+
}
|
|
54112
|
+
return accepted;
|
|
54113
|
+
}
|
|
54114
|
+
function buildTaskProposalFallbackText(text) {
|
|
54115
|
+
const seen = /* @__PURE__ */ new Set();
|
|
54116
|
+
const bulletLines = text.split("\n").map((line) => line.trim()).filter((line) => /^[-*•]\s+/.test(line)).map((line) => line.replace(/^([-*•]\s*)\[[ xX]\]\s*/, "$1")).filter((line) => {
|
|
54117
|
+
const key = line.replace(/^[-*•]\s+/, "").trim().toLowerCase();
|
|
54118
|
+
if (seen.has(key)) return false;
|
|
54119
|
+
seen.add(key);
|
|
54120
|
+
return true;
|
|
54121
|
+
});
|
|
54122
|
+
return bulletLines.length > 0 ? bulletLines.join("\n") : text;
|
|
51830
54123
|
}
|
|
51831
54124
|
function printIncognitoNoHistoryNotice(json) {
|
|
51832
54125
|
const message = "Incognito chats are not stored. There is no incognito history to show or clear.";
|
|
@@ -51942,7 +54235,7 @@ async function renderMessageText(text, embedRefIndex, client) {
|
|
|
51942
54235
|
async function printChatConversation(client, chat, messages, followUpSuggestions, options = {}) {
|
|
51943
54236
|
const block = ansiColorBlock(chat.category);
|
|
51944
54237
|
const title = chat.title ?? "(no title)";
|
|
51945
|
-
const ts =
|
|
54238
|
+
const ts = formatTimestamp2(chat.updatedAt);
|
|
51946
54239
|
if (options.example) {
|
|
51947
54240
|
process.stdout.write("\x1B[33mEXAMPLE CHAT\x1B[0m\n");
|
|
51948
54241
|
process.stdout.write("\x1B[2mThis is a public example chat. Log in to create and sync your own private chats.\x1B[0m\n\n");
|
|
@@ -51972,7 +54265,7 @@ async function printChatConversation(client, chat, messages, followUpSuggestions
|
|
|
51972
54265
|
const embedRefIndex = hasEmbedRefs ? await client.buildEmbedRefIndex(parentEmbedIds) : /* @__PURE__ */ new Map();
|
|
51973
54266
|
for (let i = 0; i < messages.length; i++) {
|
|
51974
54267
|
const msg = messages[i];
|
|
51975
|
-
const msgTs =
|
|
54268
|
+
const msgTs = formatTimestamp2(msg.createdAt);
|
|
51976
54269
|
process.stdout.write(`${SEP}
|
|
51977
54270
|
`);
|
|
51978
54271
|
if (msg.role === "user") {
|
|
@@ -52033,7 +54326,7 @@ async function printChatConversation(client, chat, messages, followUpSuggestions
|
|
|
52033
54326
|
}
|
|
52034
54327
|
async function printChatConversationRaw(chat, messages, options = {}) {
|
|
52035
54328
|
const title = chat.title ?? "(no title)";
|
|
52036
|
-
const ts =
|
|
54329
|
+
const ts = formatTimestamp2(chat.updatedAt);
|
|
52037
54330
|
if (options.example) {
|
|
52038
54331
|
process.stdout.write("EXAMPLE CHAT\n");
|
|
52039
54332
|
process.stdout.write("This is a public example chat. Log in to create and sync your own private chats.\n\n");
|
|
@@ -52048,7 +54341,7 @@ async function printChatConversationRaw(chat, messages, options = {}) {
|
|
|
52048
54341
|
return;
|
|
52049
54342
|
}
|
|
52050
54343
|
for (const msg of messages) {
|
|
52051
|
-
const msgTs =
|
|
54344
|
+
const msgTs = formatTimestamp2(msg.createdAt);
|
|
52052
54345
|
process.stdout.write(`${SEP}
|
|
52053
54346
|
`);
|
|
52054
54347
|
const role = msg.role === "user" ? "You" : msg.senderName ?? msg.role;
|
|
@@ -52551,18 +54844,18 @@ function printWhoAmI(user) {
|
|
|
52551
54844
|
}
|
|
52552
54845
|
}
|
|
52553
54846
|
function printGenericObject(value, indent = 0) {
|
|
52554
|
-
const
|
|
54847
|
+
const pad2 = " ".repeat(indent);
|
|
52555
54848
|
if (value === null || value === void 0) {
|
|
52556
|
-
console.log(`${
|
|
54849
|
+
console.log(`${pad2}(empty)`);
|
|
52557
54850
|
return;
|
|
52558
54851
|
}
|
|
52559
54852
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
52560
|
-
console.log(`${
|
|
54853
|
+
console.log(`${pad2}${value}`);
|
|
52561
54854
|
return;
|
|
52562
54855
|
}
|
|
52563
54856
|
if (Array.isArray(value)) {
|
|
52564
54857
|
if (value.length === 0) {
|
|
52565
|
-
console.log(`${
|
|
54858
|
+
console.log(`${pad2}(empty list)`);
|
|
52566
54859
|
return;
|
|
52567
54860
|
}
|
|
52568
54861
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -52570,11 +54863,11 @@ function printGenericObject(value, indent = 0) {
|
|
|
52570
54863
|
if (item && typeof item === "object") {
|
|
52571
54864
|
printGenericObject(item, indent);
|
|
52572
54865
|
if (i < value.length - 1) {
|
|
52573
|
-
process.stdout.write(`${
|
|
54866
|
+
process.stdout.write(`${pad2}\x1B[2m${"\u2500".repeat(40)}\x1B[0m
|
|
52574
54867
|
`);
|
|
52575
54868
|
}
|
|
52576
54869
|
} else {
|
|
52577
|
-
console.log(`${
|
|
54870
|
+
console.log(`${pad2}${String(item)}`);
|
|
52578
54871
|
}
|
|
52579
54872
|
}
|
|
52580
54873
|
return;
|
|
@@ -52594,34 +54887,34 @@ function printGenericObject(value, indent = 0) {
|
|
|
52594
54887
|
if (Array.isArray(v)) {
|
|
52595
54888
|
if (v.length === 0) {
|
|
52596
54889
|
process.stdout.write(
|
|
52597
|
-
`${
|
|
54890
|
+
`${pad2} \x1B[2m${k.padEnd(20)}\x1B[0m (none)
|
|
52598
54891
|
`
|
|
52599
54892
|
);
|
|
52600
54893
|
} else if (v.length > 0 && typeof v[0] === "object") {
|
|
52601
54894
|
process.stdout.write(
|
|
52602
54895
|
`
|
|
52603
|
-
${
|
|
54896
|
+
${pad2} \x1B[1m${k}\x1B[0m \x1B[2m(${v.length})\x1B[0m
|
|
52604
54897
|
`
|
|
52605
54898
|
);
|
|
52606
54899
|
printGenericObject(v, indent + 1);
|
|
52607
54900
|
} else {
|
|
52608
54901
|
process.stdout.write(
|
|
52609
|
-
`${
|
|
54902
|
+
`${pad2} \x1B[2m${k.padEnd(20)}\x1B[0m ${v.join(", ")}
|
|
52610
54903
|
`
|
|
52611
54904
|
);
|
|
52612
54905
|
}
|
|
52613
54906
|
} else if (typeof v === "object") {
|
|
52614
|
-
process.stdout.write(`${
|
|
54907
|
+
process.stdout.write(`${pad2} \x1B[1m${k}\x1B[0m
|
|
52615
54908
|
`);
|
|
52616
54909
|
printGenericObject(v, indent + 1);
|
|
52617
54910
|
} else {
|
|
52618
|
-
process.stdout.write(`${
|
|
54911
|
+
process.stdout.write(`${pad2} \x1B[2m${k.padEnd(20)}\x1B[0m ${v}
|
|
52619
54912
|
`);
|
|
52620
54913
|
}
|
|
52621
54914
|
}
|
|
52622
54915
|
return;
|
|
52623
54916
|
}
|
|
52624
|
-
console.log(`${
|
|
54917
|
+
console.log(`${pad2}${String(value)}`);
|
|
52625
54918
|
}
|
|
52626
54919
|
function printBillingResponse(obj) {
|
|
52627
54920
|
header("Billing\n");
|
|
@@ -52697,7 +54990,7 @@ function printMemoriesList(memories) {
|
|
|
52697
54990
|
header(`Memories (${memories.length})
|
|
52698
54991
|
`);
|
|
52699
54992
|
for (const mem of memories) {
|
|
52700
|
-
const ts =
|
|
54993
|
+
const ts = formatTimestamp2(mem.updated_at);
|
|
52701
54994
|
process.stdout.write(
|
|
52702
54995
|
`\x1B[1m${mem.app_id}/${mem.item_type}\x1B[0m \x1B[2mv${mem.item_version} ${ts} id:${mem.id.slice(0, 8)}\x1B[0m
|
|
52703
54996
|
`
|
|
@@ -52947,46 +55240,46 @@ async function handleMentions(client, subcommand, _rest, flags) {
|
|
|
52947
55240
|
process.exit(1);
|
|
52948
55241
|
}
|
|
52949
55242
|
function serializeToYaml(data, indent = 0) {
|
|
52950
|
-
const
|
|
55243
|
+
const pad2 = " ".repeat(indent);
|
|
52951
55244
|
let out = "";
|
|
52952
55245
|
for (const [key, val] of Object.entries(data)) {
|
|
52953
55246
|
if (val === null || val === void 0) {
|
|
52954
|
-
out += `${
|
|
55247
|
+
out += `${pad2}${key}: null
|
|
52955
55248
|
`;
|
|
52956
55249
|
} else if (typeof val === "boolean" || typeof val === "number") {
|
|
52957
|
-
out += `${
|
|
55250
|
+
out += `${pad2}${key}: ${val}
|
|
52958
55251
|
`;
|
|
52959
55252
|
} else if (typeof val === "string") {
|
|
52960
55253
|
if (val.includes("\n")) {
|
|
52961
|
-
out += `${
|
|
55254
|
+
out += `${pad2}${key}: |
|
|
52962
55255
|
`;
|
|
52963
55256
|
for (const line of val.split("\n")) {
|
|
52964
|
-
out += `${
|
|
55257
|
+
out += `${pad2} ${line}
|
|
52965
55258
|
`;
|
|
52966
55259
|
}
|
|
52967
55260
|
} else {
|
|
52968
55261
|
const needsQuote = val.includes(":") || val.includes("#") || val.startsWith("{") || val.startsWith("[") || val.startsWith("'") || val.startsWith('"') || val === "" || val === "true" || val === "false" || val === "null";
|
|
52969
|
-
out += `${
|
|
55262
|
+
out += `${pad2}${key}: ${needsQuote ? `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : val}
|
|
52970
55263
|
`;
|
|
52971
55264
|
}
|
|
52972
55265
|
} else if (Array.isArray(val)) {
|
|
52973
|
-
out += `${
|
|
55266
|
+
out += `${pad2}${key}:
|
|
52974
55267
|
`;
|
|
52975
55268
|
for (const item of val) {
|
|
52976
55269
|
if (typeof item === "object" && item !== null) {
|
|
52977
|
-
out += `${
|
|
55270
|
+
out += `${pad2}-
|
|
52978
55271
|
`;
|
|
52979
55272
|
out += serializeToYaml(
|
|
52980
55273
|
item,
|
|
52981
55274
|
indent + 2
|
|
52982
55275
|
);
|
|
52983
55276
|
} else {
|
|
52984
|
-
out += `${
|
|
55277
|
+
out += `${pad2}- ${item}
|
|
52985
55278
|
`;
|
|
52986
55279
|
}
|
|
52987
55280
|
}
|
|
52988
55281
|
} else if (typeof val === "object") {
|
|
52989
|
-
out += `${
|
|
55282
|
+
out += `${pad2}${key}:
|
|
52990
55283
|
`;
|
|
52991
55284
|
out += serializeToYaml(val, indent + 1);
|
|
52992
55285
|
}
|
|
@@ -53074,6 +55367,8 @@ Commands:
|
|
|
53074
55367
|
openmates logout Log out and clear session
|
|
53075
55368
|
openmates whoami [--json] Show account info
|
|
53076
55369
|
openmates chats [--help] Chat commands (list, search, show, ...)
|
|
55370
|
+
openmates tasks [--help] Task commands (list, create, board, ...)
|
|
55371
|
+
openmates drafts [--help] Encrypted draft lifecycle commands
|
|
53077
55372
|
openmates apps [--help] App skill commands (list, run, ...)
|
|
53078
55373
|
openmates workflows [--help] Server-side workflow commands
|
|
53079
55374
|
openmates mentions [--help] List available @mentions
|
|
@@ -53212,7 +55507,7 @@ Creates local ignored credential artifacts for reserved E2E auth accounts. The
|
|
|
53212
55507
|
command refuses production API URLs and does not upload GitHub secrets.
|
|
53213
55508
|
|
|
53214
55509
|
Options:
|
|
53215
|
-
--slot <
|
|
55510
|
+
--slot <14-20> Reserved auth-account slot
|
|
53216
55511
|
--artifact <path> Output .env artifact path
|
|
53217
55512
|
--email <email> Test email; prompted/generated when omitted
|
|
53218
55513
|
--domain <mail-domain> Generate email at this domain
|
|
@@ -53225,10 +55520,10 @@ function printChatsHelp() {
|
|
|
53225
55520
|
openmates chats show <chat-id> [--raw] [--json]
|
|
53226
55521
|
openmates chats open [<n|example-id|slug>] [--json]
|
|
53227
55522
|
openmates chats search <query> [--json]
|
|
53228
|
-
openmates chats new <message> [--json] [--learning-mode --age-group <group>] [--auto-approve] [--auto-approve-memories] [--no-pii-detection]
|
|
53229
|
-
openmates chats send [--chat <id>] [--incognito] <message> [--json] [--auto-approve] [--auto-approve-memories] [--no-pii-detection]
|
|
55523
|
+
openmates chats new <message> [--json] [--learning-mode --age-group <group>] [--auto-approve] [--auto-approve-memories] [--accept-task-proposals] [--no-pii-detection]
|
|
55524
|
+
openmates chats send [--chat <id>] [--incognito] <message> [--json] [--auto-approve] [--auto-approve-memories] [--accept-task-proposals] [--no-pii-detection]
|
|
53230
55525
|
openmates chats send --chat <id> --followup <n> [--json] [--auto-approve] [--auto-approve-memories]
|
|
53231
|
-
openmates chats answer-interactive --chat <id> --question-json '<json>' --answer-json '<json>' [--json]
|
|
55526
|
+
openmates chats answer-interactive --chat <id> --question-json '<json>' --answer-json '<json>' [--json] [--accept-task-proposals]
|
|
53232
55527
|
openmates chats download <chat-id> [--output <path>] [--zip] [--json]
|
|
53233
55528
|
openmates chats delete <id1> [id2] [id3] ... [--yes]
|
|
53234
55529
|
openmates chats share [<chat-id>] [--expires <seconds>] [--password <pwd>] [--json]
|
|
@@ -53271,6 +55566,11 @@ Options for 'new', 'send', and 'incognito':
|
|
|
53271
55566
|
--no-pii-detection Send the message exactly as typed. By default, the CLI
|
|
53272
55567
|
replaces detected PII with placeholders before send.
|
|
53273
55568
|
|
|
55569
|
+
Saved-chat task options for 'new', 'send', and 'answer-interactive':
|
|
55570
|
+
--accept-task-proposals Explicitly save assistant task proposals as encrypted
|
|
55571
|
+
Tasks V1 records scoped to the chat. Without this,
|
|
55572
|
+
proposals remain review-only, like the web app card.
|
|
55573
|
+
|
|
53274
55574
|
Guest-only options for logged-out 'new':
|
|
53275
55575
|
--learning-mode Opt anonymous chat into request-scoped Learning Mode.
|
|
53276
55576
|
--age-group <group> Required with --learning-mode: under_10, 10_12,
|
|
@@ -53326,6 +55626,46 @@ Examples:
|
|
|
53326
55626
|
openmates chats share last --expires 604800
|
|
53327
55627
|
openmates chats share d262cb68 --password mypass`);
|
|
53328
55628
|
}
|
|
55629
|
+
function printTasksHelp() {
|
|
55630
|
+
console.log(`Tasks commands:
|
|
55631
|
+
openmates tasks list [--status <status>] [--chat <id>] [--project <id>] [--json]
|
|
55632
|
+
openmates tasks board [--chat <id>] [--project <id>] [--json]
|
|
55633
|
+
openmates tasks show <task-id|short-id> [--json]
|
|
55634
|
+
openmates tasks create --title <title> [--description <text>] [--assign user|ai] [--chat <id>] [--project <id>] [--status <status>] [--due <date>] [--json]
|
|
55635
|
+
openmates tasks edit <task-id|short-id> [--title <title>] [--description <text>] [--assign user|ai] [--status <status>] [--json]
|
|
55636
|
+
openmates tasks delete <task-id|short-id> --confirm [--json]
|
|
55637
|
+
openmates tasks start <task-id|short-id> [--json]
|
|
55638
|
+
openmates tasks status [<task-id|short-id>] [--json]
|
|
55639
|
+
openmates tasks block <task-id|short-id> --reason <code> [--json]
|
|
55640
|
+
openmates tasks unblock <task-id|short-id> [--json]
|
|
55641
|
+
openmates tasks skip <task-id|short-id> [--json]
|
|
55642
|
+
openmates tasks done <task-id|short-id> [--json]
|
|
55643
|
+
openmates tasks reorder <task-id|short-id> [--before <task-id>] [--after <task-id>] [--position <n>] [--status <status>] [--json]
|
|
55644
|
+
|
|
55645
|
+
Chat-scoped aliases:
|
|
55646
|
+
openmates chats <chat-id> tasks list
|
|
55647
|
+
openmates chats <chat-id> tasks board
|
|
55648
|
+
openmates chats <chat-id> tasks create --title <title>
|
|
55649
|
+
|
|
55650
|
+
Statuses:
|
|
55651
|
+
backlog, todo, in_progress, blocked, done
|
|
55652
|
+
|
|
55653
|
+
Notes:
|
|
55654
|
+
Task IDs accept full task_id or human short IDs such as OM-6.
|
|
55655
|
+
Normal output decrypts task title and description locally; use --json for machine-readable plaintext fields.`);
|
|
55656
|
+
}
|
|
55657
|
+
function printDraftsHelp() {
|
|
55658
|
+
console.log(`Draft commands:
|
|
55659
|
+
openmates drafts create <text> [--chat <uuid>] [--preview <text>] [--json]
|
|
55660
|
+
openmates drafts update <chat-id> <text> [--preview <text>] [--json]
|
|
55661
|
+
openmates drafts list [--refresh] [--json]
|
|
55662
|
+
openmates drafts get <chat-id> [--refresh] [--json]
|
|
55663
|
+
openmates drafts clear <chat-id> [--json]
|
|
55664
|
+
openmates drafts sync [--json]
|
|
55665
|
+
|
|
55666
|
+
Draft plaintext is encrypted locally with the account master key. Only Format-D
|
|
55667
|
+
ciphertext and version metadata are sent to the server or written to CLI cache.`);
|
|
55668
|
+
}
|
|
53329
55669
|
function printAppsHelp() {
|
|
53330
55670
|
console.log(`Apps commands:
|
|
53331
55671
|
openmates apps list [--json]
|
|
@@ -53334,6 +55674,7 @@ function printAppsHelp() {
|
|
|
53334
55674
|
openmates apps skill-info <app-id> <skill-id> [--json]
|
|
53335
55675
|
openmates apps <app-id> <skill-id> "<query>" [--json]
|
|
53336
55676
|
openmates apps <app-id> <skill-id> --input '<json>' [--json]
|
|
55677
|
+
openmates apps models3d generate --image ./reference.png [--json]
|
|
53337
55678
|
openmates apps code run --language python --code 'print("Hello")'
|
|
53338
55679
|
openmates apps code run --entry main.py --file main.py [--file requirements.txt]
|
|
53339
55680
|
openmates apps code run --entry main.py --dir ./project [--exclude node_modules]
|
|
@@ -53349,6 +55690,7 @@ Examples:
|
|
|
53349
55690
|
openmates apps web search "latest AI news"
|
|
53350
55691
|
openmates apps news search "climate change"
|
|
53351
55692
|
openmates apps ai ask "Summarise this: ..."
|
|
55693
|
+
openmates apps models3d generate --image ./chair.png
|
|
53352
55694
|
openmates apps code run --language python --filename hello.py --code 'print("Hello from CLI")'
|
|
53353
55695
|
openmates apps travel search_connections --input '{"requests":[{"legs":[{"origin":"BER","destination":"LHR","date":"2026-04-15"}]}]}'
|
|
53354
55696
|
openmates apps travel booking-link --token "<booking_token from search result>"
|
|
@@ -53358,6 +55700,9 @@ function printWorkflowsHelp() {
|
|
|
53358
55700
|
console.log(`Workflows commands:
|
|
53359
55701
|
openmates workflows list [--json]
|
|
53360
55702
|
openmates workflows capabilities [--json]
|
|
55703
|
+
openmates workflows validate --file workflow.yml [--json]
|
|
55704
|
+
openmates workflows create --file workflow.yml [--json]
|
|
55705
|
+
openmates workflows update <workflow-id> --file workflow.yml [--json]
|
|
53361
55706
|
openmates workflows create --title <title> --graph '<json>' [--enabled] [--run-content-retention last_5|none] [--json]
|
|
53362
55707
|
openmates workflows input <text> [--workflow-id <id>] [--project-id <id>] [--json]
|
|
53363
55708
|
openmates workflows input-show <session-id> [--json]
|
|
@@ -53368,9 +55713,13 @@ function printWorkflowsHelp() {
|
|
|
53368
55713
|
openmates workflows show <workflow-id> [--json]
|
|
53369
55714
|
openmates workflows enable <workflow-id> [--json]
|
|
53370
55715
|
openmates workflows disable <workflow-id> [--json]
|
|
53371
|
-
openmates workflows run <workflow-id> [--mode manual|test] [--input '<json>'] [--json]
|
|
55716
|
+
openmates workflows run <workflow-id> --idempotency-key <stable-key> [--mode manual|test] [--input '<json>'] [--json]
|
|
53372
55717
|
openmates workflows runs <workflow-id> [--json]
|
|
53373
55718
|
openmates workflows run-show <workflow-id> <run-id> [--json]
|
|
55719
|
+
openmates workflows run-cancel <workflow-id> <run-id> [--json]
|
|
55720
|
+
openmates workflows step-test <workflow-id> <step-id> [--input '<json>'] [--yes] [--json]
|
|
55721
|
+
openmates workflows respond <workflow-id> <run-id> <step-id> --input '<json>' [--json]
|
|
55722
|
+
openmates workflows help-app <app.skill> [--json]
|
|
53374
55723
|
openmates workflows delete <workflow-id> --yes [--json]
|
|
53375
55724
|
|
|
53376
55725
|
Workflows run on the OpenMates server, not in this terminal process. The CLI
|
|
@@ -53380,6 +55729,7 @@ and Apple clients.
|
|
|
53380
55729
|
Examples:
|
|
53381
55730
|
openmates workflows list
|
|
53382
55731
|
openmates workflows capabilities --json
|
|
55732
|
+
openmates workflows help-app weather.forecast
|
|
53383
55733
|
openmates workflows input "alert me if it rains tomorrow"
|
|
53384
55734
|
openmates workflows run wf_123 --mode test --json`);
|
|
53385
55735
|
}
|
|
@@ -53631,9 +55981,12 @@ if (isCliEntrypoint()) {
|
|
|
53631
55981
|
}
|
|
53632
55982
|
|
|
53633
55983
|
export {
|
|
55984
|
+
deriveChatCompletionRecoveryKeypair,
|
|
55985
|
+
openChatCompletionRecoveryEnvelope,
|
|
53634
55986
|
unwrapApiKeyMasterKey,
|
|
53635
55987
|
decryptWithAesGcmCombined,
|
|
53636
55988
|
decryptBytesWithAesGcm,
|
|
55989
|
+
encryptBytesWithAesGcm,
|
|
53637
55990
|
encryptWithAesGcmCombined,
|
|
53638
55991
|
hashItemKey,
|
|
53639
55992
|
generateChatShareBlob,
|
|
@@ -53646,6 +55999,7 @@ export {
|
|
|
53646
55999
|
INTEREST_TAG_IDS,
|
|
53647
56000
|
normalizeInterestTagIds,
|
|
53648
56001
|
MEMORY_TYPE_REGISTRY,
|
|
56002
|
+
reconcileAuthoritativeChats,
|
|
53649
56003
|
MATE_NAMES,
|
|
53650
56004
|
deriveAppUrl,
|
|
53651
56005
|
OpenMatesClient,
|