openmates 0.15.0-alpha.22 → 0.15.0-alpha.23

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.
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-AXNRPVLE.js";
5
5
 
6
6
  // src/client.ts
7
- import { randomUUID as randomUUID5 } from "crypto";
7
+ import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
8
8
  import { arch, platform as platform2, release } from "os";
9
9
  import { createInterface as createInterface2 } from "readline/promises";
10
10
  import { stdin as stdin2, stdout } from "process";
@@ -3540,9 +3540,11 @@ function stripTimer(job) {
3540
3540
  }
3541
3541
 
3542
3542
  // src/tasksCli.ts
3543
- import { createHash as createHash5, randomBytes as randomBytes2, randomUUID as randomUUID4 } from "crypto";
3543
+ import { createHash as createHash5, createHmac, hkdfSync, randomBytes as randomBytes2, randomUUID as randomUUID4 } from "crypto";
3544
3544
  var TASK_STATUSES2 = ["backlog", "todo", "in_progress", "blocked", "done"];
3545
3545
  var DEFAULT_STANDALONE_PREFIX = "TASK";
3546
+ var PRIORITY_LEVELS = ["none", "low", "medium", "high", "urgent"];
3547
+ var LABEL_INDEX_INFO = "openmates-task-label-index-v1";
3546
3548
  function normalizeTaskStatus(value) {
3547
3549
  if (value === void 0) return void 0;
3548
3550
  if (TASK_STATUSES2.includes(value)) return value;
@@ -3566,12 +3568,43 @@ function parseDueAt(value) {
3566
3568
  if (Number.isNaN(parsed)) throw new Error(`Invalid --due value '${value}'.`);
3567
3569
  return Math.floor(parsed / 1e3);
3568
3570
  }
3571
+ function normalizeLabels(labels) {
3572
+ const output = [];
3573
+ const seen = /* @__PURE__ */ new Set();
3574
+ for (const label of labels ?? []) {
3575
+ const normalized = label.trim().toLowerCase().replace(/\s+/g, " ");
3576
+ if (!normalized || seen.has(normalized)) continue;
3577
+ seen.add(normalized);
3578
+ output.push(normalized);
3579
+ }
3580
+ return output;
3581
+ }
3582
+ function labelHashes(masterKey, labels) {
3583
+ const indexKey = Buffer.from(hkdfSync("sha256", Buffer.from(masterKey), Buffer.alloc(0), LABEL_INDEX_INFO, 32));
3584
+ return normalizeLabels(labels).map((label) => createHmac("sha256", indexKey).update(label).digest("hex"));
3585
+ }
3586
+ function normalizeTaskPriority(value) {
3587
+ if (value === void 0) return void 0;
3588
+ if (value === null) return 0;
3589
+ if (typeof value === "number") {
3590
+ if (!Number.isInteger(value) || value < 0 || value > 4) throw new Error(`Invalid task priority '${value}'.`);
3591
+ return value;
3592
+ }
3593
+ const normalized = value.trim().toLowerCase();
3594
+ const index = PRIORITY_LEVELS.indexOf(normalized);
3595
+ if (index < 0) throw new Error(`Unknown task priority '${value}'. Expected one of: ${PRIORITY_LEVELS.join(", ")}`);
3596
+ return index;
3597
+ }
3598
+ function taskPriorityLevel(priority) {
3599
+ return PRIORITY_LEVELS[Math.max(0, Math.min(4, Math.trunc(priority ?? 0)))] ?? "none";
3600
+ }
3569
3601
  async function buildCreateUserTaskInput(masterKey, input) {
3570
3602
  const taskKey = randomBytes2(32);
3571
3603
  const encryptedTaskKey = await encryptBytesWithAesGcm(taskKey, masterKey);
3572
3604
  const timestamp = nowSeconds();
3573
3605
  const assignee = parseAssignee(input.assign);
3574
3606
  const linkedProjectIds = input.projectIds ?? [];
3607
+ const labels = normalizeLabels(input.labels ?? input.tags ?? []);
3575
3608
  const status = input.status ?? (assignee.assigneeType === "ai" && !input.dueAt ? "in_progress" : "todo");
3576
3609
  return {
3577
3610
  task_id: randomUUIDCompat(),
@@ -3580,7 +3613,9 @@ async function buildCreateUserTaskInput(masterKey, input) {
3580
3613
  encrypted_task_key: encryptedTaskKey,
3581
3614
  encrypted_title: await encryptWithAesGcmCombined(input.title, taskKey),
3582
3615
  encrypted_description: await encryptWithAesGcmCombined(input.description ?? "", taskKey),
3583
- encrypted_tags: await encryptWithAesGcmCombined("[]", taskKey),
3616
+ encrypted_labels: await encryptWithAesGcmCombined(JSON.stringify(labels), taskKey),
3617
+ encrypted_tags: await encryptWithAesGcmCombined(JSON.stringify(labels), taskKey),
3618
+ label_hashes: labelHashes(masterKey, labels),
3584
3619
  encrypted_linked_project_ids: await encryptWithAesGcmCombined(JSON.stringify(linkedProjectIds), taskKey),
3585
3620
  status,
3586
3621
  assignee_type: assignee.assigneeType,
@@ -3589,7 +3624,7 @@ async function buildCreateUserTaskInput(masterKey, input) {
3589
3624
  linked_project_ids: linkedProjectIds,
3590
3625
  plan_id: input.planId ?? null,
3591
3626
  due_at: input.dueAt ?? null,
3592
- priority: 0,
3627
+ priority: normalizeTaskPriority(input.priority) ?? 0,
3593
3628
  position: timestamp,
3594
3629
  created_at: timestamp,
3595
3630
  updated_at: timestamp
@@ -3612,20 +3647,32 @@ async function buildUpdateUserTaskInput(task, masterKey, input) {
3612
3647
  patch.encrypted_linked_project_ids = await encryptWithAesGcmCombined(JSON.stringify(input.projectIds), taskKey);
3613
3648
  }
3614
3649
  if (input.planId !== void 0) patch.plan_id = input.planId;
3650
+ const priority = normalizeTaskPriority(input.priority);
3651
+ if (priority !== void 0) patch.priority = priority;
3652
+ const replaceLabels = input.labels ?? input.tags;
3653
+ if (replaceLabels !== void 0 || input.addLabels !== void 0 || input.addTags !== void 0 || input.removeLabels !== void 0 || input.removeTags !== void 0) {
3654
+ const remove = new Set(normalizeLabels([...input.removeLabels ?? [], ...input.removeTags ?? []]));
3655
+ const base = replaceLabels !== void 0 ? normalizeLabels(replaceLabels) : task.labels;
3656
+ const labels = normalizeLabels([...base.filter((label) => !remove.has(label)), ...input.addLabels ?? [], ...input.addTags ?? []]);
3657
+ patch.encrypted_labels = await encryptWithAesGcmCombined(JSON.stringify(labels), taskKey);
3658
+ patch.encrypted_tags = await encryptWithAesGcmCombined(JSON.stringify(labels), taskKey);
3659
+ patch.label_hashes = labelHashes(masterKey, labels);
3660
+ }
3615
3661
  return patch;
3616
3662
  }
3617
3663
  async function decryptUserTask(record, masterKey) {
3618
3664
  if (record.source === "workflow_run") return workflowProjectionToTask(record);
3619
3665
  if (typeof record.version !== "number") throw new Error(`Task ${record.task_id} is missing version.`);
3620
3666
  const taskKey = await taskKeyFromRecord(record, masterKey);
3621
- const tags = parseStringArray(await decryptOptional(record.encrypted_tags, taskKey));
3667
+ const labels = parseStringArray(await decryptOptional(record.encrypted_labels || record.encrypted_tags, taskKey));
3622
3668
  const linkedProjectIds = parseStringArray(await decryptOptional(record.encrypted_linked_project_ids, taskKey));
3623
3669
  return {
3624
3670
  taskId: record.task_id,
3625
3671
  shortId: record.short_id || deriveShortId(record),
3626
3672
  title: await decryptOptional(record.encrypted_title, taskKey) || "(untitled task)",
3627
3673
  description: await decryptOptional(record.encrypted_description, taskKey),
3628
- tags,
3674
+ labels,
3675
+ tags: labels,
3629
3676
  latestInstruction: await decryptOptional(record.encrypted_latest_instruction, taskKey),
3630
3677
  status: record.status,
3631
3678
  assigneeType: record.assignee_type,
@@ -3635,6 +3682,7 @@ async function decryptUserTask(record, masterKey) {
3635
3682
  planId: record.plan_id ?? null,
3636
3683
  dueAt: record.due_at ?? null,
3637
3684
  priority: record.priority ?? 0,
3685
+ priorityLevel: taskPriorityLevel(record.priority),
3638
3686
  position: record.position ?? 0,
3639
3687
  queueState: record.queue_state ?? "none",
3640
3688
  blockedReasonCode: record.blocked_reason_code ?? null,
@@ -3651,6 +3699,7 @@ function workflowProjectionToTask(record) {
3651
3699
  shortId: record.short_id || workflowProjectionShortId(record),
3652
3700
  title: record.title || "Workflow run",
3653
3701
  description: record.blocked_message ?? "",
3702
+ labels: [],
3654
3703
  tags: [],
3655
3704
  latestInstruction: "",
3656
3705
  status: record.status,
@@ -3661,6 +3710,7 @@ function workflowProjectionToTask(record) {
3661
3710
  planId: null,
3662
3711
  dueAt: record.due_at ?? null,
3663
3712
  priority: record.priority ?? 0,
3713
+ priorityLevel: taskPriorityLevel(record.priority),
3664
3714
  position: record.position ?? 0,
3665
3715
  queueState: String(record.run_status ?? "workflow"),
3666
3716
  blockedReasonCode: blockedReason,
@@ -3690,9 +3740,9 @@ function findTask(tasks, id) {
3690
3740
  }
3691
3741
  function renderTaskList(tasks) {
3692
3742
  if (tasks.length === 0) return "No tasks found.";
3693
- const lines = ["Tasks", "ID Status Assignee Queue Title"];
3743
+ const lines = ["Tasks", "ID Status Priority Labels Title"];
3694
3744
  for (const task of tasks) {
3695
- lines.push(`${pad(task.shortId, 9)} ${pad(task.status, 12)} ${pad(assigneeLabel(task), 11)} ${pad(task.queueState, 11)} ${task.title}`);
3745
+ lines.push(`${pad(task.shortId, 9)} ${pad(task.status, 12)} ${pad(task.priorityLevel, 9)} ${pad(task.labels.join(","), 19)} ${task.title}`);
3696
3746
  }
3697
3747
  return lines.join("\n");
3698
3748
  }
@@ -3701,11 +3751,13 @@ function renderTaskDetail(task) {
3701
3751
  `Task ${task.shortId}`,
3702
3752
  `Title: ${task.title}`,
3703
3753
  `Status: ${task.status}`,
3754
+ `Priority: ${task.priorityLevel}`,
3704
3755
  `Assignee: ${assigneeLabel(task)}`,
3705
3756
  `Queue: ${task.queueState}`,
3706
3757
  `Task ID: ${task.taskId}`
3707
3758
  ];
3708
3759
  if (task.description) lines.push(`Description: ${task.description}`);
3760
+ if (task.labels.length > 0) lines.push(`Labels: ${task.labels.join(", ")}`);
3709
3761
  if (task.primaryChatId) lines.push(`Chat: ${task.primaryChatId}`);
3710
3762
  if (task.linkedProjectIds.length > 0) lines.push(`Projects: ${task.linkedProjectIds.join(", ")}`);
3711
3763
  if (task.planId) lines.push(`Plan: ${task.planId}`);
@@ -4035,7 +4087,10 @@ function assertTaskPersistPayloadEncrypted(payload) {
4035
4087
  "blocked_reason_code",
4036
4088
  "created_at",
4037
4089
  "key_wrappers",
4090
+ "label_hashes",
4038
4091
  "linked_project_ids",
4092
+ "plan_id",
4093
+ "plan_step_id",
4039
4094
  "position",
4040
4095
  "primary_chat_id",
4041
4096
  "priority",
@@ -4854,7 +4909,7 @@ var OpenMatesClient = class _OpenMatesClient {
4854
4909
  if (!userId) {
4855
4910
  throw new Error("Could not resolve current user id for local connected-account connector.");
4856
4911
  }
4857
- const { createHash: createHash9 } = await import("crypto");
4912
+ const { createHash: createHash10 } = await import("crypto");
4858
4913
  const accountId = randomUUID5();
4859
4914
  const masterKey = this.getMasterKeyBytes();
4860
4915
  const encryptLocalConnectorValue = async (value) => {
@@ -4863,9 +4918,9 @@ var OpenMatesClient = class _OpenMatesClient {
4863
4918
  };
4864
4919
  const payload = {
4865
4920
  id: accountId,
4866
- hashed_user_id: createHash9("sha256").update(userId).digest("hex"),
4921
+ hashed_user_id: createHash10("sha256").update(userId).digest("hex"),
4867
4922
  encrypted_provider_type: await encryptLocalConnectorValue(input.provider_id),
4868
- provider_type_hash: createHash9("sha256").update(input.provider_id).digest("hex"),
4923
+ provider_type_hash: createHash10("sha256").update(input.provider_id).digest("hex"),
4869
4924
  encrypted_account_label: await encryptLocalConnectorValue(input.label),
4870
4925
  encrypted_capabilities: await encryptLocalConnectorValue(input.capabilities),
4871
4926
  encrypted_app_permissions: await encryptLocalConnectorValue({
@@ -5306,14 +5361,26 @@ var OpenMatesClient = class _OpenMatesClient {
5306
5361
  async decryptChatKey(encryptedChatKey, masterKey) {
5307
5362
  return decryptBytesWithAesGcm(encryptedChatKey, masterKey);
5308
5363
  }
5364
+ getMasterWrapperEncryptedChatKey(cache, chatId) {
5365
+ const hashedChatId = createHash6("sha256").update(chatId).digest("hex");
5366
+ const wrapper = (cache?.chatKeyWrappers ?? []).find(
5367
+ (entry) => entry.key_type === "master" && entry.hashed_chat_id === hashedChatId && typeof entry.encrypted_chat_key === "string"
5368
+ );
5369
+ return typeof wrapper?.encrypted_chat_key === "string" ? wrapper.encrypted_chat_key : null;
5370
+ }
5371
+ async resolveChatKey(cache, cached, masterKey) {
5372
+ const chatId = String(cached.details.id ?? "");
5373
+ const wrapperKey = chatId ? this.getMasterWrapperEncryptedChatKey(cache, chatId) : null;
5374
+ const encryptedChatKey = wrapperKey ?? (typeof cached.details.encrypted_chat_key === "string" ? cached.details.encrypted_chat_key : null);
5375
+ return encryptedChatKey ? this.decryptChatKey(encryptedChatKey, masterKey) : null;
5376
+ }
5309
5377
  /**
5310
5378
  * Decrypt a single chat record from the sync cache into a ChatListItem.
5311
5379
  */
5312
- async decryptChatListItem(cached, masterKey) {
5380
+ async decryptChatListItem(cached, masterKey, cache = loadSyncCache()) {
5313
5381
  const d = cached.details;
5314
5382
  const id = String(d.id ?? "");
5315
- const encKey = typeof d.encrypted_chat_key === "string" ? d.encrypted_chat_key : null;
5316
- const chatKeyBytes = encKey ? await this.decryptChatKey(encKey, masterKey) : null;
5383
+ const chatKeyBytes = await this.resolveChatKey(cache, cached, masterKey);
5317
5384
  const title = typeof d.encrypted_title === "string" && chatKeyBytes ? await decryptWithAesGcmCombined(d.encrypted_title, chatKeyBytes) : null;
5318
5385
  const summary = typeof d.encrypted_chat_summary === "string" && chatKeyBytes ? await decryptWithAesGcmCombined(
5319
5386
  d.encrypted_chat_summary,
@@ -5338,7 +5405,7 @@ var OpenMatesClient = class _OpenMatesClient {
5338
5405
  const slice = cache.chats.slice(offset, offset + limit);
5339
5406
  const output = [];
5340
5407
  for (const chat of slice) {
5341
- output.push(await this.decryptChatListItem(chat, masterKey));
5408
+ output.push(await this.decryptChatListItem(chat, masterKey, cache));
5342
5409
  }
5343
5410
  return {
5344
5411
  chats: output,
@@ -5547,7 +5614,7 @@ var OpenMatesClient = class _OpenMatesClient {
5547
5614
  }
5548
5615
  if (!found) {
5549
5616
  for (const cached of cache.chats) {
5550
- const item = await this.decryptChatListItem(cached, masterKey);
5617
+ const item = await this.decryptChatListItem(cached, masterKey, cache);
5551
5618
  const title = (item.title ?? "").toLowerCase();
5552
5619
  if (title === normalized) {
5553
5620
  found = cached;
@@ -5570,9 +5637,8 @@ var OpenMatesClient = class _OpenMatesClient {
5570
5637
  `Chat '${query}' not found. Try 'openmates chats search "${query}"' to browse matches.`
5571
5638
  );
5572
5639
  }
5573
- const chatItem = await this.decryptChatListItem(found, masterKey);
5574
- const encKey = typeof found.details.encrypted_chat_key === "string" ? found.details.encrypted_chat_key : null;
5575
- const chatKeyBytes = encKey ? await this.decryptChatKey(encKey, masterKey) : null;
5640
+ const chatItem = await this.decryptChatListItem(found, masterKey, cache);
5641
+ const chatKeyBytes = await this.resolveChatKey(cache, found, masterKey);
5576
5642
  const messages = [];
5577
5643
  for (const raw of found.messages) {
5578
5644
  const m = typeof raw === "string" ? JSON.parse(raw) : raw;
@@ -5591,8 +5657,8 @@ var OpenMatesClient = class _OpenMatesClient {
5591
5657
  );
5592
5658
  let msgEmbedIds = [];
5593
5659
  if (clientMsgId && cache.embeds.length > 0) {
5594
- const { createHash: createHash9 } = await import("crypto");
5595
- const hashed = createHash9("sha256").update(clientMsgId).digest("hex");
5660
+ const { createHash: createHash10 } = await import("crypto");
5661
+ const hashed = createHash10("sha256").update(clientMsgId).digest("hex");
5596
5662
  msgEmbedIds = cache.embeds.filter(
5597
5663
  (e) => e.hashed_message_id === hashed && // Only include parent embeds (no parent_embed_id).
5598
5664
  // Child embeds inherit the parent's key and are loaded
@@ -5639,8 +5705,8 @@ var OpenMatesClient = class _OpenMatesClient {
5639
5705
  );
5640
5706
  }
5641
5707
  const embedId = String(embed.embed_id ?? embed.id ?? "");
5642
- const { createHash: createHash9 } = await import("crypto");
5643
- const hashedEmbedId = createHash9("sha256").update(embedId).digest("hex");
5708
+ const { createHash: createHash10 } = await import("crypto");
5709
+ const hashedEmbedId = createHash10("sha256").update(embedId).digest("hex");
5644
5710
  const embedKeyBytes = await this.resolveEmbedKey(
5645
5711
  cache,
5646
5712
  masterKey,
@@ -5748,7 +5814,7 @@ var OpenMatesClient = class _OpenMatesClient {
5748
5814
  async resolveEmbedKey(cache, masterKey, embed, embedId, hashedEmbedId, visited = /* @__PURE__ */ new Set()) {
5749
5815
  if (visited.has(embedId)) return null;
5750
5816
  visited.add(embedId);
5751
- const { createHash: createHash9 } = await import("crypto");
5817
+ const { createHash: createHash10 } = await import("crypto");
5752
5818
  const masterKeyEntry = cache.embedKeys.find(
5753
5819
  (ek) => ek.hashed_embed_id === hashedEmbedId && String(ek.key_type) === "master"
5754
5820
  );
@@ -5765,7 +5831,7 @@ var OpenMatesClient = class _OpenMatesClient {
5765
5831
  if (chatKeyEntry && typeof chatKeyEntry.encrypted_embed_key === "string") {
5766
5832
  const hashedChatId = String(chatKeyEntry.hashed_chat_id ?? "");
5767
5833
  const owningChat = cache.chats.find((c) => {
5768
- const chatHash = createHash9("sha256").update(String(c.details.id ?? "")).digest("hex");
5834
+ const chatHash = createHash10("sha256").update(String(c.details.id ?? "")).digest("hex");
5769
5835
  return chatHash === hashedChatId;
5770
5836
  });
5771
5837
  if (owningChat) {
@@ -5794,7 +5860,7 @@ var OpenMatesClient = class _OpenMatesClient {
5794
5860
  const parentFullId = String(
5795
5861
  parentEmbed2.embed_id ?? parentEmbed2.id ?? ""
5796
5862
  );
5797
- const parentHashedId = createHash9("sha256").update(parentFullId).digest("hex");
5863
+ const parentHashedId = createHash10("sha256").update(parentFullId).digest("hex");
5798
5864
  const parentKey = await this.resolveEmbedKey(
5799
5865
  cache,
5800
5866
  masterKey,
@@ -5886,9 +5952,7 @@ var OpenMatesClient = class _OpenMatesClient {
5886
5952
  (c) => String(c.details.id ?? "") === chatId || String(c.details.id ?? "").startsWith(chatId)
5887
5953
  );
5888
5954
  if (!found) return [];
5889
- const encKey = typeof found.details.encrypted_chat_key === "string" ? found.details.encrypted_chat_key : null;
5890
- if (!encKey) return [];
5891
- const chatKeyBytes = await this.decryptChatKey(encKey, masterKey);
5955
+ const chatKeyBytes = await this.resolveChatKey(cache, found, masterKey);
5892
5956
  if (!chatKeyBytes) return [];
5893
5957
  const encSuggestions = typeof found.details.encrypted_follow_up_request_suggestions === "string" ? found.details.encrypted_follow_up_request_suggestions : null;
5894
5958
  if (!encSuggestions) return [];
@@ -7783,6 +7847,8 @@ var OpenMatesClient = class _OpenMatesClient {
7783
7847
  if (filters.status) params.set("status", filters.status);
7784
7848
  if (filters.chatId) params.set("chat_id", filters.chatId);
7785
7849
  if (filters.projectId) params.set("project_id", filters.projectId);
7850
+ for (const labelHash of filters.labelHashes ?? []) params.append("label_hash", labelHash);
7851
+ if (filters.priority !== void 0) params.set("priority", String(filters.priority));
7786
7852
  const limit = filters.limit;
7787
7853
  if (Number.isSafeInteger(limit) && limit !== void 0 && limit > 0) params.set("limit", String(limit));
7788
7854
  const query = params.toString();
@@ -8837,7 +8903,7 @@ Required: ${schema.required.join(", ")}`
8837
8903
  const session = this.requireSession();
8838
8904
  const masterKey = base64ToBytes(session.masterKeyExportedB64);
8839
8905
  const cache = await this.ensureSynced();
8840
- const { createHash: createHash9 } = await import("crypto");
8906
+ const { createHash: createHash10 } = await import("crypto");
8841
8907
  const embed = cache.embeds.find(
8842
8908
  (e) => String(e.embed_id ?? "").startsWith(embedIdOrShort) || String(e.id ?? "").startsWith(embedIdOrShort)
8843
8909
  );
@@ -8845,7 +8911,7 @@ Required: ${schema.required.join(", ")}`
8845
8911
  throw new Error(`Embed '${embedIdOrShort}' not found in local cache.`);
8846
8912
  }
8847
8913
  const embedId = String(embed.embed_id ?? embed.id ?? "");
8848
- const hashedEmbedId = createHash9("sha256").update(embedId).digest("hex");
8914
+ const hashedEmbedId = createHash10("sha256").update(embedId).digest("hex");
8849
8915
  const embedKeyBytes = await this.resolveEmbedKey(
8850
8916
  cache,
8851
8917
  masterKey,
@@ -8953,8 +9019,8 @@ Required: ${schema.required.join(", ")}`
8953
9019
  if (!embed) {
8954
9020
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
8955
9021
  }
8956
- const { createHash: createHash9 } = await import("crypto");
8957
- const hashedEmbedId = createHash9("sha256").update(embedId).digest("hex");
9022
+ const { createHash: createHash10 } = await import("crypto");
9023
+ const hashedEmbedId = createHash10("sha256").update(embedId).digest("hex");
8958
9024
  const embedKey = await this.resolveEmbedKey(
8959
9025
  cache,
8960
9026
  masterKey,
@@ -9048,8 +9114,8 @@ Required: ${schema.required.join(", ")}`
9048
9114
  if (!embed) {
9049
9115
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
9050
9116
  }
9051
- const { createHash: createHash9 } = await import("crypto");
9052
- const hashedEmbedId = createHash9("sha256").update(embedId).digest("hex");
9117
+ const { createHash: createHash10 } = await import("crypto");
9118
+ const hashedEmbedId = createHash10("sha256").update(embedId).digest("hex");
9053
9119
  const embedKey = await this.resolveEmbedKey(
9054
9120
  cache,
9055
9121
  masterKey,
@@ -9321,6 +9387,7 @@ Required: ${schema.required.join(", ")}`
9321
9387
  const chats = [];
9322
9388
  const embeds = [];
9323
9389
  const embedKeys = [];
9390
+ const chatKeyWrappers = [];
9324
9391
  let newChatSuggestions = [];
9325
9392
  let totalChatCount = 0;
9326
9393
  let reconciliation = { authoritative: false };
@@ -9348,6 +9415,7 @@ Required: ${schema.required.join(", ")}`
9348
9415
  }
9349
9416
  if (p.embeds) embeds.push(...p.embeds);
9350
9417
  if (p.embed_keys) embedKeys.push(...p.embed_keys);
9418
+ if (p.chat_key_wrappers) chatKeyWrappers.push(...p.chat_key_wrappers);
9351
9419
  } else if (frame.type === "phase_2_last_20_chats_ready") {
9352
9420
  const p = frame.payload;
9353
9421
  totalChatCount = p.total_chat_count ?? 0;
@@ -9361,6 +9429,7 @@ Required: ${schema.required.join(", ")}`
9361
9429
  if (!details || typeof details.id !== "string") continue;
9362
9430
  chats.push({ details, messages: [] });
9363
9431
  }
9432
+ if (p.chat_key_wrappers) chatKeyWrappers.push(...p.chat_key_wrappers);
9364
9433
  } else if (frame.type === "background_message_sync") {
9365
9434
  const p = frame.payload;
9366
9435
  for (const c of p.chats ?? []) {
@@ -9370,6 +9439,7 @@ Required: ${schema.required.join(", ")}`
9370
9439
  }
9371
9440
  if (p.embeds) embeds.push(...p.embeds);
9372
9441
  if (p.embed_keys) embedKeys.push(...p.embed_keys);
9442
+ if (p.chat_key_wrappers) chatKeyWrappers.push(...p.chat_key_wrappers);
9373
9443
  } else if (frame.type === "pending_ai_response") {
9374
9444
  pendingAIResponses.push(frame.payload);
9375
9445
  }
@@ -9435,18 +9505,29 @@ Required: ${schema.required.join(", ")}`
9435
9505
  embedKeys.push(cached);
9436
9506
  }
9437
9507
  }
9508
+ const serverChatKeyWrapperIds = new Set(
9509
+ chatKeyWrappers.map((entry) => String(entry.id ?? ""))
9510
+ );
9511
+ for (const cached of existingCache.chatKeyWrappers ?? []) {
9512
+ const cachedId = String(cached.id ?? "");
9513
+ if (cachedId && !serverChatKeyWrapperIds.has(cachedId)) {
9514
+ chatKeyWrappers.push(cached);
9515
+ }
9516
+ }
9438
9517
  }
9439
9518
  const reconciledChats = reconcileAuthoritativeChats(chats, reconciliation);
9440
9519
  chats.splice(0, chats.length, ...reconciledChats);
9441
9520
  if (reconciliation.deleted_chat_ids?.length) {
9442
- const { createHash: createHash9 } = await import("crypto");
9521
+ const { createHash: createHash10 } = await import("crypto");
9443
9522
  const deletedHashes = new Set(
9444
- reconciliation.deleted_chat_ids.map((id) => createHash9("sha256").update(id).digest("hex"))
9523
+ reconciliation.deleted_chat_ids.map((id) => createHash10("sha256").update(id).digest("hex"))
9445
9524
  );
9446
9525
  const keptEmbeds = embeds.filter((embed) => !deletedHashes.has(String(embed.hashed_chat_id ?? "")));
9447
9526
  const keptKeys = embedKeys.filter((key) => !deletedHashes.has(String(key.hashed_chat_id ?? "")));
9527
+ const keptChatKeyWrappers = chatKeyWrappers.filter((key) => !deletedHashes.has(String(key.hashed_chat_id ?? "")));
9448
9528
  embeds.splice(0, embeds.length, ...keptEmbeds);
9449
9529
  embedKeys.splice(0, embedKeys.length, ...keptKeys);
9530
+ chatKeyWrappers.splice(0, chatKeyWrappers.length, ...keptChatKeyWrappers);
9450
9531
  }
9451
9532
  chats.sort(
9452
9533
  (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)
@@ -9478,6 +9559,7 @@ Required: ${schema.required.join(", ")}`
9478
9559
  chats,
9479
9560
  embeds,
9480
9561
  embedKeys,
9562
+ chatKeyWrappers,
9481
9563
  newChatSuggestions
9482
9564
  };
9483
9565
  saveSyncCache(cache);
@@ -9509,9 +9591,7 @@ Required: ${schema.required.join(", ")}`
9509
9591
  } catch {
9510
9592
  }
9511
9593
  }
9512
- const encryptedChatKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
9513
- if (!encryptedChatKey) continue;
9514
- const chatKeyBytes = await this.decryptChatKey(encryptedChatKey, masterKey);
9594
+ const chatKeyBytes = await this.resolveChatKey(loadSyncCache(), chat, masterKey);
9515
9595
  if (!chatKeyBytes) continue;
9516
9596
  const completedAt = normalizeUnixSeconds(
9517
9597
  pending.fired_at,
@@ -13625,7 +13705,7 @@ var GeneratedAppSkills = class {
13625
13705
 
13626
13706
  // src/sdk.ts
13627
13707
  import { decode as toonDecode } from "@toon-format/toon";
13628
- import { createHash as createHash6, randomBytes as randomBytes3, randomUUID as randomUUID6 } from "crypto";
13708
+ import { createHash as createHash7, randomBytes as randomBytes3, randomUUID as randomUUID6 } from "crypto";
13629
13709
  import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
13630
13710
  import { homedir as homedir4 } from "os";
13631
13711
  import { dirname, join as join3 } from "path";
@@ -13763,15 +13843,11 @@ var OpenMates = class {
13763
13843
  }
13764
13844
  async resolveEmbedKeyForShare(embedKeys, embedId) {
13765
13845
  const masterKey = await this.getMasterKey();
13766
- const hashedEmbedId = createHash6("sha256").update(embedId).digest("hex");
13846
+ const hashedEmbedId = createHash7("sha256").update(embedId).digest("hex");
13767
13847
  return this.resolveLoadedEmbedKey(embedKeys, hashedEmbedId, masterKey, masterKey);
13768
13848
  }
13769
- async decryptChatMetadata(chat) {
13770
- if (typeof chat.encrypted_chat_key !== "string") {
13771
- return chat;
13772
- }
13773
- const masterKey = await this.getMasterKey();
13774
- const chatKey = await decryptBytesWithAesGcm(chat.encrypted_chat_key, masterKey);
13849
+ async decryptChatMetadata(chat, chatKeyWrappers) {
13850
+ const chatKey = await this.resolveLoadedChatKey(chat, chatKeyWrappers);
13775
13851
  if (!chatKey) {
13776
13852
  return chat;
13777
13853
  }
@@ -13793,8 +13869,9 @@ var OpenMates = class {
13793
13869
  return payload;
13794
13870
  }
13795
13871
  const chatMetadata = chat;
13796
- const decryptedChat = await this.decryptChatMetadata(chatMetadata);
13797
- const chatKey = typeof chatMetadata.encrypted_chat_key === "string" ? await decryptBytesWithAesGcm(chatMetadata.encrypted_chat_key, await this.getMasterKey()) : null;
13872
+ const chatKeyWrappers = Array.isArray(payload.chat_key_wrappers) ? payload.chat_key_wrappers : Array.isArray(chatMetadata.chat_key_wrappers) ? chatMetadata.chat_key_wrappers : [];
13873
+ const decryptedChat = await this.decryptChatMetadata(chatMetadata, chatKeyWrappers);
13874
+ const chatKey = await this.resolveLoadedChatKey(chatMetadata, chatKeyWrappers);
13798
13875
  if (!chatKey || !Array.isArray(payload.messages)) {
13799
13876
  return { ...payload, chat: decryptedChat };
13800
13877
  }
@@ -13821,6 +13898,15 @@ var OpenMates = class {
13821
13898
  ) : payload.embeds;
13822
13899
  return { ...payload, chat: decryptedChat, messages, embeds };
13823
13900
  }
13901
+ async resolveLoadedChatKey(chat, chatKeyWrappers) {
13902
+ const masterKey = await this.getMasterKey();
13903
+ const hashedChatId = createHash7("sha256").update(chat.id).digest("hex");
13904
+ const wrapper = (chatKeyWrappers ?? []).find(
13905
+ (entry) => entry.key_type === "master" && entry.hashed_chat_id === hashedChatId && typeof entry.encrypted_chat_key === "string"
13906
+ );
13907
+ const encryptedChatKey = typeof wrapper?.encrypted_chat_key === "string" ? wrapper.encrypted_chat_key : typeof chat.encrypted_chat_key === "string" ? chat.encrypted_chat_key : null;
13908
+ return encryptedChatKey ? decryptBytesWithAesGcm(encryptedChatKey, masterKey) : null;
13909
+ }
13824
13910
  async decryptLoadedChatEmbeds(embeds, embedKeys, chatKey) {
13825
13911
  const masterKey = await this.getMasterKey();
13826
13912
  return Promise.all(embeds.map(async (embed) => {
@@ -13828,7 +13914,7 @@ var OpenMates = class {
13828
13914
  if (!embedId) {
13829
13915
  return { ...embed };
13830
13916
  }
13831
- const hashedEmbedId = createHash6("sha256").update(embedId).digest("hex");
13917
+ const hashedEmbedId = createHash7("sha256").update(embedId).digest("hex");
13832
13918
  const embedKey = await this.resolveLoadedEmbedKey(embedKeys, hashedEmbedId, masterKey, chatKey);
13833
13919
  if (!embedKey) {
13834
13920
  return { ...embed };
@@ -13951,7 +14037,12 @@ function loadOrCreateDeviceId(customPath) {
13951
14037
  function withQuery(path, query = {}) {
13952
14038
  const params = new URLSearchParams();
13953
14039
  for (const [key, value] of Object.entries(query)) {
13954
- if (value !== void 0 && value !== null) params.set(key, String(value));
14040
+ if (value === void 0 || value === null) continue;
14041
+ if (Array.isArray(value)) {
14042
+ for (const item of value) params.append(key, String(item));
14043
+ } else {
14044
+ params.set(key, String(value));
14045
+ }
13955
14046
  }
13956
14047
  const serialized = params.toString();
13957
14048
  return serialized ? `${path}?${serialized}` : path;
@@ -14705,10 +14796,13 @@ var OpenMatesTasks = class {
14705
14796
  return this.reorder(id, move, filters);
14706
14797
  }
14707
14798
  async listRaw(filters = {}) {
14799
+ const masterKey = filters.labels || filters.tags ? await this.client.masterKey() : void 0;
14708
14800
  const response = await this.client.get(withQuery("/v1/user-tasks", {
14709
14801
  status: filters.status,
14710
14802
  chat_id: filters.chatId,
14711
- project_id: filters.projectId
14803
+ project_id: filters.projectId,
14804
+ label_hash: masterKey ? labelHashes(masterKey, normalizeLabels(filters.labels ?? filters.tags ?? [])) : void 0,
14805
+ priority: normalizeTaskPriority(filters.priority)
14712
14806
  }));
14713
14807
  return response.tasks ?? [];
14714
14808
  }
@@ -15906,7 +16000,7 @@ var OutputRedactor = class {
15906
16000
  import { readFileSync as readFileSync5, statSync, existsSync as existsSync6 } from "fs";
15907
16001
  import { basename, extname, resolve as resolve2 } from "path";
15908
16002
  import { homedir as homedir5 } from "os";
15909
- import { createHash as createHash7 } from "crypto";
16003
+ import { createHash as createHash8 } from "crypto";
15910
16004
  var MAX_PER_FILE_SIZE = 100 * 1024 * 1024;
15911
16005
  var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set([
15912
16006
  ".pem",
@@ -16216,7 +16310,7 @@ function processCodeFile(filePath, filename, redactor) {
16216
16310
  line_count: lineCount
16217
16311
  });
16218
16312
  const textPreview = `${filename} (${language}, ${lineCount} lines)`;
16219
- const contentHash = createHash7("sha256").update(content).digest("hex");
16313
+ const contentHash = createHash8("sha256").update(content).digest("hex");
16220
16314
  const embed = {
16221
16315
  embedId,
16222
16316
  embedRef,
@@ -17771,7 +17865,7 @@ function formatTs(ts) {
17771
17865
 
17772
17866
  // src/server.ts
17773
17867
  import { execFileSync as execFileSync2, execSync, spawn as nodeSpawn, spawnSync } from "child_process";
17774
- import { createHash as createHash8, randomBytes as randomBytes4 } from "crypto";
17868
+ import { createHash as createHash9, randomBytes as randomBytes4 } from "crypto";
17775
17869
  import { chmodSync as chmodSync3, closeSync, copyFileSync, cpSync, existsSync as existsSync7, mkdirSync as mkdirSync4, mkdtempSync, openSync, readFileSync as readFileSync6, readSync, readdirSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
17776
17870
  import { createInterface as createInterface3 } from "readline";
17777
17871
  import { createInterface as createPromptInterface } from "readline/promises";
@@ -18436,7 +18530,7 @@ function packagedCaddyTemplatePath(role) {
18436
18530
  }
18437
18531
  function fileHash(path) {
18438
18532
  if (!existsSync7(path)) return null;
18439
- return createHash8("sha256").update(readFileSync6(path)).digest("hex");
18533
+ return createHash9("sha256").update(readFileSync6(path)).digest("hex");
18440
18534
  }
18441
18535
  async function loadSelfHostComposeTemplate(templateRef, role) {
18442
18536
  const templateDir = process.env.OPENMATES_SELFHOST_TEMPLATE_DIR;
@@ -18822,7 +18916,7 @@ function formatSecretPreflight(preflight) {
18822
18916
  return parts.join("; ");
18823
18917
  }
18824
18918
  function hashFile(path) {
18825
- const hash = createHash8("sha256");
18919
+ const hash = createHash9("sha256");
18826
18920
  const fd = openSync(path, "r");
18827
18921
  const buffer = Buffer.allocUnsafe(CHECKSUM_BUFFER_BYTES);
18828
18922
  try {
@@ -61220,11 +61314,11 @@ function decodeBase32(input) {
61220
61314
  return Buffer.from(bytes);
61221
61315
  }
61222
61316
  async function generateTotpCode(secret) {
61223
- const { createHmac } = await import("crypto");
61317
+ const { createHmac: createHmac2 } = await import("crypto");
61224
61318
  const counter = Math.floor(Date.now() / 1e3 / 30);
61225
61319
  const counterBuffer = Buffer.alloc(8);
61226
61320
  counterBuffer.writeBigUInt64BE(BigInt(counter));
61227
- const hmac = createHmac("sha1", decodeBase32(secret)).update(counterBuffer).digest();
61321
+ const hmac = createHmac2("sha1", decodeBase32(secret)).update(counterBuffer).digest();
61228
61322
  const offset = hmac[hmac.length - 1] & 15;
61229
61323
  const code = (hmac.readUInt32BE(offset) & 2147483647) % 1e6;
61230
61324
  return String(code).padStart(6, "0");
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getExtForLang,
4
4
  serializeToYaml
5
- } from "./chunk-47BFVCB4.js";
5
+ } from "./chunk-7ONUJGJ2.js";
6
6
  import "./chunk-AXNRPVLE.js";
7
7
  export {
8
8
  getExtForLang,
package/dist/index.d.ts CHANGED
@@ -31,6 +31,9 @@ interface CachedEmbed {
31
31
  interface CachedEmbedKey {
32
32
  [key: string]: unknown;
33
33
  }
34
+ interface CachedChatKeyWrapper {
35
+ [key: string]: unknown;
36
+ }
34
37
  interface CachedNewChatSuggestion {
35
38
  [key: string]: unknown;
36
39
  }
@@ -47,6 +50,8 @@ interface SyncCache {
47
50
  embeds: CachedEmbed[];
48
51
  /** Embed keys for embed decryption */
49
52
  embedKeys: CachedEmbedKey[];
53
+ /** Chat key wrappers for wrapper-first chat decryption */
54
+ chatKeyWrappers?: CachedChatKeyWrapper[];
50
55
  /**
51
56
  * New chat suggestions from the last sync.
52
57
  * Each entry has id, chat_id, encrypted_suggestion, created_at.
@@ -559,7 +564,9 @@ interface UserTaskRecord {
559
564
  encrypted_task_key?: string | null;
560
565
  encrypted_title: string;
561
566
  encrypted_description?: string | null;
567
+ encrypted_labels?: string | null;
562
568
  encrypted_tags?: string | null;
569
+ label_hashes?: string[] | null;
563
570
  encrypted_linked_project_ids?: string | null;
564
571
  encrypted_activity_summary?: string | null;
565
572
  encrypted_latest_instruction?: string | null;
@@ -1283,6 +1290,8 @@ declare class OpenMatesClient {
1283
1290
  * encrypt/decrypt the chat's title, category, messages, etc.
1284
1291
  */
1285
1292
  private decryptChatKey;
1293
+ private getMasterWrapperEncryptedChatKey;
1294
+ private resolveChatKey;
1286
1295
  /**
1287
1296
  * Decrypt a single chat record from the sync cache into a ChatListItem.
1288
1297
  */
@@ -1582,6 +1591,8 @@ declare class OpenMatesClient {
1582
1591
  status?: UserTaskStatus;
1583
1592
  chatId?: string;
1584
1593
  projectId?: string;
1594
+ labelHashes?: string[];
1595
+ priority?: number;
1585
1596
  limit?: number;
1586
1597
  }): Promise<UserTaskRecord[]>;
1587
1598
  createUserTask(input: UserTaskCreateInput): Promise<UserTaskRecord>;
@@ -4800,12 +4811,15 @@ declare class GeneratedAppSkills {
4800
4811
  readonly workflows: WorkflowsAppSkills;
4801
4812
  }
4802
4813
 
4814
+ declare const PRIORITY_LEVELS: readonly ["none", "low", "medium", "high", "urgent"];
4815
+ type TaskPriorityLevel = typeof PRIORITY_LEVELS[number];
4803
4816
  interface DecryptedUserTask {
4804
4817
  taskId: string;
4805
4818
  source?: string;
4806
4819
  shortId: string;
4807
4820
  title: string;
4808
4821
  description: string;
4822
+ labels: string[];
4809
4823
  tags: string[];
4810
4824
  latestInstruction: string;
4811
4825
  status: UserTaskStatus;
@@ -4816,6 +4830,7 @@ interface DecryptedUserTask {
4816
4830
  planId: string | null;
4817
4831
  dueAt: number | null;
4818
4832
  priority: number;
4833
+ priorityLevel: TaskPriorityLevel;
4819
4834
  position: number;
4820
4835
  queueState: string;
4821
4836
  blockedReasonCode: string | null;
@@ -4827,21 +4842,31 @@ interface DecryptedUserTask {
4827
4842
  interface TaskCreateOptions {
4828
4843
  title: string;
4829
4844
  description?: string;
4845
+ labels?: string[];
4846
+ tags?: string[];
4830
4847
  status?: UserTaskStatus;
4831
4848
  assign?: string;
4832
4849
  chatId?: string | null;
4833
4850
  projectIds?: string[];
4834
4851
  planId?: string | null;
4835
4852
  dueAt?: number | null;
4853
+ priority?: TaskPriorityLevel | number | null;
4836
4854
  }
4837
4855
  interface TaskUpdateOptions {
4838
4856
  title?: string;
4839
4857
  description?: string;
4858
+ labels?: string[];
4859
+ tags?: string[];
4860
+ addLabels?: string[];
4861
+ addTags?: string[];
4862
+ removeLabels?: string[];
4863
+ removeTags?: string[];
4840
4864
  status?: UserTaskStatus;
4841
4865
  assign?: string;
4842
4866
  chatId?: string | null;
4843
4867
  projectIds?: string[];
4844
4868
  planId?: string | null;
4869
+ priority?: TaskPriorityLevel | number | null;
4845
4870
  }
4846
4871
 
4847
4872
  interface OpenMatesOptions {
@@ -4938,6 +4963,7 @@ interface EncryptedChatMetadata {
4938
4963
  id: string;
4939
4964
  encrypted_title?: string;
4940
4965
  encrypted_chat_key?: string;
4966
+ chat_key_wrappers?: ChatKeyWrapperRecord[];
4941
4967
  encrypted_chat_summary?: string;
4942
4968
  encrypted_category?: string;
4943
4969
  title?: string;
@@ -4961,6 +4987,9 @@ type TaskListFilters = {
4961
4987
  status?: UserTaskStatus;
4962
4988
  chatId?: string;
4963
4989
  projectId?: string;
4990
+ labels?: string[];
4991
+ tags?: string[];
4992
+ priority?: TaskPriorityLevel | number | null;
4964
4993
  };
4965
4994
  type TaskPlainCreateOptions = TaskCreateOptions;
4966
4995
  type TaskPlainUpdateOptions = TaskUpdateOptions;
@@ -4986,6 +5015,12 @@ interface EmbedKeyRecord {
4986
5015
  encrypted_embed_key?: string;
4987
5016
  [key: string]: unknown;
4988
5017
  }
5018
+ interface ChatKeyWrapperRecord {
5019
+ hashed_chat_id?: string;
5020
+ key_type?: string;
5021
+ encrypted_chat_key?: string;
5022
+ [key: string]: unknown;
5023
+ }
4989
5024
  interface FocusModeSelection {
4990
5025
  appId: string;
4991
5026
  focusModeId: string;
@@ -5043,8 +5078,9 @@ declare class OpenMates {
5043
5078
  masterKey(): Promise<Uint8Array>;
5044
5079
  sdkSession(): Promise<SdkSessionResponse>;
5045
5080
  resolveEmbedKeyForShare(embedKeys: EmbedKeyRecord[], embedId: string): Promise<Uint8Array | null>;
5046
- decryptChatMetadata<T extends EncryptedChatMetadata>(chat: T): Promise<T>;
5081
+ decryptChatMetadata<T extends EncryptedChatMetadata>(chat: T, chatKeyWrappers?: ChatKeyWrapperRecord[]): Promise<T>;
5047
5082
  decryptLoadedChatPayload<T extends Record<string, unknown>>(payload: T): Promise<T>;
5083
+ private resolveLoadedChatKey;
5048
5084
  private decryptLoadedChatEmbeds;
5049
5085
  private resolveLoadedEmbedKey;
5050
5086
  private requestWithMethod;
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  reconcileAuthoritativeChats,
19
19
  renderSupportInfo,
20
20
  serializeToYaml
21
- } from "./chunk-47BFVCB4.js";
21
+ } from "./chunk-7ONUJGJ2.js";
22
22
  import "./chunk-AXNRPVLE.js";
23
23
  export {
24
24
  APP_SKILL_METADATA,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmates",
3
- "version": "0.15.0-alpha.22",
3
+ "version": "0.15.0-alpha.23",
4
4
  "description": "OpenMates CLI and SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",