aicodeman 1.46.0 → 1.48.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.
- package/README.md +3 -0
- package/dist/cli-notify.d.ts +34 -0
- package/dist/cli-notify.d.ts.map +1 -0
- package/dist/cli-notify.js +100 -0
- package/dist/cli-notify.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +16 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/cli-registry/patterns.d.ts +1 -1
- package/dist/config/cli-registry/patterns.d.ts.map +1 -1
- package/dist/config/cli-registry/patterns.js +4 -0
- package/dist/config/cli-registry/patterns.js.map +1 -1
- package/dist/config/cli-registry/schema.d.ts +6 -0
- package/dist/config/cli-registry/schema.d.ts.map +1 -1
- package/dist/config/cli-registry/schema.js +62 -2
- package/dist/config/cli-registry/schema.js.map +1 -1
- package/dist/config/cli-registry/stock.d.ts.map +1 -1
- package/dist/config/cli-registry/stock.js +43 -26
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +47 -17
- package/dist/config/cli-registry/types.d.ts.map +1 -1
- package/dist/config/path-probe.d.ts +6 -0
- package/dist/config/path-probe.d.ts.map +1 -1
- package/dist/config/path-probe.js +6 -0
- package/dist/config/path-probe.js.map +1 -1
- package/dist/config-backup.d.ts +1 -1
- package/dist/config-backup.d.ts.map +1 -1
- package/dist/config-backup.js +2 -0
- package/dist/config-backup.js.map +1 -1
- package/dist/custom-model-hosts.d.ts +32 -8
- package/dist/custom-model-hosts.d.ts.map +1 -1
- package/dist/custom-model-hosts.js +10 -0
- package/dist/custom-model-hosts.js.map +1 -1
- package/dist/custom-model-injection-apply.d.ts +19 -5
- package/dist/custom-model-injection-apply.d.ts.map +1 -1
- package/dist/custom-model-injection-apply.js +88 -8
- package/dist/custom-model-injection-apply.js.map +1 -1
- package/dist/custom-model-injection.d.ts +6 -0
- package/dist/custom-model-injection.d.ts.map +1 -1
- package/dist/custom-model-injection.js +92 -10
- package/dist/custom-model-injection.js.map +1 -1
- package/dist/discord-bridge.d.ts +338 -0
- package/dist/discord-bridge.d.ts.map +1 -0
- package/dist/discord-bridge.js +1256 -0
- package/dist/discord-bridge.js.map +1 -0
- package/dist/edit-history-local-source.d.ts +83 -0
- package/dist/edit-history-local-source.d.ts.map +1 -0
- package/dist/edit-history-local-source.js +198 -0
- package/dist/edit-history-local-source.js.map +1 -0
- package/dist/edit-history-parse.d.ts +114 -0
- package/dist/edit-history-parse.d.ts.map +1 -0
- package/dist/edit-history-parse.js +312 -0
- package/dist/edit-history-parse.js.map +1 -0
- package/dist/edit-history-store.d.ts +209 -0
- package/dist/edit-history-store.d.ts.map +1 -0
- package/dist/edit-history-store.js +599 -0
- package/dist/edit-history-store.js.map +1 -0
- package/dist/git-clone.d.ts +6 -1
- package/dist/git-clone.d.ts.map +1 -1
- package/dist/git-clone.js +79 -14
- package/dist/git-clone.js.map +1 -1
- package/dist/image-watcher.js +4 -4
- package/dist/image-watcher.js.map +1 -1
- package/dist/mux-interface.d.ts +10 -1
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/ralph-fix-plan-watcher.d.ts +13 -1
- package/dist/ralph-fix-plan-watcher.d.ts.map +1 -1
- package/dist/ralph-fix-plan-watcher.js +21 -3
- package/dist/ralph-fix-plan-watcher.js.map +1 -1
- package/dist/ralph-tracker.d.ts +6 -0
- package/dist/ralph-tracker.d.ts.map +1 -1
- package/dist/ralph-tracker.js +8 -0
- package/dist/ralph-tracker.js.map +1 -1
- package/dist/session-cli-registry-bridge.d.ts.map +1 -1
- package/dist/session-cli-registry-bridge.js +4 -0
- package/dist/session-cli-registry-bridge.js.map +1 -1
- package/dist/session.d.ts +70 -3
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +147 -4
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts +40 -0
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +62 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/discord.d.ts +55 -0
- package/dist/types/discord.d.ts.map +1 -0
- package/dist/types/discord.js +18 -0
- package/dist/types/discord.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/session.d.ts +13 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/case-path.d.ts +2 -0
- package/dist/web/case-path.d.ts.map +1 -1
- package/dist/web/case-path.js +3 -3
- package/dist/web/case-path.js.map +1 -1
- package/dist/web/edit-history-sync.d.ts +158 -0
- package/dist/web/edit-history-sync.d.ts.map +1 -0
- package/dist/web/edit-history-sync.js +445 -0
- package/dist/web/edit-history-sync.js.map +1 -0
- package/dist/web/paste-image-gc.d.ts +6 -0
- package/dist/web/paste-image-gc.d.ts.map +1 -1
- package/dist/web/paste-image-gc.js +6 -0
- package/dist/web/paste-image-gc.js.map +1 -1
- package/dist/web/ports/session-port.d.ts +13 -0
- package/dist/web/ports/session-port.d.ts.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.3093527f.js → app.963b5ba8.js} +18 -18
- package/dist/web/public/app.963b5ba8.js.br +0 -0
- package/dist/web/public/app.963b5ba8.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.affcdde6.js → constants.782170e1.js} +114 -25
- package/dist/web/public/constants.782170e1.js.br +0 -0
- package/dist/web/public/constants.782170e1.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/edit-history-ui.js +630 -0
- package/dist/web/public/edit-history-ui.js.br +0 -0
- package/dist/web/public/edit-history-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/git-status-ui.js.gz +0 -0
- package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/host-wake-ui.js.gz +0 -0
- package/dist/web/public/i18n.33f930d7.js +2 -0
- package/dist/web/public/i18n.33f930d7.js.br +0 -0
- package/dist/web/public/i18n.33f930d7.js.gz +0 -0
- package/dist/web/public/{image-input.941386e7.js → image-input.a5d0f8f3.js} +18 -9
- package/dist/web/public/image-input.a5d0f8f3.js.br +0 -0
- package/dist/web/public/image-input.a5d0f8f3.js.gz +0 -0
- package/dist/web/public/index.html +162 -16
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.4e8b20ac.js → keyboard-accessory.d4ec5957.js} +77 -2
- package/dist/web/public/keyboard-accessory.d4ec5957.js.br +0 -0
- package/dist/web/public/keyboard-accessory.d4ec5957.js.gz +0 -0
- package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
- package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.5ef2ce85.css +1 -0
- package/dist/web/public/mobile.5ef2ce85.css.br +0 -0
- package/dist/web/public/mobile.5ef2ce85.css.gz +0 -0
- package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.4a56ee63.js → panels-ui.b100727d.js} +2 -2
- package/dist/web/public/panels-ui.b100727d.js.br +0 -0
- package/dist/web/public/{panels-ui.4a56ee63.js.gz → panels-ui.b100727d.js.gz} +0 -0
- package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
- package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/{respawn-ui.d7adcf6a.js → respawn-ui.304ba0d4.js} +1 -1
- package/dist/web/public/respawn-ui.304ba0d4.js.br +0 -0
- package/dist/web/public/respawn-ui.304ba0d4.js.gz +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
- package/dist/web/public/security-notices.json +1 -21
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/{session-ui.186f9419.js → session-ui.5ac82f2c.js} +30 -30
- package/dist/web/public/session-ui.5ac82f2c.js.br +0 -0
- package/dist/web/public/session-ui.5ac82f2c.js.gz +0 -0
- package/dist/web/public/settings-ui.d1d4fb99.js +106 -0
- package/dist/web/public/settings-ui.d1d4fb99.js.br +0 -0
- package/dist/web/public/settings-ui.d1d4fb99.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
- package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
- package/dist/web/public/styles.77dc538f.css +1 -0
- package/dist/web/public/styles.77dc538f.css.br +0 -0
- package/dist/web/public/styles.77dc538f.css.gz +0 -0
- package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
- package/dist/web/public/sw.js +2 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.98843069.js +1 -0
- package/dist/web/public/terminal-keycode229-recovery.98843069.js.br +0 -0
- package/dist/web/public/terminal-keycode229-recovery.98843069.js.gz +0 -0
- package/dist/web/public/terminal-split.js.gz +0 -0
- package/dist/web/public/terminal-tile.js +8 -6
- package/dist/web/public/terminal-tile.js.br +0 -0
- package/dist/web/public/terminal-tile.js.gz +0 -0
- package/dist/web/public/{terminal-ui.7e787829.js → terminal-ui.d7c75ce8.js} +3 -3
- package/dist/web/public/terminal-ui.d7c75ce8.js.br +0 -0
- package/dist/web/public/terminal-ui.d7c75ce8.js.gz +0 -0
- package/dist/web/public/tile-grid.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
- package/dist/web/public/vendor/fflate.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/public/whats-new.json +9 -9
- package/dist/web/request-body-parser.d.ts +23 -0
- package/dist/web/request-body-parser.d.ts.map +1 -0
- package/dist/web/request-body-parser.js +57 -0
- package/dist/web/request-body-parser.js.map +1 -0
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +104 -8
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/config-backup-routes.d.ts +5 -1
- package/dist/web/routes/config-backup-routes.d.ts.map +1 -1
- package/dist/web/routes/config-backup-routes.js +4 -1
- package/dist/web/routes/config-backup-routes.js.map +1 -1
- package/dist/web/routes/custom-model-routes.d.ts +7 -2
- package/dist/web/routes/custom-model-routes.d.ts.map +1 -1
- package/dist/web/routes/custom-model-routes.js +51 -6
- package/dist/web/routes/custom-model-routes.js.map +1 -1
- package/dist/web/routes/discord-routes.d.ts +25 -0
- package/dist/web/routes/discord-routes.d.ts.map +1 -0
- package/dist/web/routes/discord-routes.js +206 -0
- package/dist/web/routes/discord-routes.js.map +1 -0
- package/dist/web/routes/edit-history-routes.d.ts +43 -0
- package/dist/web/routes/edit-history-routes.d.ts.map +1 -0
- package/dist/web/routes/edit-history-routes.js +106 -0
- package/dist/web/routes/edit-history-routes.js.map +1 -0
- package/dist/web/routes/file-routes.d.ts +5 -0
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +67 -3
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/index.d.ts +2 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +2 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/ralph-routes.d.ts.map +1 -1
- package/dist/web/routes/ralph-routes.js +45 -2
- package/dist/web/routes/ralph-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts +17 -0
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +92 -37
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +4 -2
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +68 -1
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +77 -1
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/sensitive-path.d.ts.map +1 -1
- package/dist/web/sensitive-path.js +15 -0
- package/dist/web/sensitive-path.js.map +1 -1
- package/dist/web/server.d.ts +43 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +237 -15
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.3093527f.js.br +0 -0
- package/dist/web/public/app.3093527f.js.gz +0 -0
- package/dist/web/public/constants.affcdde6.js.br +0 -0
- package/dist/web/public/constants.affcdde6.js.gz +0 -0
- package/dist/web/public/i18n.70265257.js +0 -2
- package/dist/web/public/i18n.70265257.js.br +0 -0
- package/dist/web/public/i18n.70265257.js.gz +0 -0
- package/dist/web/public/image-input.941386e7.js.br +0 -0
- package/dist/web/public/image-input.941386e7.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.4e8b20ac.js.br +0 -0
- package/dist/web/public/keyboard-accessory.4e8b20ac.js.gz +0 -0
- package/dist/web/public/mobile.1bbd2d67.css +0 -1
- package/dist/web/public/mobile.1bbd2d67.css.br +0 -0
- package/dist/web/public/mobile.1bbd2d67.css.gz +0 -0
- package/dist/web/public/panels-ui.4a56ee63.js.br +0 -0
- package/dist/web/public/respawn-ui.d7adcf6a.js.br +0 -0
- package/dist/web/public/respawn-ui.d7adcf6a.js.gz +0 -0
- package/dist/web/public/session-ui.186f9419.js.br +0 -0
- package/dist/web/public/session-ui.186f9419.js.gz +0 -0
- package/dist/web/public/settings-ui.a191154e.js +0 -106
- package/dist/web/public/settings-ui.a191154e.js.br +0 -0
- package/dist/web/public/settings-ui.a191154e.js.gz +0 -0
- package/dist/web/public/styles.7238eae8.css +0 -1
- package/dist/web/public/styles.7238eae8.css.br +0 -0
- package/dist/web/public/styles.7238eae8.css.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.af09f99a.js +0 -1
- package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.br +0 -0
- package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
- package/dist/web/public/terminal-ui.7e787829.js.br +0 -0
- package/dist/web/public/terminal-ui.7e787829.js.gz +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.48.0",
|
|
3
3
|
"highlights": [
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
4
|
+
"Talk to your agents from Discord (#741)",
|
|
5
|
+
"Edit history panel (#693)",
|
|
6
|
+
"Custom endpoints go further (#752, #755, #757)",
|
|
7
|
+
"Clone into a custom folder, and make a folder from the picker (#744, #745)",
|
|
8
|
+
"Relative links follow your `cd` (#740)",
|
|
9
|
+
"File links keep the whole name (#735)",
|
|
10
|
+
"A failed settings save says so (#736)",
|
|
11
|
+
"Phone keys show a press (#737)"
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"case-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/case-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"case-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/case-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAmF1C,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAgE9C;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAE1E;AAgID,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GACxD,IAAI,CA4kEN"}
|
|
@@ -15,6 +15,8 @@ import { ApiErrorCode, createErrorResponse, getErrorMessage } from '../../types.
|
|
|
15
15
|
import { CreateCaseSchema, CloneCaseSchema, ClonePreflightSchema, LinkCaseSchema, CaseOrderSchema, CaseDefaultGroupSchema, RenameCaseSchema, RemoteCaseLinkSchema, RemoteHostSchema, DockerCaseLinkSchema, DockerCaseAdoptSchema, DockerAdoptPreflightSchema, DockerBrowseSchema, DockerHostSchema, DockerExportSchema, DockerImportSchema, DockerQuickCreateSchema, } from '../schemas.js';
|
|
16
16
|
import { exportDockerCase, importDockerBundle, listDockerExports, exportBundleName } from '../../docker-export.js';
|
|
17
17
|
import { cloneRepository, isGitAvailable, isSafeGitRef, parseGitRepositoryUrl, probeGitRemote, } from '../../git-clone.js';
|
|
18
|
+
import { isPathWithin } from '../../git-worktrees.js';
|
|
19
|
+
import { defaultDockerWorkspaces } from './git-status-routes.js';
|
|
18
20
|
import { generateClaudeMd } from '../../templates/claude-md.js';
|
|
19
21
|
import { prepareNewCasePath } from '../case-path.js';
|
|
20
22
|
import { CaseRenameError, isCaseSessionStarting, moveCaseFolder, moveClaudeHistory, renameInOrder, siblingPath, withCaseLock, } from '../../case-rename.js';
|
|
@@ -54,6 +56,8 @@ const APP_VERSION = (() => {
|
|
|
54
56
|
* `/api/cases/link` is admin-only for).
|
|
55
57
|
*/
|
|
56
58
|
const LOCAL_CLONE_ADMIN_ONLY = 'Cloning from a local path is admin-only in multi-user mode. Use a repository URL instead.';
|
|
59
|
+
/** Refusal text for a clone at or inside a Docker case workspace (git would run its hooks on the host). */
|
|
60
|
+
const DOCKER_CLONE_REFUSAL = 'Cloning is not available into a folder inside a Docker case workspace: a container can write there, and git would run the new repository’s hooks and filters on this machine. Clone outside the workspace and link it, or clone from inside the container.';
|
|
57
61
|
/**
|
|
58
62
|
* Whether a clone or preflight must run with git's credential helpers cleared:
|
|
59
63
|
* a non-admin in multi-user mode. Every user's git runs as the one server
|
|
@@ -519,7 +523,8 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
519
523
|
* request died mid-clone still sees the case appear over SSE when git finishes.
|
|
520
524
|
*
|
|
521
525
|
* Deliberately NOT admin-gated in multi-user mode: unlike `/api/cases/link`,
|
|
522
|
-
* this writes only inside the caller's own `resolveCasesDir
|
|
526
|
+
* this writes only inside the caller's own `resolveCasesDir`, EXCEPT with a `path` (a custom folder,
|
|
527
|
+
* registered as a linked case in the shared registry), which is admin-only like Link Existing. Two things would
|
|
523
528
|
* otherwise read through that boundary: a `local`-transport source (refused for
|
|
524
529
|
* non-admins) and the server account's git credential helpers, which every user
|
|
525
530
|
* shares (cleared for non-admins, see `cloneWithoutCredentialHelpers`).
|
|
@@ -531,7 +536,7 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
531
536
|
* user's machine the moment a session starts in the case.
|
|
532
537
|
*/
|
|
533
538
|
app.post('/api/cases/clone', async (req, reply) => {
|
|
534
|
-
const { name, repository, ref, shallow, description } = parseBody(CloneCaseSchema, req.body);
|
|
539
|
+
const { name, repository, ref, shallow, description, path: customPath } = parseBody(CloneCaseSchema, req.body);
|
|
535
540
|
const user = getAuthUser(req);
|
|
536
541
|
const parsed = parseGitRepositoryUrl(repository);
|
|
537
542
|
if (!parsed.cloneable)
|
|
@@ -546,8 +551,52 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
546
551
|
if (!isGitAvailable()) {
|
|
547
552
|
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, 'git is not installed on this machine (or not on the server’s PATH).');
|
|
548
553
|
}
|
|
554
|
+
// A custom folder writes outside the cases directory and registers the path in the shared,
|
|
555
|
+
// ownerless linked-cases registry, so it carries the same bar as POST /api/cases/link.
|
|
556
|
+
if (customPath !== undefined) {
|
|
557
|
+
const denied = adminOnly(req, reply);
|
|
558
|
+
if (denied)
|
|
559
|
+
return denied;
|
|
560
|
+
}
|
|
549
561
|
const casesDir = resolveCasesDir(user);
|
|
550
|
-
|
|
562
|
+
let casePath;
|
|
563
|
+
let intoEmptyDir = false;
|
|
564
|
+
if (customPath !== undefined) {
|
|
565
|
+
const prepared = await prepareNewCasePath(customPath, {
|
|
566
|
+
home: homedir(),
|
|
567
|
+
dataDir: getDataDir(),
|
|
568
|
+
casesDirs: [...new Set([casesDir, resolveCasesDir()])],
|
|
569
|
+
casesDirAdvice: 'clone there without a custom folder',
|
|
570
|
+
});
|
|
571
|
+
if (!prepared.ok) {
|
|
572
|
+
reply.code(prepared.code === 'UNREACHABLE'
|
|
573
|
+
? 422
|
|
574
|
+
: prepared.code === 'NOT_FOUND'
|
|
575
|
+
? 404
|
|
576
|
+
: prepared.code === 'EXISTS'
|
|
577
|
+
? 409
|
|
578
|
+
: 400);
|
|
579
|
+
return createErrorResponse(prepared.code === 'UNREACHABLE'
|
|
580
|
+
? ApiErrorCode.OPERATION_FAILED
|
|
581
|
+
: prepared.code === 'NOT_FOUND'
|
|
582
|
+
? ApiErrorCode.NOT_FOUND
|
|
583
|
+
: prepared.code === 'EXISTS'
|
|
584
|
+
? ApiErrorCode.ALREADY_EXISTS
|
|
585
|
+
: ApiErrorCode.INVALID_INPUT, prepared.code === 'EXISTS'
|
|
586
|
+
? `${customPath} already has files in it. Pick an empty folder, or a folder that does not exist yet.`
|
|
587
|
+
: prepared.reason);
|
|
588
|
+
}
|
|
589
|
+
intoEmptyDir = prepared.existedEmpty;
|
|
590
|
+
casePath = prepared.path;
|
|
591
|
+
// The same folder listed as a local case in the cases dir would make deleting it remove files.
|
|
592
|
+
if (existsSync(join(casesDir, name))) {
|
|
593
|
+
reply.code(409);
|
|
594
|
+
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'A case with this name already exists in codeman-cases.');
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
casePath = validatePathWithinBase(name, casesDir);
|
|
599
|
+
}
|
|
551
600
|
if (!casePath)
|
|
552
601
|
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Invalid case path');
|
|
553
602
|
// Reject a duplicate name across EVERY case kind before invoking git, so a
|
|
@@ -556,22 +605,52 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
556
605
|
const linkedCases = await readLinkedCases();
|
|
557
606
|
const dockerCases = await readDockerCases(CODEMAN_CONFIG_DIR);
|
|
558
607
|
const remoteCases = await readRemoteCases(CODEMAN_CONFIG_DIR);
|
|
559
|
-
|
|
608
|
+
const linkedAs = Object.entries(linkedCases).find(([, p]) => p === casePath)?.[0];
|
|
609
|
+
if (linkedAs && customPath !== undefined) {
|
|
610
|
+
reply.code(409);
|
|
611
|
+
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, `That folder is already the case "${linkedAs}"`);
|
|
612
|
+
}
|
|
613
|
+
if (
|
|
614
|
+
// A custom folder was judged by prepareNewCasePath (an existing empty one is fine).
|
|
615
|
+
(customPath === undefined && existsSync(casePath)) ||
|
|
560
616
|
linkedCases[name] ||
|
|
561
617
|
dockerCases.some((item) => item.name === name) ||
|
|
562
618
|
remoteCases.some((item) => item.name === name)) {
|
|
563
619
|
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'Case already exists');
|
|
564
620
|
}
|
|
565
|
-
// git creates the leaf, not necessarily the case space above it
|
|
621
|
+
// git creates the leaf, not necessarily the case space above it (a custom folder's parent
|
|
622
|
+
// already exists: prepareNewCasePath refused a missing one).
|
|
623
|
+
if (customPath === undefined) {
|
|
624
|
+
try {
|
|
625
|
+
mkdirSync(casesDir, { recursive: true });
|
|
626
|
+
}
|
|
627
|
+
catch (err) {
|
|
628
|
+
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// git runs the new repository's hooks and filters on THIS machine once the clone finishes, and a container
|
|
632
|
+
// with the workspace bind-mounted can write both while it fetches. Like git status, fetch and worktrees,
|
|
633
|
+
// never clone at or inside a Docker case workspace. Fails closed when the Docker case list cannot be read.
|
|
566
634
|
try {
|
|
567
|
-
|
|
635
|
+
// Probed first (#516): a case's workspace can sit on a mount that stopped answering, and an unbounded
|
|
636
|
+
// realpath there would hold this request and a threadpool worker. Such a root is judged as spelled.
|
|
637
|
+
const roots = await Promise.all((await defaultDockerWorkspaces()).map(async (root) => (await probePathKind(root, { pastCap: true })) === 'unknown'
|
|
638
|
+
? resolve(root)
|
|
639
|
+
: fs.realpath(root).catch(() => resolve(root))));
|
|
640
|
+
// A custom folder is already symlink-resolved; the default one is judged through its real cases dir.
|
|
641
|
+
const dest = customPath !== undefined ? casePath : join(await fs.realpath(casesDir).catch(() => casesDir), name);
|
|
642
|
+
if (roots.some((root) => isPathWithin(dest, root))) {
|
|
643
|
+
return createErrorResponse(ApiErrorCode.INVALID_INPUT, DOCKER_CLONE_REFUSAL);
|
|
644
|
+
}
|
|
568
645
|
}
|
|
569
|
-
catch
|
|
570
|
-
|
|
646
|
+
catch {
|
|
647
|
+
reply.code(503);
|
|
648
|
+
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, 'Could not read the Docker case list, so Codeman cannot tell whether that folder is a Docker case workspace. Try again.');
|
|
571
649
|
}
|
|
572
650
|
const clone = await cloneRepository({
|
|
573
651
|
repository: parsed.repository,
|
|
574
652
|
destination: casePath,
|
|
653
|
+
...(intoEmptyDir ? { intoEmptyDir: true } : {}),
|
|
575
654
|
withoutCredentialHelpers: cloneWithoutCredentialHelpers(req),
|
|
576
655
|
...(ref ? { ref } : {}),
|
|
577
656
|
...(shallow ? { shallow: true } : {}),
|
|
@@ -628,6 +707,23 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
628
707
|
// problem, rather than deleting a tree the user just waited for.
|
|
629
708
|
warnings.push(`Case scaffolding was incomplete: ${getErrorMessage(err)}`);
|
|
630
709
|
}
|
|
710
|
+
if (customPath !== undefined) {
|
|
711
|
+
// Re-read right before writing so a case linked while git ran is not dropped. Like
|
|
712
|
+
// POST /api/cases/link the write is not serialized, so this only narrows the window.
|
|
713
|
+
try {
|
|
714
|
+
const fresh = await readLinkedCases();
|
|
715
|
+
if (fresh[name])
|
|
716
|
+
throw Object.assign(new Error(`Case "${name}" was linked while the clone ran`), { conflict: true });
|
|
717
|
+
fresh[name] = casePath;
|
|
718
|
+
await fs.writeFile(LINKED_CASES_FILE, JSON.stringify(fresh, null, 2));
|
|
719
|
+
}
|
|
720
|
+
catch (err) {
|
|
721
|
+
// The clone itself succeeded: keep the folder (the user waited for it) and say where it is.
|
|
722
|
+
const conflict = err.conflict;
|
|
723
|
+
reply.code(conflict ? 409 : 500);
|
|
724
|
+
return createErrorResponse(conflict ? ApiErrorCode.ALREADY_EXISTS : ApiErrorCode.OPERATION_FAILED, `Cloned into ${casePath} but could not register the case (${getErrorMessage(err)}). Use Link Existing to add it.`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
631
727
|
ctx.broadcast(SseEvent.CaseCreated, { name, path: casePath });
|
|
632
728
|
return {
|
|
633
729
|
success: true,
|