@chibitek/chibi 0.1.170 → 0.1.172

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.
Files changed (76) hide show
  1. package/dist/assets/{abnfDiagram-O67JEVCF-Dwjw0xC0.js → abnfDiagram-O67JEVCF-CGoW52MW.js} +1 -1
  2. package/dist/assets/{arc-B__BIHzb.js → arc-DEkKLw4F.js} +1 -1
  3. package/dist/assets/{architectureDiagram-NJMV4G6O-DQy7n3Ga.js → architectureDiagram-NJMV4G6O-Cyi6MYs6.js} +1 -1
  4. package/dist/assets/{blockDiagram-BEXU5L5S-moFo2nSA.js → blockDiagram-BEXU5L5S-CddA0wa_.js} +1 -1
  5. package/dist/assets/{c4Diagram-YGBWAQC7-C-dF8UVl.js → c4Diagram-YGBWAQC7-Dg8q9n90.js} +1 -1
  6. package/dist/assets/channel-Dda8O6gd.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-DnF0mxAV.js → chunk-2Q5K7J3B-BSMub34c.js} +1 -1
  8. package/dist/assets/{chunk-3FUC2YCW-Dp8Dsifv.js → chunk-3FUC2YCW--Xc7Up5y.js} +2 -2
  9. package/dist/assets/{chunk-5VM5RSS4-D8fmZ4d8.js → chunk-5VM5RSS4-B9Ae4f30.js} +1 -1
  10. package/dist/assets/{chunk-GTNCS2PH-DNH2HZNQ.js → chunk-GTNCS2PH-Dh1To9G3.js} +1 -1
  11. package/dist/assets/{chunk-GWA4HPMP-BvL6j7pQ.js → chunk-GWA4HPMP-C_5eiV2n.js} +1 -1
  12. package/dist/assets/{chunk-JWPE2WC7-Co0gUA5M.js → chunk-JWPE2WC7-5wQuPsks.js} +1 -1
  13. package/dist/assets/{chunk-NETBCI7D-Deu9tpCF.js → chunk-NETBCI7D-BGXS05ur.js} +1 -1
  14. package/dist/assets/{chunk-WEXAMYUT-DKy8jAU5.js → chunk-WEXAMYUT-ClIMhmD5.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-DQhvP8zg.js → chunk-XXDRQBXY-CcKs11-L.js} +1 -1
  16. package/dist/assets/{classDiagram-v2-NBCMYWYE-mhv3hMMV.js → classDiagram-v2-NBCMYWYE-BCiLckGU.js} +1 -1
  17. package/dist/assets/{cose-bilkent-JH36ORCC-CR1nOQOe.js → cose-bilkent-JH36ORCC-JyAqT-ND.js} +1 -1
  18. package/dist/assets/{cynefin-OW5HDTMX-Ddgkhxgt.js → cynefin-OW5HDTMX-BznovZj0.js} +1 -1
  19. package/dist/assets/{cynefinDiagram-VND7K2PF-4KbkrNhB.js → cynefinDiagram-VND7K2PF-BDRrTIEB.js} +1 -1
  20. package/dist/assets/{dagre-6A5THRUB-DNxtjbvT.js → dagre-6A5THRUB-BbWguhLj.js} +1 -1
  21. package/dist/assets/{diagram-22UHCM2B-BRpvnOWX.js → diagram-22UHCM2B-BMk1KTBr.js} +1 -1
  22. package/dist/assets/{diagram-3UASUU5V-DsQVnKUb.js → diagram-3UASUU5V-ChnEP2in.js} +1 -1
  23. package/dist/assets/{diagram-ATOU4E4O-BRZ5455_.js → diagram-ATOU4E4O-C78imnTV.js} +1 -1
  24. package/dist/assets/{diagram-CDSNMT55-CidrDKp0.js → diagram-CDSNMT55-Dekx02f_.js} +1 -1
  25. package/dist/assets/{diagram-MLGK6HIB-1hkZ8SsK.js → diagram-MLGK6HIB-D2__fPwe.js} +1 -1
  26. package/dist/assets/{diagram-MPIPVDR6-D1dWGFqn.js → diagram-MPIPVDR6-C9l7MZm7.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-ZINNZB2B-DGjfEPY5.js → ebnfDiagram-ZINNZB2B-D4x0h3XT.js} +1 -1
  28. package/dist/assets/{elk-276RUBZZ-C5nvtTy7.js → elk-276RUBZZ-twChz5io.js} +1 -1
  29. package/dist/assets/{erDiagram-OPXOYQCR-CC0QBE1A.js → erDiagram-OPXOYQCR-BXEKWWMq.js} +1 -1
  30. package/dist/assets/{flowDiagram-KWPJA3E3-BthbP_HO.js → flowDiagram-KWPJA3E3-Ct-XrJMr.js} +1 -1
  31. package/dist/assets/{ganttDiagram-FUAMR5RP-BQ2wLSEI.js → ganttDiagram-FUAMR5RP-Bn92lxCs.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-X574FWY7-lTR8tNlq.js → gitGraphDiagram-X574FWY7-Dvfjo7cn.js} +1 -1
  33. package/dist/assets/{index-AWsWxgoq.js → index-CarzpTfN.js} +1 -1
  34. package/dist/assets/{index-VagspC1z.js → index-DvneZE6Z.js} +76 -73
  35. package/dist/assets/{infoDiagram-VRGFBTTK-BgI9ZuT_.js → infoDiagram-VRGFBTTK-MJ14OyNX.js} +1 -1
  36. package/dist/assets/{ishikawaDiagram-OU5B5YK6-qPe6Rhk2.js → ishikawaDiagram-OU5B5YK6-nf9xhTNX.js} +1 -1
  37. package/dist/assets/{journeyDiagram-ZHPQQLJL-CpbadZuP.js → journeyDiagram-ZHPQQLJL-DqfK6X2e.js} +1 -1
  38. package/dist/assets/{kanban-definition-PNTS6WVX-D9kCb3-t.js → kanban-definition-PNTS6WVX-CjUR1QYT.js} +1 -1
  39. package/dist/assets/{linear-CdW4LT3g.js → linear-dPqGsZwL.js} +1 -1
  40. package/dist/assets/{mermaid.core-BbPWTT56.js → mermaid.core-zL0DLPAI.js} +5 -5
  41. package/dist/assets/{mindmap-definition-NLK3R4M7-BtOaXxvb.js → mindmap-definition-NLK3R4M7-C1-Jqg3_.js} +1 -1
  42. package/dist/assets/{pegDiagram-GJSIUBJH-BtcTITAK.js → pegDiagram-GJSIUBJH-CFlvuAHC.js} +1 -1
  43. package/dist/assets/{pieDiagram-5QR66LMP-Ch_--zjt.js → pieDiagram-5QR66LMP-BvyT0IgF.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-O4NWA36T-Di8ygRal.js → quadrantDiagram-O4NWA36T-bg7JeR2c.js} +1 -1
  45. package/dist/assets/{railroadDiagram-XR7U4H2S-DxgesAmb.js → railroadDiagram-XR7U4H2S-dF2Nqim8.js} +1 -1
  46. package/dist/assets/{requirementDiagram-PLB6GJNP-DjH-jdCr.js → requirementDiagram-PLB6GJNP-CwVu_Or6.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-IPEJSGJF-ClNLceSc.js → sankeyDiagram-IPEJSGJF-wBC3-mRy.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-PO4LG4MO-Cip_EmtV.js → sequenceDiagram-PO4LG4MO-Bs9lHMf-.js} +1 -1
  49. package/dist/assets/{sizeCapture-INFHLROL-BzW2hkq8.js → sizeCapture-INFHLROL-d6YiGOIQ.js} +1 -1
  50. package/dist/assets/{stateDiagram-v2-GCMORJYK-CwHu7P19.js → stateDiagram-v2-GCMORJYK--f6dscuE.js} +1 -1
  51. package/dist/assets/{swimlanes-2SLR337P-DO9E9HUK.js → swimlanes-2SLR337P-CQpzBoAc.js} +1 -1
  52. package/dist/assets/swimlanesDiagram-TC7HE7FX-C1ko4o6t.js +8 -0
  53. package/dist/assets/{timeline-definition-EJHVYXUP-D2_E-i9h.js → timeline-definition-EJHVYXUP-DpoyUyqt.js} +1 -1
  54. package/dist/assets/{usecaseDiagram-POWQR4AR-oKBQ1FAG.js → usecaseDiagram-POWQR4AR-Dg7mR3GW.js} +1 -1
  55. package/dist/assets/{vennDiagram-UO4OBE2U-HEdQsC7L.js → vennDiagram-UO4OBE2U-CoEfTL7k.js} +1 -1
  56. package/dist/assets/{wardleyDiagram-VNRHLVJA-mNpRO8rd.js → wardleyDiagram-VNRHLVJA-24tIY01C.js} +1 -1
  57. package/dist/assets/{xychartDiagram-PMCCYNJV-uURjTK1X.js → xychartDiagram-PMCCYNJV-CsoqIRqn.js} +1 -1
  58. package/dist/handbook/atomic-handbook.html +1 -1
  59. package/dist/index.html +1 -1
  60. package/dist-server/companion/src/routes.js +36 -1
  61. package/dist-server/index.js +37509 -37213
  62. package/dist-server/openmausbot.js +6 -5
  63. package/dist-server/pair-cli.js +6 -5
  64. package/dist-server/server/drivers/acp/custom.js +3 -0
  65. package/dist-server/server/index.js +19 -118
  66. package/dist-server/server/message-file.js +28 -11
  67. package/dist-server/server/remote-desktop.js +142 -0
  68. package/dist-server/server/routes/message-file.js +115 -0
  69. package/dist-server/server/shared-deliverables.js +109 -0
  70. package/dist-server/server/skill-learn.js +4 -0
  71. package/dist-server/server/subscription-bots.js +3 -2
  72. package/dist-server/shared/provider-accounts.js +5 -5
  73. package/dist-server/shared/skill-command.js +16 -0
  74. package/package.json +1 -1
  75. package/dist/assets/channel-BQM13l0i.js +0 -1
  76. package/dist/assets/swimlanesDiagram-TC7HE7FX-UC0GNJzN.js +0 -8
@@ -19556,7 +19556,7 @@ var init_provider_accounts = __esm({
19556
19556
  // Kimi Code reads its whole data root (login included) from KIMI_CODE_HOME.
19557
19557
  { id: "kimi", kind: "signIn", label: "Kimi Code", billing: "subscription", driver: "kimiAgent", homeEnv: "KIMI_CODE_HOME", loginArgs: ["login"], signupUrl: "https://www.kimi.com/code" },
19558
19558
  // Droid resolves <FACTORY_HOME_OVERRIDE>/.factory, so the folder is a home, not the data root.
19559
- { id: "droid", kind: "signIn", label: "Factory Droid", billing: "subscription", driver: "droidAgent", homeEnv: "FACTORY_HOME_OVERRIDE", loginArgs: [], signupUrl: "https://factory.ai/pricing" },
19559
+ { id: "droid", kind: "signIn", label: "Factory Droid", billing: "subscription", driver: "droidAgent", homeEnv: "FACTORY_HOME_OVERRIDE", loginArgs: [], signupUrl: "https://factory.com/pricing" },
19560
19560
  {
19561
19561
  id: "anthropic",
19562
19562
  kind: "apiKey",
@@ -19567,7 +19567,7 @@ var init_provider_accounts = __esm({
19567
19567
  icon: "anthropic",
19568
19568
  config: {},
19569
19569
  keyTest: { provider: "anthropic" },
19570
- keyUrl: "https://console.anthropic.com/settings/keys"
19570
+ keyUrl: "https://platform.claude.com/settings/keys"
19571
19571
  },
19572
19572
  {
19573
19573
  id: "openai",
@@ -19605,7 +19605,7 @@ var init_provider_accounts = __esm({
19605
19605
  envVar: "PERPLEXITY_API_KEY",
19606
19606
  icon: "perplexity",
19607
19607
  config: { url: "https://api.perplexity.ai", apiKeyEnv: "PERPLEXITY_API_KEY", model: "sonar-pro", provider: "" },
19608
- keyUrl: "https://www.perplexity.ai/account/api/keys"
19608
+ keyUrl: "https://console.perplexity.ai/project/keys"
19609
19609
  },
19610
19610
  {
19611
19611
  // Alibaba's flat-price Qwen Coding Plan: one plan key serves Qwen, GLM, Kimi and MiniMax models.
@@ -19618,7 +19618,7 @@ var init_provider_accounts = __esm({
19618
19618
  icon: "qwen",
19619
19619
  config: { url: "https://coding-intl.dashscope.aliyuncs.com/v1", apiKeyEnv: "QWEN_CODING_PLAN_API_KEY", model: "qwen3.7-plus", provider: "" },
19620
19620
  keyTest: { provider: "openaiCompat", url: "https://coding-intl.dashscope.aliyuncs.com/v1" },
19621
- keyUrl: "https://modelstudio.console.alibabacloud.com/",
19621
+ keyUrl: "https://modelstudio.console.alibabacloud.com/ap-southeast-1/subscription/coding-plan",
19622
19622
  signupUrl: "https://www.alibabacloud.com/help/en/model-studio/coding-plan"
19623
19623
  },
19624
19624
  {
@@ -19683,7 +19683,7 @@ var init_provider_accounts = __esm({
19683
19683
  driver: "opencodeGo",
19684
19684
  envVar: "OPENCODE_API_KEY",
19685
19685
  config: {},
19686
- keyUrl: "https://opencode.ai/docs/providers/",
19686
+ keyUrl: "https://opencode.ai/go",
19687
19687
  signupUrl: "https://opencode.ai/go"
19688
19688
  }
19689
19689
  ];
@@ -56503,6 +56503,7 @@ var init_custom = __esm({
56503
56503
  transformEnv: (env, config2, instanceId) => {
56504
56504
  applyAtomicEngineAccounts(env, instanceId, config2.cli);
56505
56505
  lockAtomicEngineMemory(env, instanceId, config2.cli);
56506
+ if (isAtomicEngine(instanceId, config2.cli)) env.HERMES_ACP_SKIP_CONFIGURED_MCP = "1";
56506
56507
  },
56507
56508
  pickAuthMethod: () => null,
56508
56509
  authFailure: "continue",
@@ -19556,7 +19556,7 @@ var init_provider_accounts = __esm({
19556
19556
  // Kimi Code reads its whole data root (login included) from KIMI_CODE_HOME.
19557
19557
  { id: "kimi", kind: "signIn", label: "Kimi Code", billing: "subscription", driver: "kimiAgent", homeEnv: "KIMI_CODE_HOME", loginArgs: ["login"], signupUrl: "https://www.kimi.com/code" },
19558
19558
  // Droid resolves <FACTORY_HOME_OVERRIDE>/.factory, so the folder is a home, not the data root.
19559
- { id: "droid", kind: "signIn", label: "Factory Droid", billing: "subscription", driver: "droidAgent", homeEnv: "FACTORY_HOME_OVERRIDE", loginArgs: [], signupUrl: "https://factory.ai/pricing" },
19559
+ { id: "droid", kind: "signIn", label: "Factory Droid", billing: "subscription", driver: "droidAgent", homeEnv: "FACTORY_HOME_OVERRIDE", loginArgs: [], signupUrl: "https://factory.com/pricing" },
19560
19560
  {
19561
19561
  id: "anthropic",
19562
19562
  kind: "apiKey",
@@ -19567,7 +19567,7 @@ var init_provider_accounts = __esm({
19567
19567
  icon: "anthropic",
19568
19568
  config: {},
19569
19569
  keyTest: { provider: "anthropic" },
19570
- keyUrl: "https://console.anthropic.com/settings/keys"
19570
+ keyUrl: "https://platform.claude.com/settings/keys"
19571
19571
  },
19572
19572
  {
19573
19573
  id: "openai",
@@ -19605,7 +19605,7 @@ var init_provider_accounts = __esm({
19605
19605
  envVar: "PERPLEXITY_API_KEY",
19606
19606
  icon: "perplexity",
19607
19607
  config: { url: "https://api.perplexity.ai", apiKeyEnv: "PERPLEXITY_API_KEY", model: "sonar-pro", provider: "" },
19608
- keyUrl: "https://www.perplexity.ai/account/api/keys"
19608
+ keyUrl: "https://console.perplexity.ai/project/keys"
19609
19609
  },
19610
19610
  {
19611
19611
  // Alibaba's flat-price Qwen Coding Plan: one plan key serves Qwen, GLM, Kimi and MiniMax models.
@@ -19618,7 +19618,7 @@ var init_provider_accounts = __esm({
19618
19618
  icon: "qwen",
19619
19619
  config: { url: "https://coding-intl.dashscope.aliyuncs.com/v1", apiKeyEnv: "QWEN_CODING_PLAN_API_KEY", model: "qwen3.7-plus", provider: "" },
19620
19620
  keyTest: { provider: "openaiCompat", url: "https://coding-intl.dashscope.aliyuncs.com/v1" },
19621
- keyUrl: "https://modelstudio.console.alibabacloud.com/",
19621
+ keyUrl: "https://modelstudio.console.alibabacloud.com/ap-southeast-1/subscription/coding-plan",
19622
19622
  signupUrl: "https://www.alibabacloud.com/help/en/model-studio/coding-plan"
19623
19623
  },
19624
19624
  {
@@ -19683,7 +19683,7 @@ var init_provider_accounts = __esm({
19683
19683
  driver: "opencodeGo",
19684
19684
  envVar: "OPENCODE_API_KEY",
19685
19685
  config: {},
19686
- keyUrl: "https://opencode.ai/docs/providers/",
19686
+ keyUrl: "https://opencode.ai/go",
19687
19687
  signupUrl: "https://opencode.ai/go"
19688
19688
  }
19689
19689
  ];
@@ -56503,6 +56503,7 @@ var init_custom = __esm({
56503
56503
  transformEnv: (env, config2, instanceId) => {
56504
56504
  applyAtomicEngineAccounts(env, instanceId, config2.cli);
56505
56505
  lockAtomicEngineMemory(env, instanceId, config2.cli);
56506
+ if (isAtomicEngine(instanceId, config2.cli)) env.HERMES_ACP_SKIP_CONFIGURED_MCP = "1";
56506
56507
  },
56507
56508
  pickAuthMethod: () => null,
56508
56509
  authFailure: "continue",
@@ -142,6 +142,9 @@ const support = {
142
142
  transformEnv: (env, config, instanceId) => {
143
143
  applyAtomicEngineAccounts(env, instanceId, config.cli);
144
144
  lockAtomicEngineMemory(env, instanceId, config.cli);
145
+ // Atomic owns session mounts; ambient discovery must not race the same names.
146
+ if (isAtomicEngine(instanceId, config.cli))
147
+ env.HERMES_ACP_SKIP_CONFIGURED_MCP = "1";
145
148
  },
146
149
  pickAuthMethod: () => null,
147
150
  authFailure: "continue",
@@ -1,3 +1,5 @@
1
+ import { createMessageFileRoutes } from "./routes/message-file.js";
2
+ import { SharedDeliverables, createSharedDeliverablesRoutes, sharedDeliverableAccess } from "./shared-deliverables.js";
1
3
  import { connectionCredentialServer } from "./connection-credential.js";
2
4
  import { requestCredentialCard } from "./credential-card-request.js";
3
5
  import { invalidateBrokenCheckpoint } from "./checkpoint-recovery.js";
@@ -50,7 +52,6 @@ import { ApprovalGrantStore, describeScope, matchGrant } from "./approval-grants
50
52
  import { validateBotCwd } from "./bot-cwd.js";
51
53
  import { ATTACHMENTS_DIR, attachmentExists, cleanupStaleAttachmentPartials, deleteAttachment, extensionForMime, FILE_MAX_BYTES, IMAGE_MAX_BYTES, readAttachment, saveFile, saveImage, saveImageUpload, validateAttachmentUploadId, } from "./attachments.js";
52
54
  import { companyCheck, companyContext } from "./company-document-check.js";
53
- import { messageFileDisposition, messageFileDownloadName, messageAttachmentName, messageFileRoots, messageImageTargetAt, messageReferencesFile, openMessageFile, } from "./message-file.js";
54
55
  import { avatarGenerationRequestSchema, avatarGenerationStateMatches, avatarImageStatus, snapshotAvatarGenerationState } from "./avatar-image.js";
55
56
  import { generateRoutedAvatar, installAvatarSubscriptionRoute } from "./avatar-subscription.js";
56
57
  import { fitsOnOneLine, parseBotProfilePatch } from "./bot-profile.js";
@@ -157,11 +158,12 @@ import { applyStagedSkillWrite, applySkillWriteWithReceipt, getStagedSkillWrite,
157
158
  import { resolveSkillImport, skillImportSchema } from "./skill-fetch.js";
158
159
  import { pickBotName } from "./names.js";
159
160
  import { botSkillTemplateSchema, newBotDefaultsSchema, resolveBotCreationDefaults } from "./new-bot-defaults.js";
160
- import { expandLearnTurnText, learnSource } from "./skill-learn.js";
161
+ import { expandSkillTurnText, learnSource } from "./skill-learn.js";
161
162
  import { expandSetupTurnText, setupModeActive, setupSystemPrompt } from "./setup-mode.js";
162
163
  import { checkSoulDrift, readSoulDrift, soulFile, writeSoulMirror } from "./bot-folder.js";
163
164
  import { buildSystemPrompt, userProfileSystemPrompt, computerPrompt, accountsPrompt, customMcpPrompt, CREDENTIAL_PROMPT, mentionPrompt, THREADS_PROMPT, LEARN_PROMPT, PROFILE_PROMPT, ROUTINE_PROMPT, ROUTINE_EXECUTION_PROMPT, WEBHOOK_PROMPT, } from "./system-prompt.js";
164
165
  import { readCuaConnection, readCuaUnavailableReason, gatedLocalComputer } from "./local-computer.js";
166
+ import { remoteDesktopRoute } from "./remote-desktop.js";
165
167
  import { discoverExistingPerBotLocalVms, localVmInventoryEntry, shouldArmLocalVmIdle, } from "./local-vm-inventory.js";
166
168
  import { ATOMIC_BACKUP_DIR, BROWSER_BACKUP_INTERVAL_MS, backupBrowserProfiles, localVmWorkspaces } from "./atomic-backup.js";
167
169
  import { LocalVmIdleTimer } from "./local-vm-idle.js";
@@ -2664,12 +2666,13 @@ function subscriptionModel(bot, candidate, wanted) {
2664
2666
  const provider = subscriptionProvider(bot.subscription?.provider);
2665
2667
  if (!provider)
2666
2668
  return undefined;
2669
+ const usable = (model) => !engineResting(candidate, model);
2667
2670
  const flagship = wanted !== undefined ? undefined : provider.host ? hostDefaultModel(provider, candidate) : frontierChoice(bot, candidate)?.model;
2668
- if (flagship)
2671
+ if (flagship && usable(flagship))
2669
2672
  return flagship;
2670
2673
  return bestProviderModel({
2671
2674
  provider, candidate, wanted: wanted ?? engineTierClasses(cfg.engineTiers)[botTier(bot)],
2672
- classOf: (model) => modelClassOf(candidate.driverKind, model), ranked: rankedModelIds(candidate.driverKind),
2675
+ classOf: (model) => modelClassOf(candidate.driverKind, model), ranked: rankedModelIds(candidate.driverKind), usable,
2673
2676
  });
2674
2677
  }
2675
2678
  /** Thrown when a subscription bot's turn has no account of its provider
@@ -10041,7 +10044,7 @@ async function startTurn(botId, text, opts) {
10041
10044
  // the bot's first tool call (desktop attachments are host paths).
10042
10045
  const attachedText = commsDepth === 0 ? await withZipListings(providerText) : providerText;
10043
10046
  const setupText = agentsMounted ? expandSetupTurnText(attachedText) : attachedText;
10044
- const userTurnText = withAssistantProvenance(promptWithReply(skillAuthoring ? expandLearnTurnText(setupText) : setupText, opts?.replyTo, cfg.profile?.name?.trim() || "User"), userMessage.sender, cfg.profile?.name?.trim() || "the person");
10047
+ const userTurnText = withAssistantProvenance(promptWithReply(expandSkillTurnText(setupText, skillAuthoring), opts?.replyTo, cfg.profile?.name?.trim() || "User"), userMessage.sender, cfg.profile?.name?.trim() || "the person");
10045
10048
  // Decided again at dispatch when setup outlasted a soul edit (config).
10046
10049
  const decideContext = (config) => {
10047
10050
  const handedStale = Boolean(handed && (!unseen || (externalUpdate && handed.config !== config)));
@@ -12536,7 +12539,7 @@ failover) {
12536
12539
  .filter(Boolean)
12537
12540
  .join("\n");
12538
12541
  const latestUserText = usesNativeImageInput ? resolvedLatestImages.text : latestUser?.text;
12539
- const learnTurn = skillAuthoring && latestUserText ? expandLearnTurnText(latestUserText) : "";
12542
+ const learnTurn = latestUserText ? expandSkillTurnText(latestUserText, skillAuthoring) : "";
12540
12543
  const learnBlock = learnTurn && learnTurn !== latestUserText ? `\n\n${learnTurn}` : "";
12541
12544
  const addressedRequest = orchestration?.roomHandoffId ? roomHandoffs.nodes.get(orchestration.roomHandoffId) : undefined;
12542
12545
  // The room transcript already carries recent requests and reports. Repeat
@@ -16167,6 +16170,14 @@ ROUTES.push(createThreadStopRoutes({
16167
16170
  routines?.activeBotRunForBot(botId)?.threadId === threadId,
16168
16171
  stop: stopBotThread,
16169
16172
  }));
16173
+ const sharedDeliverables = new SharedDeliverables(DATA_DIR);
16174
+ const deliverableAccess = (senderId, auth) => sharedDeliverableAccess(sharedDeliverables, {
16175
+ dataDir: DATA_DIR, pawRoot: paw.root, store, senderId,
16176
+ canSeeBot: auth ? (id) => visibleTo(viewerFor(auth)).bot(id) : () => true,
16177
+ canSeeGroup: auth ? (id) => visibleTo(viewerFor(auth)).group(id) : () => true,
16178
+ });
16179
+ ROUTES.push(createSharedDeliverablesRoutes(sharedDeliverables, () => deliverableAccess(undefined).sharedBlockedRoots ?? []));
16180
+ ROUTES.push(createMessageFileRoutes({ store, attachments: ATTACHMENTS_DIR, workspaceDir, access: deliverableAccess }));
16170
16181
  ROUTES.push(createPinOrderRoutes({ store, visible: (auth) => visibleTo(viewerFor(auth)), personalHidden: personalBotHidden, fromBot: (req) => authorizedInternalCapability(req.headers.authorization) !== null }));
16171
16182
  // Linked repos: Settings > Repos, per-thread links and worktrees. See
16172
16183
  // server/routes/repos.ts for the routes and docs/chibitek/README.md.
@@ -19213,118 +19224,6 @@ const handleRequest = async (req, res) => {
19213
19224
  });
19214
19225
  return res.end(bytes);
19215
19226
  }
19216
- // Download one local file only when this exact stored message grants it:
19217
- // a bot must render a Markdown link or carry a generated-image attachment,
19218
- // while a user message must carry the standalone composer tag. The bot
19219
- // branch derives conversation/workspace roots; the user branch is limited
19220
- // to OpenMausBot's private attachment directory. This is deliberately not
19221
- // a general path reader.
19222
- m = path.match(/^\/api\/threads\/([\w-]+)\/messages\/([\w-]+)\/file$/);
19223
- const streamsMessageImage = Boolean(m && method === "GET" && url.searchParams.get("preview") === "1");
19224
- if (m && (method === "POST" || streamsMessageImage)) {
19225
- if (method === "POST" && !String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) {
19226
- return json(res, 415, { error: "content-type must be application/json" });
19227
- }
19228
- const threadId = m[1];
19229
- const directBot = store.botByThread(threadId);
19230
- const group = directBot ? undefined : store.groupByThread(threadId);
19231
- if (!directBot && !group)
19232
- return json(res, 404, { error: "no such conversation" });
19233
- const message = store.messagesFor(threadId).find((candidate) => candidate.id === m[2]);
19234
- if (!message)
19235
- return json(res, 404, { error: "no such message" });
19236
- if (message.kind !== "text") {
19237
- return json(res, 403, { error: "that message does not share this file" });
19238
- }
19239
- const body = method === "POST" ? await readBody(req) : null;
19240
- const rawReference = streamsMessageImage ? url.searchParams.get("ref") : null;
19241
- if (streamsMessageImage && (!rawReference || !/^\d+$/.test(rawReference))) {
19242
- return json(res, 400, { error: "ref must identify a rendered image" });
19243
- }
19244
- const href = method === "POST"
19245
- ? (typeof body.path === "string" ? body.path : "")
19246
- : messageImageTargetAt(message.text ?? "", Number(rawReference));
19247
- if (!href)
19248
- return json(res, 400, { error: "path is required" });
19249
- // Generated image paths are durable capabilities on this exact message.
19250
- // They do not grant access to arbitrary workspace files or Markdown refs.
19251
- const generatedImage = method === "POST" && message.role === "bot" &&
19252
- message.attachments?.some((attachment) => attachment.kind === "image" && attachment.path === href) === true;
19253
- let roots;
19254
- let downloadName;
19255
- if (generatedImage) {
19256
- roots = [ATTACHMENTS_DIR];
19257
- }
19258
- else if (message.role === "user") {
19259
- downloadName = messageAttachmentName(message.text ?? "", href) ?? undefined;
19260
- if (!downloadName) {
19261
- return json(res, 403, { error: "that message does not share this file" });
19262
- }
19263
- roots = [ATTACHMENTS_DIR];
19264
- }
19265
- else {
19266
- if (!messageReferencesFile(message.text ?? "", href)) {
19267
- return json(res, 403, { error: "that bot message does not link to this file" });
19268
- }
19269
- const senderId = directBot?.id ?? message.from?.botId;
19270
- // The persisted bot-role message is the author record. Membership is
19271
- // intentionally not consulted: removing a bot must not break files it
19272
- // already shared in channel history.
19273
- if (!senderId) {
19274
- return json(res, 403, { error: "the file's bot author could not be verified" });
19275
- }
19276
- let pinnedCwd;
19277
- let configuredCwd;
19278
- if (directBot) {
19279
- pinnedCwd = store.taskByThread(directBot.id, threadId)?.cwd;
19280
- configuredCwd = directBot.cwd;
19281
- }
19282
- else if (group) {
19283
- const task = store.groupTaskByThread(group.id, threadId);
19284
- pinnedCwd = task ? task.pinnedCwd : group.threadId === threadId ? group.pinnedCwd : undefined;
19285
- configuredCwd = group.cwd;
19286
- }
19287
- roots = messageFileRoots({
19288
- senderWorkspace: workspaceDir(senderId),
19289
- attachments: ATTACHMENTS_DIR,
19290
- pinnedCwd,
19291
- configuredCwd,
19292
- });
19293
- }
19294
- const file = await openMessageFile(href, roots);
19295
- if ((streamsMessageImage || generatedImage) && !file.mime.startsWith("image/")) {
19296
- await file.handle.close();
19297
- return json(res, 415, { error: "only images can be previewed here" });
19298
- }
19299
- res.writeHead(200, {
19300
- "content-type": file.mime,
19301
- "content-length": String(file.bytes),
19302
- ...(streamsMessageImage
19303
- ? { "content-disposition": "inline" }
19304
- : { "content-disposition": messageFileDisposition(messageFileDownloadName(downloadName, file.name)) }),
19305
- "cache-control": streamsMessageImage ? "private, max-age=3600" : "private, no-store",
19306
- "cdn-cache-control": "no-store",
19307
- "cloudflare-cdn-cache-control": "no-store",
19308
- pragma: "no-cache",
19309
- vary: "Authorization",
19310
- "x-content-type-options": "nosniff",
19311
- ...(streamsMessageImage
19312
- ? {
19313
- "cross-origin-resource-policy": "same-origin",
19314
- "referrer-policy": "no-referrer",
19315
- }
19316
- : {}),
19317
- });
19318
- if (file.bytes === 0) {
19319
- await file.handle.close();
19320
- return res.end();
19321
- }
19322
- const stream = file.handle.createReadStream({ start: 0, end: file.bytes - 1, autoClose: true });
19323
- stream.on("error", () => res.destroy());
19324
- res.on("close", () => stream.destroy());
19325
- stream.pipe(res);
19326
- return;
19327
- }
19328
19227
  // ── image attachments ────────────────────────────────────────────────
19329
19228
  // Pasted/dropped images are stored as files and referenced by path in
19330
19229
  // the prompt (<attached-image path="…"/>); this pair of routes is the
@@ -25136,6 +25035,8 @@ const handleRequest = async (req, res) => {
25136
25035
  }
25137
25036
  return json(res, 405, { error: "method not allowed" });
25138
25037
  }
25038
+ if (await remoteDesktopRoute(path, method, req, res, { store, surface: computerPreviewSurface, controlHeld: (id) => botComputerControlSnapshot(id).held }))
25039
+ return;
25139
25040
  m = path.match(/^\/api\/bots\/([\w-]+)\/computer\/viewer-close$/);
25140
25041
  if (m && method === "POST") {
25141
25042
  const bot = computerPreviewBot(m[1], url);
@@ -343,16 +343,25 @@ function mimeFor(path) {
343
343
  default: return "application/octet-stream";
344
344
  }
345
345
  }
346
- /**
347
- * Open a message-linked file under one of the supplied roots. The returned
348
- * handle, not the pathname, is what the caller streams. We canonicalise both
349
- * before and after opening and compare the handle with the post-open path;
350
- * this rejects symlink escapes and directory swaps instead of checking a
351
- * path and then opening a potentially different file.
352
- */
353
- export async function openMessageFile(href, roots) {
346
+ export async function openMessageFile(href, roots, access = {}) {
354
347
  const requested = referencedPath(href);
355
- const canonicalRoots = (await Promise.all(roots.map(async (root) => {
348
+ const canonicalize = async (paths) => Promise.all(paths.map(async (path) => {
349
+ try {
350
+ return await realpath(path);
351
+ }
352
+ catch {
353
+ return resolve(path);
354
+ }
355
+ }));
356
+ const blocked = [...(access.blockedRoots ?? []), ...await canonicalize(access.blockedRoots ?? [])];
357
+ const sharedBlocked = [...(access.sharedBlockedRoots ?? []), ...await canonicalize(access.sharedBlockedRoots ?? [])];
358
+ const isBlocked = (paths, candidate) => paths.some((root) => containedBy(resolve(root), candidate));
359
+ // Relative links keep their original conversation-relative meaning.
360
+ const shared = isAbsolute(requested) ? access.sharedRoots ?? [] : [];
361
+ const boundaries = await canonicalize(access.sharedRootBoundaries ?? []);
362
+ const canonicalShared = (await canonicalize(shared)).filter(root => !boundaries.some(boundary => containedBy(root, boundary)));
363
+ const canonicalOrdinary = await canonicalize(roots);
364
+ const canonicalRoots = (await Promise.all([...roots, ...canonicalShared].map(async (root) => {
356
365
  try {
357
366
  const canonical = await realpath(root);
358
367
  return (await stat(canonical)).isDirectory() ? canonical : null;
@@ -375,7 +384,15 @@ export async function openMessageFile(href, roots) {
375
384
  catch {
376
385
  continue;
377
386
  }
378
- const root = canonicalRoots.find((allowed) => containedBy(allowed, canonicalBefore));
387
+ const permitted = (canonical) => {
388
+ if (isBlocked(blocked, candidate) || isBlocked(blocked, canonical))
389
+ return false;
390
+ if (canonicalOrdinary.some((root) => containedBy(root, canonical)))
391
+ return true;
392
+ return canonicalShared.some((root) => containedBy(root, canonical))
393
+ && !isBlocked(sharedBlocked, candidate) && !isBlocked(sharedBlocked, canonical);
394
+ };
395
+ const root = permitted(canonicalBefore) && canonicalRoots.find((allowed) => containedBy(allowed, canonicalBefore));
379
396
  if (!root) {
380
397
  sawOutsideRoot = true;
381
398
  continue;
@@ -391,7 +408,7 @@ export async function openMessageFile(href, roots) {
391
408
  throw statusError(413, `file exceeds ${MESSAGE_FILE_MAX_BYTES} bytes`);
392
409
  }
393
410
  const canonicalAfter = await realpath(candidate);
394
- if (!containedBy(root, canonicalAfter))
411
+ if (!containedBy(root, canonicalAfter) || !permitted(canonicalAfter))
395
412
  throw statusError(403, "the linked file is outside this conversation's workspace");
396
413
  const after = await stat(canonicalAfter);
397
414
  if (opened.dev !== after.dev || opened.ino !== after.ino) {
@@ -0,0 +1,142 @@
1
+ // This Mac from the phone: a picture of the whole screen, and taps, keys and
2
+ // typing sent back through the same Cua Driver the bots already use. No VNC
3
+ // server and no second sign-in. The gates are the companion's per-device
4
+ // remote desktop capability and the person's Take control, which pauses the
5
+ // bot so a tap never races its next action.
6
+ //
7
+ // ponytail: one still frame per request (about 1 per second on a Retina Mac).
8
+ // A streamed video path is the upgrade if watching motion ever matters.
9
+ import { execFile } from "node:child_process";
10
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { promisify } from "node:util";
14
+ import { z } from "zod";
15
+ import { json, readBody } from "./harness/http.js";
16
+ import { readCuaConnection, readCuaUnavailableReason } from "./local-computer.js";
17
+ const run = promisify(execFile);
18
+ const coordinate = z.number().finite().min(0).max(100_000);
19
+ const key = z.string().regex(/^[a-z0-9]{1,12}$/i);
20
+ const DESKTOP = { kind: "desktop", display_id: "primary" };
21
+ export const remoteInputSchema = z.discriminatedUnion("type", [
22
+ z.object({
23
+ type: z.literal("click"),
24
+ x: coordinate,
25
+ y: coordinate,
26
+ button: z.enum(["left", "right"]).default("left"),
27
+ count: z.number().int().min(1).max(2).default(1),
28
+ }),
29
+ z.object({ type: z.literal("type"), text: z.string().min(1).max(2_000) }),
30
+ z.object({
31
+ type: z.literal("key"),
32
+ key,
33
+ modifiers: z.array(z.enum(["cmd", "shift", "option", "ctrl"])).max(4).default([]),
34
+ }),
35
+ z.object({
36
+ type: z.literal("scroll"),
37
+ x: coordinate,
38
+ y: coordinate,
39
+ direction: z.enum(["up", "down", "left", "right"]),
40
+ amount: z.number().int().min(1).max(20).default(3),
41
+ }),
42
+ ]);
43
+ export function hostCuaCall(connection) {
44
+ const socket = connection.args[connection.args.indexOf("--socket") + 1];
45
+ if (!connection.args.includes("--socket") || !socket)
46
+ throw new Error("Cua Driver has no socket for this computer");
47
+ return async (tool, args, screenshotOut) => {
48
+ const { stdout } = await run(connection.command, ["call", tool, JSON.stringify(args), "--socket", socket, ...(screenshotOut ? ["--screenshot-out-file", screenshotOut] : [])], { env: { ...process.env, ...connection.env }, timeout: 15_000, maxBuffer: 4 * 1024 * 1024 });
49
+ return JSON.parse(stdout);
50
+ };
51
+ }
52
+ /** The whole primary display. `width`/`height` are the coordinate space
53
+ * clicks use; the image itself is a smaller JPEG to keep it phone-sized. */
54
+ export async function remoteFrame(call, shrink = sipsJpeg) {
55
+ const dir = await mkdtemp(join(tmpdir(), "atomic-remote-"));
56
+ try {
57
+ const png = join(dir, "screen.png");
58
+ const state = z.object({ screenshot_width: z.number().int().positive(), screenshot_height: z.number().int().positive() })
59
+ .parse(await call("get_desktop_state", {}, png));
60
+ const jpeg = join(dir, "screen.jpg");
61
+ await shrink(png, jpeg);
62
+ return {
63
+ image: `data:image/jpeg;base64,${(await readFile(jpeg)).toString("base64")}`,
64
+ width: state.screenshot_width,
65
+ height: state.screenshot_height,
66
+ };
67
+ }
68
+ finally {
69
+ // The picture is the person's screen: never leave it in a temp folder.
70
+ await rm(dir, { recursive: true, force: true });
71
+ }
72
+ }
73
+ async function sipsJpeg(input, output) {
74
+ await run("/usr/bin/sips", ["-s", "format", "jpeg", "-s", "formatOptions", "60", "-Z", "1600", input, "--out", output], { timeout: 15_000 });
75
+ }
76
+ /** Keys and typing go to the frontmost app, the one the person last tapped. */
77
+ async function frontmostPid(call) {
78
+ const { apps } = z.object({ apps: z.array(z.object({ pid: z.number().int().positive(), active: z.boolean().optional() }).passthrough()) })
79
+ .parse(await call("list_apps", {}));
80
+ const active = apps.find((app) => app.active);
81
+ if (!active)
82
+ throw Object.assign(new Error("No app is in front on this computer; tap a window first"), { status: 409 });
83
+ return active.pid;
84
+ }
85
+ export async function remoteInput(call, input) {
86
+ switch (input.type) {
87
+ case "click":
88
+ await call("click", { target: DESKTOP, x: input.x, y: input.y, button: input.button, count: input.count });
89
+ return;
90
+ case "scroll":
91
+ await call("scroll", { target: DESKTOP, x: input.x, y: input.y, direction: input.direction, amount: input.amount });
92
+ return;
93
+ case "type":
94
+ await call("type_text", { pid: await frontmostPid(call), text: input.text });
95
+ return;
96
+ case "key":
97
+ await call("press_key", { pid: await frontmostPid(call), key: input.key, modifiers: input.modifiers });
98
+ return;
99
+ }
100
+ }
101
+ /** `POST /api/bots/:id/computer/remote-frame|remote-input`. The companion
102
+ * admits these only for a device with remote desktop access; input also needs
103
+ * the person to hold Take control, which pauses the bot. */
104
+ export async function remoteDesktopRoute(path, method, req, res, deps) {
105
+ const m = path.match(/^\/api\/bots\/([\w-]+)\/computer\/remote-(frame|input)$/);
106
+ if (!m || method !== "POST")
107
+ return false;
108
+ const reply = (status, body) => (json(res, status, body), true);
109
+ const bot = deps.store.bot(m[1]);
110
+ if (!bot)
111
+ return reply(404, { error: "no such bot" });
112
+ if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) {
113
+ return reply(415, { error: "content-type must be application/json" });
114
+ }
115
+ if ((deps.platform ?? process.platform) !== "darwin" || await deps.surface(bot, bot.threadId) !== "local") {
116
+ return reply(409, { error: "Phone remote control works for This Mac for now. Choose This Mac for this bot, or open the cloud desktop." });
117
+ }
118
+ const call = (deps.cua ?? hostCall)();
119
+ if (!call)
120
+ return reply(409, { error: readCuaUnavailableReason() ?? "CUA Driver is not ready on this computer" });
121
+ res.setHeader("cache-control", "private, no-store");
122
+ try {
123
+ if (m[2] === "frame")
124
+ return reply(200, await remoteFrame(call));
125
+ if (!deps.controlHeld(bot.id))
126
+ return reply(409, { error: "Take control before using this computer" });
127
+ const input = remoteInputSchema.safeParse(await readBody(req));
128
+ if (!input.success)
129
+ return reply(400, { error: "invalid remote input" });
130
+ await remoteInput(call, input.data);
131
+ return reply(200, { ok: true });
132
+ }
133
+ catch (error) {
134
+ if (error.status !== 409)
135
+ throw error;
136
+ return reply(409, { error: error.message });
137
+ }
138
+ }
139
+ function hostCall() {
140
+ const connection = readCuaConnection();
141
+ return connection ? hostCuaCall(connection) : null;
142
+ }
@@ -0,0 +1,115 @@
1
+ import { messageAttachmentName, messageFileDisposition, messageFileDownloadName, messageFileRoots, messageImageTargetAt, messageReferencesFile, openMessageFile } from "../message-file.js";
2
+ import { PASS } from "./table.js";
3
+ export function createMessageFileRoutes({ store, attachments: ATTACHMENTS_DIR, workspaceDir, access }) {
4
+ return async ({ req, res, url, path, method, auth, json, readBody }) => {
5
+ const m = path.match(/^\/api\/threads\/([\w-]+)\/messages\/([\w-]+)\/file$/);
6
+ const streamsMessageImage = Boolean(m && method === "GET" && url.searchParams.get("preview") === "1");
7
+ if (m && (method === "POST" || streamsMessageImage)) {
8
+ if (method === "POST" && !String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) {
9
+ return json(res, 415, { error: "content-type must be application/json" });
10
+ }
11
+ const threadId = m[1];
12
+ const directBot = store.botByThread(threadId);
13
+ const group = directBot ? undefined : store.groupByThread(threadId);
14
+ if (!directBot && !group)
15
+ return json(res, 404, { error: "no such conversation" });
16
+ const message = store.messagesFor(threadId).find((candidate) => candidate.id === m[2]);
17
+ if (!message)
18
+ return json(res, 404, { error: "no such message" });
19
+ if (message.kind !== "text") {
20
+ return json(res, 403, { error: "that message does not share this file" });
21
+ }
22
+ const body = method === "POST" ? await readBody(req) : null;
23
+ const rawReference = streamsMessageImage ? url.searchParams.get("ref") : null;
24
+ if (streamsMessageImage && (!rawReference || !/^\d+$/.test(rawReference))) {
25
+ return json(res, 400, { error: "ref must identify a rendered image" });
26
+ }
27
+ const href = method === "POST"
28
+ ? (typeof body.path === "string" ? body.path : "")
29
+ : messageImageTargetAt(message.text ?? "", Number(rawReference));
30
+ if (!href)
31
+ return json(res, 400, { error: "path is required" });
32
+ // Generated image paths are durable capabilities on this exact message.
33
+ // They do not grant access to arbitrary workspace files or Markdown refs.
34
+ const generatedImage = method === "POST" && message.role === "bot" &&
35
+ message.attachments?.some((attachment) => attachment.kind === "image" && attachment.path === href) === true;
36
+ let roots;
37
+ let downloadName;
38
+ if (generatedImage) {
39
+ roots = [ATTACHMENTS_DIR];
40
+ }
41
+ else if (message.role === "user") {
42
+ downloadName = messageAttachmentName(message.text ?? "", href) ?? undefined;
43
+ if (!downloadName) {
44
+ return json(res, 403, { error: "that message does not share this file" });
45
+ }
46
+ roots = [ATTACHMENTS_DIR];
47
+ }
48
+ else {
49
+ if (!messageReferencesFile(message.text ?? "", href)) {
50
+ return json(res, 403, { error: "that bot message does not link to this file" });
51
+ }
52
+ const senderId = directBot?.id ?? message.from?.botId;
53
+ // The persisted bot-role message is the author record. Membership is
54
+ // intentionally not consulted: removing a bot must not break files it
55
+ // already shared in channel history.
56
+ if (!senderId) {
57
+ return json(res, 403, { error: "the file's bot author could not be verified" });
58
+ }
59
+ let pinnedCwd;
60
+ let configuredCwd;
61
+ if (directBot) {
62
+ pinnedCwd = store.taskByThread(directBot.id, threadId)?.cwd;
63
+ configuredCwd = directBot.cwd;
64
+ }
65
+ else if (group) {
66
+ const task = store.groupTaskByThread(group.id, threadId);
67
+ pinnedCwd = task ? task.pinnedCwd : group.threadId === threadId ? group.pinnedCwd : undefined;
68
+ configuredCwd = group.cwd;
69
+ }
70
+ roots = messageFileRoots({
71
+ senderWorkspace: workspaceDir(senderId),
72
+ attachments: ATTACHMENTS_DIR,
73
+ pinnedCwd,
74
+ configuredCwd,
75
+ });
76
+ }
77
+ const file = await openMessageFile(href, roots, message.role === "bot" && !generatedImage
78
+ ? access(directBot?.id ?? message.from?.botId, auth) : {});
79
+ if ((streamsMessageImage || generatedImage) && !file.mime.startsWith("image/")) {
80
+ await file.handle.close();
81
+ return json(res, 415, { error: "only images can be previewed here" });
82
+ }
83
+ res.writeHead(200, {
84
+ "content-type": file.mime,
85
+ "content-length": String(file.bytes),
86
+ ...(streamsMessageImage
87
+ ? { "content-disposition": "inline" }
88
+ : { "content-disposition": messageFileDisposition(messageFileDownloadName(downloadName, file.name)) }),
89
+ "cache-control": streamsMessageImage ? "private, max-age=3600" : "private, no-store",
90
+ "cdn-cache-control": "no-store",
91
+ "cloudflare-cdn-cache-control": "no-store",
92
+ pragma: "no-cache",
93
+ vary: "Authorization",
94
+ "x-content-type-options": "nosniff",
95
+ ...(streamsMessageImage
96
+ ? {
97
+ "cross-origin-resource-policy": "same-origin",
98
+ "referrer-policy": "no-referrer",
99
+ }
100
+ : {}),
101
+ });
102
+ if (file.bytes === 0) {
103
+ await file.handle.close();
104
+ res.end();
105
+ return;
106
+ }
107
+ const stream = file.handle.createReadStream({ start: 0, end: file.bytes - 1, autoClose: true });
108
+ stream.on("error", () => res.destroy());
109
+ res.on("close", () => stream.destroy());
110
+ stream.pipe(res);
111
+ return;
112
+ }
113
+ return PASS;
114
+ };
115
+ }