openmates 0.14.7 → 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-U2RKIE56.js → chunk-7BIVPFKJ.js} +2430 -200
- 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
|
|
@@ -9534,6 +10244,22 @@ import { createInterface as createPromptInterface } from "readline/promises";
|
|
|
9534
10244
|
import { homedir as homedir5 } from "os";
|
|
9535
10245
|
import { dirname, join as join3, resolve as resolve3 } from "path";
|
|
9536
10246
|
|
|
10247
|
+
// src/branding.ts
|
|
10248
|
+
var OPENMATES_WORDMARK = "OPENMATES";
|
|
10249
|
+
var OPENMATES_ASCII_LARGE = [
|
|
10250
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588",
|
|
10251
|
+
"\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ",
|
|
10252
|
+
"\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588",
|
|
10253
|
+
"\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
10254
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588"
|
|
10255
|
+
];
|
|
10256
|
+
function openMatesAsciiLogo(width = 100) {
|
|
10257
|
+
return width >= 86 ? [...OPENMATES_ASCII_LARGE, OPENMATES_WORDMARK] : [OPENMATES_WORDMARK];
|
|
10258
|
+
}
|
|
10259
|
+
function renderOpenMatesAsciiLogo(width = 100) {
|
|
10260
|
+
return openMatesAsciiLogo(width).join("\n");
|
|
10261
|
+
}
|
|
10262
|
+
|
|
9537
10263
|
// src/support.ts
|
|
9538
10264
|
var SUPPORT_URL = "https://openmates.org/#settings/support";
|
|
9539
10265
|
var SUPPORT_TITLE = "Support OpenMates development";
|
|
@@ -10446,6 +11172,38 @@ function missingRequiredEnvKeys(installPath, role) {
|
|
|
10446
11172
|
const env = readEnvMap(installPath);
|
|
10447
11173
|
return requiredRuntimeEnvKeys(role).filter((key) => !env[key]);
|
|
10448
11174
|
}
|
|
11175
|
+
var LONG_RUNNING_SERVER_COMMANDS = /* @__PURE__ */ new Set([
|
|
11176
|
+
"install",
|
|
11177
|
+
"start",
|
|
11178
|
+
"stop",
|
|
11179
|
+
"restart",
|
|
11180
|
+
"update",
|
|
11181
|
+
"preflight",
|
|
11182
|
+
"caddy",
|
|
11183
|
+
"backup",
|
|
11184
|
+
"restore",
|
|
11185
|
+
"reset",
|
|
11186
|
+
"make-admin",
|
|
11187
|
+
"ai",
|
|
11188
|
+
"uninstall"
|
|
11189
|
+
]);
|
|
11190
|
+
function shouldPrintServerCommandStart(subcommand, rest, flags) {
|
|
11191
|
+
if (flags.json === true || !LONG_RUNNING_SERVER_COMMANDS.has(subcommand)) return false;
|
|
11192
|
+
if (subcommand === "backup" && rest[0] === "list") return false;
|
|
11193
|
+
if (subcommand === "update" && rest[0] === "status") return false;
|
|
11194
|
+
if (subcommand === "ai" && rest[0] === "models" && (rest[1] === "list" || rest[1] === "remove")) return false;
|
|
11195
|
+
if (subcommand === "caddy" && (rest[0] === "status" || rest[0] === "diff")) return false;
|
|
11196
|
+
return true;
|
|
11197
|
+
}
|
|
11198
|
+
function printServerCommandStart(subcommand, rest, flags) {
|
|
11199
|
+
const command = ["server", subcommand, ...rest].join(" ");
|
|
11200
|
+
console.log(renderOpenMatesAsciiLogo());
|
|
11201
|
+
console.log("");
|
|
11202
|
+
console.log(`Running OpenMates ${command}...`);
|
|
11203
|
+
if (typeof flags.path === "string") console.log(`Path: ${resolve3(flags.path)}`);
|
|
11204
|
+
if (typeof flags.role === "string") console.log(`Role: ${flags.role}`);
|
|
11205
|
+
console.log("");
|
|
11206
|
+
}
|
|
10449
11207
|
function vaultCheckContainerForRole(role) {
|
|
10450
11208
|
if (role === "core") return "api";
|
|
10451
11209
|
if (role === "upload") return "app-uploads";
|
|
@@ -11966,6 +12724,9 @@ async function handleServer(subcommand, rest, flags) {
|
|
|
11966
12724
|
printServerHelp();
|
|
11967
12725
|
return;
|
|
11968
12726
|
}
|
|
12727
|
+
if (shouldPrintServerCommandStart(subcommand, rest, flags)) {
|
|
12728
|
+
printServerCommandStart(subcommand, rest, flags);
|
|
12729
|
+
}
|
|
11969
12730
|
switch (subcommand) {
|
|
11970
12731
|
case "status":
|
|
11971
12732
|
return serverStatus(flags);
|
|
@@ -28546,6 +29307,11 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
28546
29307
|
}
|
|
28547
29308
|
}
|
|
28548
29309
|
},
|
|
29310
|
+
models3d: {
|
|
29311
|
+
generate: {
|
|
29312
|
+
text: "Generate 3D model"
|
|
29313
|
+
}
|
|
29314
|
+
},
|
|
28549
29315
|
music: {
|
|
28550
29316
|
generate: {
|
|
28551
29317
|
text: "Generate",
|
|
@@ -30885,6 +31651,27 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
30885
31651
|
save: {
|
|
30886
31652
|
text: "Save"
|
|
30887
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
|
+
},
|
|
30888
31675
|
settings: {
|
|
30889
31676
|
text: "Settings"
|
|
30890
31677
|
},
|
|
@@ -36515,6 +37302,22 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
36515
37302
|
},
|
|
36516
37303
|
privacy: {
|
|
36517
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
|
+
},
|
|
36518
37321
|
fitness: {
|
|
36519
37322
|
heading: {
|
|
36520
37323
|
text: "Fitness search (only when you use the fitness skill)"
|
|
@@ -40605,6 +41408,42 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
40605
41408
|
description: {
|
|
40606
41409
|
text: "Manage your credits, purchases, and what you used credits for."
|
|
40607
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
|
+
},
|
|
40608
41447
|
text: "Billing & Usage",
|
|
40609
41448
|
title: {
|
|
40610
41449
|
text: "Billing & Credits"
|
|
@@ -41385,6 +42224,12 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
41385
42224
|
learning_mode_disabled: {
|
|
41386
42225
|
text: "Learning Mode disabled."
|
|
41387
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
|
+
},
|
|
41388
42233
|
learning_mode_age_group_prompt: {
|
|
41389
42234
|
text: "Learner age group: under_10, 10_12, 13_15, 16_18, or adult"
|
|
41390
42235
|
},
|
|
@@ -42629,6 +43474,73 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
42629
43474
|
},
|
|
42630
43475
|
share_debug_logs_admin_notice: {
|
|
42631
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
|
+
}
|
|
42632
43544
|
}
|
|
42633
43545
|
},
|
|
42634
43546
|
terms_and_conditions: {
|
|
@@ -44439,6 +45351,21 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
44439
45351
|
pair_server_use_custom: {
|
|
44440
45352
|
text: "Use custom"
|
|
44441
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
|
+
},
|
|
44442
45369
|
pair_connect_apple_watch_title: {
|
|
44443
45370
|
text: "Connect Apple Watch"
|
|
44444
45371
|
},
|
|
@@ -46868,22 +47795,6 @@ var TuiTerminal = class {
|
|
|
46868
47795
|
}
|
|
46869
47796
|
};
|
|
46870
47797
|
|
|
46871
|
-
// src/branding.ts
|
|
46872
|
-
var OPENMATES_WORDMARK = "OPENMATES";
|
|
46873
|
-
var OPENMATES_ASCII_LARGE = [
|
|
46874
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588",
|
|
46875
|
-
"\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ",
|
|
46876
|
-
"\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588",
|
|
46877
|
-
"\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
46878
|
-
" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588"
|
|
46879
|
-
];
|
|
46880
|
-
function openMatesAsciiLogo(width = 100) {
|
|
46881
|
-
return width >= 86 ? [...OPENMATES_ASCII_LARGE, OPENMATES_WORDMARK] : [OPENMATES_WORDMARK];
|
|
46882
|
-
}
|
|
46883
|
-
function renderOpenMatesAsciiLogo(width = 100) {
|
|
46884
|
-
return openMatesAsciiLogo(width).join("\n");
|
|
46885
|
-
}
|
|
46886
|
-
|
|
46887
47798
|
// src/tuiRenderer.ts
|
|
46888
47799
|
var MIN_WIDTH = 48;
|
|
46889
47800
|
var CONTENT_PREVIEW_LINES = 12;
|
|
@@ -46910,6 +47821,17 @@ function createInitialTuiState() {
|
|
|
46910
47821
|
selectedInterests: [],
|
|
46911
47822
|
examples: [],
|
|
46912
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,
|
|
46913
47835
|
messages: [],
|
|
46914
47836
|
status: null,
|
|
46915
47837
|
isBusy: false
|
|
@@ -46943,6 +47865,7 @@ Files:
|
|
|
46943
47865
|
|
|
46944
47866
|
More:
|
|
46945
47867
|
openmates apps list
|
|
47868
|
+
openmates workflows list
|
|
46946
47869
|
openmates mentions list
|
|
46947
47870
|
openmates embeds show <embed-id>
|
|
46948
47871
|
openmates help
|
|
@@ -46965,7 +47888,7 @@ function renderTuiFrame(state, width, height) {
|
|
|
46965
47888
|
const contentHeight = safeHeight - 4;
|
|
46966
47889
|
const body = renderBody(state, innerWidth);
|
|
46967
47890
|
const visible = sliceForScroll(body, contentHeight, state.scrollOffset, state.screen === "chat");
|
|
46968
|
-
const inputLine = state.screen === "interests" || state.screen === "examples" ? renderHintLine(state, innerWidth) : `> ${state.input || inputPlaceholder(state)}`;
|
|
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)}`;
|
|
46969
47892
|
const lines = [
|
|
46970
47893
|
header2,
|
|
46971
47894
|
...padLines(visible, contentHeight, innerWidth).map((line) => boxed(line, innerWidth)),
|
|
@@ -46991,6 +47914,14 @@ function renderBody(state, width) {
|
|
|
46991
47914
|
return renderStatus(state, width);
|
|
46992
47915
|
case "embed":
|
|
46993
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);
|
|
46994
47925
|
case "start":
|
|
46995
47926
|
default:
|
|
46996
47927
|
return renderStart(width);
|
|
@@ -47012,7 +47943,7 @@ function renderStart(width) {
|
|
|
47012
47943
|
"Type @./notes.md, @~/Downloads/report.pdf, or @src/app.ts in your message.",
|
|
47013
47944
|
"Images, PDFs, audio, and code files are attached as encrypted embeds when you are signed in.",
|
|
47014
47945
|
"",
|
|
47015
|
-
"Shortcuts: /help /login /signup /examples /exit"
|
|
47946
|
+
"Shortcuts: /help /login /signup /examples /tasks /exit"
|
|
47016
47947
|
].flatMap((line) => wrap(line, width));
|
|
47017
47948
|
}
|
|
47018
47949
|
function renderHelp(width) {
|
|
@@ -47032,6 +47963,9 @@ function renderHelp(width) {
|
|
|
47032
47963
|
"",
|
|
47033
47964
|
"Commands",
|
|
47034
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",
|
|
47035
47969
|
" /login Pair-auth login",
|
|
47036
47970
|
" /signup Leave TUI and run guided signup",
|
|
47037
47971
|
" /embed <id> Open an embed detail view",
|
|
@@ -47040,6 +47974,150 @@ function renderHelp(width) {
|
|
|
47040
47974
|
"Outside TUI: openmates --help, openmates chats --help, openmates apps --help"
|
|
47041
47975
|
].flatMap((line) => wrap(line, width));
|
|
47042
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
|
+
}
|
|
47043
48121
|
function renderInterests(state, width) {
|
|
47044
48122
|
const lines = [
|
|
47045
48123
|
"Private local personalization",
|
|
@@ -47134,13 +48212,55 @@ function renderMessageContent(content, width) {
|
|
|
47134
48212
|
}
|
|
47135
48213
|
function renderHintLine(state, width) {
|
|
47136
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);
|
|
47137
48220
|
return truncateVisible("\u2191/\u2193 choose Enter open /search filter Esc back", width);
|
|
47138
48221
|
}
|
|
47139
48222
|
function inputPlaceholder(state) {
|
|
47140
48223
|
if (state.screen === "example") return "Continue from this example, or ask your own question...";
|
|
47141
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";
|
|
47142
48226
|
return "Ask anything...";
|
|
47143
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
|
+
}
|
|
47144
48264
|
function interestScore(haystack, interest) {
|
|
47145
48265
|
let score = haystack.includes(interest) ? 10 : 0;
|
|
47146
48266
|
for (const token of interest.split(/\s+|&/).filter((part) => part.length > 2)) {
|
|
@@ -47191,6 +48311,221 @@ function boxed(line, width) {
|
|
|
47191
48311
|
return `\u2502 ${line.padEnd(width)} \u2502`;
|
|
47192
48312
|
}
|
|
47193
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
|
+
|
|
47194
48529
|
// src/tui.ts
|
|
47195
48530
|
function defaultModeForStreams(input, output) {
|
|
47196
48531
|
return input.isTTY === true && output.isTTY === true ? "tui" : "quickstart";
|
|
@@ -47233,11 +48568,78 @@ async function handleKey(params) {
|
|
|
47233
48568
|
return;
|
|
47234
48569
|
}
|
|
47235
48570
|
if (key.name === "escape") {
|
|
47236
|
-
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";
|
|
47237
48577
|
state.input = "";
|
|
47238
48578
|
render();
|
|
47239
48579
|
return;
|
|
47240
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
|
+
}
|
|
47241
48643
|
if (key.name === "up") {
|
|
47242
48644
|
moveSelectionOrScroll(state, -1);
|
|
47243
48645
|
render();
|
|
@@ -47282,6 +48684,9 @@ async function handleKey(params) {
|
|
|
47282
48684
|
await handleEnter({ state, client, terminal, render, finish });
|
|
47283
48685
|
return;
|
|
47284
48686
|
}
|
|
48687
|
+
if (state.screen === "workflow" || state.screen === "workflows" || state.screen === "tasks" || state.screen === "task") {
|
|
48688
|
+
return;
|
|
48689
|
+
}
|
|
47285
48690
|
if (!key.ctrl && !key.meta && chunk && chunk >= " ") {
|
|
47286
48691
|
state.input += chunk;
|
|
47287
48692
|
render();
|
|
@@ -47300,6 +48705,23 @@ async function handleEnter(params) {
|
|
|
47300
48705
|
render();
|
|
47301
48706
|
return;
|
|
47302
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
|
+
}
|
|
47303
48725
|
const text = state.input.trim();
|
|
47304
48726
|
if (!text) return;
|
|
47305
48727
|
state.input = "";
|
|
@@ -47326,6 +48748,22 @@ async function handleCommand(params) {
|
|
|
47326
48748
|
render();
|
|
47327
48749
|
return;
|
|
47328
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
|
+
}
|
|
47329
48767
|
if (name === "/signup") {
|
|
47330
48768
|
finish({ action: "signup" });
|
|
47331
48769
|
return;
|
|
@@ -47429,8 +48867,368 @@ function moveSelectionOrScroll(state, direction) {
|
|
|
47429
48867
|
state.selectedIndex = clamp(state.selectedIndex + direction, 0, Math.max(0, state.examples.length - 1));
|
|
47430
48868
|
return;
|
|
47431
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
|
+
}
|
|
47432
48889
|
state.scrollOffset = Math.max(0, state.scrollOffset - direction);
|
|
47433
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
|
+
}
|
|
47434
49232
|
function toggleInterest(state) {
|
|
47435
49233
|
const interest = TUI_INTERESTS[state.selectedIndex];
|
|
47436
49234
|
if (!interest) return;
|
|
@@ -47928,6 +49726,10 @@ async function main() {
|
|
|
47928
49726
|
printChatsHelp();
|
|
47929
49727
|
return;
|
|
47930
49728
|
}
|
|
49729
|
+
if (command === "drafts") {
|
|
49730
|
+
printDraftsHelp();
|
|
49731
|
+
return;
|
|
49732
|
+
}
|
|
47931
49733
|
if (command === "apps") {
|
|
47932
49734
|
printAppsHelp();
|
|
47933
49735
|
return;
|
|
@@ -47940,6 +49742,10 @@ async function main() {
|
|
|
47940
49742
|
printWorkflowsHelp();
|
|
47941
49743
|
return;
|
|
47942
49744
|
}
|
|
49745
|
+
if (command === "tasks") {
|
|
49746
|
+
printTasksHelp();
|
|
49747
|
+
return;
|
|
49748
|
+
}
|
|
47943
49749
|
if (command === "connected-accounts") {
|
|
47944
49750
|
printConnectedAccountsHelp();
|
|
47945
49751
|
return;
|
|
@@ -48050,6 +49856,14 @@ async function main() {
|
|
|
48050
49856
|
await handleChats(client, subcommand, rest, parsed.flags, redactor);
|
|
48051
49857
|
return;
|
|
48052
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
|
+
}
|
|
48053
49867
|
if (command === "apps") {
|
|
48054
49868
|
await handleApps(client, subcommand, rest, parsed.flags);
|
|
48055
49869
|
return;
|
|
@@ -48187,6 +50001,172 @@ function handleCliVersion(flags) {
|
|
|
48187
50001
|
}
|
|
48188
50002
|
console.log("OpenMates CLI is up to date.");
|
|
48189
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
|
+
}
|
|
48190
50170
|
async function handleRemoteAccess(client, subcommand, rest, flags) {
|
|
48191
50171
|
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
48192
50172
|
printRemoteAccessHelp();
|
|
@@ -48197,7 +50177,7 @@ async function handleRemoteAccess(client, subcommand, rest, flags) {
|
|
|
48197
50177
|
if (!rootPath) {
|
|
48198
50178
|
throw new Error("Missing source path. Usage: openmates remote-access start --path <folder>");
|
|
48199
50179
|
}
|
|
48200
|
-
const sourceId = typeof flags["source-id"] === "string" ? flags["source-id"] :
|
|
50180
|
+
const sourceId = typeof flags["source-id"] === "string" ? flags["source-id"] : randomUUID6();
|
|
48201
50181
|
const projectId = typeof flags.project === "string" ? flags.project : void 0;
|
|
48202
50182
|
validateRemoteSourceRegistrationFlags(projectId, flags);
|
|
48203
50183
|
const sourceType = parseRemoteAccessSourceType(flags.type);
|
|
@@ -48303,11 +50283,57 @@ function parseJsonFlag(value, flagName) {
|
|
|
48303
50283
|
throw new Error(`Invalid JSON for ${flagName}: ${message}`);
|
|
48304
50284
|
}
|
|
48305
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
|
+
}
|
|
48306
50328
|
async function handleChats(client, subcommand, rest, flags, redactor) {
|
|
48307
50329
|
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
48308
50330
|
printChatsHelp();
|
|
48309
50331
|
return;
|
|
48310
50332
|
}
|
|
50333
|
+
if (rest[0] === "tasks") {
|
|
50334
|
+
await handleTasks(client, rest[1], rest.slice(2), { ...flags, chat: subcommand });
|
|
50335
|
+
return;
|
|
50336
|
+
}
|
|
48311
50337
|
if (subcommand === "list") {
|
|
48312
50338
|
const limit = typeof flags.limit === "string" ? parseInt(flags.limit, 10) : 10;
|
|
48313
50339
|
const page = typeof flags.page === "string" ? parseInt(flags.page, 10) : 1;
|
|
@@ -48990,7 +51016,37 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
48990
51016
|
}
|
|
48991
51017
|
return;
|
|
48992
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
|
+
}
|
|
48993
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
|
+
}
|
|
48994
51050
|
const title = typeof flags.title === "string" ? flags.title.trim() : "";
|
|
48995
51051
|
const graphJson = typeof flags.graph === "string" ? flags.graph : "";
|
|
48996
51052
|
if (!title) throw new Error("Missing --title for workflow create.");
|
|
@@ -49008,6 +51064,22 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
49008
51064
|
}
|
|
49009
51065
|
return;
|
|
49010
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
|
+
}
|
|
49011
51083
|
if (subcommand === "input") {
|
|
49012
51084
|
const text = typeof flags.text === "string" ? flags.text : rest.join(" ").trim();
|
|
49013
51085
|
if (!text) throw new Error('Missing workflow input text. Example: openmates workflows input "alert me if it rains"');
|
|
@@ -49114,9 +51186,11 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
49114
51186
|
if (subcommand === "run") {
|
|
49115
51187
|
const workflowId = rest[0];
|
|
49116
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.");
|
|
49117
51191
|
const mode = flags.mode === "test" ? "test" : "manual";
|
|
49118
51192
|
const input = typeof flags.input === "string" ? parseJsonFlag(flags.input, "--input") : {};
|
|
49119
|
-
const run = await client.runWorkflow(workflowId, { mode, input });
|
|
51193
|
+
const run = await client.runWorkflow(workflowId, { idempotencyKey, mode, input });
|
|
49120
51194
|
if (flags.json === true) {
|
|
49121
51195
|
printJson2(run);
|
|
49122
51196
|
} else {
|
|
@@ -49147,6 +51221,58 @@ async function handleWorkflows(client, subcommand, rest, flags) {
|
|
|
49147
51221
|
}
|
|
49148
51222
|
return;
|
|
49149
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
|
+
}
|
|
49150
51276
|
throw new Error(`Unknown workflows command '${subcommand}'. Run 'openmates workflows --help'.`);
|
|
49151
51277
|
}
|
|
49152
51278
|
function printWorkflowList(workflows) {
|
|
@@ -49216,6 +51342,30 @@ function printWorkflowCapabilities(capabilities) {
|
|
|
49216
51342
|
kv(capability.id, `${capability.title} \xB7 ${state}`, 24);
|
|
49217
51343
|
}
|
|
49218
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
|
+
}
|
|
49219
51369
|
async function handleApps(client, subcommand, rest, flags) {
|
|
49220
51370
|
const apiKey = resolveApiKey(flags) ?? void 0;
|
|
49221
51371
|
if (!subcommand || subcommand === "help") {
|
|
@@ -49452,7 +51602,7 @@ Run with --help for full parameter details:
|
|
|
49452
51602
|
}
|
|
49453
51603
|
}
|
|
49454
51604
|
try {
|
|
49455
|
-
const inputData = buildSkillInput(flags, inlineTokens, schemaParams);
|
|
51605
|
+
const inputData = app === "models3d" && skill === "generate" ? await buildModels3DGenerateInput(client, flags, inlineTokens, schemaParams) : buildSkillInput(flags, inlineTokens, schemaParams);
|
|
49456
51606
|
const data = await client.runSkill({ app, skill, inputData, apiKey });
|
|
49457
51607
|
if (flags.json === true) {
|
|
49458
51608
|
printJson2(data);
|
|
@@ -49657,6 +51807,27 @@ function buildSkillInput(flags, inlineTokens, schemaParams) {
|
|
|
49657
51807
|
}
|
|
49658
51808
|
return {};
|
|
49659
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
|
+
}
|
|
49660
51831
|
function getEffectiveRequiredParams(schemaParams) {
|
|
49661
51832
|
const required = schemaParams.filter((p) => p.required);
|
|
49662
51833
|
if (required.length > 0) return required;
|
|
@@ -50201,7 +52372,7 @@ async function confirmOrExit(question) {
|
|
|
50201
52372
|
process.exit(0);
|
|
50202
52373
|
}
|
|
50203
52374
|
}
|
|
50204
|
-
async function
|
|
52375
|
+
async function promptLine2(question) {
|
|
50205
52376
|
const rl = await import("readline");
|
|
50206
52377
|
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
50207
52378
|
const answer = await new Promise((resolve7) => iface.question(question, resolve7));
|
|
@@ -50210,7 +52381,7 @@ async function promptLine(question) {
|
|
|
50210
52381
|
}
|
|
50211
52382
|
async function promptSecret(question) {
|
|
50212
52383
|
if (!process.stdin.isTTY) {
|
|
50213
|
-
return
|
|
52384
|
+
return promptLine2(question);
|
|
50214
52385
|
}
|
|
50215
52386
|
return new Promise((resolve7) => {
|
|
50216
52387
|
const stdin2 = process.stdin;
|
|
@@ -50297,8 +52468,8 @@ async function writeSecretFile(filePath, content, force = false) {
|
|
|
50297
52468
|
return filePath;
|
|
50298
52469
|
}
|
|
50299
52470
|
async function generateProvisioningPassword() {
|
|
50300
|
-
const { randomBytes:
|
|
50301
|
-
return `OM-${
|
|
52471
|
+
const { randomBytes: randomBytes4 } = await import("crypto");
|
|
52472
|
+
return `OM-${randomBytes4(18).toString("base64url")}-aA2#`;
|
|
50302
52473
|
}
|
|
50303
52474
|
function decodeBase32(input) {
|
|
50304
52475
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
@@ -50333,7 +52504,7 @@ async function runSecuritySetup(client, flags, options = {}) {
|
|
|
50333
52504
|
result.otpSecret = setup.secret ?? null;
|
|
50334
52505
|
if (setup.otpauth_url) client.renderTotpQrCode(setup.otpauth_url);
|
|
50335
52506
|
if (setup.secret) console.log(`Manual TOTP secret: ${setup.secret}`);
|
|
50336
|
-
const code = typeof process.env.OPENMATES_CLI_SIGNUP_TOTP_CODE === "string" ? process.env.OPENMATES_CLI_SIGNUP_TOTP_CODE : options.autoGenerateTotp && setup.secret ? await generateTotpCode(setup.secret) : await
|
|
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: ");
|
|
50337
52508
|
await client.verifyTotpSetup(code);
|
|
50338
52509
|
const provider = typeof flags.provider === "string" ? flags.provider : "Authenticator app";
|
|
50339
52510
|
await client.setTotpProvider(provider);
|
|
@@ -50373,8 +52544,8 @@ async function handleSignup(client, flags, options = {}) {
|
|
|
50373
52544
|
if (flags.password !== void 0) {
|
|
50374
52545
|
throw new Error("Passwords must be entered through hidden prompts, not command-line flags.");
|
|
50375
52546
|
}
|
|
50376
|
-
const email = typeof flags.email === "string" ? flags.email : await
|
|
50377
|
-
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: ");
|
|
50378
52549
|
const inviteCode = typeof flags["invite-code"] === "string" ? flags["invite-code"] : "";
|
|
50379
52550
|
const language = typeof flags.language === "string" ? flags.language : "en";
|
|
50380
52551
|
const password = process.env.OPENMATES_CLI_SIGNUP_PASSWORD ?? await promptSecret("Password: ");
|
|
@@ -50383,7 +52554,7 @@ async function handleSignup(client, flags, options = {}) {
|
|
|
50383
52554
|
if (password !== confirmPassword) throw new Error("Passwords do not match.");
|
|
50384
52555
|
}
|
|
50385
52556
|
await client.requestSignupEmailCode({ email, inviteCode, language });
|
|
50386
|
-
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: ");
|
|
50387
52558
|
await client.verifySignupEmailCode({ email, username, inviteCode, code: emailCode, language });
|
|
50388
52559
|
const signup = await client.setupPasswordAccount({ email, username, password, inviteCode, language });
|
|
50389
52560
|
const security = await runSecuritySetup(client, flags, options);
|
|
@@ -50425,13 +52596,15 @@ async function handleE2E(client, subcommand, rest, flags) {
|
|
|
50425
52596
|
throw new Error("E2E provisioning refuses production API URLs.");
|
|
50426
52597
|
}
|
|
50427
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.");
|
|
50428
52601
|
const slot = parseRequiredNumber(flags.slot, "--slot");
|
|
50429
|
-
if (![15, 16, 17, 18, 19, 20].includes(slot)) {
|
|
50430
|
-
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.");
|
|
50431
52604
|
}
|
|
50432
52605
|
const artifact = typeof flags.artifact === "string" ? flags.artifact : `test-results/credential-updates/slot-${slot}.env`;
|
|
50433
52606
|
const domain = typeof flags.domain === "string" ? flags.domain : process.env.OPENMATES_CLI_E2E_EMAIL_DOMAIN;
|
|
50434
|
-
const email = typeof flags.email === "string" ? flags.email : domain ? `cli-e2e-slot-${slot}-${Date.now()}@${domain}` : await
|
|
52607
|
+
const email = typeof flags.email === "string" ? flags.email : domain ? `cli-e2e-slot-${slot}-${Date.now()}@${domain}` : await promptLine2("E2E account email: ");
|
|
50435
52608
|
const username = typeof flags.username === "string" ? flags.username : `cli_e2e_slot_${slot}_${Date.now()}`;
|
|
50436
52609
|
const generatedPassword = process.env.OPENMATES_CLI_SIGNUP_PASSWORD ?? await generateProvisioningPassword();
|
|
50437
52610
|
const originalSignupPassword = process.env.OPENMATES_CLI_SIGNUP_PASSWORD;
|
|
@@ -50442,6 +52615,7 @@ async function handleE2E(client, subcommand, rest, flags) {
|
|
|
50442
52615
|
...flags,
|
|
50443
52616
|
email,
|
|
50444
52617
|
username,
|
|
52618
|
+
"invite-code": inviteCode,
|
|
50445
52619
|
yes: true,
|
|
50446
52620
|
json: false,
|
|
50447
52621
|
"backup-codes-output": typeof flags["backup-codes-output"] === "string" ? flags["backup-codes-output"] : `${artifact}.backup-codes`,
|
|
@@ -50636,10 +52810,10 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
50636
52810
|
await confirmOrExit("Delete this account and all associated data? [y/N] ");
|
|
50637
52811
|
}
|
|
50638
52812
|
await client.requestDeleteAccountEmailCode();
|
|
50639
|
-
const emailCode = await
|
|
52813
|
+
const emailCode = await promptLine2("Enter email verification code: ");
|
|
50640
52814
|
await client.verifyDeleteAccountEmailCode(emailCode);
|
|
50641
52815
|
const authMethods = await client.getAuthMethodsStatus();
|
|
50642
|
-
const totpCode = authMethods.has_2fa ? await
|
|
52816
|
+
const totpCode = authMethods.has_2fa ? await promptLine2("Enter 2FA code: ") : void 0;
|
|
50643
52817
|
const result = await client.deleteAccountWithCliVerification(totpCode);
|
|
50644
52818
|
if (flags.json === true) printJson2(result);
|
|
50645
52819
|
else console.log("\x1B[32m\u2713\x1B[0m Account deletion requested and local CLI session cleared");
|
|
@@ -51358,11 +53532,11 @@ function ansiMateBlock(category, mateName) {
|
|
|
51358
53532
|
const [r, g, b] = CATEGORY_ANSI_COLORS[category];
|
|
51359
53533
|
return `\x1B[48;2;${r};${g};${b}m\x1B[97m ${label} \x1B[0m`;
|
|
51360
53534
|
}
|
|
51361
|
-
function
|
|
53535
|
+
function formatTimestamp2(ts) {
|
|
51362
53536
|
if (!ts) return "\u2014";
|
|
51363
53537
|
const d = new Date(ts * 1e3);
|
|
51364
|
-
const
|
|
51365
|
-
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())}`;
|
|
51366
53540
|
}
|
|
51367
53541
|
function printChatsTable(result) {
|
|
51368
53542
|
const { chats, total, page, limit, hasMore } = result;
|
|
@@ -51381,7 +53555,7 @@ function printChatsTable(result) {
|
|
|
51381
53555
|
);
|
|
51382
53556
|
for (const chat of chats) {
|
|
51383
53557
|
const block = ansiColorBlock(chat.category);
|
|
51384
|
-
const time =
|
|
53558
|
+
const time = formatTimestamp2(chat.updatedAt);
|
|
51385
53559
|
const title = chat.title ?? "(no title)";
|
|
51386
53560
|
const idStr = chat.shortId;
|
|
51387
53561
|
const sourceLabel = chat.source === "example" ? " \x1B[33mEXAMPLE CHAT\x1B[0m" : "";
|
|
@@ -51942,7 +54116,7 @@ async function renderMessageText(text, embedRefIndex, client) {
|
|
|
51942
54116
|
async function printChatConversation(client, chat, messages, followUpSuggestions, options = {}) {
|
|
51943
54117
|
const block = ansiColorBlock(chat.category);
|
|
51944
54118
|
const title = chat.title ?? "(no title)";
|
|
51945
|
-
const ts =
|
|
54119
|
+
const ts = formatTimestamp2(chat.updatedAt);
|
|
51946
54120
|
if (options.example) {
|
|
51947
54121
|
process.stdout.write("\x1B[33mEXAMPLE CHAT\x1B[0m\n");
|
|
51948
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");
|
|
@@ -51972,7 +54146,7 @@ async function printChatConversation(client, chat, messages, followUpSuggestions
|
|
|
51972
54146
|
const embedRefIndex = hasEmbedRefs ? await client.buildEmbedRefIndex(parentEmbedIds) : /* @__PURE__ */ new Map();
|
|
51973
54147
|
for (let i = 0; i < messages.length; i++) {
|
|
51974
54148
|
const msg = messages[i];
|
|
51975
|
-
const msgTs =
|
|
54149
|
+
const msgTs = formatTimestamp2(msg.createdAt);
|
|
51976
54150
|
process.stdout.write(`${SEP}
|
|
51977
54151
|
`);
|
|
51978
54152
|
if (msg.role === "user") {
|
|
@@ -52033,7 +54207,7 @@ async function printChatConversation(client, chat, messages, followUpSuggestions
|
|
|
52033
54207
|
}
|
|
52034
54208
|
async function printChatConversationRaw(chat, messages, options = {}) {
|
|
52035
54209
|
const title = chat.title ?? "(no title)";
|
|
52036
|
-
const ts =
|
|
54210
|
+
const ts = formatTimestamp2(chat.updatedAt);
|
|
52037
54211
|
if (options.example) {
|
|
52038
54212
|
process.stdout.write("EXAMPLE CHAT\n");
|
|
52039
54213
|
process.stdout.write("This is a public example chat. Log in to create and sync your own private chats.\n\n");
|
|
@@ -52048,7 +54222,7 @@ async function printChatConversationRaw(chat, messages, options = {}) {
|
|
|
52048
54222
|
return;
|
|
52049
54223
|
}
|
|
52050
54224
|
for (const msg of messages) {
|
|
52051
|
-
const msgTs =
|
|
54225
|
+
const msgTs = formatTimestamp2(msg.createdAt);
|
|
52052
54226
|
process.stdout.write(`${SEP}
|
|
52053
54227
|
`);
|
|
52054
54228
|
const role = msg.role === "user" ? "You" : msg.senderName ?? msg.role;
|
|
@@ -52551,18 +54725,18 @@ function printWhoAmI(user) {
|
|
|
52551
54725
|
}
|
|
52552
54726
|
}
|
|
52553
54727
|
function printGenericObject(value, indent = 0) {
|
|
52554
|
-
const
|
|
54728
|
+
const pad2 = " ".repeat(indent);
|
|
52555
54729
|
if (value === null || value === void 0) {
|
|
52556
|
-
console.log(`${
|
|
54730
|
+
console.log(`${pad2}(empty)`);
|
|
52557
54731
|
return;
|
|
52558
54732
|
}
|
|
52559
54733
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
52560
|
-
console.log(`${
|
|
54734
|
+
console.log(`${pad2}${value}`);
|
|
52561
54735
|
return;
|
|
52562
54736
|
}
|
|
52563
54737
|
if (Array.isArray(value)) {
|
|
52564
54738
|
if (value.length === 0) {
|
|
52565
|
-
console.log(`${
|
|
54739
|
+
console.log(`${pad2}(empty list)`);
|
|
52566
54740
|
return;
|
|
52567
54741
|
}
|
|
52568
54742
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -52570,11 +54744,11 @@ function printGenericObject(value, indent = 0) {
|
|
|
52570
54744
|
if (item && typeof item === "object") {
|
|
52571
54745
|
printGenericObject(item, indent);
|
|
52572
54746
|
if (i < value.length - 1) {
|
|
52573
|
-
process.stdout.write(`${
|
|
54747
|
+
process.stdout.write(`${pad2}\x1B[2m${"\u2500".repeat(40)}\x1B[0m
|
|
52574
54748
|
`);
|
|
52575
54749
|
}
|
|
52576
54750
|
} else {
|
|
52577
|
-
console.log(`${
|
|
54751
|
+
console.log(`${pad2}${String(item)}`);
|
|
52578
54752
|
}
|
|
52579
54753
|
}
|
|
52580
54754
|
return;
|
|
@@ -52594,34 +54768,34 @@ function printGenericObject(value, indent = 0) {
|
|
|
52594
54768
|
if (Array.isArray(v)) {
|
|
52595
54769
|
if (v.length === 0) {
|
|
52596
54770
|
process.stdout.write(
|
|
52597
|
-
`${
|
|
54771
|
+
`${pad2} \x1B[2m${k.padEnd(20)}\x1B[0m (none)
|
|
52598
54772
|
`
|
|
52599
54773
|
);
|
|
52600
54774
|
} else if (v.length > 0 && typeof v[0] === "object") {
|
|
52601
54775
|
process.stdout.write(
|
|
52602
54776
|
`
|
|
52603
|
-
${
|
|
54777
|
+
${pad2} \x1B[1m${k}\x1B[0m \x1B[2m(${v.length})\x1B[0m
|
|
52604
54778
|
`
|
|
52605
54779
|
);
|
|
52606
54780
|
printGenericObject(v, indent + 1);
|
|
52607
54781
|
} else {
|
|
52608
54782
|
process.stdout.write(
|
|
52609
|
-
`${
|
|
54783
|
+
`${pad2} \x1B[2m${k.padEnd(20)}\x1B[0m ${v.join(", ")}
|
|
52610
54784
|
`
|
|
52611
54785
|
);
|
|
52612
54786
|
}
|
|
52613
54787
|
} else if (typeof v === "object") {
|
|
52614
|
-
process.stdout.write(`${
|
|
54788
|
+
process.stdout.write(`${pad2} \x1B[1m${k}\x1B[0m
|
|
52615
54789
|
`);
|
|
52616
54790
|
printGenericObject(v, indent + 1);
|
|
52617
54791
|
} else {
|
|
52618
|
-
process.stdout.write(`${
|
|
54792
|
+
process.stdout.write(`${pad2} \x1B[2m${k.padEnd(20)}\x1B[0m ${v}
|
|
52619
54793
|
`);
|
|
52620
54794
|
}
|
|
52621
54795
|
}
|
|
52622
54796
|
return;
|
|
52623
54797
|
}
|
|
52624
|
-
console.log(`${
|
|
54798
|
+
console.log(`${pad2}${String(value)}`);
|
|
52625
54799
|
}
|
|
52626
54800
|
function printBillingResponse(obj) {
|
|
52627
54801
|
header("Billing\n");
|
|
@@ -52697,7 +54871,7 @@ function printMemoriesList(memories) {
|
|
|
52697
54871
|
header(`Memories (${memories.length})
|
|
52698
54872
|
`);
|
|
52699
54873
|
for (const mem of memories) {
|
|
52700
|
-
const ts =
|
|
54874
|
+
const ts = formatTimestamp2(mem.updated_at);
|
|
52701
54875
|
process.stdout.write(
|
|
52702
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
|
|
52703
54877
|
`
|
|
@@ -52947,46 +55121,46 @@ async function handleMentions(client, subcommand, _rest, flags) {
|
|
|
52947
55121
|
process.exit(1);
|
|
52948
55122
|
}
|
|
52949
55123
|
function serializeToYaml(data, indent = 0) {
|
|
52950
|
-
const
|
|
55124
|
+
const pad2 = " ".repeat(indent);
|
|
52951
55125
|
let out = "";
|
|
52952
55126
|
for (const [key, val] of Object.entries(data)) {
|
|
52953
55127
|
if (val === null || val === void 0) {
|
|
52954
|
-
out += `${
|
|
55128
|
+
out += `${pad2}${key}: null
|
|
52955
55129
|
`;
|
|
52956
55130
|
} else if (typeof val === "boolean" || typeof val === "number") {
|
|
52957
|
-
out += `${
|
|
55131
|
+
out += `${pad2}${key}: ${val}
|
|
52958
55132
|
`;
|
|
52959
55133
|
} else if (typeof val === "string") {
|
|
52960
55134
|
if (val.includes("\n")) {
|
|
52961
|
-
out += `${
|
|
55135
|
+
out += `${pad2}${key}: |
|
|
52962
55136
|
`;
|
|
52963
55137
|
for (const line of val.split("\n")) {
|
|
52964
|
-
out += `${
|
|
55138
|
+
out += `${pad2} ${line}
|
|
52965
55139
|
`;
|
|
52966
55140
|
}
|
|
52967
55141
|
} else {
|
|
52968
55142
|
const needsQuote = val.includes(":") || val.includes("#") || val.startsWith("{") || val.startsWith("[") || val.startsWith("'") || val.startsWith('"') || val === "" || val === "true" || val === "false" || val === "null";
|
|
52969
|
-
out += `${
|
|
55143
|
+
out += `${pad2}${key}: ${needsQuote ? `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : val}
|
|
52970
55144
|
`;
|
|
52971
55145
|
}
|
|
52972
55146
|
} else if (Array.isArray(val)) {
|
|
52973
|
-
out += `${
|
|
55147
|
+
out += `${pad2}${key}:
|
|
52974
55148
|
`;
|
|
52975
55149
|
for (const item of val) {
|
|
52976
55150
|
if (typeof item === "object" && item !== null) {
|
|
52977
|
-
out += `${
|
|
55151
|
+
out += `${pad2}-
|
|
52978
55152
|
`;
|
|
52979
55153
|
out += serializeToYaml(
|
|
52980
55154
|
item,
|
|
52981
55155
|
indent + 2
|
|
52982
55156
|
);
|
|
52983
55157
|
} else {
|
|
52984
|
-
out += `${
|
|
55158
|
+
out += `${pad2}- ${item}
|
|
52985
55159
|
`;
|
|
52986
55160
|
}
|
|
52987
55161
|
}
|
|
52988
55162
|
} else if (typeof val === "object") {
|
|
52989
|
-
out += `${
|
|
55163
|
+
out += `${pad2}${key}:
|
|
52990
55164
|
`;
|
|
52991
55165
|
out += serializeToYaml(val, indent + 1);
|
|
52992
55166
|
}
|
|
@@ -53074,6 +55248,8 @@ Commands:
|
|
|
53074
55248
|
openmates logout Log out and clear session
|
|
53075
55249
|
openmates whoami [--json] Show account info
|
|
53076
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
|
|
53077
55253
|
openmates apps [--help] App skill commands (list, run, ...)
|
|
53078
55254
|
openmates workflows [--help] Server-side workflow commands
|
|
53079
55255
|
openmates mentions [--help] List available @mentions
|
|
@@ -53212,7 +55388,7 @@ Creates local ignored credential artifacts for reserved E2E auth accounts. The
|
|
|
53212
55388
|
command refuses production API URLs and does not upload GitHub secrets.
|
|
53213
55389
|
|
|
53214
55390
|
Options:
|
|
53215
|
-
--slot <
|
|
55391
|
+
--slot <14-20> Reserved auth-account slot
|
|
53216
55392
|
--artifact <path> Output .env artifact path
|
|
53217
55393
|
--email <email> Test email; prompted/generated when omitted
|
|
53218
55394
|
--domain <mail-domain> Generate email at this domain
|
|
@@ -53326,6 +55502,46 @@ Examples:
|
|
|
53326
55502
|
openmates chats share last --expires 604800
|
|
53327
55503
|
openmates chats share d262cb68 --password mypass`);
|
|
53328
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
|
+
}
|
|
53329
55545
|
function printAppsHelp() {
|
|
53330
55546
|
console.log(`Apps commands:
|
|
53331
55547
|
openmates apps list [--json]
|
|
@@ -53334,6 +55550,7 @@ function printAppsHelp() {
|
|
|
53334
55550
|
openmates apps skill-info <app-id> <skill-id> [--json]
|
|
53335
55551
|
openmates apps <app-id> <skill-id> "<query>" [--json]
|
|
53336
55552
|
openmates apps <app-id> <skill-id> --input '<json>' [--json]
|
|
55553
|
+
openmates apps models3d generate --image ./reference.png [--json]
|
|
53337
55554
|
openmates apps code run --language python --code 'print("Hello")'
|
|
53338
55555
|
openmates apps code run --entry main.py --file main.py [--file requirements.txt]
|
|
53339
55556
|
openmates apps code run --entry main.py --dir ./project [--exclude node_modules]
|
|
@@ -53349,6 +55566,7 @@ Examples:
|
|
|
53349
55566
|
openmates apps web search "latest AI news"
|
|
53350
55567
|
openmates apps news search "climate change"
|
|
53351
55568
|
openmates apps ai ask "Summarise this: ..."
|
|
55569
|
+
openmates apps models3d generate --image ./chair.png
|
|
53352
55570
|
openmates apps code run --language python --filename hello.py --code 'print("Hello from CLI")'
|
|
53353
55571
|
openmates apps travel search_connections --input '{"requests":[{"legs":[{"origin":"BER","destination":"LHR","date":"2026-04-15"}]}]}'
|
|
53354
55572
|
openmates apps travel booking-link --token "<booking_token from search result>"
|
|
@@ -53358,6 +55576,9 @@ function printWorkflowsHelp() {
|
|
|
53358
55576
|
console.log(`Workflows commands:
|
|
53359
55577
|
openmates workflows list [--json]
|
|
53360
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]
|
|
53361
55582
|
openmates workflows create --title <title> --graph '<json>' [--enabled] [--run-content-retention last_5|none] [--json]
|
|
53362
55583
|
openmates workflows input <text> [--workflow-id <id>] [--project-id <id>] [--json]
|
|
53363
55584
|
openmates workflows input-show <session-id> [--json]
|
|
@@ -53368,9 +55589,13 @@ function printWorkflowsHelp() {
|
|
|
53368
55589
|
openmates workflows show <workflow-id> [--json]
|
|
53369
55590
|
openmates workflows enable <workflow-id> [--json]
|
|
53370
55591
|
openmates workflows disable <workflow-id> [--json]
|
|
53371
|
-
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]
|
|
53372
55593
|
openmates workflows runs <workflow-id> [--json]
|
|
53373
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]
|
|
53374
55599
|
openmates workflows delete <workflow-id> --yes [--json]
|
|
53375
55600
|
|
|
53376
55601
|
Workflows run on the OpenMates server, not in this terminal process. The CLI
|
|
@@ -53380,6 +55605,7 @@ and Apple clients.
|
|
|
53380
55605
|
Examples:
|
|
53381
55606
|
openmates workflows list
|
|
53382
55607
|
openmates workflows capabilities --json
|
|
55608
|
+
openmates workflows help-app weather.forecast
|
|
53383
55609
|
openmates workflows input "alert me if it rains tomorrow"
|
|
53384
55610
|
openmates workflows run wf_123 --mode test --json`);
|
|
53385
55611
|
}
|
|
@@ -53631,9 +55857,12 @@ if (isCliEntrypoint()) {
|
|
|
53631
55857
|
}
|
|
53632
55858
|
|
|
53633
55859
|
export {
|
|
55860
|
+
deriveChatCompletionRecoveryKeypair,
|
|
55861
|
+
openChatCompletionRecoveryEnvelope,
|
|
53634
55862
|
unwrapApiKeyMasterKey,
|
|
53635
55863
|
decryptWithAesGcmCombined,
|
|
53636
55864
|
decryptBytesWithAesGcm,
|
|
55865
|
+
encryptBytesWithAesGcm,
|
|
53637
55866
|
encryptWithAesGcmCombined,
|
|
53638
55867
|
hashItemKey,
|
|
53639
55868
|
generateChatShareBlob,
|
|
@@ -53646,6 +55875,7 @@ export {
|
|
|
53646
55875
|
INTEREST_TAG_IDS,
|
|
53647
55876
|
normalizeInterestTagIds,
|
|
53648
55877
|
MEMORY_TYPE_REGISTRY,
|
|
55878
|
+
reconcileAuthoritativeChats,
|
|
53649
55879
|
MATE_NAMES,
|
|
53650
55880
|
deriveAppUrl,
|
|
53651
55881
|
OpenMatesClient,
|