@covro/covro 0.3.1-dev.2 → 0.3.1-dev.4
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 +7 -3
- package/README.zh-CN.md +7 -3
- package/dist/server/config-store.js +10 -2
- package/dist/server/config-store.js.map +2 -2
- package/dist/server/config.js +4 -0
- package/dist/server/config.js.map +2 -2
- package/dist/server/http.js +33 -5
- package/dist/server/http.js.map +2 -2
- package/dist/server/local-files.js.map +1 -1
- package/dist/server/project-clones.js +2 -2
- package/dist/server/project-clones.js.map +2 -2
- package/dist/server/project-paths.js +39 -0
- package/dist/server/project-paths.js.map +7 -0
- package/dist/server/service.js +10 -3
- package/dist/server/service.js.map +2 -2
- package/dist/server/text-files.js +32 -0
- package/dist/server/text-files.js.map +7 -0
- package/dist/shared/file-preview.js +22 -0
- package/dist/shared/file-preview.js.map +7 -0
- package/dist/shared/project-path.js +8 -0
- package/dist/shared/project-path.js.map +7 -0
- package/dist/shared/release-notes.js +9 -0
- package/dist/shared/release-notes.js.map +7 -0
- package/dist/update-worker.cjs +4 -0
- package/dist/updates/release-notes.js +73 -0
- package/dist/updates/release-notes.js.map +7 -0
- package/dist/web/assets/{abnfDiagram-WEYOIA3C-BFmMm05x.js → abnfDiagram-WEYOIA3C-DU421iUb.js} +1 -1
- package/dist/web/assets/{arc-lnrGH4K4.js → arc-CsuqxmT0.js} +1 -1
- package/dist/web/assets/{architecture-WOLXFQ4H-CDbpw3av.js → architecture-WOLXFQ4H-DsTUnfuI.js} +1 -1
- package/dist/web/assets/{architectureDiagram-ZHVYKT53-Br4uXjzV.js → architectureDiagram-ZHVYKT53-Dc3b_aSn.js} +1 -1
- package/dist/web/assets/{blockDiagram-RJAE7QA3-2cto1XrH.js → blockDiagram-RJAE7QA3-CJo6zohL.js} +1 -1
- package/dist/web/assets/{c4Diagram-XDPPPE54-pPOONSo8.js → c4Diagram-XDPPPE54-PmN0Qtd6.js} +1 -1
- package/dist/web/assets/channel-BHJ4nRbW.js +1 -0
- package/dist/web/assets/{chunk-2E7WHVMD-CqOBPcBj.js → chunk-2E7WHVMD-BzKtvYJa.js} +1 -1
- package/dist/web/assets/{chunk-4EMNKR2T-BvMm1kcN.js → chunk-4EMNKR2T-CcgitPaF.js} +1 -1
- package/dist/web/assets/{chunk-55XCU76K-CojzCeCA.js → chunk-55XCU76K-NmryZEsr.js} +1 -1
- package/dist/web/assets/{chunk-7S6XSTFO-wa76hIsr.js → chunk-7S6XSTFO-DT2qxT9H.js} +1 -1
- package/dist/web/assets/{chunk-AQDTSKZB-tD5bFGjM.js → chunk-AQDTSKZB-BFnqrVb4.js} +1 -1
- package/dist/web/assets/{chunk-DSNLSHJZ-ByVh88M0.js → chunk-DSNLSHJZ-BavzRNt-.js} +1 -1
- package/dist/web/assets/{chunk-HAGFOTED-BhOQL-iG.js → chunk-HAGFOTED-Cx7L4TRC.js} +1 -1
- package/dist/web/assets/{chunk-HNE4Q36P-BZiD7Dq0.js → chunk-HNE4Q36P-C38XMJJg.js} +1 -1
- package/dist/web/assets/{chunk-NQOKRVIF-DhSSkcDm.js → chunk-NQOKRVIF-Dpc22eOs.js} +1 -1
- package/dist/web/assets/{chunk-UHW7PFBP-BONg0JLM.js → chunk-UHW7PFBP-BcpaVf5S.js} +1 -1
- package/dist/web/assets/{chunk-VQ5W62QI-CehpDQpX.js → chunk-VQ5W62QI-Y3XkIrNL.js} +1 -1
- package/dist/web/assets/{chunk-WQQL4CDK-C4Cm0U7u.js → chunk-WQQL4CDK-CGnED4F9.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-Ddcg44F2.js → chunk-XXDRQBXY-BCTxe0a_.js} +1 -1
- package/dist/web/assets/{chunk-YZO67HFG-CJYc1t5Y.js → chunk-YZO67HFG-BQrkk4s_.js} +1 -1
- package/dist/web/assets/{chunk-ZAUX2LTX-BeF7PiBe.js → chunk-ZAUX2LTX-DpzBrbZ-.js} +1 -1
- package/dist/web/assets/{chunk-ZCHMQSOR-BzxdttZQ.js → chunk-ZCHMQSOR-BY7KKI-8.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-4MYWR3UA-XeRozDII.js → classDiagram-v2-4MYWR3UA-DqahpcIZ.js} +1 -1
- package/dist/web/assets/{cose-bilkent-JH36ORCC-zvgm5h82.js → cose-bilkent-JH36ORCC-4jWM2ykR.js} +1 -1
- package/dist/web/assets/{cynefin-EF2NZ3EQ-DffawypB.js → cynefin-EF2NZ3EQ-CrG0LFB9.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-WF24UDRP-e2kk_Xg4.js → cynefinDiagram-WF24UDRP-Cry3FTAy.js} +1 -1
- package/dist/web/assets/{dagre-NK4GIZVX-DPTt7HIR.js → dagre-NK4GIZVX-Cg4GppVh.js} +1 -1
- package/dist/web/assets/{diagram-CBK3MWTC-CKQPukdf.js → diagram-CBK3MWTC-BpQwz3HE.js} +1 -1
- package/dist/web/assets/{diagram-I5XIIV2D-CBI_lUfl.js → diagram-I5XIIV2D-B3o4jW3k.js} +1 -1
- package/dist/web/assets/{diagram-ODIL2UCW-DXuDuOob.js → diagram-ODIL2UCW-BU1V7yRC.js} +1 -1
- package/dist/web/assets/{diagram-PFBEJUXE-C9Y1lchY.js → diagram-PFBEJUXE-D5xrUiPd.js} +1 -1
- package/dist/web/assets/{diagram-RJA3I4VV-CbcN7pHo.js → diagram-RJA3I4VV-BQ6G7wDx.js} +1 -1
- package/dist/web/assets/{diagram-TYUNFSNR-D_MmDJKc.js → diagram-TYUNFSNR-CJ7rOc5D.js} +1 -1
- package/dist/web/assets/{dist-C-d_G2FS.js → dist-BID9-USD.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-V4P3TTPT-CIlDI7Ps.js → ebnfDiagram-V4P3TTPT-C2KvMr_G.js} +1 -1
- package/dist/web/assets/{elk-QONTBCTQ-BrywoKK7.js → elk-QONTBCTQ-CeySt_Wk.js} +1 -1
- package/dist/web/assets/{erDiagram-KYQIGQAA-CKg6BT7V.js → erDiagram-KYQIGQAA-BM3B_3tj.js} +1 -1
- package/dist/web/assets/{eventmodeling-K75KTNOO-BkwvMA16.js → eventmodeling-K75KTNOO-BMAMYPh6.js} +1 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-By-VA-YW.js +1 -0
- package/dist/web/assets/{ganttDiagram-R7GPNTJY-DR_AlXXn.js → ganttDiagram-R7GPNTJY-DB-g05eG.js} +1 -1
- package/dist/web/assets/{gitGraph-VSP46ZUC-Cv1cefI5.js → gitGraph-VSP46ZUC-PkenObLV.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-PDYL7XHY-D_prlvP9.js → gitGraphDiagram-PDYL7XHY-DWIs-lGp.js} +1 -1
- package/dist/web/assets/index-B1wlD3sg.css +1 -0
- package/dist/web/assets/index-j8-Ii3xv.js +147 -0
- package/dist/web/assets/{info-OHQRW6UA--i-b1e1W.js → info-OHQRW6UA-DK-Wm12q.js} +1 -1
- package/dist/web/assets/{infoDiagram-MVDRKAX5-D5xGvRSb.js → infoDiagram-MVDRKAX5-JOEaVccz.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-HPEHV2GE-CdDzjaNo.js → ishikawaDiagram-HPEHV2GE-B-2x06G3.js} +1 -1
- package/dist/web/assets/{journeyDiagram-BOSHW7O7-DYXkvOgG.js → journeyDiagram-BOSHW7O7-CSkaKHoo.js} +1 -1
- package/dist/web/assets/{kanban-definition-FNSA4HC5-DMRrVeRI.js → kanban-definition-FNSA4HC5-BxhCZnNd.js} +1 -1
- package/dist/web/assets/{line-IKuSdaT6.js → line-BXURMkw2.js} +1 -1
- package/dist/web/assets/{linear-DG-Bn7df.js → linear-pVpEGZKc.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-Cr4hqI31.js → mermaid-parser.core-BkW0a1PY.js} +3 -3
- package/dist/web/assets/{mermaid.core-Ch6ggfwZ.js → mermaid.core-CeVhftut.js} +4 -4
- package/dist/web/assets/{mindmap-definition-6OY57UG3-BLIFHye9.js → mindmap-definition-6OY57UG3-mQdsPF38.js} +1 -1
- package/dist/web/assets/{packet-JDAUHWVQ-BaBw3Vck.js → packet-JDAUHWVQ-oobWVwGf.js} +1 -1
- package/dist/web/assets/{pegDiagram-ECIUJNBX-BgFAs43s.js → pegDiagram-ECIUJNBX-FSt7wyW3.js} +1 -1
- package/dist/web/assets/{pie-XZMESJXO-Dr-clP68.js → pie-XZMESJXO-DUVTQw1D.js} +1 -1
- package/dist/web/assets/{pieDiagram-GNMPY74T-bQ1SwzEf.js → pieDiagram-GNMPY74T-etPUdDAu.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-D57K4MC3-suHq6vLw.js → quadrantDiagram-D57K4MC3-mfBTnvPL.js} +1 -1
- package/dist/web/assets/{radar-ABXABTNO-Cd4Nn-aB.js → radar-ABXABTNO-D1Qyk6F_.js} +1 -1
- package/dist/web/assets/{railroad-I3PHUGI6-BEUNjieA.js → railroad-I3PHUGI6-CwM9b5Py.js} +1 -1
- package/dist/web/assets/{railroad-abnf-LNEFI6M7-DosoD9le.js → railroad-abnf-LNEFI6M7-cheN8Z4Q.js} +1 -1
- package/dist/web/assets/{railroad-ebnf-DWYD2UWJ-CNykm4Du.js → railroad-ebnf-DWYD2UWJ-CEygxJQE.js} +1 -1
- package/dist/web/assets/{railroad-peg-JR7BVNR7-C97ZuCXG.js → railroad-peg-JR7BVNR7-CNOvVcos.js} +1 -1
- package/dist/web/assets/{railroadDiagram-Y72NRBL7-B1BnvTWP.js → railroadDiagram-Y72NRBL7-Cr86pwS6.js} +1 -1
- package/dist/web/assets/{requirementDiagram-EQYSW5RA-CrlURwLY.js → requirementDiagram-EQYSW5RA-CSxt1jYV.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-SY24JHO5-CEYWGGec.js → sankeyDiagram-SY24JHO5-E6hncSWD.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-BLEHNHQU-CbUEnXPs.js → sequenceDiagram-BLEHNHQU-BUTVi_WU.js} +1 -1
- package/dist/web/assets/{src-Dy1r7Mij.js → src-BUn_l5HW.js} +1 -1
- package/dist/web/assets/{stateDiagram-v2-A6IESKWZ-DkxgX1jY.js → stateDiagram-v2-A6IESKWZ-CguJkUZp.js} +1 -1
- package/dist/web/assets/{swimlanes-B3ENRKFQ-B7RmFqNN.js → swimlanes-B3ENRKFQ-DXLF8UOQ.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-BQoBHwji.js +8 -0
- package/dist/web/assets/{timeline-definition-JXYKGLGB-DdaRshUD.js → timeline-definition-JXYKGLGB-D-etuHSZ.js} +1 -1
- package/dist/web/assets/{treeView-D4JQ5SDB-UjtdDBri.js → treeView-D4JQ5SDB-CiMI5YgG.js} +1 -1
- package/dist/web/assets/{treemap-SAJKECNS-CfFPhlAU.js → treemap-SAJKECNS-BnncdTmf.js} +1 -1
- package/dist/web/assets/{usecaseDiagram-WPOLBNNX-DpibMf1h.js → usecaseDiagram-WPOLBNNX-Crl4DQoV.js} +1 -1
- package/dist/web/assets/{vennDiagram-Q3Y3IDCW-DB_R8qT1.js → vennDiagram-Q3Y3IDCW-BxJZecDR.js} +1 -1
- package/dist/web/assets/{wardley-7MLQ67FV-Bhatoa5s.js → wardley-7MLQ67FV-zPBbyPtv.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-IVAIDKWA-DBhy_JBR.js → wardleyDiagram-IVAIDKWA-CSyK6CR6.js} +1 -1
- package/dist/web/assets/{xychartDiagram-4URNHDU4-C2oV2jkE.js → xychartDiagram-4URNHDU4-eVobxotm.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +3 -3
- package/dist/web/assets/channel-D_YOL9S0.js +0 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-DTkRetsK.js +0 -1
- package/dist/web/assets/index-Br54jmas.js +0 -147
- package/dist/web/assets/index-Crq1cgK9.css +0 -1
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-C0XU1o_x.js +0 -8
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
- 🗂️ **See every project in one place.** Run independent tasks in parallel and spot which conversations are working, waiting for approval, or ready to read. Use **Reorder projects** to drag projects or move them with the arrow buttons on desktop and mobile. Your order survives refreshes and restarts; new or restored projects appear at the end.
|
|
36
36
|
- 💬 **Pick up your Codex CLI conversations.** Browse saved history, resume existing work, or fork a new direction. Rename conversations to find them later and archive them when you're done. View and copy the full Thread ID in conversation details or diagnostics.
|
|
37
37
|
- ⚡ **Open long conversations sooner.** Recent messages appear first; scrolling preloads older history. On this device, cached conversation lists and history pages appear while the server checks for updates. [Loading and cache behavior](docs/session-history.md).
|
|
38
|
-
- 📖 **Review the work beside the conversation.** Read Markdown documents with code, tables, images, and Mermaid diagrams, or open JSON files in a desktop side panel while you keep talking to Codex. Mermaid diagrams also render in replies; view or copy their source and inspect wide diagrams at actual size. JSON previews include indentation, syntax highlighting, and original text for invalid JSON. Copy the unchanged source, with a full-screen reader on mobile. File previews are read-only;
|
|
38
|
+
- 📖 **Review the work beside the conversation.** Read Markdown documents with code, tables, images, and Mermaid diagrams, or open JSON files in a desktop side panel while you keep talking to Codex. Mermaid diagrams also render in replies; view or copy their source and inspect wide diagrams at actual size. JSON previews include indentation, syntax highlighting, and original text for invalid JSON. Copy the unchanged source, with a full-screen reader on mobile. TXT, CSV, TSV, TOML, YAML/YML, JSONL, LOG, and INI files open as plain text, preserving whitespace with optional line wrapping. File previews are read-only; all supported text formats have a 2 MiB limit and require UTF-8.
|
|
39
39
|
- 🔎 **Inspect code changes.** Open Codex’s saved patches for a turn or tool in the same read-only panel. Scroll through all files, inspect line numbers, see the exact words changed within each line, and jump between change blocks. Long diffs start collapsed and render when expanded. File headers stay visible while scrolling so you can collapse the current file.
|
|
40
40
|
- ✍️ **Switch tasks without losing your draft.** Each conversation keeps its own draft, pasted images, and model settings in the current page. On phones and tablets, Enter inserts a new line and the send button submits; desktop keeps Enter to send and Shift + Enter for a new line. Copy or export results when you're done. Use the copy button at the top right of a command’s output to copy all loaded results; truncated output is clearly marked.
|
|
41
41
|
- 🖥️ **Run a command beside your task.** Open **Shell** to execute commands in the selected project's directory, follow stdout/stderr, interrupt a run, and reuse recent commands. The panel uses Codex's configured sandbox, with a ten-minute limit and no interactive input. Closing it keeps the command running. [Capabilities and limits](docs/shell.md).
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
|
|
44
44
|
Mermaid diagrams render locally after their code fence closes, with automatic light/dark colors. Failed diagrams keep their source; rendering is limited to 50,000 source characters and 500 edges.
|
|
45
45
|
|
|
46
|
+
Local image previews reuse loaded images for 30 seconds. Reopening an expired image shows the existing preview while the server checks for updates.
|
|
47
|
+
|
|
46
48
|
<details>
|
|
47
49
|
<summary><strong>📱 A workspace that fits your phone</strong></summary>
|
|
48
50
|
<p>Check a result, answer a question, or send the next instruction without returning to your desk.</p>
|
|
@@ -53,7 +55,7 @@ Mermaid diagrams render locally after their code fence closes, with automatic li
|
|
|
53
55
|
|
|
54
56
|
## 🚀 Get started
|
|
55
57
|
|
|
56
|
-
You need **Node.js 24.19 or later in the 24.x line** and an installed, signed-in **Codex CLI**. The interface supports Simplified Chinese, English, Japanese and Korean.
|
|
58
|
+
You need **Node.js 24.19 or later in the 24.x line** and an installed, signed-in **Codex CLI**. The interface supports Simplified Chinese, English, Japanese and Korean. Open **Settings → Language** in the sidebar; the first visit follows your browser preferences, and manual choices are saved on this device.
|
|
57
59
|
|
|
58
60
|
<!-- codex-compatibility:start -->
|
|
59
61
|
|
|
@@ -109,7 +111,7 @@ pnpm start -- --config ./config.local.json
|
|
|
109
111
|
|
|
110
112
|
Open **<http://127.0.0.1:8787>** and click **+** beside **Unassigned** in the sidebar to send a task without choosing a project.
|
|
111
113
|
|
|
112
|
-
To work in a project directory, click **Add project**, then choose an existing host directory or **Clone Git repository**. For
|
|
114
|
+
To work in a project directory, click **Add project**, then choose an existing host directory or **Clone Git repository**. Set **Service configuration → Data and projects → Project base directory (workDir, optional)** to start from a shared host directory. For example, with `workDir=/workspace/projects`, enter `demo` to add `/workspace/projects/demo`; cloning a `demo` repository defaults to that path too. Absolute paths override the prefix. The final server path is shown before submission and the destination remains editable. Saving `workDir` applies immediately to subsequent imports and survives refreshes and restarts, without moving existing projects. Leave it blank to keep entering absolute paths. Git cloning uses the host’s existing credentials, with progress and cancellation in the dialog. [Directory configuration, clone requirements and recovery](docs/project-clone.md).
|
|
113
115
|
|
|
114
116
|
Unassigned conversations use separate directories under `<dataDir>/workspaces/`, allocated on the first send. Their files are retained until you explicitly remove them; archiving does not delete files. See [conversation workspaces](docs/workspaces.md) for directory, recovery, and backup details.
|
|
115
117
|
|
|
@@ -149,6 +151,8 @@ Stable checks only `latest`. Beta and Dev each compare their own tag with `lates
|
|
|
149
151
|
|
|
150
152
|
Successful Dev and Beta npm publications also create a [GitHub Pre-release](https://github.com/yuzheng14/covro/releases) with automatically generated notes. The comparison starts from whichever has the newer source commit: the preceding version in the same channel or a stable version. Push a Beta tag to publish both the npm package and its Pre-release; existing release notes are preserved on retries. Beta notes also include compatibility declarations from the selected source. Stable releases retain their grouped changelog.
|
|
151
153
|
|
|
154
|
+
Before installing, the update panel displays the selected version’s GitHub Release notes with Markdown and a link to the original. Notes follow the exact target version; some Beta / Dev versions have no matching Release. Missing notes or GitHub connection failures do not block updates.
|
|
155
|
+
|
|
152
156
|
Each release declares its recommended and verified Codex CLI versions. The update panel shows the current and candidate combinations. Automatic upgrades require a verified combination; an unverified combination needs explicit confirmation for that exact upgrade. Unknown versions, invalid/missing declarations, and explicitly unsupported combinations block managed upgrades. The CLI offers `covro update --allow-unverified-codex` for the same explicit attempt. Covro does not upgrade your Codex CLI.
|
|
153
157
|
|
|
154
158
|
Automatic installation is off by default. Managed upgrades support **macOS and Linux npm global installations**; source, linked, container, and Windows installations use their original update method. A failed candidate is rolled back before business writes resume; downgrades must pass database compatibility checks. Old backups are retained without automatic rotation.
|
package/README.zh-CN.md
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
- 🗂️ **多个项目,一眼掌握进展。** 让独立任务并行执行,随时看到哪些会话正在工作、等待审批,哪些已有结果可读。点击「调整项目顺序」,在电脑上拖拽项目,或在电脑和手机上用上下按钮排序;刷新和重启后顺序仍然保留,新增或重新添加的项目排在末尾。
|
|
36
36
|
- 💬 **接着你的 Codex CLI 会话。** 浏览已有历史、恢复工作,或 Fork 一个新方向;为会话命名以便查找,完成后归档。在会话详情或状态诊断中查看、复制完整 Thread ID。
|
|
37
37
|
- ⚡ **更快打开长会话。** 优先显示最近消息,向上滚动时提前加载历史;本机会话列表和历史分片可先显示缓存,再由服务端核对更新。[加载与缓存说明](docs/session-history.md)。
|
|
38
|
-
- 📖 **一边看产出,一边继续讨论。** 电脑上将 Markdown 文档、JSON 文件与会话并排打开,继续向 Codex 提问;Markdown 支持代码、表格、图片和 Mermaid 图表,回复也可直接显示图表,可查看或复制图表源码,并以实际大小查看宽图。JSON
|
|
38
|
+
- 📖 **一边看产出,一边继续讨论。** 电脑上将 Markdown 文档、JSON 文件与会话并排打开,继续向 Codex 提问;Markdown 支持代码、表格、图片和 Mermaid 图表,回复也可直接显示图表,可查看或复制图表源码,并以实际大小查看宽图。JSON 支持格式化缩进、语法高亮,格式无效时保留原文,可复制完整原文,手机上也有全屏阅读器。TXT、CSV、TSV、TOML、YAML/YML、JSONL、LOG 和 INI 文件按纯文本展示,保留空白并支持切换自动换行。文件预览保持只读,所有支持的文本格式均限制为 2 MiB,仅支持 UTF-8。
|
|
39
39
|
- 🔎 **逐行查看代码变更。** 在同一只读预览窗口查看某轮或某工具的 Codex 补丁快照,按顺序连续浏览全部文件,支持行号、行内文字高亮和区块跳转;长 diff 默认折叠,展开后按需渲染,阅读时文件标题栏吸顶,随时收起当前文件。
|
|
40
40
|
- ✍️ **切换任务,草稿还在。** 每个会话分别保留当前页面中的草稿、粘贴图片和模型设置。手机和平板按 Enter 换行、点击发送按钮提交;桌面保留 Enter 发送、Shift + Enter 换行。完成后可以复制或导出结果。在命令输出区域右上角一键复制全部已加载结果,截断内容会明确提示。
|
|
41
41
|
- 🖥️ **在任务旁运行命令。** 打开 **Shell**,在所选项目目录执行命令、查看实时标准输出和错误输出、中断执行,并复用近期命令。面板沿用 Codex 配置的沙箱,最长运行十分钟,不支持交互输入;关闭面板后命令仍继续运行。[能力与限制](docs/shell.md)。
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
|
|
44
44
|
Mermaid 图表在代码块闭合后于本地渲染,自动适配浅色/深色。渲染失败时保留源码;渲染上限为 50,000 个源码字符、500 条边。
|
|
45
45
|
|
|
46
|
+
本地图片预览在加载后 30 秒内直接复用已有图片;过期后再次打开时,先显示已有预览,再由服务端核对更新。
|
|
47
|
+
|
|
46
48
|
<details>
|
|
47
49
|
<summary><strong>📱 手机上也能舒服地使用</strong></summary>
|
|
48
50
|
<p>查看结果、回答问题、补充下一步要求,不必每次都回到电脑前。</p>
|
|
@@ -53,7 +55,7 @@ Mermaid 图表在代码块闭合后于本地渲染,自动适配浅色/深色
|
|
|
53
55
|
|
|
54
56
|
## 🚀 开始使用
|
|
55
57
|
|
|
56
|
-
需要 **Node.js 24.x,版本不低于 24.19**,以及已经安装、登录的 **Codex CLI
|
|
58
|
+
需要 **Node.js 24.x,版本不低于 24.19**,以及已经安装、登录的 **Codex CLI**。界面支持简体中文、英语、日语和韩语。在侧栏打开「设置 → 语言」;首次访问按浏览器偏好匹配,手动选择会保存在当前设备。
|
|
57
59
|
|
|
58
60
|
<!-- codex-compatibility:start -->
|
|
59
61
|
|
|
@@ -109,7 +111,7 @@ pnpm start -- --config ./config.local.json
|
|
|
109
111
|
|
|
110
112
|
打开 **<http://127.0.0.1:8787>**,点击侧栏 **未关联项目** 标题右侧的 **+** 即可直接发送任务,无需选择项目。
|
|
111
113
|
|
|
112
|
-
如需操作项目目录,点击
|
|
114
|
+
如需操作项目目录,点击 **添加项目**,选择已有目录或 **克隆 Git 仓库**。可在 **服务配置 → 数据与项目 → 项目基础目录(workDir,可选)** 设置共用的主机目录。例如 `workDir=/workspace/projects` 时,填写 `demo` 即添加 `/workspace/projects/demo`,克隆 `demo` 仓库时也默认使用该路径。绝对路径直接使用,不再拼接前缀;提交前会显示最终服务器路径,目录可自行编辑。`workDir` 保存后立即用于后续添加和克隆,刷新或重启后仍保留,不会移动已有项目;留空时继续填写绝对路径。克隆沿用主机已有的 Git 认证,弹窗可查看进度或取消。[目录配置、克隆要求与失败恢复](docs/project-clone.md)。
|
|
113
115
|
|
|
114
116
|
未关联项目的会话在首次发送时分配 `<dataDir>/workspaces/` 下的独立目录。文件持续保留,归档不会删除文件;目录、恢复与备份规则见[会话工作目录](docs/workspaces.md)。
|
|
115
117
|
|
|
@@ -149,6 +151,8 @@ covro update --channel dev
|
|
|
149
151
|
|
|
150
152
|
开发版和预览版 npm 发布成功后,还会创建带 GitHub 自动生成说明的 [GitHub Pre-release](https://github.com/yuzheng14/covro/releases)。比较起点取同一渠道的先前版本与稳定版本中源码提交较新的那个。推送 Beta 标签即可发布 npm 包并创建对应的 Pre-release;重跑时保留已有发布说明。Beta 说明还会附上所选源码的兼容性声明。稳定版仍保留原来的分组 Changelog。
|
|
151
153
|
|
|
154
|
+
安装前,更新面板会展示所选目标版本的 GitHub Release 说明,支持 Markdown 和原文链接。说明始终对应确切目标版本;部分 Beta / Dev 版本没有对应 Release。说明缺失或 GitHub 连接失败不会阻止更新。
|
|
155
|
+
|
|
152
156
|
每个发行版声明推荐和已验证的 Codex CLI 版本,更新面板会同时显示当前与目标组合。自动升级只接受已验证组合;未验证组合需要对确切目标明确确认。版本未知、声明缺失/无效或明确不支持的组合会阻止受管升级。CLI 可通过 `covro update --allow-unverified-codex` 明确尝试同一组合。Covro 不会自动升级你的 Codex CLI。
|
|
153
157
|
|
|
154
158
|
自动安装默认关闭。受管升级支持 **macOS 和 Linux 的 npm 全局安装**;源码、链接、容器和 Windows 安装沿用原更新方式。候选版本失败时,会在重新开放业务写入前回滚;降级需通过数据库兼容检查。旧备份会保留,暂不自动轮转。
|
|
@@ -2,18 +2,23 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { access, lstat, open, readFile, rename, rm } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
+
import { isDeepStrictEqual } from "node:util";
|
|
5
6
|
import lockfile from "proper-lockfile";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
import { AppError } from "../shared/errors.js";
|
|
8
9
|
import { configSource, parseConfig, parseFileConfig } from "./config.js";
|
|
10
|
+
import { assertWorkDir } from "./project-paths.js";
|
|
9
11
|
const saveSchema = z.object({ revision: z.string().regex(/^[a-f0-9]{64}$/), config: z.unknown() }).strict();
|
|
10
12
|
const importSchema = z.object({ config: z.unknown() }).strict();
|
|
11
13
|
const revision = (text) => createHash("sha256").update(text).digest("hex");
|
|
12
|
-
const same =
|
|
14
|
+
const same = isDeepStrictEqual;
|
|
13
15
|
const MAX_CONFIG_BYTES = 256 * 1024;
|
|
14
16
|
class ConfigStore {
|
|
15
17
|
source;
|
|
16
18
|
running;
|
|
19
|
+
get workDir() {
|
|
20
|
+
return this.running.workDir;
|
|
21
|
+
}
|
|
17
22
|
constructor(config) {
|
|
18
23
|
this.source = configSource(config);
|
|
19
24
|
const { demo: _demo, ...running } = config;
|
|
@@ -67,7 +72,7 @@ class ConfigStore {
|
|
|
67
72
|
path: this.source.path,
|
|
68
73
|
revision: file.revision,
|
|
69
74
|
saved: file.saved,
|
|
70
|
-
running: this.running,
|
|
75
|
+
running: structuredClone(this.running),
|
|
71
76
|
restartRequired: !same(effective, this.running),
|
|
72
77
|
writable: !readOnlyReason,
|
|
73
78
|
...readOnlyReason ? { readOnlyReason } : {}
|
|
@@ -111,6 +116,7 @@ class ConfigStore {
|
|
|
111
116
|
"\u6570\u636E\u76EE\u5F55\u4E0D\u53EF\u5728\u6B64\u4FEE\u6539\uFF1B\u9879\u76EE\u8BF7\u4F7F\u7528\u9879\u76EE\u7BA1\u7406\u529F\u80FD\u3002",
|
|
112
117
|
400
|
|
113
118
|
);
|
|
119
|
+
if (config.workDir && config.workDir !== current.saved.workDir) assertWorkDir(config.workDir);
|
|
114
120
|
const contents = `${JSON.stringify(config, null, 2)}
|
|
115
121
|
`;
|
|
116
122
|
if (Buffer.byteLength(contents) > MAX_CONFIG_BYTES)
|
|
@@ -126,6 +132,8 @@ class ConfigStore {
|
|
|
126
132
|
if (latest.revision !== current.revision || !latest.writable)
|
|
127
133
|
throw new AppError("CONFIG_CONFLICT", "\u914D\u7F6E\u6587\u4EF6\u5DF2\u53D1\u751F\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u518D\u4FDD\u5B58\u3002", 409);
|
|
128
134
|
await rename(temporary, this.source.path);
|
|
135
|
+
if (config.workDir) this.running.workDir = config.workDir;
|
|
136
|
+
else delete this.running.workDir;
|
|
129
137
|
return await this.snapshot();
|
|
130
138
|
} catch (error) {
|
|
131
139
|
if (error instanceof AppError) throw error;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/server/config-store.ts"],
|
|
4
|
-
"sourcesContent": ["import { createHash, randomUUID } from 'node:crypto';\nimport { constants } from 'node:fs';\nimport { access, lstat, open, readFile, rename, rm } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport lockfile from 'proper-lockfile';\nimport { z } from 'zod';\nimport { AppError } from '../shared/errors.js';\nimport type { GatewayConfig, GatewayConfigSnapshot } from '../shared/gateway-config.js';\nimport { configSource, parseConfig, parseFileConfig, type AppConfig } from './config.js';\n\nconst saveSchema = z\n .object({ revision: z.string().regex(/^[a-f0-9]{64}$/), config: z.unknown() })\n .strict();\nconst importSchema = z.object({ config: z.unknown() }).strict();\nconst revision = (text: string) => createHash('sha256').update(text).digest('hex');\nconst same =
|
|
5
|
-
"mappings": "AAAA,SAAS,YAAY,kBAAkB;AACvC,SAAS,iBAAiB;AAC1B,SAAS,QAAQ,OAAO,MAAM,UAAU,QAAQ,UAAU;AAC1D,SAAS,SAAS,YAAY;AAC9B,OAAO,cAAc;AACrB,SAAS,SAAS;AAClB,SAAS,gBAAgB;AAEzB,SAAS,cAAc,aAAa,uBAAuC;
|
|
4
|
+
"sourcesContent": ["import { createHash, randomUUID } from 'node:crypto';\nimport { constants } from 'node:fs';\nimport { access, lstat, open, readFile, rename, rm } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { isDeepStrictEqual } from 'node:util';\nimport lockfile from 'proper-lockfile';\nimport { z } from 'zod';\nimport { AppError } from '../shared/errors.js';\nimport type { GatewayConfig, GatewayConfigSnapshot } from '../shared/gateway-config.js';\nimport { configSource, parseConfig, parseFileConfig, type AppConfig } from './config.js';\nimport { assertWorkDir } from './project-paths.js';\n\nconst saveSchema = z\n .object({ revision: z.string().regex(/^[a-f0-9]{64}$/), config: z.unknown() })\n .strict();\nconst importSchema = z.object({ config: z.unknown() }).strict();\nconst revision = (text: string) => createHash('sha256').update(text).digest('hex');\nconst same = isDeepStrictEqual;\nexport const MAX_CONFIG_BYTES = 256 * 1024;\n\n/** Service settings apply on restart; workDir applies immediately to future imports. */\nexport class ConfigStore {\n private readonly source;\n private readonly running: GatewayConfig;\n get workDir() {\n return this.running.workDir;\n }\n constructor(config: AppConfig) {\n this.source = configSource(config);\n const { demo: _demo, ...running } = config;\n this.running = structuredClone(running);\n }\n\n private async read() {\n if (!this.source)\n throw new AppError('CONFIG_UNAVAILABLE', '\u6B64\u5B9E\u4F8B\u672A\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\uFF0C\u65E0\u6CD5\u7BA1\u7406\u670D\u52A1\u914D\u7F6E\u3002', 503);\n try {\n const stat = await lstat(this.source.path);\n if (stat.size > MAX_CONFIG_BYTES)\n throw new AppError('INVALID_CONFIG', '\u914D\u7F6E\u6587\u4EF6\u4E0D\u80FD\u8D85\u8FC7 256 KiB\u3002');\n const text = await readFile(this.source.path, 'utf8');\n if (Buffer.byteLength(text) > MAX_CONFIG_BYTES)\n throw new AppError('INVALID_CONFIG', '\u914D\u7F6E\u6587\u4EF6\u4E0D\u80FD\u8D85\u8FC7 256 KiB\u3002');\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n throw new AppError('INVALID_CONFIG', '\u914D\u7F6E\u6587\u4EF6\u4E0D\u662F\u6709\u6548\u7684 JSON\uFF0C\u8BF7\u5148\u4FEE\u590D\u6587\u4EF6\u3002');\n }\n return { saved: parseFileConfig(value), revision: revision(text), stat };\n } catch (error) {\n if (error instanceof AppError) throw error;\n throw new AppError(\n 'CONFIG_READ_FAILED',\n '\u65E0\u6CD5\u8BFB\u53D6\u542F\u52A8\u65F6\u6307\u5B9A\u7684\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u548C\u6743\u9650\u3002',\n 503,\n );\n }\n }\n\n async snapshot(): Promise<GatewayConfigSnapshot> {\n const file = await this.read();\n let readOnlyReason: string | undefined;\n if (!file.stat.isFile())\n readOnlyReason = '\u8F6F\u94FE\u63A5\u6216\u7279\u6B8A\u914D\u7F6E\u6587\u4EF6\u53EA\u652F\u6301\u67E5\u770B\u548C\u5BFC\u51FA\uFF0C\u8BF7\u76F4\u63A5\u7F16\u8F91\u6E90\u6587\u4EF6\u3002';\n else {\n try {\n await access(this.source!.path, constants.W_OK);\n await access(dirname(this.source!.path), constants.W_OK);\n } catch {\n readOnlyReason = '\u914D\u7F6E\u6587\u4EF6\u6216\u6240\u5728\u76EE\u5F55\u4E0D\u53EF\u5199\uFF0C\u8BF7\u8C03\u6574\u670D\u52A1\u8FDB\u7A0B\u7684\u6587\u4EF6\u6743\u9650\u3002';\n }\n }\n const { demo: _demo, ...effective } = parseConfig(\n file.saved,\n this.source!.path,\n this.source!.development,\n );\n return {\n path: this.source!.path,\n revision: file.revision,\n saved: file.saved,\n running: structuredClone(this.running),\n restartRequired: !same(effective, this.running),\n writable: !readOnlyReason,\n ...(readOnlyReason ? { readOnlyReason } : {}),\n };\n }\n\n async previewImport(input: unknown) {\n const imported = parseFileConfig(importSchema.parse(input).config);\n const { saved } = await this.read();\n const preserved = (['dataDir', 'projects'] as const).filter(\n (key) => !same(imported[key], saved[key]),\n );\n return { config: { ...imported, dataDir: saved.dataDir, projects: saved.projects }, preserved };\n }\n\n async export() {\n return (await this.read()).saved;\n }\n\n async save(input: unknown): Promise<GatewayConfigSnapshot> {\n const parsed = saveSchema.parse(input);\n const config = parseFileConfig(parsed.config);\n const initial = await this.snapshot();\n if (!initial.writable) throw new AppError('CONFIG_READ_ONLY', initial.readOnlyReason!, 409);\n let release: (() => Promise<void>) | undefined;\n const temporary = join(dirname(this.source!.path), `.codex-config-${randomUUID()}.tmp`);\n try {\n try {\n release = await lockfile.lock(this.source!.path, { realpath: false, retries: 0 });\n } catch {\n throw new AppError('CONFIG_BUSY', '\u914D\u7F6E\u6B63\u5728\u88AB\u5176\u4ED6\u64CD\u4F5C\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002', 409);\n }\n const current = await this.snapshot();\n if (!current.writable) throw new AppError('CONFIG_READ_ONLY', current.readOnlyReason!, 409);\n if (current.revision !== parsed.revision)\n throw new AppError(\n 'CONFIG_CONFLICT',\n '\u914D\u7F6E\u6587\u4EF6\u5DF2\u88AB\u5176\u4ED6\u9875\u9762\u6216\u7A0B\u5E8F\u4FEE\u6539\u3002\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u518D\u4FDD\u5B58\u3002',\n 409,\n );\n if (\n config.dataDir !== current.saved.dataDir ||\n !same(config.projects, current.saved.projects)\n )\n throw new AppError(\n 'CONFIG_PROTECTED',\n '\u6570\u636E\u76EE\u5F55\u4E0D\u53EF\u5728\u6B64\u4FEE\u6539\uFF1B\u9879\u76EE\u8BF7\u4F7F\u7528\u9879\u76EE\u7BA1\u7406\u529F\u80FD\u3002',\n 400,\n );\n if (config.workDir && config.workDir !== current.saved.workDir) assertWorkDir(config.workDir);\n const contents = `${JSON.stringify(config, null, 2)}\\n`;\n if (Buffer.byteLength(contents) > MAX_CONFIG_BYTES)\n throw new AppError('INVALID_CONFIG', '\u914D\u7F6E\u6587\u4EF6\u4E0D\u80FD\u8D85\u8FC7 256 KiB\u3002');\n const file = await open(temporary, 'wx', 0o600);\n try {\n await file.writeFile(contents, 'utf8');\n await file.sync();\n } finally {\n await file.close();\n }\n // Catch external editors that changed the file while the temporary file was written.\n const latest = await this.snapshot();\n if (latest.revision !== current.revision || !latest.writable)\n throw new AppError('CONFIG_CONFLICT', '\u914D\u7F6E\u6587\u4EF6\u5DF2\u53D1\u751F\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u518D\u4FDD\u5B58\u3002', 409);\n await rename(temporary, this.source!.path);\n if (config.workDir) this.running.workDir = config.workDir;\n else delete this.running.workDir;\n return await this.snapshot();\n } catch (error) {\n if (error instanceof AppError) throw error;\n throw new AppError(\n 'CONFIG_WRITE_FAILED',\n '\u4FDD\u5B58\u914D\u7F6E\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u6743\u9650\u548C\u78C1\u76D8\u7A7A\u95F4\u540E\u91CD\u8BD5\u3002',\n 500,\n );\n } finally {\n await rm(temporary, { force: true });\n await release?.();\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/server/config.js
CHANGED
|
@@ -25,6 +25,10 @@ const configSchema = z.object({
|
|
|
25
25
|
publicOrigin: z.array(origin).min(1).default(["http://127.0.0.1:8787"]).transform((values) => [...new Set(values)]),
|
|
26
26
|
developmentOrigins: z.array(origin).default([]),
|
|
27
27
|
dataDir: z.string().min(1).default("./.local/codex-web"),
|
|
28
|
+
workDir: z.string().trim().max(4096).refine(
|
|
29
|
+
(path) => isAbsolute(path) && !path.includes("\0"),
|
|
30
|
+
"workDir \u5FC5\u987B\u662F\u670D\u52A1\u4E3B\u673A\u4E0A\u7684\u7EDD\u5BF9\u76EE\u5F55\u8DEF\u5F84"
|
|
31
|
+
).optional(),
|
|
28
32
|
codex: z.object({
|
|
29
33
|
binary: z.string().min(1).default("codex"),
|
|
30
34
|
home: z.string().refine(isAbsolute).optional(),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/server/config.ts"],
|
|
4
|
-
"sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { z } from 'zod';\nimport { AppError } from '../shared/errors.js';\nimport { DEFAULT_MAX_LINE_BYTES } from '../codex/transport.js';\nimport type { GatewayConfig } from '../shared/gateway-config.js';\nconst origin = z\n .string()\n .regex(/^https?:\\/\\/[^/?#\\\\\\s]+\\/?$/i, 'Expected an exact HTTP(S) origin')\n .pipe(z.string().url())\n .transform((value, context) => {\n const url = new URL(value);\n if (\n !['http:', 'https:'].includes(url.protocol) ||\n url.hostname.includes('*') ||\n value.includes('@') ||\n url.username ||\n url.password ||\n url.pathname !== '/' ||\n url.search ||\n url.hash\n ) {\n context.addIssue({\n code: 'custom',\n message:\n 'Expected an exact HTTP(S) origin without wildcard, path, credentials, query or hash',\n });\n return z.NEVER;\n }\n return url.origin;\n });\nconst project = z\n .object({\n id: z.string().regex(/^[a-zA-Z0-9_-]+$/),\n name: z.string().min(1),\n path: z.string().refine(isAbsolute, 'Project path must be absolute'),\n })\n .strict();\nexport const configSchema = z\n .object({\n host: z.string().default('127.0.0.1'),\n port: z.number().int().min(1).max(65535).default(8787),\n publicOrigin: z\n .array(origin)\n .min(1)\n .default(['http://127.0.0.1:8787'])\n .transform((values) => [...new Set(values)]),\n developmentOrigins: z.array(origin).default([]),\n dataDir: z.string().min(1).default('./.local/codex-web'),\n codex: z\n .object({\n binary: z.string().min(1).default('codex'),\n home: z.string().refine(isAbsolute).optional(),\n requestTimeoutMs: z.number().int().min(100).max(300_000).default(30_000),\n maxLineBytes: z\n .number()\n .int()\n .min(1024)\n .max(256 * 1024 * 1024)\n .default(DEFAULT_MAX_LINE_BYTES),\n })\n .strict()\n .default({ binary: 'codex', requestTimeoutMs: 30_000, maxLineBytes: DEFAULT_MAX_LINE_BYTES }),\n auth: z\n .discriminatedUnion('mode', [\n z.object({ mode: z.literal('local') }).strict(),\n z\n .object({\n mode: z.literal('cloudflare-access'),\n issuer: z.string().url(),\n audience: z.string().min(1),\n })\n .strict(),\n ])\n .default({ mode: 'local' }),\n projects: z.array(project).default([]),\n limits: z\n .object({\n eventBytes: z\n .number()\n .int()\n .min(1024)\n .max(64 * 1024 * 1024)\n .default(16 * 1024 * 1024),\n wsBytes: z\n .number()\n .int()\n .min(1024)\n .max(16 * 1024 * 1024)\n .default(4 * 1024 * 1024),\n itemBytes: z\n .number()\n .int()\n .min(1024)\n .max(8 * 1024 * 1024)\n .default(2 * 1024 * 1024),\n })\n .strict()\n .default({\n eventBytes: 16 * 1024 * 1024,\n wsBytes: 4 * 1024 * 1024,\n itemBytes: 2 * 1024 * 1024,\n }),\n })\n .strict();\nexport type AppConfig = z.infer<typeof configSchema> & { demo: boolean };\nconst sources = new WeakMap<AppConfig, { path: string; development: boolean }>();\nexport const configSource = (config: AppConfig) => sources.get(config);\nexport function isLoopback(hostname: string): boolean {\n return ['127.0.0.1', 'localhost', '::1', '[::1]'].includes(hostname);\n}\nexport function parseFileConfig(value: unknown): GatewayConfig {\n const parsed = configSchema.safeParse(value);\n if (!parsed.success)\n throw new AppError(\n 'INVALID_CONFIG',\n parsed.error.issues\n .map((issue) => `${issue.path.join('.') || '\u914D\u7F6E'}: ${issue.message}`)\n .join('\\n'),\n );\n const config = parsed.data;\n if (new Set(config.projects.map((p) => p.id)).size !== config.projects.length)\n throw new AppError('INVALID_CONFIG', 'Project IDs must be unique');\n if (config.auth.mode === 'local' && !isLoopback(config.host))\n throw new AppError('INVALID_CONFIG', 'Local mode requires a loopback host');\n if (config.developmentOrigins.some((o) => !isLoopback(new URL(o).hostname)))\n throw new AppError('INVALID_CONFIG', 'Development origins must be loopback');\n if (config.auth.mode === 'cloudflare-access') {\n const issuer = new URL(config.auth.issuer);\n if (\n issuer.protocol !== 'https:' ||\n !issuer.hostname.endsWith('.cloudflareaccess.com') ||\n issuer.pathname !== '/' ||\n issuer.search ||\n issuer.hash ||\n issuer.username ||\n issuer.password\n )\n throw new AppError(\n 'INVALID_CONFIG',\n 'Access issuer must be https://<team>.cloudflareaccess.com',\n );\n if (config.publicOrigin.some((value) => new URL(value).protocol !== 'https:'))\n throw new AppError('INVALID_CONFIG', 'All Access public origins must use HTTPS');\n config.auth.issuer = issuer.origin;\n }\n return config;\n}\nexport function parseConfig(value: unknown, filename: string, development = false): AppConfig {\n const config = parseFileConfig(value);\n const result: AppConfig = {\n ...config,\n developmentOrigins: development ? config.developmentOrigins : [],\n dataDir: resolve(dirname(filename), config.dataDir),\n demo: false,\n };\n sources.set(result, { path: resolve(filename), development });\n return result;\n}\nexport function cliOptions(args = process.argv.slice(2)): {\n configPath: string;\n development: boolean;\n} {\n const remaining = args.filter((arg) => arg !== '--');\n let configPath = 'config.local.json';\n let development = false;\n for (let i = 0; i < remaining.length; i++) {\n const arg = remaining[i];\n if (arg === '--dev') development = true;\n else if (arg === '--config' && remaining[i + 1]) configPath = remaining[++i]!;\n else throw new AppError('INVALID_ARGUMENT', `Unknown or incomplete argument: ${arg}`);\n }\n return { configPath: resolve(configPath), development };\n}\nexport async function loadConfig(options = cliOptions()): Promise<AppConfig> {\n return parseConfig(\n JSON.parse(await readFile(options.configPath, 'utf8')),\n options.configPath,\n options.development,\n );\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { z } from 'zod';\nimport { AppError } from '../shared/errors.js';\nimport { DEFAULT_MAX_LINE_BYTES } from '../codex/transport.js';\nimport type { GatewayConfig } from '../shared/gateway-config.js';\nconst origin = z\n .string()\n .regex(/^https?:\\/\\/[^/?#\\\\\\s]+\\/?$/i, 'Expected an exact HTTP(S) origin')\n .pipe(z.string().url())\n .transform((value, context) => {\n const url = new URL(value);\n if (\n !['http:', 'https:'].includes(url.protocol) ||\n url.hostname.includes('*') ||\n value.includes('@') ||\n url.username ||\n url.password ||\n url.pathname !== '/' ||\n url.search ||\n url.hash\n ) {\n context.addIssue({\n code: 'custom',\n message:\n 'Expected an exact HTTP(S) origin without wildcard, path, credentials, query or hash',\n });\n return z.NEVER;\n }\n return url.origin;\n });\nconst project = z\n .object({\n id: z.string().regex(/^[a-zA-Z0-9_-]+$/),\n name: z.string().min(1),\n path: z.string().refine(isAbsolute, 'Project path must be absolute'),\n })\n .strict();\nexport const configSchema = z\n .object({\n host: z.string().default('127.0.0.1'),\n port: z.number().int().min(1).max(65535).default(8787),\n publicOrigin: z\n .array(origin)\n .min(1)\n .default(['http://127.0.0.1:8787'])\n .transform((values) => [...new Set(values)]),\n developmentOrigins: z.array(origin).default([]),\n dataDir: z.string().min(1).default('./.local/codex-web'),\n workDir: z\n .string()\n .trim()\n .max(4096)\n .refine(\n (path) => isAbsolute(path) && !path.includes('\\0'),\n 'workDir \u5FC5\u987B\u662F\u670D\u52A1\u4E3B\u673A\u4E0A\u7684\u7EDD\u5BF9\u76EE\u5F55\u8DEF\u5F84',\n )\n .optional(),\n codex: z\n .object({\n binary: z.string().min(1).default('codex'),\n home: z.string().refine(isAbsolute).optional(),\n requestTimeoutMs: z.number().int().min(100).max(300_000).default(30_000),\n maxLineBytes: z\n .number()\n .int()\n .min(1024)\n .max(256 * 1024 * 1024)\n .default(DEFAULT_MAX_LINE_BYTES),\n })\n .strict()\n .default({ binary: 'codex', requestTimeoutMs: 30_000, maxLineBytes: DEFAULT_MAX_LINE_BYTES }),\n auth: z\n .discriminatedUnion('mode', [\n z.object({ mode: z.literal('local') }).strict(),\n z\n .object({\n mode: z.literal('cloudflare-access'),\n issuer: z.string().url(),\n audience: z.string().min(1),\n })\n .strict(),\n ])\n .default({ mode: 'local' }),\n projects: z.array(project).default([]),\n limits: z\n .object({\n eventBytes: z\n .number()\n .int()\n .min(1024)\n .max(64 * 1024 * 1024)\n .default(16 * 1024 * 1024),\n wsBytes: z\n .number()\n .int()\n .min(1024)\n .max(16 * 1024 * 1024)\n .default(4 * 1024 * 1024),\n itemBytes: z\n .number()\n .int()\n .min(1024)\n .max(8 * 1024 * 1024)\n .default(2 * 1024 * 1024),\n })\n .strict()\n .default({\n eventBytes: 16 * 1024 * 1024,\n wsBytes: 4 * 1024 * 1024,\n itemBytes: 2 * 1024 * 1024,\n }),\n })\n .strict();\nexport type AppConfig = z.infer<typeof configSchema> & { demo: boolean };\nconst sources = new WeakMap<AppConfig, { path: string; development: boolean }>();\nexport const configSource = (config: AppConfig) => sources.get(config);\nexport function isLoopback(hostname: string): boolean {\n return ['127.0.0.1', 'localhost', '::1', '[::1]'].includes(hostname);\n}\nexport function parseFileConfig(value: unknown): GatewayConfig {\n const parsed = configSchema.safeParse(value);\n if (!parsed.success)\n throw new AppError(\n 'INVALID_CONFIG',\n parsed.error.issues\n .map((issue) => `${issue.path.join('.') || '\u914D\u7F6E'}: ${issue.message}`)\n .join('\\n'),\n );\n const config = parsed.data;\n if (new Set(config.projects.map((p) => p.id)).size !== config.projects.length)\n throw new AppError('INVALID_CONFIG', 'Project IDs must be unique');\n if (config.auth.mode === 'local' && !isLoopback(config.host))\n throw new AppError('INVALID_CONFIG', 'Local mode requires a loopback host');\n if (config.developmentOrigins.some((o) => !isLoopback(new URL(o).hostname)))\n throw new AppError('INVALID_CONFIG', 'Development origins must be loopback');\n if (config.auth.mode === 'cloudflare-access') {\n const issuer = new URL(config.auth.issuer);\n if (\n issuer.protocol !== 'https:' ||\n !issuer.hostname.endsWith('.cloudflareaccess.com') ||\n issuer.pathname !== '/' ||\n issuer.search ||\n issuer.hash ||\n issuer.username ||\n issuer.password\n )\n throw new AppError(\n 'INVALID_CONFIG',\n 'Access issuer must be https://<team>.cloudflareaccess.com',\n );\n if (config.publicOrigin.some((value) => new URL(value).protocol !== 'https:'))\n throw new AppError('INVALID_CONFIG', 'All Access public origins must use HTTPS');\n config.auth.issuer = issuer.origin;\n }\n return config;\n}\nexport function parseConfig(value: unknown, filename: string, development = false): AppConfig {\n const config = parseFileConfig(value);\n const result: AppConfig = {\n ...config,\n developmentOrigins: development ? config.developmentOrigins : [],\n dataDir: resolve(dirname(filename), config.dataDir),\n demo: false,\n };\n sources.set(result, { path: resolve(filename), development });\n return result;\n}\nexport function cliOptions(args = process.argv.slice(2)): {\n configPath: string;\n development: boolean;\n} {\n const remaining = args.filter((arg) => arg !== '--');\n let configPath = 'config.local.json';\n let development = false;\n for (let i = 0; i < remaining.length; i++) {\n const arg = remaining[i];\n if (arg === '--dev') development = true;\n else if (arg === '--config' && remaining[i + 1]) configPath = remaining[++i]!;\n else throw new AppError('INVALID_ARGUMENT', `Unknown or incomplete argument: ${arg}`);\n }\n return { configPath: resolve(configPath), development };\n}\nexport async function loadConfig(options = cliOptions()): Promise<AppConfig> {\n return parseConfig(\n JSON.parse(await readFile(options.configPath, 'utf8')),\n options.configPath,\n options.development,\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["value"]
|
|
7
7
|
}
|
package/dist/server/http.js
CHANGED
|
@@ -10,11 +10,13 @@ import { AppError } from "../shared/errors.js";
|
|
|
10
10
|
import { IMAGE_REQUEST_BYTES } from "../shared/images.js";
|
|
11
11
|
import * as schemas from "../shared/validation.js";
|
|
12
12
|
import { ConfigStore } from "./config-store.js";
|
|
13
|
+
import { projectPathPreview, resolveProjectPath } from "./project-paths.js";
|
|
13
14
|
import { createAuthenticator } from "./auth.js";
|
|
14
15
|
import { socketHandlers } from "./websocket.js";
|
|
15
16
|
import { z } from "zod";
|
|
16
17
|
import { releaseChannels } from "../shared/updates.js";
|
|
17
18
|
import { settingsSchema, updateRequestSchema } from "../updates/manager.js";
|
|
19
|
+
import { createReleaseNotesLookup, releaseNotesQuery } from "../updates/release-notes.js";
|
|
18
20
|
import { startShellSchema } from "../shared/shell.js";
|
|
19
21
|
function createApp({
|
|
20
22
|
config,
|
|
@@ -65,6 +67,11 @@ function createApp({
|
|
|
65
67
|
);
|
|
66
68
|
}
|
|
67
69
|
if (updates) {
|
|
70
|
+
const releaseNotes = createReleaseNotesLookup();
|
|
71
|
+
app.get("/api/updates/release-notes", async (c) => {
|
|
72
|
+
const { version, refresh } = releaseNotesQuery.parse(c.req.query());
|
|
73
|
+
return c.json(await releaseNotes(version, refresh === "1"));
|
|
74
|
+
});
|
|
68
75
|
app.get("/api/updates", async (c) => c.json(await updates.status()));
|
|
69
76
|
app.post("/api/updates/check", async (c) => {
|
|
70
77
|
const input = z.object({ channel: z.enum(releaseChannels).optional() }).strict().parse(await c.req.json());
|
|
@@ -88,6 +95,13 @@ function createApp({
|
|
|
88
95
|
};
|
|
89
96
|
const status = (op) => op.status === "dispatching" || op.status === "accepted" ? 202 : 200;
|
|
90
97
|
const configuration = new ConfigStore(config);
|
|
98
|
+
app.get("/api/project-path", (c) => {
|
|
99
|
+
const input = z.object({
|
|
100
|
+
path: z.string().max(4096).default(""),
|
|
101
|
+
directory: z.string().max(255).refine((value) => !/[\\/\0]/.test(value) && value !== "." && value !== "..").optional()
|
|
102
|
+
}).parse({ path: c.req.query("path"), directory: c.req.query("directory") });
|
|
103
|
+
return c.json(projectPathPreview(input.path.trim(), configuration.workDir, input.directory));
|
|
104
|
+
});
|
|
91
105
|
app.use("/api/config*", async (c, next) => {
|
|
92
106
|
c.header("Cache-Control", "no-store");
|
|
93
107
|
await next();
|
|
@@ -176,7 +190,13 @@ function createApp({
|
|
|
176
190
|
});
|
|
177
191
|
app.post(
|
|
178
192
|
"/api/project-clones",
|
|
179
|
-
async (c) => c.json(
|
|
193
|
+
async (c) => c.json(
|
|
194
|
+
service.cloneProject(
|
|
195
|
+
await parse(c, schemas.cloneProjectSchema),
|
|
196
|
+
(path) => resolveProjectPath(path, configuration.workDir)
|
|
197
|
+
),
|
|
198
|
+
202
|
|
199
|
+
)
|
|
180
200
|
);
|
|
181
201
|
app.get(
|
|
182
202
|
"/api/project-clones/:cloneId",
|
|
@@ -205,10 +225,12 @@ function createApp({
|
|
|
205
225
|
);
|
|
206
226
|
return c.json(op, status(op));
|
|
207
227
|
});
|
|
208
|
-
app.post(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
228
|
+
app.post("/api/projects", async (c) => {
|
|
229
|
+
const input = await parse(c, schemas.createProjectSchema);
|
|
230
|
+
return c.json(
|
|
231
|
+
service.addProject({ ...input, path: resolveProjectPath(input.path, configuration.workDir) })
|
|
232
|
+
);
|
|
233
|
+
});
|
|
212
234
|
app.post(
|
|
213
235
|
"/api/projects/:projectId/rename",
|
|
214
236
|
async (c) => c.json(
|
|
@@ -296,6 +318,12 @@ function createApp({
|
|
|
296
318
|
await service.getThreadMarkdown(id(c.req.param("threadId")), c.req.query("path"))
|
|
297
319
|
);
|
|
298
320
|
});
|
|
321
|
+
app.get("/api/threads/:threadId/text", async (c) => {
|
|
322
|
+
c.header("Cache-Control", "private, no-store");
|
|
323
|
+
c.header("X-Content-Type-Options", "nosniff");
|
|
324
|
+
c.header("Cross-Origin-Resource-Policy", "same-origin");
|
|
325
|
+
return c.json(await service.getThreadText(id(c.req.param("threadId")), c.req.query("path")));
|
|
326
|
+
});
|
|
299
327
|
app.get("/api/threads/:threadId/json", async (c) => {
|
|
300
328
|
c.header("Cache-Control", "private, no-store");
|
|
301
329
|
c.header("X-Content-Type-Options", "nosniff");
|
package/dist/server/http.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/server/http.ts"],
|
|
4
|
-
"sourcesContent": ["import type { BackupController } from '../shared/backups.js';\nimport { backupIdSchema } from '../shared/backups.js';\nimport { backupRequestSchema } from '../backups/manager.js';\nimport { createHash } from 'node:crypto';\nimport { Hono } from 'hono';\nimport { bodyLimit } from 'hono/body-limit';\nimport { serveStatic } from '@hono/node-server/serve-static';\nimport { upgradeWebSocket, type HttpBindings } from '@hono/node-server';\nimport type { ZodType } from 'zod';\nimport { ZodError } from 'zod';\nimport { AppError } from '../shared/errors.js';\nimport { IMAGE_REQUEST_BYTES } from '../shared/images.js';\nimport * as schemas from '../shared/validation.js';\nimport type { Operation } from '../shared/types.js';\nimport type { AppConfig } from './config.js';\nimport { ConfigStore } from './config-store.js';\nimport { createAuthenticator, type Authenticator, type Identity } from './auth.js';\nimport type { GatewayService } from './service.js';\nimport { socketHandlers } from './websocket.js';\nimport { z } from 'zod';\nimport { releaseChannels, type UpdateController } from '../shared/updates.js';\nimport { settingsSchema, updateRequestSchema } from '../updates/manager.js';\nimport { startShellSchema } from '../shared/shell.js';\nexport interface HttpDependencies {\n config: AppConfig;\n service: GatewayService;\n staticDir: string;\n authenticate?: Authenticator;\n updates?: UpdateController;\n backups?: BackupController;\n}\nexport function createApp({\n config,\n service,\n staticDir,\n authenticate = createAuthenticator(config),\n updates,\n backups,\n}: HttpDependencies) {\n const app = new Hono<{\n Bindings: HttpBindings;\n Variables: { identity: Identity; operationId?: string };\n }>();\n app.onError((error, c) => {\n const failure =\n error instanceof AppError\n ? error\n : error instanceof ZodError || error instanceof SyntaxError\n ? new AppError('VALIDATION_FAILED', 'Invalid request data', 400)\n : new AppError('INTERNAL_ERROR', 'An internal error occurred', 500);\n return c.json({ error: failure.toJSON(c.get('operationId')) }, failure.status as 400);\n });\n app.get('/api/health', (c) => c.json({ ok: true }));\n app.use('*', async (c, next) => {\n c.set('identity', await authenticate(c.req.raw, c.req.path === '/ws'));\n await next();\n });\n app.use('/api/*', (c, next) =>\n bodyLimit({\n maxSize: /^\\/api\\/threads\\/[^/]+\\/(turns|steer)$/.test(c.req.path)\n ? IMAGE_REQUEST_BYTES\n : 512 * 1024,\n onError: () => {\n throw new AppError('BODY_TOO_LARGE', 'Request body exceeds limit', 413);\n },\n })(c, next),\n );\n app.use('/api/*', async (c, next) => {\n c.header('Cache-Control', 'no-store');\n if (\n service.isInMaintenance() &&\n !['GET', 'HEAD', 'OPTIONS'].includes(c.req.method) &&\n !c.req.path.startsWith('/api/updates') &&\n !c.req.path.startsWith('/api/backups')\n )\n throw service.maintenanceError();\n await next();\n });\n if (backups) {\n app.get('/api/backups', async (c) => c.json(await backups.status()));\n app.post('/api/backups', async (c) =>\n c.json(await backups.start(backupRequestSchema.parse(await c.req.json())), 202),\n );\n app.post('/api/backups/:id/cancel', async (c) =>\n c.json(await backups.cancel(backupIdSchema.parse(c.req.param('id')))),\n );\n app.post('/api/backups/:id/verify', async (c) =>\n c.json(await backups.verify(backupIdSchema.parse(c.req.param('id')))),\n );\n }\n if (updates) {\n app.get('/api/updates', async (c) => c.json(await updates.status()));\n app.post('/api/updates/check', async (c) => {\n const input = z\n .object({ channel: z.enum(releaseChannels).optional() })\n .strict()\n .parse(await c.req.json());\n return c.json(await updates.check(input.channel));\n });\n app.post('/api/updates/settings', async (c) =>\n c.json(await updates.configure(settingsSchema.parse(await c.req.json()))),\n );\n app.post('/api/updates/run', async (c) =>\n c.json(await updates.start(updateRequestSchema.parse(await c.req.json())), 202),\n );\n }\n const id = (value: string) => schemas.idSchema.parse(value);\n const parse = async <T>(\n c: { req: { json: () => Promise<unknown> }; set: (key: 'operationId', value: string) => void },\n schema: ZodType<T>,\n ): Promise<T> => {\n const value: unknown = await c.req.json();\n if (\n value &&\n typeof value === 'object' &&\n 'clientOperationId' in value &&\n typeof value.clientOperationId === 'string'\n )\n c.set('operationId', value.clientOperationId);\n return schema.parse(value);\n };\n const status = (op: Operation) =>\n op.status === 'dispatching' || op.status === 'accepted' ? (202 as const) : (200 as const);\n const configuration = new ConfigStore(config);\n app.use('/api/config*', async (c, next) => {\n c.header('Cache-Control', 'no-store');\n await next();\n });\n app.get('/api/config', async (c) => c.json(await configuration.snapshot()));\n app.post('/api/config', async (c) => c.json(await configuration.save(await c.req.json())));\n app.post('/api/config/import', async (c) =>\n c.json(await configuration.previewImport(await c.req.json())),\n );\n app.get('/api/config/export', async (c) => {\n c.header('Content-Disposition', 'attachment; filename=\"codex-web-config.json\"');\n return c.json(await configuration.export());\n });\n app.get('/api/history-cache-context', async (c) => {\n const identity = c.get('identity');\n if (config.auth.mode !== 'local' && !identity.subject)\n throw new AppError('UNAUTHENTICATED', '\u65E0\u6CD5\u786E\u8BA4\u7F13\u5B58\u6240\u5C5E\u7528\u6237', 401);\n const projects = await service.listProjects();\n // A restart, user change or project/permission change creates a new namespace.\n // The subject is hashed on the server and is never exposed to browser storage.\n const scope = createHash('sha256')\n .update(\n JSON.stringify([\n service.events.serverEpoch,\n service.getSystem().codexEpoch,\n config.auth.mode,\n identity.subject ?? 'local',\n projects.map((project) => [project.id, project.rootPath, project.available]),\n ]),\n )\n .digest('hex');\n return c.json({ scope });\n });\n app.get('/api/system', (c) => c.json(service.getSystem()));\n app.get('/api/shell', (c) =>\n c.json(\n service.shell.snapshot(\n c.req.query('epoch'),\n z.coerce\n .number()\n .int()\n .min(0)\n .max(Number.MAX_SAFE_INTEGER)\n .default(0)\n .parse(c.req.query('after')),\n ),\n ),\n );\n app.post('/api/projects/:projectId/shell', async (c) => {\n const op = await service.startShell(\n id(c.req.param('projectId')),\n await parse(c, startShellSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/shell/:commandId/interrupt', async (c) => {\n const op = await service.interruptShell(\n id(c.req.param('commandId')),\n await parse(c, schemas.operationSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/system/compatibility/confirm', async (c) => {\n const op = await service.confirmCompatibility(\n await parse(c, schemas.confirmCompatibilitySchema),\n );\n return c.json(op, status(op));\n });\n app.get('/api/projects', async (c) => c.json({ data: await service.listProjects() }));\n app.get('/api/unassigned/runtime-settings', async (c) =>\n c.json(await service.readRuntimeSettings(null)),\n );\n app.post('/api/unassigned/model-defaults', async (c) => {\n const op = await service.saveModelDefaults(\n null,\n await parse(c, schemas.saveModelDefaultsSchema),\n );\n return c.json(op, status(op));\n });\n app.get('/api/unassigned/threads', async (c) =>\n c.json(\n await service.listThreadSummaries(\n null,\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.post('/api/unassigned/threads', async (c) => {\n const op = await service.createThread(null, await parse(c, schemas.createThreadSchema));\n return c.json(op, status(op));\n });\n app.post('/api/project-clones', async (c) =>\n c.json(service.cloneProject(await parse(c, schemas.cloneProjectSchema)), 202),\n );\n app.get('/api/project-clones/:cloneId', (c) =>\n c.json(service.projectClones.get(id(c.req.param('cloneId')))),\n );\n app.post('/api/project-clones/:cloneId/cancel', async (c) => {\n await parse(c, schemas.removeProjectSchema);\n return c.json(service.projectClones.cancel(id(c.req.param('cloneId'))));\n });\n app.post('/api/project-clones/:cloneId/register', async (c) => {\n await parse(c, schemas.removeProjectSchema);\n return c.json(service.projectClones.retryRegistration(id(c.req.param('cloneId'))));\n });\n app.post('/api/projects/reorder', async (c) =>\n c.json(service.reorderProjects(await parse(c, schemas.reorderProjectsSchema))),\n );\n app.get('/api/projects/:projectId/runtime-settings', async (c) =>\n c.json(await service.readRuntimeSettings(id(c.req.param('projectId')))),\n );\n app.post('/api/projects/:projectId/model-defaults', async (c) => {\n const op = await service.saveModelDefaults(\n id(c.req.param('projectId')),\n await parse(c, schemas.saveModelDefaultsSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/projects', async (c) =>\n c.json(service.addProject(await parse(c, schemas.createProjectSchema))),\n );\n app.post('/api/projects/:projectId/rename', async (c) =>\n c.json(\n service.renameProject(\n id(c.req.param('projectId')),\n await parse(c, schemas.renameProjectSchema),\n ),\n ),\n );\n app.post('/api/projects/:projectId/remove', async (c) => {\n await parse(c, schemas.removeProjectSchema);\n return c.json(service.removeProject(id(c.req.param('projectId'))));\n });\n app.get('/api/projects/:projectId/threads', async (c) =>\n c.json(\n await service.listThreadSummaries(\n id(c.req.param('projectId')),\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.get('/api/threads', async (c) =>\n c.json(\n await service.listThreads(\n id(c.req.query('projectId') ?? ''),\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.post('/api/threads/:threadId/archive', async (c) => {\n return c.json(\n await service.setThreadArchived(\n id(c.req.param('threadId')),\n await parse(c, schemas.archiveThreadSchema),\n ),\n );\n });\n app.get('/api/threads/:threadId', async (c) =>\n c.json(await service.getThread(id(c.req.param('threadId')))),\n );\n app.get('/api/threads/:threadId/history', async (c) =>\n c.json(await service.getThreadHistory(id(c.req.param('threadId')), c.req.query('cursor'))),\n );\n app.get('/api/threads/:threadId/metadata', async (c) =>\n c.json(await service.getThreadMetadata(id(c.req.param('threadId')))),\n );\n app.get('/api/threads/:threadId/export', async (c) => {\n const exported = await service.exportThread(id(c.req.param('threadId')));\n c.header('Content-Type', 'text/markdown; charset=utf-8');\n c.header('Content-Disposition', `attachment; filename=\"${exported.filename}\"`);\n c.header('Cache-Control', 'no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('X-Export-Completeness', exported.partial ? 'partial' : 'saved-history');\n return c.body(exported.markdown);\n });\n app.get('/api/threads/:threadId/images', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n c.header('Content-Security-Policy', \"default-src 'none'; sandbox\");\n const image = await service.getThreadImage(id(c.req.param('threadId')), c.req.query('path'));\n // Hash the validated bytes so same-size overwrites with preserved mtimes also invalidate.\n const etag = `\"${createHash('sha256').update(image.bytes).digest('hex')}\"`;\n c.header('Cache-Control', 'private, no-cache');\n c.header('ETag', etag);\n const ifNoneMatch = c.req.header('If-None-Match');\n if (\n ifNoneMatch === '*' ||\n ifNoneMatch?.split(',').some((tag) => tag.trim().replace(/^W\\//, '') === etag)\n )\n return c.body(null, 304);\n return c.body(image.bytes, 200, {\n 'Content-Type': image.contentType,\n 'Content-Length': String(image.bytes.length),\n });\n });\n app.get('/api/threads/:threadId/markdown', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n return c.json(\n await service.getThreadMarkdown(id(c.req.param('threadId')), c.req.query('path')),\n );\n });\n app.get('/api/threads/:threadId/json', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n return c.json(await service.getThreadJson(id(c.req.param('threadId')), c.req.query('path')));\n });\n app.get('/api/operations/:operationId', (c) =>\n c.json(service.getOperation(id(c.req.param('operationId')))),\n );\n app.post('/api/projects/:projectId/threads', async (c) => {\n const op = await service.createThread(\n id(c.req.param('projectId')),\n await parse(c, schemas.createThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/resume', async (c) => {\n const op = await service.resumeThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.resumeThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/fork', async (c) => {\n const op = await service.forkThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.forkThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/rename', async (c) => {\n const op = await service.renameThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.renameThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/turns', async (c) => {\n const op = await service.startTurn(\n id(c.req.param('threadId')),\n await parse(c, schemas.startTurnSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/review', async (c) => {\n const op = await service.startReview(\n id(c.req.param('threadId')),\n await parse(c, schemas.startReviewSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/steer', async (c) => {\n const op = await service.steer(\n id(c.req.param('threadId')),\n await parse(c, schemas.steerSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/compact', async (c) => {\n const op = await service.compactThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.operationSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/interrupt', async (c) => {\n const op = await service.interrupt(\n id(c.req.param('threadId')),\n await parse(c, schemas.interruptSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/actions/:actionId/resolve', async (c) => {\n const op = await service.resolveAction(\n id(c.req.param('actionId')),\n await parse(c, schemas.resolveActionSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/operations/:operationId/reconcile', async (c) => {\n const op = await service.reconcileOperation(\n id(c.req.param('operationId')),\n await parse(c, schemas.reconcileSchema),\n );\n return c.json(op, status(op));\n });\n app.get(\n '/ws',\n upgradeWebSocket((c) =>\n socketHandlers(service, config, c.get('identity'), c.req.query('overview') === '1'),\n ),\n );\n app.all('/api/*', () => {\n throw new AppError('NOT_FOUND', 'API route not found', 404);\n });\n app.all('/api', () => {\n throw new AppError('NOT_FOUND', 'API route not found', 404);\n });\n app.all('/ws/*', () => {\n throw new AppError('NOT_FOUND', 'WebSocket route not found', 404);\n });\n app.use('*', async (c, next) => {\n if (!c.req.path.startsWith('/assets/')) c.header('Cache-Control', 'no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Referrer-Policy', 'same-origin');\n c.header(\n 'Content-Security-Policy',\n // Remote Markdown images load directly in the browser, including HTTPS redirects.\n // Keep scripts, connections and all other resource policies restricted as before.\n \"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'\",\n );\n await next();\n });\n app.get('*', serveStatic({ root: staticDir }));\n app.get('*', async (c, next) => {\n if (!c.req.header('accept')?.includes('text/html') || /\\.[^/]+$/.test(c.req.path))\n return c.json({ error: { code: 'NOT_FOUND', message: 'Resource not found' } }, 404);\n return serveStatic({ path: `${staticDir}/index.html` })(c, next);\n });\n app.notFound((c) => c.json({ error: { code: 'NOT_FOUND', message: 'Resource not found' } }, 404));\n return app;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { BackupController } from '../shared/backups.js';\nimport { backupIdSchema } from '../shared/backups.js';\nimport { backupRequestSchema } from '../backups/manager.js';\nimport { createHash } from 'node:crypto';\nimport { Hono } from 'hono';\nimport { bodyLimit } from 'hono/body-limit';\nimport { serveStatic } from '@hono/node-server/serve-static';\nimport { upgradeWebSocket, type HttpBindings } from '@hono/node-server';\nimport type { ZodType } from 'zod';\nimport { ZodError } from 'zod';\nimport { AppError } from '../shared/errors.js';\nimport { IMAGE_REQUEST_BYTES } from '../shared/images.js';\nimport * as schemas from '../shared/validation.js';\nimport type { Operation } from '../shared/types.js';\nimport type { AppConfig } from './config.js';\nimport { ConfigStore } from './config-store.js';\nimport { projectPathPreview, resolveProjectPath } from './project-paths.js';\nimport { createAuthenticator, type Authenticator, type Identity } from './auth.js';\nimport type { GatewayService } from './service.js';\nimport { socketHandlers } from './websocket.js';\nimport { z } from 'zod';\nimport { releaseChannels, type UpdateController } from '../shared/updates.js';\nimport { settingsSchema, updateRequestSchema } from '../updates/manager.js';\nimport { createReleaseNotesLookup, releaseNotesQuery } from '../updates/release-notes.js';\nimport { startShellSchema } from '../shared/shell.js';\nexport interface HttpDependencies {\n config: AppConfig;\n service: GatewayService;\n staticDir: string;\n authenticate?: Authenticator;\n updates?: UpdateController;\n backups?: BackupController;\n}\nexport function createApp({\n config,\n service,\n staticDir,\n authenticate = createAuthenticator(config),\n updates,\n backups,\n}: HttpDependencies) {\n const app = new Hono<{\n Bindings: HttpBindings;\n Variables: { identity: Identity; operationId?: string };\n }>();\n app.onError((error, c) => {\n const failure =\n error instanceof AppError\n ? error\n : error instanceof ZodError || error instanceof SyntaxError\n ? new AppError('VALIDATION_FAILED', 'Invalid request data', 400)\n : new AppError('INTERNAL_ERROR', 'An internal error occurred', 500);\n return c.json({ error: failure.toJSON(c.get('operationId')) }, failure.status as 400);\n });\n app.get('/api/health', (c) => c.json({ ok: true }));\n app.use('*', async (c, next) => {\n c.set('identity', await authenticate(c.req.raw, c.req.path === '/ws'));\n await next();\n });\n app.use('/api/*', (c, next) =>\n bodyLimit({\n maxSize: /^\\/api\\/threads\\/[^/]+\\/(turns|steer)$/.test(c.req.path)\n ? IMAGE_REQUEST_BYTES\n : 512 * 1024,\n onError: () => {\n throw new AppError('BODY_TOO_LARGE', 'Request body exceeds limit', 413);\n },\n })(c, next),\n );\n app.use('/api/*', async (c, next) => {\n c.header('Cache-Control', 'no-store');\n if (\n service.isInMaintenance() &&\n !['GET', 'HEAD', 'OPTIONS'].includes(c.req.method) &&\n !c.req.path.startsWith('/api/updates') &&\n !c.req.path.startsWith('/api/backups')\n )\n throw service.maintenanceError();\n await next();\n });\n if (backups) {\n app.get('/api/backups', async (c) => c.json(await backups.status()));\n app.post('/api/backups', async (c) =>\n c.json(await backups.start(backupRequestSchema.parse(await c.req.json())), 202),\n );\n app.post('/api/backups/:id/cancel', async (c) =>\n c.json(await backups.cancel(backupIdSchema.parse(c.req.param('id')))),\n );\n app.post('/api/backups/:id/verify', async (c) =>\n c.json(await backups.verify(backupIdSchema.parse(c.req.param('id')))),\n );\n }\n if (updates) {\n const releaseNotes = createReleaseNotesLookup();\n app.get('/api/updates/release-notes', async (c) => {\n const { version, refresh } = releaseNotesQuery.parse(c.req.query());\n return c.json(await releaseNotes(version, refresh === '1'));\n });\n app.get('/api/updates', async (c) => c.json(await updates.status()));\n app.post('/api/updates/check', async (c) => {\n const input = z\n .object({ channel: z.enum(releaseChannels).optional() })\n .strict()\n .parse(await c.req.json());\n return c.json(await updates.check(input.channel));\n });\n app.post('/api/updates/settings', async (c) =>\n c.json(await updates.configure(settingsSchema.parse(await c.req.json()))),\n );\n app.post('/api/updates/run', async (c) =>\n c.json(await updates.start(updateRequestSchema.parse(await c.req.json())), 202),\n );\n }\n const id = (value: string) => schemas.idSchema.parse(value);\n const parse = async <T>(\n c: { req: { json: () => Promise<unknown> }; set: (key: 'operationId', value: string) => void },\n schema: ZodType<T>,\n ): Promise<T> => {\n const value: unknown = await c.req.json();\n if (\n value &&\n typeof value === 'object' &&\n 'clientOperationId' in value &&\n typeof value.clientOperationId === 'string'\n )\n c.set('operationId', value.clientOperationId);\n return schema.parse(value);\n };\n const status = (op: Operation) =>\n op.status === 'dispatching' || op.status === 'accepted' ? (202 as const) : (200 as const);\n const configuration = new ConfigStore(config);\n app.get('/api/project-path', (c) => {\n const input = z\n .object({\n path: z.string().max(4096).default(''),\n directory: z\n .string()\n .max(255)\n .refine((value) => !/[\\\\/\\0]/.test(value) && value !== '.' && value !== '..')\n .optional(),\n })\n .parse({ path: c.req.query('path'), directory: c.req.query('directory') });\n return c.json(projectPathPreview(input.path.trim(), configuration.workDir, input.directory));\n });\n app.use('/api/config*', async (c, next) => {\n c.header('Cache-Control', 'no-store');\n await next();\n });\n app.get('/api/config', async (c) => c.json(await configuration.snapshot()));\n app.post('/api/config', async (c) => c.json(await configuration.save(await c.req.json())));\n app.post('/api/config/import', async (c) =>\n c.json(await configuration.previewImport(await c.req.json())),\n );\n app.get('/api/config/export', async (c) => {\n c.header('Content-Disposition', 'attachment; filename=\"codex-web-config.json\"');\n return c.json(await configuration.export());\n });\n app.get('/api/history-cache-context', async (c) => {\n const identity = c.get('identity');\n if (config.auth.mode !== 'local' && !identity.subject)\n throw new AppError('UNAUTHENTICATED', '\u65E0\u6CD5\u786E\u8BA4\u7F13\u5B58\u6240\u5C5E\u7528\u6237', 401);\n const projects = await service.listProjects();\n // A restart, user change or project/permission change creates a new namespace.\n // The subject is hashed on the server and is never exposed to browser storage.\n const scope = createHash('sha256')\n .update(\n JSON.stringify([\n service.events.serverEpoch,\n service.getSystem().codexEpoch,\n config.auth.mode,\n identity.subject ?? 'local',\n projects.map((project) => [project.id, project.rootPath, project.available]),\n ]),\n )\n .digest('hex');\n return c.json({ scope });\n });\n app.get('/api/system', (c) => c.json(service.getSystem()));\n app.get('/api/shell', (c) =>\n c.json(\n service.shell.snapshot(\n c.req.query('epoch'),\n z.coerce\n .number()\n .int()\n .min(0)\n .max(Number.MAX_SAFE_INTEGER)\n .default(0)\n .parse(c.req.query('after')),\n ),\n ),\n );\n app.post('/api/projects/:projectId/shell', async (c) => {\n const op = await service.startShell(\n id(c.req.param('projectId')),\n await parse(c, startShellSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/shell/:commandId/interrupt', async (c) => {\n const op = await service.interruptShell(\n id(c.req.param('commandId')),\n await parse(c, schemas.operationSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/system/compatibility/confirm', async (c) => {\n const op = await service.confirmCompatibility(\n await parse(c, schemas.confirmCompatibilitySchema),\n );\n return c.json(op, status(op));\n });\n app.get('/api/projects', async (c) => c.json({ data: await service.listProjects() }));\n app.get('/api/unassigned/runtime-settings', async (c) =>\n c.json(await service.readRuntimeSettings(null)),\n );\n app.post('/api/unassigned/model-defaults', async (c) => {\n const op = await service.saveModelDefaults(\n null,\n await parse(c, schemas.saveModelDefaultsSchema),\n );\n return c.json(op, status(op));\n });\n app.get('/api/unassigned/threads', async (c) =>\n c.json(\n await service.listThreadSummaries(\n null,\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.post('/api/unassigned/threads', async (c) => {\n const op = await service.createThread(null, await parse(c, schemas.createThreadSchema));\n return c.json(op, status(op));\n });\n app.post('/api/project-clones', async (c) =>\n c.json(\n service.cloneProject(await parse(c, schemas.cloneProjectSchema), (path) =>\n resolveProjectPath(path, configuration.workDir),\n ),\n 202,\n ),\n );\n app.get('/api/project-clones/:cloneId', (c) =>\n c.json(service.projectClones.get(id(c.req.param('cloneId')))),\n );\n app.post('/api/project-clones/:cloneId/cancel', async (c) => {\n await parse(c, schemas.removeProjectSchema);\n return c.json(service.projectClones.cancel(id(c.req.param('cloneId'))));\n });\n app.post('/api/project-clones/:cloneId/register', async (c) => {\n await parse(c, schemas.removeProjectSchema);\n return c.json(service.projectClones.retryRegistration(id(c.req.param('cloneId'))));\n });\n app.post('/api/projects/reorder', async (c) =>\n c.json(service.reorderProjects(await parse(c, schemas.reorderProjectsSchema))),\n );\n app.get('/api/projects/:projectId/runtime-settings', async (c) =>\n c.json(await service.readRuntimeSettings(id(c.req.param('projectId')))),\n );\n app.post('/api/projects/:projectId/model-defaults', async (c) => {\n const op = await service.saveModelDefaults(\n id(c.req.param('projectId')),\n await parse(c, schemas.saveModelDefaultsSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/projects', async (c) => {\n const input = await parse(c, schemas.createProjectSchema);\n return c.json(\n service.addProject({ ...input, path: resolveProjectPath(input.path, configuration.workDir) }),\n );\n });\n app.post('/api/projects/:projectId/rename', async (c) =>\n c.json(\n service.renameProject(\n id(c.req.param('projectId')),\n await parse(c, schemas.renameProjectSchema),\n ),\n ),\n );\n app.post('/api/projects/:projectId/remove', async (c) => {\n await parse(c, schemas.removeProjectSchema);\n return c.json(service.removeProject(id(c.req.param('projectId'))));\n });\n app.get('/api/projects/:projectId/threads', async (c) =>\n c.json(\n await service.listThreadSummaries(\n id(c.req.param('projectId')),\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.get('/api/threads', async (c) =>\n c.json(\n await service.listThreads(\n id(c.req.query('projectId') ?? ''),\n c.req.query('cursor') === undefined\n ? undefined\n : schemas.cursorSchema.parse(c.req.query('cursor')),\n schemas.archivedQuerySchema.parse(c.req.query('archived')),\n ),\n ),\n );\n app.post('/api/threads/:threadId/archive', async (c) => {\n return c.json(\n await service.setThreadArchived(\n id(c.req.param('threadId')),\n await parse(c, schemas.archiveThreadSchema),\n ),\n );\n });\n app.get('/api/threads/:threadId', async (c) =>\n c.json(await service.getThread(id(c.req.param('threadId')))),\n );\n app.get('/api/threads/:threadId/history', async (c) =>\n c.json(await service.getThreadHistory(id(c.req.param('threadId')), c.req.query('cursor'))),\n );\n app.get('/api/threads/:threadId/metadata', async (c) =>\n c.json(await service.getThreadMetadata(id(c.req.param('threadId')))),\n );\n app.get('/api/threads/:threadId/export', async (c) => {\n const exported = await service.exportThread(id(c.req.param('threadId')));\n c.header('Content-Type', 'text/markdown; charset=utf-8');\n c.header('Content-Disposition', `attachment; filename=\"${exported.filename}\"`);\n c.header('Cache-Control', 'no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('X-Export-Completeness', exported.partial ? 'partial' : 'saved-history');\n return c.body(exported.markdown);\n });\n app.get('/api/threads/:threadId/images', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n c.header('Content-Security-Policy', \"default-src 'none'; sandbox\");\n const image = await service.getThreadImage(id(c.req.param('threadId')), c.req.query('path'));\n // Hash the validated bytes so same-size overwrites with preserved mtimes also invalidate.\n const etag = `\"${createHash('sha256').update(image.bytes).digest('hex')}\"`;\n c.header('Cache-Control', 'private, no-cache');\n c.header('ETag', etag);\n const ifNoneMatch = c.req.header('If-None-Match');\n if (\n ifNoneMatch === '*' ||\n ifNoneMatch?.split(',').some((tag) => tag.trim().replace(/^W\\//, '') === etag)\n )\n return c.body(null, 304);\n return c.body(image.bytes, 200, {\n 'Content-Type': image.contentType,\n 'Content-Length': String(image.bytes.length),\n });\n });\n app.get('/api/threads/:threadId/markdown', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n return c.json(\n await service.getThreadMarkdown(id(c.req.param('threadId')), c.req.query('path')),\n );\n });\n app.get('/api/threads/:threadId/text', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n return c.json(await service.getThreadText(id(c.req.param('threadId')), c.req.query('path')));\n });\n app.get('/api/threads/:threadId/json', async (c) => {\n c.header('Cache-Control', 'private, no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Cross-Origin-Resource-Policy', 'same-origin');\n return c.json(await service.getThreadJson(id(c.req.param('threadId')), c.req.query('path')));\n });\n app.get('/api/operations/:operationId', (c) =>\n c.json(service.getOperation(id(c.req.param('operationId')))),\n );\n app.post('/api/projects/:projectId/threads', async (c) => {\n const op = await service.createThread(\n id(c.req.param('projectId')),\n await parse(c, schemas.createThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/resume', async (c) => {\n const op = await service.resumeThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.resumeThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/fork', async (c) => {\n const op = await service.forkThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.forkThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/rename', async (c) => {\n const op = await service.renameThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.renameThreadSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/turns', async (c) => {\n const op = await service.startTurn(\n id(c.req.param('threadId')),\n await parse(c, schemas.startTurnSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/review', async (c) => {\n const op = await service.startReview(\n id(c.req.param('threadId')),\n await parse(c, schemas.startReviewSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/steer', async (c) => {\n const op = await service.steer(\n id(c.req.param('threadId')),\n await parse(c, schemas.steerSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/compact', async (c) => {\n const op = await service.compactThread(\n id(c.req.param('threadId')),\n await parse(c, schemas.operationSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/threads/:threadId/interrupt', async (c) => {\n const op = await service.interrupt(\n id(c.req.param('threadId')),\n await parse(c, schemas.interruptSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/actions/:actionId/resolve', async (c) => {\n const op = await service.resolveAction(\n id(c.req.param('actionId')),\n await parse(c, schemas.resolveActionSchema),\n );\n return c.json(op, status(op));\n });\n app.post('/api/operations/:operationId/reconcile', async (c) => {\n const op = await service.reconcileOperation(\n id(c.req.param('operationId')),\n await parse(c, schemas.reconcileSchema),\n );\n return c.json(op, status(op));\n });\n app.get(\n '/ws',\n upgradeWebSocket((c) =>\n socketHandlers(service, config, c.get('identity'), c.req.query('overview') === '1'),\n ),\n );\n app.all('/api/*', () => {\n throw new AppError('NOT_FOUND', 'API route not found', 404);\n });\n app.all('/api', () => {\n throw new AppError('NOT_FOUND', 'API route not found', 404);\n });\n app.all('/ws/*', () => {\n throw new AppError('NOT_FOUND', 'WebSocket route not found', 404);\n });\n app.use('*', async (c, next) => {\n if (!c.req.path.startsWith('/assets/')) c.header('Cache-Control', 'no-store');\n c.header('X-Content-Type-Options', 'nosniff');\n c.header('Referrer-Policy', 'same-origin');\n c.header(\n 'Content-Security-Policy',\n // Remote Markdown images load directly in the browser, including HTTPS redirects.\n // Keep scripts, connections and all other resource policies restricted as before.\n \"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'\",\n );\n await next();\n });\n app.get('*', serveStatic({ root: staticDir }));\n app.get('*', async (c, next) => {\n if (!c.req.header('accept')?.includes('text/html') || /\\.[^/]+$/.test(c.req.path))\n return c.json({ error: { code: 'NOT_FOUND', message: 'Resource not found' } }, 404);\n return serveStatic({ path: `${staticDir}/index.html` })(c, next);\n });\n app.notFound((c) => c.json({ error: { code: 'NOT_FOUND', message: 'Resource not found' } }, 404));\n return app;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/server/local-files.ts"],
|
|
4
|
-
"sourcesContent": ["import { constants, realpathSync, statSync } from 'node:fs';\nimport { open, realpath, stat } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, parse, resolve } from 'node:path';\nimport { AppError } from '../shared/errors.js';\nimport { containsPath } from './projects.js';\n\nexport interface FileRoot {\n canonicalRoot: string;\n aliases: string[];\n}\n\nexport interface FileScope {\n cwd: string;\n roots: readonly FileRoot[];\n excludedRoots?: readonly string[];\n}\n\n/** Capture server-owned roots once; clients cannot nominate a temporary directory. */\nexport function temporaryFileRoots(): FileRoot[] {\n const roots = new Map<string, FileRoot>();\n for (const directory of [tmpdir(), ...(process.platform === 'win32' ? [] : ['/tmp'])]) {\n if (!isAbsolute(directory)) continue;\n const alias = resolve(directory);\n if (alias === parse(alias).root) continue;\n try {\n const canonicalRoot = realpathSync(alias);\n if (canonicalRoot === parse(canonicalRoot).root || !statSync(canonicalRoot).isDirectory())\n continue;\n const root = roots.get(canonicalRoot) ?? { canonicalRoot, aliases: [] };\n if (!root.aliases.includes(alias)) root.aliases.push(alias);\n roots.set(canonicalRoot, root);\n } catch {\n // Unavailable temp locations do not prevent previews in the remaining roots.\n }\n }\n return [...roots.values()];\n}\n\ninterface FilePolicy {\n code: 'IMAGE' | 'MARKDOWN' | 'JSON';\n label: string;\n maxBytes: number;\n sizeError: string;\n validatePath: (path: string) => void;\n}\n\nfunction outsideProject(policy: FilePolicy): AppError {\n return new AppError(\n `${policy.code}_OUTSIDE_PROJECT`,\n `\u53EA\u80FD\u9884\u89C8\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u6216\u5141\u8BB8\u7684\u670D\u52A1\u5668\u4E34\u65F6\u76EE\u5F55\u5185\u7684${policy.label}`,\n 403,\n );\n}\n\nfunction changedFile(policy: FilePolicy): AppError {\n return new AppError(`${policy.code}_CHANGED`, `${policy.label}\u6587\u4EF6\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u9884\u89C8`, 409);\n}\n\n/** Shared project/temp authorization and bounded, verified file reads for previews. */\nexport async function readLocalFile(\n path: string | undefined,\n scope: FileScope,\n policy: FilePolicy,\n) {\n if (\n !path ||\n path.length > 4096 ||\n [...path].some(\n (character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127,\n ) ||\n /^[a-z][a-z\\d+.-]*:/i.test(path) ||\n path.startsWith('//') ||\n path.startsWith('\\\\\\\\')\n )\n throw new AppError(`INVALID_${policy.code}_PATH`, `${policy.label}\u8DEF\u5F84\u65E0\u6548`, 400);\n\n const candidate = resolve(scope.cwd, path);\n // Both spellings matter on systems where /tmp or the registered directory is an alias.\n if (\n !scope.roots.some(\n (root) =>\n containsPath(root.canonicalRoot, candidate) ||\n root.aliases.some((alias) => containsPath(alias, candidate)),\n )\n )\n throw outsideProject(policy);\n const isAllowed = (path: string) =>\n !scope.excludedRoots?.some((root) => containsPath(root, path)) &&\n scope.roots.some((root) => containsPath(root.canonicalRoot, path));\n policy.validatePath(candidate);\n\n try {\n const canonical = await realpath(candidate);\n if (!isAllowed(canonical)) throw outsideProject(policy);\n // NOFOLLOW rejects a last-component symlink swapped in after realpath. NONBLOCK\n // ensures opening a substituted FIFO cannot leave the request waiting forever.\n const file = await open(\n canonical,\n constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,\n );\n try {\n const before = await file.stat();\n if (!before.isFile())\n throw new AppError(`${policy.code}_NOT_FILE`, `${policy.label}\u8DEF\u5F84\u4E0D\u662F\u666E\u901A\u6587\u4EF6`, 415);\n if (before.size > policy.maxBytes)\n throw new AppError(`${policy.code}_TOO_LARGE`, policy.sizeError, 413);\n\n // Recheck the path and opened inode to detect ordinary concurrent replacements.\n // Node's portable file API cannot make parent-directory traversal race-free.\n const verifyPath = async () => {\n const current = await realpath(candidate);\n if (!isAllowed(current)) throw outsideProject(policy);\n if (current !== canonical) throw changedFile(policy);\n const currentStat = await stat(current);\n if (currentStat.dev !== before.dev || currentStat.ino !== before.ino)\n throw changedFile(policy);\n };\n await verifyPath();\n // One extra byte detects growth without letting readFile allocate unbounded data.\n const data = Buffer.alloc(before.size + 1);\n let length = 0;\n while (length < data.length) {\n const { bytesRead } = await file.read(data, length, data.length - length, length);\n if (!bytesRead) break;\n length += bytesRead;\n }\n if (length > policy.maxBytes)\n throw new AppError(`${policy.code}_TOO_LARGE`, policy.sizeError, 413);\n const after = await file.stat();\n if (length !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs)\n throw changedFile(policy);\n await verifyPath();\n const bytes = data.subarray(0, length);\n return { bytes, path: canonical };\n } finally {\n await file.close();\n }\n } catch (error) {\n if (error instanceof AppError) throw error;\n const code = (error as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ENOTDIR')\n throw new AppError(`${policy.code}_NOT_FOUND`, `${policy.label}\u6587\u4EF6\u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u5220\u9664`, 404);\n if (code === 'ELOOP') throw outsideProject(policy);\n throw new AppError(`${policy.code}_UNREADABLE`, `${policy.label}\u6587\u4EF6\u4E0D\u53EF\u8BFB\u53D6`, 403);\n }\n}\n"],
|
|
4
|
+
"sourcesContent": ["import { constants, realpathSync, statSync } from 'node:fs';\nimport { open, realpath, stat } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, parse, resolve } from 'node:path';\nimport { AppError } from '../shared/errors.js';\nimport { containsPath } from './projects.js';\n\nexport interface FileRoot {\n canonicalRoot: string;\n aliases: string[];\n}\n\nexport interface FileScope {\n cwd: string;\n roots: readonly FileRoot[];\n excludedRoots?: readonly string[];\n}\n\n/** Capture server-owned roots once; clients cannot nominate a temporary directory. */\nexport function temporaryFileRoots(): FileRoot[] {\n const roots = new Map<string, FileRoot>();\n for (const directory of [tmpdir(), ...(process.platform === 'win32' ? [] : ['/tmp'])]) {\n if (!isAbsolute(directory)) continue;\n const alias = resolve(directory);\n if (alias === parse(alias).root) continue;\n try {\n const canonicalRoot = realpathSync(alias);\n if (canonicalRoot === parse(canonicalRoot).root || !statSync(canonicalRoot).isDirectory())\n continue;\n const root = roots.get(canonicalRoot) ?? { canonicalRoot, aliases: [] };\n if (!root.aliases.includes(alias)) root.aliases.push(alias);\n roots.set(canonicalRoot, root);\n } catch {\n // Unavailable temp locations do not prevent previews in the remaining roots.\n }\n }\n return [...roots.values()];\n}\n\ninterface FilePolicy {\n code: 'IMAGE' | 'MARKDOWN' | 'JSON' | 'TEXT';\n label: string;\n maxBytes: number;\n sizeError: string;\n validatePath: (path: string) => void;\n}\n\nfunction outsideProject(policy: FilePolicy): AppError {\n return new AppError(\n `${policy.code}_OUTSIDE_PROJECT`,\n `\u53EA\u80FD\u9884\u89C8\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u6216\u5141\u8BB8\u7684\u670D\u52A1\u5668\u4E34\u65F6\u76EE\u5F55\u5185\u7684${policy.label}`,\n 403,\n );\n}\n\nfunction changedFile(policy: FilePolicy): AppError {\n return new AppError(`${policy.code}_CHANGED`, `${policy.label}\u6587\u4EF6\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u9884\u89C8`, 409);\n}\n\n/** Shared project/temp authorization and bounded, verified file reads for previews. */\nexport async function readLocalFile(\n path: string | undefined,\n scope: FileScope,\n policy: FilePolicy,\n) {\n if (\n !path ||\n path.length > 4096 ||\n [...path].some(\n (character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127,\n ) ||\n /^[a-z][a-z\\d+.-]*:/i.test(path) ||\n path.startsWith('//') ||\n path.startsWith('\\\\\\\\')\n )\n throw new AppError(`INVALID_${policy.code}_PATH`, `${policy.label}\u8DEF\u5F84\u65E0\u6548`, 400);\n\n const candidate = resolve(scope.cwd, path);\n // Both spellings matter on systems where /tmp or the registered directory is an alias.\n if (\n !scope.roots.some(\n (root) =>\n containsPath(root.canonicalRoot, candidate) ||\n root.aliases.some((alias) => containsPath(alias, candidate)),\n )\n )\n throw outsideProject(policy);\n const isAllowed = (path: string) =>\n !scope.excludedRoots?.some((root) => containsPath(root, path)) &&\n scope.roots.some((root) => containsPath(root.canonicalRoot, path));\n policy.validatePath(candidate);\n\n try {\n const canonical = await realpath(candidate);\n if (!isAllowed(canonical)) throw outsideProject(policy);\n // NOFOLLOW rejects a last-component symlink swapped in after realpath. NONBLOCK\n // ensures opening a substituted FIFO cannot leave the request waiting forever.\n const file = await open(\n canonical,\n constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,\n );\n try {\n const before = await file.stat();\n if (!before.isFile())\n throw new AppError(`${policy.code}_NOT_FILE`, `${policy.label}\u8DEF\u5F84\u4E0D\u662F\u666E\u901A\u6587\u4EF6`, 415);\n if (before.size > policy.maxBytes)\n throw new AppError(`${policy.code}_TOO_LARGE`, policy.sizeError, 413);\n\n // Recheck the path and opened inode to detect ordinary concurrent replacements.\n // Node's portable file API cannot make parent-directory traversal race-free.\n const verifyPath = async () => {\n const current = await realpath(candidate);\n if (!isAllowed(current)) throw outsideProject(policy);\n if (current !== canonical) throw changedFile(policy);\n const currentStat = await stat(current);\n if (currentStat.dev !== before.dev || currentStat.ino !== before.ino)\n throw changedFile(policy);\n };\n await verifyPath();\n // One extra byte detects growth without letting readFile allocate unbounded data.\n const data = Buffer.alloc(before.size + 1);\n let length = 0;\n while (length < data.length) {\n const { bytesRead } = await file.read(data, length, data.length - length, length);\n if (!bytesRead) break;\n length += bytesRead;\n }\n if (length > policy.maxBytes)\n throw new AppError(`${policy.code}_TOO_LARGE`, policy.sizeError, 413);\n const after = await file.stat();\n if (length !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs)\n throw changedFile(policy);\n await verifyPath();\n const bytes = data.subarray(0, length);\n return { bytes, path: canonical };\n } finally {\n await file.close();\n }\n } catch (error) {\n if (error instanceof AppError) throw error;\n const code = (error as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ENOTDIR')\n throw new AppError(`${policy.code}_NOT_FOUND`, `${policy.label}\u6587\u4EF6\u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u5220\u9664`, 404);\n if (code === 'ELOOP') throw outsideProject(policy);\n throw new AppError(`${policy.code}_UNREADABLE`, `${policy.label}\u6587\u4EF6\u4E0D\u53EF\u8BFB\u53D6`, 403);\n }\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["path"]
|
|
7
7
|
}
|