openmates 0.14.8-alpha.15 → 0.14.8-alpha.16

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.
@@ -3594,7 +3594,8 @@ var CLOUD_API_URL = "https://api.openmates.org";
3594
3594
  var DEFAULT_API_URL = process.env.OPENMATES_API_URL ?? CLOUD_API_URL;
3595
3595
  var SETTINGS_GET_RATE_LIMIT_RETRY_MS = 61e3;
3596
3596
  var SKILL_TASK_POLL_INTERVAL_MS = 2e3;
3597
- var SKILL_TASK_POLL_TIMEOUT_MS = 3e5;
3597
+ var SKILL_TASK_POLL_TIMEOUT_MS = 12e5;
3598
+ var SKILL_TASK_POLL_TRANSIENT_ERROR_STATUS = 500;
3598
3599
  function normalizeOrigin(url) {
3599
3600
  url.pathname = "";
3600
3601
  url.search = "";
@@ -5339,237 +5340,22 @@ var OpenMatesClient = class _OpenMatesClient {
5339
5340
  `The assistant requested memories (${event.requestedKeys.join(", ")}). Rerun with --auto-approve-memories to explicitly approve requested memory categories from the CLI, or continue this chat in the web app to approve or reject the request.`
5340
5341
  );
5341
5342
  };
5342
- const persistEncryptedSystemMessage = async (systemMessage, targetChatId = chatId) => {
5343
- await ws.sendAsync("chat_system_message_added", {
5344
- chat_id: targetChatId,
5345
- message: systemMessage
5346
- });
5347
- await ws.waitForMessage(
5348
- "system_message_confirmed",
5349
- (payload) => payload.message_id === systemMessage.message_id,
5350
- 2e4
5351
- );
5352
- };
5353
5343
  const persistedTaskEventIds = /* @__PURE__ */ new Set();
5354
5344
  const persistTaskEventSystemMessages = async (events) => {
5355
5345
  if (!chatKeyBytes || events.length === 0) return;
5356
5346
  for (const event of events) {
5357
5347
  if (persistedTaskEventIds.has(event.event_id)) continue;
5358
- await persistEncryptedSystemMessage(await buildTaskEventSystemMessage({
5359
- chatKey: chatKeyBytes,
5360
- userMessageId: messageId,
5361
- event
5362
- }));
5363
- persistedTaskEventIds.add(event.event_id);
5364
- }
5365
- };
5366
- const persistPendingTaskUpdateJobs = async (jobs, events) => {
5367
- const handledJobIds = /* @__PURE__ */ new Set();
5368
- if (jobs.length === 0) return handledJobIds;
5369
- const masterKey = this.getMasterKeyBytes();
5370
- let decryptedTasksCache = null;
5371
- const eventByJobId = new Map(events.map((event) => [event.task_update_job_id, event]));
5372
- const buildTaskKeyWrappersForChat = async (task, targetChatId, createdAt2) => {
5373
- const encryptedTaskKey = task.encrypted.encrypted_task_key;
5374
- if (!encryptedTaskKey) throw new Error(`Task ${task.taskId} is missing encrypted task key.`);
5375
- const taskKey = await decryptBytesWithAesGcm(encryptedTaskKey, masterKey);
5376
- if (!taskKey) throw new Error(`Failed to decrypt task key for ${task.taskId}.`);
5377
- const targetChatKey = await resolveChatKey(targetChatId);
5378
- const existingProjectWrappers = await listProjectTaskKeyWrappers(task.taskId, createdAt2);
5379
- const linkedProjectIds = task.linkedProjectIds ?? [];
5380
- if (linkedProjectIds.length > existingProjectWrappers.length) {
5381
- throw new Error(`Task ${task.taskId} is missing project key wrappers required for move persistence.`);
5382
- }
5383
- return [
5384
- {
5385
- key_type: "master",
5386
- encrypted_task_key: await encryptBytesWithAesGcm(taskKey, masterKey),
5387
- created_at: createdAt2
5388
- },
5389
- {
5390
- key_type: "chat",
5391
- hashed_chat_id: computeSHA256(targetChatId),
5392
- encrypted_task_key: await encryptBytesWithAesGcm(taskKey, targetChatKey),
5393
- created_at: createdAt2
5394
- },
5395
- ...existingProjectWrappers
5396
- ];
5397
- };
5398
- const listProjectTaskKeyWrappers = async (taskId, createdAt2) => {
5399
- const response = await this.http.get(
5400
- `/v1/user-tasks/${encodeURIComponent(taskId)}/key-wrappers`,
5401
- this.getCliRequestHeaders()
5348
+ await this.persistEncryptedSystemMessage(
5349
+ ws,
5350
+ await buildTaskEventSystemMessage({
5351
+ chatKey: chatKeyBytes,
5352
+ userMessageId: messageId,
5353
+ event
5354
+ }),
5355
+ chatId
5402
5356
  );
5403
- if (!response.ok) {
5404
- throw new Error(`User task key wrapper list failed with HTTP ${response.status}`);
5405
- }
5406
- return (response.data.key_wrappers ?? []).filter((wrapper) => wrapper.key_type === "project").map((wrapper) => ({
5407
- key_type: "project",
5408
- hashed_project_id: wrapper.hashed_project_id,
5409
- encrypted_task_key: wrapper.encrypted_task_key,
5410
- created_at: typeof wrapper.created_at === "number" ? wrapper.created_at : createdAt2,
5411
- expires_at: wrapper.expires_at ?? null
5412
- }));
5413
- };
5414
- const resolveChatKey = async (targetChatId) => {
5415
- if (targetChatId === chatId && chatKeyBytes) return chatKeyBytes;
5416
- const cache = loadSyncCache() ?? await this.ensureSynced();
5417
- const targetChat = cache.chats.find((chat) => String(chat.details.id ?? "") === targetChatId);
5418
- const encryptedTargetChatKey = typeof targetChat?.details.encrypted_chat_key === "string" ? targetChat.details.encrypted_chat_key : null;
5419
- if (!encryptedTargetChatKey) {
5420
- throw new Error(`Encrypted chat key not found for task move target '${targetChatId}'. Sync and try again.`);
5421
- }
5422
- const targetChatKey = await decryptBytesWithAesGcm(encryptedTargetChatKey, masterKey);
5423
- if (!targetChatKey) {
5424
- throw new Error(`Failed to decrypt chat key for task move target '${targetChatId}'.`);
5425
- }
5426
- return targetChatKey;
5427
- };
5428
- const findTaskForUpdateJob = async (claim) => {
5429
- const chatIds = [claim.source_task_chat_id, claim.chat_id, claim.safe_metadata?.primary_chat_id].filter((value) => typeof value === "string" && value.length > 0);
5430
- const uniqueChatIds = [...new Set(chatIds)];
5431
- for (const candidateChatId of uniqueChatIds) {
5432
- const scopedTasks = await decryptUserTasks(await this.listUserTasks({ chatId: candidateChatId }), masterKey);
5433
- try {
5434
- return findTask(scopedTasks, claim.task_id);
5435
- } catch (error) {
5436
- if (!(error instanceof Error) || !error.message.includes("was not found")) throw error;
5437
- }
5438
- }
5439
- if (!decryptedTasksCache) {
5440
- decryptedTasksCache = await decryptUserTasks(await this.listUserTasks({ limit: 1e3 }), masterKey);
5441
- }
5442
- return findTask(decryptedTasksCache, claim.task_id);
5443
- };
5444
- const taskEventTypeForOperation = (operation) => {
5445
- switch (operation) {
5446
- case "create":
5447
- return "created";
5448
- case "move":
5449
- return "moved";
5450
- case "update":
5451
- return "updated";
5452
- default:
5453
- return operation || "updated";
5454
- }
5455
- };
5456
- for (const job of jobs) {
5457
- if (!taskUpdateJobBelongsToActiveTurn(job, chatId, events)) {
5458
- handledJobIds.add(job.job_id);
5459
- continue;
5460
- }
5461
- const claimPromise = ws.waitForMessage(
5462
- "task_update_job_claimed",
5463
- (payload) => payload.job_id === job.job_id,
5464
- 2e4
5465
- );
5466
- await ws.sendAsync("task_update_job_claim", {
5467
- protocol_version: 1,
5468
- job_id: job.job_id
5469
- });
5470
- const claim = (await claimPromise).payload;
5471
- const privatePatch = claim.private_patch ?? {};
5472
- const safeMetadata = claim.safe_metadata ?? {};
5473
- const sourceChatId = claim.chat_id ?? job.chat_id ?? chatId;
5474
- const sourceChatKey = await resolveChatKey(sourceChatId);
5475
- const event = eventByJobId.get(job.job_id) ?? {
5476
- event_id: `task-event-${job.job_id}`,
5477
- chat_id: sourceChatId,
5478
- task_id: claim.task_id,
5479
- event_type: taskEventTypeForOperation(claim.operation),
5480
- title: typeof privatePatch.title === "string" ? privatePatch.title : null,
5481
- status: typeof safeMetadata.status === "string" ? safeMetadata.status : null,
5482
- created_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : Math.floor(Date.now() / 1e3),
5483
- task_update_job_id: job.job_id
5484
- };
5485
- const eventMessage = await buildTaskEventSystemMessage({
5486
- chatKey: sourceChatKey,
5487
- userMessageId: claim.message_id ?? messageId,
5488
- event
5489
- });
5490
- const confirmTaskEventPersisted = async () => {
5491
- const confirmedPromise = ws.waitForMessage(
5492
- "task_update_job_event_confirmed",
5493
- (payload) => payload.job_id === job.job_id,
5494
- 2e4
5495
- );
5496
- await ws.sendAsync("task_update_job_event_confirmed", {
5497
- protocol_version: 1,
5498
- job_id: job.job_id,
5499
- event_system_message_id: eventMessage.message_id
5500
- });
5501
- await confirmedPromise;
5502
- };
5503
- if (claim.state === "TASK_PERSISTED") {
5504
- await persistEncryptedSystemMessage(eventMessage, sourceChatId);
5505
- persistedTaskEventIds.add(event.event_id);
5506
- await confirmTaskEventPersisted();
5507
- handledJobIds.add(job.job_id);
5508
- continue;
5509
- }
5510
- if (!claim.lease_token || !Number.isSafeInteger(claim.lease_generation)) {
5511
- throw new Error("Task update job claim returned an invalid lease.");
5512
- }
5513
- let encryptedTaskPayload;
5514
- if (claim.operation === "create") {
5515
- const input = await buildCreateUserTaskInput(masterKey, {
5516
- title: typeof privatePatch.title === "string" ? privatePatch.title : "Untitled task",
5517
- description: typeof privatePatch.description === "string" ? privatePatch.description : "",
5518
- status: typeof safeMetadata.status === "string" ? safeMetadata.status : "todo",
5519
- assign: typeof safeMetadata.assignee_type === "string" ? safeMetadata.assignee_type : "user",
5520
- chatId: typeof safeMetadata.primary_chat_id === "string" ? safeMetadata.primary_chat_id : claim.chat_id ?? chatId
5521
- });
5522
- encryptedTaskPayload = {
5523
- ...input,
5524
- task_id: claim.task_id,
5525
- position: typeof safeMetadata.position === "number" ? safeMetadata.position : input.position,
5526
- created_at: typeof safeMetadata.created_at === "number" ? safeMetadata.created_at : input.created_at,
5527
- updated_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : input.updated_at
5528
- };
5529
- } else {
5530
- const task = await findTaskForUpdateJob(claim);
5531
- const patch = await buildUpdateUserTaskInput(task, masterKey, {
5532
- title: typeof privatePatch.title === "string" ? privatePatch.title : void 0,
5533
- description: typeof privatePatch.description === "string" ? privatePatch.description : void 0,
5534
- status: typeof safeMetadata.status === "string" ? safeMetadata.status : void 0,
5535
- assign: typeof safeMetadata.assignee_type === "string" ? safeMetadata.assignee_type : void 0,
5536
- chatId: typeof safeMetadata.primary_chat_id === "string" ? safeMetadata.primary_chat_id : void 0
5537
- });
5538
- encryptedTaskPayload = {
5539
- ...patch,
5540
- version: claim.expected_task_version,
5541
- updated_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : patch.updated_at
5542
- };
5543
- if (typeof safeMetadata.primary_chat_id === "string") {
5544
- encryptedTaskPayload.key_wrappers = await buildTaskKeyWrappersForChat(
5545
- task,
5546
- safeMetadata.primary_chat_id,
5547
- typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : Math.floor(Date.now() / 1e3)
5548
- );
5549
- }
5550
- }
5551
- const encryptedEventMessage = eventMessage.encrypted_content;
5552
- const persistedPromise = ws.waitForMessage(
5553
- "task_update_job_persisted",
5554
- (payload) => payload.job_id === job.job_id,
5555
- 2e4
5556
- );
5557
- await ws.sendAsync("task_update_job_persist", buildTaskUpdateJobPersistPayload({
5558
- jobId: job.job_id,
5559
- leaseToken: claim.lease_token,
5560
- leaseGeneration: claim.lease_generation,
5561
- expectedTaskVersion: claim.expected_task_version,
5562
- encryptedTaskPayload,
5563
- encryptedTaskEventMessage: encryptedEventMessage
5564
- }));
5565
- await persistedPromise;
5566
- await persistEncryptedSystemMessage(eventMessage, sourceChatId);
5567
5357
  persistedTaskEventIds.add(event.event_id);
5568
- await confirmTaskEventPersisted();
5569
- handledJobIds.add(job.job_id);
5570
5358
  }
5571
- clearSyncCache();
5572
- return handledJobIds;
5573
5359
  };
5574
5360
  const streamOpts = {
5575
5361
  onStream: params.onStream,
@@ -5761,7 +5547,15 @@ var OpenMatesClient = class _OpenMatesClient {
5761
5547
  newChatSuggestions: resp.newChatSuggestions,
5762
5548
  encryptedChatKey
5763
5549
  });
5764
- const persistedTaskJobIds = await persistPendingTaskUpdateJobs(pendingTaskUpdateJobs, taskEvents);
5550
+ const persistedTaskJobIds = await this.persistPendingTaskUpdateJobs({
5551
+ ws,
5552
+ jobs: pendingTaskUpdateJobs,
5553
+ events: taskEvents,
5554
+ activeChatId: chatId,
5555
+ activeChatKeyBytes: chatKeyBytes,
5556
+ fallbackUserMessageId: messageId,
5557
+ requireActiveTurnEvent: true
5558
+ });
5765
5559
  pendingTaskUpdateJobs = pendingTaskUpdateJobs.filter((job) => !persistedTaskJobIds.has(job.job_id));
5766
5560
  await persistTaskEventSystemMessages(taskEvents);
5767
5561
  clearSyncCache();
@@ -5788,6 +5582,224 @@ var OpenMatesClient = class _OpenMatesClient {
5788
5582
  appSettingsMemoryRequests
5789
5583
  };
5790
5584
  }
5585
+ async persistEncryptedSystemMessage(ws, systemMessage, targetChatId) {
5586
+ await ws.sendAsync("chat_system_message_added", {
5587
+ chat_id: targetChatId,
5588
+ message: systemMessage
5589
+ });
5590
+ await ws.waitForMessage(
5591
+ "system_message_confirmed",
5592
+ (payload) => payload.message_id === systemMessage.message_id,
5593
+ 2e4
5594
+ );
5595
+ }
5596
+ async persistPendingTaskUpdateJobs(params) {
5597
+ const handledJobIds = /* @__PURE__ */ new Set();
5598
+ if (params.jobs.length === 0) return handledJobIds;
5599
+ const masterKey = this.getMasterKeyBytes();
5600
+ let decryptedTasksCache = null;
5601
+ const eventByJobId = new Map(params.events.map((event) => [event.task_update_job_id, event]));
5602
+ const resolveChatKey = async (targetChatId) => {
5603
+ if (targetChatId === params.activeChatId && params.activeChatKeyBytes) return params.activeChatKeyBytes;
5604
+ const findChat = (chats) => chats?.find((chat) => String(chat.details.id ?? "") === targetChatId);
5605
+ const targetChat = findChat(params.syncedChats) ?? findChat(loadSyncCache()?.chats);
5606
+ const encryptedTargetChatKey = typeof targetChat?.details.encrypted_chat_key === "string" ? targetChat.details.encrypted_chat_key : null;
5607
+ if (!encryptedTargetChatKey) {
5608
+ throw new Error(`Encrypted chat key not found for task update job target '${targetChatId}'. Sync and try again.`);
5609
+ }
5610
+ const targetChatKey = await decryptBytesWithAesGcm(encryptedTargetChatKey, masterKey);
5611
+ if (!targetChatKey) {
5612
+ throw new Error(`Failed to decrypt chat key for task update job target '${targetChatId}'.`);
5613
+ }
5614
+ return targetChatKey;
5615
+ };
5616
+ const listProjectTaskKeyWrappers = async (taskId, createdAt) => {
5617
+ const response = await this.http.get(
5618
+ `/v1/user-tasks/${encodeURIComponent(taskId)}/key-wrappers`,
5619
+ this.getCliRequestHeaders()
5620
+ );
5621
+ if (!response.ok) {
5622
+ throw new Error(`User task key wrapper list failed with HTTP ${response.status}`);
5623
+ }
5624
+ return (response.data.key_wrappers ?? []).filter((wrapper) => wrapper.key_type === "project").map((wrapper) => ({
5625
+ key_type: "project",
5626
+ hashed_project_id: wrapper.hashed_project_id,
5627
+ encrypted_task_key: wrapper.encrypted_task_key,
5628
+ created_at: typeof wrapper.created_at === "number" ? wrapper.created_at : createdAt,
5629
+ expires_at: wrapper.expires_at ?? null
5630
+ }));
5631
+ };
5632
+ const buildTaskKeyWrappersForChat = async (task, targetChatId, createdAt) => {
5633
+ const encryptedTaskKey = task.encrypted.encrypted_task_key;
5634
+ if (!encryptedTaskKey) throw new Error(`Task ${task.taskId} is missing encrypted task key.`);
5635
+ const taskKey = await decryptBytesWithAesGcm(encryptedTaskKey, masterKey);
5636
+ if (!taskKey) throw new Error(`Failed to decrypt task key for ${task.taskId}.`);
5637
+ const targetChatKey = await resolveChatKey(targetChatId);
5638
+ const existingProjectWrappers = await listProjectTaskKeyWrappers(task.taskId, createdAt);
5639
+ const linkedProjectIds = task.linkedProjectIds ?? [];
5640
+ if (linkedProjectIds.length > existingProjectWrappers.length) {
5641
+ throw new Error(`Task ${task.taskId} is missing project key wrappers required for move persistence.`);
5642
+ }
5643
+ return [
5644
+ {
5645
+ key_type: "master",
5646
+ encrypted_task_key: await encryptBytesWithAesGcm(taskKey, masterKey),
5647
+ created_at: createdAt
5648
+ },
5649
+ {
5650
+ key_type: "chat",
5651
+ hashed_chat_id: computeSHA256(targetChatId),
5652
+ encrypted_task_key: await encryptBytesWithAesGcm(taskKey, targetChatKey),
5653
+ created_at: createdAt
5654
+ },
5655
+ ...existingProjectWrappers
5656
+ ];
5657
+ };
5658
+ const findTaskForUpdateJob = async (claim) => {
5659
+ const chatIds = [claim.source_task_chat_id, claim.chat_id, claim.safe_metadata?.primary_chat_id].filter((value) => typeof value === "string" && value.length > 0);
5660
+ for (const candidateChatId of [...new Set(chatIds)]) {
5661
+ const scopedTasks = await decryptUserTasks(await this.listUserTasks({ chatId: candidateChatId }), masterKey);
5662
+ try {
5663
+ return findTask(scopedTasks, claim.task_id);
5664
+ } catch (error) {
5665
+ if (!(error instanceof Error) || !error.message.includes("was not found")) throw error;
5666
+ }
5667
+ }
5668
+ if (!decryptedTasksCache) {
5669
+ decryptedTasksCache = await decryptUserTasks(await this.listUserTasks({ limit: 1e3 }), masterKey);
5670
+ }
5671
+ return findTask(decryptedTasksCache, claim.task_id);
5672
+ };
5673
+ const taskEventTypeForOperation = (operation) => {
5674
+ switch (operation) {
5675
+ case "create":
5676
+ return "created";
5677
+ case "move":
5678
+ return "moved";
5679
+ case "update":
5680
+ return "updated";
5681
+ default:
5682
+ return operation || "updated";
5683
+ }
5684
+ };
5685
+ for (const job of params.jobs) {
5686
+ if (params.requireActiveTurnEvent && params.activeChatId && !taskUpdateJobBelongsToActiveTurn(job, params.activeChatId, params.events)) {
5687
+ handledJobIds.add(job.job_id);
5688
+ continue;
5689
+ }
5690
+ const claimPromise = params.ws.waitForMessage(
5691
+ "task_update_job_claimed",
5692
+ (payload) => payload.job_id === job.job_id,
5693
+ 2e4
5694
+ );
5695
+ await params.ws.sendAsync("task_update_job_claim", {
5696
+ protocol_version: 1,
5697
+ job_id: job.job_id
5698
+ });
5699
+ const claim = (await claimPromise).payload;
5700
+ const privatePatch = claim.private_patch ?? {};
5701
+ const safeMetadata = claim.safe_metadata ?? {};
5702
+ const sourceChatId = claim.chat_id ?? job.chat_id ?? params.activeChatId;
5703
+ if (!sourceChatId) throw new Error(`Task update job ${job.job_id} is missing a source chat id.`);
5704
+ const sourceChatKey = await resolveChatKey(sourceChatId);
5705
+ const event = eventByJobId.get(job.job_id) ?? {
5706
+ event_id: `task-event-${job.job_id}`,
5707
+ chat_id: sourceChatId,
5708
+ task_id: claim.task_id,
5709
+ event_type: taskEventTypeForOperation(claim.operation),
5710
+ title: typeof privatePatch.title === "string" ? privatePatch.title : null,
5711
+ status: typeof safeMetadata.status === "string" ? safeMetadata.status : null,
5712
+ created_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : Math.floor(Date.now() / 1e3),
5713
+ task_update_job_id: job.job_id
5714
+ };
5715
+ const userMessageId = claim.message_id ?? params.fallbackUserMessageId;
5716
+ if (!userMessageId) throw new Error(`Task update job ${job.job_id} is missing a user message id.`);
5717
+ const eventMessage = await buildTaskEventSystemMessage({
5718
+ chatKey: sourceChatKey,
5719
+ userMessageId,
5720
+ event
5721
+ });
5722
+ const confirmTaskEventPersisted = async () => {
5723
+ const confirmedPromise = params.ws.waitForMessage(
5724
+ "task_update_job_event_confirmed",
5725
+ (payload) => payload.job_id === job.job_id,
5726
+ 2e4
5727
+ );
5728
+ await params.ws.sendAsync("task_update_job_event_confirmed", {
5729
+ protocol_version: 1,
5730
+ job_id: job.job_id,
5731
+ event_system_message_id: eventMessage.message_id
5732
+ });
5733
+ await confirmedPromise;
5734
+ };
5735
+ if (claim.state === "TASK_PERSISTED") {
5736
+ await this.persistEncryptedSystemMessage(params.ws, eventMessage, sourceChatId);
5737
+ await confirmTaskEventPersisted();
5738
+ handledJobIds.add(job.job_id);
5739
+ continue;
5740
+ }
5741
+ if (!claim.lease_token || !Number.isSafeInteger(claim.lease_generation)) {
5742
+ throw new Error("Task update job claim returned an invalid lease.");
5743
+ }
5744
+ let encryptedTaskPayload;
5745
+ if (claim.operation === "create") {
5746
+ const input = await buildCreateUserTaskInput(masterKey, {
5747
+ title: typeof privatePatch.title === "string" ? privatePatch.title : "Untitled task",
5748
+ description: typeof privatePatch.description === "string" ? privatePatch.description : "",
5749
+ status: typeof safeMetadata.status === "string" ? safeMetadata.status : "todo",
5750
+ assign: typeof safeMetadata.assignee_type === "string" ? safeMetadata.assignee_type : "user",
5751
+ chatId: typeof safeMetadata.primary_chat_id === "string" ? safeMetadata.primary_chat_id : claim.chat_id ?? params.activeChatId
5752
+ });
5753
+ encryptedTaskPayload = {
5754
+ ...input,
5755
+ task_id: claim.task_id,
5756
+ position: typeof safeMetadata.position === "number" ? safeMetadata.position : input.position,
5757
+ created_at: typeof safeMetadata.created_at === "number" ? safeMetadata.created_at : input.created_at,
5758
+ updated_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : input.updated_at
5759
+ };
5760
+ } else {
5761
+ const task = await findTaskForUpdateJob(claim);
5762
+ const patch = await buildUpdateUserTaskInput(task, masterKey, {
5763
+ title: typeof privatePatch.title === "string" ? privatePatch.title : void 0,
5764
+ description: typeof privatePatch.description === "string" ? privatePatch.description : void 0,
5765
+ status: typeof safeMetadata.status === "string" ? safeMetadata.status : void 0,
5766
+ assign: typeof safeMetadata.assignee_type === "string" ? safeMetadata.assignee_type : void 0,
5767
+ chatId: typeof safeMetadata.primary_chat_id === "string" ? safeMetadata.primary_chat_id : void 0
5768
+ });
5769
+ encryptedTaskPayload = {
5770
+ ...patch,
5771
+ version: claim.expected_task_version,
5772
+ updated_at: typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : patch.updated_at
5773
+ };
5774
+ if (typeof safeMetadata.primary_chat_id === "string") {
5775
+ encryptedTaskPayload.key_wrappers = await buildTaskKeyWrappersForChat(
5776
+ task,
5777
+ safeMetadata.primary_chat_id,
5778
+ typeof safeMetadata.updated_at === "number" ? safeMetadata.updated_at : Math.floor(Date.now() / 1e3)
5779
+ );
5780
+ }
5781
+ }
5782
+ const persistedPromise = params.ws.waitForMessage(
5783
+ "task_update_job_persisted",
5784
+ (payload) => payload.job_id === job.job_id,
5785
+ 2e4
5786
+ );
5787
+ await params.ws.sendAsync("task_update_job_persist", buildTaskUpdateJobPersistPayload({
5788
+ jobId: job.job_id,
5789
+ leaseToken: claim.lease_token,
5790
+ leaseGeneration: claim.lease_generation,
5791
+ expectedTaskVersion: claim.expected_task_version,
5792
+ encryptedTaskPayload,
5793
+ encryptedTaskEventMessage: eventMessage.encrypted_content
5794
+ }));
5795
+ await persistedPromise;
5796
+ await this.persistEncryptedSystemMessage(params.ws, eventMessage, sourceChatId);
5797
+ await confirmTaskEventPersisted();
5798
+ handledJobIds.add(job.job_id);
5799
+ }
5800
+ clearSyncCache();
5801
+ return handledJobIds;
5802
+ }
5791
5803
  async persistStreamedEmbeds(params) {
5792
5804
  const finalized = new Map(
5793
5805
  params.embeds.filter((embed) => {
@@ -6029,14 +6041,28 @@ var OpenMatesClient = class _OpenMatesClient {
6029
6041
  }
6030
6042
  async pollTaskUntilComplete(taskId, headers) {
6031
6043
  const started = Date.now();
6044
+ let lastTransientError = null;
6032
6045
  while (Date.now() - started < SKILL_TASK_POLL_TIMEOUT_MS) {
6033
- const response = await this.http.get(
6034
- `/v1/tasks/${encodeURIComponent(taskId)}`,
6035
- headers
6036
- );
6046
+ let response;
6047
+ try {
6048
+ response = await this.http.get(
6049
+ `/v1/tasks/${encodeURIComponent(taskId)}`,
6050
+ headers
6051
+ );
6052
+ } catch (error) {
6053
+ lastTransientError = error instanceof Error ? error.message : String(error);
6054
+ await new Promise((resolve7) => setTimeout(resolve7, SKILL_TASK_POLL_INTERVAL_MS));
6055
+ continue;
6056
+ }
6037
6057
  if (!response.ok) {
6058
+ if (response.status >= SKILL_TASK_POLL_TRANSIENT_ERROR_STATUS) {
6059
+ lastTransientError = `HTTP ${response.status}`;
6060
+ await new Promise((resolve7) => setTimeout(resolve7, SKILL_TASK_POLL_INTERVAL_MS));
6061
+ continue;
6062
+ }
6038
6063
  throw new Error(`Task polling failed with HTTP ${response.status}`);
6039
6064
  }
6065
+ lastTransientError = null;
6040
6066
  if (response.data.status === "completed") {
6041
6067
  return response.data;
6042
6068
  }
@@ -6045,6 +6071,11 @@ var OpenMatesClient = class _OpenMatesClient {
6045
6071
  }
6046
6072
  await new Promise((resolve7) => setTimeout(resolve7, SKILL_TASK_POLL_INTERVAL_MS));
6047
6073
  }
6074
+ if (lastTransientError) {
6075
+ throw new Error(
6076
+ `Task ${taskId} did not complete within ${SKILL_TASK_POLL_TIMEOUT_MS / 1e3}s; last polling error: ${lastTransientError}`
6077
+ );
6078
+ }
6048
6079
  throw new Error(`Task ${taskId} did not complete within ${SKILL_TASK_POLL_TIMEOUT_MS / 1e3}s`);
6049
6080
  }
6050
6081
  wrapResolvedSkillResult(original, result) {
@@ -7608,7 +7639,13 @@ Required: ${schema.required.join(", ")}`
7608
7639
  * Get the session for file upload authentication.
7609
7640
  */
7610
7641
  getSession() {
7611
- return this.requireSession();
7642
+ const session = this.requireSession();
7643
+ const currentCookies = this.http.getCookieMap();
7644
+ if (JSON.stringify(session.cookies) !== JSON.stringify(currentCookies)) {
7645
+ session.cookies = currentCookies;
7646
+ saveSession(session);
7647
+ }
7648
+ return session;
7612
7649
  }
7613
7650
  // ── Share link creation ──────────────────────────────────────────────
7614
7651
  /**
@@ -8112,6 +8149,7 @@ Required: ${schema.required.join(", ")}`
8112
8149
  let totalChatCount = 0;
8113
8150
  let reconciliation = { authoritative: false };
8114
8151
  const pendingAIResponses = [];
8152
+ let pendingTaskUpdateJobs = [];
8115
8153
  try {
8116
8154
  ws.send("phased_sync_request", {
8117
8155
  phase: "all",
@@ -8160,6 +8198,7 @@ Required: ${schema.required.join(", ")}`
8160
8198
  pendingAIResponses.push(frame.payload);
8161
8199
  }
8162
8200
  }
8201
+ pendingTaskUpdateJobs = ws.drainPassiveTaskUpdateJobs();
8163
8202
  for (const chat of chats) {
8164
8203
  const id = typeof chat.details.id === "string" ? chat.details.id : "";
8165
8204
  const msgs = messagesByChatId.get(id);
@@ -8236,11 +8275,26 @@ Required: ${schema.required.join(", ")}`
8236
8275
  chats.sort(
8237
8276
  (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)
8238
8277
  );
8278
+ let persistedTaskJobIds = /* @__PURE__ */ new Set();
8239
8279
  try {
8240
8280
  await this.persistPendingAIResponsesFromSync(ws, chats, pendingAIResponses);
8281
+ persistedTaskJobIds = await this.persistPendingTaskUpdateJobs({
8282
+ ws,
8283
+ jobs: pendingTaskUpdateJobs,
8284
+ events: [],
8285
+ activeChatId: null,
8286
+ activeChatKeyBytes: null,
8287
+ fallbackUserMessageId: null,
8288
+ syncedChats: chats,
8289
+ requireActiveTurnEvent: false
8290
+ });
8241
8291
  } finally {
8242
8292
  ws.close();
8243
8293
  }
8294
+ if (persistedTaskJobIds.size > 0) {
8295
+ clearSyncCache();
8296
+ return this.ensureSynced(true, refreshChatIds);
8297
+ }
8244
8298
  const cache = {
8245
8299
  syncedAt: Date.now(),
8246
8300
  totalChatCount,
@@ -23627,6 +23681,76 @@ var privateWorkspaceDemoVideoChat = {
23627
23681
  }
23628
23682
  };
23629
23683
 
23684
+ // ../ui/src/demo_chats/data/example_chats/reference-image-3d-model.ts
23685
+ var MODEL3D_POSTER_DATA_URL = "data:image/webp;base64,UklGRp4PAABXRUJQVlA4IJIPAADQcgCdASqAAYABPpVKoUslpLGqJNKp6jASiWdu3N09a2LxMU/7HDGEagE15+rI9Wvj3TbSfHDMzy8fmt3i/OqbrrvYPps5KBh+4L7o/5njVYs2yrVr77+ZL52+RJ8y9Q39Resl/qeav61HBKaoYv21LGPfo9/lQxftqWMe/R7/Khi/bUsY9+j3+VDF+2pYx79Hv8qGL9tSxj36Pf5UMX7aljHv0e/yoYv21LGPfo9/lQxftqWMe/R7/Khi/bUsY9+j3+VDF+2pYx79Hv8qGL9tP4MbSFnrySa9pz+fUk7a8vyoYv21LGDHqTDaH6+r0sREWhIXwFDDz/lEE27yDvTTg7P75Zb57ZG2pYwoASSWSnWz0hIrPgwYYD9zSfFiDpXw1ImQeKxmTikll+/KT64Fi+qGnOmETGoYMNJnTzAlh6M2htSeBNsv625Il8Q7UsZo4MRSVVh55n9witIpafMCpZbpPc4v0NVwfSoJTVDmtuRcn+2nyum1rz5CcPLwc4dA8wF+CsgfT2N2VI21KOZPQnUP1fcs2KfMdAjwieLQG4PxemYFFFzb9OW2UrHd3E1pjwp0tbNP6a6fNAeFMe/Yde5pVV93Zu5oZkY49KgRmvWB/nUJyVSCyw2MUWDmIoX8cCYOxoNXYyNqmV6GmunUqEpgqMvfncVVTwZX2q/XaSiPTbFhboaNXv1+zZBwFeuPjPwrBmbPSUcdqMnWqmSMn5zoLSSe+oSnd0QYEFiXYXZznFuWIv0/8TgcTKVy6JMEe1ZJzShzXbdwIMUK7/vq39gESHkF2E1OTT4f9RFTE3G2rAtAHJOV/4LhhFm+Yu9jqXl2UcrRltso7QqztenOr4BHne8uZf+qvn3GbhLpLZcK4+XtJTbJwduSv7quiBy1/dxFe/jw8WPzwiKh4jSHSjJGQSwJh+lIEHFo/aqwyOqkXYUW19C5QHqsPz5OtYP638epT1IK8t0XghHGyhWUTbIl7kANBV3cTG8hWwjgSgr91V4u9ct2yk/L4ewJsGWHql0KlhRzzQ9jSe+Y/AqsYiO/hK4D0TZl1kBFLUuSxjehV0ebxm1NLJpEyK3HBSlwS35kaME7GRG4/MtrwpBMYBrF+UmMSXM8SNtRmQ3Ce5xP++/AT4vjbM9kjCv49crUyiJaNh6t9Oxyo6oaFrjnbCBT+5j9iQC92w8u9tf/ROoYzswzm6JjVhuKO1KKrViStw9WZBwEZl7bSHHBooAA/v4RgqHRuIAADmbiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO3OAcWOBOvHfEtijOz4x+uH5H2bQnZuiL1YUosey+wOpHz+aUC0XRZUeYPOD7AfBsfYGPNvTu8Y3Qgjo/nC+0UEaPFuE0G827P5wlVTtOgABtE0zXBbucHU6ZvzCEa9xwcTEat/SN8w+MRGNYy2Z9UQpxjWa+rlSHKpF4rsZ8w9obO61HHFg/1R836hgfokbGea76/FPL5wtC8t5fHnf82kf5kr4Ima/oWmqQSmh3/9Sk1DJb9Eb6Y1nyQeumzN3p/MpAGbH9YkR6Aw+tygQVumOYxTbiYd75KKAEnv5VRK54EuTcLjtUKTJKitlBncg9PLP00BTyEDozfPO1Ya6Le7wx4oixhkHVmoWZ4tt/ZrxQn151tokomnUeYiBg+bdSKP4yyCImZGuGbIytugLpU31DuSqwcaVpQlc2nwlXQCYiz3g8Z7rL2ibKRjVP8IedNc//I/tl2zrxG5lDfo8DG2rY3cnKLEKw1cJJ1upleVGqEhL5Y6kOm0pGQkE5CuZOdB2Q7niICZ4s5e2X923xrfOIsBDEeO7rLNYIyngbvx8XRa3URxfKjKWGJj6q8lxqGbQJMDn+U4SqVd3/wqIvFtWFrJy315EDgNkyJuQr4XVA36A2UnVyxiGlhfD2Rq9DZNl+4zgu+sfsEpDX9bP1cCKwI3ED4bA6FlYKTAQtv0MEzI1lIsxtf0r+gEP7kz+qmibvj56S47MZwVMvIyvKawlHmzwba7egGAiUZhik6hKMQC8ZxUuOwu4xuuglvFQk7cSsnBbhDDC0zOReVZH8qc39FXrr3BUgk0UQvg51fQFgaK3p2oQdVaOkiTD5YuGOo5JBu/KJPaPrP3o4tQBa3AanLhymEpzdRSY/AXenAf7JDvapI9kDnk5TnbDpNnnpGJWrrV42dByNiKgJbYoi8wQzE28ceha8fjhCoO2yfl21HrgCtGCQy/EeVxA5xgwJIhv+iKrcaCrtTg/py4t7cTKvtqKsBo0FeBDHswSTwoaiMytFYFjw9iX2WzmzwymbShsrFMfhi5aJva1vRJysIeXtR+V4VyqKl7F0RPLyJCwdroZKutPRp3zq3tP+k0Lg7vJj/DU5BXPDW+DcMIliOmWMyhGrXnqNH7e+2py1Ppwod+Cc3G09eQbTPrKJ1X1FyI4d13E7JcynoJ60kbuLlub+TSv+Me0V3LbLeEvQvjejcKz9sPEXlcAT4oV5CcU2qRWonU6zO8ljcPwW6s6NNfPLUhXfZIujrb+An16B96oQ3cRsrryHGkl1l4/uV/3dACJFktGfcZOc411abYGumvt/rw4Z0v6fcrudpb/KfbkLA6MLjFxBLM7sRnMiH1kiNLUoOXUsB7P4uDumSYAFEUI7m+xOiIJbC+9jrNrQnZEfQ+ZSGmFUDuSyPRSs6P5FdnbdwRV8M+0MbsDoji0PsIViOCJvnZmZBalzka25fTclfoqMBwiAOoRslBFDhwCOYEuGhSr1H65B67tUwiRR7ogvdmamocSskcxlXEs2mnl/3dAaD/bTeAXMdqlZNeLy2eeKpK+ARs1LRYDF/zPvuRC5QMEojgKxhz0AHEGx9ZZ/LSL+cuD7frW0EUg/KMjelXOJcVAiB7BGy98ecr8MRFxgxU3cLLGGYg/qbEVB48qksP7he0JhSYeLoHJsuzUAne1S+jPnWDP1CuVwp8yIdJ78VyOWQvSwJkKWOkUT6MqNFU+yxYISYLGPmnLY3z3bwPia+F9SsVJGIDmHyzSGSbGtJawR+Jm6RslkYjh1iXG5vvr2iySPR5GWsQNeHI2bIr2qUwvjkD6OznqVtArewKLvJlswcETgcBdL09dBAN/lb/jUQuC6GaDQSSlv6crYrz5+/TxwsYJ117bUsINaVGPptWkMvYS+3WwZF5AdNkTa5s802/ycWSta4Hi6oJ3nDiXslkLi1DJGeLWDHrPaNBvktaT5PC83I5wzkmobr5ThIyf4wTKfhpmqaQ3Ddo0XWAbJv6BtlCHjLXWg04NGqERoHNSRoh11Cvl3O6BV179ergEoMdsDh0hq/PLLJNBUe7IQGEn0OK/HHIZ+hGloRMK8H4tgL6uJ7ao+q0YLyQxWg2bJygDOvIAovXTJhtl3QkoxAjgTwrInvyPeT36o+3lXTViiO85S7eIqDiskSUbDWhvWjgqMNpUZPmxX/iOn8GdetIIZAPalbM044Ycm622wwhZ9JZMUdrtWWbr+qf1KwtSmtx4T7AuT3YRSEYJ4vy6i4JFa3gTcoG4/kA9ucXG/rfOavAfeOL9kSI5b/zY199c5kxXcVzfbgA/8arbqPkEaDTot2qh8kNhOkSm6BFI4ClAdRTn62R2j/v2YgL00zrnSOVXAC4FK4J6hVOC3UsiBYZWyPcP0zEc/La700k9AtAgVtnuH9mbyATkTR7+kHGJ1CFPKbVI9ZIhk1OzM77avI21VwAosHW3GfxlXglGVEVoByBWTETD99ywZ6UghOY+dSGsfPnwu+y0iBcNd7em6y2Q9NUdTDk1AXKA8z+ecPRcwLUAOkxKE2C/fNps/TBaHtJgwPlXXJ9QEa7yUCS6fFjC6JZqtDS24ICPFwl4qJbNUNetHz8v3O0CavdBO1UAug6bMvOyLM4x9XlbESCM39b53djWKvwT3No4LwfyHCGIKikkLP4IXYWYX5ohkC2iPFDjTTD+Cw87RLi7F1vPHzGgcvJUBAD0+a8tGJ/fEgASCAZzGJHXWFqz+swNisOudy0VtI//NvP4nd0+UApJIHYU0Vd92f4vN8OJxPTpYPynXQGJaBbyEGRaqT3Y/6YlIq4vDeYgyFw/gRbYC4Qa+0MG5TDrSxX63Ce/MgaTA/v+w1yi+U6iEt6PhXpqnS6Lh5HkcWFjkQKya8hIAPoGtf+GkdGkceb8DS+BnTyeovl0XAe5Wfri1WKgB9PJo9GAOojfFLe9GCjvPXATtNY106+71lpgvmKFdoez1rU7732iH3hnUlu3SaGvhg92jMuaCvvZxXbHoPXUIvAeLGI+tx+S4LEsCmWl1XIGVbW7sa+FPza65P9AhCNNxKbY7TsiQElqYD1+ovX3Q1z4zjgbEri8liQ8OozgrV1wZDc5gHk7LtEzjtpAmfKb6NvkTAABLHeYD0W9qQjh00SU22hc1oNNKdVaLvNSQJnApxCCFI8sMyNYmrRonSY/S27GcxzMGyzPmAjt3PKo8Iy9llRXbBd7jmywfMTltDsn46nJvOZZ8Ymw5S8iMjlitYJrUgEvqMWZ4zzQDzar9zRG/MIPZckoquTitTTZ1vP1eaAwvDm35OiMaW9fM4zBRmUvgTgRl7Co3VpucEOYr3KroGlrvb1NrvHt3AQsMse4fQ1U1LA5KAxzudJjQ71hZLR3yTuwcBTaQakNuwLE3fp9InhhFKi4V03m4kKaz86hpQ5mqzCB5uvGv7A5U5JFwfuT6QbS0bGOCdnCFoViox0Pbt1hDyMqFtHp7B1GScPlhtQfFNImbBLA3k2w6dem6vA40I/vretIyXHIPRJc0SKMcW9YNkzQFcxlYuYOmn7IUpcpEe+Gx7XwDYQoa6nARFIZuAb5hXkvrHowFGAeQtobt2xZyIDB/8R6tF4OiXXQYe0WrLzLT7+OdmjClp//98k302Lx8N2JqPWBORhbkR2rq8VP0qtaZwcYCzM58nkB6KbTFqrnqyjsFg7P2sKUWmRaxfkuvOeXT1YK/GP3q5ZsKjJB9KAXyCC7Z/oA6jLKPB1pP9yjc2dn2XxIe4+AlnZTVremr1ZE4pi2Mw2yBPGtcHSAYQ5XucbuCNpU1T28wV2j2qX9RxkNGGnL4nbzuKgfmP4OYpenpXZybdNaD7rSl+ZyWn0sNCsO2NGiTauXZqZh3kwMNCu10bH2J+Ac+nxpB1XenE1XFJWtIvGyF5JehRzAPWCVL/4tep7EvYiUled1jLdZbejtCQQ+BAUGkbRONKQYDlSJfcNCJRMpWaeQXXByc3++UGcjrXbCVYMl1vqX0gAsO0fuddaT8dYXr4CYEJZBFtnUAEmEKBTfmAAAA==";
23686
+ var referenceImage3DModelChat = {
23687
+ chat_id: "example-reference-image-3d-model",
23688
+ slug: "reference-image-3d-model",
23689
+ title: "example_chats.reference_image_3d_model.title",
23690
+ summary: "example_chats.reference_image_3d_model.summary",
23691
+ icon: "3dmodels",
23692
+ category: "design",
23693
+ keywords: ["3D model generation", "image to 3D", "Hi3D", "GLB", "product design"],
23694
+ follow_up_suggestions: [],
23695
+ messages: [
23696
+ {
23697
+ id: "c79b9ff0-7d58-4ed7-b860-4c427cdab6bd",
23698
+ role: "user",
23699
+ content: "example_chats.reference_image_3d_model.message_1",
23700
+ created_at: 1784071246
23701
+ },
23702
+ {
23703
+ id: "fbf595b9-2f71-4a7a-b544-56529bf9a38d",
23704
+ role: "assistant",
23705
+ content: "example_chats.reference_image_3d_model.message_2",
23706
+ created_at: 1784071268,
23707
+ category: "design",
23708
+ model_name: "Gemini 3 Flash"
23709
+ }
23710
+ ],
23711
+ embeds: [
23712
+ {
23713
+ embed_id: "85716124-48d5-4c4c-9919-3126b733b41e",
23714
+ type: "app_skill_use",
23715
+ content: `app_id: models3d
23716
+ skill_id: generate
23717
+ type: model3d
23718
+ status: finished
23719
+ input_mode: image
23720
+ prompt: "Create a textured 3D GLB model from the uploaded reference image."
23721
+ provider: Hi3D
23722
+ provider_model: Hi3D
23723
+ poster_url: "${MODEL3D_POSTER_DATA_URL}"
23724
+ files:
23725
+ master:
23726
+ size_bytes: 25847448
23727
+ format: glb
23728
+ mime_type: model/gltf-binary
23729
+ poster:
23730
+ size_bytes: 20690
23731
+ format: webp
23732
+ mime_type: image/webp
23733
+ preview:
23734
+ size_bytes: 25847448
23735
+ format: glb
23736
+ mime_type: model/gltf-binary
23737
+ optimized: false
23738
+ fallback_reason: preview_optimization_failed
23739
+ generated_at: "2026-07-14T23:20:46+00:00"
23740
+ provenance:
23741
+ ai_generated: true
23742
+ labeling: static_public_fixture`,
23743
+ parent_embed_id: null,
23744
+ embed_ids: null
23745
+ }
23746
+ ],
23747
+ metadata: {
23748
+ featured: true,
23749
+ order: 38,
23750
+ app_skill_examples: ["models3d.generate"]
23751
+ }
23752
+ };
23753
+
23630
23754
  // ../ui/src/demo_chats/data/example_chats/upcoming-reminders-list.ts
23631
23755
  var upcomingRemindersListChat = {
23632
23756
  chat_id: "example-upcoming-reminders-list",
@@ -28224,6 +28348,7 @@ var ALL_EXAMPLE_CHATS = [
28224
28348
  berlinDermatologyAppointmentsChat,
28225
28349
  productLaunchSynthLoopChat,
28226
28350
  privateWorkspaceDemoVideoChat,
28351
+ referenceImage3DModelChat,
28227
28352
  upcomingRemindersListChat,
28228
28353
  cancelTestReminderChat,
28229
28354
  fediverseActivitypubSocialSearchChat,
@@ -36863,6 +36988,20 @@ Like [Denis Villeneuve](wiki:Denis_Villeneuve)\u2019s work, *Contact* avoids the
36863
36988
  text: '```json\n{"type": "app_skill_use", "embed_id": "11ff377e-a0a2-40ed-81c3-9556b68142f3", "app_id": "videos", "skill_id": "search", "query": "retrieval augmented generation explained for beginners", "provider": "Brave"}\n```\n\nI\'ve found some excellent beginner-friendly videos that explain Retrieval-Augmented Generation (RAG). Here is a curated selection to get you started:\n\n[!](embed:youtube.com-OCK)\n[!](embed:youtube.com-20R)\n[!](embed:youtube.com-8SW)\n[!](embed:youtube.com-Fn4)\n[!](embed:youtube.com-rgp)\n\n### Which one should you watch first?\n\nI highly recommend starting with **[What is Retrieval-Augmented Generation (RAG)?](embed:youtube.com-OCK)** by **IBM Technology**.\n\n**Why this one first?**\n* **Conciseness:** At just 6 and a half minutes, it provides the most efficient "conceptual hook" without getting bogged down in implementation details.\n* **High-Level Clarity:** IBM\u2019s explanations are famous for being professional and easy to follow. They focus on the *why* (fixing LLM hallucinations and outdated data) before diving into the *what*.\n* **Authoritative:** It establishes a solid foundation from a reputable industry leader, which helps you better understand the more technical or "hands-on" videos later.\n\n### What to watch next?\n\nOnce you have the concept down, here are two paths depending on your interest:\n\n1. **For a better intuitive understanding:** Watch **[RAG Explained in 12 Minutes](embed:youtube.com-20R)** by **Aishwarya Srinivasan**. She uses a brilliant "Open-Book Exam" analogy that makes the whole process click instantly for most beginners.\n2. **For a technical overview:** If you want to see the "moving parts" (like vector databases and embeddings), check out **[RAG Explained For Beginners](embed:youtube.com-8SW)** by **KodeKloud**. It breaks the process down into a simple 3-step framework: Retrieval, Augmentation, and Generation.\n\nIf you\'re interested in building a RAG system yourself, I can help you find documentation for popular tools like [LangChain](wiki:LangChain) or LlamaIndex. Would you like to see how a simple RAG implementation looks in code?\n\n**Further exploration:**\n* [Check out more AI tutorials](/#settings/apps/videos)\n* [Search for RAG documentation](/#settings/apps/code)'
36864
36989
  }
36865
36990
  },
36991
+ reference_image_3d_model: {
36992
+ title: {
36993
+ text: "Reference Image to 3D Model"
36994
+ },
36995
+ summary: {
36996
+ text: "A 3D generation example that turns a reference image into a textured GLB model preview."
36997
+ },
36998
+ message_1: {
36999
+ text: "@skill:models3d:generate Turn this reference image into a textured 3D GLB model suitable for a product mockup."
37000
+ },
37001
+ message_2: {
37002
+ text: '```json\n{"type":"app_skill_use","embed_id":"85716124-48d5-4c4c-9919-3126b733b41e","app_id":"models3d","skill_id":"generate"}\n```\n\nI generated a textured GLB model from the reference image and attached the 3D model preview above.'
37003
+ }
37004
+ },
36866
37005
  right_to_repair_laws_eu_us: {
36867
37006
  title: {
36868
37007
  text: "Right-to-Repair Laws in the EU and US"
@@ -47252,6 +47391,84 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
47252
47391
  first_invites: {
47253
47392
  text: "First invites in Jan/Feb 2025"
47254
47393
  }
47394
+ },
47395
+ workflows: {
47396
+ template_share: {
47397
+ title: {
47398
+ text: "Share as template"
47399
+ },
47400
+ create_link: {
47401
+ text: "Create template link"
47402
+ },
47403
+ import: {
47404
+ text: "Import template"
47405
+ },
47406
+ complete_bindings: {
47407
+ text: "Complete requirements and enable"
47408
+ },
47409
+ revoked: {
47410
+ text: "Template access revoked."
47411
+ }
47412
+ },
47413
+ version_history: {
47414
+ title: {
47415
+ text: "Definition history"
47416
+ },
47417
+ retention: {
47418
+ text: "Keeps up to {max} definitions"
47419
+ },
47420
+ retention_loading: {
47421
+ text: "Loading retention..."
47422
+ },
47423
+ loading: {
47424
+ text: "Loading definition history..."
47425
+ },
47426
+ empty: {
47427
+ text: "No definition history is available yet."
47428
+ },
47429
+ version: {
47430
+ text: "Version {version}"
47431
+ },
47432
+ current: {
47433
+ text: "Current"
47434
+ },
47435
+ restored: {
47436
+ text: "Restored"
47437
+ },
47438
+ inspecting: {
47439
+ text: "Loading definition..."
47440
+ },
47441
+ inspecting_version: {
47442
+ text: "Version {version} graph"
47443
+ },
47444
+ nodes: {
47445
+ text: "nodes"
47446
+ },
47447
+ restore_as_new: {
47448
+ text: "Restore version {version} as new current version"
47449
+ },
47450
+ restore_explanation: {
47451
+ text: "This creates a new current version. The selected and existing versions remain unchanged."
47452
+ },
47453
+ confirm_restore: {
47454
+ text: "Create new current version"
47455
+ },
47456
+ restoring: {
47457
+ text: "Restoring..."
47458
+ },
47459
+ restore_success: {
47460
+ text: "Restored version {version} as a new current version."
47461
+ },
47462
+ load_failed: {
47463
+ text: "Failed to load definition history."
47464
+ },
47465
+ inspect_failed: {
47466
+ text: "Failed to load the selected definition."
47467
+ },
47468
+ restore_failed: {
47469
+ text: "Failed to restore the selected definition."
47470
+ }
47471
+ }
47255
47472
  }
47256
47473
  };
47257
47474
 
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getExtForLang,
4
4
  serializeToYaml
5
- } from "./chunk-CPJPAPQL.js";
5
+ } from "./chunk-6M52O6HO.js";
6
6
  import "./chunk-AXNRPVLE.js";
7
7
  export {
8
8
  getExtForLang,
package/dist/index.d.ts CHANGED
@@ -1235,6 +1235,8 @@ declare class OpenMatesClient {
1235
1235
  entryCount: number;
1236
1236
  }>;
1237
1237
  }>;
1238
+ private persistEncryptedSystemMessage;
1239
+ private persistPendingTaskUpdateJobs;
1238
1240
  private persistStreamedEmbeds;
1239
1241
  private persistPostProcessingMetadata;
1240
1242
  /**
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  renderSupportInfo,
30
30
  serializeToYaml,
31
31
  unwrapApiKeyMasterKey
32
- } from "./chunk-CPJPAPQL.js";
32
+ } from "./chunk-6M52O6HO.js";
33
33
  import "./chunk-AXNRPVLE.js";
34
34
 
35
35
  // src/generated/appSkills.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmates",
3
- "version": "0.14.8-alpha.15",
3
+ "version": "0.14.8-alpha.16",
4
4
  "description": "OpenMates CLI and SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",