openmausbot 0.1.87 → 0.1.89

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 (150) hide show
  1. package/README.md +2 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-BTiuL79F.js → abnfDiagram-O67JEVCF-BwaMaZEm.js} +1 -1
  3. package/dist/assets/{arc-C88Blc2v.js → arc-Be-8GKxd.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-BZsJIZrb.js → architectureDiagram-NJMV4G6O-Dp_U2Kv1.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CVeMe9Ah.js → blockDiagram-BEXU5L5S-DQjUjYUk.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-Cedk3Bhx.js → c4Diagram-YGBWAQC7-Bd3aE4Kc.js} +1 -1
  7. package/dist/assets/channel-DqY_gMLg.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-D-VRqzTB.js → chunk-2Q5K7J3B-BijjTBeJ.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BChov01Y.js → chunk-3FUC2YCW-CMGdwvzW.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-C-TfwvHy.js → chunk-5VM5RSS4-0G09BfsZ.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-Db7CGj5Y.js → chunk-GTNCS2PH-FOp3vSHe.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-CNvIfqKT.js → chunk-GWA4HPMP-Dv-20VIn.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-C_bEp5tq.js → chunk-JWPE2WC7-CRgrdFcz.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-Coi4BOls.js → chunk-NETBCI7D-3hP4f7QB.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-CzylEuL3.js → chunk-WEXAMYUT-CG98IjlG.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-m7p7ztaD.js → chunk-XXDRQBXY-BrSTC9xG.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BMy6o9x2.js → classDiagram-v2-NBCMYWYE-rpQQQDFX.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-BDQ__0_J.js → cose-bilkent-JH36ORCC-G6RSafHA.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-Djv4ghUb.js → cynefin-OW5HDTMX-CXjv-IM2.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-BULrlGYH.js → cynefinDiagram-VND7K2PF-Dw07MR9f.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-D-jrT_hU.js → dagre-6A5THRUB-BwPnlTg2.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-p2UhQmfL.js → diagram-22UHCM2B-Cm6xcMiz.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-DHvGWZ7q.js → diagram-3UASUU5V-CDpogPbE.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-ChhKDot-.js → diagram-ATOU4E4O-BV8z3Pnc.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-B-T7A43R.js → diagram-CDSNMT55-CKCQhOOC.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-CJm8ENNY.js → diagram-MLGK6HIB-FGvJg69C.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-510tlza8.js → diagram-MPIPVDR6-CwATvgnS.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-PkvRPHel.js → ebnfDiagram-ZINNZB2B-CH_c3SSC.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-BVBVPoX3.js → elk-276RUBZZ-3guZi71R.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-BgYMVm7L.js → erDiagram-OPXOYQCR-BMHphR2n.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-BGqsnc_3.js → flowDiagram-KWPJA3E3-Vm5DkW5x.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-D_-UeReH.js → ganttDiagram-FUAMR5RP-DO-1ct07.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-D0uooONx.js → gitGraphDiagram-X574FWY7-nxeksEIN.js} +1 -1
  34. package/dist/assets/index-BIMdIbaw.css +1 -0
  35. package/dist/assets/index-BVPdbq2r.js +583 -0
  36. package/dist/assets/{index-zRsJERGJ.js → index-Oxk9x45i.js} +1 -1
  37. package/dist/assets/{infoDiagram-VRGFBTTK-B9e1z77f.js → infoDiagram-VRGFBTTK-Dkcvr0p7.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-BB4vNwvD.js → ishikawaDiagram-OU5B5YK6-BwVibkoO.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-DpkosOTB.js → journeyDiagram-ZHPQQLJL-ByiBZqgG.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-DbDP4kUu.js → kanban-definition-PNTS6WVX-qqjgXBF3.js} +1 -1
  41. package/dist/assets/{linear-DBJGPmjT.js → linear-CtJnKe6A.js} +1 -1
  42. package/dist/assets/{mermaid.core-DWPRi-ue.js → mermaid.core-BEiDklrY.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-nszBbJmx.js → mindmap-definition-NLK3R4M7-DfIYHia9.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-CWykJWu_.js → pegDiagram-GJSIUBJH-CYyRqa1a.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-g3Da-Vnv.js → pieDiagram-5QR66LMP-hOToy_zH.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-Wzrlpzgh.js → quadrantDiagram-O4NWA36T-DKAsNN9I.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-moHTFMC0.js → railroadDiagram-XR7U4H2S-BSA-d8JU.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-CwfXdFH8.js → requirementDiagram-PLB6GJNP-CSmlvb-p.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-3ZROYvnq.js → sankeyDiagram-IPEJSGJF-CD5F6fnG.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-E1TBebP8.js → sequenceDiagram-PO4LG4MO-JUq5sx6d.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-DHKEU5pu.js → sizeCapture-INFHLROL-BjIS-Stk.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-BguEj09D.js → stateDiagram-v2-GCMORJYK-DklkUxOg.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-C7IDYnhd.js → swimlanes-2SLR337P-CsjIU6be.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-Bk14M0_B.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-DCW-F5PW.js → timeline-definition-EJHVYXUP-BkFqK7AB.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-xBMFtWYV.js → usecaseDiagram-POWQR4AR-70gJB1EU.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-B5nIkuH8.js → vennDiagram-UO4OBE2U-83Tm62dN.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DYFAhdbr.js → wardleyDiagram-VNRHLVJA-tTmseoX9.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-DeXwX6Cz.js → xychartDiagram-PMCCYNJV-R1LBA55a.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/companion/src/routes.js +8 -1
  62. package/dist-server/connector-proxy.js +105 -1
  63. package/dist-server/container-mcp.js +11 -1
  64. package/dist-server/drivers/agents-proxy.js +277 -22
  65. package/dist-server/index.js +23722 -20272
  66. package/dist-server/local-computer-proxy.js +2 -0
  67. package/dist-server/mcp-gate.js +2 -0
  68. package/dist-server/mcp-server.js +5 -3
  69. package/dist-server/message-search.worker.js +52 -0
  70. package/dist-server/openmausbot.js +755 -404
  71. package/dist-server/pair-cli.js +755 -404
  72. package/dist-server/server/admission.js +81 -0
  73. package/dist-server/server/aside-queue.js +294 -0
  74. package/dist-server/server/attachments.js +43 -1
  75. package/dist-server/server/automatic-recovery.js +15 -0
  76. package/dist-server/server/bot-attachment.js +107 -0
  77. package/dist-server/server/bot-overview.js +19 -0
  78. package/dist-server/server/composio.js +297 -1
  79. package/dist-server/server/config.js +34 -3
  80. package/dist-server/server/connector-advertisement.js +165 -0
  81. package/dist-server/server/connector-proxy.js +14 -2
  82. package/dist-server/server/connector-verdict.js +31 -4
  83. package/dist-server/server/container-computer.js +70 -0
  84. package/dist-server/server/contracts.js +32 -0
  85. package/dist-server/server/drivers/acp/core.js +148 -23
  86. package/dist-server/server/drivers/acp/cursor.js +3 -7
  87. package/dist-server/server/drivers/acp/droid.js +4 -7
  88. package/dist-server/server/drivers/acp/gemini.js +4 -4
  89. package/dist-server/server/drivers/acp/grok.js +4 -9
  90. package/dist-server/server/drivers/acp/hermes.js +3 -5
  91. package/dist-server/server/drivers/acp/opencode-go.js +2 -5
  92. package/dist-server/server/drivers/acp/qwen.js +12 -15
  93. package/dist-server/server/drivers/agents-call.js +137 -10
  94. package/dist-server/server/drivers/agents-catalog.js +100 -6
  95. package/dist-server/server/drivers/boxagent.js +3 -17
  96. package/dist-server/server/drivers/claude.js +31 -17
  97. package/dist-server/server/drivers/codex-catalog.js +14 -6
  98. package/dist-server/server/drivers/codex-identity.js +2 -1
  99. package/dist-server/server/drivers/codex-release.js +48 -0
  100. package/dist-server/server/drivers/codex.js +7 -25
  101. package/dist-server/server/drivers/openai-chat.js +31 -3
  102. package/dist-server/server/drivers/pi.js +237 -30
  103. package/dist-server/server/drivers/prompt-split.js +32 -9
  104. package/dist-server/server/drivers/session-idle.js +29 -0
  105. package/dist-server/server/env-path.js +15 -0
  106. package/dist-server/server/guarded-requests.js +2 -2
  107. package/dist-server/server/harness-capabilities.js +61 -0
  108. package/dist-server/server/index.js +1173 -262
  109. package/dist-server/server/message-db.js +22 -42
  110. package/dist-server/server/message-file.js +9 -1
  111. package/dist-server/server/message-search-query.js +43 -0
  112. package/dist-server/server/message-search-worker.js +80 -0
  113. package/dist-server/server/message-search.worker.js +21 -0
  114. package/dist-server/server/model-requests.js +248 -0
  115. package/dist-server/server/options-card.js +21 -0
  116. package/dist-server/server/org-library.js +15 -1
  117. package/dist-server/server/peer-delivery.js +60 -0
  118. package/dist-server/server/peer-roster.js +57 -0
  119. package/dist-server/server/presets.js +32 -32
  120. package/dist-server/server/profile-requests.js +66 -39
  121. package/dist-server/server/profile-revision.js +9 -1
  122. package/dist-server/server/profile-versions.js +6 -3
  123. package/dist-server/server/routine-requests.js +29 -7
  124. package/dist-server/server/skill-fetch.js +72 -14
  125. package/dist-server/server/skills.js +1 -0
  126. package/dist-server/server/store.js +32 -2
  127. package/dist-server/server/system-prompt.js +37 -2
  128. package/dist-server/server/team-setup-requests.js +124 -23
  129. package/dist-server/server/thread-events.js +3 -1
  130. package/dist-server/server/tightening-requests.js +405 -0
  131. package/dist-server/server/tts/chatterbox.js +27 -5
  132. package/dist-server/server/usage-ledger.js +7 -1
  133. package/dist-server/server/webhooks.js +21 -3
  134. package/dist-server/server/workspace-backup.js +40 -3
  135. package/dist-server/server/workspace-backup.worker.js +10 -0
  136. package/dist-server/shared/ask-question.js +19 -0
  137. package/dist-server/shared/credential-request.js +5 -1
  138. package/dist-server/shared/line-diff.js +23 -0
  139. package/dist-server/shared/model-request.js +1 -0
  140. package/dist-server/shared/options-card.js +51 -0
  141. package/dist-server/shared/profile-request.js +5 -4
  142. package/dist-server/shared/tightening-request.js +87 -0
  143. package/dist-server/src/lib/composer-attachments.js +42 -3
  144. package/dist-server/vps-container-mcp.js +11 -1
  145. package/dist-server/workspace-backup.worker.js +19482 -0
  146. package/package.json +1 -1
  147. package/dist/assets/channel-taCNRlUU.js +0 -1
  148. package/dist/assets/index-BwdMAQ6p.css +0 -1
  149. package/dist/assets/index-ByJennr7.js +0 -581
  150. package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +0 -8
@@ -40,3 +40,26 @@ export function lineDiff(before, after) {
40
40
  out.push(`+${b[j++]}`);
41
41
  return out;
42
42
  }
43
+ /** Cap on diff lines rendered inside an approval card. Past this the card
44
+ * shows the complete proposed document instead of a truncated diff, because
45
+ * truncation can hide the very instructions being approved. */
46
+ export const MAX_DIFF_LINES = 400;
47
+ /** The SOUL.md review block shared by the profile and team-setup cards:
48
+ * byte counts, a line diff for reviewable changes, and the complete
49
+ * replacement when the diff would outrun the card. The full replacement is
50
+ * already byte-bounded by profile validation and is readable in the same
51
+ * generic card on desktop and phones. */
52
+ export function soulDiffLines(before, after) {
53
+ const encoder = new TextEncoder();
54
+ const bytesBefore = encoder.encode(before).length;
55
+ const bytesAfter = encoder.encode(after).length;
56
+ const diff = lineDiff(before, after);
57
+ if (diff.length > MAX_DIFF_LINES) {
58
+ return [
59
+ `SOUL.md (${bytesBefore} → ${bytesAfter} bytes):`,
60
+ "Large change — complete proposed instructions (replaces the current SOUL.md):",
61
+ after || "(empty)",
62
+ ];
63
+ }
64
+ return [`SOUL.md (${bytesBefore} → ${bytesAfter} bytes):`, ...diff];
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ /** The one bot allowed to create passive choice cards for the Watcher flow. */
2
+ export const WATCHER_OPTIONS_CARD_BOT_ID = "f08dd8e3-f942-4eb4-8783-df32b26b88a4";
3
+ /** Keep agent-authored cards small enough to remain useful on desktop and mobile. */
4
+ export const OPTIONS_CARD_LIMITS = {
5
+ title: 120,
6
+ subtitle: 1_000,
7
+ option: 120,
8
+ minOptions: 2,
9
+ maxOptions: 6,
10
+ };
11
+ function record(value) {
12
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
13
+ }
14
+ function boundedText(value, field, maximum) {
15
+ if (typeof value !== "string")
16
+ return { error: `${field} must be a string.` };
17
+ const text = value.trim();
18
+ if (!text)
19
+ return { error: `${field} must not be blank.` };
20
+ if (text.length > maximum)
21
+ return { error: `${field} must be at most ${maximum} characters.` };
22
+ return text;
23
+ }
24
+ /** Normalize untrusted model input before it is persisted into a native card. */
25
+ export function parseOptionsCardInput(value) {
26
+ if (!record(value))
27
+ return { ok: false, error: "create_options_card needs an object." };
28
+ const title = boundedText(value.title, "title", OPTIONS_CARD_LIMITS.title);
29
+ if (typeof title !== "string")
30
+ return { ok: false, error: title.error };
31
+ const subtitle = boundedText(value.subtitle, "subtitle", OPTIONS_CARD_LIMITS.subtitle);
32
+ if (typeof subtitle !== "string")
33
+ return { ok: false, error: subtitle.error };
34
+ if (!Array.isArray(value.options))
35
+ return { ok: false, error: "options must be an array of strings." };
36
+ if (value.options.length < OPTIONS_CARD_LIMITS.minOptions || value.options.length > OPTIONS_CARD_LIMITS.maxOptions) {
37
+ return { ok: false, error: `options must contain ${OPTIONS_CARD_LIMITS.minOptions}-${OPTIONS_CARD_LIMITS.maxOptions} items.` };
38
+ }
39
+ const options = [];
40
+ const seen = new Set();
41
+ for (let index = 0; index < value.options.length; index += 1) {
42
+ const option = boundedText(value.options[index], `options[${index}]`, OPTIONS_CARD_LIMITS.option);
43
+ if (typeof option !== "string")
44
+ return { ok: false, error: option.error };
45
+ if (seen.has(option))
46
+ return { ok: false, error: `options must be unique; "${option}" is repeated.` };
47
+ seen.add(option);
48
+ options.push(option);
49
+ }
50
+ return { ok: true, value: { title, subtitle, options } };
51
+ }
@@ -2,8 +2,9 @@
2
2
  * Durable payload carried by a profile confirmation card (propose_profile).
3
3
  *
4
4
  * The fields a bot may propose for itself (`cwd` is the working folder its
5
- * tools run in; "" means its private workspace). `before` is the snapshot the
6
- * user was shown; `expectedRevision` is a hash of the target's whole profile
7
- * at proposal time so a confirmation fails closed if anything moved.
5
+ * tools run in; "" means its private workspace; `notifications` and
6
+ * `speakReplies` are the alert and voice toggles). `before` is the snapshot
7
+ * the user was shown; `expectedRevision` is a hash of the target's whole
8
+ * profile at proposal time so a confirmation fails closed if anything moved.
8
9
  */
9
- export const PROFILE_REQUEST_FIELDS = ["name", "title", "description", "soul", "cwd"];
10
+ export const PROFILE_REQUEST_FIELDS = ["name", "title", "description", "soul", "cwd", "notifications", "speakReplies"];
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Durable payload carried by a tightening card (propose_tightening).
3
+ *
4
+ * Authority reductions a bot may propose for itself or (for a Chief) a
5
+ * section peer. Direction is enforced by shape: the intents cannot express
6
+ * an escalation — approvalMode targets stop below full and custom, the
7
+ * booleans only carry their tightening value, and the list fields only
8
+ * carry removals. `validateTightening` then checks the remaining
9
+ * state-relative direction (an upward move inside ask/edits/auto, a grant
10
+ * that is not currently held) against live bot state, at propose AND again
11
+ * at confirm, so a card that sat open can never apply a transition that
12
+ * stopped being a reduction. Escalation stays human-only in Edit Profile.
13
+ */
14
+ import { APPROVAL_MODES } from "./approval-mode.js";
15
+ /** Targets a proposal may name. "full" and "custom" are owner-only. */
16
+ export const TIGHTENING_MODE_TARGETS = ["ask", "edits", "auto"];
17
+ /** Direction check against live state. Pure: the caller supplies the
18
+ * snapshot, at propose and again at confirm. Every rejection names the
19
+ * field and the allowed direction. */
20
+ export function validateTightening(before, intents) {
21
+ const after = {
22
+ ...before,
23
+ alwaysAllow: [...before.alwaysAllow],
24
+ mcpServers: [...before.mcpServers],
25
+ skills: [...before.skills],
26
+ };
27
+ if (intents.approvalMode !== undefined) {
28
+ const to = intents.approvalMode;
29
+ const from = before.approvalMode;
30
+ const reduces = from === "custom"
31
+ ? to === "ask"
32
+ : APPROVAL_MODES.indexOf(to) < APPROVAL_MODES.indexOf(from);
33
+ if (!reduces) {
34
+ return {
35
+ ok: false,
36
+ error: `approvalMode can only tighten: down the selector order (ask, edits, auto, full), or custom to ask. "${to}" does not reduce "${from}".`,
37
+ };
38
+ }
39
+ after.approvalMode = to;
40
+ }
41
+ if (intents.composio !== undefined) {
42
+ if (!before.composio)
43
+ return { ok: false, error: "composio is already off; it may only be turned off" };
44
+ after.composio = false;
45
+ }
46
+ if (intents.browser !== undefined) {
47
+ if (!before.browser)
48
+ return { ok: false, error: "browser is already off; it may only be turned off" };
49
+ after.browser = false;
50
+ }
51
+ if (intents.approvePeerComms !== undefined) {
52
+ if (before.approvePeerComms)
53
+ return { ok: false, error: "approvePeerComms is already on; it may only be turned on" };
54
+ after.approvePeerComms = true;
55
+ }
56
+ if (intents.alwaysAllow !== undefined) {
57
+ const removals = [...new Set(intents.alwaysAllow)];
58
+ if (!removals.length)
59
+ return { ok: false, error: "alwaysAllow must name at least one grant to remove" };
60
+ const unknown = removals.filter((key) => !before.alwaysAllow.includes(key));
61
+ if (unknown.length) {
62
+ return { ok: false, error: `alwaysAllow entries may only be removed, never added; not currently granted: ${unknown.join(", ")}` };
63
+ }
64
+ after.alwaysAllow = before.alwaysAllow.filter((key) => !removals.includes(key));
65
+ }
66
+ if (intents.mcpServers !== undefined) {
67
+ const removals = [...new Set(intents.mcpServers)];
68
+ if (!removals.length)
69
+ return { ok: false, error: "mcpServers must name at least one server to unmount" };
70
+ const unknown = removals.filter((name) => !before.mcpServers.includes(name));
71
+ if (unknown.length) {
72
+ return { ok: false, error: `mcpServers may only unmount servers this bot currently mounts; not mounted: ${unknown.join(", ")}` };
73
+ }
74
+ after.mcpServers = before.mcpServers.filter((name) => !removals.includes(name));
75
+ }
76
+ if (intents.skills !== undefined) {
77
+ const removals = [...new Set(intents.skills)];
78
+ if (!removals.length)
79
+ return { ok: false, error: "skills must name at least one skill to disable" };
80
+ const unknown = removals.filter((name) => !before.skills.includes(name));
81
+ if (unknown.length) {
82
+ return { ok: false, error: `skills may only be disabled, never enabled; not currently enabled: ${unknown.join(", ")}` };
83
+ }
84
+ after.skills = before.skills.filter((name) => !removals.includes(name));
85
+ }
86
+ return { ok: true, after };
87
+ }
@@ -426,6 +426,11 @@ export function isPrivateAttachmentPath(path) {
426
426
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.[a-z0-9]+$/i
427
427
  .test(attachmentBasename(path));
428
428
  }
429
+ /** The exact wrapper lines composeMessage writes around a pasted block. The
430
+ * bot needs them to tell pasted from typed text; a person reading their own
431
+ * message does not. Anything else on the line keeps the tag visible. */
432
+ const PASTED_TEXT_OPEN = /^ {0,3}<pasted-text(?:[\t ]+index="\d+")?[\t ]*>[\t ]*$/i;
433
+ const PASTED_TEXT_CLOSE = /^[\t ]*<\/pasted-text>[\t ]*$/i;
429
434
  /** Recognise CommonMark-style fenced code without pulling a Markdown parser
430
435
  * into the composer bundle. An unterminated fence deliberately protects the
431
436
  * rest of the message: examples must never turn into actionable attachments. */
@@ -511,8 +516,10 @@ function transcriptBlockStarting(line) {
511
516
  }
512
517
  const TRANSCRIPT_ATTACHMENT_TAG = /^<attached-(image|file)[\t ]+path="([^"\r\n]*)"(?:[\t ]+name="([^"\r\n]*)")?[\t ]*\/>[\t ]*$/;
513
518
  /** Split a stored user message into its display text and attachments for
514
- * transcript rendering. Markdown exports preserve whitespace; bubbles trim it. */
515
- export function splitTranscriptAttachments(text, trimDisplay = true) {
519
+ * transcript rendering. Markdown exports preserve whitespace; bubbles trim it.
520
+ * Bubbles also hide the `<pasted-text>` wrapper lines and show only what was
521
+ * pasted; exports keep the message exactly as the bot received it. */
522
+ export function splitTranscriptAttachments(text, trimDisplay = true, hidePasteWrappers = true) {
516
523
  const images = [];
517
524
  const files = [];
518
525
  let display = "";
@@ -541,6 +548,8 @@ export function splitTranscriptAttachments(text, trimDisplay = true) {
541
548
  block = null;
542
549
  }
543
550
  else if (line.toLowerCase().includes(block.closingToken)) {
551
+ if (block.hiddenWrapper && PASTED_TEXT_CLOSE.test(line))
552
+ consumed = true;
544
553
  block = null;
545
554
  }
546
555
  }
@@ -565,8 +574,13 @@ export function splitTranscriptAttachments(text, trimDisplay = true) {
565
574
  consumed = true;
566
575
  }
567
576
  }
568
- if (!consumed)
577
+ if (!consumed) {
569
578
  block = transcriptBlockStarting(line);
579
+ if (hidePasteWrappers && block?.kind === "untilToken" && block.closingToken === "</pasted-text>" && PASTED_TEXT_OPEN.test(line)) {
580
+ block = { ...block, hiddenWrapper: true };
581
+ consumed = true;
582
+ }
583
+ }
570
584
  }
571
585
  if (!consumed)
572
586
  display += text.slice(cursor, wholeLineEnd);
@@ -635,6 +649,14 @@ export function attachmentImageUrl(path) {
635
649
  return null;
636
650
  return `/api/attachments/${encodeURIComponent(name)}`;
637
651
  }
652
+ /** Voice notes park as bare generated .mp3 filenames; anything else stays
653
+ * out of an <audio> src rather than 404ing on a private path. */
654
+ export function attachmentAudioUrl(path) {
655
+ const name = attachmentBasename(path);
656
+ if (!/^[A-Za-z0-9-]+\.mp3$/.test(name))
657
+ return null;
658
+ return `/api/attachments/${encodeURIComponent(name)}`;
659
+ }
638
660
  /** One intake path for files arriving by drop OR by the composer's attach
639
661
  * button, so a picked file and a dropped one can never behave differently.
640
662
  * Uploaders are injected for deterministic tests: callers own the network,
@@ -707,3 +729,20 @@ export function composerShouldRefocus(active, input) {
707
729
  const composer = input.closest("[data-tour=composer]");
708
730
  return Boolean(composer?.contains(active));
709
731
  }
732
+ /**
733
+ * Whether a freshly opened thread's composer should take keyboard focus.
734
+ * Opening a thread from the sidebar leaves focus on the row or the New thread
735
+ * button, so the composer takes it from any plain control. It never takes it
736
+ * from another text field (the sidebar search, a rename) or from an open
737
+ * dialog, where the person is typing or deciding something else.
738
+ */
739
+ export function composerTakesFocusOnOpen(active, input) {
740
+ if (composerShouldRefocus(active, input))
741
+ return true;
742
+ if (!active)
743
+ return true;
744
+ const tag = active.tagName?.toUpperCase();
745
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || active.isContentEditable)
746
+ return false;
747
+ return !active.closest?.("[role=dialog], [role=alertdialog], [aria-modal=true]");
748
+ }
@@ -112,6 +112,8 @@ function knownDirs() {
112
112
  join(home, ".volta", "bin"),
113
113
  join(home, ".bun", "bin"),
114
114
  join(home, ".asdf", "shims"),
115
+ join(home, ".local", "share", "mise", "shims"),
116
+ // mise installer
115
117
  join(home, ".deno", "bin"),
116
118
  join(home, "bin"),
117
119
  ...nvmBinDirs()
@@ -16026,7 +16028,7 @@ var MAX_CONCURRENT_BOT_THREADS = 10;
16026
16028
  var MIN_THREAD_EVENT_LOG_BYTES = 256 * 1024;
16027
16029
  var MAX_THREAD_EVENT_LOG_BYTES = 4 * 1024 * 1024 * 1024;
16028
16030
  var MIN_LOCAL_VM_MAX_INSTANCES = 1;
16029
- var MAX_LOCAL_VM_MAX_INSTANCES = 4;
16031
+ var MAX_LOCAL_VM_MAX_INSTANCES = 8;
16030
16032
  function isValidSshAlias(value) {
16031
16033
  return typeof value === "string" && SSH_ALIAS.test(value);
16032
16034
  }
@@ -16227,6 +16229,13 @@ var defaultModelSelectionSchema = external_exports.object({
16227
16229
  (selection) => selection.variant === void 0 || selection.effort === void 0,
16228
16230
  "choose either a model variant or an effort level"
16229
16231
  );
16232
+ var automaticRecoverySchema = external_exports.object({
16233
+ enabled: external_exports.boolean(),
16234
+ backup: defaultModelSelectionSchema.optional()
16235
+ }).strict().refine(
16236
+ (recovery) => !recovery.enabled || recovery.backup !== void 0,
16237
+ { message: "Choose a backup model before enabling automatic recovery", path: ["backup"] }
16238
+ );
16230
16239
  var threadsConfigSchema = external_exports.object({
16231
16240
  maxConcurrentPerBot: external_exports.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
16232
16241
  /** Cap each per-thread events/ and native/ NDJSON log at this many
@@ -16255,6 +16264,7 @@ var appConfigSchema = external_exports.object({
16255
16264
  * addresses or `@domain` entries; admins get every scope, members chat only. */
16256
16265
  signIn: external_exports.object({ admins: external_exports.array(external_exports.string().max(320)).max(500).optional(), members: external_exports.array(external_exports.string().max(320)).max(5e3).optional() }).optional(),
16257
16266
  defaultModelSelection: defaultModelSelectionSchema.optional(),
16267
+ automaticRecovery: automaticRecoverySchema.optional(),
16258
16268
  newBotDefaults: newBotDefaultsSchema.optional(),
16259
16269
  newBots: newBotsConfigSchema.optional(),
16260
16270
  /** CLI-only launch preferences. Never enable remote access implicitly. */