openmates 0.14.7-alpha.9 → 0.14.8-alpha.0
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-ADAQQBYE.js → chunk-7BIVPFKJ.js} +2379 -184
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +226 -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,29 @@ 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 WebSocketProtocolError = class extends Error {
|
|
1194
|
+
code;
|
|
1195
|
+
constructor(code, message) {
|
|
1196
|
+
super(message);
|
|
1197
|
+
this.name = "WebSocketProtocolError";
|
|
1198
|
+
this.code = code;
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
function websocketProtocolError(envelope) {
|
|
1202
|
+
const payload = envelope.payload && typeof envelope.payload === "object" ? envelope.payload : {};
|
|
1203
|
+
const code = envelope.type === "client_update_required" ? envelope.type : envelope.type === "error" && typeof payload.code === "string" ? payload.code : null;
|
|
1204
|
+
if (code === "client_update_required") {
|
|
1205
|
+
return new WebSocketProtocolError(code, CLIENT_UPDATE_REQUIRED_GUIDANCE);
|
|
1206
|
+
}
|
|
1207
|
+
if (envelope.type === "error") {
|
|
1208
|
+
return new WebSocketProtocolError(
|
|
1209
|
+
code ?? "websocket_error",
|
|
1210
|
+
typeof payload.message === "string" ? payload.message : "Unknown WebSocket error"
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
1045
1215
|
var OpenMatesWsClient = class {
|
|
1046
1216
|
socket;
|
|
1047
1217
|
constructor(options) {
|
|
@@ -1111,13 +1281,23 @@ var OpenMatesWsClient = class {
|
|
|
1111
1281
|
}
|
|
1112
1282
|
waitForMessage(expectedType, predicate, timeoutMs = 2e4) {
|
|
1113
1283
|
return new Promise((resolve7, reject) => {
|
|
1284
|
+
const seenTypes = /* @__PURE__ */ new Set();
|
|
1285
|
+
let predicateMisses = 0;
|
|
1114
1286
|
const onMessage = (rawData) => {
|
|
1115
1287
|
try {
|
|
1116
1288
|
const parsed = JSON.parse(rawData.toString());
|
|
1289
|
+
if (typeof parsed.type === "string") seenTypes.add(parsed.type);
|
|
1290
|
+
const protocolError = websocketProtocolError(parsed);
|
|
1291
|
+
if (protocolError) {
|
|
1292
|
+
cleanup();
|
|
1293
|
+
reject(protocolError);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1117
1296
|
if (parsed.type !== expectedType) {
|
|
1118
1297
|
return;
|
|
1119
1298
|
}
|
|
1120
1299
|
if (predicate && !predicate(parsed.payload)) {
|
|
1300
|
+
predicateMisses += 1;
|
|
1121
1301
|
return;
|
|
1122
1302
|
}
|
|
1123
1303
|
cleanup();
|
|
@@ -1135,7 +1315,12 @@ var OpenMatesWsClient = class {
|
|
|
1135
1315
|
};
|
|
1136
1316
|
const timeout = setTimeout(() => {
|
|
1137
1317
|
cleanup();
|
|
1138
|
-
|
|
1318
|
+
const observed = [...seenTypes].sort().join(", ") || "none";
|
|
1319
|
+
reject(
|
|
1320
|
+
new Error(
|
|
1321
|
+
`Timeout waiting for '${expectedType}' (seen types: ${observed}; predicate misses: ${predicateMisses})`
|
|
1322
|
+
)
|
|
1323
|
+
);
|
|
1139
1324
|
}, timeoutMs);
|
|
1140
1325
|
const cleanup = () => {
|
|
1141
1326
|
clearTimeout(timeout);
|
|
@@ -1159,6 +1344,12 @@ var OpenMatesWsClient = class {
|
|
|
1159
1344
|
const onMessage = (rawData) => {
|
|
1160
1345
|
try {
|
|
1161
1346
|
const parsed = JSON.parse(rawData.toString());
|
|
1347
|
+
const protocolError = websocketProtocolError(parsed);
|
|
1348
|
+
if (protocolError) {
|
|
1349
|
+
cleanup();
|
|
1350
|
+
reject(protocolError);
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1162
1353
|
if (parsed.type === terminatorType) {
|
|
1163
1354
|
cleanup();
|
|
1164
1355
|
resolve7(collected);
|
|
@@ -1218,6 +1409,7 @@ var OpenMatesWsClient = class {
|
|
|
1218
1409
|
let taskId = null;
|
|
1219
1410
|
let category = null;
|
|
1220
1411
|
let modelName = null;
|
|
1412
|
+
let recoveryJobId = null;
|
|
1221
1413
|
let followUpSuggestions = [];
|
|
1222
1414
|
let newChatSuggestions = [];
|
|
1223
1415
|
const subChatEvents = [];
|
|
@@ -1248,6 +1440,8 @@ var OpenMatesWsClient = class {
|
|
|
1248
1440
|
if (typeof p.category === "string" && p.category) category = p.category;
|
|
1249
1441
|
if (typeof p.model_name === "string" && p.model_name)
|
|
1250
1442
|
modelName = p.model_name;
|
|
1443
|
+
if (typeof p.recovery_job_id === "string" && p.recovery_job_id && p.recovery_protocol_version === 1)
|
|
1444
|
+
recoveryJobId = p.recovery_job_id;
|
|
1251
1445
|
};
|
|
1252
1446
|
const extractMessageContent = (message) => {
|
|
1253
1447
|
if (typeof message.content === "string") return message.content;
|
|
@@ -1279,7 +1473,8 @@ var OpenMatesWsClient = class {
|
|
|
1279
1473
|
followUpSuggestions,
|
|
1280
1474
|
newChatSuggestions,
|
|
1281
1475
|
embeds: [...embeds.values()],
|
|
1282
|
-
subChatEvents
|
|
1476
|
+
subChatEvents,
|
|
1477
|
+
recoveryJobId
|
|
1283
1478
|
});
|
|
1284
1479
|
return;
|
|
1285
1480
|
}
|
|
@@ -1299,7 +1494,8 @@ var OpenMatesWsClient = class {
|
|
|
1299
1494
|
followUpSuggestions,
|
|
1300
1495
|
newChatSuggestions,
|
|
1301
1496
|
embeds: [...embeds.values()],
|
|
1302
|
-
subChatEvents
|
|
1497
|
+
subChatEvents,
|
|
1498
|
+
recoveryJobId
|
|
1303
1499
|
});
|
|
1304
1500
|
}, asyncEmbedWaitMs);
|
|
1305
1501
|
return;
|
|
@@ -1316,7 +1512,8 @@ var OpenMatesWsClient = class {
|
|
|
1316
1512
|
followUpSuggestions,
|
|
1317
1513
|
newChatSuggestions,
|
|
1318
1514
|
embeds: [...embeds.values()],
|
|
1319
|
-
subChatEvents
|
|
1515
|
+
subChatEvents,
|
|
1516
|
+
recoveryJobId
|
|
1320
1517
|
});
|
|
1321
1518
|
};
|
|
1322
1519
|
const handleSubChatEvent = (type, p) => {
|
|
@@ -1388,13 +1585,10 @@ var OpenMatesWsClient = class {
|
|
|
1388
1585
|
const parsed = JSON.parse(rawData.toString());
|
|
1389
1586
|
const p = parsed.payload ?? {};
|
|
1390
1587
|
const type = parsed.type;
|
|
1391
|
-
|
|
1588
|
+
const protocolError = websocketProtocolError(parsed);
|
|
1589
|
+
if (protocolError) {
|
|
1392
1590
|
cleanup();
|
|
1393
|
-
reject(
|
|
1394
|
-
new Error(
|
|
1395
|
-
typeof p.message === "string" ? p.message : "Unknown chat error"
|
|
1396
|
-
)
|
|
1397
|
-
);
|
|
1591
|
+
reject(protocolError);
|
|
1398
1592
|
return;
|
|
1399
1593
|
}
|
|
1400
1594
|
if (SUB_CHAT_EVENT_TYPES.has(type)) {
|
|
@@ -1530,7 +1724,8 @@ var OpenMatesWsClient = class {
|
|
|
1530
1724
|
followUpSuggestions,
|
|
1531
1725
|
newChatSuggestions,
|
|
1532
1726
|
embeds: [...embeds.values()],
|
|
1533
|
-
subChatEvents
|
|
1727
|
+
subChatEvents,
|
|
1728
|
+
recoveryJobId
|
|
1534
1729
|
});
|
|
1535
1730
|
return;
|
|
1536
1731
|
}
|
|
@@ -1957,7 +2152,7 @@ async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId
|
|
|
1957
2152
|
const hashedMessageId = computeSHA256(messageId);
|
|
1958
2153
|
const hashedUserId = computeSHA256(userId);
|
|
1959
2154
|
const wrappedWithMaster = await wrapKey(embedKey, masterKey);
|
|
1960
|
-
const
|
|
2155
|
+
const nowSeconds2 = Math.floor(Date.now() / 1e3);
|
|
1961
2156
|
const embedKeys = [
|
|
1962
2157
|
{
|
|
1963
2158
|
hashed_embed_id: hashedEmbedId,
|
|
@@ -1965,7 +2160,7 @@ async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId
|
|
|
1965
2160
|
hashed_chat_id: null,
|
|
1966
2161
|
encrypted_embed_key: wrappedWithMaster,
|
|
1967
2162
|
hashed_user_id: hashedUserId,
|
|
1968
|
-
created_at:
|
|
2163
|
+
created_at: nowSeconds2
|
|
1969
2164
|
}
|
|
1970
2165
|
];
|
|
1971
2166
|
if (chatKey) {
|
|
@@ -1976,7 +2171,7 @@ async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId
|
|
|
1976
2171
|
hashed_chat_id: hashedChatId,
|
|
1977
2172
|
encrypted_embed_key: wrappedWithChat,
|
|
1978
2173
|
hashed_user_id: hashedUserId,
|
|
1979
|
-
created_at:
|
|
2174
|
+
created_at: nowSeconds2
|
|
1980
2175
|
});
|
|
1981
2176
|
}
|
|
1982
2177
|
return {
|
|
@@ -1991,8 +2186,8 @@ async function encryptEmbed(embed, masterKey, chatKey, chatId, messageId, userId
|
|
|
1991
2186
|
file_path: embed.filePath,
|
|
1992
2187
|
content_hash: embed.contentHash,
|
|
1993
2188
|
text_length_chars: embed.textLengthChars,
|
|
1994
|
-
created_at:
|
|
1995
|
-
updated_at:
|
|
2189
|
+
created_at: nowSeconds2,
|
|
2190
|
+
updated_at: nowSeconds2,
|
|
1996
2191
|
embed_keys: embedKeys
|
|
1997
2192
|
};
|
|
1998
2193
|
} catch (error) {
|
|
@@ -2137,11 +2332,11 @@ async function decryptConnectedAccountCliTransferPayload(encryptedPayload, passc
|
|
|
2137
2332
|
throw new Error("Unsupported connected account import payload format.");
|
|
2138
2333
|
}
|
|
2139
2334
|
try {
|
|
2140
|
-
const key = await deriveTransferKey(passcode,
|
|
2335
|
+
const key = await deriveTransferKey(passcode, base64UrlToBytes2(envelope.kdf.salt), envelope.kdf.iterations);
|
|
2141
2336
|
const plaintext = await webcrypto5.subtle.decrypt(
|
|
2142
|
-
{ name: "AES-GCM", iv: toArrayBuffer3(
|
|
2337
|
+
{ name: "AES-GCM", iv: toArrayBuffer3(base64UrlToBytes2(envelope.cipher.iv)) },
|
|
2143
2338
|
key,
|
|
2144
|
-
toArrayBuffer3(
|
|
2339
|
+
toArrayBuffer3(base64UrlToBytes2(envelope.cipher.text))
|
|
2145
2340
|
);
|
|
2146
2341
|
return validateTransferPayload(JSON.parse(new TextDecoder().decode(plaintext)));
|
|
2147
2342
|
} catch (error) {
|
|
@@ -2189,7 +2384,7 @@ async function buildEncryptedConnectedAccountImportRow(params) {
|
|
|
2189
2384
|
}
|
|
2190
2385
|
function parseEnvelope(encodedEnvelope) {
|
|
2191
2386
|
try {
|
|
2192
|
-
return JSON.parse(new TextDecoder().decode(
|
|
2387
|
+
return JSON.parse(new TextDecoder().decode(base64UrlToBytes2(encodedEnvelope)));
|
|
2193
2388
|
} catch {
|
|
2194
2389
|
throw new Error("Connected account import payload is malformed.");
|
|
2195
2390
|
}
|
|
@@ -2269,7 +2464,7 @@ function defaultProviderLabel(providerId) {
|
|
|
2269
2464
|
function sha256Hex2(value) {
|
|
2270
2465
|
return createHash4("sha256").update(value).digest("hex");
|
|
2271
2466
|
}
|
|
2272
|
-
function
|
|
2467
|
+
function base64UrlToBytes2(value) {
|
|
2273
2468
|
return base64ToBytes(value.replace(/-/g, "+").replace(/_/g, "/"));
|
|
2274
2469
|
}
|
|
2275
2470
|
function toArrayBuffer3(input) {
|
|
@@ -2863,6 +3058,15 @@ var MEMORY_TYPE_REGISTRY = {
|
|
|
2863
3058
|
properties: { url: { type: "string" }, notes: { type: "string" } }
|
|
2864
3059
|
}
|
|
2865
3060
|
};
|
|
3061
|
+
function reconcileAuthoritativeChats(chats, evidence) {
|
|
3062
|
+
const deletedIds = new Set(evidence.deleted_chat_ids ?? []);
|
|
3063
|
+
const authoritativeIds = evidence.authoritative && evidence.authoritative_chat_ids ? new Set(evidence.authoritative_chat_ids) : null;
|
|
3064
|
+
if (!authoritativeIds && deletedIds.size === 0) return chats;
|
|
3065
|
+
return chats.filter((chat) => {
|
|
3066
|
+
const chatId = String(chat.details.id ?? "");
|
|
3067
|
+
return (!authoritativeIds || authoritativeIds.has(chatId)) && !deletedIds.has(chatId);
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
2866
3070
|
var MATE_NAMES = {
|
|
2867
3071
|
software_development: "Sophia",
|
|
2868
3072
|
business_development: "Burton",
|
|
@@ -3635,6 +3839,157 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3635
3839
|
hasMore: offset + limit < total
|
|
3636
3840
|
};
|
|
3637
3841
|
}
|
|
3842
|
+
async saveDraft(params) {
|
|
3843
|
+
const markdown = params.markdown.trim();
|
|
3844
|
+
if (!markdown) throw new Error("Draft markdown must not be empty.");
|
|
3845
|
+
const masterKey = this.getMasterKeyBytes();
|
|
3846
|
+
const encrypted = await this.saveEncryptedDraft({
|
|
3847
|
+
chatId: params.chatId ?? randomUUID3(),
|
|
3848
|
+
encryptedDraftMd: await encryptWithAesGcmCombined(markdown, masterKey),
|
|
3849
|
+
encryptedDraftPreview: params.preview ? await encryptWithAesGcmCombined(params.preview, masterKey) : null
|
|
3850
|
+
});
|
|
3851
|
+
return { ...encrypted, markdown, preview: params.preview ?? null };
|
|
3852
|
+
}
|
|
3853
|
+
async saveEncryptedDraft(params) {
|
|
3854
|
+
const { ws } = await this.openWsClient();
|
|
3855
|
+
try {
|
|
3856
|
+
const receipt = ws.waitForMessage(
|
|
3857
|
+
"draft_update_receipt",
|
|
3858
|
+
(payload) => payload.chat_id === params.chatId
|
|
3859
|
+
);
|
|
3860
|
+
await ws.sendAsync("update_draft", {
|
|
3861
|
+
chat_id: params.chatId,
|
|
3862
|
+
encrypted_draft_md: params.encryptedDraftMd,
|
|
3863
|
+
encrypted_draft_preview: params.encryptedDraftPreview ?? null
|
|
3864
|
+
});
|
|
3865
|
+
const response = await receipt;
|
|
3866
|
+
const draftV = Number(response.payload.draft_v ?? 0);
|
|
3867
|
+
this.storeEncryptedDraft({
|
|
3868
|
+
chatId: params.chatId,
|
|
3869
|
+
encryptedDraftMd: params.encryptedDraftMd,
|
|
3870
|
+
encryptedDraftPreview: params.encryptedDraftPreview ?? null,
|
|
3871
|
+
draftV
|
|
3872
|
+
});
|
|
3873
|
+
return {
|
|
3874
|
+
chatId: params.chatId,
|
|
3875
|
+
encryptedDraftMd: params.encryptedDraftMd,
|
|
3876
|
+
encryptedDraftPreview: params.encryptedDraftPreview ?? null,
|
|
3877
|
+
draftV
|
|
3878
|
+
};
|
|
3879
|
+
} finally {
|
|
3880
|
+
ws.close();
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
async listDrafts(forceRefresh = false) {
|
|
3884
|
+
if (forceRefresh) await this.reconcileDraftVersions();
|
|
3885
|
+
const cache = await this.ensureSynced(forceRefresh);
|
|
3886
|
+
const drafts = [];
|
|
3887
|
+
for (const chat of cache.chats) {
|
|
3888
|
+
const draft = await this.decryptCachedDraft(chat);
|
|
3889
|
+
if (draft) drafts.push(draft);
|
|
3890
|
+
}
|
|
3891
|
+
return drafts;
|
|
3892
|
+
}
|
|
3893
|
+
async getDraft(chatId, forceRefresh = false) {
|
|
3894
|
+
if (forceRefresh) await this.reconcileDraftVersions();
|
|
3895
|
+
const cache = await this.ensureSynced(forceRefresh);
|
|
3896
|
+
const chat = cache.chats.find((entry) => String(entry.details.id ?? "") === chatId);
|
|
3897
|
+
return chat ? this.decryptCachedDraft(chat) : null;
|
|
3898
|
+
}
|
|
3899
|
+
async clearDraft(chatId) {
|
|
3900
|
+
const { ws } = await this.openWsClient();
|
|
3901
|
+
try {
|
|
3902
|
+
const receipt = ws.waitForMessage(
|
|
3903
|
+
"draft_delete_receipt",
|
|
3904
|
+
(payload) => payload.chat_id === chatId
|
|
3905
|
+
);
|
|
3906
|
+
await ws.sendAsync("delete_draft", { chat_id: chatId });
|
|
3907
|
+
await receipt;
|
|
3908
|
+
} finally {
|
|
3909
|
+
ws.close();
|
|
3910
|
+
}
|
|
3911
|
+
const cache = loadSyncCache();
|
|
3912
|
+
if (!cache) return;
|
|
3913
|
+
const chat = cache.chats.find((entry) => String(entry.details.id ?? "") === chatId);
|
|
3914
|
+
if (chat) {
|
|
3915
|
+
delete chat.details.encrypted_draft_md;
|
|
3916
|
+
delete chat.details.encrypted_draft_preview;
|
|
3917
|
+
chat.details.draft_v = 0;
|
|
3918
|
+
if (chat.messages.length === 0 && !chat.details.encrypted_chat_key) {
|
|
3919
|
+
cache.chats = cache.chats.filter((entry) => entry !== chat);
|
|
3920
|
+
}
|
|
3921
|
+
saveSyncCache(cache);
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
async reconcileDraftVersions() {
|
|
3925
|
+
const cache = loadSyncCache();
|
|
3926
|
+
const drafts = (cache?.chats ?? []).filter(
|
|
3927
|
+
(chat) => typeof chat.details.encrypted_draft_md === "string"
|
|
3928
|
+
);
|
|
3929
|
+
if (drafts.length === 0) return {};
|
|
3930
|
+
const { ws } = await this.openWsClient();
|
|
3931
|
+
try {
|
|
3932
|
+
const response = ws.waitForMessage("draft_versions_response");
|
|
3933
|
+
await ws.sendAsync("get_draft_versions", {
|
|
3934
|
+
chats: drafts.map((chat) => ({
|
|
3935
|
+
chat_id: String(chat.details.id),
|
|
3936
|
+
client_draft_v: Number(chat.details.draft_v ?? 0)
|
|
3937
|
+
}))
|
|
3938
|
+
});
|
|
3939
|
+
const frame = await response;
|
|
3940
|
+
const versions = frame.payload.versions ?? {};
|
|
3941
|
+
for (const chat of drafts) {
|
|
3942
|
+
const chatId = String(chat.details.id);
|
|
3943
|
+
if (versions[chatId] === 0) {
|
|
3944
|
+
delete chat.details.encrypted_draft_md;
|
|
3945
|
+
delete chat.details.encrypted_draft_preview;
|
|
3946
|
+
chat.details.draft_v = 0;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
if (cache) saveSyncCache(cache);
|
|
3950
|
+
return versions;
|
|
3951
|
+
} finally {
|
|
3952
|
+
ws.close();
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
storeEncryptedDraft(draft) {
|
|
3956
|
+
const cache = loadSyncCache() ?? {
|
|
3957
|
+
syncedAt: 0,
|
|
3958
|
+
totalChatCount: 0,
|
|
3959
|
+
loadedChatCount: 0,
|
|
3960
|
+
chats: [],
|
|
3961
|
+
embeds: [],
|
|
3962
|
+
embedKeys: []
|
|
3963
|
+
};
|
|
3964
|
+
let chat = cache.chats.find((entry) => String(entry.details.id ?? "") === draft.chatId);
|
|
3965
|
+
if (!chat) {
|
|
3966
|
+
chat = { details: { id: draft.chatId }, messages: [] };
|
|
3967
|
+
cache.chats.unshift(chat);
|
|
3968
|
+
}
|
|
3969
|
+
chat.details.encrypted_draft_md = draft.encryptedDraftMd;
|
|
3970
|
+
chat.details.encrypted_draft_preview = draft.encryptedDraftPreview;
|
|
3971
|
+
chat.details.draft_v = draft.draftV;
|
|
3972
|
+
cache.syncedAt = Date.now();
|
|
3973
|
+
cache.loadedChatCount = cache.chats.length;
|
|
3974
|
+
saveSyncCache(cache);
|
|
3975
|
+
}
|
|
3976
|
+
async decryptCachedDraft(chat) {
|
|
3977
|
+
const encryptedDraftMd = chat.details.encrypted_draft_md;
|
|
3978
|
+
if (typeof encryptedDraftMd !== "string") return null;
|
|
3979
|
+
const encryptedPreview = typeof chat.details.encrypted_draft_preview === "string" ? chat.details.encrypted_draft_preview : null;
|
|
3980
|
+
const masterKey = this.getMasterKeyBytes();
|
|
3981
|
+
const markdown = await decryptWithAesGcmCombined(encryptedDraftMd, masterKey);
|
|
3982
|
+
if (markdown === null) throw new Error("Failed to decrypt draft markdown.");
|
|
3983
|
+
const preview = encryptedPreview ? await decryptWithAesGcmCombined(encryptedPreview, masterKey) : markdown.slice(0, 160);
|
|
3984
|
+
return {
|
|
3985
|
+
chatId: String(chat.details.id ?? ""),
|
|
3986
|
+
encryptedDraftMd,
|
|
3987
|
+
encryptedDraftPreview: encryptedPreview,
|
|
3988
|
+
draftV: Number(chat.details.draft_v ?? 0),
|
|
3989
|
+
markdown,
|
|
3990
|
+
preview
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3638
3993
|
async searchChats(query) {
|
|
3639
3994
|
const cache = await this.ensureSynced();
|
|
3640
3995
|
const masterKey = this.getMasterKeyBytes();
|
|
@@ -3727,8 +4082,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3727
4082
|
);
|
|
3728
4083
|
let msgEmbedIds = [];
|
|
3729
4084
|
if (clientMsgId && cache.embeds.length > 0) {
|
|
3730
|
-
const { createHash:
|
|
3731
|
-
const hashed =
|
|
4085
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
4086
|
+
const hashed = createHash8("sha256").update(clientMsgId).digest("hex");
|
|
3732
4087
|
msgEmbedIds = cache.embeds.filter(
|
|
3733
4088
|
(e) => e.hashed_message_id === hashed && // Only include parent embeds (no parent_embed_id).
|
|
3734
4089
|
// Child embeds inherit the parent's key and are loaded
|
|
@@ -3775,8 +4130,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3775
4130
|
);
|
|
3776
4131
|
}
|
|
3777
4132
|
const embedId = String(embed.embed_id ?? embed.id ?? "");
|
|
3778
|
-
const { createHash:
|
|
3779
|
-
const hashedEmbedId =
|
|
4133
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
4134
|
+
const hashedEmbedId = createHash8("sha256").update(embedId).digest("hex");
|
|
3780
4135
|
const embedKeyBytes = await this.resolveEmbedKey(
|
|
3781
4136
|
cache,
|
|
3782
4137
|
masterKey,
|
|
@@ -3884,7 +4239,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3884
4239
|
async resolveEmbedKey(cache, masterKey, embed, embedId, hashedEmbedId, visited = /* @__PURE__ */ new Set()) {
|
|
3885
4240
|
if (visited.has(embedId)) return null;
|
|
3886
4241
|
visited.add(embedId);
|
|
3887
|
-
const { createHash:
|
|
4242
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
3888
4243
|
const masterKeyEntry = cache.embedKeys.find(
|
|
3889
4244
|
(ek) => ek.hashed_embed_id === hashedEmbedId && String(ek.key_type) === "master"
|
|
3890
4245
|
);
|
|
@@ -3901,7 +4256,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3901
4256
|
if (chatKeyEntry && typeof chatKeyEntry.encrypted_embed_key === "string") {
|
|
3902
4257
|
const hashedChatId = String(chatKeyEntry.hashed_chat_id ?? "");
|
|
3903
4258
|
const owningChat = cache.chats.find((c) => {
|
|
3904
|
-
const chatHash =
|
|
4259
|
+
const chatHash = createHash8("sha256").update(String(c.details.id ?? "")).digest("hex");
|
|
3905
4260
|
return chatHash === hashedChatId;
|
|
3906
4261
|
});
|
|
3907
4262
|
if (owningChat) {
|
|
@@ -3930,7 +4285,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3930
4285
|
const parentFullId = String(
|
|
3931
4286
|
parentEmbed2.embed_id ?? parentEmbed2.id ?? ""
|
|
3932
4287
|
);
|
|
3933
|
-
const parentHashedId =
|
|
4288
|
+
const parentHashedId = createHash8("sha256").update(parentFullId).digest("hex");
|
|
3934
4289
|
const parentKey = await this.resolveEmbedKey(
|
|
3935
4290
|
cache,
|
|
3936
4291
|
masterKey,
|
|
@@ -4074,7 +4429,11 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4074
4429
|
}
|
|
4075
4430
|
}
|
|
4076
4431
|
}
|
|
4077
|
-
const { ws, session } = await this.openWsClient();
|
|
4432
|
+
const { ws, session, ownerId } = await this.openWsClient();
|
|
4433
|
+
if (!params.incognito && !ownerId) {
|
|
4434
|
+
ws.close();
|
|
4435
|
+
throw new Error("Authenticated user identity is required for saved chat recovery.");
|
|
4436
|
+
}
|
|
4078
4437
|
const messageId = randomUUID3();
|
|
4079
4438
|
const createdAt = Math.floor(Date.now() / 1e3);
|
|
4080
4439
|
const isNewChat = !params.chatId;
|
|
@@ -4089,6 +4448,40 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4089
4448
|
}) : [];
|
|
4090
4449
|
assertNoConnectedAccountSecretLeak(connectedAccountTokenRefs);
|
|
4091
4450
|
const piiMappings = params.piiMappings ?? [];
|
|
4451
|
+
let chatKeyBytes = null;
|
|
4452
|
+
let encryptedChatKey = null;
|
|
4453
|
+
let baselineMessagesV = 0;
|
|
4454
|
+
let savedTurnId = null;
|
|
4455
|
+
if (!params.incognito) {
|
|
4456
|
+
const masterKey = this.getMasterKeyBytes();
|
|
4457
|
+
if (isNewChat) {
|
|
4458
|
+
chatKeyBytes = globalThis.crypto ? new Uint8Array(globalThis.crypto.getRandomValues(new Uint8Array(32))) : new Uint8Array(
|
|
4459
|
+
(await import("crypto")).webcrypto.getRandomValues(
|
|
4460
|
+
new Uint8Array(32)
|
|
4461
|
+
)
|
|
4462
|
+
);
|
|
4463
|
+
encryptedChatKey = await encryptBytesWithAesGcm(
|
|
4464
|
+
chatKeyBytes,
|
|
4465
|
+
masterKey
|
|
4466
|
+
);
|
|
4467
|
+
} else {
|
|
4468
|
+
const cache = loadSyncCache() ?? await this.ensureSynced();
|
|
4469
|
+
const chat = cache.chats.find(
|
|
4470
|
+
(c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
|
|
4471
|
+
);
|
|
4472
|
+
if (chat) {
|
|
4473
|
+
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
4474
|
+
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
4475
|
+
if (encKey) {
|
|
4476
|
+
chatKeyBytes = await decryptBytesWithAesGcm(encKey, masterKey);
|
|
4477
|
+
encryptedChatKey = encKey;
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
if (!chatKeyBytes || !encryptedChatKey) {
|
|
4481
|
+
throw new Error(`Encrypted chat key not found for chat '${chatId}'. Sync and try again.`);
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4092
4485
|
const messagePayload = {
|
|
4093
4486
|
chat_id: chatId,
|
|
4094
4487
|
is_incognito: Boolean(params.incognito),
|
|
@@ -4103,6 +4496,9 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4103
4496
|
chat_has_title: Boolean(params.chatId)
|
|
4104
4497
|
}
|
|
4105
4498
|
};
|
|
4499
|
+
if (isNewChat && encryptedChatKey) {
|
|
4500
|
+
messagePayload.encrypted_chat_key = encryptedChatKey;
|
|
4501
|
+
}
|
|
4106
4502
|
if (memoryMetadataKeys.length > 0) {
|
|
4107
4503
|
messagePayload.app_settings_memories_metadata = memoryMetadataKeys;
|
|
4108
4504
|
}
|
|
@@ -4144,37 +4540,6 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4144
4540
|
created_at: historyMessage.created_at
|
|
4145
4541
|
}));
|
|
4146
4542
|
}
|
|
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
4543
|
if (params.preparedEmbeds && params.preparedEmbeds.length > 0) {
|
|
4179
4544
|
messagePayload.embeds = params.preparedEmbeds.map((embed) => ({
|
|
4180
4545
|
embed_id: embed.embedId,
|
|
@@ -4202,47 +4567,93 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4202
4567
|
if (encryptedEmbeds.length > 0) {
|
|
4203
4568
|
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
4204
4569
|
}
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
if (!params.incognito && chatKeyBytes) {
|
|
4570
|
+
let precollectedResponse = params.precollectResponse && params.incognito ? ws.collectAiResponse(messageId, chatId, { onStream: params.onStream }) : null;
|
|
4571
|
+
if (!params.incognito && chatKeyBytes && encryptedChatKey) {
|
|
4572
|
+
const protocolVersion = 1;
|
|
4573
|
+
const chatKeyVersion = 1;
|
|
4574
|
+
const turnId = randomUUID3();
|
|
4575
|
+
savedTurnId = turnId;
|
|
4576
|
+
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
4577
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
4578
|
+
chatId,
|
|
4579
|
+
chatKeyVersion
|
|
4580
|
+
);
|
|
4217
4581
|
const encryptedContent = await encryptWithAesGcmCombined(
|
|
4218
4582
|
params.message,
|
|
4219
4583
|
chatKeyBytes
|
|
4220
4584
|
);
|
|
4221
|
-
const
|
|
4222
|
-
|
|
4223
|
-
chatKeyBytes
|
|
4224
|
-
);
|
|
4225
|
-
const metadataPayload = {
|
|
4585
|
+
const encryptedUserMessage = {
|
|
4586
|
+
client_message_id: messageId,
|
|
4226
4587
|
chat_id: chatId,
|
|
4227
|
-
message_id: messageId,
|
|
4228
4588
|
encrypted_content: encryptedContent,
|
|
4229
|
-
|
|
4589
|
+
role: "user",
|
|
4230
4590
|
created_at: createdAt,
|
|
4231
|
-
|
|
4232
|
-
versions: {
|
|
4233
|
-
messages_v: 0,
|
|
4234
|
-
title_v: 0,
|
|
4235
|
-
last_edited_overall_timestamp: createdAt
|
|
4236
|
-
}
|
|
4591
|
+
updated_at: createdAt
|
|
4237
4592
|
};
|
|
4238
4593
|
if (piiMappings.length > 0) {
|
|
4239
|
-
|
|
4594
|
+
encryptedUserMessage.encrypted_pii_mappings = await encryptWithAesGcmCombined(
|
|
4240
4595
|
JSON.stringify(piiMappings),
|
|
4241
4596
|
chatKeyBytes
|
|
4242
4597
|
);
|
|
4243
4598
|
}
|
|
4244
|
-
|
|
4599
|
+
Object.assign(messagePayload, {
|
|
4600
|
+
turn_id: turnId,
|
|
4601
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
4602
|
+
chat_key_version: chatKeyVersion
|
|
4603
|
+
});
|
|
4604
|
+
const preflightPayload = {
|
|
4605
|
+
protocol_version: protocolVersion,
|
|
4606
|
+
chat_id: chatId,
|
|
4607
|
+
turn_id: turnId,
|
|
4608
|
+
message_id: messageId,
|
|
4609
|
+
chat_key_version: chatKeyVersion,
|
|
4610
|
+
encrypted_chat_key: encryptedChatKey,
|
|
4611
|
+
recovery_public_key: recoveryKeypair.publicKey,
|
|
4612
|
+
expected_messages_v: baselineMessagesV,
|
|
4613
|
+
encrypted_user_message: encryptedUserMessage,
|
|
4614
|
+
inference_request: messagePayload
|
|
4615
|
+
};
|
|
4616
|
+
if (isNewChat) {
|
|
4617
|
+
preflightPayload.encrypted_chat_metadata = {
|
|
4618
|
+
encrypted_title: await encryptWithAesGcmCombined("", chatKeyBytes),
|
|
4619
|
+
encrypted_chat_key: encryptedChatKey,
|
|
4620
|
+
created_at: createdAt,
|
|
4621
|
+
updated_at: createdAt
|
|
4622
|
+
};
|
|
4623
|
+
}
|
|
4624
|
+
const preflightAck = ws.waitForMessage("chat_turn_preflight_ack");
|
|
4625
|
+
let ackPayload;
|
|
4626
|
+
try {
|
|
4627
|
+
await ws.sendAsync("chat_turn_preflight", preflightPayload);
|
|
4628
|
+
ackPayload = (await preflightAck).payload;
|
|
4629
|
+
} catch (error) {
|
|
4630
|
+
ws.close();
|
|
4631
|
+
throw error;
|
|
4632
|
+
}
|
|
4633
|
+
if (typeof ackPayload.preflight_id !== "string" || !ackPayload.preflight_id) {
|
|
4634
|
+
ws.close();
|
|
4635
|
+
throw new Error("Encrypted chat preflight acknowledgement omitted preflight_id.");
|
|
4636
|
+
}
|
|
4637
|
+
Object.assign(messagePayload, {
|
|
4638
|
+
protocol_version: protocolVersion,
|
|
4639
|
+
preflight_id: ackPayload.preflight_id
|
|
4640
|
+
});
|
|
4641
|
+
}
|
|
4642
|
+
if (params.precollectResponse && !params.incognito) {
|
|
4643
|
+
precollectedResponse = ws.collectAiResponse(messageId, chatId, {
|
|
4644
|
+
onStream: params.onStream
|
|
4645
|
+
});
|
|
4245
4646
|
}
|
|
4647
|
+
const confirmed = ws.waitForMessage(
|
|
4648
|
+
"chat_message_confirmed",
|
|
4649
|
+
(payload) => {
|
|
4650
|
+
const eventPayload = payload;
|
|
4651
|
+
return eventPayload.chat_id === chatId && eventPayload.message_id === messageId;
|
|
4652
|
+
},
|
|
4653
|
+
2e4
|
|
4654
|
+
);
|
|
4655
|
+
await ws.sendAsync("chat_message_added", messagePayload);
|
|
4656
|
+
await confirmed;
|
|
4246
4657
|
let assistant = "";
|
|
4247
4658
|
let assistantMessageId = null;
|
|
4248
4659
|
let category = null;
|
|
@@ -4442,7 +4853,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4442
4853
|
}
|
|
4443
4854
|
} else {
|
|
4444
4855
|
try {
|
|
4445
|
-
const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
|
|
4856
|
+
const resp = await (precollectedResponse ?? ws.collectAiResponse(messageId, chatId, streamOpts));
|
|
4446
4857
|
assistantMessageId = resp.messageId;
|
|
4447
4858
|
assistant = resp.content;
|
|
4448
4859
|
category = resp.category;
|
|
@@ -4463,47 +4874,110 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
4463
4874
|
appSettingsMemoryRequests
|
|
4464
4875
|
};
|
|
4465
4876
|
}
|
|
4466
|
-
if (chatKeyBytes
|
|
4877
|
+
if (chatKeyBytes) {
|
|
4878
|
+
if (!resp.recoveryJobId || !savedTurnId || !ownerId) {
|
|
4879
|
+
throw new Error("Saved chat completion did not include recoverable terminal identity.");
|
|
4880
|
+
}
|
|
4881
|
+
const recoveryJobId = resp.recoveryJobId;
|
|
4882
|
+
const claimPromise = ws.waitForMessage(
|
|
4883
|
+
"recovery_job_claimed",
|
|
4884
|
+
(payload) => payload.job_id === recoveryJobId,
|
|
4885
|
+
2e4
|
|
4886
|
+
);
|
|
4887
|
+
await ws.sendAsync("recovery_job_claim", {
|
|
4888
|
+
protocol_version: 1,
|
|
4889
|
+
job_id: recoveryJobId
|
|
4890
|
+
});
|
|
4891
|
+
const claim = (await claimPromise).payload;
|
|
4892
|
+
const leaseToken = typeof claim.lease_token === "string" ? claim.lease_token : null;
|
|
4893
|
+
const leaseGeneration = typeof claim.lease_generation === "number" && Number.isSafeInteger(claim.lease_generation) ? claim.lease_generation : null;
|
|
4894
|
+
const assistantId = typeof claim.assistant_message_id === "string" ? claim.assistant_message_id : null;
|
|
4895
|
+
const keyVersion = typeof claim.chat_key_version === "number" && Number.isSafeInteger(claim.chat_key_version) ? claim.chat_key_version : null;
|
|
4896
|
+
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") {
|
|
4897
|
+
throw new Error("Recovery job claim returned invalid lease or identity data.");
|
|
4898
|
+
}
|
|
4899
|
+
const recoveryKeypair = await deriveChatCompletionRecoveryKeypair(
|
|
4900
|
+
Buffer.from(chatKeyBytes).toString("base64url"),
|
|
4901
|
+
chatId,
|
|
4902
|
+
keyVersion
|
|
4903
|
+
);
|
|
4904
|
+
let envelope;
|
|
4905
|
+
try {
|
|
4906
|
+
envelope = JSON.parse(claim.sealed_payload);
|
|
4907
|
+
} catch {
|
|
4908
|
+
throw new Error("Recovery job contained an invalid sealed envelope.");
|
|
4909
|
+
}
|
|
4910
|
+
const plaintext = await openChatCompletionRecoveryEnvelope(
|
|
4911
|
+
envelope,
|
|
4912
|
+
{
|
|
4913
|
+
recoveryPrivateKey: recoveryKeypair.privateKey,
|
|
4914
|
+
ownerId,
|
|
4915
|
+
chatId,
|
|
4916
|
+
turnId: savedTurnId,
|
|
4917
|
+
jobId: recoveryJobId,
|
|
4918
|
+
assistantMessageId: assistantId,
|
|
4919
|
+
keyVersion
|
|
4920
|
+
}
|
|
4921
|
+
);
|
|
4922
|
+
let recovered;
|
|
4923
|
+
try {
|
|
4924
|
+
recovered = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(plaintext));
|
|
4925
|
+
} catch {
|
|
4926
|
+
throw new Error("Recovery job plaintext was not valid UTF-8 JSON.");
|
|
4927
|
+
}
|
|
4928
|
+
const expectedRecoveryFields = [
|
|
4929
|
+
"assistant_message_id",
|
|
4930
|
+
"category",
|
|
4931
|
+
"chat_id",
|
|
4932
|
+
"content",
|
|
4933
|
+
"job_id",
|
|
4934
|
+
"key_version",
|
|
4935
|
+
"model_name",
|
|
4936
|
+
"turn_id"
|
|
4937
|
+
];
|
|
4938
|
+
if (Object.keys(recovered).sort().join(",") !== expectedRecoveryFields.join(",") || recovered.assistant_message_id !== assistantId || recovered.chat_id !== chatId || recovered.turn_id !== savedTurnId || recovered.job_id !== recoveryJobId || recovered.key_version !== keyVersion || typeof recovered.content !== "string" || recovered.category !== null && typeof recovered.category !== "string" || recovered.model_name !== null && typeof recovered.model_name !== "string" || recovered.content !== assistant || recovered.category !== category || recovered.model_name !== modelName) {
|
|
4939
|
+
throw new Error("Recovery job plaintext did not match the terminal completion identity.");
|
|
4940
|
+
}
|
|
4467
4941
|
const completedAt = Math.floor(Date.now() / 1e3);
|
|
4468
4942
|
const encryptedAssistantContent = await encryptWithAesGcmCombined(
|
|
4469
|
-
|
|
4943
|
+
recovered.content,
|
|
4470
4944
|
chatKeyBytes
|
|
4471
4945
|
);
|
|
4472
|
-
const
|
|
4473
|
-
const
|
|
4474
|
-
const
|
|
4475
|
-
ws.
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4946
|
+
const encryptedSenderName = await encryptWithAesGcmCombined("Assistant", chatKeyBytes);
|
|
4947
|
+
const encryptedCategory = recovered.category ? await encryptWithAesGcmCombined(recovered.category, chatKeyBytes) : void 0;
|
|
4948
|
+
const encryptedModelName = recovered.model_name ? await encryptWithAesGcmCombined(recovered.model_name, chatKeyBytes) : void 0;
|
|
4949
|
+
const persistedPromise = ws.waitForMessage(
|
|
4950
|
+
"recovery_job_persisted",
|
|
4951
|
+
(payload) => payload.job_id === recoveryJobId,
|
|
4952
|
+
2e4
|
|
4953
|
+
);
|
|
4954
|
+
await ws.sendAsync("recovery_job_persist", {
|
|
4955
|
+
protocol_version: 1,
|
|
4956
|
+
job_id: recoveryJobId,
|
|
4957
|
+
lease_token: leaseToken,
|
|
4958
|
+
lease_generation: leaseGeneration,
|
|
4959
|
+
expected_messages_v: baselineMessagesV + 1,
|
|
4960
|
+
encrypted_assistant_message: {
|
|
4961
|
+
client_message_id: assistantId,
|
|
4479
4962
|
chat_id: chatId,
|
|
4480
|
-
role: "assistant",
|
|
4481
|
-
created_at: completedAt,
|
|
4482
|
-
status: "synced",
|
|
4483
|
-
user_message_id: messageId,
|
|
4484
4963
|
encrypted_content: encryptedAssistantContent,
|
|
4964
|
+
encrypted_sender_name: encryptedSenderName,
|
|
4485
4965
|
encrypted_category: encryptedCategory,
|
|
4486
|
-
encrypted_model_name: encryptedModelName
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4966
|
+
encrypted_model_name: encryptedModelName,
|
|
4967
|
+
role: "assistant",
|
|
4968
|
+
user_message_id: messageId,
|
|
4969
|
+
created_at: completedAt,
|
|
4970
|
+
updated_at: completedAt
|
|
4491
4971
|
}
|
|
4492
4972
|
});
|
|
4493
|
-
await
|
|
4494
|
-
|
|
4495
|
-
(payload) => {
|
|
4496
|
-
const p = payload;
|
|
4497
|
-
return p.message_id === persistedAssistantMessageId;
|
|
4498
|
-
},
|
|
4499
|
-
2e4
|
|
4500
|
-
);
|
|
4973
|
+
await persistedPromise;
|
|
4974
|
+
assistantMessageId = assistantId;
|
|
4501
4975
|
await this.persistStreamedEmbeds({
|
|
4502
4976
|
ws,
|
|
4503
4977
|
embeds: resp.embeds,
|
|
4504
4978
|
chatId,
|
|
4505
4979
|
chatKeyBytes,
|
|
4506
|
-
fallbackMessageId:
|
|
4980
|
+
fallbackMessageId: assistantId
|
|
4507
4981
|
});
|
|
4508
4982
|
await this.persistPostProcessingMetadata({
|
|
4509
4983
|
ws,
|
|
@@ -5041,6 +5515,30 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5041
5515
|
}
|
|
5042
5516
|
return response.data.workflow;
|
|
5043
5517
|
}
|
|
5518
|
+
async validateWorkflowYaml(source) {
|
|
5519
|
+
this.requireSession();
|
|
5520
|
+
const response = await this.http.post("/v1/workflows/validate", { source }, this.getCliRequestHeaders());
|
|
5521
|
+
if (!response.ok || !response.data.validation) {
|
|
5522
|
+
throw new Error(`Workflow YAML validation failed with HTTP ${response.status}`);
|
|
5523
|
+
}
|
|
5524
|
+
return response.data.validation;
|
|
5525
|
+
}
|
|
5526
|
+
async createWorkflowYaml(source) {
|
|
5527
|
+
this.requireSession();
|
|
5528
|
+
const response = await this.http.post("/v1/workflows/yaml", { source }, this.getCliRequestHeaders());
|
|
5529
|
+
if (!response.ok || !response.data.workflow || !response.data.validation) {
|
|
5530
|
+
throw new Error(`Workflow YAML create failed with HTTP ${response.status}`);
|
|
5531
|
+
}
|
|
5532
|
+
return { workflow: response.data.workflow, validation: response.data.validation };
|
|
5533
|
+
}
|
|
5534
|
+
async updateWorkflowYaml(workflowId, source) {
|
|
5535
|
+
this.requireSession();
|
|
5536
|
+
const createLike = await this.http.post(`/v1/workflows/${encodeURIComponent(workflowId)}/yaml`, { source }, this.getCliRequestHeaders());
|
|
5537
|
+
if (!createLike.ok || !createLike.data.workflow || !createLike.data.validation) {
|
|
5538
|
+
throw new Error(`Workflow YAML update failed with HTTP ${createLike.status}`);
|
|
5539
|
+
}
|
|
5540
|
+
return { workflow: createLike.data.workflow, validation: createLike.data.validation };
|
|
5541
|
+
}
|
|
5044
5542
|
async getWorkflow(workflowId) {
|
|
5045
5543
|
this.requireSession();
|
|
5046
5544
|
const response = await this.http.get(
|
|
@@ -5099,12 +5597,15 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5099
5597
|
async disableWorkflow(workflowId) {
|
|
5100
5598
|
return this.setWorkflowEnabled(workflowId, false);
|
|
5101
5599
|
}
|
|
5102
|
-
async runWorkflow(workflowId, params
|
|
5600
|
+
async runWorkflow(workflowId, params) {
|
|
5103
5601
|
this.requireSession();
|
|
5602
|
+
if (!params.idempotencyKey.trim()) {
|
|
5603
|
+
throw new Error("Workflow run requires a stable idempotencyKey");
|
|
5604
|
+
}
|
|
5104
5605
|
const response = await this.http.post(
|
|
5105
5606
|
`/v1/workflows/${encodeURIComponent(workflowId)}/run`,
|
|
5106
5607
|
{ mode: params.mode ?? "manual", input: params.input ?? {} },
|
|
5107
|
-
this.getCliRequestHeaders()
|
|
5608
|
+
{ ...this.getCliRequestHeaders(), "Idempotency-Key": params.idempotencyKey }
|
|
5108
5609
|
);
|
|
5109
5610
|
if (!response.ok || !response.data.run) {
|
|
5110
5611
|
throw new Error(`Workflow run failed with HTTP ${response.status}`);
|
|
@@ -5133,6 +5634,42 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5133
5634
|
}
|
|
5134
5635
|
return response.data.run;
|
|
5135
5636
|
}
|
|
5637
|
+
async cancelWorkflowRun(workflowId, runId) {
|
|
5638
|
+
this.requireSession();
|
|
5639
|
+
const response = await this.http.post(
|
|
5640
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/runs/${encodeURIComponent(runId)}/cancel`,
|
|
5641
|
+
{},
|
|
5642
|
+
this.getCliRequestHeaders()
|
|
5643
|
+
);
|
|
5644
|
+
if (!response.ok || response.data.status !== "cancellation_requested" && response.data.status !== "cancelled") {
|
|
5645
|
+
throw new Error(`Workflow run cancellation failed with HTTP ${response.status}`);
|
|
5646
|
+
}
|
|
5647
|
+
return response.data;
|
|
5648
|
+
}
|
|
5649
|
+
async testWorkflowStep(workflowId, stepId, params = {}) {
|
|
5650
|
+
this.requireSession();
|
|
5651
|
+
const response = await this.http.post(
|
|
5652
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/steps/${encodeURIComponent(stepId)}/test`,
|
|
5653
|
+
{ input: params.input ?? {}, confirmed: params.confirmed === true },
|
|
5654
|
+
this.getCliRequestHeaders()
|
|
5655
|
+
);
|
|
5656
|
+
if (!response.ok || !response.data.run) {
|
|
5657
|
+
throw new Error(`Workflow step test failed with HTTP ${response.status}`);
|
|
5658
|
+
}
|
|
5659
|
+
return response.data.run;
|
|
5660
|
+
}
|
|
5661
|
+
async respondToWorkflowRun(workflowId, runId, stepId, input) {
|
|
5662
|
+
this.requireSession();
|
|
5663
|
+
const response = await this.http.post(
|
|
5664
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/runs/${encodeURIComponent(runId)}/respond`,
|
|
5665
|
+
{ step_id: stepId, input },
|
|
5666
|
+
this.getCliRequestHeaders()
|
|
5667
|
+
);
|
|
5668
|
+
if (!response.ok || !response.data.run) {
|
|
5669
|
+
throw new Error(`Workflow response failed with HTTP ${response.status}`);
|
|
5670
|
+
}
|
|
5671
|
+
return response.data.run;
|
|
5672
|
+
}
|
|
5136
5673
|
async listWorkflowCapabilities() {
|
|
5137
5674
|
this.requireSession();
|
|
5138
5675
|
const response = await this.http.get(
|
|
@@ -5144,6 +5681,114 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5144
5681
|
}
|
|
5145
5682
|
return response.data.capabilities ?? [];
|
|
5146
5683
|
}
|
|
5684
|
+
async upsertWorkflowTemplateProjection(workflowId, params) {
|
|
5685
|
+
this.requireSession();
|
|
5686
|
+
const response = await this.http.put(
|
|
5687
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/template-projection`,
|
|
5688
|
+
{
|
|
5689
|
+
template_id: params.templateId,
|
|
5690
|
+
source_version: params.sourceVersion,
|
|
5691
|
+
ciphertext: params.ciphertext,
|
|
5692
|
+
ciphertext_checksum: params.ciphertextChecksum,
|
|
5693
|
+
owner_wrapped_key: params.ownerWrappedKey,
|
|
5694
|
+
projection_schema_version: params.projectionSchemaVersion
|
|
5695
|
+
},
|
|
5696
|
+
this.getCliRequestHeaders()
|
|
5697
|
+
);
|
|
5698
|
+
if (!response.ok) {
|
|
5699
|
+
throw new Error(`Workflow template projection upsert failed with HTTP ${response.status}`);
|
|
5700
|
+
}
|
|
5701
|
+
return response.data;
|
|
5702
|
+
}
|
|
5703
|
+
async getPublicWorkflowTemplateProjection(templateId) {
|
|
5704
|
+
const response = await this.http.get(
|
|
5705
|
+
`/v1/workflows/template-projections/${encodeURIComponent(templateId)}`,
|
|
5706
|
+
this.getCliRequestHeaders()
|
|
5707
|
+
);
|
|
5708
|
+
if (!response.ok) {
|
|
5709
|
+
throw new Error(`Workflow template projection retrieval failed with HTTP ${response.status}`);
|
|
5710
|
+
}
|
|
5711
|
+
return response.data;
|
|
5712
|
+
}
|
|
5713
|
+
async revokeWorkflowTemplateProjection(workflowId) {
|
|
5714
|
+
this.requireSession();
|
|
5715
|
+
const response = await this.http.post(
|
|
5716
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/template-projection/revoke`,
|
|
5717
|
+
{},
|
|
5718
|
+
this.getCliRequestHeaders()
|
|
5719
|
+
);
|
|
5720
|
+
if (!response.ok) {
|
|
5721
|
+
throw new Error(`Workflow template projection revoke failed with HTTP ${response.status}`);
|
|
5722
|
+
}
|
|
5723
|
+
return response.data;
|
|
5724
|
+
}
|
|
5725
|
+
async unrevokeWorkflowTemplateProjection(workflowId) {
|
|
5726
|
+
this.requireSession();
|
|
5727
|
+
const response = await this.http.post(
|
|
5728
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/template-projection/unrevoke`,
|
|
5729
|
+
{},
|
|
5730
|
+
this.getCliRequestHeaders()
|
|
5731
|
+
);
|
|
5732
|
+
if (!response.ok) {
|
|
5733
|
+
throw new Error(`Workflow template projection unrevoke failed with HTTP ${response.status}`);
|
|
5734
|
+
}
|
|
5735
|
+
return response.data;
|
|
5736
|
+
}
|
|
5737
|
+
async completeImportedWorkflowBinding(workflowId, params) {
|
|
5738
|
+
this.requireSession();
|
|
5739
|
+
const response = await this.http.post(
|
|
5740
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/binding-requirements/complete`,
|
|
5741
|
+
{ type: params.type, node_id: params.nodeId },
|
|
5742
|
+
this.getCliRequestHeaders()
|
|
5743
|
+
);
|
|
5744
|
+
if (!response.ok) {
|
|
5745
|
+
throw new Error(`Workflow imported binding completion failed with HTTP ${response.status}`);
|
|
5746
|
+
}
|
|
5747
|
+
return response.data;
|
|
5748
|
+
}
|
|
5749
|
+
async createWorkflowTemplateShortUrl(params) {
|
|
5750
|
+
this.requireSession();
|
|
5751
|
+
const response = await this.http.post(
|
|
5752
|
+
"/v1/share/short-url",
|
|
5753
|
+
{
|
|
5754
|
+
token: params.token,
|
|
5755
|
+
encrypted_url: params.encryptedUrl,
|
|
5756
|
+
content_type: "workflow_template",
|
|
5757
|
+
content_id: params.templateId,
|
|
5758
|
+
password_protected: params.passwordProtected ?? false,
|
|
5759
|
+
...params.ttlSeconds !== void 0 ? { ttl_seconds: params.ttlSeconds } : {}
|
|
5760
|
+
},
|
|
5761
|
+
this.getCliRequestHeaders()
|
|
5762
|
+
);
|
|
5763
|
+
if (!response.ok) {
|
|
5764
|
+
throw new Error(`Workflow template short URL create failed with HTTP ${response.status}`);
|
|
5765
|
+
}
|
|
5766
|
+
return response.data;
|
|
5767
|
+
}
|
|
5768
|
+
async revokeShortUrl(token) {
|
|
5769
|
+
this.requireSession();
|
|
5770
|
+
const response = await this.http.delete(
|
|
5771
|
+
`/v1/share/short-url/${encodeURIComponent(token)}`,
|
|
5772
|
+
void 0,
|
|
5773
|
+
this.getCliRequestHeaders()
|
|
5774
|
+
);
|
|
5775
|
+
if (!response.ok) {
|
|
5776
|
+
throw new Error(`Short URL revoke failed with HTTP ${response.status}`);
|
|
5777
|
+
}
|
|
5778
|
+
return response.data;
|
|
5779
|
+
}
|
|
5780
|
+
async importWorkflowTemplate(payload) {
|
|
5781
|
+
this.requireSession();
|
|
5782
|
+
const response = await this.http.post(
|
|
5783
|
+
"/v1/workflows/template-import",
|
|
5784
|
+
payload,
|
|
5785
|
+
this.getCliRequestHeaders()
|
|
5786
|
+
);
|
|
5787
|
+
if (!response.ok || !response.data.workflow) {
|
|
5788
|
+
throw new Error(`Workflow template import failed with HTTP ${response.status}`);
|
|
5789
|
+
}
|
|
5790
|
+
return response.data.workflow;
|
|
5791
|
+
}
|
|
5147
5792
|
async startWorkflowInput(params) {
|
|
5148
5793
|
this.requireSession();
|
|
5149
5794
|
const response = await this.http.post(
|
|
@@ -5314,6 +5959,54 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
5314
5959
|
}
|
|
5315
5960
|
return response.data.task;
|
|
5316
5961
|
}
|
|
5962
|
+
async deleteUserTask(taskId) {
|
|
5963
|
+
this.requireSession();
|
|
5964
|
+
const response = await this.http.delete(
|
|
5965
|
+
`/v1/user-tasks/${encodeURIComponent(taskId)}`,
|
|
5966
|
+
void 0,
|
|
5967
|
+
this.getCliRequestHeaders()
|
|
5968
|
+
);
|
|
5969
|
+
if (!response.ok) {
|
|
5970
|
+
throw new Error(`User task delete failed with HTTP ${response.status}`);
|
|
5971
|
+
}
|
|
5972
|
+
return response.data;
|
|
5973
|
+
}
|
|
5974
|
+
async completeUserTask(taskId, input = {}) {
|
|
5975
|
+
return this.postUserTaskAction(taskId, "complete", input);
|
|
5976
|
+
}
|
|
5977
|
+
async blockUserTask(taskId, input = {}) {
|
|
5978
|
+
return this.postUserTaskAction(taskId, "block", input);
|
|
5979
|
+
}
|
|
5980
|
+
async unblockUserTask(taskId, input = {}) {
|
|
5981
|
+
return this.postUserTaskAction(taskId, "unblock", input);
|
|
5982
|
+
}
|
|
5983
|
+
async skipUserTask(taskId, input = {}) {
|
|
5984
|
+
return this.postUserTaskAction(taskId, "skip", input);
|
|
5985
|
+
}
|
|
5986
|
+
async reorderUserTasks(input) {
|
|
5987
|
+
this.requireSession();
|
|
5988
|
+
const response = await this.http.post(
|
|
5989
|
+
"/v1/user-tasks/reorder",
|
|
5990
|
+
input,
|
|
5991
|
+
this.getCliRequestHeaders()
|
|
5992
|
+
);
|
|
5993
|
+
if (!response.ok) {
|
|
5994
|
+
throw new Error(`User task reorder failed with HTTP ${response.status}`);
|
|
5995
|
+
}
|
|
5996
|
+
return response.data.tasks ?? [];
|
|
5997
|
+
}
|
|
5998
|
+
async postUserTaskAction(taskId, action, input = {}) {
|
|
5999
|
+
this.requireSession();
|
|
6000
|
+
const response = await this.http.post(
|
|
6001
|
+
`/v1/user-tasks/${encodeURIComponent(taskId)}/${encodeURIComponent(action)}`,
|
|
6002
|
+
input,
|
|
6003
|
+
this.getCliRequestHeaders()
|
|
6004
|
+
);
|
|
6005
|
+
if (!response.ok || !response.data.task) {
|
|
6006
|
+
throw new Error(`User task ${action} failed with HTTP ${response.status}`);
|
|
6007
|
+
}
|
|
6008
|
+
return response.data.task;
|
|
6009
|
+
}
|
|
5317
6010
|
// -------------------------------------------------------------------------
|
|
5318
6011
|
// User plans
|
|
5319
6012
|
// -------------------------------------------------------------------------
|
|
@@ -6194,7 +6887,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
6194
6887
|
const session = this.requireSession();
|
|
6195
6888
|
const masterKey = base64ToBytes(session.masterKeyExportedB64);
|
|
6196
6889
|
const cache = await this.ensureSynced();
|
|
6197
|
-
const { createHash:
|
|
6890
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
6198
6891
|
const embed = cache.embeds.find(
|
|
6199
6892
|
(e) => String(e.embed_id ?? "").startsWith(embedIdOrShort) || String(e.id ?? "").startsWith(embedIdOrShort)
|
|
6200
6893
|
);
|
|
@@ -6202,7 +6895,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
6202
6895
|
throw new Error(`Embed '${embedIdOrShort}' not found in local cache.`);
|
|
6203
6896
|
}
|
|
6204
6897
|
const embedId = String(embed.embed_id ?? embed.id ?? "");
|
|
6205
|
-
const hashedEmbedId =
|
|
6898
|
+
const hashedEmbedId = createHash8("sha256").update(embedId).digest("hex");
|
|
6206
6899
|
const embedKeyBytes = await this.resolveEmbedKey(
|
|
6207
6900
|
cache,
|
|
6208
6901
|
masterKey,
|
|
@@ -6262,8 +6955,8 @@ Required: ${schema.required.join(", ")}`
|
|
|
6262
6955
|
if (!embed) {
|
|
6263
6956
|
throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
|
|
6264
6957
|
}
|
|
6265
|
-
const { createHash:
|
|
6266
|
-
const hashedEmbedId =
|
|
6958
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
6959
|
+
const hashedEmbedId = createHash8("sha256").update(embedId).digest("hex");
|
|
6267
6960
|
const embedKey = await this.resolveEmbedKey(
|
|
6268
6961
|
cache,
|
|
6269
6962
|
masterKey,
|
|
@@ -6357,8 +7050,8 @@ Required: ${schema.required.join(", ")}`
|
|
|
6357
7050
|
if (!embed) {
|
|
6358
7051
|
throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
|
|
6359
7052
|
}
|
|
6360
|
-
const { createHash:
|
|
6361
|
-
const hashedEmbedId =
|
|
7053
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
7054
|
+
const hashedEmbedId = createHash8("sha256").update(embedId).digest("hex");
|
|
6362
7055
|
const embedKey = await this.resolveEmbedKey(
|
|
6363
7056
|
cache,
|
|
6364
7057
|
masterKey,
|
|
@@ -6550,11 +7243,11 @@ Required: ${schema.required.join(", ")}`
|
|
|
6550
7243
|
* so every WebSocket usage gets a fresh HMAC token automatically.
|
|
6551
7244
|
*/
|
|
6552
7245
|
async openWsClient() {
|
|
6553
|
-
await this.refreshWsToken();
|
|
7246
|
+
const ownerId = await this.refreshWsToken();
|
|
6554
7247
|
const session = this.requireSession();
|
|
6555
7248
|
const ws = this.makeWsClient(session);
|
|
6556
7249
|
await ws.open();
|
|
6557
|
-
return { ws, session };
|
|
7250
|
+
return { ws, session, ownerId };
|
|
6558
7251
|
}
|
|
6559
7252
|
/**
|
|
6560
7253
|
* Refresh the ws_token by calling /auth/session.
|
|
@@ -6571,7 +7264,9 @@ Required: ${schema.required.join(", ")}`
|
|
|
6571
7264
|
}
|
|
6572
7265
|
session.cookies = this.http.getCookieMap();
|
|
6573
7266
|
saveSession(session);
|
|
7267
|
+
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
7268
|
} catch {
|
|
7269
|
+
return null;
|
|
6575
7270
|
}
|
|
6576
7271
|
}
|
|
6577
7272
|
/**
|
|
@@ -6616,6 +7311,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
6616
7311
|
const embedKeys = [];
|
|
6617
7312
|
let newChatSuggestions = [];
|
|
6618
7313
|
let totalChatCount = 0;
|
|
7314
|
+
let reconciliation = { authoritative: false };
|
|
6619
7315
|
const pendingAIResponses = [];
|
|
6620
7316
|
try {
|
|
6621
7317
|
ws.send("phased_sync_request", {
|
|
@@ -6642,6 +7338,11 @@ Required: ${schema.required.join(", ")}`
|
|
|
6642
7338
|
} else if (frame.type === "phase_2_last_20_chats_ready") {
|
|
6643
7339
|
const p = frame.payload;
|
|
6644
7340
|
totalChatCount = p.total_chat_count ?? 0;
|
|
7341
|
+
reconciliation = {
|
|
7342
|
+
authoritative: p.authoritative === true,
|
|
7343
|
+
authoritative_chat_ids: p.authoritative_chat_ids,
|
|
7344
|
+
deleted_chat_ids: p.deleted_chat_ids
|
|
7345
|
+
};
|
|
6645
7346
|
for (const wrapper of p.chats ?? []) {
|
|
6646
7347
|
const details = wrapper.chat_details;
|
|
6647
7348
|
if (!details || typeof details.id !== "string") continue;
|
|
@@ -6721,12 +7422,21 @@ Required: ${schema.required.join(", ")}`
|
|
|
6721
7422
|
}
|
|
6722
7423
|
}
|
|
6723
7424
|
}
|
|
7425
|
+
const reconciledChats = reconcileAuthoritativeChats(chats, reconciliation);
|
|
7426
|
+
chats.splice(0, chats.length, ...reconciledChats);
|
|
7427
|
+
if (reconciliation.deleted_chat_ids?.length) {
|
|
7428
|
+
const { createHash: createHash8 } = await import("crypto");
|
|
7429
|
+
const deletedHashes = new Set(
|
|
7430
|
+
reconciliation.deleted_chat_ids.map((id) => createHash8("sha256").update(id).digest("hex"))
|
|
7431
|
+
);
|
|
7432
|
+
const keptEmbeds = embeds.filter((embed) => !deletedHashes.has(String(embed.hashed_chat_id ?? "")));
|
|
7433
|
+
const keptKeys = embedKeys.filter((key) => !deletedHashes.has(String(key.hashed_chat_id ?? "")));
|
|
7434
|
+
embeds.splice(0, embeds.length, ...keptEmbeds);
|
|
7435
|
+
embedKeys.splice(0, embedKeys.length, ...keptKeys);
|
|
7436
|
+
}
|
|
6724
7437
|
chats.sort(
|
|
6725
7438
|
(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
7439
|
);
|
|
6727
|
-
if (totalChatCount > 0 && chats.length > totalChatCount) {
|
|
6728
|
-
chats.length = totalChatCount;
|
|
6729
|
-
}
|
|
6730
7440
|
try {
|
|
6731
7441
|
await this.persistPendingAIResponsesFromSync(ws, chats, pendingAIResponses);
|
|
6732
7442
|
} finally {
|
|
@@ -6979,10 +7689,10 @@ function printLogo() {
|
|
|
6979
7689
|
|
|
6980
7690
|
// src/cli.ts
|
|
6981
7691
|
import { createInterface as createInterface4 } from "readline/promises";
|
|
6982
|
-
import { realpathSync, writeFileSync as writeFileSync6 } from "fs";
|
|
7692
|
+
import { readFileSync as readFileSync9, realpathSync, writeFileSync as writeFileSync6 } from "fs";
|
|
6983
7693
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6984
7694
|
import { basename as basename3, dirname as dirname3 } from "path";
|
|
6985
|
-
import { randomUUID as
|
|
7695
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
6986
7696
|
import WebSocket2 from "ws";
|
|
6987
7697
|
|
|
6988
7698
|
// ../secret-scanner/src/registry.ts
|
|
@@ -28597,6 +29307,11 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
28597
29307
|
}
|
|
28598
29308
|
}
|
|
28599
29309
|
},
|
|
29310
|
+
models3d: {
|
|
29311
|
+
generate: {
|
|
29312
|
+
text: "Generate 3D model"
|
|
29313
|
+
}
|
|
29314
|
+
},
|
|
28600
29315
|
music: {
|
|
28601
29316
|
generate: {
|
|
28602
29317
|
text: "Generate",
|
|
@@ -30936,6 +31651,27 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
30936
31651
|
save: {
|
|
30937
31652
|
text: "Save"
|
|
30938
31653
|
},
|
|
31654
|
+
detail_title_hint: {
|
|
31655
|
+
text: "Enter to save \xB7 Escape to cancel"
|
|
31656
|
+
},
|
|
31657
|
+
detail_description_hint: {
|
|
31658
|
+
text: "Ctrl/Command+Enter to save \xB7 Escape to cancel"
|
|
31659
|
+
},
|
|
31660
|
+
detail_save_error: {
|
|
31661
|
+
text: "Your changes were not saved."
|
|
31662
|
+
},
|
|
31663
|
+
detail_loading: {
|
|
31664
|
+
text: "Loading {item}\u2026"
|
|
31665
|
+
},
|
|
31666
|
+
detail_load_error: {
|
|
31667
|
+
text: "{item} could not be loaded."
|
|
31668
|
+
},
|
|
31669
|
+
detail_untitled: {
|
|
31670
|
+
text: "Untitled {item}"
|
|
31671
|
+
},
|
|
31672
|
+
detail_items_count: {
|
|
31673
|
+
text: "{count} items"
|
|
31674
|
+
},
|
|
30939
31675
|
settings: {
|
|
30940
31676
|
text: "Settings"
|
|
30941
31677
|
},
|
|
@@ -36566,6 +37302,22 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
36566
37302
|
},
|
|
36567
37303
|
privacy: {
|
|
36568
37304
|
providers: {
|
|
37305
|
+
model_generation: {
|
|
37306
|
+
heading: {
|
|
37307
|
+
text: "3D model generation (only when you use a 3D model skill)"
|
|
37308
|
+
},
|
|
37309
|
+
description: {
|
|
37310
|
+
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."
|
|
37311
|
+
},
|
|
37312
|
+
hi3d: {
|
|
37313
|
+
heading: {
|
|
37314
|
+
text: "Hi3D (3D model generation)"
|
|
37315
|
+
},
|
|
37316
|
+
description: {
|
|
37317
|
+
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."
|
|
37318
|
+
}
|
|
37319
|
+
}
|
|
37320
|
+
},
|
|
36569
37321
|
fitness: {
|
|
36570
37322
|
heading: {
|
|
36571
37323
|
text: "Fitness search (only when you use the fitness skill)"
|
|
@@ -40656,6 +41408,42 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
40656
41408
|
description: {
|
|
40657
41409
|
text: "Manage your credits, purchases, and what you used credits for."
|
|
40658
41410
|
},
|
|
41411
|
+
apple_support: {
|
|
41412
|
+
text: "Support OpenMates"
|
|
41413
|
+
},
|
|
41414
|
+
apple_usage_details: {
|
|
41415
|
+
text: "View usage details"
|
|
41416
|
+
},
|
|
41417
|
+
apple_purchased_gift_cards: {
|
|
41418
|
+
text: "Purchased"
|
|
41419
|
+
},
|
|
41420
|
+
apple_no_gift_cards: {
|
|
41421
|
+
text: "No gift cards yet"
|
|
41422
|
+
},
|
|
41423
|
+
apple_gift_card_purchase_unavailable: {
|
|
41424
|
+
text: "Gift card purchase is unavailable until your account email is fully synchronized."
|
|
41425
|
+
},
|
|
41426
|
+
apple_support_amount: {
|
|
41427
|
+
text: "Amount in EUR"
|
|
41428
|
+
},
|
|
41429
|
+
apple_create_bank_transfer: {
|
|
41430
|
+
text: "Create bank transfer"
|
|
41431
|
+
},
|
|
41432
|
+
apple_invalid_support_amount: {
|
|
41433
|
+
text: "Enter a valid amount and make sure your account has an email address."
|
|
41434
|
+
},
|
|
41435
|
+
apple_fulfillment_delayed: {
|
|
41436
|
+
text: "Your purchase is awaiting fulfillment. It remains retryable and will not be marked complete yet."
|
|
41437
|
+
},
|
|
41438
|
+
apple_product_not_found: {
|
|
41439
|
+
text: "This product is not available in the App Store."
|
|
41440
|
+
},
|
|
41441
|
+
apple_credits_count: {
|
|
41442
|
+
text: "{credits} credits"
|
|
41443
|
+
},
|
|
41444
|
+
apple_bank_transfer_reference: {
|
|
41445
|
+
text: "Bank transfer reference: {reference}"
|
|
41446
|
+
},
|
|
40659
41447
|
text: "Billing & Usage",
|
|
40660
41448
|
title: {
|
|
40661
41449
|
text: "Billing & Credits"
|
|
@@ -41436,6 +42224,12 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
41436
42224
|
learning_mode_disabled: {
|
|
41437
42225
|
text: "Learning Mode disabled."
|
|
41438
42226
|
},
|
|
42227
|
+
learning_mode_attempts_remaining: {
|
|
42228
|
+
text: "{count} attempts remaining."
|
|
42229
|
+
},
|
|
42230
|
+
learning_mode_locked: {
|
|
42231
|
+
text: "Deactivation is locked for 24 hours after too many incorrect attempts."
|
|
42232
|
+
},
|
|
41439
42233
|
learning_mode_age_group_prompt: {
|
|
41440
42234
|
text: "Learner age group: under_10, 10_12, 13_15, 16_18, or adult"
|
|
41441
42235
|
},
|
|
@@ -42680,6 +43474,73 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
42680
43474
|
},
|
|
42681
43475
|
share_debug_logs_admin_notice: {
|
|
42682
43476
|
text: "You are an admin. Therefore your debug logs are automatically shared already. Regular users can turn on/off debug logs sharing here."
|
|
43477
|
+
},
|
|
43478
|
+
native: {
|
|
43479
|
+
save_error: {
|
|
43480
|
+
text: "Could not save this privacy setting. Please try again."
|
|
43481
|
+
},
|
|
43482
|
+
master_key_unavailable: {
|
|
43483
|
+
text: "Your encryption key is unavailable. Sign in again to manage this setting."
|
|
43484
|
+
},
|
|
43485
|
+
debug_session: {
|
|
43486
|
+
description: {
|
|
43487
|
+
text: "Temporarily share sanitized native diagnostics with support. Message content, encryption keys, credentials, file names, and personal data are never shared."
|
|
43488
|
+
},
|
|
43489
|
+
duration: {
|
|
43490
|
+
text: "Duration"
|
|
43491
|
+
},
|
|
43492
|
+
start: {
|
|
43493
|
+
text: "Start sharing debug logs"
|
|
43494
|
+
},
|
|
43495
|
+
activating: {
|
|
43496
|
+
text: "Activating..."
|
|
43497
|
+
},
|
|
43498
|
+
active: {
|
|
43499
|
+
text: "Active debug session"
|
|
43500
|
+
},
|
|
43501
|
+
id: {
|
|
43502
|
+
text: "Your Debug ID"
|
|
43503
|
+
},
|
|
43504
|
+
share_hint: {
|
|
43505
|
+
text: "Share this ID with the support team so they can find your sanitized logs."
|
|
43506
|
+
},
|
|
43507
|
+
stop: {
|
|
43508
|
+
text: "Stop sharing"
|
|
43509
|
+
},
|
|
43510
|
+
stopping: {
|
|
43511
|
+
text: "Stopping..."
|
|
43512
|
+
},
|
|
43513
|
+
no_expiry: {
|
|
43514
|
+
text: "Active until manually stopped"
|
|
43515
|
+
},
|
|
43516
|
+
error: {
|
|
43517
|
+
text: "Could not update the debug sharing session. Please try again."
|
|
43518
|
+
},
|
|
43519
|
+
duration_5m: {
|
|
43520
|
+
text: "5 minutes"
|
|
43521
|
+
},
|
|
43522
|
+
duration_1h: {
|
|
43523
|
+
text: "1 hour"
|
|
43524
|
+
},
|
|
43525
|
+
duration_3d: {
|
|
43526
|
+
text: "3 days"
|
|
43527
|
+
},
|
|
43528
|
+
duration_7d: {
|
|
43529
|
+
text: "7 days"
|
|
43530
|
+
},
|
|
43531
|
+
duration_none: {
|
|
43532
|
+
text: "No time limit"
|
|
43533
|
+
},
|
|
43534
|
+
minutes_remaining: {
|
|
43535
|
+
text: "{count}m remaining"
|
|
43536
|
+
},
|
|
43537
|
+
hours_remaining: {
|
|
43538
|
+
text: "{count}h remaining"
|
|
43539
|
+
},
|
|
43540
|
+
days_remaining: {
|
|
43541
|
+
text: "{count}d remaining"
|
|
43542
|
+
}
|
|
43543
|
+
}
|
|
42683
43544
|
}
|
|
42684
43545
|
},
|
|
42685
43546
|
terms_and_conditions: {
|
|
@@ -44490,6 +45351,21 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
44490
45351
|
pair_server_use_custom: {
|
|
44491
45352
|
text: "Use custom"
|
|
44492
45353
|
},
|
|
45354
|
+
pair_self_hosted_edition: {
|
|
45355
|
+
text: "Connect self-hosted edition"
|
|
45356
|
+
},
|
|
45357
|
+
pair_self_hosted_placeholder: {
|
|
45358
|
+
text: "Server URL"
|
|
45359
|
+
},
|
|
45360
|
+
pair_self_hosted_connect: {
|
|
45361
|
+
text: "Connect"
|
|
45362
|
+
},
|
|
45363
|
+
pair_self_hosted_invalid_url: {
|
|
45364
|
+
text: "Enter a valid HTTPS server URL."
|
|
45365
|
+
},
|
|
45366
|
+
pair_use_production: {
|
|
45367
|
+
text: "Use OpenMates.org"
|
|
45368
|
+
},
|
|
44493
45369
|
pair_connect_apple_watch_title: {
|
|
44494
45370
|
text: "Connect Apple Watch"
|
|
44495
45371
|
},
|
|
@@ -46945,6 +47821,17 @@ function createInitialTuiState() {
|
|
|
46945
47821
|
selectedInterests: [],
|
|
46946
47822
|
examples: [],
|
|
46947
47823
|
activeExample: null,
|
|
47824
|
+
workflows: [],
|
|
47825
|
+
activeWorkflow: null,
|
|
47826
|
+
workflowRuns: [],
|
|
47827
|
+
tasks: [],
|
|
47828
|
+
activeTask: null,
|
|
47829
|
+
workflowTab: "graph",
|
|
47830
|
+
selectedWorkflowNodeIndex: 0,
|
|
47831
|
+
selectedWorkflowRunIndex: 0,
|
|
47832
|
+
expandedWorkflowNodeId: null,
|
|
47833
|
+
expandedWorkflowRunNodeId: null,
|
|
47834
|
+
workflowEdit: null,
|
|
46948
47835
|
messages: [],
|
|
46949
47836
|
status: null,
|
|
46950
47837
|
isBusy: false
|
|
@@ -46978,6 +47865,7 @@ Files:
|
|
|
46978
47865
|
|
|
46979
47866
|
More:
|
|
46980
47867
|
openmates apps list
|
|
47868
|
+
openmates workflows list
|
|
46981
47869
|
openmates mentions list
|
|
46982
47870
|
openmates embeds show <embed-id>
|
|
46983
47871
|
openmates help
|
|
@@ -47000,7 +47888,7 @@ function renderTuiFrame(state, width, height) {
|
|
|
47000
47888
|
const contentHeight = safeHeight - 4;
|
|
47001
47889
|
const body = renderBody(state, innerWidth);
|
|
47002
47890
|
const visible = sliceForScroll(body, contentHeight, state.scrollOffset, state.screen === "chat");
|
|
47003
|
-
const inputLine = state.screen === "interests" || state.screen === "examples" ? renderHintLine(state, innerWidth) : `> ${state.input || inputPlaceholder(state)}`;
|
|
47891
|
+
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)}`;
|
|
47004
47892
|
const lines = [
|
|
47005
47893
|
header2,
|
|
47006
47894
|
...padLines(visible, contentHeight, innerWidth).map((line) => boxed(line, innerWidth)),
|
|
@@ -47026,6 +47914,14 @@ function renderBody(state, width) {
|
|
|
47026
47914
|
return renderStatus(state, width);
|
|
47027
47915
|
case "embed":
|
|
47028
47916
|
return renderStatus(state, width);
|
|
47917
|
+
case "workflows":
|
|
47918
|
+
return renderWorkflows(state, width);
|
|
47919
|
+
case "workflow":
|
|
47920
|
+
return renderWorkflowDetail(state, width);
|
|
47921
|
+
case "tasks":
|
|
47922
|
+
return renderTasks(state, width);
|
|
47923
|
+
case "task":
|
|
47924
|
+
return renderTaskDetail(state, width);
|
|
47029
47925
|
case "start":
|
|
47030
47926
|
default:
|
|
47031
47927
|
return renderStart(width);
|
|
@@ -47047,7 +47943,7 @@ function renderStart(width) {
|
|
|
47047
47943
|
"Type @./notes.md, @~/Downloads/report.pdf, or @src/app.ts in your message.",
|
|
47048
47944
|
"Images, PDFs, audio, and code files are attached as encrypted embeds when you are signed in.",
|
|
47049
47945
|
"",
|
|
47050
|
-
"Shortcuts: /help /login /signup /examples /exit"
|
|
47946
|
+
"Shortcuts: /help /login /signup /examples /tasks /exit"
|
|
47051
47947
|
].flatMap((line) => wrap(line, width));
|
|
47052
47948
|
}
|
|
47053
47949
|
function renderHelp(width) {
|
|
@@ -47067,6 +47963,9 @@ function renderHelp(width) {
|
|
|
47067
47963
|
"",
|
|
47068
47964
|
"Commands",
|
|
47069
47965
|
" /examples Choose example chats",
|
|
47966
|
+
" /workflows List and run saved workflows",
|
|
47967
|
+
" /workflow <id> Open a workflow by ID",
|
|
47968
|
+
" /tasks Open your task workspace",
|
|
47070
47969
|
" /login Pair-auth login",
|
|
47071
47970
|
" /signup Leave TUI and run guided signup",
|
|
47072
47971
|
" /embed <id> Open an embed detail view",
|
|
@@ -47075,6 +47974,150 @@ function renderHelp(width) {
|
|
|
47075
47974
|
"Outside TUI: openmates --help, openmates chats --help, openmates apps --help"
|
|
47076
47975
|
].flatMap((line) => wrap(line, width));
|
|
47077
47976
|
}
|
|
47977
|
+
function renderTasks(state, width) {
|
|
47978
|
+
const lines = ["Tasks", ""];
|
|
47979
|
+
if (state.tasks.length === 0) {
|
|
47980
|
+
lines.push("No tasks found.", "Create one outside TUI with: openmates tasks create --title <title>");
|
|
47981
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
47982
|
+
}
|
|
47983
|
+
const visibleCount = Math.max(1, CONTENT_PREVIEW_LINES);
|
|
47984
|
+
const start = Math.max(0, Math.min(state.selectedIndex, state.tasks.length - visibleCount));
|
|
47985
|
+
for (let i = 0; i < state.tasks.slice(start, start + visibleCount).length; i += 1) {
|
|
47986
|
+
const absoluteIndex = start + i;
|
|
47987
|
+
const task = state.tasks[absoluteIndex];
|
|
47988
|
+
const cursor = absoluteIndex === state.selectedIndex ? ">" : " ";
|
|
47989
|
+
const assignee = task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
47990
|
+
lines.push(`${cursor} ${task.shortId} ${task.status} ${assignee} ${task.title}`);
|
|
47991
|
+
if (task.queueState !== "none") lines.push(` queue: ${task.queueState}`);
|
|
47992
|
+
if (task.description) lines.push(` ${task.description}`);
|
|
47993
|
+
lines.push("");
|
|
47994
|
+
}
|
|
47995
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
47996
|
+
}
|
|
47997
|
+
function renderTaskDetail(state, width) {
|
|
47998
|
+
const task = state.activeTask;
|
|
47999
|
+
if (!task) return renderTasks(state, width);
|
|
48000
|
+
const assignee = task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
48001
|
+
const lines = [
|
|
48002
|
+
`Task: ${task.shortId}`,
|
|
48003
|
+
`Title: ${task.title}`,
|
|
48004
|
+
`Status: ${task.status}`,
|
|
48005
|
+
`Assignee: ${assignee}`,
|
|
48006
|
+
`Queue: ${task.queueState}`,
|
|
48007
|
+
`ID: ${task.taskId}`,
|
|
48008
|
+
task.description ? `Description: ${task.description}` : null,
|
|
48009
|
+
task.primaryChatId ? `Chat: ${task.primaryChatId}` : null,
|
|
48010
|
+
task.linkedProjectIds.length > 0 ? `Projects: ${task.linkedProjectIds.join(", ")}` : null,
|
|
48011
|
+
task.blockedReasonCode ? `Blocked reason: ${task.blockedReasonCode}` : null,
|
|
48012
|
+
task.aiExecutionState ? `AI state: ${task.aiExecutionState}` : null,
|
|
48013
|
+
"",
|
|
48014
|
+
"Actions: c create, e edit, x delete, r reorder, s start, d done, b block, u unblock, k skip, Esc back"
|
|
48015
|
+
].filter((line) => line !== null);
|
|
48016
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
48017
|
+
}
|
|
48018
|
+
function renderWorkflows(state, width) {
|
|
48019
|
+
const lines = ["Workflows", ""];
|
|
48020
|
+
if (state.workflows.length === 0) {
|
|
48021
|
+
lines.push("No workflows found.", "Create one outside TUI with: openmates workflows create --file workflow.yml");
|
|
48022
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
48023
|
+
}
|
|
48024
|
+
const visibleCount = Math.max(1, CONTENT_PREVIEW_LINES);
|
|
48025
|
+
const start = Math.max(0, Math.min(state.selectedIndex, state.workflows.length - visibleCount));
|
|
48026
|
+
for (let i = 0; i < state.workflows.slice(start, start + visibleCount).length; i += 1) {
|
|
48027
|
+
const absoluteIndex = start + i;
|
|
48028
|
+
const workflow = state.workflows[absoluteIndex];
|
|
48029
|
+
const cursor = absoluteIndex === state.selectedIndex ? ">" : " ";
|
|
48030
|
+
const status = workflow.enabled ? "enabled" : "disabled";
|
|
48031
|
+
const lastRun = workflow.last_run_status ? ` last: ${workflow.last_run_status}` : "";
|
|
48032
|
+
lines.push(`${cursor} ${workflow.title} (${status})${lastRun}`);
|
|
48033
|
+
lines.push(` ${workflow.id}`);
|
|
48034
|
+
if (workflow.trigger_summary) lines.push(` ${workflow.trigger_summary}`);
|
|
48035
|
+
lines.push("");
|
|
48036
|
+
}
|
|
48037
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
48038
|
+
}
|
|
48039
|
+
function renderWorkflowDetail(state, width) {
|
|
48040
|
+
const workflow = state.activeWorkflow;
|
|
48041
|
+
if (!workflow) return renderWorkflows(state, width);
|
|
48042
|
+
const graphNodes = workflow.graph?.nodes ?? [];
|
|
48043
|
+
const selectedRun = state.workflowRuns[state.selectedWorkflowRunIndex] ?? null;
|
|
48044
|
+
const lines = [
|
|
48045
|
+
`Workflow: ${workflow.title}`,
|
|
48046
|
+
`ID: ${workflow.id}`,
|
|
48047
|
+
`Status: ${workflow.enabled ? "enabled" : "disabled"}`,
|
|
48048
|
+
workflow.trigger_summary ? `Trigger: ${workflow.trigger_summary}` : null,
|
|
48049
|
+
workflow.next_run_at ? `Next run: ${formatTimestamp(workflow.next_run_at)}` : null,
|
|
48050
|
+
workflow.last_run_status ? `Last run: ${workflow.last_run_status}` : null,
|
|
48051
|
+
"",
|
|
48052
|
+
state.workflowTab === "graph" ? "[Graph] Runs" : "Graph [Runs]",
|
|
48053
|
+
""
|
|
48054
|
+
].filter((line) => line !== null);
|
|
48055
|
+
if (state.workflowTab === "runs") {
|
|
48056
|
+
lines.push(...renderRunSelector(state, width), "");
|
|
48057
|
+
if (selectedRun) {
|
|
48058
|
+
lines.push(`Run graph: ${selectedRun.id} (${selectedRun.status})`);
|
|
48059
|
+
lines.push(...renderWorkflowGraph({ nodes: graphNodes, state, width, run: selectedRun }));
|
|
48060
|
+
} else {
|
|
48061
|
+
lines.push("No runs yet.");
|
|
48062
|
+
}
|
|
48063
|
+
} else {
|
|
48064
|
+
lines.push("Graph");
|
|
48065
|
+
lines.push(...renderWorkflowGraph({ nodes: graphNodes, state, width, run: null }));
|
|
48066
|
+
if (state.workflowEdit) lines.push("", `Editing ${state.workflowEdit.field}: ${state.workflowEdit.value}`);
|
|
48067
|
+
}
|
|
48068
|
+
if (state.status) lines.push("", state.status);
|
|
48069
|
+
return lines.flatMap((line) => wrap(line, width));
|
|
48070
|
+
}
|
|
48071
|
+
function renderRunSelector(state, width) {
|
|
48072
|
+
if (state.workflowRuns.length === 0) return ["Runs", "No runs yet."];
|
|
48073
|
+
const lines = ["Runs"];
|
|
48074
|
+
const visibleRuns = state.workflowRuns.slice(0, 5);
|
|
48075
|
+
for (let index = 0; index < visibleRuns.length; index += 1) {
|
|
48076
|
+
const run = visibleRuns[index];
|
|
48077
|
+
const cursor = index === state.selectedWorkflowRunIndex ? ">" : " ";
|
|
48078
|
+
lines.push(`${cursor} ${run.id} ${run.status} ${formatTimestamp(run.started_at)}`);
|
|
48079
|
+
}
|
|
48080
|
+
return lines.map((line) => truncateVisible(line, width));
|
|
48081
|
+
}
|
|
48082
|
+
function renderWorkflowGraph(params) {
|
|
48083
|
+
const { nodes, state, run } = params;
|
|
48084
|
+
if (nodes.length === 0) return ["No graph nodes available."];
|
|
48085
|
+
const lines = [];
|
|
48086
|
+
const selectedIndex = state.workflowTab === "runs" ? selectedRunGraphNodeIndex(nodes, state) : state.selectedWorkflowNodeIndex;
|
|
48087
|
+
const expandedId = state.workflowTab === "runs" ? state.expandedWorkflowRunNodeId : state.expandedWorkflowNodeId;
|
|
48088
|
+
const nodeRunsById = new Map((run?.node_runs ?? []).map((nodeRun) => [nodeRun.node_id, nodeRun]));
|
|
48089
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
48090
|
+
const node = nodes[index];
|
|
48091
|
+
const nodeRun = nodeRunsById.get(node.id) ?? null;
|
|
48092
|
+
const cursor = index === selectedIndex ? ">" : " ";
|
|
48093
|
+
const status = nodeRun ? ` [${nodeRun.status}]` : "";
|
|
48094
|
+
lines.push(`${cursor} [${nodeTypeLabel(node.type)}] ${node.title ?? cardSummary(node)}${status}`);
|
|
48095
|
+
if (nodeRun?.output_summary) lines.push(` output: ${summarizeObject(nodeRun.output_summary)}`);
|
|
48096
|
+
if (nodeRun?.error_summary) lines.push(` error: ${nodeRun.error_summary}`);
|
|
48097
|
+
if (expandedId === node.id) {
|
|
48098
|
+
lines.push(...renderExpandedNode(node, nodeRun));
|
|
48099
|
+
}
|
|
48100
|
+
if (index < nodes.length - 1) lines.push(" |");
|
|
48101
|
+
}
|
|
48102
|
+
return lines;
|
|
48103
|
+
}
|
|
48104
|
+
function selectedRunGraphNodeIndex(nodes, state) {
|
|
48105
|
+
const run = state.workflowRuns[state.selectedWorkflowRunIndex];
|
|
48106
|
+
const nodeRun = run?.node_runs?.find((candidate) => candidate.node_id === state.expandedWorkflowRunNodeId);
|
|
48107
|
+
if (!nodeRun) return Math.min(state.selectedWorkflowNodeIndex, Math.max(0, nodes.length - 1));
|
|
48108
|
+
return Math.max(0, nodes.findIndex((node) => node.id === nodeRun.node_id));
|
|
48109
|
+
}
|
|
48110
|
+
function renderExpandedNode(node, nodeRun) {
|
|
48111
|
+
const lines = [
|
|
48112
|
+
` id: ${node.id}`,
|
|
48113
|
+
` type: ${node.type}`
|
|
48114
|
+
];
|
|
48115
|
+
if (node.config && Object.keys(node.config).length > 0) lines.push(` config: ${summarizeObject(node.config)}`);
|
|
48116
|
+
if (node.input_mapping && Object.keys(node.input_mapping).length > 0) lines.push(` input: ${summarizeObject(node.input_mapping)}`);
|
|
48117
|
+
if (nodeRun?.input_summary) lines.push(` run input: ${summarizeObject(nodeRun.input_summary)}`);
|
|
48118
|
+
if (nodeRun?.output_summary) lines.push(` run output: ${summarizeObject(nodeRun.output_summary)}`);
|
|
48119
|
+
return lines;
|
|
48120
|
+
}
|
|
47078
48121
|
function renderInterests(state, width) {
|
|
47079
48122
|
const lines = [
|
|
47080
48123
|
"Private local personalization",
|
|
@@ -47169,13 +48212,55 @@ function renderMessageContent(content, width) {
|
|
|
47169
48212
|
}
|
|
47170
48213
|
function renderHintLine(state, width) {
|
|
47171
48214
|
if (state.screen === "interests") return truncateVisible("\u2191/\u2193 move Space select Enter continue Esc back", width);
|
|
48215
|
+
if (state.screen === "tasks") return truncateVisible("\u2191/\u2193 choose Enter open c create Esc back", width);
|
|
48216
|
+
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);
|
|
48217
|
+
if (state.screen === "workflows") return truncateVisible("\u2191/\u2193 choose Enter open Esc back", width);
|
|
48218
|
+
if (state.screen === "workflow" && state.workflowEdit) return truncateVisible("Enter save title Esc cancel edit", width);
|
|
48219
|
+
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);
|
|
47172
48220
|
return truncateVisible("\u2191/\u2193 choose Enter open /search filter Esc back", width);
|
|
47173
48221
|
}
|
|
47174
48222
|
function inputPlaceholder(state) {
|
|
47175
48223
|
if (state.screen === "example") return "Continue from this example, or ask your own question...";
|
|
47176
48224
|
if (state.screen === "chat") return "Ask a follow-up, use @file, or type /help";
|
|
48225
|
+
if (state.screen === "workflow" || state.screen === "workflows" || state.screen === "tasks" || state.screen === "task") return "Use shortcuts below, or type /help";
|
|
47177
48226
|
return "Ask anything...";
|
|
47178
48227
|
}
|
|
48228
|
+
function nodeTypeLabel(type) {
|
|
48229
|
+
switch (type) {
|
|
48230
|
+
case "manual_trigger":
|
|
48231
|
+
return "manual trigger";
|
|
48232
|
+
case "schedule_trigger":
|
|
48233
|
+
return "schedule";
|
|
48234
|
+
case "app_skill_action":
|
|
48235
|
+
return "app skill";
|
|
48236
|
+
case "send_notification":
|
|
48237
|
+
return "notification";
|
|
48238
|
+
case "ask_user":
|
|
48239
|
+
return "ask user";
|
|
48240
|
+
default:
|
|
48241
|
+
return type.replaceAll("_", " ");
|
|
48242
|
+
}
|
|
48243
|
+
}
|
|
48244
|
+
function cardSummary(node) {
|
|
48245
|
+
const config = node.config ?? {};
|
|
48246
|
+
if (node.type === "app_skill_action") {
|
|
48247
|
+
const app = typeof config.app === "string" ? config.app : "app";
|
|
48248
|
+
const skill = typeof config.skill === "string" ? config.skill : "skill";
|
|
48249
|
+
return `${app}.${skill}`;
|
|
48250
|
+
}
|
|
48251
|
+
if (node.type === "decision") return "If condition";
|
|
48252
|
+
if (node.type === "send_notification") return "Send notification";
|
|
48253
|
+
if (node.type === "ask_user") return "Ask for user input";
|
|
48254
|
+
return node.id;
|
|
48255
|
+
}
|
|
48256
|
+
function formatTimestamp(value) {
|
|
48257
|
+
if (!value) return "-";
|
|
48258
|
+
return new Date(value * 1e3).toISOString().replace("T", " ").slice(0, 16);
|
|
48259
|
+
}
|
|
48260
|
+
function summarizeObject(value) {
|
|
48261
|
+
const entries = Object.entries(value).slice(0, 3).map(([key, item]) => `${key}=${String(item)}`);
|
|
48262
|
+
return entries.join(", ") || "object";
|
|
48263
|
+
}
|
|
47179
48264
|
function interestScore(haystack, interest) {
|
|
47180
48265
|
let score = haystack.includes(interest) ? 10 : 0;
|
|
47181
48266
|
for (const token of interest.split(/\s+|&/).filter((part) => part.length > 2)) {
|
|
@@ -47226,6 +48311,221 @@ function boxed(line, width) {
|
|
|
47226
48311
|
return `\u2502 ${line.padEnd(width)} \u2502`;
|
|
47227
48312
|
}
|
|
47228
48313
|
|
|
48314
|
+
// src/tasksCli.ts
|
|
48315
|
+
import { createHash as createHash7, randomBytes as randomBytes3, randomUUID as randomUUID5 } from "crypto";
|
|
48316
|
+
var TASK_STATUSES = ["backlog", "todo", "in_progress", "blocked", "done"];
|
|
48317
|
+
var DEFAULT_STANDALONE_PREFIX = "TASK";
|
|
48318
|
+
function normalizeTaskStatus(value) {
|
|
48319
|
+
if (value === void 0) return void 0;
|
|
48320
|
+
if (TASK_STATUSES.includes(value)) return value;
|
|
48321
|
+
throw new Error(`Unknown task status '${value}'. Expected one of: ${TASK_STATUSES.join(", ")}`);
|
|
48322
|
+
}
|
|
48323
|
+
function parseAssignee(value) {
|
|
48324
|
+
if (!value || value === "user") return { assigneeType: "user", assigneeHash: null };
|
|
48325
|
+
if (["ai", "openmates", "OpenMates"].includes(value)) return { assigneeType: "ai", assigneeHash: null };
|
|
48326
|
+
return { assigneeType: "user", assigneeHash: value };
|
|
48327
|
+
}
|
|
48328
|
+
function splitCsvFlag(value) {
|
|
48329
|
+
if (typeof value !== "string") return [];
|
|
48330
|
+
return value.split(/[,\n]/).map((item) => item.trim()).filter(Boolean);
|
|
48331
|
+
}
|
|
48332
|
+
function parseDueAt(value) {
|
|
48333
|
+
if (value === void 0) return void 0;
|
|
48334
|
+
if (value === false || value === true) throw new Error("--due requires a timestamp or date value.");
|
|
48335
|
+
const numeric = Number(value);
|
|
48336
|
+
if (Number.isFinite(numeric) && numeric > 0) return Math.floor(numeric);
|
|
48337
|
+
const parsed = Date.parse(value);
|
|
48338
|
+
if (Number.isNaN(parsed)) throw new Error(`Invalid --due value '${value}'.`);
|
|
48339
|
+
return Math.floor(parsed / 1e3);
|
|
48340
|
+
}
|
|
48341
|
+
async function buildCreateUserTaskInput(masterKey, input) {
|
|
48342
|
+
const taskKey = randomBytes3(32);
|
|
48343
|
+
const encryptedTaskKey = await encryptBytesWithAesGcm(taskKey, masterKey);
|
|
48344
|
+
const timestamp = nowSeconds();
|
|
48345
|
+
const assignee = parseAssignee(input.assign);
|
|
48346
|
+
const linkedProjectIds = input.projectIds ?? [];
|
|
48347
|
+
const status = input.status ?? (assignee.assigneeType === "ai" && !input.dueAt ? "in_progress" : "todo");
|
|
48348
|
+
return {
|
|
48349
|
+
task_id: randomUUIDCompat(),
|
|
48350
|
+
short_id: void 0,
|
|
48351
|
+
encrypted_task_key: encryptedTaskKey,
|
|
48352
|
+
encrypted_title: await encryptWithAesGcmCombined(input.title, taskKey),
|
|
48353
|
+
encrypted_description: await encryptWithAesGcmCombined(input.description ?? "", taskKey),
|
|
48354
|
+
encrypted_tags: await encryptWithAesGcmCombined("[]", taskKey),
|
|
48355
|
+
encrypted_linked_project_ids: await encryptWithAesGcmCombined(JSON.stringify(linkedProjectIds), taskKey),
|
|
48356
|
+
status,
|
|
48357
|
+
assignee_type: assignee.assigneeType,
|
|
48358
|
+
assignee_hash: assignee.assigneeHash,
|
|
48359
|
+
primary_chat_id: input.chatId ?? null,
|
|
48360
|
+
linked_project_ids: linkedProjectIds,
|
|
48361
|
+
plan_id: input.planId ?? null,
|
|
48362
|
+
due_at: input.dueAt ?? null,
|
|
48363
|
+
priority: 0,
|
|
48364
|
+
position: timestamp,
|
|
48365
|
+
created_at: timestamp,
|
|
48366
|
+
updated_at: timestamp
|
|
48367
|
+
};
|
|
48368
|
+
}
|
|
48369
|
+
async function buildUpdateUserTaskInput(task, masterKey, input) {
|
|
48370
|
+
const taskKey = await taskKeyFromRecord(task.encrypted, masterKey);
|
|
48371
|
+
const patch = { version: task.version, updated_at: nowSeconds() };
|
|
48372
|
+
if (input.title !== void 0) patch.encrypted_title = await encryptWithAesGcmCombined(input.title, taskKey);
|
|
48373
|
+
if (input.description !== void 0) patch.encrypted_description = await encryptWithAesGcmCombined(input.description, taskKey);
|
|
48374
|
+
if (input.status !== void 0) patch.status = input.status;
|
|
48375
|
+
if (input.assign !== void 0) {
|
|
48376
|
+
const assignee = parseAssignee(input.assign);
|
|
48377
|
+
patch.assignee_type = assignee.assigneeType;
|
|
48378
|
+
patch.assignee_hash = assignee.assigneeHash;
|
|
48379
|
+
}
|
|
48380
|
+
if (input.chatId !== void 0) patch.primary_chat_id = input.chatId;
|
|
48381
|
+
if (input.projectIds !== void 0) {
|
|
48382
|
+
patch.linked_project_ids = input.projectIds;
|
|
48383
|
+
patch.encrypted_linked_project_ids = await encryptWithAesGcmCombined(JSON.stringify(input.projectIds), taskKey);
|
|
48384
|
+
}
|
|
48385
|
+
if (input.planId !== void 0) patch.plan_id = input.planId;
|
|
48386
|
+
return patch;
|
|
48387
|
+
}
|
|
48388
|
+
async function decryptUserTask(record, masterKey) {
|
|
48389
|
+
const taskKey = await taskKeyFromRecord(record, masterKey);
|
|
48390
|
+
const tags = parseStringArray(await decryptOptional(record.encrypted_tags, taskKey));
|
|
48391
|
+
const linkedProjectIds = parseStringArray(await decryptOptional(record.encrypted_linked_project_ids, taskKey));
|
|
48392
|
+
return {
|
|
48393
|
+
taskId: record.task_id,
|
|
48394
|
+
shortId: record.short_id || deriveShortId(record),
|
|
48395
|
+
title: await decryptOptional(record.encrypted_title, taskKey) || "(untitled task)",
|
|
48396
|
+
description: await decryptOptional(record.encrypted_description, taskKey),
|
|
48397
|
+
tags,
|
|
48398
|
+
latestInstruction: await decryptOptional(record.encrypted_latest_instruction, taskKey),
|
|
48399
|
+
status: record.status,
|
|
48400
|
+
assigneeType: record.assignee_type,
|
|
48401
|
+
assigneeHash: record.assignee_hash ?? null,
|
|
48402
|
+
primaryChatId: record.primary_chat_id ?? null,
|
|
48403
|
+
linkedProjectIds: linkedProjectIds.length > 0 ? linkedProjectIds : record.linked_project_ids ?? [],
|
|
48404
|
+
planId: record.plan_id ?? null,
|
|
48405
|
+
dueAt: record.due_at ?? null,
|
|
48406
|
+
priority: record.priority ?? 0,
|
|
48407
|
+
position: record.position ?? 0,
|
|
48408
|
+
queueState: record.queue_state ?? "none",
|
|
48409
|
+
blockedReasonCode: record.blocked_reason_code ?? null,
|
|
48410
|
+
aiExecutionState: record.ai_execution_state ?? null,
|
|
48411
|
+
version: record.version ?? 1,
|
|
48412
|
+
encrypted: record
|
|
48413
|
+
};
|
|
48414
|
+
}
|
|
48415
|
+
async function decryptUserTasks(records, masterKey) {
|
|
48416
|
+
const output = [];
|
|
48417
|
+
for (const record of records) output.push(await decryptUserTask(record, masterKey));
|
|
48418
|
+
return output;
|
|
48419
|
+
}
|
|
48420
|
+
function findTask(tasks, id) {
|
|
48421
|
+
const task = tasks.find((candidate) => candidate.taskId === id || candidate.shortId === id);
|
|
48422
|
+
if (!task) throw new Error(`Task '${id}' was not found in the current task list.`);
|
|
48423
|
+
return task;
|
|
48424
|
+
}
|
|
48425
|
+
function renderTaskList(tasks) {
|
|
48426
|
+
if (tasks.length === 0) return "No tasks found.";
|
|
48427
|
+
const lines = ["Tasks", "ID Status Assignee Queue Title"];
|
|
48428
|
+
for (const task of tasks) {
|
|
48429
|
+
lines.push(`${pad(task.shortId, 9)} ${pad(task.status, 12)} ${pad(assigneeLabel(task), 11)} ${pad(task.queueState, 11)} ${task.title}`);
|
|
48430
|
+
}
|
|
48431
|
+
return lines.join("\n");
|
|
48432
|
+
}
|
|
48433
|
+
function renderTaskDetail2(task) {
|
|
48434
|
+
const lines = [
|
|
48435
|
+
`Task ${task.shortId}`,
|
|
48436
|
+
`Title: ${task.title}`,
|
|
48437
|
+
`Status: ${task.status}`,
|
|
48438
|
+
`Assignee: ${assigneeLabel(task)}`,
|
|
48439
|
+
`Queue: ${task.queueState}`,
|
|
48440
|
+
`Task ID: ${task.taskId}`
|
|
48441
|
+
];
|
|
48442
|
+
if (task.description) lines.push(`Description: ${task.description}`);
|
|
48443
|
+
if (task.primaryChatId) lines.push(`Chat: ${task.primaryChatId}`);
|
|
48444
|
+
if (task.linkedProjectIds.length > 0) lines.push(`Projects: ${task.linkedProjectIds.join(", ")}`);
|
|
48445
|
+
if (task.planId) lines.push(`Plan: ${task.planId}`);
|
|
48446
|
+
if (task.blockedReasonCode) lines.push(`Blocked reason: ${task.blockedReasonCode}`);
|
|
48447
|
+
if (task.aiExecutionState) lines.push(`AI state: ${task.aiExecutionState}`);
|
|
48448
|
+
return lines.join("\n");
|
|
48449
|
+
}
|
|
48450
|
+
function renderTaskBoard(tasks, width = process.stdout.columns || 100) {
|
|
48451
|
+
const columns = TASK_STATUSES.map((status) => ({ status, tasks: tasks.filter((task) => task.status === status).sort(compareTasks) }));
|
|
48452
|
+
if (width < 96) {
|
|
48453
|
+
const lines2 = ["OpenMates Tasks Board"];
|
|
48454
|
+
for (const column of columns) {
|
|
48455
|
+
lines2.push("", `${columnTitle(column.status)} (${column.tasks.length})`, "-".repeat(24));
|
|
48456
|
+
lines2.push(...boardColumnLines(column.tasks, 8));
|
|
48457
|
+
}
|
|
48458
|
+
return lines2.join("\n");
|
|
48459
|
+
}
|
|
48460
|
+
const perColumn = 6;
|
|
48461
|
+
const columnWidth = 22;
|
|
48462
|
+
const lines = ["OpenMates Tasks Board", ""];
|
|
48463
|
+
lines.push(columns.map((column) => pad(`${columnTitle(column.status)} (${column.tasks.length})`, columnWidth)).join(" "));
|
|
48464
|
+
lines.push(columns.map(() => "-".repeat(columnWidth)).join(" "));
|
|
48465
|
+
const renderedColumns = columns.map((column) => boardColumnLines(column.tasks, perColumn).map((line) => truncate(line, columnWidth)));
|
|
48466
|
+
const maxRows = Math.max(...renderedColumns.map((column) => column.length));
|
|
48467
|
+
for (let row = 0; row < maxRows; row += 1) {
|
|
48468
|
+
lines.push(renderedColumns.map((column) => pad(column[row] ?? "", columnWidth)).join(" "));
|
|
48469
|
+
}
|
|
48470
|
+
return lines.join("\n");
|
|
48471
|
+
}
|
|
48472
|
+
function boardColumnLines(tasks, limit) {
|
|
48473
|
+
if (tasks.length === 0) return ["No tasks here."];
|
|
48474
|
+
const visible = tasks.slice(0, limit).flatMap((task) => [
|
|
48475
|
+
`[${task.shortId}] ${task.title}`,
|
|
48476
|
+
` ${assigneeLabel(task)} ${task.queueState === "none" ? "" : task.queueState}`.trimEnd()
|
|
48477
|
+
]);
|
|
48478
|
+
if (tasks.length > limit) visible.push(`... ${tasks.length - limit} more`);
|
|
48479
|
+
return visible;
|
|
48480
|
+
}
|
|
48481
|
+
function compareTasks(a, b) {
|
|
48482
|
+
return a.position - b.position || a.title.localeCompare(b.title);
|
|
48483
|
+
}
|
|
48484
|
+
function columnTitle(status) {
|
|
48485
|
+
if (status === "in_progress") return "In progress";
|
|
48486
|
+
return status.slice(0, 1).toUpperCase() + status.slice(1).replace("_", " ");
|
|
48487
|
+
}
|
|
48488
|
+
function assigneeLabel(task) {
|
|
48489
|
+
return task.assigneeType === "ai" ? "OpenMates" : task.assigneeHash ?? "user";
|
|
48490
|
+
}
|
|
48491
|
+
async function taskKeyFromRecord(record, masterKey) {
|
|
48492
|
+
if (!record.encrypted_task_key) throw new Error(`Task ${record.task_id} is missing encrypted task key.`);
|
|
48493
|
+
const taskKey = await decryptBytesWithAesGcm(record.encrypted_task_key, masterKey);
|
|
48494
|
+
if (!taskKey) throw new Error(`Failed to decrypt task key for ${record.task_id}.`);
|
|
48495
|
+
return taskKey;
|
|
48496
|
+
}
|
|
48497
|
+
async function decryptOptional(value, key) {
|
|
48498
|
+
if (!value) return "";
|
|
48499
|
+
return await decryptWithAesGcmCombined(value, key) ?? "";
|
|
48500
|
+
}
|
|
48501
|
+
function parseStringArray(value) {
|
|
48502
|
+
if (!value) return [];
|
|
48503
|
+
try {
|
|
48504
|
+
const parsed = JSON.parse(value);
|
|
48505
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
|
|
48506
|
+
} catch {
|
|
48507
|
+
return [];
|
|
48508
|
+
}
|
|
48509
|
+
}
|
|
48510
|
+
function deriveShortId(record) {
|
|
48511
|
+
const prefix = record.short_id_prefix || DEFAULT_STANDALONE_PREFIX;
|
|
48512
|
+
const source = record.task_id || `${record.created_at ?? ""}-${record.position ?? ""}`;
|
|
48513
|
+
const digest = createHash7("sha256").update(source).digest("hex").slice(0, 4).toUpperCase();
|
|
48514
|
+
return `${prefix}-${parseInt(digest, 16) % 1e4}`;
|
|
48515
|
+
}
|
|
48516
|
+
function nowSeconds() {
|
|
48517
|
+
return Math.floor(Date.now() / 1e3);
|
|
48518
|
+
}
|
|
48519
|
+
function randomUUIDCompat() {
|
|
48520
|
+
return randomUUID5();
|
|
48521
|
+
}
|
|
48522
|
+
function pad(value, length) {
|
|
48523
|
+
return truncate(value, length).padEnd(length);
|
|
48524
|
+
}
|
|
48525
|
+
function truncate(value, length) {
|
|
48526
|
+
return value.length <= length ? value : `${value.slice(0, Math.max(0, length - 3))}...`;
|
|
48527
|
+
}
|
|
48528
|
+
|
|
47229
48529
|
// src/tui.ts
|
|
47230
48530
|
function defaultModeForStreams(input, output) {
|
|
47231
48531
|
return input.isTTY === true && output.isTTY === true ? "tui" : "quickstart";
|
|
@@ -47268,11 +48568,78 @@ async function handleKey(params) {
|
|
|
47268
48568
|
return;
|
|
47269
48569
|
}
|
|
47270
48570
|
if (key.name === "escape") {
|
|
47271
|
-
state.
|
|
48571
|
+
if (state.workflowEdit) {
|
|
48572
|
+
state.workflowEdit = null;
|
|
48573
|
+
render();
|
|
48574
|
+
return;
|
|
48575
|
+
}
|
|
48576
|
+
state.screen = state.screen === "workflow" ? "workflows" : state.screen === "task" ? "tasks" : "start";
|
|
47272
48577
|
state.input = "";
|
|
47273
48578
|
render();
|
|
47274
48579
|
return;
|
|
47275
48580
|
}
|
|
48581
|
+
if (state.screen === "workflow" && state.workflowEdit) {
|
|
48582
|
+
if (key.name === "return") {
|
|
48583
|
+
await saveWorkflowNodeTitle({ state, client, render });
|
|
48584
|
+
return;
|
|
48585
|
+
}
|
|
48586
|
+
if (key.name === "backspace") {
|
|
48587
|
+
state.workflowEdit.value = state.workflowEdit.value.slice(0, -1);
|
|
48588
|
+
render();
|
|
48589
|
+
return;
|
|
48590
|
+
}
|
|
48591
|
+
if (!key.ctrl && !key.meta && chunk && chunk >= " ") {
|
|
48592
|
+
state.workflowEdit.value += chunk;
|
|
48593
|
+
render();
|
|
48594
|
+
}
|
|
48595
|
+
return;
|
|
48596
|
+
}
|
|
48597
|
+
if (state.screen === "workflow" && !state.input && !key.ctrl && !key.meta) {
|
|
48598
|
+
if (chunk === "g") {
|
|
48599
|
+
state.workflowTab = "graph";
|
|
48600
|
+
render();
|
|
48601
|
+
return;
|
|
48602
|
+
}
|
|
48603
|
+
if (chunk === "r") {
|
|
48604
|
+
state.workflowTab = "runs";
|
|
48605
|
+
render();
|
|
48606
|
+
return;
|
|
48607
|
+
}
|
|
48608
|
+
if (chunk === "x") {
|
|
48609
|
+
await runActiveWorkflow({ state, client, render });
|
|
48610
|
+
return;
|
|
48611
|
+
}
|
|
48612
|
+
if (chunk === "u") {
|
|
48613
|
+
await refreshActiveWorkflowRuns({ state, client, render });
|
|
48614
|
+
return;
|
|
48615
|
+
}
|
|
48616
|
+
if (chunk === "c") {
|
|
48617
|
+
await cancelLatestWorkflowRun({ state, client, render });
|
|
48618
|
+
return;
|
|
48619
|
+
}
|
|
48620
|
+
if (chunk === "e") {
|
|
48621
|
+
startWorkflowNodeEdit(state, "title");
|
|
48622
|
+
render();
|
|
48623
|
+
return;
|
|
48624
|
+
}
|
|
48625
|
+
if (chunk === "E") {
|
|
48626
|
+
startWorkflowNodeEdit(state, "config");
|
|
48627
|
+
render();
|
|
48628
|
+
return;
|
|
48629
|
+
}
|
|
48630
|
+
}
|
|
48631
|
+
if ((state.screen === "tasks" || state.screen === "task") && !state.input && !key.ctrl && !key.meta) {
|
|
48632
|
+
if (chunk === "c") {
|
|
48633
|
+
await createTaskFromTui({ state, client, terminal, render });
|
|
48634
|
+
return;
|
|
48635
|
+
}
|
|
48636
|
+
}
|
|
48637
|
+
if (state.screen === "task" && !state.input && !key.ctrl && !key.meta) {
|
|
48638
|
+
if (["s", "d", "b", "u", "k", "e", "x", "r"].includes(chunk)) {
|
|
48639
|
+
await handleActiveTaskAction({ state, client, terminal, actionKey: chunk, render });
|
|
48640
|
+
return;
|
|
48641
|
+
}
|
|
48642
|
+
}
|
|
47276
48643
|
if (key.name === "up") {
|
|
47277
48644
|
moveSelectionOrScroll(state, -1);
|
|
47278
48645
|
render();
|
|
@@ -47317,6 +48684,9 @@ async function handleKey(params) {
|
|
|
47317
48684
|
await handleEnter({ state, client, terminal, render, finish });
|
|
47318
48685
|
return;
|
|
47319
48686
|
}
|
|
48687
|
+
if (state.screen === "workflow" || state.screen === "workflows" || state.screen === "tasks" || state.screen === "task") {
|
|
48688
|
+
return;
|
|
48689
|
+
}
|
|
47320
48690
|
if (!key.ctrl && !key.meta && chunk && chunk >= " ") {
|
|
47321
48691
|
state.input += chunk;
|
|
47322
48692
|
render();
|
|
@@ -47335,6 +48705,23 @@ async function handleEnter(params) {
|
|
|
47335
48705
|
render();
|
|
47336
48706
|
return;
|
|
47337
48707
|
}
|
|
48708
|
+
if (state.screen === "workflows") {
|
|
48709
|
+
const selected = state.workflows[state.selectedIndex];
|
|
48710
|
+
if (selected) await openWorkflowDetail({ state, client, workflow: selected, render });
|
|
48711
|
+
render();
|
|
48712
|
+
return;
|
|
48713
|
+
}
|
|
48714
|
+
if (state.screen === "tasks") {
|
|
48715
|
+
const selected = state.tasks[state.selectedIndex];
|
|
48716
|
+
if (selected) openTaskDetail(state, selected);
|
|
48717
|
+
render();
|
|
48718
|
+
return;
|
|
48719
|
+
}
|
|
48720
|
+
if (state.screen === "workflow") {
|
|
48721
|
+
toggleSelectedWorkflowNode(state);
|
|
48722
|
+
render();
|
|
48723
|
+
return;
|
|
48724
|
+
}
|
|
47338
48725
|
const text = state.input.trim();
|
|
47339
48726
|
if (!text) return;
|
|
47340
48727
|
state.input = "";
|
|
@@ -47361,6 +48748,22 @@ async function handleCommand(params) {
|
|
|
47361
48748
|
render();
|
|
47362
48749
|
return;
|
|
47363
48750
|
}
|
|
48751
|
+
if (name === "/workflows") {
|
|
48752
|
+
await openWorkflowList({ state, client, render });
|
|
48753
|
+
return;
|
|
48754
|
+
}
|
|
48755
|
+
if (name === "/tasks") {
|
|
48756
|
+
await openTaskList({ state, client, render });
|
|
48757
|
+
return;
|
|
48758
|
+
}
|
|
48759
|
+
if (name === "/workflow") {
|
|
48760
|
+
if (!arg) {
|
|
48761
|
+
await openWorkflowList({ state, client, render });
|
|
48762
|
+
return;
|
|
48763
|
+
}
|
|
48764
|
+
await openWorkflowById({ state, client, workflowId: arg, render });
|
|
48765
|
+
return;
|
|
48766
|
+
}
|
|
47364
48767
|
if (name === "/signup") {
|
|
47365
48768
|
finish({ action: "signup" });
|
|
47366
48769
|
return;
|
|
@@ -47464,8 +48867,368 @@ function moveSelectionOrScroll(state, direction) {
|
|
|
47464
48867
|
state.selectedIndex = clamp(state.selectedIndex + direction, 0, Math.max(0, state.examples.length - 1));
|
|
47465
48868
|
return;
|
|
47466
48869
|
}
|
|
48870
|
+
if (state.screen === "workflows") {
|
|
48871
|
+
state.selectedIndex = clamp(state.selectedIndex + direction, 0, Math.max(0, state.workflows.length - 1));
|
|
48872
|
+
return;
|
|
48873
|
+
}
|
|
48874
|
+
if (state.screen === "tasks") {
|
|
48875
|
+
state.selectedIndex = clamp(state.selectedIndex + direction, 0, Math.max(0, state.tasks.length - 1));
|
|
48876
|
+
return;
|
|
48877
|
+
}
|
|
48878
|
+
if (state.screen === "workflow") {
|
|
48879
|
+
if (state.workflowTab === "runs") {
|
|
48880
|
+
state.selectedWorkflowRunIndex = clamp(state.selectedWorkflowRunIndex + direction, 0, Math.max(0, state.workflowRuns.length - 1));
|
|
48881
|
+
const selectedRun = state.workflowRuns[state.selectedWorkflowRunIndex];
|
|
48882
|
+
state.selectedWorkflowNodeIndex = firstRunNodeIndex(state, selectedRun);
|
|
48883
|
+
return;
|
|
48884
|
+
}
|
|
48885
|
+
const nodeCount = state.activeWorkflow?.graph.nodes.length ?? 0;
|
|
48886
|
+
state.selectedWorkflowNodeIndex = clamp(state.selectedWorkflowNodeIndex + direction, 0, Math.max(0, nodeCount - 1));
|
|
48887
|
+
return;
|
|
48888
|
+
}
|
|
47467
48889
|
state.scrollOffset = Math.max(0, state.scrollOffset - direction);
|
|
47468
48890
|
}
|
|
48891
|
+
function toggleSelectedWorkflowNode(state) {
|
|
48892
|
+
const workflow = state.activeWorkflow;
|
|
48893
|
+
if (!workflow) return;
|
|
48894
|
+
const node = workflow.graph.nodes[state.selectedWorkflowNodeIndex];
|
|
48895
|
+
if (!node) return;
|
|
48896
|
+
if (state.workflowTab === "runs") {
|
|
48897
|
+
state.expandedWorkflowRunNodeId = state.expandedWorkflowRunNodeId === node.id ? null : node.id;
|
|
48898
|
+
} else {
|
|
48899
|
+
state.expandedWorkflowNodeId = state.expandedWorkflowNodeId === node.id ? null : node.id;
|
|
48900
|
+
}
|
|
48901
|
+
}
|
|
48902
|
+
function firstRunNodeIndex(state, run) {
|
|
48903
|
+
const workflow = state.activeWorkflow;
|
|
48904
|
+
const firstNodeRun = run?.node_runs?.[0];
|
|
48905
|
+
if (!workflow || !firstNodeRun) return 0;
|
|
48906
|
+
return Math.max(0, workflow.graph.nodes.findIndex((node) => node.id === firstNodeRun.node_id));
|
|
48907
|
+
}
|
|
48908
|
+
async function openTaskList(params) {
|
|
48909
|
+
const { state, client, render } = params;
|
|
48910
|
+
state.screen = "status";
|
|
48911
|
+
state.status = "Loading tasks...";
|
|
48912
|
+
render();
|
|
48913
|
+
try {
|
|
48914
|
+
state.tasks = await decryptUserTasks(await client.listUserTasks(), client.getMasterKeyBytes());
|
|
48915
|
+
state.selectedIndex = 0;
|
|
48916
|
+
state.scrollOffset = 0;
|
|
48917
|
+
state.activeTask = null;
|
|
48918
|
+
state.status = null;
|
|
48919
|
+
state.screen = "tasks";
|
|
48920
|
+
} catch (error) {
|
|
48921
|
+
state.status = taskError(error, "Could not load tasks. Use /login first if you are not signed in.");
|
|
48922
|
+
state.screen = "status";
|
|
48923
|
+
}
|
|
48924
|
+
render();
|
|
48925
|
+
}
|
|
48926
|
+
function openTaskDetail(state, task) {
|
|
48927
|
+
state.activeTask = task;
|
|
48928
|
+
state.screen = "task";
|
|
48929
|
+
state.scrollOffset = 0;
|
|
48930
|
+
}
|
|
48931
|
+
async function handleActiveTaskAction(params) {
|
|
48932
|
+
const { state, client, terminal, actionKey, render } = params;
|
|
48933
|
+
const task = state.activeTask;
|
|
48934
|
+
if (!task) return;
|
|
48935
|
+
state.status = "Updating task...";
|
|
48936
|
+
render();
|
|
48937
|
+
try {
|
|
48938
|
+
if (actionKey === "e") {
|
|
48939
|
+
const title = await promptLine(terminal, `New title for ${task.shortId}: `);
|
|
48940
|
+
if (!title.trim()) {
|
|
48941
|
+
state.status = "Edit cancelled.";
|
|
48942
|
+
render();
|
|
48943
|
+
return;
|
|
48944
|
+
}
|
|
48945
|
+
const patch = await buildUpdateUserTaskInput(task, client.getMasterKeyBytes(), { title: title.trim() });
|
|
48946
|
+
const updated2 = await client.updateUserTask(task.taskId, patch);
|
|
48947
|
+
const decrypted2 = await decryptUserTask(updated2, client.getMasterKeyBytes());
|
|
48948
|
+
replaceTask(state, decrypted2);
|
|
48949
|
+
state.activeTask = decrypted2;
|
|
48950
|
+
state.status = null;
|
|
48951
|
+
state.screen = "task";
|
|
48952
|
+
render();
|
|
48953
|
+
return;
|
|
48954
|
+
}
|
|
48955
|
+
if (actionKey === "x") {
|
|
48956
|
+
const confirmation = await promptLine(terminal, `Type DELETE to delete ${task.shortId}: `);
|
|
48957
|
+
if (confirmation !== "DELETE") {
|
|
48958
|
+
state.status = "Delete cancelled.";
|
|
48959
|
+
render();
|
|
48960
|
+
return;
|
|
48961
|
+
}
|
|
48962
|
+
await client.deleteUserTask(task.taskId);
|
|
48963
|
+
state.tasks = state.tasks.filter((candidate) => candidate.taskId !== task.taskId);
|
|
48964
|
+
state.activeTask = null;
|
|
48965
|
+
state.selectedIndex = Math.min(state.selectedIndex, Math.max(0, state.tasks.length - 1));
|
|
48966
|
+
state.status = `Deleted ${task.shortId}.`;
|
|
48967
|
+
state.screen = "tasks";
|
|
48968
|
+
render();
|
|
48969
|
+
return;
|
|
48970
|
+
}
|
|
48971
|
+
if (actionKey === "r") {
|
|
48972
|
+
const positionText = await promptLine(terminal, `New numeric position for ${task.shortId}: `);
|
|
48973
|
+
const position = Number(positionText.trim());
|
|
48974
|
+
if (!Number.isFinite(position)) {
|
|
48975
|
+
state.status = "Reorder cancelled: position must be a number.";
|
|
48976
|
+
render();
|
|
48977
|
+
return;
|
|
48978
|
+
}
|
|
48979
|
+
const updated2 = await client.reorderUserTasks({ moves: [{ task_id: task.taskId, position }] });
|
|
48980
|
+
const [decrypted2] = await decryptUserTasks(updated2, client.getMasterKeyBytes());
|
|
48981
|
+
if (decrypted2) {
|
|
48982
|
+
replaceTask(state, decrypted2);
|
|
48983
|
+
state.activeTask = decrypted2;
|
|
48984
|
+
}
|
|
48985
|
+
state.status = null;
|
|
48986
|
+
state.screen = "task";
|
|
48987
|
+
render();
|
|
48988
|
+
return;
|
|
48989
|
+
}
|
|
48990
|
+
const payload = actionKey === "b" ? { version: task.version, blocked_reason_code: "needs_user_input" } : { version: task.version };
|
|
48991
|
+
const updated = actionKey === "s" ? await client.startUserTaskWithAI(task.taskId, {
|
|
48992
|
+
version: task.version,
|
|
48993
|
+
primary_chat_id: task.primaryChatId ?? void 0,
|
|
48994
|
+
linked_project_ids: task.linkedProjectIds,
|
|
48995
|
+
plaintext_title: task.title,
|
|
48996
|
+
plaintext_description: task.description,
|
|
48997
|
+
plaintext_latest_instruction: task.latestInstruction
|
|
48998
|
+
}) : 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);
|
|
48999
|
+
const decrypted = await decryptUserTask(updated, client.getMasterKeyBytes());
|
|
49000
|
+
replaceTask(state, decrypted);
|
|
49001
|
+
state.activeTask = decrypted;
|
|
49002
|
+
state.status = null;
|
|
49003
|
+
state.screen = "task";
|
|
49004
|
+
} catch (error) {
|
|
49005
|
+
state.status = taskError(error, "Could not update task.");
|
|
49006
|
+
state.screen = "status";
|
|
49007
|
+
}
|
|
49008
|
+
render();
|
|
49009
|
+
}
|
|
49010
|
+
async function createTaskFromTui(params) {
|
|
49011
|
+
const { state, client, terminal, render } = params;
|
|
49012
|
+
state.status = "Creating task...";
|
|
49013
|
+
render();
|
|
49014
|
+
try {
|
|
49015
|
+
const title = await promptLine(terminal, "Task title: ");
|
|
49016
|
+
if (!title.trim()) {
|
|
49017
|
+
state.status = "Create cancelled.";
|
|
49018
|
+
render();
|
|
49019
|
+
return;
|
|
49020
|
+
}
|
|
49021
|
+
const description = await promptLine(terminal, "Description (optional): ");
|
|
49022
|
+
const input = await buildCreateUserTaskInput(client.getMasterKeyBytes(), {
|
|
49023
|
+
title: title.trim(),
|
|
49024
|
+
description: description.trim(),
|
|
49025
|
+
assign: "user"
|
|
49026
|
+
});
|
|
49027
|
+
const created = await client.createUserTask(input);
|
|
49028
|
+
const decrypted = await decryptUserTask(created, client.getMasterKeyBytes());
|
|
49029
|
+
replaceTask(state, decrypted);
|
|
49030
|
+
state.activeTask = decrypted;
|
|
49031
|
+
state.selectedIndex = Math.max(0, state.tasks.findIndex((task) => task.taskId === decrypted.taskId));
|
|
49032
|
+
state.status = null;
|
|
49033
|
+
state.screen = "task";
|
|
49034
|
+
} catch (error) {
|
|
49035
|
+
state.status = taskError(error, "Could not create task.");
|
|
49036
|
+
state.screen = "status";
|
|
49037
|
+
}
|
|
49038
|
+
render();
|
|
49039
|
+
}
|
|
49040
|
+
async function promptLine(terminal, prompt) {
|
|
49041
|
+
return terminal.suspend(async () => {
|
|
49042
|
+
const rl = createInterface3({ input: nodeStdin, output: nodeStdout });
|
|
49043
|
+
try {
|
|
49044
|
+
return await rl.question(prompt);
|
|
49045
|
+
} finally {
|
|
49046
|
+
rl.close();
|
|
49047
|
+
}
|
|
49048
|
+
});
|
|
49049
|
+
}
|
|
49050
|
+
function replaceTask(state, task) {
|
|
49051
|
+
const index = state.tasks.findIndex((candidate) => candidate.taskId === task.taskId);
|
|
49052
|
+
if (index >= 0) state.tasks[index] = task;
|
|
49053
|
+
else state.tasks.unshift(task);
|
|
49054
|
+
}
|
|
49055
|
+
function taskError(error, fallback) {
|
|
49056
|
+
return error instanceof Error ? `${fallback} ${error.message}` : fallback;
|
|
49057
|
+
}
|
|
49058
|
+
async function openWorkflowList(params) {
|
|
49059
|
+
const { state, client, render } = params;
|
|
49060
|
+
state.screen = "status";
|
|
49061
|
+
state.status = "Loading workflows...";
|
|
49062
|
+
render();
|
|
49063
|
+
try {
|
|
49064
|
+
state.workflows = await client.listWorkflows();
|
|
49065
|
+
state.selectedIndex = 0;
|
|
49066
|
+
state.scrollOffset = 0;
|
|
49067
|
+
state.status = null;
|
|
49068
|
+
state.screen = "workflows";
|
|
49069
|
+
} catch (error) {
|
|
49070
|
+
state.status = workflowError(error, "Could not load workflows. Use /login first if you are not signed in.");
|
|
49071
|
+
state.screen = "status";
|
|
49072
|
+
}
|
|
49073
|
+
render();
|
|
49074
|
+
}
|
|
49075
|
+
async function openWorkflowById(params) {
|
|
49076
|
+
const { state, client, workflowId, render } = params;
|
|
49077
|
+
state.screen = "status";
|
|
49078
|
+
state.status = `Loading workflow ${workflowId}...`;
|
|
49079
|
+
render();
|
|
49080
|
+
try {
|
|
49081
|
+
const workflow = await client.getWorkflow(workflowId);
|
|
49082
|
+
await openWorkflowDetail({ state, client, workflow, render });
|
|
49083
|
+
} catch (error) {
|
|
49084
|
+
state.status = workflowError(error, `Could not load workflow ${workflowId}.`);
|
|
49085
|
+
state.screen = "status";
|
|
49086
|
+
render();
|
|
49087
|
+
}
|
|
49088
|
+
}
|
|
49089
|
+
async function openWorkflowDetail(params) {
|
|
49090
|
+
const { state, client, workflow, render } = params;
|
|
49091
|
+
state.status = `Loading workflow ${workflow.id}...`;
|
|
49092
|
+
render();
|
|
49093
|
+
let detail;
|
|
49094
|
+
try {
|
|
49095
|
+
detail = await client.getWorkflow(workflow.id);
|
|
49096
|
+
} catch (error) {
|
|
49097
|
+
state.status = workflowError(error, `Could not load workflow ${workflow.id}.`);
|
|
49098
|
+
state.screen = "status";
|
|
49099
|
+
render();
|
|
49100
|
+
return;
|
|
49101
|
+
}
|
|
49102
|
+
state.activeWorkflow = detail;
|
|
49103
|
+
state.workflowRuns = [];
|
|
49104
|
+
state.workflowTab = "graph";
|
|
49105
|
+
state.selectedWorkflowNodeIndex = 0;
|
|
49106
|
+
state.selectedWorkflowRunIndex = 0;
|
|
49107
|
+
state.expandedWorkflowNodeId = null;
|
|
49108
|
+
state.expandedWorkflowRunNodeId = null;
|
|
49109
|
+
state.workflowEdit = null;
|
|
49110
|
+
state.screen = "workflow";
|
|
49111
|
+
state.scrollOffset = 0;
|
|
49112
|
+
state.status = null;
|
|
49113
|
+
render();
|
|
49114
|
+
await refreshActiveWorkflowRuns({ state, client, render });
|
|
49115
|
+
}
|
|
49116
|
+
async function refreshActiveWorkflowRuns(params) {
|
|
49117
|
+
const { state, client, render } = params;
|
|
49118
|
+
const workflow = state.activeWorkflow;
|
|
49119
|
+
if (!workflow) return;
|
|
49120
|
+
state.status = "Refreshing workflow runs...";
|
|
49121
|
+
render();
|
|
49122
|
+
try {
|
|
49123
|
+
state.workflowRuns = await client.listWorkflowRuns(workflow.id);
|
|
49124
|
+
state.selectedWorkflowRunIndex = clamp(state.selectedWorkflowRunIndex, 0, Math.max(0, state.workflowRuns.length - 1));
|
|
49125
|
+
state.status = null;
|
|
49126
|
+
state.screen = "workflow";
|
|
49127
|
+
} catch (error) {
|
|
49128
|
+
state.status = workflowError(error, "Could not refresh workflow runs.");
|
|
49129
|
+
}
|
|
49130
|
+
render();
|
|
49131
|
+
}
|
|
49132
|
+
async function saveWorkflowNodeTitle(params) {
|
|
49133
|
+
const { state, client, render } = params;
|
|
49134
|
+
const workflow = state.activeWorkflow;
|
|
49135
|
+
const edit = state.workflowEdit;
|
|
49136
|
+
if (!workflow || !edit) return;
|
|
49137
|
+
let parsedConfig = null;
|
|
49138
|
+
if (edit.field === "config") {
|
|
49139
|
+
try {
|
|
49140
|
+
const parsed = JSON.parse(edit.value || "{}");
|
|
49141
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Config must be a JSON object");
|
|
49142
|
+
parsedConfig = parsed;
|
|
49143
|
+
} catch (error) {
|
|
49144
|
+
state.status = workflowError(error, "Invalid config JSON.");
|
|
49145
|
+
render();
|
|
49146
|
+
return;
|
|
49147
|
+
}
|
|
49148
|
+
}
|
|
49149
|
+
const graph = {
|
|
49150
|
+
...workflow.graph,
|
|
49151
|
+
nodes: workflow.graph.nodes.map((node) => {
|
|
49152
|
+
if (node.id !== edit.nodeId) return node;
|
|
49153
|
+
if (edit.field === "config") return { ...node, config: parsedConfig ?? {} };
|
|
49154
|
+
return { ...node, title: edit.value.trim() || null };
|
|
49155
|
+
})
|
|
49156
|
+
};
|
|
49157
|
+
state.status = `Saving node ${edit.field}...`;
|
|
49158
|
+
render();
|
|
49159
|
+
try {
|
|
49160
|
+
state.activeWorkflow = await client.updateWorkflow(workflow.id, { graph });
|
|
49161
|
+
state.workflowEdit = null;
|
|
49162
|
+
state.status = `Saved node ${edit.field}.`;
|
|
49163
|
+
} catch (error) {
|
|
49164
|
+
state.status = workflowError(error, "Could not save node title.");
|
|
49165
|
+
}
|
|
49166
|
+
render();
|
|
49167
|
+
}
|
|
49168
|
+
function startWorkflowNodeEdit(state, field) {
|
|
49169
|
+
const workflow = state.activeWorkflow;
|
|
49170
|
+
if (!workflow || state.workflowTab !== "graph") {
|
|
49171
|
+
state.status = "Switch to the Graph tab before editing node details.";
|
|
49172
|
+
return;
|
|
49173
|
+
}
|
|
49174
|
+
const node = workflow.graph.nodes[state.selectedWorkflowNodeIndex];
|
|
49175
|
+
if (!node) return;
|
|
49176
|
+
state.workflowEdit = {
|
|
49177
|
+
nodeId: node.id,
|
|
49178
|
+
field,
|
|
49179
|
+
value: field === "config" ? JSON.stringify(node.config ?? {}) : node.title ?? ""
|
|
49180
|
+
};
|
|
49181
|
+
state.expandedWorkflowNodeId = node.id;
|
|
49182
|
+
state.status = `Editing ${field} for ${node.id}.`;
|
|
49183
|
+
}
|
|
49184
|
+
async function runActiveWorkflow(params) {
|
|
49185
|
+
const { state, client, render } = params;
|
|
49186
|
+
const workflow = state.activeWorkflow;
|
|
49187
|
+
if (!workflow) return;
|
|
49188
|
+
if (!workflow.enabled) {
|
|
49189
|
+
state.status = "Enable this workflow outside TUI before running it.";
|
|
49190
|
+
render();
|
|
49191
|
+
return;
|
|
49192
|
+
}
|
|
49193
|
+
state.status = "Starting workflow run...";
|
|
49194
|
+
render();
|
|
49195
|
+
try {
|
|
49196
|
+
const run = await client.runWorkflow(workflow.id, {
|
|
49197
|
+
idempotencyKey: `tui-${workflow.id}-${Date.now()}`,
|
|
49198
|
+
mode: "manual",
|
|
49199
|
+
input: {}
|
|
49200
|
+
});
|
|
49201
|
+
state.workflowRuns = [run, ...state.workflowRuns.filter((candidate) => candidate.id !== run.id)];
|
|
49202
|
+
state.status = `Started run ${run.id}. Press u to refresh.`;
|
|
49203
|
+
} catch (error) {
|
|
49204
|
+
state.status = workflowError(error, "Could not start workflow run.");
|
|
49205
|
+
}
|
|
49206
|
+
render();
|
|
49207
|
+
}
|
|
49208
|
+
async function cancelLatestWorkflowRun(params) {
|
|
49209
|
+
const { state, client, render } = params;
|
|
49210
|
+
const workflow = state.activeWorkflow;
|
|
49211
|
+
const run = state.workflowRuns.find((candidate) => ["queued", "running", "waiting"].includes(candidate.status));
|
|
49212
|
+
if (!workflow || !run) {
|
|
49213
|
+
state.status = "No active workflow run to cancel.";
|
|
49214
|
+
render();
|
|
49215
|
+
return;
|
|
49216
|
+
}
|
|
49217
|
+
state.status = `Cancelling run ${run.id}...`;
|
|
49218
|
+
render();
|
|
49219
|
+
try {
|
|
49220
|
+
const result = await client.cancelWorkflowRun(workflow.id, run.id);
|
|
49221
|
+
state.status = `Run ${result.run_id} ${result.status}.`;
|
|
49222
|
+
await refreshActiveWorkflowRuns({ state, client, render });
|
|
49223
|
+
} catch (error) {
|
|
49224
|
+
state.status = workflowError(error, "Could not cancel workflow run.");
|
|
49225
|
+
render();
|
|
49226
|
+
}
|
|
49227
|
+
}
|
|
49228
|
+
function workflowError(error, fallback) {
|
|
49229
|
+
const details = error instanceof Error ? error.message : String(error);
|
|
49230
|
+
return `${fallback} ${details}`;
|
|
49231
|
+
}
|
|
47469
49232
|
function toggleInterest(state) {
|
|
47470
49233
|
const interest = TUI_INTERESTS[state.selectedIndex];
|
|
47471
49234
|
if (!interest) return;
|
|
@@ -47963,6 +49726,10 @@ async function main() {
|
|
|
47963
49726
|
printChatsHelp();
|
|
47964
49727
|
return;
|
|
47965
49728
|
}
|
|
49729
|
+
if (command === "drafts") {
|
|
49730
|
+
printDraftsHelp();
|
|
49731
|
+
return;
|
|
49732
|
+
}
|
|
47966
49733
|
if (command === "apps") {
|
|
47967
49734
|
printAppsHelp();
|
|
47968
49735
|
return;
|
|
@@ -47975,6 +49742,10 @@ async function main() {
|
|
|
47975
49742
|
printWorkflowsHelp();
|
|
47976
49743
|
return;
|
|
47977
49744
|
}
|
|
49745
|
+
if (command === "tasks") {
|
|
49746
|
+
printTasksHelp();
|
|
49747
|
+
return;
|
|
49748
|
+
}
|
|
47978
49749
|
if (command === "connected-accounts") {
|
|
47979
49750
|
printConnectedAccountsHelp();
|
|
47980
49751
|
return;
|
|
@@ -48085,6 +49856,14 @@ async function main() {
|
|
|
48085
49856
|
await handleChats(client, subcommand, rest, parsed.flags, redactor);
|
|
48086
49857
|
return;
|
|
48087
49858
|
}
|
|
49859
|
+
if (command === "tasks") {
|
|
49860
|
+
await handleTasks(client, subcommand, rest, parsed.flags);
|
|
49861
|
+
return;
|
|
49862
|
+
}
|
|
49863
|
+
if (command === "drafts") {
|
|
49864
|
+
await handleDrafts(client, subcommand, rest, parsed.flags);
|
|
49865
|
+
return;
|
|
49866
|
+
}
|
|
48088
49867
|
if (command === "apps") {
|
|
48089
49868
|
await handleApps(client, subcommand, rest, parsed.flags);
|
|
48090
49869
|
return;
|
|
@@ -48222,6 +50001,172 @@ function handleCliVersion(flags) {
|
|
|
48222
50001
|
}
|
|
48223
50002
|
console.log("OpenMates CLI is up to date.");
|
|
48224
50003
|
}
|
|
50004
|
+
async function handleTasks(client, subcommand, rest, flags) {
|
|
50005
|
+
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
50006
|
+
printTasksHelp();
|
|
50007
|
+
return;
|
|
50008
|
+
}
|
|
50009
|
+
const masterKey = client.getMasterKeyBytes();
|
|
50010
|
+
const scope = taskScopeFromFlags(flags);
|
|
50011
|
+
if (subcommand === "list" || subcommand === "status") {
|
|
50012
|
+
if (subcommand === "status" && rest[0]) {
|
|
50013
|
+
const task = await resolveTask(client, masterKey, rest[0], scope);
|
|
50014
|
+
printTaskOutput(task, flags);
|
|
50015
|
+
return;
|
|
50016
|
+
}
|
|
50017
|
+
const tasks = await loadTasks(client, masterKey, scope);
|
|
50018
|
+
if (flags.json === true) printJson2({ tasks: tasks.map(taskToJson) });
|
|
50019
|
+
else console.log(renderTaskList(tasks));
|
|
50020
|
+
return;
|
|
50021
|
+
}
|
|
50022
|
+
if (subcommand === "board") {
|
|
50023
|
+
const tasks = await loadTasks(client, masterKey, scope);
|
|
50024
|
+
if (flags.json === true) printJson2({ tasks: tasks.map(taskToJson) });
|
|
50025
|
+
else console.log(renderTaskBoard(tasks));
|
|
50026
|
+
return;
|
|
50027
|
+
}
|
|
50028
|
+
if (subcommand === "show") {
|
|
50029
|
+
const id = rest[0];
|
|
50030
|
+
if (!id) throw new Error("Missing task ID. Usage: openmates tasks show <task-id>");
|
|
50031
|
+
const task = await resolveTask(client, masterKey, id, scope);
|
|
50032
|
+
printTaskOutput(task, flags);
|
|
50033
|
+
return;
|
|
50034
|
+
}
|
|
50035
|
+
if (subcommand === "create") {
|
|
50036
|
+
const title = taskTitleFromFlagsOrRest(flags, rest);
|
|
50037
|
+
const input = await buildCreateUserTaskInput(masterKey, {
|
|
50038
|
+
title,
|
|
50039
|
+
description: typeof flags.description === "string" ? flags.description : "",
|
|
50040
|
+
status: normalizeTaskStatus(typeof flags.status === "string" ? flags.status : void 0),
|
|
50041
|
+
assign: taskAssignFlag(flags),
|
|
50042
|
+
chatId: typeof flags.chat === "string" ? flags.chat : null,
|
|
50043
|
+
projectIds: splitCsvFlag(flags.project ?? flags.projects),
|
|
50044
|
+
planId: typeof flags.plan === "string" ? flags.plan : null,
|
|
50045
|
+
dueAt: parseDueAt(flags.due)
|
|
50046
|
+
});
|
|
50047
|
+
const created = await client.createUserTask(input);
|
|
50048
|
+
printTaskOutput(await decryptUserTask(created, masterKey), flags);
|
|
50049
|
+
return;
|
|
50050
|
+
}
|
|
50051
|
+
if (subcommand === "edit") {
|
|
50052
|
+
const id = rest[0];
|
|
50053
|
+
if (!id) throw new Error("Missing task ID. Usage: openmates tasks edit <task-id> [--title ...]");
|
|
50054
|
+
const task = await resolveTask(client, masterKey, id, scope);
|
|
50055
|
+
const patch = await buildUpdateUserTaskInput(task, masterKey, {
|
|
50056
|
+
title: typeof flags.title === "string" ? flags.title : void 0,
|
|
50057
|
+
description: typeof flags.description === "string" ? flags.description : void 0,
|
|
50058
|
+
status: normalizeTaskStatus(typeof flags.status === "string" ? flags.status : void 0),
|
|
50059
|
+
assign: taskAssignFlag(flags),
|
|
50060
|
+
chatId: flags.chat === true ? null : typeof flags.chat === "string" ? flags.chat : void 0,
|
|
50061
|
+
projectIds: flags.project || flags.projects ? splitCsvFlag(flags.project ?? flags.projects) : void 0,
|
|
50062
|
+
planId: flags.plan === true ? null : typeof flags.plan === "string" ? flags.plan : void 0
|
|
50063
|
+
});
|
|
50064
|
+
const updated = await client.updateUserTask(task.taskId, patch);
|
|
50065
|
+
printTaskOutput(await decryptUserTask(updated, masterKey), flags);
|
|
50066
|
+
return;
|
|
50067
|
+
}
|
|
50068
|
+
if (subcommand === "delete") {
|
|
50069
|
+
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, "delete");
|
|
50070
|
+
if (flags.confirm !== true) throw new Error("Deleting a task requires --confirm.");
|
|
50071
|
+
const result = await client.deleteUserTask(task.taskId);
|
|
50072
|
+
if (flags.json === true) printJson2(result);
|
|
50073
|
+
else console.log(`Task deleted: ${task.shortId}`);
|
|
50074
|
+
return;
|
|
50075
|
+
}
|
|
50076
|
+
if (subcommand === "start") {
|
|
50077
|
+
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, "start");
|
|
50078
|
+
const started = await client.startUserTaskWithAI(task.taskId, {
|
|
50079
|
+
version: task.version,
|
|
50080
|
+
primary_chat_id: task.primaryChatId ?? void 0,
|
|
50081
|
+
linked_project_ids: task.linkedProjectIds,
|
|
50082
|
+
plaintext_title: task.title,
|
|
50083
|
+
plaintext_description: task.description,
|
|
50084
|
+
plaintext_latest_instruction: task.latestInstruction
|
|
50085
|
+
});
|
|
50086
|
+
printTaskOutput(await decryptUserTask(started, masterKey), flags);
|
|
50087
|
+
return;
|
|
50088
|
+
}
|
|
50089
|
+
if (["block", "unblock", "skip", "done"].includes(subcommand)) {
|
|
50090
|
+
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, subcommand);
|
|
50091
|
+
const payload = { version: task.version };
|
|
50092
|
+
if (subcommand === "block") {
|
|
50093
|
+
if (typeof flags.reason !== "string") throw new Error("Blocking a task requires --reason <code>.");
|
|
50094
|
+
payload.blocked_reason_code = flags.reason;
|
|
50095
|
+
}
|
|
50096
|
+
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);
|
|
50097
|
+
printTaskOutput(await decryptUserTask(updated, masterKey), flags);
|
|
50098
|
+
return;
|
|
50099
|
+
}
|
|
50100
|
+
if (subcommand === "reorder") {
|
|
50101
|
+
const task = await requiredResolvedTask(client, masterKey, rest[0], scope, "reorder");
|
|
50102
|
+
const move = { task_id: task.taskId };
|
|
50103
|
+
if (typeof flags.before === "string") move.before_task_id = (await resolveTask(client, masterKey, flags.before, scope)).taskId;
|
|
50104
|
+
if (typeof flags.after === "string") move.after_task_id = (await resolveTask(client, masterKey, flags.after, scope)).taskId;
|
|
50105
|
+
if (typeof flags.status === "string") move.status = normalizeTaskStatus(flags.status);
|
|
50106
|
+
if (typeof flags.position === "string") move.position = Number(flags.position);
|
|
50107
|
+
const updated = await client.reorderUserTasks({ moves: [move] });
|
|
50108
|
+
const decrypted = await decryptUserTasks(updated, masterKey);
|
|
50109
|
+
if (flags.json === true) printJson2({ tasks: decrypted.map(taskToJson) });
|
|
50110
|
+
else console.log(`Task reordered: ${task.shortId}`);
|
|
50111
|
+
return;
|
|
50112
|
+
}
|
|
50113
|
+
throw new Error(`Unknown tasks command '${subcommand}'. Run 'openmates tasks --help'.`);
|
|
50114
|
+
}
|
|
50115
|
+
function taskScopeFromFlags(flags) {
|
|
50116
|
+
return {
|
|
50117
|
+
status: normalizeTaskStatus(typeof flags.status === "string" ? flags.status : void 0),
|
|
50118
|
+
chatId: typeof flags.chat === "string" ? flags.chat : void 0,
|
|
50119
|
+
projectId: typeof flags.project === "string" ? flags.project : void 0,
|
|
50120
|
+
planId: typeof flags.plan === "string" ? flags.plan : void 0
|
|
50121
|
+
};
|
|
50122
|
+
}
|
|
50123
|
+
async function loadTasks(client, masterKey, scope) {
|
|
50124
|
+
const records = await client.listUserTasks({ status: scope.status, chatId: scope.chatId, projectId: scope.projectId });
|
|
50125
|
+
const tasks = await decryptUserTasks(records, masterKey);
|
|
50126
|
+
return scope.planId ? tasks.filter((task) => task.planId === scope.planId) : tasks;
|
|
50127
|
+
}
|
|
50128
|
+
async function resolveTask(client, masterKey, id, scope) {
|
|
50129
|
+
return findTask(await loadTasks(client, masterKey, { ...scope, status: void 0 }), id);
|
|
50130
|
+
}
|
|
50131
|
+
async function requiredResolvedTask(client, masterKey, id, scope, action) {
|
|
50132
|
+
if (!id) throw new Error(`Missing task ID. Usage: openmates tasks ${action} <task-id>`);
|
|
50133
|
+
return resolveTask(client, masterKey, id, scope);
|
|
50134
|
+
}
|
|
50135
|
+
function printTaskOutput(task, flags) {
|
|
50136
|
+
if (flags.json === true) printJson2({ task: taskToJson(task) });
|
|
50137
|
+
else console.log(renderTaskDetail2(task));
|
|
50138
|
+
}
|
|
50139
|
+
function taskToJson(task) {
|
|
50140
|
+
return {
|
|
50141
|
+
task_id: task.taskId,
|
|
50142
|
+
short_id: task.shortId,
|
|
50143
|
+
title: task.title,
|
|
50144
|
+
description: task.description,
|
|
50145
|
+
tags: task.tags,
|
|
50146
|
+
latest_instruction: task.latestInstruction,
|
|
50147
|
+
status: task.status,
|
|
50148
|
+
assignee_type: task.assigneeType,
|
|
50149
|
+
assignee_hash: task.assigneeHash,
|
|
50150
|
+
primary_chat_id: task.primaryChatId,
|
|
50151
|
+
linked_project_ids: task.linkedProjectIds,
|
|
50152
|
+
plan_id: task.planId,
|
|
50153
|
+
due_at: task.dueAt,
|
|
50154
|
+
priority: task.priority,
|
|
50155
|
+
position: task.position,
|
|
50156
|
+
queue_state: task.queueState,
|
|
50157
|
+
blocked_reason_code: task.blockedReasonCode,
|
|
50158
|
+
ai_execution_state: task.aiExecutionState,
|
|
50159
|
+
version: task.version
|
|
50160
|
+
};
|
|
50161
|
+
}
|
|
50162
|
+
function taskTitleFromFlagsOrRest(flags, rest) {
|
|
50163
|
+
const title = typeof flags.title === "string" ? flags.title : rest.join(" ").trim();
|
|
50164
|
+
if (!title) throw new Error("Missing task title. Usage: openmates tasks create --title <title>");
|
|
50165
|
+
return title;
|
|
50166
|
+
}
|
|
50167
|
+
function taskAssignFlag(flags) {
|
|
50168
|
+
return typeof flags.assign === "string" ? flags.assign : typeof flags.assignee === "string" ? flags.assignee : void 0;
|
|
50169
|
+
}
|
|
48225
50170
|
async function handleRemoteAccess(client, subcommand, rest, flags) {
|
|
48226
50171
|
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
48227
50172
|
printRemoteAccessHelp();
|
|
@@ -48232,7 +50177,7 @@ async function handleRemoteAccess(client, subcommand, rest, flags) {
|
|
|
48232
50177
|
if (!rootPath) {
|
|
48233
50178
|
throw new Error("Missing source path. Usage: openmates remote-access start --path <folder>");
|
|
48234
50179
|
}
|
|
48235
|
-
const sourceId = typeof flags["source-id"] === "string" ? flags["source-id"] :
|
|
50180
|
+
const sourceId = typeof flags["source-id"] === "string" ? flags["source-id"] : randomUUID6();
|
|
48236
50181
|
const projectId = typeof flags.project === "string" ? flags.project : void 0;
|
|
48237
50182
|
validateRemoteSourceRegistrationFlags(projectId, flags);
|
|
48238
50183
|
const sourceType = parseRemoteAccessSourceType(flags.type);
|
|
@@ -48338,11 +50283,57 @@ function parseJsonFlag(value, flagName) {
|
|
|
48338
50283
|
throw new Error(`Invalid JSON for ${flagName}: ${message}`);
|
|
48339
50284
|
}
|
|
48340
50285
|
}
|
|
50286
|
+
async function handleDrafts(client, subcommand, rest, flags) {
|
|
50287
|
+
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
50288
|
+
printDraftsHelp();
|
|
50289
|
+
return;
|
|
50290
|
+
}
|
|
50291
|
+
if (subcommand === "create" || subcommand === "update") {
|
|
50292
|
+
const chatId = subcommand === "update" ? rest[0] : typeof flags.chat === "string" ? flags.chat : void 0;
|
|
50293
|
+
const markdown = subcommand === "update" ? rest.slice(1).join(" ").trim() : rest.join(" ").trim();
|
|
50294
|
+
if (subcommand === "update" && !chatId) throw new Error("Missing chat ID for draft update.");
|
|
50295
|
+
if (!markdown) throw new Error(`Missing draft text for draft ${subcommand}.`);
|
|
50296
|
+
const draft = await client.saveDraft({
|
|
50297
|
+
chatId,
|
|
50298
|
+
markdown,
|
|
50299
|
+
preview: typeof flags.preview === "string" ? flags.preview : markdown.slice(0, 160)
|
|
50300
|
+
});
|
|
50301
|
+
printJson2(draft);
|
|
50302
|
+
return;
|
|
50303
|
+
}
|
|
50304
|
+
if (subcommand === "list") {
|
|
50305
|
+
const drafts = await client.listDrafts(flags.refresh === true);
|
|
50306
|
+
printJson2({ drafts });
|
|
50307
|
+
return;
|
|
50308
|
+
}
|
|
50309
|
+
if (subcommand === "get") {
|
|
50310
|
+
const chatId = rest[0];
|
|
50311
|
+
if (!chatId) throw new Error("Missing chat ID for draft get.");
|
|
50312
|
+
printJson2({ draft: await client.getDraft(chatId, flags.refresh === true) });
|
|
50313
|
+
return;
|
|
50314
|
+
}
|
|
50315
|
+
if (subcommand === "clear") {
|
|
50316
|
+
const chatId = rest[0];
|
|
50317
|
+
if (!chatId) throw new Error("Missing chat ID for draft clear.");
|
|
50318
|
+
await client.clearDraft(chatId);
|
|
50319
|
+
printJson2({ success: true, chat_id: chatId });
|
|
50320
|
+
return;
|
|
50321
|
+
}
|
|
50322
|
+
if (subcommand === "sync") {
|
|
50323
|
+
printJson2({ versions: await client.reconcileDraftVersions() });
|
|
50324
|
+
return;
|
|
50325
|
+
}
|
|
50326
|
+
throw new Error(`Unknown drafts subcommand '${subcommand}'.`);
|
|
50327
|
+
}
|
|
48341
50328
|
async function handleChats(client, subcommand, rest, flags, redactor) {
|
|
48342
50329
|
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
48343
50330
|
printChatsHelp();
|
|
48344
50331
|
return;
|
|
48345
50332
|
}
|
|
50333
|
+
if (rest[0] === "tasks") {
|
|
50334
|
+
await handleTasks(client, rest[1], rest.slice(2), { ...flags, chat: subcommand });
|
|
50335
|
+
return;
|
|
50336
|
+
}
|
|
48346
50337
|
if (subcommand === "list") {
|
|
48347
50338
|
const limit = typeof flags.limit === "string" ? parseInt(flags.limit, 10) : 10;
|
|
48348
50339
|
const page = typeof flags.page === "string" ? parseInt(flags.page, 10) : 1;
|
|
@@ -49025,7 +51016,37 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
49025
51016
|
}
|
|
49026
51017
|
return;
|
|
49027
51018
|
}
|
|
51019
|
+
if (subcommand === "validate") {
|
|
51020
|
+
const file = typeof flags.file === "string" ? flags.file : "";
|
|
51021
|
+
if (!file) throw new Error("Missing --file. Example: openmates workflows validate --file workflow.yml");
|
|
51022
|
+
const validation = await client.validateWorkflowYaml(readFileSync9(file, "utf8"));
|
|
51023
|
+
if (flags.json === true) {
|
|
51024
|
+
printJson2(validation);
|
|
51025
|
+
} else {
|
|
51026
|
+
kv("Draft valid", validation.draft_valid ? "yes" : "no");
|
|
51027
|
+
kv("Ready to enable", validation.enable_ready ? "yes" : "no");
|
|
51028
|
+
for (const diagnostic of validation.diagnostics) {
|
|
51029
|
+
console.log(` - ${String(diagnostic.path ?? "$")}: ${String(diagnostic.message ?? diagnostic.code ?? "invalid")}`);
|
|
51030
|
+
}
|
|
51031
|
+
}
|
|
51032
|
+
if (!validation.draft_valid) process.exitCode = 1;
|
|
51033
|
+
return;
|
|
51034
|
+
}
|
|
49028
51035
|
if (subcommand === "create") {
|
|
51036
|
+
const yamlFile = typeof flags.file === "string" ? flags.file : "";
|
|
51037
|
+
if (yamlFile) {
|
|
51038
|
+
const result = await client.createWorkflowYaml(readFileSync9(yamlFile, "utf8"));
|
|
51039
|
+
if (flags.json === true) {
|
|
51040
|
+
printJson2(result);
|
|
51041
|
+
} else {
|
|
51042
|
+
printWorkflowDetail(result.workflow);
|
|
51043
|
+
kv("Ready to enable", result.validation.enable_ready ? "yes" : "no");
|
|
51044
|
+
for (const diagnostic of result.validation.diagnostics) {
|
|
51045
|
+
console.log(` - ${String(diagnostic.path ?? "$")}: ${String(diagnostic.message ?? diagnostic.code ?? "input required")}`);
|
|
51046
|
+
}
|
|
51047
|
+
}
|
|
51048
|
+
return;
|
|
51049
|
+
}
|
|
49029
51050
|
const title = typeof flags.title === "string" ? flags.title.trim() : "";
|
|
49030
51051
|
const graphJson = typeof flags.graph === "string" ? flags.graph : "";
|
|
49031
51052
|
if (!title) throw new Error("Missing --title for workflow create.");
|
|
@@ -49043,6 +51064,22 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
49043
51064
|
}
|
|
49044
51065
|
return;
|
|
49045
51066
|
}
|
|
51067
|
+
if (subcommand === "update") {
|
|
51068
|
+
const workflowId = rest[0];
|
|
51069
|
+
const yamlFile = typeof flags.file === "string" ? flags.file : "";
|
|
51070
|
+
if (!workflowId || !yamlFile) throw new Error("Missing workflow ID or --file. Example: openmates workflows update <id> --file workflow.yml");
|
|
51071
|
+
const result = await client.updateWorkflowYaml(workflowId, readFileSync9(yamlFile, "utf8"));
|
|
51072
|
+
if (flags.json === true) {
|
|
51073
|
+
printJson2(result);
|
|
51074
|
+
} else {
|
|
51075
|
+
printWorkflowDetail(result.workflow);
|
|
51076
|
+
kv("Ready to enable", result.validation.enable_ready ? "yes" : "no");
|
|
51077
|
+
for (const diagnostic of result.validation.diagnostics) {
|
|
51078
|
+
console.log(` - ${String(diagnostic.path ?? "$")}: ${String(diagnostic.message ?? diagnostic.code ?? "input required")}`);
|
|
51079
|
+
}
|
|
51080
|
+
}
|
|
51081
|
+
return;
|
|
51082
|
+
}
|
|
49046
51083
|
if (subcommand === "input") {
|
|
49047
51084
|
const text = typeof flags.text === "string" ? flags.text : rest.join(" ").trim();
|
|
49048
51085
|
if (!text) throw new Error('Missing workflow input text. Example: openmates workflows input "alert me if it rains"');
|
|
@@ -49149,9 +51186,11 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
49149
51186
|
if (subcommand === "run") {
|
|
49150
51187
|
const workflowId = rest[0];
|
|
49151
51188
|
if (!workflowId) throw new Error("Missing workflow ID. Example: openmates workflows run <id>");
|
|
51189
|
+
const idempotencyKey = typeof flags["idempotency-key"] === "string" ? flags["idempotency-key"] : "";
|
|
51190
|
+
if (!idempotencyKey) throw new Error("Missing --idempotency-key. Reuse this stable key when retrying the same workflow run.");
|
|
49152
51191
|
const mode = flags.mode === "test" ? "test" : "manual";
|
|
49153
51192
|
const input = typeof flags.input === "string" ? parseJsonFlag(flags.input, "--input") : {};
|
|
49154
|
-
const run = await client.runWorkflow(workflowId, { mode, input });
|
|
51193
|
+
const run = await client.runWorkflow(workflowId, { idempotencyKey, mode, input });
|
|
49155
51194
|
if (flags.json === true) {
|
|
49156
51195
|
printJson2(run);
|
|
49157
51196
|
} else {
|
|
@@ -49182,6 +51221,58 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
49182
51221
|
}
|
|
49183
51222
|
return;
|
|
49184
51223
|
}
|
|
51224
|
+
if (subcommand === "run-cancel") {
|
|
51225
|
+
const workflowId = rest[0];
|
|
51226
|
+
const runId = rest[1];
|
|
51227
|
+
if (!workflowId || !runId) throw new Error("Missing workflow/run ID. Example: openmates workflows run-cancel <workflow-id> <run-id>");
|
|
51228
|
+
const result = await client.cancelWorkflowRun(workflowId, runId);
|
|
51229
|
+
if (flags.json === true) {
|
|
51230
|
+
printJson2(result);
|
|
51231
|
+
} else {
|
|
51232
|
+
kv("Status", result.status);
|
|
51233
|
+
}
|
|
51234
|
+
return;
|
|
51235
|
+
}
|
|
51236
|
+
if (subcommand === "step-test") {
|
|
51237
|
+
const workflowId = rest[0];
|
|
51238
|
+
const stepId = rest[1];
|
|
51239
|
+
if (!workflowId || !stepId) throw new Error("Missing workflow/step ID. Example: openmates workflows step-test <workflow-id> <step-id> --yes");
|
|
51240
|
+
const input = typeof flags.input === "string" ? parseJsonFlag(flags.input, "--input") : {};
|
|
51241
|
+
const run = await client.testWorkflowStep(workflowId, stepId, { input, confirmed: flags.yes === true });
|
|
51242
|
+
if (flags.json === true) {
|
|
51243
|
+
printJson2(run);
|
|
51244
|
+
} else {
|
|
51245
|
+
printWorkflowRun(run);
|
|
51246
|
+
}
|
|
51247
|
+
return;
|
|
51248
|
+
}
|
|
51249
|
+
if (subcommand === "respond") {
|
|
51250
|
+
const workflowId = rest[0];
|
|
51251
|
+
const runId = rest[1];
|
|
51252
|
+
const stepId = rest[2];
|
|
51253
|
+
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"}'`);
|
|
51254
|
+
const input = typeof flags.input === "string" ? parseJsonFlag(flags.input, "--input") : {};
|
|
51255
|
+
const run = await client.respondToWorkflowRun(workflowId, runId, stepId, input);
|
|
51256
|
+
if (flags.json === true) {
|
|
51257
|
+
printJson2(run);
|
|
51258
|
+
} else {
|
|
51259
|
+
printWorkflowRun(run);
|
|
51260
|
+
}
|
|
51261
|
+
return;
|
|
51262
|
+
}
|
|
51263
|
+
if (subcommand === "help-app") {
|
|
51264
|
+
const capabilityId = rest[0];
|
|
51265
|
+
if (!capabilityId) throw new Error("Missing app skill. Example: openmates workflows help-app weather.forecast");
|
|
51266
|
+
const capabilities = await client.listWorkflowCapabilities();
|
|
51267
|
+
const capability = capabilities.find((item) => item.id === capabilityId || item.id === capabilityId.replace(":", "."));
|
|
51268
|
+
if (!capability) throw new Error(`Workflow capability not found: ${capabilityId}`);
|
|
51269
|
+
if (flags.json === true) {
|
|
51270
|
+
printJson2(capability);
|
|
51271
|
+
} else {
|
|
51272
|
+
printWorkflowCapabilityHelp(capability);
|
|
51273
|
+
}
|
|
51274
|
+
return;
|
|
51275
|
+
}
|
|
49185
51276
|
throw new Error(`Unknown workflows command '${subcommand}'. Run 'openmates workflows --help'.`);
|
|
49186
51277
|
}
|
|
49187
51278
|
function printWorkflowList(workflows) {
|
|
@@ -49251,6 +51342,30 @@ function printWorkflowCapabilities(capabilities) {
|
|
|
49251
51342
|
kv(capability.id, `${capability.title} \xB7 ${state}`, 24);
|
|
49252
51343
|
}
|
|
49253
51344
|
}
|
|
51345
|
+
function printWorkflowCapabilityHelp(capability) {
|
|
51346
|
+
header(`${capability.title}
|
|
51347
|
+
`);
|
|
51348
|
+
kv("ID", capability.id);
|
|
51349
|
+
kv("Type", capability.type);
|
|
51350
|
+
kv("Enabled", capability.enabled ? "yes" : "no");
|
|
51351
|
+
if (capability.reason) kv("Reason", capability.reason);
|
|
51352
|
+
const metadata = capability.metadata ?? {};
|
|
51353
|
+
const workflow = metadata.workflow;
|
|
51354
|
+
if (workflow) {
|
|
51355
|
+
kv("Effect", String(workflow.effect ?? "unknown"));
|
|
51356
|
+
kv("Execution", String(workflow.execution_mode ?? "unknown"));
|
|
51357
|
+
kv("Approval", String(workflow.approval ?? "unknown"));
|
|
51358
|
+
kv("Unattended", workflow.unattended === true ? "yes" : "no");
|
|
51359
|
+
}
|
|
51360
|
+
if (metadata.input_schema) {
|
|
51361
|
+
console.log("\nInput schema:");
|
|
51362
|
+
console.log(JSON.stringify(metadata.input_schema, null, 2));
|
|
51363
|
+
}
|
|
51364
|
+
if (workflow?.test_example_input) {
|
|
51365
|
+
console.log("\nTest example:");
|
|
51366
|
+
console.log(JSON.stringify(workflow.test_example_input, null, 2));
|
|
51367
|
+
}
|
|
51368
|
+
}
|
|
49254
51369
|
async function handleApps(client, subcommand, rest, flags) {
|
|
49255
51370
|
const apiKey = resolveApiKey(flags) ?? void 0;
|
|
49256
51371
|
if (!subcommand || subcommand === "help") {
|
|
@@ -49487,7 +51602,7 @@ Run with --help for full parameter details:
|
|
|
49487
51602
|
}
|
|
49488
51603
|
}
|
|
49489
51604
|
try {
|
|
49490
|
-
const inputData = buildSkillInput(flags, inlineTokens, schemaParams);
|
|
51605
|
+
const inputData = app === "models3d" && skill === "generate" ? await buildModels3DGenerateInput(client, flags, inlineTokens, schemaParams) : buildSkillInput(flags, inlineTokens, schemaParams);
|
|
49491
51606
|
const data = await client.runSkill({ app, skill, inputData, apiKey });
|
|
49492
51607
|
if (flags.json === true) {
|
|
49493
51608
|
printJson2(data);
|
|
@@ -49692,6 +51807,27 @@ function buildSkillInput(flags, inlineTokens, schemaParams) {
|
|
|
49692
51807
|
}
|
|
49693
51808
|
return {};
|
|
49694
51809
|
}
|
|
51810
|
+
async function buildModels3DGenerateInput(client, flags, inlineTokens, schemaParams) {
|
|
51811
|
+
const imagePath = typeof flags.image === "string" ? flags.image : void 0;
|
|
51812
|
+
if (!imagePath) return buildSkillInput(flags, inlineTokens, schemaParams);
|
|
51813
|
+
if (typeof flags.input === "string" || inlineTokens.length > 0) {
|
|
51814
|
+
throw new Error("Use either a text prompt or --image <path> for 3D generation, not both.");
|
|
51815
|
+
}
|
|
51816
|
+
if (!client.hasSession()) {
|
|
51817
|
+
throw new Error("Image-based 3D generation requires `openmates login` to upload the reference image.");
|
|
51818
|
+
}
|
|
51819
|
+
const processed = processFiles([imagePath], null);
|
|
51820
|
+
if (processed.blocked.length > 0 || processed.errors.length > 0 || processed.embeds.length !== 1) {
|
|
51821
|
+
const reason = [...processed.blocked, ...processed.errors].map((entry) => entry.error).join("; ") || "no image embed produced";
|
|
51822
|
+
throw new Error(`Failed to prepare 3D reference image: ${reason}`);
|
|
51823
|
+
}
|
|
51824
|
+
const fileEmbed = processed.embeds[0];
|
|
51825
|
+
if (!fileEmbed.requiresUpload || !fileEmbed.localPath || fileEmbed.embed.type !== "image") {
|
|
51826
|
+
throw new Error("3D generation requires a supported image file.");
|
|
51827
|
+
}
|
|
51828
|
+
const uploadResult = await uploadFile(fileEmbed.localPath, client.getSession());
|
|
51829
|
+
return { image_embed_refs: [uploadResult.embed_id] };
|
|
51830
|
+
}
|
|
49695
51831
|
function getEffectiveRequiredParams(schemaParams) {
|
|
49696
51832
|
const required = schemaParams.filter((p) => p.required);
|
|
49697
51833
|
if (required.length > 0) return required;
|
|
@@ -50236,7 +52372,7 @@ async function confirmOrExit(question) {
|
|
|
50236
52372
|
process.exit(0);
|
|
50237
52373
|
}
|
|
50238
52374
|
}
|
|
50239
|
-
async function
|
|
52375
|
+
async function promptLine2(question) {
|
|
50240
52376
|
const rl = await import("readline");
|
|
50241
52377
|
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
50242
52378
|
const answer = await new Promise((resolve7) => iface.question(question, resolve7));
|
|
@@ -50245,7 +52381,7 @@ async function promptLine(question) {
|
|
|
50245
52381
|
}
|
|
50246
52382
|
async function promptSecret(question) {
|
|
50247
52383
|
if (!process.stdin.isTTY) {
|
|
50248
|
-
return
|
|
52384
|
+
return promptLine2(question);
|
|
50249
52385
|
}
|
|
50250
52386
|
return new Promise((resolve7) => {
|
|
50251
52387
|
const stdin2 = process.stdin;
|
|
@@ -50332,8 +52468,8 @@ async function writeSecretFile(filePath, content, force = false) {
|
|
|
50332
52468
|
return filePath;
|
|
50333
52469
|
}
|
|
50334
52470
|
async function generateProvisioningPassword() {
|
|
50335
|
-
const { randomBytes:
|
|
50336
|
-
return `OM-${
|
|
52471
|
+
const { randomBytes: randomBytes4 } = await import("crypto");
|
|
52472
|
+
return `OM-${randomBytes4(18).toString("base64url")}-aA2#`;
|
|
50337
52473
|
}
|
|
50338
52474
|
function decodeBase32(input) {
|
|
50339
52475
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
@@ -50368,7 +52504,7 @@ async function runSecuritySetup(client, flags, options = {}) {
|
|
|
50368
52504
|
result.otpSecret = setup.secret ?? null;
|
|
50369
52505
|
if (setup.otpauth_url) client.renderTotpQrCode(setup.otpauth_url);
|
|
50370
52506
|
if (setup.secret) console.log(`Manual TOTP secret: ${setup.secret}`);
|
|
50371
|
-
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
|
|
52507
|
+
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: ");
|
|
50372
52508
|
await client.verifyTotpSetup(code);
|
|
50373
52509
|
const provider = typeof flags.provider === "string" ? flags.provider : "Authenticator app";
|
|
50374
52510
|
await client.setTotpProvider(provider);
|
|
@@ -50408,8 +52544,8 @@ async function handleSignup(client, flags, options = {}) {
|
|
|
50408
52544
|
if (flags.password !== void 0) {
|
|
50409
52545
|
throw new Error("Passwords must be entered through hidden prompts, not command-line flags.");
|
|
50410
52546
|
}
|
|
50411
|
-
const email = typeof flags.email === "string" ? flags.email : await
|
|
50412
|
-
const username = typeof flags.username === "string" ? flags.username : await
|
|
52547
|
+
const email = typeof flags.email === "string" ? flags.email : await promptLine2("Email: ");
|
|
52548
|
+
const username = typeof flags.username === "string" ? flags.username : await promptLine2("Username: ");
|
|
50413
52549
|
const inviteCode = typeof flags["invite-code"] === "string" ? flags["invite-code"] : "";
|
|
50414
52550
|
const language = typeof flags.language === "string" ? flags.language : "en";
|
|
50415
52551
|
const password = process.env.OPENMATES_CLI_SIGNUP_PASSWORD ?? await promptSecret("Password: ");
|
|
@@ -50418,7 +52554,7 @@ async function handleSignup(client, flags, options = {}) {
|
|
|
50418
52554
|
if (password !== confirmPassword) throw new Error("Passwords do not match.");
|
|
50419
52555
|
}
|
|
50420
52556
|
await client.requestSignupEmailCode({ email, inviteCode, language });
|
|
50421
|
-
const emailCode = process.env.OPENMATES_CLI_SIGNUP_EMAIL_CODE ?? await
|
|
52557
|
+
const emailCode = process.env.OPENMATES_CLI_SIGNUP_EMAIL_CODE ?? await promptLine2("Email verification code: ");
|
|
50422
52558
|
await client.verifySignupEmailCode({ email, username, inviteCode, code: emailCode, language });
|
|
50423
52559
|
const signup = await client.setupPasswordAccount({ email, username, password, inviteCode, language });
|
|
50424
52560
|
const security = await runSecuritySetup(client, flags, options);
|
|
@@ -50460,13 +52596,15 @@ async function handleE2E(client, subcommand, rest, flags) {
|
|
|
50460
52596
|
throw new Error("E2E provisioning refuses production API URLs.");
|
|
50461
52597
|
}
|
|
50462
52598
|
if (flags.password !== void 0) throw new Error("Use generated passwords or OPENMATES_CLI_SIGNUP_PASSWORD, not --password.");
|
|
52599
|
+
const inviteCode = process.env.OPENMATES_CLI_SIGNUP_INVITE_CODE;
|
|
52600
|
+
if (!inviteCode) throw new Error("OPENMATES_CLI_SIGNUP_INVITE_CODE is required for E2E provisioning.");
|
|
50463
52601
|
const slot = parseRequiredNumber(flags.slot, "--slot");
|
|
50464
|
-
if (![15, 16, 17, 18, 19, 20].includes(slot)) {
|
|
50465
|
-
throw new Error("Only reserved slots
|
|
52602
|
+
if (![14, 15, 16, 17, 18, 19, 20].includes(slot)) {
|
|
52603
|
+
throw new Error("Only reserved slots 14-20 are supported.");
|
|
50466
52604
|
}
|
|
50467
52605
|
const artifact = typeof flags.artifact === "string" ? flags.artifact : `test-results/credential-updates/slot-${slot}.env`;
|
|
50468
52606
|
const domain = typeof flags.domain === "string" ? flags.domain : process.env.OPENMATES_CLI_E2E_EMAIL_DOMAIN;
|
|
50469
|
-
const email = typeof flags.email === "string" ? flags.email : domain ? `cli-e2e-slot-${slot}-${Date.now()}@${domain}` : await
|
|
52607
|
+
const email = typeof flags.email === "string" ? flags.email : domain ? `cli-e2e-slot-${slot}-${Date.now()}@${domain}` : await promptLine2("E2E account email: ");
|
|
50470
52608
|
const username = typeof flags.username === "string" ? flags.username : `cli_e2e_slot_${slot}_${Date.now()}`;
|
|
50471
52609
|
const generatedPassword = process.env.OPENMATES_CLI_SIGNUP_PASSWORD ?? await generateProvisioningPassword();
|
|
50472
52610
|
const originalSignupPassword = process.env.OPENMATES_CLI_SIGNUP_PASSWORD;
|
|
@@ -50477,6 +52615,7 @@ async function handleE2E(client, subcommand, rest, flags) {
|
|
|
50477
52615
|
...flags,
|
|
50478
52616
|
email,
|
|
50479
52617
|
username,
|
|
52618
|
+
"invite-code": inviteCode,
|
|
50480
52619
|
yes: true,
|
|
50481
52620
|
json: false,
|
|
50482
52621
|
"backup-codes-output": typeof flags["backup-codes-output"] === "string" ? flags["backup-codes-output"] : `${artifact}.backup-codes`,
|
|
@@ -50671,10 +52810,10 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
50671
52810
|
await confirmOrExit("Delete this account and all associated data? [y/N] ");
|
|
50672
52811
|
}
|
|
50673
52812
|
await client.requestDeleteAccountEmailCode();
|
|
50674
|
-
const emailCode = await
|
|
52813
|
+
const emailCode = await promptLine2("Enter email verification code: ");
|
|
50675
52814
|
await client.verifyDeleteAccountEmailCode(emailCode);
|
|
50676
52815
|
const authMethods = await client.getAuthMethodsStatus();
|
|
50677
|
-
const totpCode = authMethods.has_2fa ? await
|
|
52816
|
+
const totpCode = authMethods.has_2fa ? await promptLine2("Enter 2FA code: ") : void 0;
|
|
50678
52817
|
const result = await client.deleteAccountWithCliVerification(totpCode);
|
|
50679
52818
|
if (flags.json === true) printJson2(result);
|
|
50680
52819
|
else console.log("\x1B[32m\u2713\x1B[0m Account deletion requested and local CLI session cleared");
|
|
@@ -51393,11 +53532,11 @@ function ansiMateBlock(category, mateName) {
|
|
|
51393
53532
|
const [r, g, b] = CATEGORY_ANSI_COLORS[category];
|
|
51394
53533
|
return `\x1B[48;2;${r};${g};${b}m\x1B[97m ${label} \x1B[0m`;
|
|
51395
53534
|
}
|
|
51396
|
-
function
|
|
53535
|
+
function formatTimestamp2(ts) {
|
|
51397
53536
|
if (!ts) return "\u2014";
|
|
51398
53537
|
const d = new Date(ts * 1e3);
|
|
51399
|
-
const
|
|
51400
|
-
return `${d.getFullYear()}-${
|
|
53538
|
+
const pad2 = (n) => String(n).padStart(2, "0");
|
|
53539
|
+
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
51401
53540
|
}
|
|
51402
53541
|
function printChatsTable(result) {
|
|
51403
53542
|
const { chats, total, page, limit, hasMore } = result;
|
|
@@ -51416,7 +53555,7 @@ function printChatsTable(result) {
|
|
|
51416
53555
|
);
|
|
51417
53556
|
for (const chat of chats) {
|
|
51418
53557
|
const block = ansiColorBlock(chat.category);
|
|
51419
|
-
const time =
|
|
53558
|
+
const time = formatTimestamp2(chat.updatedAt);
|
|
51420
53559
|
const title = chat.title ?? "(no title)";
|
|
51421
53560
|
const idStr = chat.shortId;
|
|
51422
53561
|
const sourceLabel = chat.source === "example" ? " \x1B[33mEXAMPLE CHAT\x1B[0m" : "";
|
|
@@ -51977,7 +54116,7 @@ async function renderMessageText(text, embedRefIndex, client) {
|
|
|
51977
54116
|
async function printChatConversation(client, chat, messages, followUpSuggestions, options = {}) {
|
|
51978
54117
|
const block = ansiColorBlock(chat.category);
|
|
51979
54118
|
const title = chat.title ?? "(no title)";
|
|
51980
|
-
const ts =
|
|
54119
|
+
const ts = formatTimestamp2(chat.updatedAt);
|
|
51981
54120
|
if (options.example) {
|
|
51982
54121
|
process.stdout.write("\x1B[33mEXAMPLE CHAT\x1B[0m\n");
|
|
51983
54122
|
process.stdout.write("\x1B[2mThis is a public example chat. Log in to create and sync your own private chats.\x1B[0m\n\n");
|
|
@@ -52007,7 +54146,7 @@ async function printChatConversation(client, chat, messages, followUpSuggestions
|
|
|
52007
54146
|
const embedRefIndex = hasEmbedRefs ? await client.buildEmbedRefIndex(parentEmbedIds) : /* @__PURE__ */ new Map();
|
|
52008
54147
|
for (let i = 0; i < messages.length; i++) {
|
|
52009
54148
|
const msg = messages[i];
|
|
52010
|
-
const msgTs =
|
|
54149
|
+
const msgTs = formatTimestamp2(msg.createdAt);
|
|
52011
54150
|
process.stdout.write(`${SEP}
|
|
52012
54151
|
`);
|
|
52013
54152
|
if (msg.role === "user") {
|
|
@@ -52068,7 +54207,7 @@ async function printChatConversation(client, chat, messages, followUpSuggestions
|
|
|
52068
54207
|
}
|
|
52069
54208
|
async function printChatConversationRaw(chat, messages, options = {}) {
|
|
52070
54209
|
const title = chat.title ?? "(no title)";
|
|
52071
|
-
const ts =
|
|
54210
|
+
const ts = formatTimestamp2(chat.updatedAt);
|
|
52072
54211
|
if (options.example) {
|
|
52073
54212
|
process.stdout.write("EXAMPLE CHAT\n");
|
|
52074
54213
|
process.stdout.write("This is a public example chat. Log in to create and sync your own private chats.\n\n");
|
|
@@ -52083,7 +54222,7 @@ async function printChatConversationRaw(chat, messages, options = {}) {
|
|
|
52083
54222
|
return;
|
|
52084
54223
|
}
|
|
52085
54224
|
for (const msg of messages) {
|
|
52086
|
-
const msgTs =
|
|
54225
|
+
const msgTs = formatTimestamp2(msg.createdAt);
|
|
52087
54226
|
process.stdout.write(`${SEP}
|
|
52088
54227
|
`);
|
|
52089
54228
|
const role = msg.role === "user" ? "You" : msg.senderName ?? msg.role;
|
|
@@ -52586,18 +54725,18 @@ function printWhoAmI(user) {
|
|
|
52586
54725
|
}
|
|
52587
54726
|
}
|
|
52588
54727
|
function printGenericObject(value, indent = 0) {
|
|
52589
|
-
const
|
|
54728
|
+
const pad2 = " ".repeat(indent);
|
|
52590
54729
|
if (value === null || value === void 0) {
|
|
52591
|
-
console.log(`${
|
|
54730
|
+
console.log(`${pad2}(empty)`);
|
|
52592
54731
|
return;
|
|
52593
54732
|
}
|
|
52594
54733
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
52595
|
-
console.log(`${
|
|
54734
|
+
console.log(`${pad2}${value}`);
|
|
52596
54735
|
return;
|
|
52597
54736
|
}
|
|
52598
54737
|
if (Array.isArray(value)) {
|
|
52599
54738
|
if (value.length === 0) {
|
|
52600
|
-
console.log(`${
|
|
54739
|
+
console.log(`${pad2}(empty list)`);
|
|
52601
54740
|
return;
|
|
52602
54741
|
}
|
|
52603
54742
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -52605,11 +54744,11 @@ function printGenericObject(value, indent = 0) {
|
|
|
52605
54744
|
if (item && typeof item === "object") {
|
|
52606
54745
|
printGenericObject(item, indent);
|
|
52607
54746
|
if (i < value.length - 1) {
|
|
52608
|
-
process.stdout.write(`${
|
|
54747
|
+
process.stdout.write(`${pad2}\x1B[2m${"\u2500".repeat(40)}\x1B[0m
|
|
52609
54748
|
`);
|
|
52610
54749
|
}
|
|
52611
54750
|
} else {
|
|
52612
|
-
console.log(`${
|
|
54751
|
+
console.log(`${pad2}${String(item)}`);
|
|
52613
54752
|
}
|
|
52614
54753
|
}
|
|
52615
54754
|
return;
|
|
@@ -52629,34 +54768,34 @@ function printGenericObject(value, indent = 0) {
|
|
|
52629
54768
|
if (Array.isArray(v)) {
|
|
52630
54769
|
if (v.length === 0) {
|
|
52631
54770
|
process.stdout.write(
|
|
52632
|
-
`${
|
|
54771
|
+
`${pad2} \x1B[2m${k.padEnd(20)}\x1B[0m (none)
|
|
52633
54772
|
`
|
|
52634
54773
|
);
|
|
52635
54774
|
} else if (v.length > 0 && typeof v[0] === "object") {
|
|
52636
54775
|
process.stdout.write(
|
|
52637
54776
|
`
|
|
52638
|
-
${
|
|
54777
|
+
${pad2} \x1B[1m${k}\x1B[0m \x1B[2m(${v.length})\x1B[0m
|
|
52639
54778
|
`
|
|
52640
54779
|
);
|
|
52641
54780
|
printGenericObject(v, indent + 1);
|
|
52642
54781
|
} else {
|
|
52643
54782
|
process.stdout.write(
|
|
52644
|
-
`${
|
|
54783
|
+
`${pad2} \x1B[2m${k.padEnd(20)}\x1B[0m ${v.join(", ")}
|
|
52645
54784
|
`
|
|
52646
54785
|
);
|
|
52647
54786
|
}
|
|
52648
54787
|
} else if (typeof v === "object") {
|
|
52649
|
-
process.stdout.write(`${
|
|
54788
|
+
process.stdout.write(`${pad2} \x1B[1m${k}\x1B[0m
|
|
52650
54789
|
`);
|
|
52651
54790
|
printGenericObject(v, indent + 1);
|
|
52652
54791
|
} else {
|
|
52653
|
-
process.stdout.write(`${
|
|
54792
|
+
process.stdout.write(`${pad2} \x1B[2m${k.padEnd(20)}\x1B[0m ${v}
|
|
52654
54793
|
`);
|
|
52655
54794
|
}
|
|
52656
54795
|
}
|
|
52657
54796
|
return;
|
|
52658
54797
|
}
|
|
52659
|
-
console.log(`${
|
|
54798
|
+
console.log(`${pad2}${String(value)}`);
|
|
52660
54799
|
}
|
|
52661
54800
|
function printBillingResponse(obj) {
|
|
52662
54801
|
header("Billing\n");
|
|
@@ -52732,7 +54871,7 @@ function printMemoriesList(memories) {
|
|
|
52732
54871
|
header(`Memories (${memories.length})
|
|
52733
54872
|
`);
|
|
52734
54873
|
for (const mem of memories) {
|
|
52735
|
-
const ts =
|
|
54874
|
+
const ts = formatTimestamp2(mem.updated_at);
|
|
52736
54875
|
process.stdout.write(
|
|
52737
54876
|
`\x1B[1m${mem.app_id}/${mem.item_type}\x1B[0m \x1B[2mv${mem.item_version} ${ts} id:${mem.id.slice(0, 8)}\x1B[0m
|
|
52738
54877
|
`
|
|
@@ -52982,46 +55121,46 @@ async function handleMentions(client, subcommand, _rest, flags) {
|
|
|
52982
55121
|
process.exit(1);
|
|
52983
55122
|
}
|
|
52984
55123
|
function serializeToYaml(data, indent = 0) {
|
|
52985
|
-
const
|
|
55124
|
+
const pad2 = " ".repeat(indent);
|
|
52986
55125
|
let out = "";
|
|
52987
55126
|
for (const [key, val] of Object.entries(data)) {
|
|
52988
55127
|
if (val === null || val === void 0) {
|
|
52989
|
-
out += `${
|
|
55128
|
+
out += `${pad2}${key}: null
|
|
52990
55129
|
`;
|
|
52991
55130
|
} else if (typeof val === "boolean" || typeof val === "number") {
|
|
52992
|
-
out += `${
|
|
55131
|
+
out += `${pad2}${key}: ${val}
|
|
52993
55132
|
`;
|
|
52994
55133
|
} else if (typeof val === "string") {
|
|
52995
55134
|
if (val.includes("\n")) {
|
|
52996
|
-
out += `${
|
|
55135
|
+
out += `${pad2}${key}: |
|
|
52997
55136
|
`;
|
|
52998
55137
|
for (const line of val.split("\n")) {
|
|
52999
|
-
out += `${
|
|
55138
|
+
out += `${pad2} ${line}
|
|
53000
55139
|
`;
|
|
53001
55140
|
}
|
|
53002
55141
|
} else {
|
|
53003
55142
|
const needsQuote = val.includes(":") || val.includes("#") || val.startsWith("{") || val.startsWith("[") || val.startsWith("'") || val.startsWith('"') || val === "" || val === "true" || val === "false" || val === "null";
|
|
53004
|
-
out += `${
|
|
55143
|
+
out += `${pad2}${key}: ${needsQuote ? `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : val}
|
|
53005
55144
|
`;
|
|
53006
55145
|
}
|
|
53007
55146
|
} else if (Array.isArray(val)) {
|
|
53008
|
-
out += `${
|
|
55147
|
+
out += `${pad2}${key}:
|
|
53009
55148
|
`;
|
|
53010
55149
|
for (const item of val) {
|
|
53011
55150
|
if (typeof item === "object" && item !== null) {
|
|
53012
|
-
out += `${
|
|
55151
|
+
out += `${pad2}-
|
|
53013
55152
|
`;
|
|
53014
55153
|
out += serializeToYaml(
|
|
53015
55154
|
item,
|
|
53016
55155
|
indent + 2
|
|
53017
55156
|
);
|
|
53018
55157
|
} else {
|
|
53019
|
-
out += `${
|
|
55158
|
+
out += `${pad2}- ${item}
|
|
53020
55159
|
`;
|
|
53021
55160
|
}
|
|
53022
55161
|
}
|
|
53023
55162
|
} else if (typeof val === "object") {
|
|
53024
|
-
out += `${
|
|
55163
|
+
out += `${pad2}${key}:
|
|
53025
55164
|
`;
|
|
53026
55165
|
out += serializeToYaml(val, indent + 1);
|
|
53027
55166
|
}
|
|
@@ -53109,6 +55248,8 @@ Commands:
|
|
|
53109
55248
|
openmates logout Log out and clear session
|
|
53110
55249
|
openmates whoami [--json] Show account info
|
|
53111
55250
|
openmates chats [--help] Chat commands (list, search, show, ...)
|
|
55251
|
+
openmates tasks [--help] Task commands (list, create, board, ...)
|
|
55252
|
+
openmates drafts [--help] Encrypted draft lifecycle commands
|
|
53112
55253
|
openmates apps [--help] App skill commands (list, run, ...)
|
|
53113
55254
|
openmates workflows [--help] Server-side workflow commands
|
|
53114
55255
|
openmates mentions [--help] List available @mentions
|
|
@@ -53247,7 +55388,7 @@ Creates local ignored credential artifacts for reserved E2E auth accounts. The
|
|
|
53247
55388
|
command refuses production API URLs and does not upload GitHub secrets.
|
|
53248
55389
|
|
|
53249
55390
|
Options:
|
|
53250
|
-
--slot <
|
|
55391
|
+
--slot <14-20> Reserved auth-account slot
|
|
53251
55392
|
--artifact <path> Output .env artifact path
|
|
53252
55393
|
--email <email> Test email; prompted/generated when omitted
|
|
53253
55394
|
--domain <mail-domain> Generate email at this domain
|
|
@@ -53361,6 +55502,46 @@ Examples:
|
|
|
53361
55502
|
openmates chats share last --expires 604800
|
|
53362
55503
|
openmates chats share d262cb68 --password mypass`);
|
|
53363
55504
|
}
|
|
55505
|
+
function printTasksHelp() {
|
|
55506
|
+
console.log(`Tasks commands:
|
|
55507
|
+
openmates tasks list [--status <status>] [--chat <id>] [--project <id>] [--json]
|
|
55508
|
+
openmates tasks board [--chat <id>] [--project <id>] [--json]
|
|
55509
|
+
openmates tasks show <task-id|short-id> [--json]
|
|
55510
|
+
openmates tasks create --title <title> [--description <text>] [--assign user|ai] [--chat <id>] [--project <id>] [--status <status>] [--due <date>] [--json]
|
|
55511
|
+
openmates tasks edit <task-id|short-id> [--title <title>] [--description <text>] [--assign user|ai] [--status <status>] [--json]
|
|
55512
|
+
openmates tasks delete <task-id|short-id> --confirm [--json]
|
|
55513
|
+
openmates tasks start <task-id|short-id> [--json]
|
|
55514
|
+
openmates tasks status [<task-id|short-id>] [--json]
|
|
55515
|
+
openmates tasks block <task-id|short-id> --reason <code> [--json]
|
|
55516
|
+
openmates tasks unblock <task-id|short-id> [--json]
|
|
55517
|
+
openmates tasks skip <task-id|short-id> [--json]
|
|
55518
|
+
openmates tasks done <task-id|short-id> [--json]
|
|
55519
|
+
openmates tasks reorder <task-id|short-id> [--before <task-id>] [--after <task-id>] [--position <n>] [--status <status>] [--json]
|
|
55520
|
+
|
|
55521
|
+
Chat-scoped aliases:
|
|
55522
|
+
openmates chats <chat-id> tasks list
|
|
55523
|
+
openmates chats <chat-id> tasks board
|
|
55524
|
+
openmates chats <chat-id> tasks create --title <title>
|
|
55525
|
+
|
|
55526
|
+
Statuses:
|
|
55527
|
+
backlog, todo, in_progress, blocked, done
|
|
55528
|
+
|
|
55529
|
+
Notes:
|
|
55530
|
+
Task IDs accept full task_id or human short IDs such as OM-6.
|
|
55531
|
+
Normal output decrypts task title and description locally; use --json for machine-readable plaintext fields.`);
|
|
55532
|
+
}
|
|
55533
|
+
function printDraftsHelp() {
|
|
55534
|
+
console.log(`Draft commands:
|
|
55535
|
+
openmates drafts create <text> [--chat <uuid>] [--preview <text>] [--json]
|
|
55536
|
+
openmates drafts update <chat-id> <text> [--preview <text>] [--json]
|
|
55537
|
+
openmates drafts list [--refresh] [--json]
|
|
55538
|
+
openmates drafts get <chat-id> [--refresh] [--json]
|
|
55539
|
+
openmates drafts clear <chat-id> [--json]
|
|
55540
|
+
openmates drafts sync [--json]
|
|
55541
|
+
|
|
55542
|
+
Draft plaintext is encrypted locally with the account master key. Only Format-D
|
|
55543
|
+
ciphertext and version metadata are sent to the server or written to CLI cache.`);
|
|
55544
|
+
}
|
|
53364
55545
|
function printAppsHelp() {
|
|
53365
55546
|
console.log(`Apps commands:
|
|
53366
55547
|
openmates apps list [--json]
|
|
@@ -53369,6 +55550,7 @@ function printAppsHelp() {
|
|
|
53369
55550
|
openmates apps skill-info <app-id> <skill-id> [--json]
|
|
53370
55551
|
openmates apps <app-id> <skill-id> "<query>" [--json]
|
|
53371
55552
|
openmates apps <app-id> <skill-id> --input '<json>' [--json]
|
|
55553
|
+
openmates apps models3d generate --image ./reference.png [--json]
|
|
53372
55554
|
openmates apps code run --language python --code 'print("Hello")'
|
|
53373
55555
|
openmates apps code run --entry main.py --file main.py [--file requirements.txt]
|
|
53374
55556
|
openmates apps code run --entry main.py --dir ./project [--exclude node_modules]
|
|
@@ -53384,6 +55566,7 @@ Examples:
|
|
|
53384
55566
|
openmates apps web search "latest AI news"
|
|
53385
55567
|
openmates apps news search "climate change"
|
|
53386
55568
|
openmates apps ai ask "Summarise this: ..."
|
|
55569
|
+
openmates apps models3d generate --image ./chair.png
|
|
53387
55570
|
openmates apps code run --language python --filename hello.py --code 'print("Hello from CLI")'
|
|
53388
55571
|
openmates apps travel search_connections --input '{"requests":[{"legs":[{"origin":"BER","destination":"LHR","date":"2026-04-15"}]}]}'
|
|
53389
55572
|
openmates apps travel booking-link --token "<booking_token from search result>"
|
|
@@ -53393,6 +55576,9 @@ function printWorkflowsHelp() {
|
|
|
53393
55576
|
console.log(`Workflows commands:
|
|
53394
55577
|
openmates workflows list [--json]
|
|
53395
55578
|
openmates workflows capabilities [--json]
|
|
55579
|
+
openmates workflows validate --file workflow.yml [--json]
|
|
55580
|
+
openmates workflows create --file workflow.yml [--json]
|
|
55581
|
+
openmates workflows update <workflow-id> --file workflow.yml [--json]
|
|
53396
55582
|
openmates workflows create --title <title> --graph '<json>' [--enabled] [--run-content-retention last_5|none] [--json]
|
|
53397
55583
|
openmates workflows input <text> [--workflow-id <id>] [--project-id <id>] [--json]
|
|
53398
55584
|
openmates workflows input-show <session-id> [--json]
|
|
@@ -53403,9 +55589,13 @@ function printWorkflowsHelp() {
|
|
|
53403
55589
|
openmates workflows show <workflow-id> [--json]
|
|
53404
55590
|
openmates workflows enable <workflow-id> [--json]
|
|
53405
55591
|
openmates workflows disable <workflow-id> [--json]
|
|
53406
|
-
openmates workflows run <workflow-id> [--mode manual|test] [--input '<json>'] [--json]
|
|
55592
|
+
openmates workflows run <workflow-id> --idempotency-key <stable-key> [--mode manual|test] [--input '<json>'] [--json]
|
|
53407
55593
|
openmates workflows runs <workflow-id> [--json]
|
|
53408
55594
|
openmates workflows run-show <workflow-id> <run-id> [--json]
|
|
55595
|
+
openmates workflows run-cancel <workflow-id> <run-id> [--json]
|
|
55596
|
+
openmates workflows step-test <workflow-id> <step-id> [--input '<json>'] [--yes] [--json]
|
|
55597
|
+
openmates workflows respond <workflow-id> <run-id> <step-id> --input '<json>' [--json]
|
|
55598
|
+
openmates workflows help-app <app.skill> [--json]
|
|
53409
55599
|
openmates workflows delete <workflow-id> --yes [--json]
|
|
53410
55600
|
|
|
53411
55601
|
Workflows run on the OpenMates server, not in this terminal process. The CLI
|
|
@@ -53415,6 +55605,7 @@ and Apple clients.
|
|
|
53415
55605
|
Examples:
|
|
53416
55606
|
openmates workflows list
|
|
53417
55607
|
openmates workflows capabilities --json
|
|
55608
|
+
openmates workflows help-app weather.forecast
|
|
53418
55609
|
openmates workflows input "alert me if it rains tomorrow"
|
|
53419
55610
|
openmates workflows run wf_123 --mode test --json`);
|
|
53420
55611
|
}
|
|
@@ -53666,9 +55857,12 @@ if (isCliEntrypoint()) {
|
|
|
53666
55857
|
}
|
|
53667
55858
|
|
|
53668
55859
|
export {
|
|
55860
|
+
deriveChatCompletionRecoveryKeypair,
|
|
55861
|
+
openChatCompletionRecoveryEnvelope,
|
|
53669
55862
|
unwrapApiKeyMasterKey,
|
|
53670
55863
|
decryptWithAesGcmCombined,
|
|
53671
55864
|
decryptBytesWithAesGcm,
|
|
55865
|
+
encryptBytesWithAesGcm,
|
|
53672
55866
|
encryptWithAesGcmCombined,
|
|
53673
55867
|
hashItemKey,
|
|
53674
55868
|
generateChatShareBlob,
|
|
@@ -53681,6 +55875,7 @@ export {
|
|
|
53681
55875
|
INTEREST_TAG_IDS,
|
|
53682
55876
|
normalizeInterestTagIds,
|
|
53683
55877
|
MEMORY_TYPE_REGISTRY,
|
|
55878
|
+
reconcileAuthoritativeChats,
|
|
53684
55879
|
MATE_NAMES,
|
|
53685
55880
|
deriveAppUrl,
|
|
53686
55881
|
OpenMatesClient,
|