openmates 0.11.0-alpha.18 → 0.11.0-alpha.19

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.
@@ -743,6 +743,15 @@ function isSyncCacheFresh(maxAgeMs = 3e5) {
743
743
  import { createRequire } from "module";
744
744
  var require2 = createRequire(import.meta.url);
745
745
  var WebSocket = require2("ws");
746
+ var SUB_CHAT_EVENT_TYPES = /* @__PURE__ */ new Set([
747
+ "spawn_sub_chats",
748
+ "sub_chat_progress",
749
+ "sub_chat_confirmation_required",
750
+ "sub_chat_confirmation_resolved",
751
+ "awaiting_sub_chats_completion",
752
+ "sub_chat_completed",
753
+ "awaiting_user_input"
754
+ ]);
746
755
  var OpenMatesWsClient = class {
747
756
  socket;
748
757
  constructor(options) {
@@ -920,6 +929,8 @@ var OpenMatesWsClient = class {
920
929
  let category = null;
921
930
  let modelName = null;
922
931
  let followUpSuggestions = [];
932
+ const subChatEvents = [];
933
+ const pendingSubChatHandlers = /* @__PURE__ */ new Set();
923
934
  const embeds = /* @__PURE__ */ new Map();
924
935
  const processingEmbedIds = /* @__PURE__ */ new Set();
925
936
  let aiResponseDone = false;
@@ -957,6 +968,7 @@ var OpenMatesWsClient = class {
957
968
  };
958
969
  const maybeResolve = () => {
959
970
  if (!aiResponseDone || !postProcessingDone) return;
971
+ if (pendingSubChatHandlers.size > 0) return;
960
972
  if (processingEmbedIds.size > 0 && !asyncEmbedTimer) {
961
973
  asyncEmbedTimer = setTimeout(() => {
962
974
  cleanup();
@@ -967,7 +979,8 @@ var OpenMatesWsClient = class {
967
979
  category,
968
980
  modelName,
969
981
  followUpSuggestions,
970
- embeds: [...embeds.values()]
982
+ embeds: [...embeds.values()],
983
+ subChatEvents
971
984
  });
972
985
  }, asyncEmbedWaitMs);
973
986
  return;
@@ -981,7 +994,25 @@ var OpenMatesWsClient = class {
981
994
  category,
982
995
  modelName,
983
996
  followUpSuggestions,
984
- embeds: [...embeds.values()]
997
+ embeds: [...embeds.values()],
998
+ subChatEvents
999
+ });
1000
+ };
1001
+ const handleSubChatEvent = (type, p) => {
1002
+ const eventChatId = typeof p.chat_id === "string" ? p.chat_id : typeof p.parent_id === "string" ? p.parent_id : null;
1003
+ if (eventChatId && eventChatId !== chatId) return;
1004
+ const event = { type, payload: p };
1005
+ subChatEvents.push(event);
1006
+ const handler = options?.onSubChatEvent;
1007
+ if (!handler) return;
1008
+ const pending = Promise.resolve(handler(event));
1009
+ pendingSubChatHandlers.add(pending);
1010
+ pending.catch((error) => {
1011
+ cleanup();
1012
+ reject(error instanceof Error ? error : new Error(String(error)));
1013
+ }).finally(() => {
1014
+ pendingSubChatHandlers.delete(pending);
1015
+ maybeResolve();
985
1016
  });
986
1017
  };
987
1018
  const scheduleResolve = (content) => {
@@ -1004,6 +1035,10 @@ var OpenMatesWsClient = class {
1004
1035
  );
1005
1036
  return;
1006
1037
  }
1038
+ if (SUB_CHAT_EVENT_TYPES.has(type)) {
1039
+ handleSubChatEvent(type, p);
1040
+ return;
1041
+ }
1007
1042
  if (type === "send_embed_data") {
1008
1043
  const embedPayload = p.payload && typeof p.payload === "object" ? p.payload : p;
1009
1044
  if (typeof embedPayload.chat_id === "string" && embedPayload.chat_id !== chatId) {
@@ -1120,7 +1155,8 @@ var OpenMatesWsClient = class {
1120
1155
  category,
1121
1156
  modelName,
1122
1157
  followUpSuggestions,
1123
- embeds: [...embeds.values()]
1158
+ embeds: [...embeds.values()],
1159
+ subChatEvents
1124
1160
  });
1125
1161
  return;
1126
1162
  }
@@ -1698,6 +1734,42 @@ function normalizeUnixSeconds(value, fallback) {
1698
1734
  }
1699
1735
  return value > 1e10 ? Math.floor(value / 1e3) : Math.floor(value);
1700
1736
  }
1737
+ function buildSubChatConfirmationPayload(params) {
1738
+ return {
1739
+ chat_id: params.chatId,
1740
+ task_id: params.taskId,
1741
+ action: params.approved ? "approve" : "cancel",
1742
+ approve_count: params.approved ? params.approveCount ?? null : null
1743
+ };
1744
+ }
1745
+ async function buildSubChatEncryptedMetadataPayloads(params) {
1746
+ const createdAt = params.createdAt ?? Math.floor(Date.now() / 1e3);
1747
+ const payloads = [];
1748
+ for (const subChat of params.subChats) {
1749
+ const chatId = subChat.id || subChat.chat_id;
1750
+ const messageId = subChat.user_message_id || subChat.message_id;
1751
+ const prompt = subChat.prompt || "";
1752
+ if (!chatId || !messageId) continue;
1753
+ const title = prompt.substring(0, 30);
1754
+ payloads.push({
1755
+ chat_id: chatId,
1756
+ parent_id: params.parentChatId,
1757
+ is_sub_chat: true,
1758
+ message_id: messageId,
1759
+ encrypted_content: await encryptWithAesGcmCombined(prompt, params.parentChatKey),
1760
+ encrypted_sender_name: await encryptWithAesGcmCombined("User", params.parentChatKey),
1761
+ encrypted_title: await encryptWithAesGcmCombined(title, params.parentChatKey),
1762
+ created_at: createdAt,
1763
+ encrypted_chat_key: params.encryptedParentChatKey,
1764
+ versions: {
1765
+ messages_v: 1,
1766
+ title_v: 0,
1767
+ last_edited_overall_timestamp: createdAt
1768
+ }
1769
+ });
1770
+ }
1771
+ return payloads;
1772
+ }
1701
1773
  var MEMORY_TYPE_REGISTRY = {
1702
1774
  "ai/communication_style": {
1703
1775
  appId: "ai",
@@ -2070,8 +2142,8 @@ var OpenMatesClient = class _OpenMatesClient {
2070
2142
  session;
2071
2143
  http;
2072
2144
  constructor(options = {}) {
2073
- this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
2074
2145
  const diskSession = options.session ?? this.getValidSessionFromDisk();
2146
+ this.apiUrl = (options.apiUrl ?? process.env.OPENMATES_API_URL ?? diskSession?.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
2075
2147
  this.session = diskSession;
2076
2148
  this.http = new OpenMatesHttpClient({
2077
2149
  apiUrl: this.apiUrl,
@@ -2173,6 +2245,11 @@ var OpenMatesClient = class _OpenMatesClient {
2173
2245
  if (!response.ok || !response.data.success) {
2174
2246
  throw new Error("Session is invalid. Please run `openmates login`.");
2175
2247
  }
2248
+ if (response.data.ws_token) {
2249
+ session.wsToken = response.data.ws_token;
2250
+ }
2251
+ session.cookies = this.http.getCookieMap();
2252
+ saveSession(session);
2176
2253
  return response.data.user ?? {};
2177
2254
  }
2178
2255
  async logout() {
@@ -2745,7 +2822,71 @@ var OpenMatesClient = class _OpenMatesClient {
2745
2822
  let category = null;
2746
2823
  let modelName = null;
2747
2824
  let followUpSuggestions = [];
2748
- const streamOpts = { onStream: params.onStream };
2825
+ let subChatEvents = [];
2826
+ const numberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
2827
+ const isApprovalWithinServerLimits = (request) => {
2828
+ const count = request.subChats.length;
2829
+ if (count === 0) return false;
2830
+ if (request.remainingSubChats !== null && count > request.remainingSubChats) {
2831
+ return false;
2832
+ }
2833
+ if (request.maxDirectSubChats !== null && request.existingSubChats !== null && request.existingSubChats + count > request.maxDirectSubChats) {
2834
+ return false;
2835
+ }
2836
+ return true;
2837
+ };
2838
+ const isAutoApprovalWithinServerLimits = (request) => {
2839
+ if (!isApprovalWithinServerLimits(request)) return false;
2840
+ return request.maxAutoSubChats === null || request.subChats.length <= request.maxAutoSubChats;
2841
+ };
2842
+ const handleSubChatEvent = async (event) => {
2843
+ params.onSubChatEvent?.(event);
2844
+ if (event.type === "spawn_sub_chats") {
2845
+ if (params.incognito || !chatKeyBytes) return;
2846
+ const rawSubChats = event.payload.sub_chats;
2847
+ if (!Array.isArray(rawSubChats)) return;
2848
+ const metadataPayloads = await buildSubChatEncryptedMetadataPayloads({
2849
+ parentChatId: chatId,
2850
+ parentChatKey: chatKeyBytes,
2851
+ encryptedParentChatKey: encryptedChatKey,
2852
+ subChats: rawSubChats
2853
+ });
2854
+ for (const metadataPayload of metadataPayloads) {
2855
+ await ws.sendAsync("encrypted_chat_metadata", metadataPayload);
2856
+ }
2857
+ return;
2858
+ }
2859
+ if (event.type !== "sub_chat_confirmation_required") return;
2860
+ const payload = event.payload;
2861
+ const confirmationChatId = typeof payload.chat_id === "string" ? payload.chat_id : chatId;
2862
+ const taskId = typeof payload.task_id === "string" ? payload.task_id : null;
2863
+ const subChats = Array.isArray(payload.sub_chats) ? payload.sub_chats : [];
2864
+ if (!taskId) return;
2865
+ const request = {
2866
+ chatId: confirmationChatId,
2867
+ taskId,
2868
+ subChats,
2869
+ maxAutoSubChats: numberOrNull(payload.max_auto_sub_chats),
2870
+ maxDirectSubChats: numberOrNull(payload.max_direct_sub_chats),
2871
+ existingSubChats: numberOrNull(payload.existing_sub_chats),
2872
+ remainingSubChats: numberOrNull(payload.remaining_sub_chats)
2873
+ };
2874
+ const withinLimits = isApprovalWithinServerLimits(request);
2875
+ const approved = params.autoApproveSubChats ? isAutoApprovalWithinServerLimits(request) : withinLimits && params.onSubChatApprovalRequest ? await params.onSubChatApprovalRequest(request) : false;
2876
+ await ws.sendAsync(
2877
+ "sub_chat_confirmation",
2878
+ buildSubChatConfirmationPayload({
2879
+ chatId: request.chatId,
2880
+ taskId: request.taskId,
2881
+ approved,
2882
+ approveCount: approved ? request.subChats.length : void 0
2883
+ })
2884
+ );
2885
+ };
2886
+ const streamOpts = {
2887
+ onStream: params.onStream,
2888
+ onSubChatEvent: handleSubChatEvent
2889
+ };
2749
2890
  if (params.incognito) {
2750
2891
  try {
2751
2892
  const resp = await ws.collectAiResponse(messageId, chatId, streamOpts);
@@ -2753,6 +2894,7 @@ var OpenMatesClient = class _OpenMatesClient {
2753
2894
  assistant = resp.content;
2754
2895
  category = resp.category;
2755
2896
  modelName = resp.modelName;
2897
+ subChatEvents = resp.subChatEvents;
2756
2898
  } finally {
2757
2899
  ws.close();
2758
2900
  }
@@ -2764,6 +2906,7 @@ var OpenMatesClient = class _OpenMatesClient {
2764
2906
  category = resp.category;
2765
2907
  modelName = resp.modelName;
2766
2908
  followUpSuggestions = resp.followUpSuggestions;
2909
+ subChatEvents = resp.subChatEvents;
2767
2910
  if (chatKeyBytes && assistant) {
2768
2911
  const completedAt = Math.floor(Date.now() / 1e3);
2769
2912
  const encryptedAssistantContent = await encryptWithAesGcmCombined(
@@ -2819,7 +2962,8 @@ var OpenMatesClient = class _OpenMatesClient {
2819
2962
  category,
2820
2963
  modelName,
2821
2964
  mateName,
2822
- followUpSuggestions
2965
+ followUpSuggestions,
2966
+ subChatEvents
2823
2967
  };
2824
2968
  }
2825
2969
  async persistStreamedEmbeds(params) {
@@ -4343,6 +4487,9 @@ function printLogo() {
4343
4487
  stdout.write(lines.join("\n") + "\n");
4344
4488
  }
4345
4489
 
4490
+ // src/cli.ts
4491
+ import { createInterface as createInterface3 } from "readline/promises";
4492
+
4346
4493
  // ../secret-scanner/src/registry.ts
4347
4494
  import { createRequire as createRequire2 } from "module";
4348
4495
 
@@ -7596,7 +7743,8 @@ async function handleChats(client, subcommand, rest, flags, redactor) {
7596
7743
  message,
7597
7744
  chatId: void 0,
7598
7745
  incognito: false,
7599
- json: flags.json === true
7746
+ json: flags.json === true,
7747
+ autoApproveSubChats: flags["auto-approve"] === true
7600
7748
  },
7601
7749
  redactor
7602
7750
  );
@@ -7667,7 +7815,8 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
7667
7815
  message,
7668
7816
  chatId,
7669
7817
  incognito: flags.incognito === true,
7670
- json: flags.json === true
7818
+ json: flags.json === true,
7819
+ autoApproveSubChats: flags["auto-approve"] === true
7671
7820
  },
7672
7821
  redactor
7673
7822
  );
@@ -7685,7 +7834,8 @@ Run 'openmates chats show ` + chatId + "' to check if suggestions have been save
7685
7834
  {
7686
7835
  message,
7687
7836
  incognito: true,
7688
- json: flags.json === true
7837
+ json: flags.json === true,
7838
+ autoApproveSubChats: flags["auto-approve"] === true
7689
7839
  },
7690
7840
  redactor
7691
7841
  );
@@ -9562,6 +9712,56 @@ async function sendMessageStreaming(client, params, redactor) {
9562
9712
  processedRawLength = raw.length;
9563
9713
  }
9564
9714
  };
9715
+ const onSubChatEvent = (event) => {
9716
+ if (params.json) return;
9717
+ clearTyping();
9718
+ const payload = event.payload;
9719
+ if (event.type === "spawn_sub_chats") {
9720
+ const count = Array.isArray(payload.sub_chats) ? payload.sub_chats.length : 0;
9721
+ process.stderr.write(
9722
+ `\x1B[2mStarting ${count} sub-chat${count === 1 ? "" : "s"} for parallel research...\x1B[0m
9723
+ `
9724
+ );
9725
+ return;
9726
+ }
9727
+ if (event.type === "sub_chat_progress") {
9728
+ const completed = typeof payload.completed === "number" ? payload.completed : null;
9729
+ const total = typeof payload.total === "number" ? payload.total : null;
9730
+ const status = typeof payload.status === "string" ? payload.status : "running";
9731
+ const progress = completed !== null && total !== null ? `${completed}/${total}` : status;
9732
+ process.stderr.write(`\x1B[2mSub-chats: ${progress} ${status}\x1B[0m
9733
+ `);
9734
+ return;
9735
+ }
9736
+ if (event.type === "sub_chat_confirmation_resolved") {
9737
+ const status = typeof payload.status === "string" ? payload.status : "resolved";
9738
+ process.stderr.write(`\x1B[2mSub-chat approval ${status}.\x1B[0m
9739
+ `);
9740
+ return;
9741
+ }
9742
+ if (event.type === "awaiting_user_input") {
9743
+ process.stderr.write(
9744
+ "\x1B[33mA sub-chat needs additional user input. Continue this chat in the web app if the parent cannot finish.\x1B[0m\n"
9745
+ );
9746
+ }
9747
+ };
9748
+ const onSubChatApprovalRequest = async (request) => {
9749
+ if (params.autoApproveSubChats) return true;
9750
+ clearTyping();
9751
+ const count = request.subChats.length;
9752
+ const remaining = request.remainingSubChats === null ? "" : ` (${request.remainingSubChats} remaining for this parent chat)`;
9753
+ process.stderr.write(
9754
+ `\x1B[33mDeep research wants to start ${count} additional sub-chat${count === 1 ? "" : "s"}${remaining}.\x1B[0m
9755
+ `
9756
+ );
9757
+ const rl = createInterface3({ input: process.stdin, output: process.stderr });
9758
+ try {
9759
+ const answer = await rl.question("Approve? [y/N] ");
9760
+ return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
9761
+ } finally {
9762
+ rl.close();
9763
+ }
9764
+ };
9565
9765
  const preparedEmbeds = [];
9566
9766
  let finalMessage = params.message;
9567
9767
  if (params.message.includes("@")) {
@@ -9733,6 +9933,9 @@ async function sendMessageStreaming(client, params, redactor) {
9733
9933
  chatId: params.chatId,
9734
9934
  incognito: params.incognito,
9735
9935
  onStream,
9936
+ onSubChatEvent,
9937
+ onSubChatApprovalRequest,
9938
+ autoApproveSubChats: params.autoApproveSubChats,
9736
9939
  preparedEmbeds: preparedEmbeds.length > 0 ? preparedEmbeds : void 0
9737
9940
  });
9738
9941
  clearTyping();
@@ -10958,9 +11161,9 @@ function printChatsHelp() {
10958
11161
  openmates chats show <chat-id> [--raw] [--json]
10959
11162
  openmates chats open [<n>] [--json]
10960
11163
  openmates chats search <query> [--json]
10961
- openmates chats new <message> [--json]
10962
- openmates chats send [--chat <id>] [--incognito] <message> [--json]
10963
- openmates chats send --chat <id> --followup <n> [--json]
11164
+ openmates chats new <message> [--json] [--auto-approve]
11165
+ openmates chats send [--chat <id>] [--incognito] <message> [--json] [--auto-approve]
11166
+ openmates chats send --chat <id> --followup <n> [--json] [--auto-approve]
10964
11167
  openmates chats download <chat-id> [--output <path>] [--zip] [--json]
10965
11168
  openmates chats delete <id1> [id2] [id3] ... [--yes]
10966
11169
  openmates chats share [<chat-id>] [--expires <seconds>] [--password <pwd>] [--json]
@@ -10987,6 +11190,10 @@ Options for 'send':
10987
11190
  typing the full message (requires --chat)
10988
11191
  --incognito Send without saving to chat history
10989
11192
 
11193
+ Options for 'new', 'send', and 'incognito':
11194
+ --auto-approve Automatically approve server-requested sub-chat batches.
11195
+ Without this, the CLI prompts in the terminal like the web app.
11196
+
10990
11197
  Options for 'download':
10991
11198
  --output <path> Target directory (default: current directory)
10992
11199
  --zip Create a .zip archive instead of a folder
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getExtForLang,
4
4
  serializeToYaml
5
- } from "./chunk-IJXZKFPX.js";
5
+ } from "./chunk-S6RDNSHY.js";
6
6
  import "./chunk-AXNRPVLE.js";
7
7
  export {
8
8
  getExtForLang,
package/dist/index.d.ts CHANGED
@@ -66,6 +66,11 @@ interface StreamEvent {
66
66
  /** Human-readable model name. */
67
67
  modelName: string | null;
68
68
  }
69
+ type SubChatEventType = "spawn_sub_chats" | "sub_chat_progress" | "sub_chat_confirmation_required" | "sub_chat_confirmation_resolved" | "awaiting_sub_chats_completion" | "sub_chat_completed" | "awaiting_user_input";
70
+ interface SubChatEvent {
71
+ type: SubChatEventType;
72
+ payload: Record<string, unknown>;
73
+ }
69
74
 
70
75
  /** Minimal model info needed for mention resolution */
71
76
  interface ModelInfo {
@@ -182,6 +187,23 @@ interface EmbedKeyWrapper {
182
187
  */
183
188
  type ShareDuration = 0 | 60 | 3600 | 86400 | 604800 | 1209600 | 2592000 | 7776000;
184
189
 
190
+ interface CliSubChatRequest {
191
+ id?: string;
192
+ chat_id?: string;
193
+ user_message_id?: string;
194
+ message_id?: string;
195
+ prompt?: string;
196
+ wait_for_completion?: boolean;
197
+ }
198
+ interface SubChatApprovalRequest {
199
+ chatId: string;
200
+ taskId: string;
201
+ subChats: CliSubChatRequest[];
202
+ maxAutoSubChats: number | null;
203
+ maxDirectSubChats: number | null;
204
+ existingSubChats: number | null;
205
+ remainingSubChats: number | null;
206
+ }
185
207
  /** A single field definition within a memory type schema. */
186
208
  interface MemoryFieldDef {
187
209
  type: string;
@@ -451,6 +473,12 @@ declare class OpenMatesClient {
451
473
  incognito?: boolean;
452
474
  /** Streaming callback — fires for typing, chunk, and done events. */
453
475
  onStream?: (event: StreamEvent) => void;
476
+ /** Sub-chat lifecycle callback for progress/status output. */
477
+ onSubChatEvent?: (event: SubChatEvent) => void;
478
+ /** Approval callback used when the server asks before starting a large sub-chat batch. */
479
+ onSubChatApprovalRequest?: (request: SubChatApprovalRequest) => boolean | Promise<boolean>;
480
+ /** Explicit opt-in for automatic sub-chat approval in non-interactive runs. */
481
+ autoApproveSubChats?: boolean;
454
482
  /** Encrypted file embeds to attach to the message (code, images, PDFs). */
455
483
  encryptedEmbeds?: EncryptedEmbed[];
456
484
  /** Prepared embeds to encrypt after the real chat/message IDs are known. */
@@ -463,6 +491,8 @@ declare class OpenMatesClient {
463
491
  mateName: string | null;
464
492
  /** Follow-up suggestions from post-processing (may be empty for incognito chats). */
465
493
  followUpSuggestions: string[];
494
+ /** Sub-chat lifecycle frames observed while collecting the parent response. */
495
+ subChatEvents: SubChatEvent[];
466
496
  }>;
467
497
  private persistStreamedEmbeds;
468
498
  /**
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  getExtForLang,
8
8
  parseNewChatSuggestionText,
9
9
  serializeToYaml
10
- } from "./chunk-IJXZKFPX.js";
10
+ } from "./chunk-S6RDNSHY.js";
11
11
  import "./chunk-AXNRPVLE.js";
12
12
  export {
13
13
  MATE_NAMES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmates",
3
- "version": "0.11.0-alpha.18",
3
+ "version": "0.11.0-alpha.19",
4
4
  "description": "OpenMates CLI and SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",