sliccy 6.154.1 → 6.154.3

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 (190) hide show
  1. package/dist/node-server/browser-shutdown.d.ts +6 -1
  2. package/dist/node-server/browser-shutdown.js +3 -3
  3. package/dist/node-server/chrome-launch.d.ts +3 -0
  4. package/dist/node-server/chrome-launch.js +2 -1
  5. package/dist/node-server/electron-controller.d.ts +63 -2
  6. package/dist/node-server/electron-controller.js +86 -48
  7. package/dist/node-server/electron-runtime.d.ts +15 -1
  8. package/dist/node-server/electron-runtime.js +10 -10
  9. package/dist/node-server/fetch-proxy-gzip.js +5 -7
  10. package/dist/node-server/hosted-bootstrap.d.ts +1 -0
  11. package/dist/node-server/hosted-bootstrap.js +12 -14
  12. package/dist/node-server/hostfs.d.ts +37 -1
  13. package/dist/node-server/hostfs.js +5 -5
  14. package/dist/node-server/install-cli.d.ts +1 -0
  15. package/dist/node-server/install-cli.js +1 -1
  16. package/dist/node-server/launch-url.js +2 -6
  17. package/dist/node-server/leader-restart.d.ts +9 -0
  18. package/dist/node-server/leader-restart.js +1 -1
  19. package/dist/node-server/publish-chrome-web-store-main.d.ts +1 -0
  20. package/dist/node-server/publish-chrome-web-store-main.js +13 -0
  21. package/dist/node-server/publish-chrome-web-store.d.ts +2 -0
  22. package/dist/node-server/publish-chrome-web-store.js +2 -15
  23. package/dist/node-server/release-package-main.d.ts +1 -0
  24. package/dist/node-server/release-package-main.js +17 -0
  25. package/dist/node-server/release-package.d.ts +38 -3
  26. package/dist/node-server/release-package.js +46 -50
  27. package/dist/node-server/routes/fetch-proxy.d.ts +40 -1
  28. package/dist/node-server/routes/fetch-proxy.js +20 -12
  29. package/dist/node-server/routes/secrets.d.ts +23 -1
  30. package/dist/node-server/routes/secrets.js +34 -30
  31. package/dist/node-server/secrets/proxy-manager.d.ts +2 -1
  32. package/dist/node-server/secrets/proxy-manager.js +27 -26
  33. package/dist/node-server/secrets/session-id-file.d.ts +5 -1
  34. package/dist/node-server/secrets/session-id-file.js +4 -4
  35. package/dist/node-server/secrets/sign-and-forward.d.ts +8 -0
  36. package/dist/node-server/secrets/sign-and-forward.js +1 -1
  37. package/dist/node-server/sudo/select.d.ts +2 -0
  38. package/dist/node-server/sudo/select.js +1 -1
  39. package/dist/node-server/sudo/tty-backend.d.ts +1 -0
  40. package/dist/node-server/sudo/tty-backend.js +1 -1
  41. package/dist/ui/.vite/manifest.json +171 -171
  42. package/dist/ui/assets/SKILL-BGww2dFz.js +1 -0
  43. package/dist/ui/assets/{SKILL-cqUV8C7U.js → SKILL-Cl9PVarg.js} +1 -1
  44. package/dist/ui/assets/{account-store-BqdqUcgS.js → account-store-BYDUpeqo.js} +1 -1
  45. package/dist/ui/assets/{account-store-BgFuxoIt.js → account-store-BvEgWXDi.js} +1 -1
  46. package/dist/ui/assets/{adobe-BWEqvpfE.js → adobe-Bf44Mdem.js} +1 -1
  47. package/dist/ui/assets/{adobe-De7VHe-t.js → adobe-DIeHS2Yx.js} +1 -1
  48. package/dist/ui/assets/{agent-bridge-EXV0qNBB.js → agent-bridge-pmiSNwoc.js} +1 -1
  49. package/dist/ui/assets/{agent-panels-dcldnH3V.js → agent-panels-CWBbnFvW.js} +1 -1
  50. package/dist/ui/assets/api-endpoint-3qEcn7BY.js +1 -0
  51. package/dist/ui/assets/{apply-layout-DVgK-NlB.js → apply-layout-Eft8a3mq.js} +1 -1
  52. package/dist/ui/assets/{apps-BqrASr5C.js → apps-BklKPyC6.js} +1 -1
  53. package/dist/ui/assets/{auto-mount-table-0jMTi9rV.js → auto-mount-table-BDyGIOIb.js} +1 -1
  54. package/dist/ui/assets/{azure-openai-vXGFZfPE.js → azure-openai-7VfJn11R.js} +1 -1
  55. package/dist/ui/assets/{azure-openai-94kdulWT.js → azure-openai-o6cG2UDZ.js} +1 -1
  56. package/dist/ui/assets/{backend-hostfs-BxfjZHzO.js → backend-hostfs-BJU2QL9q.js} +1 -1
  57. package/dist/ui/assets/{budget-usage-source-BNEsxDHX.js → budget-usage-source-B5GSAynq.js} +1 -1
  58. package/dist/ui/assets/{cdp-D0GOzfX9.js → cdp-Di9nU0k7.js} +1 -1
  59. package/dist/ui/assets/{cerebras-eckgXNEU.js → cerebras-BRpdCI1J.js} +1 -1
  60. package/dist/ui/assets/{cerebras-v41u2mpJ.js → cerebras-DuUgRhfz.js} +1 -1
  61. package/dist/ui/assets/{client-BOSdqiLb.js → client-B6iKb_ye.js} +1 -1
  62. package/dist/ui/assets/{composer-speech-ButVgF9_.js → composer-speech-Chlrvbhg.js} +1 -1
  63. package/dist/ui/assets/{connect-surface-C0WXwJHl.js → connect-surface-DBXtjfUP.js} +1 -1
  64. package/dist/ui/assets/{core-D59qaXEb.js → core-C9qgaWgE.js} +2 -2
  65. package/dist/ui/assets/{directed-approval-D1CLB6cG.js → directed-approval-C2OTs8pv.js} +1 -1
  66. package/dist/ui/assets/dist-BPPG9gF6.js +12 -0
  67. package/dist/ui/assets/dist-BnmWpUNI.js +18 -0
  68. package/dist/ui/assets/{dist-DCrgApJQ.js → dist-CFI3XeIO.js} +1 -1
  69. package/dist/ui/assets/{dist-UJah3hVh.js → dist-Ciikigd5.js} +1 -1
  70. package/dist/ui/assets/{ensure-speech-assets-uGHvkmNC.js → ensure-speech-assets-DOu9EHnU.js} +1 -1
  71. package/dist/ui/assets/{extension-ops-B9_1VB7W.js → extension-ops-B05mAXsq.js} +1 -1
  72. package/dist/ui/assets/{fs-DK3k_wQh.js → fs-Dqh17T5J.js} +2 -2
  73. package/dist/ui/assets/{fs-mC_RRTZZ.js → fs-T4T9WqhX.js} +1 -1
  74. package/dist/ui/assets/{github-CT-Oyj_0.js → github-CL_7yoHL.js} +1 -1
  75. package/dist/ui/assets/github-auth-A6Mopmg8.js +1 -0
  76. package/dist/ui/assets/{github-copilot-CACLYChL.js → github-copilot-B2ugBoRh.js} +1 -1
  77. package/dist/ui/assets/{github-copilot-D2H-jvYu.js → github-copilot-_5qoxGCT.js} +1 -1
  78. package/dist/ui/assets/github-install-8yCqacop.js +1 -0
  79. package/dist/ui/assets/{github-BLOM522m.js → github-koUNqi7Y.js} +2 -2
  80. package/dist/ui/assets/github-zip-CV41OIqx.js +1 -0
  81. package/dist/ui/assets/{guest-tool-gate-6uzbOlEb.js → guest-tool-gate-_NuvhpkB.js} +1 -1
  82. package/dist/ui/assets/{hear-BXcOoxVl.js → hear-DScAQlnK.js} +1 -1
  83. package/dist/ui/assets/{help-BAqixBa0.js → help-BvOxNE_5.js} +1 -1
  84. package/dist/ui/assets/{js-realm-worker-CRY5Fv-C.js → js-realm-worker-fE5Mi5F2.js} +1 -1
  85. package/dist/ui/assets/{kernel-worker-C1DLWYBM.js → kernel-worker-DURzH7I0.js} +586 -585
  86. package/dist/ui/assets/{lick-urls-HYeTtmbf.js → lick-urls-BZVq_P3x.js} +1 -1
  87. package/dist/ui/assets/{lick-ws-bridge-DqmithfF.js → lick-ws-bridge-CDToX4F0.js} +1 -1
  88. package/dist/ui/assets/{local-llm-D723hPLf.js → local-llm-D_HQ1XG8.js} +1 -1
  89. package/dist/ui/assets/{local-llm-Cv98D1SL.js → local-llm-QN3Afs2P.js} +1 -1
  90. package/dist/ui/assets/{main-C-pVQBFE.js → main-DyPgoSsm.js} +3 -3
  91. package/dist/ui/assets/{main-cherry-BdH-nz0p.js → main-cherry-v6ksBa29.js} +1 -1
  92. package/dist/ui/assets/{memory-curation-seam-eUVjWT6W.js → memory-curation-seam-DXGqoWuF.js} +1 -1
  93. package/dist/ui/assets/{mount-3VFe3Pa7.js → mount-CdTXGHvm.js} +3 -3
  94. package/dist/ui/assets/{mount-Dh4tJ6XQ.js → mount-Drg9K5Dc.js} +1 -1
  95. package/dist/ui/assets/{mount-bridge-client-B5qLR9Xe.js → mount-bridge-client-DWY67yip.js} +1 -1
  96. package/dist/ui/assets/{mount-recovery-Cp7KYqTq.js → mount-recovery-CUhmg6UT.js} +1 -1
  97. package/dist/ui/assets/{new-session-hwwKqrO5.js → new-session-BBCvIUGf.js} +2 -2
  98. package/dist/ui/assets/{oauth-bootstrap-BP7bAgwJ.js → oauth-bootstrap-37qdAFNO.js} +2 -2
  99. package/dist/ui/assets/{oauth-service-BZl-9EoE.js → oauth-service-IlBYPvwr.js} +1 -1
  100. package/dist/ui/assets/{onboarding-orchestrator-DOOTJ9OG.js → onboarding-orchestrator-CgzN2Wut.js} +1 -1
  101. package/dist/ui/assets/{openai-codex--3foibzj.js → openai-codex-CByg7ExV.js} +1 -1
  102. package/dist/ui/assets/{openai-codex-vGpH6Ldz.js → openai-codex-yA937LOi.js} +1 -1
  103. package/dist/ui/assets/{openrouter-D17vOiK5.js → openrouter-Bq9TiHqe.js} +1 -1
  104. package/dist/ui/assets/{openrouter-B3v5OQPe.js → openrouter-D1YZxJcz.js} +1 -1
  105. package/dist/ui/assets/{openrouter-free-Mha-ykI6.js → openrouter-free-BwEyNzqx.js} +1 -1
  106. package/dist/ui/assets/{openrouter-free-DndyeHH8.js → openrouter-free-Db1fart9.js} +1 -1
  107. package/dist/ui/assets/{openrouter-oauth-C4aGXNeF.js → openrouter-oauth-CFlNuCUA.js} +1 -1
  108. package/dist/ui/assets/{openrouter-oauth-ov7dnUnh.js → openrouter-oauth-Cj61jTQa.js} +1 -1
  109. package/dist/ui/assets/{panel-rpc-handlers-CDsWA3s8.js → panel-rpc-handlers-CLsTGXPT.js} +2 -2
  110. package/dist/ui/assets/{panelize-shell-CXpZWzgn.js → panelize-shell-Bf9yr7Qf.js} +1 -1
  111. package/dist/ui/assets/{port-bridge-client-CzOd_VUf.js → port-bridge-client-Ds3xwapn.js} +1 -1
  112. package/dist/ui/assets/provenance-DdmsQ3Nr.js +1 -0
  113. package/dist/ui/assets/{provider-BtT9_ZsY.js → provider-BD-0pPN8.js} +1 -1
  114. package/dist/ui/assets/{provider-2fDgJArv.js → provider-C66yENxs.js} +2 -2
  115. package/dist/ui/assets/provider-settings-DysstkNf.js +1 -0
  116. package/dist/ui/assets/provider-store-access-BeX0OGEs.js +1 -0
  117. package/dist/ui/assets/provider-store-access-Cc45yVjV.js +1 -0
  118. package/dist/ui/assets/{providers-BWmPj6ob.js → providers-BtR_Oc-m.js} +2 -2
  119. package/dist/ui/assets/{providers-D_mm-oKv.js → providers-C2bj3jKv.js} +1 -1
  120. package/dist/ui/assets/proxied-fetch-DEK8DT-z.js +1 -0
  121. package/dist/ui/assets/{quick-llm-Ch_X9AH9.js → quick-llm-D-nM71kO.js} +1 -1
  122. package/dist/ui/assets/{recovery-screen-Ldb8xS5S.js → recovery-screen-YuMG99R0.js} +1 -1
  123. package/dist/ui/assets/{redirect-uri-C6BjkDE0.js → redirect-uri-CgKdq7-K.js} +1 -1
  124. package/dist/ui/assets/rest-ops-Bps3Ay-k.js +1 -0
  125. package/dist/ui/assets/{run-DXUToOLa.js → run-CDicdmnK.js} +1 -1
  126. package/dist/ui/assets/{run-Bmb6g7U2.js → run-CdzvBpS0.js} +3 -3
  127. package/dist/ui/assets/{run-xG3-BOwR.js → run-D5LoTYwZ.js} +1 -1
  128. package/dist/ui/assets/{secrets-bridge-client-DBeju3mP.js → secrets-bridge-client-ZpBL1Zhl.js} +1 -1
  129. package/dist/ui/assets/{session-jsonl-DlNZajGM.js → session-jsonl-By2mMQqb.js} +1 -1
  130. package/dist/ui/assets/{setup-feature-flags-remote-D-IPtvuE.js → setup-feature-flags-remote-BuBwyibG.js} +1 -1
  131. package/dist/ui/assets/sidecar-repair-DRkuMyU_.js +1 -0
  132. package/dist/ui/assets/{silent-renew-backoff-BocHwLdw.js → silent-renew-backoff-CS_htRXY.js} +1 -1
  133. package/dist/ui/assets/{skills-CTDnQTUW.js → skills-Cg34jB8I.js} +1 -1
  134. package/dist/ui/assets/slicc-version-DbT116fI.js +1 -0
  135. package/dist/ui/assets/{snapshot-BZ-_n6uU.js → snapshot-B6SF0dpr.js} +1 -1
  136. package/dist/ui/assets/{snapshot-features-BWzc1ERL.js → snapshot-features-A-jmNbR9.js} +1 -1
  137. package/dist/ui/assets/{soundscape-CowYlYSr.js → soundscape-smt9sQsm.js} +1 -1
  138. package/dist/ui/assets/{speak-rCiisrK7.js → speak-DeYQY3R8.js} +1 -1
  139. package/dist/ui/assets/{sprinkle-manager-DaVvW4Pl.js → sprinkle-manager-q3Ku7Zvz.js} +1 -1
  140. package/dist/ui/assets/{sprinkle-renderer-CDcbztf1.js → sprinkle-renderer-B9Re-_rl.js} +1 -1
  141. package/dist/ui/assets/{state-DHftNsPN.js → state-NPdZC8rd.js} +1 -1
  142. package/dist/ui/assets/{store-CTM4679Z.js → store-BbRPb7Di.js} +1 -1
  143. package/dist/ui/assets/{store-MZJeSOYF.js → store-BfdKxUK3.js} +1 -1
  144. package/dist/ui/assets/{sudo-DLRz2gLx.js → sudo-CtUlj7iw.js} +1 -1
  145. package/dist/ui/assets/{sync-dialog-BUFCYPxf.js → sync-dialog-CXgM1Uvh.js} +1 -1
  146. package/dist/ui/assets/{transformers-env-BfjOXz9f.js → transformers-env-C7dJtjmn.js} +1 -1
  147. package/dist/ui/assets/{tray-runtime-config--rtR6Gxd.js → tray-runtime-config-CRZxBChd.js} +1 -1
  148. package/dist/ui/assets/{tray-sidecar-BP70hrzZ.js → tray-sidecar-BtFzGslJ.js} +1 -1
  149. package/dist/ui/assets/types-Dzr0qYl3.js +1 -0
  150. package/dist/ui/assets/{upgrade-detection-Be8A7nY7.js → upgrade-detection-BWpOvoVx.js} +1 -1
  151. package/dist/ui/assets/upskill-lick-skip-ZGNBIEXS.js +1 -0
  152. package/dist/ui/assets/{voice-reply-BtqQr2DX.js → voice-reply-CCkglCEp.js} +1 -1
  153. package/dist/ui/assets/{wc-attach-zW7DuRo7.js → wc-attach-C8CgBet1.js} +2 -2
  154. package/dist/ui/assets/{wc-extension-DGYx9jMG.js → wc-extension-CHb4P34s.js} +2 -2
  155. package/dist/ui/assets/{wc-follower-Bc6Mmmda.js → wc-follower-DmIZM7ao.js} +3 -3
  156. package/dist/ui/assets/{wc-follower-oauth-DOaUEVw8.js → wc-follower-oauth-CQgoqG9v.js} +1 -1
  157. package/dist/ui/assets/{wc-gelatiere-CdqOnO6V.js → wc-gelatiere-B8JhDjog.js} +1 -1
  158. package/dist/ui/assets/{wc-live-BrKyGs-7.js → wc-live-gCevk2Vh.js} +5 -5
  159. package/dist/ui/assets/{wc-nav-dSAbBsA5.js → wc-nav-CpHEdfwe.js} +2 -2
  160. package/dist/ui/assets/{wc-onboarding-C1uyya9g.js → wc-onboarding-iwpabkC3.js} +2 -2
  161. package/dist/ui/assets/{wc-placeholder-Cw7uGVuJ.js → wc-placeholder-Cnl3OQfj.js} +3 -3
  162. package/dist/ui/assets/{wc-secret-request-CwA6DiWC.js → wc-secret-request-B9oB6kAt.js} +1 -1
  163. package/dist/ui/assets/{wc-settings-DJEhDr23.js → wc-settings-_0roTDdf.js} +3 -3
  164. package/dist/ui/assets/{wc-shell-C-DwVQCG.js → wc-shell-DkJPUxGT.js} +3 -3
  165. package/dist/ui/assets/{wc-shortcut-config-D9HBEfqv.js → wc-shortcut-config-CIup4nE8.js} +1 -1
  166. package/dist/ui/assets/{wc-shortcuts-7i4KncH2.js → wc-shortcuts-C9SNe-vm.js} +1 -1
  167. package/dist/ui/assets/{wc-sprinkles-DsRJYYRI.js → wc-sprinkles-C7_z9Yfm.js} +2 -2
  168. package/dist/ui/assets/{wc-transcript-export-Dq9HNkpA.js → wc-transcript-export-DnNvjokD.js} +2 -2
  169. package/dist/ui/assets/{wc-tray-f6xi76Bq.js → wc-tray-DhWe0-Ie.js} +4 -4
  170. package/dist/ui/assets/welcome-detection-BbyyaOna.js +1 -0
  171. package/dist/ui/assets/{whisper-session-B6knWfce.js → whisper-session-CMXLPRdV.js} +2 -2
  172. package/dist/ui/assets/{xai-grok-B0KRInrx.js → xai-grok-BNRMntfw.js} +1 -1
  173. package/dist/ui/assets/{xai-grok-BIfBT2BL.js → xai-grok-BiF6I0r6.js} +1 -1
  174. package/dist/ui/index.html +3 -3
  175. package/dist/ui/packages/webapp/index.html +3 -3
  176. package/package.json +3 -3
  177. package/dist/ui/assets/SKILL-CuHGVKei.js +0 -1
  178. package/dist/ui/assets/api-endpoint-Lx_FU5cP.js +0 -1
  179. package/dist/ui/assets/dist-B3W7EgGh.js +0 -12
  180. package/dist/ui/assets/dist-CmI2JIly.js +0 -18
  181. package/dist/ui/assets/github-install-Bk-KTJD-.js +0 -1
  182. package/dist/ui/assets/github-zip-Dvb4zHOl.js +0 -1
  183. package/dist/ui/assets/provider-settings-D2AYwHOa.js +0 -1
  184. package/dist/ui/assets/provider-store-access-BdvWm9Kj.js +0 -1
  185. package/dist/ui/assets/provider-store-access-C5A5Se3D.js +0 -1
  186. package/dist/ui/assets/proxied-fetch-B57RqgUb.js +0 -1
  187. package/dist/ui/assets/rest-ops-CB2up_K0.js +0 -1
  188. package/dist/ui/assets/sidecar-repair-DRUvDMSe.js +0 -1
  189. package/dist/ui/assets/slicc-version-CWl4uhV3.js +0 -1
  190. package/dist/ui/assets/welcome-detection-DmqQ-hO4.js +0 -1
@@ -56,7 +56,8 @@
56
56
  * otherwise express's default handler answers malformed JSON with code-less
57
57
  * HTML and the webapp cannot rethrow a faithful `FsError`.
58
58
  */
59
- import type { Express } from 'express';
59
+ import type { ErrorRequestHandler, Express, Response } from 'express';
60
+ import { createReadStream } from 'fs';
60
61
  import type { HostMountMapping } from './runtime-flags.js';
61
62
  /**
62
63
  * Cap on a WHOLE-FILE `GET /api/hostfs/read` (and on `PUT /write`). It exists
@@ -68,6 +69,14 @@ import type { HostMountMapping } from './runtime-flags.js';
68
69
  * Matches the webapp's hostfs body cap (backend-hostfs.ts).
69
70
  */
70
71
  export declare const HOSTFS_MAX_BODY_BYTES: number;
72
+ interface FsCodeError {
73
+ status: number;
74
+ code: string;
75
+ message: string;
76
+ }
77
+ /** Map an fs errno (or traversal rejection) to an HTTP status + FsError code. */
78
+ export declare function toFsCodeError(err: unknown): FsCodeError;
79
+ export declare function sendFsError(res: Response, err: unknown): void;
71
80
  /**
72
81
  * Resolve `relPath` against `root`, rejecting anything that leaves the root
73
82
  * either lexically (`..`) or via symlinks. Symlinks *inside* the root that
@@ -114,6 +123,22 @@ export type ParsedByteRange = {
114
123
  * A zero-length file has no satisfiable range at all.
115
124
  */
116
125
  export declare function parseByteRange(header: string | undefined, size: number): ParsedByteRange;
126
+ /**
127
+ * Stream a file (or a window of it) to the response instead of buffering it.
128
+ *
129
+ * `readFile` + `res.send` held the whole body in the server process — 92 MB
130
+ * per pack read in the benchmark of issue #2707 — for no benefit: express is
131
+ * already writing to a socket. Headers are committed on the stream's `open`,
132
+ * so a failure to open (a raced unlink, EACCES) still rejects BEFORE anything
133
+ * is on the wire and is mapped to the usual errno JSON by the caller. After
134
+ * that point the status line is spent, so a mid-body failure can only be
135
+ * reported by destroying the connection — a truncated body the client's
136
+ * `fetch` rejects on, which its retry path already handles.
137
+ */
138
+ export declare function streamFileBody(res: Response, target: string, status: number, headers: Record<string, string>, window?: {
139
+ start: number;
140
+ end: number;
141
+ }, createStream?: typeof createReadStream): Promise<void>;
117
142
  /**
118
143
  * Resolve the configured mappings to realpath'd roots. Mappings whose OS
119
144
  * path does not exist (or is not a directory) are dropped with a warning —
@@ -139,6 +164,16 @@ export declare function isHostFsStableBodyRequest(req: {
139
164
  method?: string;
140
165
  url?: string;
141
166
  }): boolean;
167
+ /**
168
+ * Map body-parser failures on the hostfs surface to `{ code, message }`.
169
+ * Registered after the routes (express only runs an error handler for
170
+ * middleware mounted before it), so it catches the stable dispatcher's JSON
171
+ * parser AND `PUT /write`'s raw parser. Mirrors swift-server, which answers
172
+ * 400 `EINVAL` for an unparseable body and 413 `EFBIG` for an oversized one.
173
+ * Anything that is not a body-parser error, and every non-hostfs path, is
174
+ * passed through untouched.
175
+ */
176
+ export declare const hostFsBodyErrorHandler: ErrorRequestHandler;
142
177
  /**
143
178
  * Register the /api/hostfs routes for the resolved mount roots. The route
144
179
  * surface mirrors `MountBackend` 1:1 (webapp `backend-hostfs.ts`):
@@ -167,3 +202,4 @@ export declare function isHostFsStableBodyRequest(req: {
167
202
  * per-op routes — so every error this route emits MUST carry a `code`.
168
203
  */
169
204
  export declare function registerHostFsRoutes(app: Express, roots: readonly HostMountRoot[]): void;
205
+ export {};
@@ -75,7 +75,7 @@ function errnoCode(err) {
75
75
  return typeof code === 'string' ? code : null;
76
76
  }
77
77
  /** Map an fs errno (or traversal rejection) to an HTTP status + FsError code. */
78
- function toFsCodeError(err) {
78
+ export function toFsCodeError(err) {
79
79
  const code = errnoCode(err) ?? 'EIO';
80
80
  const message = err instanceof Error ? err.message : String(err);
81
81
  switch (code) {
@@ -93,7 +93,7 @@ function toFsCodeError(err) {
93
93
  return { status: 500, code, message };
94
94
  }
95
95
  }
96
- function sendFsError(res, err) {
96
+ export function sendFsError(res, err) {
97
97
  // A streamed body may already have committed the status line; the only
98
98
  // honest signal left is a broken connection.
99
99
  if (res.headersSent) {
@@ -200,8 +200,8 @@ export function parseByteRange(header, size) {
200
200
  * reported by destroying the connection — a truncated body the client's
201
201
  * `fetch` rejects on, which its retry path already handles.
202
202
  */
203
- async function streamFileBody(res, target, status, headers, window) {
204
- const stream = createReadStream(target, window);
203
+ export async function streamFileBody(res, target, status, headers, window, createStream = createReadStream) {
204
+ const stream = createStream(target, window);
205
205
  await new Promise((resolveStream, rejectStream) => {
206
206
  let committed = false;
207
207
  stream.once('open', () => {
@@ -382,7 +382,7 @@ export function isHostFsStableBodyRequest(req) {
382
382
  * Anything that is not a body-parser error, and every non-hostfs path, is
383
383
  * passed through untouched.
384
384
  */
385
- const hostFsBodyErrorHandler = (err, req, res, next) => {
385
+ export const hostFsBodyErrorHandler = (err, req, res, next) => {
386
386
  const type = err?.type;
387
387
  if (typeof type !== 'string' || res.headersSent || !isHostFsPath(req.path)) {
388
388
  next(err);
@@ -4,6 +4,7 @@ export interface ResolvedCliAsset {
4
4
  downloadUrl: string;
5
5
  }
6
6
  export declare function cliAssetName(platform: string, arch: string): string | null;
7
+ export declare function defaultIsWritableDir(dir: string): boolean;
7
8
  /**
8
9
  * OS-idiomatic install dir, in preference order:
9
10
  *
@@ -30,7 +30,7 @@ export function cliAssetName(platform, arch) {
30
30
  }
31
31
  return `slicc-${os}-${goArch}${os === 'windows' ? '.exe' : ''}`;
32
32
  }
33
- function defaultIsWritableDir(dir) {
33
+ export function defaultIsWritableDir(dir) {
34
34
  try {
35
35
  accessSync(dir, constants.W_OK);
36
36
  return true;
@@ -15,12 +15,8 @@ function buildTrayJoinLaunchUrl(locationHref, joinUrl) {
15
15
  }
16
16
  return buildCanonicalTrayLaunchUrl(locationHref, parsedJoinUrl.joinUrl);
17
17
  }
18
- function buildTrayLeadLaunchUrl(locationHref, workerBaseUrl) {
19
- const normalizedBase = normalizeTrayWorkerBaseUrl(workerBaseUrl);
20
- if (!normalizedBase) {
21
- throw new Error(`Invalid tray worker base URL: ${workerBaseUrl}`);
22
- }
23
- return buildCanonicalTrayLaunchUrl(locationHref, normalizedBase);
18
+ function buildTrayLeadLaunchUrl(locationHref, normalizedWorkerBaseUrl) {
19
+ return buildCanonicalTrayLaunchUrl(locationHref, normalizedWorkerBaseUrl);
24
20
  }
25
21
  export function resolveCliBrowserLaunchUrl(options) {
26
22
  if (options.lead && options.join) {
@@ -1,4 +1,5 @@
1
1
  import type { Express } from 'express';
2
+ import { WebSocket } from 'ws';
2
3
  export interface CdpTargetInfo {
3
4
  id?: string;
4
5
  targetId?: string;
@@ -18,6 +19,14 @@ export type RestartResult = {
18
19
  message: string;
19
20
  };
20
21
  export declare function restartLeader(cdp: CdpLike, pageUrlPrefix: string): Promise<RestartResult>;
22
+ export declare class CdpClient {
23
+ private readonly socket;
24
+ private nextId;
25
+ private readonly pending;
26
+ constructor(socket: WebSocket);
27
+ send(method: string, params?: unknown, sessionId?: string): Promise<unknown>;
28
+ close(): void;
29
+ }
21
30
  /**
22
31
  * HTTP-backed CdpLike. Implements Target.getTargets via the CDP HTTP /json
23
32
  * endpoint. Target.attachToTarget + Page.reload are sent over WebSocket.
@@ -41,7 +41,7 @@ export async function restartLeader(cdp, pageUrlPrefix) {
41
41
  return { ok: false, code: 'INTERNAL', message: String(err) };
42
42
  }
43
43
  }
44
- class CdpClient {
44
+ export class CdpClient {
45
45
  socket;
46
46
  nextId = 1;
47
47
  pending = new Map();
@@ -0,0 +1,13 @@
1
+ import { resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { publishChromeWebStoreRelease } from './publish-chrome-web-store.js';
4
+ const dirname = fileURLToPath(new URL('.', import.meta.url));
5
+ const projectRoot = resolve(dirname, '..', '..');
6
+ const manifestPath = process.argv[2]
7
+ ? resolve(projectRoot, process.argv[2])
8
+ : resolve(projectRoot, 'artifacts', 'release', 'release-artifacts.json');
9
+ void publishChromeWebStoreRelease({ manifestPath }).catch((error) => {
10
+ const message = error instanceof Error ? error.message : String(error);
11
+ console.error(`[publish-chrome-web-store] ${message}`);
12
+ process.exit(1);
13
+ });
@@ -46,6 +46,8 @@ export interface PublishChromeWebStoreOptions {
46
46
  export declare function parseServiceAccountCredentials(jsonValue: string | undefined, base64Value: string | undefined): ServiceAccountCredentials | undefined;
47
47
  export declare function readChromeWebStoreConfig(env?: NodeJS.ProcessEnv): ChromeWebStoreConfig | null;
48
48
  export declare function createServiceAccountAssertion(serviceAccount: ServiceAccountCredentials, nowSeconds: number): string;
49
+ export declare function cancelPendingSubmission(config: ChromeWebStoreConfig, accessToken: string, fetchImpl: typeof fetch): Promise<void>;
50
+ export declare function waitForPendingReviewCancellation(config: ChromeWebStoreConfig, accessToken: string, fetchImpl: typeof fetch, waitMs: (ms: number) => Promise<void>, pollIntervalMs: number, maxAttempts: number): Promise<FetchStatusResponse>;
49
51
  export declare function waitForUploadCompletion(config: ChromeWebStoreConfig, accessToken: string, fetchImpl: typeof fetch, waitMs: (ms: number) => Promise<void>, pollIntervalMs: number, maxAttempts: number): Promise<FetchStatusResponse>;
50
52
  export declare function publishChromeWebStoreRelease(options?: PublishChromeWebStoreOptions): Promise<ChromeWebStorePublishResult | null>;
51
53
  export {};
@@ -201,7 +201,7 @@ async function fetchUploadStatus(config, accessToken, fetchImpl) {
201
201
  });
202
202
  return expectJsonResponse(response, 'Chrome Web Store upload status');
203
203
  }
204
- async function cancelPendingSubmission(config, accessToken, fetchImpl) {
204
+ export async function cancelPendingSubmission(config, accessToken, fetchImpl) {
205
205
  const itemName = createPublisherItemName(config);
206
206
  const response = await fetchImpl(`https://chromewebstore.googleapis.com/v2/${itemName}:cancelSubmission`, {
207
207
  method: 'POST',
@@ -214,7 +214,7 @@ async function cancelPendingSubmission(config, accessToken, fetchImpl) {
214
214
  throw new Error(`Chrome Web Store cancel submission failed with ${response.status} ${response.statusText}.${text ? ` ${text}` : ''}`);
215
215
  }
216
216
  }
217
- async function waitForPendingReviewCancellation(config, accessToken, fetchImpl, waitMs, pollIntervalMs, maxAttempts) {
217
+ export async function waitForPendingReviewCancellation(config, accessToken, fetchImpl, waitMs, pollIntervalMs, maxAttempts) {
218
218
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
219
219
  const status = await fetchUploadStatus(config, accessToken, fetchImpl);
220
220
  if (status.submittedItemRevisionStatus?.state !== 'PENDING_REVIEW') {
@@ -358,16 +358,3 @@ export async function publishChromeWebStoreRelease(options = {}) {
358
358
  publishState: publishResponse.state,
359
359
  };
360
360
  }
361
- async function main() {
362
- const manifestPath = process.argv[2]
363
- ? resolve(PROJECT_ROOT, process.argv[2])
364
- : DEFAULT_RELEASE_MANIFEST_PATH;
365
- await publishChromeWebStoreRelease({ manifestPath });
366
- }
367
- if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
368
- void main().catch((error) => {
369
- const message = error instanceof Error ? error.message : String(error);
370
- console.error(`[publish-chrome-web-store] ${message}`);
371
- process.exit(1);
372
- });
373
- }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { packageReleaseArtifacts, toProjectRelative } from './release-package.js';
4
+ const dirname = fileURLToPath(new URL('.', import.meta.url));
5
+ const projectRoot = resolve(dirname, '..', '..');
6
+ const releaseDir = resolve(projectRoot, 'artifacts', 'release');
7
+ try {
8
+ const manifest = packageReleaseArtifacts({ projectRoot, releaseDir });
9
+ console.log(`Created extension archive: ${manifest.extensionArchive}`);
10
+ console.log(`Created npm package tarball: ${manifest.npmPackageTarball}`);
11
+ console.log(`Created release manifest: ${toProjectRelative(projectRoot, resolve(releaseDir, 'release-artifacts.json'))}`);
12
+ }
13
+ catch (error) {
14
+ const message = error instanceof Error ? error.message : String(error);
15
+ console.error(`[package:release] ${message}`);
16
+ process.exit(1);
17
+ }
@@ -1,16 +1,51 @@
1
- interface ZipEntry {
1
+ export interface PackageMetadata {
2
+ name: string;
3
+ version: string;
4
+ }
5
+ export interface ZipEntry {
2
6
  path: string;
3
7
  data: Buffer;
4
8
  mode: number;
5
9
  }
6
- interface ReleaseManifest {
10
+ export interface ReleaseManifest {
7
11
  version: string;
8
12
  extensionArchive: string;
9
13
  npmPackageTarball: string;
10
14
  }
15
+ export declare function comparePaths(left: string, right: string): number;
11
16
  export declare function sanitizeArtifactName(value: string): string;
12
17
  export declare function collectZipEntries(rootDir: string): ZipEntry[];
13
18
  export declare function createDeterministicZip(entries: readonly ZipEntry[]): Buffer;
19
+ export declare function requirePath(path: string, description: string): void;
20
+ export declare function toProjectRelative(projectRoot: string, path: string): string;
21
+ export declare function resolveNpmCommand(options?: {
22
+ npmExecPath?: string;
23
+ execPath?: string;
24
+ platform?: NodeJS.Platform;
25
+ }): {
26
+ command: string;
27
+ argsPrefix: string[];
28
+ };
14
29
  export declare function parseNpmPackFilename(output: string): string;
15
- export declare function packageReleaseArtifacts(): ReleaseManifest;
30
+ export declare function createExtensionArchive(metadata: PackageMetadata, projectRoot: string, releaseDir: string): string;
31
+ interface NpmPackResult {
32
+ status: number | null;
33
+ stdout: string;
34
+ stderr: string;
35
+ }
36
+ export declare function createNpmPackageTarball(options: {
37
+ projectRoot: string;
38
+ releaseDir: string;
39
+ npm?: {
40
+ command: string;
41
+ argsPrefix: string[];
42
+ };
43
+ spawn?: (command: string, args: string[], options: object) => NpmPackResult;
44
+ }): string;
45
+ export declare function writeReleaseManifest(manifest: ReleaseManifest, releaseDir: string): string;
46
+ export declare function packageReleaseArtifacts(options: {
47
+ projectRoot: string;
48
+ releaseDir?: string;
49
+ spawn?: (command: string, args: string[], options: object) => NpmPackResult;
50
+ }): ReleaseManifest;
16
51
  export {};
@@ -1,18 +1,14 @@
1
1
  import { spawnSync } from 'child_process';
2
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from 'fs';
3
3
  import { join, relative, resolve } from 'path';
4
- import { fileURLToPath } from 'url';
5
4
  import { deflateRawSync } from 'zlib';
6
- const Dirname = fileURLToPath(new URL('.', import.meta.url));
7
- const PROJECT_ROOT = resolve(Dirname, '..', '..');
8
- const RELEASE_DIR = resolve(PROJECT_ROOT, 'artifacts', 'release');
9
5
  const FIXED_ZIP_DATE = new Date(Date.UTC(1980, 0, 1, 0, 0, 0));
10
6
  const ZIP_VERSION = 20;
11
7
  const ZIP_UTF8_FLAG = 0x0800;
12
8
  const ZIP_METHOD_DEFLATE = 8;
13
9
  const ZIP_FILE_MODE = 0o100644;
14
10
  const CRC32_TABLE = buildCrc32Table();
15
- function comparePaths(left, right) {
11
+ export function comparePaths(left, right) {
16
12
  if (left < right)
17
13
  return -1;
18
14
  if (left > right)
@@ -138,21 +134,21 @@ function encodeDosDateTime(value) {
138
134
  function readJsonFile(path) {
139
135
  return JSON.parse(readFileSync(path, 'utf8'));
140
136
  }
141
- function requirePath(path, description) {
137
+ export function requirePath(path, description) {
142
138
  if (!existsSync(path)) {
143
139
  throw new Error(`${description} was not found at ${path}. Run the required build command(s) first.`);
144
140
  }
145
141
  }
146
- function toProjectRelative(path) {
147
- return relative(PROJECT_ROOT, path).split('\\').join('/');
142
+ export function toProjectRelative(projectRoot, path) {
143
+ return relative(projectRoot, path).split('\\').join('/');
148
144
  }
149
- function resolveNpmCommand() {
150
- const npmExecPath = process.env['npm_execpath'];
145
+ export function resolveNpmCommand(options = {}) {
146
+ const npmExecPath = options.npmExecPath ?? process.env['npm_execpath'];
151
147
  if (npmExecPath) {
152
- return { command: process.execPath, argsPrefix: [npmExecPath] };
148
+ return { command: options.execPath ?? process.execPath, argsPrefix: [npmExecPath] };
153
149
  }
154
150
  return {
155
- command: process.platform === 'win32' ? 'npm.cmd' : 'npm',
151
+ command: (options.platform ?? process.platform) === 'win32' ? 'npm.cmd' : 'npm',
156
152
  argsPrefix: [],
157
153
  };
158
154
  }
@@ -164,60 +160,60 @@ export function parseNpmPackFilename(output) {
164
160
  }
165
161
  return filename;
166
162
  }
167
- function createExtensionArchive(metadata) {
168
- const extensionDir = resolve(PROJECT_ROOT, 'dist', 'extension');
163
+ export function createExtensionArchive(metadata, projectRoot, releaseDir) {
164
+ const extensionDir = resolve(projectRoot, 'dist', 'extension');
169
165
  requirePath(extensionDir, 'Extension build output');
170
- const zipPath = resolve(RELEASE_DIR, `${sanitizeArtifactName(metadata.name)}-extension-v${metadata.version}.zip`);
166
+ const zipPath = resolve(releaseDir, `${sanitizeArtifactName(metadata.name)}-extension-v${metadata.version}.zip`);
171
167
  const zipBuffer = createDeterministicZip(collectZipEntries(extensionDir));
172
168
  writeFileSync(zipPath, zipBuffer);
173
169
  return zipPath;
174
170
  }
175
- function createNpmPackageTarball() {
176
- requirePath(resolve(PROJECT_ROOT, 'dist', 'node-server'), 'CLI build output');
177
- requirePath(resolve(PROJECT_ROOT, 'dist', 'ui'), 'UI build output');
178
- const npm = resolveNpmCommand();
179
- const result = spawnSync(npm.command, [...npm.argsPrefix, 'pack', '--json', '--ignore-scripts', '--pack-destination', RELEASE_DIR], {
180
- cwd: PROJECT_ROOT,
171
+ export function createNpmPackageTarball(options) {
172
+ requirePath(resolve(options.projectRoot, 'dist', 'node-server'), 'CLI build output');
173
+ requirePath(resolve(options.projectRoot, 'dist', 'ui'), 'UI build output');
174
+ const npm = options.npm ?? resolveNpmCommand();
175
+ const args = [
176
+ ...npm.argsPrefix,
177
+ 'pack',
178
+ '--json',
179
+ '--ignore-scripts',
180
+ '--pack-destination',
181
+ options.releaseDir,
182
+ ];
183
+ const spawnOptions = {
184
+ cwd: options.projectRoot,
181
185
  encoding: 'utf8',
182
186
  stdio: ['ignore', 'pipe', 'pipe'],
183
- });
187
+ };
188
+ const result = options.spawn
189
+ ? options.spawn(npm.command, args, spawnOptions)
190
+ : spawnSync(npm.command, args, spawnOptions);
184
191
  if (result.status !== 0) {
185
192
  throw new Error((result.stderr || result.stdout || 'npm pack failed').trim());
186
193
  }
187
- return resolve(RELEASE_DIR, parseNpmPackFilename(result.stdout));
194
+ return resolve(options.releaseDir, parseNpmPackFilename(result.stdout));
188
195
  }
189
- function writeReleaseManifest(manifest) {
190
- const manifestPath = resolve(RELEASE_DIR, 'release-artifacts.json');
196
+ export function writeReleaseManifest(manifest, releaseDir) {
197
+ const manifestPath = resolve(releaseDir, 'release-artifacts.json');
191
198
  writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
192
199
  return manifestPath;
193
200
  }
194
- export function packageReleaseArtifacts() {
195
- const packageJson = readJsonFile(resolve(PROJECT_ROOT, 'package.json'));
196
- rmSync(RELEASE_DIR, { recursive: true, force: true });
197
- mkdirSync(RELEASE_DIR, { recursive: true });
198
- const extensionArchive = createExtensionArchive(packageJson);
199
- const npmPackageTarball = createNpmPackageTarball();
201
+ export function packageReleaseArtifacts(options) {
202
+ const releaseDir = options.releaseDir ?? resolve(options.projectRoot, 'artifacts', 'release');
203
+ const packageJson = readJsonFile(resolve(options.projectRoot, 'package.json'));
204
+ rmSync(releaseDir, { recursive: true, force: true });
205
+ mkdirSync(releaseDir, { recursive: true });
206
+ const extensionArchive = createExtensionArchive(packageJson, options.projectRoot, releaseDir);
207
+ const npmPackageTarball = createNpmPackageTarball({
208
+ projectRoot: options.projectRoot,
209
+ releaseDir,
210
+ spawn: options.spawn,
211
+ });
200
212
  const manifest = {
201
213
  version: packageJson.version,
202
- extensionArchive: toProjectRelative(extensionArchive),
203
- npmPackageTarball: toProjectRelative(npmPackageTarball),
214
+ extensionArchive: toProjectRelative(options.projectRoot, extensionArchive),
215
+ npmPackageTarball: toProjectRelative(options.projectRoot, npmPackageTarball),
204
216
  };
205
- writeReleaseManifest(manifest);
217
+ writeReleaseManifest(manifest, releaseDir);
206
218
  return manifest;
207
219
  }
208
- function main() {
209
- const manifest = packageReleaseArtifacts();
210
- console.log(`Created extension archive: ${manifest.extensionArchive}`);
211
- console.log(`Created npm package tarball: ${manifest.npmPackageTarball}`);
212
- console.log(`Created release manifest: ${toProjectRelative(resolve(RELEASE_DIR, 'release-artifacts.json'))}`);
213
- }
214
- if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
215
- try {
216
- main();
217
- }
218
- catch (error) {
219
- const message = error instanceof Error ? error.message : String(error);
220
- console.error(`[package:release] ${message}`);
221
- process.exit(1);
222
- }
223
- }
@@ -1,4 +1,5 @@
1
- import type { Express } from 'express';
1
+ import { Transform } from 'node:stream';
2
+ import type { Express, Request, Response } from 'express';
2
3
  import type { SecretProxyManager } from '../secrets/proxy-manager.js';
3
4
  import { AgentActivityTracker } from './agent-activity.js';
4
5
  export interface FetchProxyDeps {
@@ -13,6 +14,43 @@ export interface FetchProxyDeps {
13
14
  */
14
15
  logger?: Pick<Console, 'log' | 'warn' | 'error'>;
15
16
  }
17
+ export declare function attachUpstreamAbort(res: Response): {
18
+ controller: AbortController;
19
+ detach: () => void;
20
+ };
21
+ /** Get the body — either from express.json()'s parsed body or raw chunks. */
22
+ export declare function collectRawBody(req: Request): Promise<Buffer>;
23
+ /**
24
+ * Build the forwarded header set: copy non-hop-by-hop headers, then restore
25
+ * the forbidden-header transports (Cookie/Origin/Referer/Proxy-*) the browser
26
+ * could not send via fetch(). `accept-encoding` is skipped so undici
27
+ * negotiates gzip/br and decompresses transparently (#3037).
28
+ */
29
+ export declare function buildForwardHeaders(req: Request, targetUrl: string): Record<string, string>;
30
+ interface ForbiddenSecret {
31
+ secretName: string;
32
+ hostname: string;
33
+ }
34
+ /**
35
+ * Apply request-side secret injection: unmask headers and URL-embedded
36
+ * credentials. Mutates `headers` to attach a synthetic Authorization when the
37
+ * URL carried credentials. Returns the forbidden descriptor when a masked
38
+ * secret is used against a domain it is not scoped to, else the cleaned URL.
39
+ */
40
+ export declare function injectRequestSecrets(secretProxy: SecretProxyManager, headers: Record<string, string>, targetUrl: string, targetHostname: string): {
41
+ forbidden: ForbiddenSecret;
42
+ } | {
43
+ cleanedUrl: string;
44
+ };
45
+ /**
46
+ * Buffer-aware UTF-8 secret scrubber. A `StringDecoder` keeps trailing partial
47
+ * multi-byte sequences out of the scrub so codepoints straddling a chunk
48
+ * boundary aren't corrupted (fatal for CJK/emoji model output). Pass-through
49
+ * when the body is non-text or no secrets are configured.
50
+ */
51
+ export declare function createScrubStream(secretProxy: SecretProxyManager, isText: boolean): Transform;
52
+ /** Stream the upstream body to the client through the secret-scrub transform. */
53
+ export declare function streamUpstreamBody(res: Response, upstream: globalThis.Response, secretProxy: SecretProxyManager, detachClientClose: () => void): void;
16
54
  /**
17
55
  * Fetch proxy — forwards cross-origin requests from the browser to bypass
18
56
  * CORS (used by just-bash's curl, which calls the browser's fetch() API).
@@ -20,3 +58,4 @@ export interface FetchProxyDeps {
20
58
  * checks req.body first.
21
59
  */
22
60
  export declare function registerFetchProxyRoute(app: Express, deps: FetchProxyDeps): void;
61
+ export {};
@@ -4,6 +4,18 @@ import { HMAC_SIGN_HEADER, isFormContentType, isLoopbackOrigin, isTextContentTyp
4
4
  import { createMaybeGunzipStream } from '../fetch-proxy-gzip.js';
5
5
  import { buildFetchProxyExposeHeaders, FETCH_PROXY_CONTENT_LENGTH_HEADER, FETCH_PROXY_SKIP_HEADERS, FETCH_PROXY_SKIP_RESPONSE_HEADERS, FETCH_PROXY_SKIP_RESPONSE_PREFIXES, } from '../fetch-proxy-headers.js';
6
6
  import { AgentActivityTracker, registerAgentActivityRoute } from './agent-activity.js';
7
+ export function attachUpstreamAbort(res) {
8
+ const controller = new AbortController();
9
+ const onClose = () => {
10
+ if (!res.writableEnded)
11
+ controller.abort();
12
+ };
13
+ res.on('close', onClose);
14
+ return {
15
+ controller,
16
+ detach: () => res.off('close', onClose),
17
+ };
18
+ }
7
19
  /** Pick the first value of a possibly-multi-valued request header. */
8
20
  function firstHeaderValue(value) {
9
21
  if (value === undefined)
@@ -11,7 +23,7 @@ function firstHeaderValue(value) {
11
23
  return Array.isArray(value) ? value[0] : value;
12
24
  }
13
25
  /** Get the body — either from express.json()'s parsed body or raw chunks. */
14
- async function collectRawBody(req) {
26
+ export async function collectRawBody(req) {
15
27
  if (req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0) {
16
28
  // Body was already parsed by express.json() — re-serialize it.
17
29
  return Buffer.from(JSON.stringify(req.body), 'utf-8');
@@ -28,7 +40,7 @@ async function collectRawBody(req) {
28
40
  * could not send via fetch(). `accept-encoding` is skipped so undici
29
41
  * negotiates gzip/br and decompresses transparently (#3037).
30
42
  */
31
- function buildForwardHeaders(req, targetUrl) {
43
+ export function buildForwardHeaders(req, targetUrl) {
32
44
  const headers = {};
33
45
  for (const [key, value] of Object.entries(req.headers)) {
34
46
  if (!FETCH_PROXY_SKIP_HEADERS.has(key) && typeof value === 'string') {
@@ -82,7 +94,7 @@ function buildForwardHeaders(req, targetUrl) {
82
94
  * URL carried credentials. Returns the forbidden descriptor when a masked
83
95
  * secret is used against a domain it is not scoped to, else the cleaned URL.
84
96
  */
85
- function injectRequestSecrets(secretProxy, headers, targetUrl, targetHostname) {
97
+ export function injectRequestSecrets(secretProxy, headers, targetUrl, targetHostname) {
86
98
  if (!secretProxy.hasSecrets())
87
99
  return { cleanedUrl: targetUrl };
88
100
  const headerResult = secretProxy.unmaskHeaders(headers, targetHostname);
@@ -200,7 +212,7 @@ function forwardUpstreamHeaders(res, upstream, secretProxy) {
200
212
  * boundary aren't corrupted (fatal for CJK/emoji model output). Pass-through
201
213
  * when the body is non-text or no secrets are configured.
202
214
  */
203
- function createScrubStream(secretProxy, isText) {
215
+ export function createScrubStream(secretProxy, isText) {
204
216
  const utf8Decoder = new StringDecoder('utf8');
205
217
  return new Transform({
206
218
  transform(chunk, _enc, cb) {
@@ -241,7 +253,7 @@ function createScrubStream(secretProxy, isText) {
241
253
  });
242
254
  }
243
255
  /** Stream the upstream body to the client through the secret-scrub transform. */
244
- function streamUpstreamBody(res, upstream, secretProxy, detachClientClose) {
256
+ export function streamUpstreamBody(res, upstream, secretProxy, detachClientClose) {
245
257
  const isText = isTextContentType(upstream.headers.get('content-type') ?? '');
246
258
  const upstreamStream = Readable.fromWeb(upstream.body);
247
259
  // Text only: a real `application/gzip` / `.tar.gz` body is a gzip member
@@ -349,13 +361,9 @@ export function registerFetchProxyRoute(app, deps) {
349
361
  // streams (LLM SSE completions) are torn down promptly. Listen on
350
362
  // `res.on('close')`, not `req.on('close')` — Node fires req close as soon
351
363
  // as the request body is consumed, which would abort before fetch starts.
352
- const abortController = new AbortController();
353
- onClientClose = () => {
354
- if (!res.writableEnded)
355
- abortController.abort();
356
- };
357
- res.on('close', onClientClose);
358
- fetchInit.signal = abortController.signal;
364
+ const upstreamAbort = attachUpstreamAbort(res);
365
+ onClientClose = upstreamAbort.detach;
366
+ fetchInit.signal = upstreamAbort.controller.signal;
359
367
  const upstream = await fetch(injection.cleanedUrl, fetchInit);
360
368
  logger.log(`[fetch-proxy] ${req.method} ${targetUrl} ← ${upstream.status}`);
361
369
  forwardUpstreamHeaders(res, upstream, secretProxy);
@@ -1,14 +1,36 @@
1
- import { type Express } from 'express';
1
+ import { type Express, type Response } from 'express';
2
2
  import type { EnvSecretStore } from '../secrets/env-secret-store.js';
3
3
  import type { OauthSecretStore } from '../secrets/oauth-secret-store.js';
4
4
  import type { SecretProxyManager } from '../secrets/proxy-manager.js';
5
+ import { handleDaSignAndForward, handleS3SignAndForward } from '../secrets/sign-and-forward.js';
5
6
  export interface SecretRoutesDeps {
6
7
  secretStore: EnvSecretStore;
7
8
  secretProxy: SecretProxyManager;
8
9
  oauthStore: OauthSecretStore;
9
10
  /** When true, the full sign-and-forward error is logged for the local operator. */
10
11
  devMode: boolean;
12
+ handleS3?: typeof handleS3SignAndForward;
13
+ handleDa?: typeof handleDaSignAndForward;
11
14
  }
15
+ /**
16
+ * Sign-and-forward failure responder. Logs only a generic line + trace id —
17
+ * the err.message can carry profile names, bucket names, or partial URLs we
18
+ * don't want in shared log aggregators. The trace id lets the user correlate
19
+ * the 500 they got with the server log; the detail goes to the file logger
20
+ * (above DEBUG) only when devMode is on.
21
+ */
22
+ export declare function respondSignAndForwardError(res: Response, err: unknown, devMode: boolean, label: string): void;
23
+ /**
24
+ * Scope edit handler — updates allowed domains of an existing secret without
25
+ * changing its value. Checks session store first, then falls back to persisted.
26
+ */
27
+ export declare function handleScopeEdit(res: Response, name: unknown, domains: unknown, secretStore: EnvSecretStore, secretProxy: SecretProxyManager): Promise<Response>;
28
+ /**
29
+ * Persisted/session delete handler — checks the session store first so a
30
+ * session shadow does not leak through after deletion, then falls back to the
31
+ * persisted store. Reloads the masking pipeline either way.
32
+ */
33
+ export declare function handleDeleteSecret(name: string | undefined, res: Response, secretStore: EnvSecretStore, secretProxy: SecretProxyManager): Promise<Response>;
12
34
  /**
13
35
  * Secret management API — direct .env file access (no browser needed). The
14
36
  * `secretStore` is wired into `secretProxy` so the fetch-proxy and the