aicodeman 1.45.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.
- package/README.md +26 -6
- package/dist/agent-codex-identity.d.ts +26 -0
- package/dist/agent-codex-identity.d.ts.map +1 -0
- package/dist/agent-codex-identity.js +253 -0
- package/dist/agent-codex-identity.js.map +1 -0
- package/dist/agent-inspection.d.ts +32 -0
- package/dist/agent-inspection.d.ts.map +1 -0
- package/dist/agent-inspection.js +196 -0
- package/dist/agent-inspection.js.map +1 -0
- package/dist/agent-staging.d.ts +130 -0
- package/dist/agent-staging.d.ts.map +1 -0
- package/dist/agent-staging.js +312 -0
- package/dist/agent-staging.js.map +1 -0
- package/dist/cli-agent.d.ts +31 -2
- package/dist/cli-agent.d.ts.map +1 -1
- package/dist/cli-agent.js +279 -34
- package/dist/cli-agent.js.map +1 -1
- package/dist/cli-history.d.ts +20 -0
- package/dist/cli-history.d.ts.map +1 -0
- package/dist/cli-history.js +61 -0
- package/dist/cli-history.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -1
- package/dist/cli.js.map +1 -1
- package/dist/codex-rollout-format.d.ts +66 -0
- package/dist/codex-rollout-format.d.ts.map +1 -0
- package/dist/codex-rollout-format.js +111 -0
- package/dist/codex-rollout-format.js.map +1 -0
- package/dist/codex-transcript.d.ts +3 -0
- package/dist/codex-transcript.d.ts.map +1 -1
- package/dist/codex-transcript.js +13 -41
- package/dist/codex-transcript.js.map +1 -1
- package/dist/config/cli-registry/schema.d.ts +12 -7
- package/dist/config/cli-registry/schema.d.ts.map +1 -1
- package/dist/config/cli-registry/schema.js +26 -0
- 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 +40 -1
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +57 -0
- package/dist/config/cli-registry/types.d.ts.map +1 -1
- package/dist/config/server-timing.d.ts +6 -0
- package/dist/config/server-timing.d.ts.map +1 -1
- package/dist/config/server-timing.js +9 -0
- package/dist/config/server-timing.js.map +1 -1
- package/dist/daemon-control.d.ts +1 -0
- package/dist/daemon-control.d.ts.map +1 -1
- package/dist/daemon-control.js +2 -0
- package/dist/daemon-control.js.map +1 -1
- package/dist/deepseek-transcript.d.ts +9 -7
- package/dist/deepseek-transcript.d.ts.map +1 -1
- package/dist/deepseek-transcript.js +9 -7
- package/dist/deepseek-transcript.js.map +1 -1
- package/dist/docker-hosts.d.ts +8 -0
- package/dist/docker-hosts.d.ts.map +1 -1
- package/dist/docker-hosts.js +29 -0
- package/dist/docker-hosts.js.map +1 -1
- package/dist/git-fetch-scheduler.d.ts +124 -0
- package/dist/git-fetch-scheduler.d.ts.map +1 -0
- package/dist/git-fetch-scheduler.js +249 -0
- package/dist/git-fetch-scheduler.js.map +1 -0
- package/dist/git-fetch.d.ts +241 -0
- package/dist/git-fetch.d.ts.map +1 -0
- package/dist/git-fetch.js +392 -0
- package/dist/git-fetch.js.map +1 -0
- package/dist/git-workspace-status.d.ts +36 -1
- package/dist/git-workspace-status.d.ts.map +1 -1
- package/dist/git-workspace-status.js +42 -5
- package/dist/git-workspace-status.js.map +1 -1
- package/dist/hooks-config.d.ts +14 -2
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +39 -3
- package/dist/hooks-config.js.map +1 -1
- package/dist/local-history.d.ts +71 -0
- package/dist/local-history.d.ts.map +1 -0
- package/dist/local-history.js +750 -0
- package/dist/local-history.js.map +1 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +2 -2
- package/dist/mcp-server.js.map +1 -1
- package/dist/mux-interface.d.ts +5 -2
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/prompt-cache-keepwarm.d.ts.map +1 -1
- package/dist/prompt-cache-keepwarm.js +5 -3
- package/dist/prompt-cache-keepwarm.js.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.d.ts +113 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +341 -13
- package/dist/session.js.map +1 -1
- package/dist/terminal-input-classifier.d.ts +8 -0
- package/dist/terminal-input-classifier.d.ts.map +1 -0
- package/dist/terminal-input-classifier.js +60 -0
- package/dist/terminal-input-classifier.js.map +1 -0
- package/dist/tmux-manager.d.ts +23 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +54 -6
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/session.d.ts +15 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/network-auth-policy.d.ts +13 -3
- package/dist/web/network-auth-policy.d.ts.map +1 -1
- package/dist/web/network-auth-policy.js +15 -3
- package/dist/web/network-auth-policy.js.map +1 -1
- package/dist/web/ports/infra-port.d.ts +2 -0
- package/dist/web/ports/infra-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.fe8a6b4c.js +43 -0
- package/dist/web/public/app.fe8a6b4c.js.br +0 -0
- package/dist/web/public/app.fe8a6b4c.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.0b3cdde9.js → constants.affcdde6.js} +611 -12
- package/dist/web/public/constants.affcdde6.js.br +0 -0
- package/dist/web/public/constants.affcdde6.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/git-status-ui.js +88 -5
- package/dist/web/public/git-status-ui.js.br +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.a618ef4e.js +2 -0
- package/dist/web/public/i18n.a618ef4e.js.br +0 -0
- package/dist/web/public/i18n.a618ef4e.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 +110 -19
- 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.ff64fafc.js} +5 -1
- package/dist/web/public/keyboard-accessory.ff64fafc.js.br +0 -0
- package/dist/web/public/keyboard-accessory.ff64fafc.js.gz +0 -0
- package/dist/web/public/{mobile-handlers.1c0cbcd8.js → mobile-handlers.f7c3f692.js} +22 -0
- package/dist/web/public/mobile-handlers.f7c3f692.js.br +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.1bbd2d67.css +1 -0
- package/dist/web/public/mobile.1bbd2d67.css.br +0 -0
- package/dist/web/public/mobile.1bbd2d67.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.78c3defb.js → panels-ui.4a56ee63.js} +1 -1
- package/dist/web/public/panels-ui.4a56ee63.js.br +0 -0
- package/dist/web/public/panels-ui.4a56ee63.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.gz +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/{session-ui.ea6b1994.js → session-ui.426fb671.js} +8 -8
- package/dist/web/public/session-ui.426fb671.js.br +0 -0
- package/dist/web/public/session-ui.426fb671.js.gz +0 -0
- package/dist/web/public/{settings-ui.085dd202.js → settings-ui.a191154e.js} +11 -11
- 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/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.4f0c67a0.css +1 -0
- package/dist/web/public/styles.4f0c67a0.css.br +0 -0
- package/dist/web/public/styles.4f0c67a0.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.7157e0bd.js → tab-rail-resize.b5cdceaf.js} +1 -1
- package/dist/web/public/tab-rail-resize.b5cdceaf.js.br +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 +10 -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 +4 -0
- package/dist/web/public/terminal-ui.7e787829.js.br +0 -0
- package/dist/web/public/terminal-ui.7e787829.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.ae5e3d5d.js → xterm-predictive-echo.276b11d9.js} +1 -1
- package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.br +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 +5 -0
- package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.br +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 +8 -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/agent-session-routes.d.ts +20 -0
- package/dist/web/routes/agent-session-routes.d.ts.map +1 -0
- package/dist/web/routes/agent-session-routes.js +160 -0
- package/dist/web/routes/agent-session-routes.js.map +1 -0
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +32 -26
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/config-backup-routes.js +2 -2
- package/dist/web/routes/config-backup-routes.js.map +1 -1
- 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 +5 -1
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/git-status-routes.d.ts +21 -7
- package/dist/web/routes/git-status-routes.d.ts.map +1 -1
- package/dist/web/routes/git-status-routes.js +78 -12
- package/dist/web/routes/git-status-routes.js.map +1 -1
- package/dist/web/routes/index.d.ts +1 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +1 -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 +52 -18
- package/dist/web/routes/ralph-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +101 -28
- 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 +87 -59
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/routes/ws-routes.d.ts +5 -0
- package/dist/web/routes/ws-routes.d.ts.map +1 -1
- package/dist/web/routes/ws-routes.js +84 -47
- package/dist/web/routes/ws-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +30 -7
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +40 -1
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +5 -2
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +102 -18
- package/dist/web/server.js.map +1 -1
- package/dist/web/settings-writer.d.ts +47 -0
- package/dist/web/settings-writer.d.ts.map +1 -0
- package/dist/web/settings-writer.js +110 -0
- package/dist/web/settings-writer.js.map +1 -0
- package/package.json +1 -1
- package/skills/codeman/SKILL.md +25 -6
- package/skills/codeman/reference/history.md +84 -0
- package/skills/codeman/reference/inspection-and-staging.md +152 -0
- package/skills/codeman/reference/messaging.md +1 -1
- package/skills/codeman/reference/verbs.md +18 -3
- package/dist/web/public/app.42d0abae.js +0 -43
- package/dist/web/public/app.42d0abae.js.br +0 -0
- package/dist/web/public/app.42d0abae.js.gz +0 -0
- package/dist/web/public/constants.0b3cdde9.js.br +0 -0
- package/dist/web/public/constants.0b3cdde9.js.gz +0 -0
- package/dist/web/public/i18n.1e68220d.js +0 -2
- package/dist/web/public/i18n.1e68220d.js.br +0 -0
- package/dist/web/public/i18n.1e68220d.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-handlers.1c0cbcd8.js.br +0 -0
- package/dist/web/public/mobile-handlers.1c0cbcd8.js.gz +0 -0
- package/dist/web/public/mobile.12fb18de.css +0 -1
- package/dist/web/public/mobile.12fb18de.css.br +0 -0
- package/dist/web/public/mobile.12fb18de.css.gz +0 -0
- package/dist/web/public/panels-ui.78c3defb.js.br +0 -0
- package/dist/web/public/panels-ui.78c3defb.js.gz +0 -0
- package/dist/web/public/session-ui.ea6b1994.js.br +0 -0
- package/dist/web/public/session-ui.ea6b1994.js.gz +0 -0
- package/dist/web/public/settings-ui.085dd202.js.br +0 -0
- package/dist/web/public/settings-ui.085dd202.js.gz +0 -0
- package/dist/web/public/styles.3b59b41d.css +0 -1
- package/dist/web/public/styles.3b59b41d.css.br +0 -0
- package/dist/web/public/styles.3b59b41d.css.gz +0 -0
- package/dist/web/public/tab-rail-resize.7157e0bd.js.br +0 -0
- package/dist/web/public/tab-rail-resize.7157e0bd.js.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.fb2a27f5.js +0 -2
- package/dist/web/public/terminal-ui.fb2a27f5.js.br +0 -0
- package/dist/web/public/terminal-ui.fb2a27f5.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.br +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js +0 -4
- package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.br +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.0b69623c.js.gz +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.47.0",
|
|
3
3
|
"highlights": [
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"A
|
|
10
|
-
"
|
|
11
|
-
"The wheel no longer moves a Codex menu's selection (#698)"
|
|
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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Agent-facing bounded inspection and guarded stage/submit endpoints.
|
|
3
|
+
* Ownership is checked before any disk or pane read. Native evidence comes only
|
|
4
|
+
* from strict conversation attribution; all other modes get labeled snapshots.
|
|
5
|
+
*
|
|
6
|
+
* Experimental and opt-in: all three routes answer `403 FORBIDDEN`, naming the
|
|
7
|
+
* setting, until the synced `agentCoordinationEnabled` is on (default OFF), the
|
|
8
|
+
* same shape as `cliManagementEnabled` and `mcpSyncEnabled`. The gate runs first,
|
|
9
|
+
* before session lookup, so a disabled install reveals nothing about its sessions.
|
|
10
|
+
*/
|
|
11
|
+
import type { FastifyInstance } from 'fastify';
|
|
12
|
+
import type { SessionPort, InfraPort } from '../ports/index.js';
|
|
13
|
+
/** Default OFF, same shape as `readMcpSyncEnabled`: read fresh so a toggle applies at once. */
|
|
14
|
+
export declare function readAgentCoordinationEnabled(): Promise<boolean>;
|
|
15
|
+
/** The refusal every route sends while the setting is off. Exported for the tests. */
|
|
16
|
+
export declare const AGENT_COORDINATION_DISABLED_MESSAGE = "Experimental agent coordination (inspect, stage, submit) is disabled. Turn on \"Experimental agent coordination\" in Settings (agentCoordinationEnabled) and save first.";
|
|
17
|
+
/** A Node error code (`EACCES`, `EIO`, ...) or the error's class name; never its message. */
|
|
18
|
+
export declare function errorCode(error: unknown): string;
|
|
19
|
+
export declare function registerAgentSessionRoutes(app: FastifyInstance, ctx: SessionPort & InfraPort): void;
|
|
20
|
+
//# sourceMappingURL=agent-session-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-session-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/agent-session-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAehE,+FAA+F;AAC/F,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,OAAO,CAAC,CAGrE;AAED,sFAAsF;AACtF,eAAO,MAAM,mCAAmC,6KAC0H,CAAC;AAO3K,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIhD;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CA+InG"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { getCli } from '../../config/cli-registry/registry.js';
|
|
2
|
+
import { findExactCodexConversation, resolveEffectiveCodexHome } from '../../agent-codex-identity.js';
|
|
3
|
+
import { InspectionReader, InspectionConflict, boundedUtf8Tail } from '../../agent-inspection.js';
|
|
4
|
+
import { GuardedStageStore, GuardedInputConflict, guardedComposerChrome, MAY_BE_TYPED, } from '../../agent-staging.js';
|
|
5
|
+
import { ApiErrorCode, createErrorResponse } from '../../types.js';
|
|
6
|
+
import { findSessionOrFail, getAuthUser, parseBody, readJsonConfig, SETTINGS_PATH } from '../route-helpers.js';
|
|
7
|
+
import { AgentInspectQuerySchema, AgentStageSchema, AgentSubmitSchema } from '../schemas.js';
|
|
8
|
+
/** Default OFF, same shape as `readMcpSyncEnabled`: read fresh so a toggle applies at once. */
|
|
9
|
+
export async function readAgentCoordinationEnabled() {
|
|
10
|
+
const settings = await readJsonConfig(SETTINGS_PATH, 'settings.json', {});
|
|
11
|
+
return settings.agentCoordinationEnabled === true;
|
|
12
|
+
}
|
|
13
|
+
/** The refusal every route sends while the setting is off. Exported for the tests. */
|
|
14
|
+
export const AGENT_COORDINATION_DISABLED_MESSAGE = 'Experimental agent coordination (inspect, stage, submit) is disabled. Turn on "Experimental agent coordination" in Settings (agentCoordinationEnabled) and save first.';
|
|
15
|
+
async function gate() {
|
|
16
|
+
if (await readAgentCoordinationEnabled())
|
|
17
|
+
return null;
|
|
18
|
+
return createErrorResponse(ApiErrorCode.FORBIDDEN, AGENT_COORDINATION_DISABLED_MESSAGE);
|
|
19
|
+
}
|
|
20
|
+
/** A Node error code (`EACCES`, `EIO`, ...) or the error's class name; never its message. */
|
|
21
|
+
export function errorCode(error) {
|
|
22
|
+
const code = error?.code;
|
|
23
|
+
if (typeof code === 'string' && /^[A-Z0-9_]{1,40}$/.test(code))
|
|
24
|
+
return code;
|
|
25
|
+
return error instanceof Error ? error.constructor.name : 'unknown';
|
|
26
|
+
}
|
|
27
|
+
export function registerAgentSessionRoutes(app, ctx) {
|
|
28
|
+
const reader = new InspectionReader();
|
|
29
|
+
const stages = new GuardedStageStore();
|
|
30
|
+
app.addHook('onClose', async () => {
|
|
31
|
+
reader.clear();
|
|
32
|
+
stages.clear();
|
|
33
|
+
});
|
|
34
|
+
const generation = (session) => session.inspectionGeneration;
|
|
35
|
+
const target = (session) => ({
|
|
36
|
+
id: session.id,
|
|
37
|
+
chrome: () => guardedComposerChrome(getCli(session.mode)),
|
|
38
|
+
snapshot: () => {
|
|
39
|
+
if (ctx.sessions.get(session.id) !== session)
|
|
40
|
+
return null;
|
|
41
|
+
const snapshot = session.guardedInputSnapshot?.();
|
|
42
|
+
return snapshot ?? null;
|
|
43
|
+
},
|
|
44
|
+
write: (text) => session.stageGuardedInput(text),
|
|
45
|
+
submit: (expected) => session.submitGuardedInput(expected),
|
|
46
|
+
});
|
|
47
|
+
app.get('/api/sessions/:id/inspect', async (req, reply) => {
|
|
48
|
+
const refused = await gate();
|
|
49
|
+
if (refused)
|
|
50
|
+
return reply.code(403).send(refused);
|
|
51
|
+
const { id } = req.params;
|
|
52
|
+
const session = findSessionOrFail(ctx, id, req);
|
|
53
|
+
const { maxBytes, since } = parseBody(AgentInspectQuerySchema, req.query);
|
|
54
|
+
const initialGeneration = generation(session);
|
|
55
|
+
const identity = { sessionId: session.id, mode: session.mode };
|
|
56
|
+
const activity = { status: session.status, lastSubmitAt: session.lastSubmitAt || null, observedAt: Date.now() };
|
|
57
|
+
try {
|
|
58
|
+
if (getCli(session.mode)?.capabilities.transcript === 'codex-rollout' && !session.remote && !session.docker) {
|
|
59
|
+
const conversation = await findExactCodexConversation({
|
|
60
|
+
codexHome: resolveEffectiveCodexHome(session.getCodexHomeOverride(), session.workingDir),
|
|
61
|
+
sessionId: session.id,
|
|
62
|
+
workingDir: session.workingDir,
|
|
63
|
+
resumeSessionId: session.codexConfig?.resumeSessionId,
|
|
64
|
+
}, { maxEntries: 8192, maxBytes: 1048576 });
|
|
65
|
+
if (generation(session) !== initialGeneration || ctx.sessions.get(id) !== session)
|
|
66
|
+
throw new InspectionConflict('Session changed during inspection');
|
|
67
|
+
if (conversation) {
|
|
68
|
+
const result = await reader.read(conversation.path, {
|
|
69
|
+
owner: getAuthUser(req).username,
|
|
70
|
+
sessionId: id,
|
|
71
|
+
generation: initialGeneration,
|
|
72
|
+
conversationId: conversation.id,
|
|
73
|
+
}, maxBytes, since);
|
|
74
|
+
if (generation(session) !== initialGeneration || ctx.sessions.get(id) !== session)
|
|
75
|
+
throw new InspectionConflict('Session changed during inspection');
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
data: {
|
|
79
|
+
source: 'codex-jsonl',
|
|
80
|
+
identity: { ...identity, conversationId: conversation.id },
|
|
81
|
+
activity,
|
|
82
|
+
incremental: true,
|
|
83
|
+
maxBytes,
|
|
84
|
+
...result,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (since)
|
|
90
|
+
throw new InspectionConflict('Attributed transcript unavailable; this cursor cannot resume a pane snapshot');
|
|
91
|
+
const frame = session.muxName ? ctx.mux.capturePaneText?.(session.muxName) : null;
|
|
92
|
+
const evidence = frame ?? session.terminalBuffer;
|
|
93
|
+
return {
|
|
94
|
+
success: true,
|
|
95
|
+
data: {
|
|
96
|
+
source: 'pane-snapshot',
|
|
97
|
+
identity,
|
|
98
|
+
activity,
|
|
99
|
+
incremental: false,
|
|
100
|
+
cursor: null,
|
|
101
|
+
maxBytes,
|
|
102
|
+
evidence: boundedUtf8Tail(evidence, maxBytes),
|
|
103
|
+
truncated: evidence.length > maxBytes || Buffer.byteLength(evidence) > maxBytes,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (error instanceof InspectionConflict)
|
|
109
|
+
return reply.code(409).send(createErrorResponse(ApiErrorCode.CONFLICT, error.message));
|
|
110
|
+
// Anything else (an unreadable rollout, an IO error) is not a retryable cursor
|
|
111
|
+
// conflict. Log the session and the error code only: the message can carry the
|
|
112
|
+
// native transcript path, which must never reach the log or the response.
|
|
113
|
+
console.warn(`[agent-inspect] session ${session.id}: inspection failed (${errorCode(error)})`);
|
|
114
|
+
return reply.code(422).send(createErrorResponse(ApiErrorCode.OPERATION_FAILED, 'Inspection source unavailable'));
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
app.post('/api/sessions/:id/stage', async (req, reply) => {
|
|
118
|
+
const refused = await gate();
|
|
119
|
+
if (refused)
|
|
120
|
+
return reply.code(403).send(refused);
|
|
121
|
+
const session = findSessionOrFail(ctx, req.params.id, req);
|
|
122
|
+
const { message } = parseBody(AgentStageSchema, req.body);
|
|
123
|
+
try {
|
|
124
|
+
const receipt = await stages.stage(target(session), getAuthUser(req).username, message);
|
|
125
|
+
return { success: true, data: receipt };
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (!(error instanceof GuardedInputConflict))
|
|
129
|
+
console.warn(`[agent-stage] session ${session.id}: delivery failed (${errorCode(error)})`);
|
|
130
|
+
return reply
|
|
131
|
+
.code(409)
|
|
132
|
+
.send(createErrorResponse(ApiErrorCode.CONFLICT, error instanceof GuardedInputConflict
|
|
133
|
+
? error.message
|
|
134
|
+
: `Stage delivery failed or ambiguous; ${MAY_BE_TYPED}`));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
app.post('/api/sessions/:id/submit', async (req, reply) => {
|
|
138
|
+
const refused = await gate();
|
|
139
|
+
if (refused)
|
|
140
|
+
return reply.code(403).send(refused);
|
|
141
|
+
const session = findSessionOrFail(ctx, req.params.id, req);
|
|
142
|
+
const { stageId, confirmSha } = parseBody(AgentSubmitSchema, req.body);
|
|
143
|
+
try {
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
data: await stages.submit(target(session), getAuthUser(req).username, stageId, confirmSha),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (!(error instanceof GuardedInputConflict))
|
|
151
|
+
console.warn(`[agent-submit] session ${session.id}: delivery failed (${errorCode(error)})`);
|
|
152
|
+
return reply
|
|
153
|
+
.code(409)
|
|
154
|
+
.send(createErrorResponse(ApiErrorCode.CONFLICT, error instanceof GuardedInputConflict
|
|
155
|
+
? error.message
|
|
156
|
+
: 'Submit delivery failed or ambiguous; receipt consumed, inspect the pane'));
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=agent-session-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-session-routes.js","sourceRoot":"","sources":["../../../src/web/routes/agent-session-routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;AAiF1C,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA4D9C;;;;;;;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,CA+9DN"}
|
|
@@ -22,6 +22,7 @@ import { boundedPathExists, describeUnknownPath, probePath, probePathKind } from
|
|
|
22
22
|
import { readAgentCaseMarker } from '../../agent-case-marker.js';
|
|
23
23
|
import { settingsWriteBlocker, writeHooksConfig } from '../../hooks-config.js';
|
|
24
24
|
import { canAccessOwned, getAuthUser, isAdmin, isWorkingDirAllowed, ownerFor, requireAdmin, resolveCasesDir, SETTINGS_PATH, validatePathWithinBase, parseBody, readJsonConfig, } from '../route-helpers.js';
|
|
25
|
+
import { assertSettingsWritable, mutateSettingsFile, SettingsWriteRefusedError, settingsWriteErrorResponse, } from '../settings-writer.js';
|
|
25
26
|
import { isMultiUserMode } from '../../config/multiuser.js';
|
|
26
27
|
import { SseEvent } from '../sse-events.js';
|
|
27
28
|
import { ownerLayoutKey } from '../../tab-layout-persistence.js';
|
|
@@ -1529,6 +1530,19 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
1529
1530
|
if (open.some((s) => !canAccessOwned(user, s.owner))) {
|
|
1530
1531
|
return fail(403, ApiErrorCode.FORBIDDEN, "Another user's session has this case open");
|
|
1531
1532
|
}
|
|
1533
|
+
// The admin's rename writes settings.json only after the sessions are ended and the folder moved, and
|
|
1534
|
+
// the writer refuses a file that is not a JSON object: check it by the writer's own rule now, so no
|
|
1535
|
+
// session is ended for a rename that cannot complete. Not between the starting check and the lock below.
|
|
1536
|
+
if (isAdmin(req)) {
|
|
1537
|
+
try {
|
|
1538
|
+
await assertSettingsWritable();
|
|
1539
|
+
}
|
|
1540
|
+
catch (err) {
|
|
1541
|
+
if (err instanceof SettingsWriteRefusedError)
|
|
1542
|
+
return fail(409, ApiErrorCode.CONFLICT, err.message);
|
|
1543
|
+
throw err;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1532
1546
|
// A session being created in the folder is not in ctx.sessions yet, so it could be neither listed nor
|
|
1533
1547
|
// stopped: it would start in the old path after the move. Checked in the same tick that takes the lock
|
|
1534
1548
|
// below (nothing is awaited in between), and the Session constructor refuses the folder from then on.
|
|
@@ -1551,20 +1565,24 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
1551
1565
|
if (moveFolder)
|
|
1552
1566
|
await moveCaseFolder(oldPath, newPath);
|
|
1553
1567
|
const originalLinks = isLinked ? await readLinkedCases() : undefined;
|
|
1554
|
-
// The settings.json
|
|
1555
|
-
//
|
|
1556
|
-
let
|
|
1568
|
+
// The settings.json values this rename replaced, set only once it wrote them. A failure puts back
|
|
1569
|
+
// these keys alone, so a settings save that landed in between is kept.
|
|
1570
|
+
let settingsUndo;
|
|
1557
1571
|
try {
|
|
1558
1572
|
// settings.json is server-wide and caseOrder/lastUsedCase name the admin's cases, so only the
|
|
1559
1573
|
// admin's rename touches it (a non-admin's own rename must not rewrite someone else's list).
|
|
1560
1574
|
// Written before linked-cases.json so a failure here leaves the registry untouched.
|
|
1561
1575
|
if (isAdmin(req)) {
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1576
|
+
const { before, after } = await mutateSettingsFile((existing) => {
|
|
1577
|
+
const order = renameInOrder(existing.caseOrder, name, newName);
|
|
1578
|
+
const last = existing.lastUsedCase === name ? newName : undefined;
|
|
1579
|
+
if (!order && !last)
|
|
1580
|
+
return undefined;
|
|
1581
|
+
return { ...existing, ...(order ? { caseOrder: order } : {}), ...(last ? { lastUsedCase: last } : {}) };
|
|
1582
|
+
});
|
|
1583
|
+
if (after) {
|
|
1584
|
+
const keys = ['caseOrder', 'lastUsedCase'].filter((k) => after[k] !== before[k]);
|
|
1585
|
+
settingsUndo = Object.fromEntries(keys.map((k) => [k, before[k]]));
|
|
1568
1586
|
}
|
|
1569
1587
|
}
|
|
1570
1588
|
if (originalLinks) {
|
|
@@ -1583,9 +1601,9 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
1583
1601
|
catch (err) {
|
|
1584
1602
|
// The folder moved but the registries did not follow: put settings.json and the folder back
|
|
1585
1603
|
// rather than leave a case whose name and folder disagree.
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1604
|
+
const undo = settingsUndo;
|
|
1605
|
+
if (undo)
|
|
1606
|
+
await mutateSettingsFile((current) => ({ ...current, ...undo })).catch(() => { });
|
|
1589
1607
|
if (moveFolder) {
|
|
1590
1608
|
const movedBack = await fs.rename(newPath, oldPath).then(() => true, () => false);
|
|
1591
1609
|
if (!movedBack) {
|
|
@@ -1638,24 +1656,12 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
1638
1656
|
return;
|
|
1639
1657
|
const { order } = parseBody(CaseOrderSchema, req.body, 'Invalid order data');
|
|
1640
1658
|
try {
|
|
1641
|
-
|
|
1642
|
-
if (!existsSync(dir)) {
|
|
1643
|
-
mkdirSync(dir, { recursive: true });
|
|
1644
|
-
}
|
|
1645
|
-
let existing = {};
|
|
1646
|
-
try {
|
|
1647
|
-
existing = JSON.parse(await fs.readFile(SETTINGS_PATH, 'utf-8'));
|
|
1648
|
-
}
|
|
1649
|
-
catch {
|
|
1650
|
-
/* ignore */
|
|
1651
|
-
}
|
|
1652
|
-
const merged = { ...existing, caseOrder: order };
|
|
1653
|
-
await fs.writeFile(SETTINGS_PATH, JSON.stringify(merged, null, 2));
|
|
1659
|
+
await mutateSettingsFile((current) => ({ ...current, caseOrder: order }));
|
|
1654
1660
|
ctx.broadcast(SseEvent.CaseOrderChanged, { order });
|
|
1655
1661
|
return { success: true, data: { order } };
|
|
1656
1662
|
}
|
|
1657
1663
|
catch (err) {
|
|
1658
|
-
return
|
|
1664
|
+
return settingsWriteErrorResponse(err);
|
|
1659
1665
|
}
|
|
1660
1666
|
});
|
|
1661
1667
|
app.get('/api/cases/:name', async (req) => {
|