aicodeman 1.46.0 → 1.47.0

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 (129) hide show
  1. package/dist/ralph-fix-plan-watcher.d.ts +13 -1
  2. package/dist/ralph-fix-plan-watcher.d.ts.map +1 -1
  3. package/dist/ralph-fix-plan-watcher.js +21 -3
  4. package/dist/ralph-fix-plan-watcher.js.map +1 -1
  5. package/dist/ralph-tracker.d.ts +6 -0
  6. package/dist/ralph-tracker.d.ts.map +1 -1
  7. package/dist/ralph-tracker.js +8 -0
  8. package/dist/ralph-tracker.js.map +1 -1
  9. package/dist/web/public/admin-ui.js.gz +0 -0
  10. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  11. package/dist/web/public/{app.3093527f.js → app.fe8a6b4c.js} +16 -16
  12. package/dist/web/public/app.fe8a6b4c.js.br +0 -0
  13. package/dist/web/public/app.fe8a6b4c.js.gz +0 -0
  14. package/dist/web/public/approvals-ui.js.gz +0 -0
  15. package/dist/web/public/constants.affcdde6.js.gz +0 -0
  16. package/dist/web/public/cron-ui.js.gz +0 -0
  17. package/dist/web/public/entrance-animations.js.gz +0 -0
  18. package/dist/web/public/git-status-ui.js.gz +0 -0
  19. package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
  20. package/dist/web/public/home-sessions.js.gz +0 -0
  21. package/dist/web/public/host-wake-ui.js.gz +0 -0
  22. package/dist/web/public/i18n.a618ef4e.js +2 -0
  23. package/dist/web/public/i18n.a618ef4e.js.br +0 -0
  24. package/dist/web/public/i18n.a618ef4e.js.gz +0 -0
  25. package/dist/web/public/{image-input.941386e7.js → image-input.a5d0f8f3.js} +18 -9
  26. package/dist/web/public/image-input.a5d0f8f3.js.br +0 -0
  27. package/dist/web/public/image-input.a5d0f8f3.js.gz +0 -0
  28. package/dist/web/public/index.html +8 -8
  29. package/dist/web/public/index.html.br +0 -0
  30. package/dist/web/public/index.html.gz +0 -0
  31. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  32. package/dist/web/public/{keyboard-accessory.4e8b20ac.js → keyboard-accessory.ff64fafc.js} +5 -1
  33. package/dist/web/public/keyboard-accessory.ff64fafc.js.br +0 -0
  34. package/dist/web/public/keyboard-accessory.ff64fafc.js.gz +0 -0
  35. package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
  36. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  37. package/dist/web/public/mobile-overview.js.gz +0 -0
  38. package/dist/web/public/mobile.1bbd2d67.css.gz +0 -0
  39. package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
  40. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  41. package/dist/web/public/panels-ui.4a56ee63.js.gz +0 -0
  42. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  43. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  44. package/dist/web/public/readmymind-ui.js.gz +0 -0
  45. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  46. package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
  47. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  48. package/dist/web/public/session-lineage.js.gz +0 -0
  49. package/dist/web/public/{session-ui.186f9419.js → session-ui.426fb671.js} +6 -6
  50. package/dist/web/public/session-ui.426fb671.js.br +0 -0
  51. package/dist/web/public/session-ui.426fb671.js.gz +0 -0
  52. package/dist/web/public/settings-ui.a191154e.js.gz +0 -0
  53. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  54. package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
  55. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  56. package/dist/web/public/styles.4f0c67a0.css +1 -0
  57. package/dist/web/public/styles.4f0c67a0.css.br +0 -0
  58. package/dist/web/public/styles.4f0c67a0.css.gz +0 -0
  59. package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
  60. package/dist/web/public/sw.js +2 -2
  61. package/dist/web/public/sw.js.br +0 -0
  62. package/dist/web/public/sw.js.gz +0 -0
  63. package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
  64. package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
  65. package/dist/web/public/terminal-keycode229-recovery.98843069.js +1 -0
  66. package/dist/web/public/terminal-keycode229-recovery.98843069.js.br +0 -0
  67. package/dist/web/public/terminal-keycode229-recovery.98843069.js.gz +0 -0
  68. package/dist/web/public/terminal-split.js.gz +0 -0
  69. package/dist/web/public/terminal-tile.js +8 -6
  70. package/dist/web/public/terminal-tile.js.br +0 -0
  71. package/dist/web/public/terminal-tile.js.gz +0 -0
  72. package/dist/web/public/terminal-ui.7e787829.js.gz +0 -0
  73. package/dist/web/public/tile-grid.js.gz +0 -0
  74. package/dist/web/public/ultracode-panel.js.gz +0 -0
  75. package/dist/web/public/ultracode-windows.js.gz +0 -0
  76. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  77. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  78. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  79. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  80. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  81. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  82. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  83. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  84. package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
  85. package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
  86. package/dist/web/public/vendor/xterm.css.gz +0 -0
  87. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  88. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  89. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  90. package/dist/web/public/webview-tabs.js.gz +0 -0
  91. package/dist/web/public/whats-new.json +8 -9
  92. package/dist/web/request-body-parser.d.ts +23 -0
  93. package/dist/web/request-body-parser.d.ts.map +1 -0
  94. package/dist/web/request-body-parser.js +57 -0
  95. package/dist/web/request-body-parser.js.map +1 -0
  96. package/dist/web/routes/file-routes.d.ts +5 -0
  97. package/dist/web/routes/file-routes.d.ts.map +1 -1
  98. package/dist/web/routes/file-routes.js +5 -1
  99. package/dist/web/routes/file-routes.js.map +1 -1
  100. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  101. package/dist/web/routes/ralph-routes.js +45 -2
  102. package/dist/web/routes/ralph-routes.js.map +1 -1
  103. package/dist/web/routes/session-routes.d.ts.map +1 -1
  104. package/dist/web/routes/session-routes.js +5 -0
  105. package/dist/web/routes/session-routes.js.map +1 -1
  106. package/dist/web/routes/system-routes.d.ts.map +1 -1
  107. package/dist/web/routes/system-routes.js +3 -2
  108. package/dist/web/routes/system-routes.js.map +1 -1
  109. package/dist/web/server.d.ts.map +1 -1
  110. package/dist/web/server.js +18 -4
  111. package/dist/web/server.js.map +1 -1
  112. package/package.json +1 -1
  113. package/dist/web/public/app.3093527f.js.br +0 -0
  114. package/dist/web/public/app.3093527f.js.gz +0 -0
  115. package/dist/web/public/i18n.70265257.js +0 -2
  116. package/dist/web/public/i18n.70265257.js.br +0 -0
  117. package/dist/web/public/i18n.70265257.js.gz +0 -0
  118. package/dist/web/public/image-input.941386e7.js.br +0 -0
  119. package/dist/web/public/image-input.941386e7.js.gz +0 -0
  120. package/dist/web/public/keyboard-accessory.4e8b20ac.js.br +0 -0
  121. package/dist/web/public/keyboard-accessory.4e8b20ac.js.gz +0 -0
  122. package/dist/web/public/session-ui.186f9419.js.br +0 -0
  123. package/dist/web/public/session-ui.186f9419.js.gz +0 -0
  124. package/dist/web/public/styles.7238eae8.css +0 -1
  125. package/dist/web/public/styles.7238eae8.css.br +0 -0
  126. package/dist/web/public/styles.7238eae8.css.gz +0 -0
  127. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js +0 -1
  128. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.br +0 -0
  129. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
@@ -26,7 +26,7 @@
26
26
  // not the current one, so the old constant key meant that cleanup never deleted
27
27
  // anything — hashed assets from every release ever deployed accumulated in one
28
28
  // bucket until the origin hit its storage quota.
29
- const BUILD_ID = '86c261a69d9f';
29
+ const BUILD_ID = 'fada7ad2af00';
30
30
  const CACHE_NAME = `codeman-${BUILD_ID}`;
31
31
 
32
32
  // Reverse-proxy base path: the worker is served at `<base>/sw.js`, so its own
@@ -48,7 +48,7 @@ const B = (p) => (p && p[0] === '/' ? SW_BASE + p : p);
48
48
  // handler caches every successful GET at runtime, but the precache warmed
49
49
  // nothing while looking like it did. Deriving it from the same manifest that
50
50
  // renames the files is the only thing that keeps the two from drifting again.
51
- const HASHED_ASSETS = ["styles.7238eae8.css", "mobile.1bbd2d67.css", "constants.affcdde6.js", "i18n.70265257.js", "mobile-handlers.f7c3f692.js", "voice-input.2342c37b.js", "notification-manager.feb82c04.js", "keyboard-accessory.4e8b20ac.js", "input-cjk.8bc46081.js", "mobile-ime-preview.2dbaf262.js", "terminal-keycode229-recovery.af09f99a.js", "sanitize-html.e7147e76.js", "tab-layout-browser.2d6189f7.js", "app.3093527f.js", "tab-rail-resize.b5cdceaf.js", "header-divider.eb2d2463.js", "terminal-ui.7e787829.js", "respawn-ui.d7adcf6a.js", "ralph-panel.b350e2ef.js", "settings-ui.a191154e.js", "panels-ui.4a56ee63.js", "session-ui.186f9419.js", "ralph-wizard.b6c0dd19.js", "api-client.c9b1cddc.js", "subagent-windows.07277a58.js", "image-input.941386e7.js", "spreadsheet-preview.75c751de.js", "vendor/xterm-zerolag-input.1be42307.js", "vendor/xterm-predictive-echo.276b11d9.js"];
51
+ const HASHED_ASSETS = ["styles.4f0c67a0.css", "mobile.1bbd2d67.css", "constants.affcdde6.js", "i18n.a618ef4e.js", "mobile-handlers.f7c3f692.js", "voice-input.2342c37b.js", "notification-manager.feb82c04.js", "keyboard-accessory.ff64fafc.js", "input-cjk.8bc46081.js", "mobile-ime-preview.2dbaf262.js", "terminal-keycode229-recovery.98843069.js", "sanitize-html.e7147e76.js", "tab-layout-browser.2d6189f7.js", "app.fe8a6b4c.js", "tab-rail-resize.b5cdceaf.js", "header-divider.eb2d2463.js", "terminal-ui.7e787829.js", "respawn-ui.d7adcf6a.js", "ralph-panel.b350e2ef.js", "settings-ui.a191154e.js", "panels-ui.4a56ee63.js", "session-ui.426fb671.js", "ralph-wizard.b6c0dd19.js", "api-client.c9b1cddc.js", "subagent-windows.07277a58.js", "image-input.a5d0f8f3.js", "spreadsheet-preview.75c751de.js", "vendor/xterm-zerolag-input.1be42307.js", "vendor/xterm-predictive-echo.276b11d9.js"];
52
52
 
53
53
  // Core app shell -- cached on install for instant startup
54
54
  const APP_SHELL = [
Binary file
Binary file
@@ -0,0 +1 @@
1
+ "use strict";(function(c){"use strict";function T(o,t){const u=Array.from(o),d=Array.from(t);let r=0;const y=Math.min(u.length,d.length);for(;r<y&&u[r]===d[r];)r+=1;return{deleted:u.length-r,inserted:d.slice(r).join("")}}function D(){try{return/Android/i.test(c.navigator?.userAgent||"")?!0:c.matchMedia?.("(pointer: coarse)")?.matches===!0}catch{return!1}}function M(o){if(!D())return null;const t=o?.textarea,u=o?.emitRecovered;if(!t?.addEventListener||!t?.removeEventListener||typeof u!="function")return null;const d=o.isScreenReaderMode,r=o.setTimer||c.setTimeout.bind(c),y=o.clearTimer||c.clearTimeout.bind(c);let i=!1,p=0,x=0,v=!1;const m=[];let g=()=>{};function j(){for(const e of m.splice(0)){if(e.timer!==null){try{y(e.timer)}catch{}e.timer=null}S(e)}}function C(){for(const e of m.splice(0))if(e.active=!1,e.timer!==null){try{y(e.timer)}catch{}e.timer=null}}function S(e){const n=m.indexOf(e);if(n!==-1&&m.splice(n,1),e.timer=null,!(!e.active||i)&&(e.active=!1,!(p>e.snapshot)))try{u(e.data)}catch{}}function k(){p+=1}function K(e){i||e?.type!=="keydown"||(g(e),j(),x=p)}function w(e){if(i||v||e?.isComposing||e.inputType!=="insertText")return;const n=e.data;if(typeof n!="string"||n==="")return;try{if(d?.())return}catch{return}const a={data:n,snapshot:x??p,active:!0,timer:null};m.push(a);try{a.timer=r(()=>S(a),0)}catch{C()}}function O(){let e;try{e=o.getCompositionHelper?.()}catch{return null}const n=e?._handleAnyTextareaChanges,a=e?._coreService;if(typeof n!="function"||typeof a?.triggerDataEvent!="function")return null;let h=t.value;const f=new Set;function R(){if(i||e._isComposing)return;const l=t.value;if(l===h)return;const{deleted:s,inserted:E}=T(h,l);h=l;try{for(let z=0;z<s;z+=1)a.triggerDataEvent("\x7F",!0);E&&(e._dataAlreadySent=E,a.triggerDataEvent(E,!0))}catch{}}return e._handleAnyTextareaChanges=function(){if(i)return n.call(this);f.size===0&&h!==t.value&&(h=t.value);const s={id:null};f.add(s),s.id=r(()=>{f.delete(s),R()},0)},g=l=>{if(f.size!==0){for(const s of f)try{y(s.id)}catch{}f.clear(),!(e._isSendingComposition&&![229,20,16,17,18].includes(l?.keyCode))&&R()}},()=>{g=()=>{},e._handleAnyTextareaChanges!==n&&(e._handleAnyTextareaChanges=n)}}const P=O();function A(){i||(v=!0,C())}function L(){i||(v=!1)}function _(){if(!i){i=!0,C();try{P?.()}catch{}try{t.removeEventListener("input",w,!0),t.removeEventListener("compositionstart",A,!0),t.removeEventListener("compositionend",L,!0)}catch{}}}try{t.addEventListener("input",w,!0),t.addEventListener("compositionstart",A,!0),t.addEventListener("compositionend",L,!0)}catch{return _(),null}return Object.freeze({handleKeyEvent:K,notifyCanonicalData:k,destroy:_})}c.CodemanKeyCode229Recovery=Object.freeze({create:M,editBetween:T})})(typeof window<"u"?window:globalThis);
@@ -344,9 +344,10 @@
344
344
  if (global.app?.tileShortcutFor?.(ev)) return false;
345
345
  // Ctrl+V / Cmd+V: the primary pane's paste trap, aimed at THIS pane, so
346
346
  // a pasted image uploads to this pane's session and its path is typed
347
- // here, and pasted text goes into this xterm with its bracketed-paste
348
- // markers intact. Mirrors terminal-ui.js's own Ctrl+V gate; without it
349
- // xterm's default only ever pasted text.
347
+ // here. Plain text goes to this pane's session as a Codeman-built
348
+ // bracketed-paste frame; this terminal is only refocused and receives
349
+ // input(framed), which onData delivers. Mirrors terminal-ui.js's own
350
+ // Ctrl+V gate; without it xterm's default only ever pasted text.
350
351
  if ((ev.ctrlKey || ev.metaKey) && ev.key === 'v' && ev.type === 'keydown') {
351
352
  global.app?._handleImagePaste?.({ terminal: this.terminal, sessionId: this.sessionId });
352
353
  return false;
@@ -646,9 +647,10 @@
646
647
  // Recovered text goes straight to _onTerminalData(), never through xterm's
647
648
  // onData, so it is not counted as xterm's own (see connect()'s onData).
648
649
  // Created after terminal.open(): xterm's capture `input` listener on the
649
- // textarea is registered there, and must run before the controller's. No
650
- // device or mode gate, as in the primary pane: with a hardware keyboard it
651
- // costs one assignment per keydown. A failure leaves xterm's own handling.
650
+ // textarea is registered there, and must run before the controller's.
651
+ // create() returns null on a fine pointer (#716): the recovery is for soft
652
+ // keyboards, and on a desktop it delivers a dead-key character twice.
653
+ // A failure leaves xterm's own handling.
652
654
  _createKeyCode229Recovery() {
653
655
  this._destroyKeyCode229Recovery();
654
656
  if (!this.terminal) return;
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,13 +1,12 @@
1
1
  {
2
- "version": "1.46.0",
2
+ "version": "1.47.0",
3
3
  "highlights": [
4
- "Fetch from the Git status window (#714)",
5
- "Start a session beside any tab (#712, opt-in)",
6
- "The phone keyboard no longer costs Claude its transcript (#711)",
7
- "Typing stays smooth in a long prompt (#713)",
8
- "Links survive wrapping and spaces (#686, #687)",
9
- "`codeman agent spawn` in a project directory (#708)",
10
- "Read saved Codex conversations offline (#707)",
11
- "Experimental: bounded inspection and guarded submission for agents (#694, opt-in)"
4
+ "The wrapped tab strip uses the room under the toolbar (#721)",
5
+ "Bodyless requests work through Cloudflare Tunnel (#725)",
6
+ "A desktop dead key is typed once, and Ctrl+V stays a paste after a replay (#726, #716)",
7
+ "Remote sessions no longer touch the local disk at their path (#722, #727, #728)",
8
+ "Rendered Markdown fills a resized File Viewer (#730)",
9
+ "A middle click on a file path opens the file (#731)",
10
+ "No more duplicate-key warning on build (#729)"
12
11
  ]
13
12
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fileoverview Accept empty, untyped API POSTs even when a reverse proxy frames them as chunked.
3
+ *
4
+ * Cloudflare Tunnel (cloudflared, Go's HTTP client) forwards a bodyless POST as an HTTP/1.1
5
+ * chunked request with no Content-Type and a zero-length body. Fastify only skips its
6
+ * content-type check for a `Content-Length: 0` request, so this framing was refused 415
7
+ * (`FST_ERR_CTP_INVALID_MEDIA_TYPE`) before the route ran, breaking actions such as opening a
8
+ * saved web tab. This parser lets such a request reach its route with `request.body === undefined`,
9
+ * exactly as a `Content-Length: 0` request does.
10
+ *
11
+ * Scope is POST under `/api/` only: Go frames an empty body of unknown length as chunked for
12
+ * POST, PUT and PATCH and drops it for DELETE, and every PUT/PATCH the frontend sends carries a
13
+ * JSON body. Anything else (another method, a non-API route, a Content-Type, a non-zero
14
+ * Content-Length, or a single body byte) is still the 415 Fastify would have answered.
15
+ *
16
+ * ⚠️ It owns the ROOT `'*'` content-type slot. The JSON, `text/plain` (kept RAW, see the CSRF
17
+ * note in CLAUDE.md) and multipart parsers match first; the web-tab proxy registers its own
18
+ * `'*'` in an encapsulated scope and is unaffected. Fastify refuses a second root `'*'` parser at
19
+ * boot (`FST_ERR_CTP_ALREADY_PRESENT`), so extend this function instead of adding one.
20
+ */
21
+ import { type FastifyInstance } from 'fastify';
22
+ export declare function installEmptyApiPostParser(app: FastifyInstance): void;
23
+ //# sourceMappingURL=request-body-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-body-parser.d.ts","sourceRoot":"","sources":["../../src/web/request-body-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAc,KAAK,eAAe,EAAuB,MAAM,SAAS,CAAC;AAGhF,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAmCpE"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @fileoverview Accept empty, untyped API POSTs even when a reverse proxy frames them as chunked.
3
+ *
4
+ * Cloudflare Tunnel (cloudflared, Go's HTTP client) forwards a bodyless POST as an HTTP/1.1
5
+ * chunked request with no Content-Type and a zero-length body. Fastify only skips its
6
+ * content-type check for a `Content-Length: 0` request, so this framing was refused 415
7
+ * (`FST_ERR_CTP_INVALID_MEDIA_TYPE`) before the route ran, breaking actions such as opening a
8
+ * saved web tab. This parser lets such a request reach its route with `request.body === undefined`,
9
+ * exactly as a `Content-Length: 0` request does.
10
+ *
11
+ * Scope is POST under `/api/` only: Go frames an empty body of unknown length as chunked for
12
+ * POST, PUT and PATCH and drops it for DELETE, and every PUT/PATCH the frontend sends carries a
13
+ * JSON body. Anything else (another method, a non-API route, a Content-Type, a non-zero
14
+ * Content-Length, or a single body byte) is still the 415 Fastify would have answered.
15
+ *
16
+ * ⚠️ It owns the ROOT `'*'` content-type slot. The JSON, `text/plain` (kept RAW, see the CSRF
17
+ * note in CLAUDE.md) and multipart parsers match first; the web-tab proxy registers its own
18
+ * `'*'` in an encapsulated scope and is unaffected. Fastify refuses a second root `'*'` parser at
19
+ * boot (`FST_ERR_CTP_ALREADY_PRESENT`), so extend this function instead of adding one.
20
+ */
21
+ import { errorCodes } from 'fastify';
22
+ export function installEmptyApiPostParser(app) {
23
+ // Existing JSON, text and multipart parsers take precedence. The webview
24
+ // proxy installs its own parser in an encapsulated scope.
25
+ app.addContentTypeParser('*', async (req, payload) => {
26
+ // Fastify normally skips an unsupported parser on its 404 path. Preserve
27
+ // that behavior, including the unread stream used by referer fallbacks.
28
+ if (req.is404)
29
+ return undefined;
30
+ if (req.method !== 'POST' ||
31
+ !req.routeOptions.url?.startsWith('/api/') ||
32
+ req.headers['content-type'] !== undefined ||
33
+ (req.headers['content-length'] !== undefined && req.headers['content-length'] !== '0')) {
34
+ throw new errorCodes.FST_ERR_CTP_INVALID_MEDIA_TYPE();
35
+ }
36
+ // Wait for the terminating chunk to prove the body is empty. Reject on the
37
+ // first byte instead of buffering an unsupported body. Leave the socket
38
+ // alive so Fastify can send its 415 and close the connection on parser errors.
39
+ try {
40
+ for await (const chunk of payload.iterator({ destroyOnReturn: false })) {
41
+ if (Buffer.byteLength(chunk) > 0)
42
+ throw new errorCodes.FST_ERR_CTP_INVALID_MEDIA_TYPE();
43
+ }
44
+ }
45
+ catch (error) {
46
+ // Match Fastify's body reader: an interrupted request is a client error.
47
+ const err = error;
48
+ if (typeof err.statusCode !== 'number' || err.statusCode < 400)
49
+ err.statusCode = 400;
50
+ throw err;
51
+ }
52
+ // No synthetic JSON or header rewriting: required-body validation still
53
+ // sees the same undefined value as a direct Content-Length: 0 request.
54
+ return undefined;
55
+ });
56
+ }
57
+ //# sourceMappingURL=request-body-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-body-parser.js","sourceRoot":"","sources":["../../src/web/request-body-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,UAAU,EAA6C,MAAM,SAAS,CAAC;AAGhF,MAAM,UAAU,yBAAyB,CAAC,GAAoB;IAC5D,yEAAyE;IACzE,0DAA0D;IAC1D,GAAG,CAAC,oBAAoB,CAAC,GAAG,EAAE,KAAK,EAAE,GAAmB,EAAE,OAAiB,EAAE,EAAE;QAC7E,yEAAyE;QACzE,wEAAwE;QACxE,IAAI,GAAG,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAEhC,IACE,GAAG,CAAC,MAAM,KAAK,MAAM;YACrB,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC;YAC1C,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,SAAS;YACzC,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,GAAG,CAAC,EACtF,CAAC;YACD,MAAM,IAAI,UAAU,CAAC,8BAA8B,EAAE,CAAC;QACxD,CAAC;QAED,2EAA2E;QAC3E,wEAAwE;QACxE,+EAA+E;QAC/E,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;gBACvE,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC;oBAAE,MAAM,IAAI,UAAU,CAAC,8BAA8B,EAAE,CAAC;YAC1F,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yEAAyE;YACzE,MAAM,GAAG,GAAG,KAAwC,CAAC;YACrD,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,GAAG,GAAG;gBAAE,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;YACrF,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,wEAAwE;QACxE,uEAAuE;QACvE,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -58,6 +58,11 @@ export declare function sendFileBody(reply: FastifyReply, size: number, rangeHea
58
58
  * the form is refused instead of guessed.
59
59
  */
60
60
  export declare const DOCKER_HOME_PATH_ERROR = "A ~/ path inside a Docker case is the container home, which this server cannot read; use the absolute path";
61
+ /**
62
+ * Whether `candidate` is `root` itself or lies beneath it (both already resolved). The one lexical
63
+ * containment check for workspace confinement; ralph-routes.ts uses it for a remote `@fix_plan.md`.
64
+ */
65
+ export declare function isPathWithinRoot(root: string, candidate: string): boolean;
61
66
  export declare function registerFileRoutes(app: FastifyInstance, ctx: SessionPort & EventPort & ConfigPort): void;
62
67
  export {};
63
68
  //# sourceMappingURL=file-routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/file-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAgE5C,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAU5E;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,QAAmB,CAAC;AAgH/D;;;GAGG;AACH,UAAU,cAAc;IACtB,OAAO,EAAE,QAAQ,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,IAC1C,QAAQ;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,KAAG,cAAc,CAGhE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAC1C,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,KAAK,cAAc,GAC/D,IAAI,CAyBN;AAoTD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,+GAC2E,CAAC;AA2tB/G,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,CA09CxG"}
1
+ {"version":3,"file":"file-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/file-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAgE5C,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAU5E;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,QAAmB,CAAC;AAgH/D;;;GAGG;AACH,UAAU,cAAc;IACtB,OAAO,EAAE,QAAQ,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,IAC1C,QAAQ;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,KAAG,cAAc,CAGhE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAC1C,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,KAAK,cAAc,GAC/D,IAAI,CAyBN;AAoTD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,+GAC2E,CAAC;AA0L/G;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAGzE;AA8hBD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,CA09CxG"}
@@ -515,7 +515,11 @@ const FILESYSTEM_BINARY_PREVIEW_LIMIT = 50 * 1024 * 1024;
515
515
  const FILESYSTEM_IMAGE_PREVIEW_EXTENSIONS = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp']);
516
516
  const FILESYSTEM_TEXT_PREVIEW_EXTENSIONS = new Set(['md', 'txt', 'json']);
517
517
  const FILESYSTEM_DOCUMENT_PREVIEW_EXTENSIONS = new Set(['pdf', 'docx', 'pptx']);
518
- function isPathWithinRoot(root, candidate) {
518
+ /**
519
+ * Whether `candidate` is `root` itself or lies beneath it (both already resolved). The one lexical
520
+ * containment check for workspace confinement; ralph-routes.ts uses it for a remote `@fix_plan.md`.
521
+ */
522
+ export function isPathWithinRoot(root, candidate) {
519
523
  const rel = relative(root, candidate);
520
524
  return rel === '' || (!isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${sep}`));
521
525
  }