@muyichengshayu/promptx 0.1.48 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.en.md +68 -27
  3. package/README.md +67 -26
  4. package/apps/runner/src/engines/index.js +13 -3
  5. package/apps/runner/src/engines/shellRunner.js +216 -0
  6. package/apps/runner/src/runManager.js +68 -9
  7. package/apps/server/src/agents/index.js +18 -3
  8. package/apps/server/src/codexRoutes.js +36 -8
  9. package/apps/server/src/codexRuns.js +24 -7
  10. package/apps/server/src/codexSessions.js +273 -37
  11. package/apps/server/src/db.js +2 -0
  12. package/apps/server/src/gitDiff.js +279 -17
  13. package/apps/server/src/index.js +22 -13
  14. package/apps/server/src/internalRoutes.js +8 -1
  15. package/apps/server/src/relayClient.js +5 -1
  16. package/apps/server/src/relayConfig.js +10 -0
  17. package/apps/server/src/runDispatchService.js +63 -16
  18. package/apps/server/src/runEventIngest.js +50 -8
  19. package/apps/server/src/taskRoutes.js +73 -0
  20. package/apps/web/dist/assets/CodexSessionManagerDialog-Cprbem1C.js +3 -0
  21. package/apps/web/dist/assets/{TaskDiffReviewDialog-Dl_-f3f3.js → TaskDiffReviewDialog-Bxxkkdld.js} +1 -1
  22. package/apps/web/dist/assets/WorkbenchSettingsDialog-B046sue0.js +1 -0
  23. package/apps/web/dist/assets/WorkbenchView-BbyYTwTa.js +60 -0
  24. package/apps/web/dist/assets/index-BS3I37dk.js +2 -0
  25. package/apps/web/dist/assets/index-CTHBQ5Ng.css +1 -0
  26. package/apps/web/dist/assets/{vendor-ui-C5E3MVzU.js → vendor-ui-BwEQCho1.js} +1 -1
  27. package/apps/web/dist/index.html +2 -2
  28. package/docs/assets/readme-diff-wechat.png +0 -0
  29. package/docs/assets/readme-execution-focus-wechat.png +0 -0
  30. package/docs/assets/readme-mobile-wechat.png +0 -0
  31. package/docs/assets/readme-project-manager-wechat.png +0 -0
  32. package/docs/assets/readme-settings-system-wechat.png +0 -0
  33. package/docs/assets/readme-settings-theme-wechat.png +0 -0
  34. package/docs/assets/readme-source-browser-wechat.png +0 -0
  35. package/docs/assets/readme-workbench-wechat.png +0 -0
  36. package/package.json +21 -14
  37. package/packages/shared/src/index.js +6 -0
  38. package/packages/shared/src/shellCommands.js +81 -0
  39. package/packages/shared/src/shellCommands.test.js +45 -0
  40. package/apps/runner/src/engines/claudeCodeRunner.test.js +0 -181
  41. package/apps/runner/src/engines/openCodeRunner.test.js +0 -73
  42. package/apps/runner/src/runManager.test.js +0 -724
  43. package/apps/runner/src/serverClient.test.js +0 -93
  44. package/apps/server/src/agentSessionDiscovery.test.js +0 -127
  45. package/apps/server/src/agents/claudeCodeRunner.test.js +0 -433
  46. package/apps/server/src/agents/openCodeRunner.test.js +0 -236
  47. package/apps/server/src/agents/runnerContract.test.js +0 -382
  48. package/apps/server/src/appPaths.test.js +0 -52
  49. package/apps/server/src/assetRoutes.test.js +0 -168
  50. package/apps/server/src/codex.test.js +0 -518
  51. package/apps/server/src/codexRoutes.test.js +0 -376
  52. package/apps/server/src/codexRuns.test.js +0 -160
  53. package/apps/server/src/codexSessions.test.js +0 -131
  54. package/apps/server/src/db.test.js +0 -182
  55. package/apps/server/src/gitDiff.test.js +0 -278
  56. package/apps/server/src/gitDiffClient.test.js +0 -113
  57. package/apps/server/src/internalRoutes.test.js +0 -94
  58. package/apps/server/src/maintenance.test.js +0 -154
  59. package/apps/server/src/processControl.test.js +0 -147
  60. package/apps/server/src/relayClient.test.js +0 -478
  61. package/apps/server/src/relayConfig.test.js +0 -38
  62. package/apps/server/src/relayProtocol.test.js +0 -49
  63. package/apps/server/src/relayServer.test.js +0 -798
  64. package/apps/server/src/relayTenants.test.js +0 -137
  65. package/apps/server/src/relayUsageStore.test.js +0 -65
  66. package/apps/server/src/repository.test.js +0 -150
  67. package/apps/server/src/runDispatchService.test.js +0 -301
  68. package/apps/server/src/runEventIngest.test.js +0 -135
  69. package/apps/server/src/runRecovery.test.js +0 -73
  70. package/apps/server/src/runnerClient.test.js +0 -80
  71. package/apps/server/src/runnerDispatch.test.js +0 -136
  72. package/apps/server/src/systemConfig.test.js +0 -44
  73. package/apps/server/src/systemRoutes.test.js +0 -251
  74. package/apps/server/src/taskRoutes.test.js +0 -266
  75. package/apps/server/src/upload.test.js +0 -30
  76. package/apps/server/src/webAppRoutes.test.js +0 -67
  77. package/apps/server/src/workspaceFiles.test.js +0 -262
  78. package/apps/web/dist/assets/CodexSessionManagerDialog-CBD-Qfra.js +0 -3
  79. package/apps/web/dist/assets/WorkbenchSettingsDialog-BN9QGfvY.js +0 -1
  80. package/apps/web/dist/assets/WorkbenchView-BQ8DWZ-_.js +0 -53
  81. package/apps/web/dist/assets/index-DaELubyR.css +0 -1
  82. package/apps/web/dist/assets/index-OSgndpOb.js +0 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ - 新增工作台 `shell` 命令模式:在右侧编辑区输入 `!pwd`、`!git status`、`!pnpm test` 这类纯文本命令时,PromptX 会直接按当前选中的 `Codex / Claude Code / OpenCode` 语义发起本地命令执行,并把结果继续收拢回原有的 `PromptX → Agent / Agent` 对话流,不额外引入一套独立的 Shell 卡片心智。
6
+ - 收紧命令模式的安全边界与展示逻辑:命令意图改为由服务端重新解析,不再信任前端直传命令;shell run 固定绑定 root project session,避免污染 member session 的 thread/identity;默认仅本机可用,Relay 需在设置里显式开启“允许远程执行命令模式”后,且仅对带内部标记的 relay 转发请求放行。
7
+ - 为远程命令模式补齐配置与回归测试:Relay 设置页新增高权限开关与明确警告,诊断信息也会带上该配置;同时新增本地允许、远程默认拒绝、仅 relay 显式开启可用、环境变量覆盖等测试,降低后续回归风险。
8
+ - 修复 runner 大事件上报导致任务卡死的问题:内部 runner events 接口提高了 body limit,避免长过程日志或大批量事件刷新时触发 `Request body is too large`,导致一轮任务停在中间。
9
+ - 改进代码变更审查对 Git submodule 的支持:代码变更与 diff review 现在会展开 submodule 内部的真实文件改动,而不再只显示顶层 submodule 占位项;相关任务级、轮次级与子文件明细链路都已补齐测试。
10
+ - 补充开发环境的 `Vite allowedHosts` 配置,方便在代理、局域网或特定本地域名下更稳定地访问工作台开发服务。
11
+
12
+ ## 0.2.0
13
+
14
+ - 项目正式升级为多 Agent 协作模型:一个项目可同时绑定 `Codex / Claude Code / OpenCode`,右侧可直接切换本轮发送目标,中栏也支持按 Agent 过滤整轮消息流,适合同一任务里拆分“方案 / 执行 / 复核”等分工。
15
+ - 加强项目与本机会话的双向复用:创建或编辑项目时可直接发现并选择本机已有 session,`PromptX` 内新建和推进出来的会话也能稳定回流到候选列表;同时补齐 root project session 与 hidden member sessions 的绑定、回写、重置与删除语义。
16
+ - 收敛多 Agent 下的事件与状态同步:runner 派发会把 run 正确绑定到实际 member session,事件回写后仍广播完整项目快照,避免 partial session 更新把 `agentBindings` 冲掉;同时为这条链路补齐服务端回归测试与真实浏览器 E2E。
17
+ - 持续打磨源码查看、代码变更与上下文回插体验:支持在回复、源码与 diff 中直接选中片段插回右侧编辑区,并统一源码 / 搜索 / 代码变更 / Agent selector 的交互与选中态,减少桌面端与移动端的割裂感。
18
+ - 更新产品文档与展示素材:中英文 README 重写为当前产品结构,截图统一切换为 `WeChat` 主题,并新增工作台总览、多 Agent 项目管理、源码查看、代码变更、设置页与手机端等多组场景图。
19
+ - 修复 relay 场景下图片下发给 agent 时的地址问题:发送给 `Codex / Claude Code / OpenCode` 的图片 URL 改为优先使用本机 PromptX 服务地址,避免 relay 域名下的临时图片链接因鉴权或过期导致模型看图失败。
20
+
3
21
  ## 0.1.48
4
22
 
5
23
  - 修复移动端“选中后插入到编辑区”偶发无效的问题:选区按钮改为更早的 `pointerdown` 触发,避免手机上点击按钮时浏览器先清掉选区,导致按钮消失但内容没有真正插入。
package/README.en.md CHANGED
@@ -2,21 +2,31 @@
2
2
 
3
3
  [中文 README](README.md)
4
4
 
5
- PromptX is a local AI agent workspace.
5
+ PromptX is a local workspace for AI agents.
6
6
 
7
- It organizes `Codex`, `Claude Code`, and `OpenCode` into a cleaner workflow:
7
+ It takes the CLI tools you already use — `Codex`, `Claude Code`, and `OpenCode` — and organizes them into a workflow that is easier to keep running over time:
8
8
 
9
9
  ```text
10
10
  Task -> Project -> Directory -> Thread -> Run -> Diff
11
11
  ```
12
12
 
13
- You keep using the agent CLI you already know, while PromptX brings inputs, project binding, execution logs, final replies, and code diffs into one workspace.
13
+ You keep using the agent and model you already know. PromptX brings structured input, project binding, execution logs, final replies, code review, and source browsing into one workspace.
14
14
 
15
15
  PromptX can also reuse sessions with these agent CLIs in both directions:
16
16
 
17
17
  - Start a session in `Codex`, `Claude Code`, or `OpenCode`, then continue it inside PromptX
18
18
  - Start and advance a project inside PromptX, then resume the same session back in the agent CLI
19
19
 
20
+ ## Why It Is More Useful Now
21
+
22
+ - One project can attach multiple agents, and you can switch the send target from the right panel
23
+ - Review `PromptX` input cards, execution logs, final replies, and run history on the same page
24
+ - Browse the current project source code in read-only mode, with path and content search
25
+ - Inspect workspace-level, task-level, or per-run diffs without leaving the app
26
+ - Insert useful fragments from prompts, replies, source files, and diffs back into the editor
27
+ - Multiple built-in themes are available; the screenshots below use the `WeChat` theme
28
+ - Relay support lets you access your local PromptX from mobile or external networks
29
+
20
30
  ## Quick Start
21
31
 
22
32
  ### Requirements
@@ -45,51 +55,82 @@ promptx status
45
55
  promptx stop
46
56
  ```
47
57
 
48
- ### How To Use
58
+ ### Basic Workflow
49
59
 
50
- 1. Create a task and prepare the context you want to send
51
- 2. Bind the task to a project
52
- 3. Choose a working directory and engine for that project
53
- 4. Send and review execution logs, final replies, and code diffs on the same page
60
+ 1. Create a task and prepare the requirements, logs, screenshots, and files you want to send
61
+ 2. Bind that task to a project
62
+ 3. Choose a working directory, default engine, and optional collaborating agents
63
+ 4. Pick which agent should receive this round from the right panel
64
+ 5. Send and review logs, replies, source files, and diffs in the same workspace
65
+ 6. Insert useful snippets back into the editor and continue the next round
54
66
 
55
67
  ## Core Features
56
68
 
57
69
  - Structured input: text, images, `md`, `txt`, and `pdf`
58
- - Project reuse: keep a stable directory and engine/thread context
59
- - Visible process: inspect logs, final replies, and run history
70
+ - Project reuse: keep a stable directory and default engine/thread context
71
+ - Multi-agent collaboration: one project can attach `Codex`, `Claude Code`, and `OpenCode`
72
+ - Visible process: inspect execution logs, final replies, and run history
73
+ - Source browser: read-only source browsing with path search and content search
60
74
  - Built-in diff review: inspect workspace, accumulated task, or per-run changes
61
- - Multi-engine support: `Codex`, `Claude Code`, `OpenCode`
75
+ - Context reinsertion: insert content from prompts, replies, source files, and diffs back into the editor
62
76
  - Session interoperability: PromptX can reuse sessions with `Codex`, `Claude Code`, and `OpenCode`
63
77
  - Remote access: connect from mobile or external networks through Relay
64
78
 
65
79
  ## Screenshots
66
80
 
67
- ### Workspace
81
+ All screenshots below use the `WeChat` theme.
82
+
83
+ ### 1. Workspace Overview
84
+
85
+ See the task list, agent filter, input cards, execution logs, and final replies together.
86
+
87
+ ![PromptX workspace overview](docs/assets/readme-workbench-wechat.png)
88
+
89
+ ### 2. Execution Logs and Replies
90
+
91
+ Review prompts, process logs, and final replies from the same round without bouncing between the terminal and diff tools.
92
+
93
+ ![PromptX execution focus](docs/assets/readme-execution-focus-wechat.png)
94
+
95
+ ### 3. Multi-Agent Project Management
96
+
97
+ One project can attach multiple agents while sharing the same directory. Each agent builds its own thread on first send.
98
+
99
+ ![PromptX multi-agent project management](docs/assets/readme-project-manager-wechat.png)
100
+
101
+ ### 4. Read-Only Source Browser
102
+
103
+ Use the directory tree and search results on the left, with file preview on the right, to inspect code and send context back into the editor.
104
+
105
+ ![PromptX source browser](docs/assets/readme-source-browser-wechat.png)
106
+
107
+ ### 5. Diff Review
108
+
109
+ Inspect workspace-level, task-level, or single-run changes to understand exactly what an agent changed.
110
+
111
+ ![PromptX diff review](docs/assets/readme-diff-wechat.png)
68
112
 
69
- ![PromptX workspace](docs/assets/readme-workbench-glass.png)
113
+ ### 6. Settings and System Panels
70
114
 
71
- ### Settings
115
+ Theme, shortcuts, remote access, and system configuration all live in the same settings dialog.
72
116
 
73
- ![PromptX settings](docs/assets/readme-settings-theme-glass.png)
117
+ ![PromptX theme settings](docs/assets/readme-settings-theme-wechat.png)
74
118
 
75
- ### Mobile
119
+ ![PromptX system settings](docs/assets/readme-settings-system-wechat.png)
76
120
 
77
- ![PromptX mobile](docs/assets/readme-mobile-remote-glass.png)
121
+ ### 7. Mobile
78
122
 
79
- ## Why It Helps
123
+ With Relay or local network access, you can keep reading replies and pushing work forward from your phone.
80
124
 
81
- - Keeps long-running tasks out of scattered terminal tabs and notes
82
- - Avoids re-explaining directories, projects, and context every round
83
- - Lets you review process and result together, not just the final answer
84
- - Makes code review easier with built-in diff inspection
85
- - Keeps tasks accessible on mobile when you are away from your desk
125
+ ![PromptX mobile](docs/assets/readme-mobile-wechat.png)
86
126
 
87
127
  ## Good Fit For
88
128
 
89
- - Preparing requirements, screenshots, logs, and files before sending
90
- - Reusing the same project and directory across many rounds
91
- - Reviewing execution logs, final output, and code changes together
92
- - Exposing local PromptX to phones or external networks through Relay
129
+ - Preparing requirements, screenshots, logs, and files before sending them to an agent
130
+ - Reusing the same project, directory, and thread across many rounds
131
+ - Coordinating multiple agents in one task without bouncing between terminals
132
+ - Reviewing execution logs, replies, source files, and code changes together
133
+ - Continuing local PromptX work from your phone when you are away from your desk
93
134
 
94
135
  ## Development
95
136
 
package/README.md CHANGED
@@ -2,20 +2,30 @@
2
2
 
3
3
  [English README](README.en.md)
4
4
 
5
- PromptX 是一个面向本机 AI Agent 协作的工作台。
5
+ PromptX 是一个面向本机 AI Agent 的协作工作台。
6
6
 
7
- 它把 `Codex`、`Claude Code`、`OpenCode` 的使用过程整理成一套更顺手的结构:
7
+ 它把 `Codex`、`Claude Code`、`OpenCode` 这些你已经熟悉的 CLI,整理成一套更适合持续协作的界面:
8
8
 
9
9
  ```text
10
10
  任务 -> 项目 -> 目录 -> 线程 -> Run -> Diff
11
11
  ```
12
12
 
13
- 你继续使用熟悉的 agent CLI,PromptX 负责把输入整理、项目绑定、执行过程、最终回复和代码变更放进同一个工作台里。
13
+ 你继续使用熟悉的 agent 与模型,PromptX 负责把输入整理、项目绑定、执行过程、最终回复、代码变更、源码查看放进同一个工作台里。
14
14
 
15
- 并且,PromptX 和这些 agent CLI 之间可以互相复用会话:
15
+ 并且,PromptX 与这些 agent CLI 可以双向复用会话:
16
16
 
17
- - 可以在 `Codex`、`Claude Code`、`OpenCode` 里先创建会话,再回到 PromptX 里继续
18
- - 也可以在 PromptX 里创建和推进项目,再回到 agent CLI 里直接恢复同一个会话
17
+ - 你可以先在 `Codex`、`Claude Code`、`OpenCode` 里创建会话,再回到 PromptX 继续
18
+ - 也可以在 PromptX 里创建和推进项目,再回到 agent CLI 里恢复同一个会话
19
+
20
+ ## 为什么现在值得用
21
+
22
+ - 一个项目可以挂多个 Agent,右侧直接切换发送目标
23
+ - 同页查看 `PromptX` 输入卡片、执行过程、最终回复、历史 run
24
+ - 直接查看当前项目源码,并支持按路径或内容搜索
25
+ - 直接审查 workspace / 任务累计 / 单轮 run 的代码变更
26
+ - 可以把 `Prompt`、回复、源码、Diff 中的内容再次插回编辑区,继续下一轮
27
+ - 内置多主题,当前 README 截图统一使用 `WeChat` 主题
28
+ - 可配合 Relay 从手机或外网访问自己的 PromptX
19
29
 
20
30
  ## 快速开始
21
31
 
@@ -45,51 +55,82 @@ promptx status
45
55
  promptx stop
46
56
  ```
47
57
 
48
- ### 怎么用
58
+ ### 基本使用流程
49
59
 
50
- 1. 新建任务,整理本轮要发给 agent 的内容
60
+ 1. 新建任务,先整理本轮要发给 agent 的需求、日志、图片、文件
51
61
  2. 给任务绑定一个项目
52
- 3. 给项目选择工作目录和执行引擎
53
- 4. 点击发送,在同一页查看执行过程、最终回复和代码变更
62
+ 3. 给项目选择工作目录、默认执行引擎,以及可协作的其他 Agents
63
+ 4. 在右侧选择这轮要发给哪个 Agent
64
+ 5. 发送后,在同一页查看执行过程、最终回复、源码、代码变更
65
+ 6. 把有价值的片段重新插回编辑区,继续下一轮
54
66
 
55
67
  ## 核心能力
56
68
 
57
69
  - 输入整理:支持文本、图片、`md`、`txt`、`pdf`
58
- - 项目复用:一个项目绑定固定目录和执行引擎,持续复用线程上下文
70
+ - 项目复用:一个项目绑定固定目录与默认执行引擎,持续复用线程上下文
71
+ - 多 Agent 协作:一个项目可同时挂 `Codex`、`Claude Code`、`OpenCode`
59
72
  - 过程可见:同页查看执行过程、最终回复、历史 run
73
+ - 源码查看:只读浏览当前项目源码,支持路径搜索与内容搜索
60
74
  - 代码审查:直接查看 workspace / 任务累计 / 单次 run 的 diff
61
- - 多引擎统一:当前支持 `Codex`、`Claude Code`、`OpenCode`
75
+ - 上下文回插:支持从 Prompt、回复、源码、Diff 中选取内容插回编辑区
62
76
  - 会话互通:PromptX 与 `Codex`、`Claude Code`、`OpenCode` 可双向复用会话
63
77
  - 远程访问:支持通过 Relay 从手机或外网访问自己的 PromptX
64
78
 
65
79
  ## 系统截图
66
80
 
67
- ### 工作台
81
+ 以下截图基于 `WeChat` 主题。
82
+
83
+ ### 1. 工作台总览
84
+
85
+ 可以同时看到任务列表、Agent 过滤、输入卡片、执行过程和最终回复。
86
+
87
+ ![PromptX 工作台总览](docs/assets/readme-workbench-wechat.png)
88
+
89
+ ### 2. 执行过程与回复
90
+
91
+ 同一轮里直接看 Prompt、过程日志、最终回复,不用在终端和 diff 工具之间来回切。
92
+
93
+ ![PromptX 执行过程与回复](docs/assets/readme-execution-focus-wechat.png)
94
+
95
+ ### 3. 多 Agent 项目管理
96
+
97
+ 一个项目可以挂多个 Agent,目录保持一致,首次发送时自动建立各自线程。
98
+
99
+ ![PromptX 多 Agent 项目管理](docs/assets/readme-project-manager-wechat.png)
100
+
101
+ ### 4. 只读源码查看
102
+
103
+ 左侧目录树与搜索结果,右侧文件预览;适合快速浏览、搜索、选中代码并插回编辑区。
104
+
105
+ ![PromptX 源码查看](docs/assets/readme-source-browser-wechat.png)
106
+
107
+ ### 5. 代码变更审查
108
+
109
+ 直接看当前项目、任务累计或单轮 run 的代码变更,适合回顾 agent 到底改了什么。
110
+
111
+ ![PromptX 代码变更审查](docs/assets/readme-diff-wechat.png)
68
112
 
69
- ![PromptX 工作台](docs/assets/readme-workbench-glass.png)
113
+ ### 6. 设置与系统
70
114
 
71
- ### 设置
115
+ 主题、快捷键、远程访问、系统配置集中在同一个设置面板里。
72
116
 
73
- ![PromptX 设置](docs/assets/readme-settings-theme-glass.png)
117
+ ![PromptX 主题设置](docs/assets/readme-settings-theme-wechat.png)
74
118
 
75
- ### 手机端
119
+ ![PromptX 系统设置](docs/assets/readme-settings-system-wechat.png)
76
120
 
77
- ![PromptX 手机端](docs/assets/readme-mobile-remote-glass.png)
121
+ ### 7. 手机端
78
122
 
79
- ## 为什么好用
123
+ 通过 Relay 或局域网访问时,可以在手机端继续查看任务、阅读回复、推进协作。
80
124
 
81
- - 不用把多轮任务散落在终端和聊天记录里
82
- - 不用每轮都重新交代目录、项目和上下文
83
- - 不只看最终一句回答,还能回看过程和 run 历史
84
- - 改完代码后不用再切回命令行单独看 diff
85
- - 离开电脑后,也能继续在手机上看任务和推进任务
125
+ ![PromptX 手机端](docs/assets/readme-mobile-wechat.png)
86
126
 
87
127
  ## 适合什么场景
88
128
 
89
129
  - 先整理需求、截图、日志、文件,再交给 agent 执行
90
130
  - 一个项目要持续多轮协作,希望稳定复用目录和线程
91
- - 需要同时看执行过程、最终回复和代码改动
92
- - 希望把本机 PromptX 通过 Relay 暴露给手机或外部网络
131
+ - 一个任务里需要多个 Agent 配合,但又不想来回切命令行
132
+ - 需要同时看执行过程、最终回复、源码与代码改动
133
+ - 离开电脑后,也想从手机继续推进本机 PromptX
93
134
 
94
135
  ## 源码开发
95
136
 
@@ -7,22 +7,32 @@ import {
7
7
  import { codexRunner } from './codexRunner.js'
8
8
  import { claudeCodeRunner } from './claudeCodeRunner.js'
9
9
  import { openCodeRunner } from './openCodeRunner.js'
10
+ import { shellRunner } from './shellRunner.js'
10
11
 
11
12
  const runnerRegistry = new Map([
12
13
  [codexRunner.engine, codexRunner],
13
14
  [claudeCodeRunner.engine, claudeCodeRunner],
14
15
  [openCodeRunner.engine, openCodeRunner],
16
+ [shellRunner.engine, shellRunner],
15
17
  ])
16
18
 
19
+ function normalizeRunnerEngine(engine = AGENT_ENGINES.CODEX) {
20
+ const normalized = String(engine || '').trim().toLowerCase()
21
+ if (normalized === shellRunner.engine) {
22
+ return shellRunner.engine
23
+ }
24
+ return normalizeAgentEngine(normalized)
25
+ }
26
+
17
27
  export function getAgentRunner(engine = AGENT_ENGINES.CODEX) {
18
- return runnerRegistry.get(normalizeAgentEngine(engine)) || null
28
+ return runnerRegistry.get(normalizeRunnerEngine(engine)) || null
19
29
  }
20
30
 
21
31
  export function assertAgentRunner(engine = AGENT_ENGINES.CODEX) {
22
- const normalized = normalizeAgentEngine(engine)
32
+ const normalized = normalizeRunnerEngine(engine)
23
33
  const runner = getAgentRunner(normalized)
24
34
  if (!runner) {
25
- throw new Error(`当前还不支持执行引擎:${getAgentEngineLabel(normalized)}`)
35
+ throw new Error(`当前还不支持执行引擎:${normalized === shellRunner.engine ? shellRunner.label : getAgentEngineLabel(normalized)}`)
26
36
  }
27
37
  return runner
28
38
  }
@@ -0,0 +1,216 @@
1
+ import { spawn } from 'node:child_process'
2
+ import {
3
+ createAgentEventEnvelopeEvent,
4
+ createCompletedEnvelopeEvent,
5
+ createStderrEnvelopeEvent,
6
+ createStdoutEnvelopeEvent,
7
+ } from '../../../../packages/shared/src/agentRunEnvelopeEvents.js'
8
+ import {
9
+ AGENT_RUN_EVENT_TYPES,
10
+ AGENT_RUN_ITEM_TYPES,
11
+ createItemCompletedEvent,
12
+ createItemStartedEvent,
13
+ } from '../../../../packages/shared/src/agentRunEvents.js'
14
+ import { createManagedSpawnOptions, forceStopChildProcess } from '../processControl.js'
15
+
16
+ const SHELL_ENGINE = 'shell'
17
+ const MAX_SHELL_OUTPUT_TAIL_LENGTH = Math.max(
18
+ 16 * 1024,
19
+ Number(process.env.PROMPTX_SHELL_OUTPUT_TAIL_LENGTH) || 128 * 1024
20
+ )
21
+
22
+ function appendOutputTail(current = '', chunk = '', maxLength = MAX_SHELL_OUTPUT_TAIL_LENGTH) {
23
+ const next = `${String(current || '')}${String(chunk || '')}`
24
+ if (next.length <= maxLength) {
25
+ return next
26
+ }
27
+ return next.slice(next.length - maxLength)
28
+ }
29
+
30
+ function splitBufferedLines(buffer = '') {
31
+ const normalized = String(buffer || '')
32
+ if (!normalized) {
33
+ return { lines: [], rest: '' }
34
+ }
35
+
36
+ const parts = normalized.split(/\r?\n/g)
37
+ const rest = /(?:\r?\n)$/.test(normalized) ? '' : parts.pop() || ''
38
+ return {
39
+ lines: parts.filter(Boolean),
40
+ rest,
41
+ }
42
+ }
43
+
44
+ function getShellCommand(command = '') {
45
+ const raw = String(command || '').trim()
46
+ if (!raw) {
47
+ return {
48
+ executable: '',
49
+ args: [],
50
+ displayCommand: '',
51
+ }
52
+ }
53
+
54
+ if (process.platform === 'win32') {
55
+ const executable = process.env.ComSpec || 'cmd.exe'
56
+ return {
57
+ executable,
58
+ args: ['/d', '/s', '/c', raw],
59
+ displayCommand: `${executable} /d /s /c ${raw}`,
60
+ }
61
+ }
62
+
63
+ const executable = process.env.SHELL || '/bin/zsh'
64
+ return {
65
+ executable,
66
+ args: ['-lc', raw],
67
+ displayCommand: `${executable} -lc ${raw}`,
68
+ }
69
+ }
70
+
71
+ function createCommandItem(command = '', status = 'running', aggregatedOutput = '', exitCode = null) {
72
+ return {
73
+ type: AGENT_RUN_ITEM_TYPES.COMMAND_EXECUTION,
74
+ command: String(command || '').trim(),
75
+ status: String(status || '').trim() || 'running',
76
+ aggregated_output: String(aggregatedOutput || ''),
77
+ ...(typeof exitCode === 'number' ? { exit_code: exitCode } : {}),
78
+ }
79
+ }
80
+
81
+ export const shellRunner = {
82
+ engine: SHELL_ENGINE,
83
+ label: 'Shell',
84
+ supportsWorkspaceHistory: false,
85
+ streamSessionPrompt(session, prompt, callbacks = {}) {
86
+ const command = String(prompt || '').trim()
87
+ if (!command) {
88
+ throw new Error('缺少要执行的命令。')
89
+ }
90
+
91
+ const cwd = String(session?.cwd || '').trim()
92
+ const { executable, args, displayCommand } = getShellCommand(command)
93
+ if (!executable) {
94
+ throw new Error('当前环境没有可用的 shell。')
95
+ }
96
+
97
+ const onEvent = typeof callbacks.onEvent === 'function' ? callbacks.onEvent : () => {}
98
+ const child = spawn(executable, args, createManagedSpawnOptions({ cwd }))
99
+ let stdoutBuffer = ''
100
+ let stderrBuffer = ''
101
+ let outputTail = ''
102
+ let settled = false
103
+
104
+ onEvent(createAgentEventEnvelopeEvent(createItemStartedEvent(createCommandItem(displayCommand, 'running'))))
105
+
106
+ const result = new Promise((resolve, reject) => {
107
+ const rejectWithOutput = (message, payload = {}) => {
108
+ const error = new Error(String(message || '命令执行失败。'))
109
+ error.output = String(payload.output || outputTail || '').trim()
110
+ error.exitCode = typeof payload.exitCode === 'number' ? payload.exitCode : null
111
+ reject(error)
112
+ }
113
+
114
+ const flushStdout = (buffer, force = false) => {
115
+ const { lines, rest } = splitBufferedLines(buffer)
116
+ lines.forEach((line) => {
117
+ onEvent(createStdoutEnvelopeEvent(line))
118
+ outputTail = appendOutputTail(outputTail, `${line}\n`)
119
+ })
120
+ if (force && rest) {
121
+ onEvent(createStdoutEnvelopeEvent(rest))
122
+ outputTail = appendOutputTail(outputTail, `${rest}\n`)
123
+ return ''
124
+ }
125
+ return rest
126
+ }
127
+
128
+ const flushStderr = (buffer, force = false) => {
129
+ const { lines, rest } = splitBufferedLines(buffer)
130
+ lines.forEach((line) => {
131
+ onEvent(createStderrEnvelopeEvent(line))
132
+ outputTail = appendOutputTail(outputTail, `${line}\n`)
133
+ })
134
+ if (force && rest) {
135
+ onEvent(createStderrEnvelopeEvent(rest))
136
+ outputTail = appendOutputTail(outputTail, `${rest}\n`)
137
+ return ''
138
+ }
139
+ return rest
140
+ }
141
+
142
+ child.stdout?.on('data', (chunk) => {
143
+ stdoutBuffer += chunk.toString()
144
+ stdoutBuffer = flushStdout(stdoutBuffer)
145
+ })
146
+
147
+ child.stderr?.on('data', (chunk) => {
148
+ stderrBuffer += chunk.toString()
149
+ stderrBuffer = flushStderr(stderrBuffer)
150
+ })
151
+
152
+ child.once('error', (error) => {
153
+ if (settled) {
154
+ return
155
+ }
156
+ settled = true
157
+ stdoutBuffer = flushStdout(stdoutBuffer, true)
158
+ stderrBuffer = flushStderr(stderrBuffer, true)
159
+ const finalOutput = String(outputTail || '').trim()
160
+ onEvent(createAgentEventEnvelopeEvent(createItemCompletedEvent(createCommandItem(
161
+ displayCommand,
162
+ 'failed',
163
+ finalOutput,
164
+ 1
165
+ ))))
166
+ rejectWithOutput(error?.message || '命令启动失败。', {
167
+ output: finalOutput,
168
+ exitCode: 1,
169
+ })
170
+ })
171
+
172
+ child.once('close', (code, signal) => {
173
+ if (settled) {
174
+ return
175
+ }
176
+ settled = true
177
+ stdoutBuffer = flushStdout(stdoutBuffer, true)
178
+ stderrBuffer = flushStderr(stderrBuffer, true)
179
+ const exitCode = Number.isInteger(code) ? code : (signal ? 1 : 0)
180
+ const finalOutput = String(outputTail || '').trim()
181
+ const success = exitCode === 0
182
+
183
+ onEvent(createAgentEventEnvelopeEvent(createItemCompletedEvent(createCommandItem(
184
+ displayCommand,
185
+ success ? 'completed' : 'failed',
186
+ finalOutput,
187
+ exitCode
188
+ ))))
189
+
190
+ if (success) {
191
+ const message = finalOutput || `命令执行完成:${command}`
192
+ onEvent(createCompletedEnvelopeEvent(message))
193
+ resolve({
194
+ sessionId: String(session?.id || '').trim(),
195
+ threadId: '',
196
+ message,
197
+ })
198
+ return
199
+ }
200
+
201
+ rejectWithOutput(`命令执行失败(exit ${exitCode})`, {
202
+ output: finalOutput,
203
+ exitCode,
204
+ })
205
+ })
206
+ })
207
+
208
+ return {
209
+ child,
210
+ result,
211
+ cancel(options = {}) {
212
+ forceStopChildProcess(child, options)
213
+ },
214
+ }
215
+ },
216
+ }