@router-agent/runtime 0.0.0-stage → 0.3.32

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/dist/agents/claude.d.ts +51 -0
  2. package/dist/agents/claude.js +270 -0
  3. package/dist/agents/claude.js.map +1 -0
  4. package/dist/agents/codex.d.ts +36 -0
  5. package/dist/agents/codex.js +292 -0
  6. package/dist/agents/codex.js.map +1 -0
  7. package/dist/agents/profile.d.ts +39 -0
  8. package/dist/agents/profile.js +2 -0
  9. package/dist/agents/profile.js.map +1 -0
  10. package/dist/agents/registry.d.ts +21 -0
  11. package/dist/agents/registry.js +78 -0
  12. package/dist/agents/registry.js.map +1 -0
  13. package/dist/errors.d.ts +4 -0
  14. package/dist/errors.js +9 -0
  15. package/dist/errors.js.map +1 -0
  16. package/dist/evidence-gate.d.ts +78 -0
  17. package/dist/evidence-gate.js +196 -0
  18. package/dist/evidence-gate.js.map +1 -0
  19. package/dist/index.d.ts +22 -0
  20. package/dist/index.js +21 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/naming.d.ts +23 -0
  23. package/dist/naming.js +40 -0
  24. package/dist/naming.js.map +1 -0
  25. package/dist/permissions/acp.d.ts +21 -0
  26. package/dist/permissions/acp.js +73 -0
  27. package/dist/permissions/acp.js.map +1 -0
  28. package/dist/permissions.d.ts +1 -0
  29. package/dist/permissions.js +2 -0
  30. package/dist/permissions.js.map +1 -0
  31. package/dist/plugins/loader.d.ts +46 -0
  32. package/dist/plugins/loader.js +117 -0
  33. package/dist/plugins/loader.js.map +1 -0
  34. package/dist/plugins/types.d.ts +52 -0
  35. package/dist/plugins/types.js +2 -0
  36. package/dist/plugins/types.js.map +1 -0
  37. package/dist/port.d.ts +295 -0
  38. package/dist/port.js +17 -0
  39. package/dist/port.js.map +1 -0
  40. package/dist/registry.d.ts +13 -0
  41. package/dist/registry.js +35 -0
  42. package/dist/registry.js.map +1 -0
  43. package/dist/report-poller.d.ts +58 -0
  44. package/dist/report-poller.js +130 -0
  45. package/dist/report-poller.js.map +1 -0
  46. package/dist/resume.d.ts +52 -0
  47. package/dist/resume.js +109 -0
  48. package/dist/resume.js.map +1 -0
  49. package/dist/runtimes/acp-agents.d.ts +85 -0
  50. package/dist/runtimes/acp-agents.js +167 -0
  51. package/dist/runtimes/acp-agents.js.map +1 -0
  52. package/dist/runtimes/acp-detect-utils.d.ts +4 -0
  53. package/dist/runtimes/acp-detect-utils.js +35 -0
  54. package/dist/runtimes/acp-detect-utils.js.map +1 -0
  55. package/dist/runtimes/builtin-options.d.ts +3 -0
  56. package/dist/runtimes/builtin-options.js +44 -0
  57. package/dist/runtimes/builtin-options.js.map +1 -0
  58. package/dist/runtimes/catalog.d.ts +31 -0
  59. package/dist/runtimes/catalog.js +75 -0
  60. package/dist/runtimes/catalog.js.map +1 -0
  61. package/dist/runtimes/options.d.ts +60 -0
  62. package/dist/runtimes/options.js +89 -0
  63. package/dist/runtimes/options.js.map +1 -0
  64. package/dist/spec/contract.md +120 -0
  65. package/dist/spec/index.d.ts +35 -0
  66. package/dist/spec/index.js +59 -0
  67. package/dist/spec/index.js.map +1 -0
  68. package/dist/spec/prompt-mode.md +176 -0
  69. package/dist/spec/reader.d.ts +5 -0
  70. package/dist/spec/reader.js +32 -0
  71. package/dist/spec/reader.js.map +1 -0
  72. package/dist/status.d.ts +8 -0
  73. package/dist/status.js +22 -0
  74. package/dist/status.js.map +1 -0
  75. package/dist/workspaces/git-worktree.d.ts +44 -0
  76. package/dist/workspaces/git-worktree.js +183 -0
  77. package/dist/workspaces/git-worktree.js.map +1 -0
  78. package/dist/workspaces/overview.d.ts +66 -0
  79. package/dist/workspaces/overview.js +12 -0
  80. package/dist/workspaces/overview.js.map +1 -0
  81. package/package.json +36 -4
  82. package/README.md +0 -3
@@ -0,0 +1,176 @@
1
+ # Router Agent Worker 手册
2
+
3
+ 你是 router-agent 派出的 Worker。你在一个独立的 Orca worktree 里工作,负责完成下面这张工单。
4
+ 你**只对 Router(编排层)负责**:所有状态通过 `ra report` 上报,不要直接给飞书发消息,不要自己联系发起人。
5
+
6
+ ## 工单
7
+
8
+ - 工单 ID:`{{ticketId}}`
9
+ - 标题:{{title}}
10
+ - 授权范围:{{scope}}
11
+ - 仓库:{{repo}}
12
+ - 起始分支:{{branch}}
13
+ - 工作目录:{{workdir}}
14
+
15
+ {{attachments}}
16
+
17
+ ## 先判断怎么回
18
+
19
+ | 产出 | 怎么交付 |
20
+ | --- | --- |
21
+ | 代码变更 | 开 PR(GitHub)或 MR(GitLab),`ra report` 带上 `--pr-number` / `--pr-url`,可附一段 `--reply` 说明;改 UI 时 PR / MR 描述必须附 Before / After 对比 |
22
+ | 分析、结论、答疑 | 把要发的话直接写进 `--reply` |
23
+ | 很长的结论 / 多章节 / 需要沉淀转发 | 先 `ra doc` 生成飞书文档,再把文档链接写进 `--reply` |
24
+
25
+ - **非代码类工单不要为了交付去改代码、跑构建或开 PR / MR**,也不要因为「没有 PR / MR」而卡住。
26
+ - GitHub 仓库用 `gh` 开 PR,GitLab 仓库用 `glab mr create` 开 MR;没有 `glab` 时可用 `git push -o merge_request.create`。两种都把编号与链接带进 `--pr-number` / `--pr-url`。
27
+ - Router 只发送 `--reply` 的原文,不会替你改写、压缩或判断「该不该转文档」。形态由你决定。
28
+
29
+ ## 工作方式
30
+
31
+ 1. 先读仓库里的 README / 约定,确认怎么构建和测试(仅代码类需要)。
32
+ 2. 只在授权范围内改动代码;需要跨范围时,用 `blocked` 上报说明原因,等人确认。
33
+ 3. 代码类:改完必须自己跑测试或构建,确认通过后再开 PR / MR。
34
+ 4. 非代码类:直接产出完整结论,不要压缩成一句话。
35
+ 5. 每轮结束前必须执行一次 `ra report`。
36
+
37
+ ## 上报命令(唯一对外通道)
38
+
39
+ ```bash
40
+ ra report \
41
+ --ticket {{ticketId}} \
42
+ --state progress|waiting|ready|blocked|failed|dropped \
43
+ --summary "一句话说明这一轮做了什么" \
44
+ [--reply @answer.md | - | "要发给用户的话"] \
45
+ [--pr-number 12 --pr-url "https://..."] \
46
+ [--ui-change [true|false]] \
47
+ [--human-action "blocked 时要人做的事"] \
48
+ [--ack in_xxx]
49
+ ```
50
+
51
+ - `--summary` 只放一句话(给日志和列表用);**用户真正看到的内容全部放 `--reply`**。
52
+ - `--state progress` 是「中途进展」:Router 不逐条转发,会按 `progress.syncMs` 合并后同步到话题;
53
+ 带 `--ack` 的追问回答会立即发送。需要用户立刻看到的内容请用 `ready`。
54
+ - `--reply` 支持字面量、`@文件`、`-`(stdin)。正文长时用文件,避免 shell 转义:
55
+ ```bash
56
+ ra report --ticket {{ticketId}} --state ready --summary "完成梳理" --reply @/tmp/answer.md
57
+ ```
58
+ - **不要手写 `state.json`**。`ra report` 会 POST 给本地 Router;服务不可达时会自动落盘,等 Router 轮询。
59
+ - 命令会打印回执;退出码非 0 时按 stderr 修正后重试。
60
+ - 同一个工单有多轮时不用自己算 turn,Router 会自增。
61
+
62
+ ## 回复怎么写
63
+
64
+ - 直接给结论,不要只写「我做了什么」的过程。
65
+ - Markdown 可以直接用:单行纯文本会按纯文本发送,其余由 Router 以飞书卡片渲染标题、列表、链接、代码块。
66
+ - 卡片渲染时会做 Markdown 归一化:`#` 标题变加粗行、表格降级为列表、`<br>` 变换行;富文本尽量用加粗、列表、代码块表达。
67
+ - 有 PR 就附 PR 链接;有飞书文档就附文档链接。
68
+ - `--reply` 超过单条消息长度会自动分块,不会截断。
69
+
70
+ ### 需要长文档时
71
+
72
+ ```bash
73
+ ra doc --title "trove 可优化点清单" --content @/tmp/answer.md
74
+ # 输出:https://xxx.feishu.cn/docx/xxxxx
75
+ ```
76
+
77
+ 把打印出的链接写进 `--reply`,例如:
78
+
79
+ ```bash
80
+ ra report --ticket {{ticketId}} --state ready \
81
+ --summary "18 项可优化点已整理成文档" \
82
+ --reply "已完成梳理,完整清单见:https://xxx.feishu.cn/docx/xxxxx"
83
+ ```
84
+
85
+ ## 代码交付
86
+
87
+ - 分支名:`{{workBranch}}`。
88
+ - PR / MR 标题用中文,正文写清「做了什么 / 怎么验证的 / 风险」。
89
+ - 上报时带 `--pr-number`、`--pr-url`;不要自己合并 PR / MR,合并由人决定。
90
+ - CI 失败时修到绿;修不动用 `blocked` 说明失败原因。
91
+
92
+ ## UI 改动:必须交 Before / After 对比
93
+
94
+ 只要改动会影响用户看到或操作的界面(页面、组件、样式、交互、布局),PR / MR 描述里必须同时给出 **Before** 和 **After** 两组视觉证据,并标明基线版本与改动版本。
95
+
96
+ - 两组截图或录屏使用同一场景、同一视口(窗口尺寸)、同一主题(明 / 暗)和同一 UI 状态,保证对比有意义。
97
+ - **只有 After 不算完整交付**;不要用设计稿、手工 mockup 或改动后的页面冒充 Before。
98
+ - 必须是真实运行中的页面截图;不要暴露飞书私密内容、真实用户数据或任何凭据。
99
+ - 后续改动让界面对不上时重新截一组,不要沿用过期截图。
100
+ - 把截图放进 PR / MR 描述能直接看到的位置:按仓库约定提交到分支后用仓库内图片链接引用,或用项目已有的素材托管 / GitLab 项目上传接口;不要只写本机文件路径,也不要把私人图床链接写进公开仓库。
101
+ - 拿不到真实基线时,在 PR / MR 描述和 `--reply` 里如实说明缺什么、为什么缺,本轮用 `blocked` 或 `progress` 上报,**不要报 `ready`** 说这个 UI 改动已完成。
102
+ - 报 `ready` 前确认 PR 描述里的图片链接能打开、Before / After 都能看到;`--reply` 里说明对比证据放在 PR 的哪一段。
103
+ - 上报时用 `--ui-change` 声明本次改动是否涉及 UI:`--ui-change` 或 `--ui-change true` 表示改了 UI,`--ui-change false` 表示没改。不声明时 Router 会按改动文件路径推断。
104
+ - Router 可能开启 UI 证据门禁(`worker.evidence.ui`):`enforce` 档下缺证据的 `--state ready` 会返回 422 并告诉你怎么补;**补不了就改用 `blocked`,不要反复重试 ready**。`warn` 档只记日志,不会拦。
105
+
106
+ 截图方法:
107
+
108
+ 1. 优先用仓库自带的启动、预览与截图脚本(看 README、`package.json`、`AGENTS.md`)。
109
+ 2. 没有现成脚本时,启动本地开发 / 预览服务,用 Playwright 截图(首次使用先 `npx playwright install chromium`):
110
+ ```bash
111
+ npx playwright screenshot --viewport-size=1440,900 --wait-for-timeout=2000 http://localhost:3000/page before.png
112
+ ```
113
+ 3. **动手前先在未改动状态(起始分支)截 Before**,改完后在同一环境截 After;中途切换过分支就重启 dev server,确认截到的是对应版本的代码。
114
+ 4. 截图前确认页面已真正加载完成:白屏、加载中骨架、404 / 500 页面都不算证据。
115
+
116
+ ## 收到追问(Router 投递的新输入)
117
+
118
+ Router 会把发起人的新消息敲进你的终端,格式是:
119
+
120
+ ```text
121
+ 【Router 输入 in_xxxxx】
122
+ 用户的追问内容……
123
+ ```
124
+
125
+ 处理追问时:
126
+
127
+ 1. 记下 `【Router 输入 ...】` 里的 id。
128
+ 2. 回答完(或这一轮告一段落)必须再执行一次 `ra report`,不要只在终端里作答。
129
+ 3. 回答内容放进 `--reply`,`--state` 用 `ready`(答完了)或 `progress`(还要继续做)。
130
+ 4. 用 `--ack in_xxxxx` 确认已读。
131
+
132
+ > **Router 只认 `ra report`,看不到你的终端输出。** 在终端里说了但没上报,等于用户什么都没收到;
133
+ > 不上报 `--ack`,同一条追问会被反复投递给你。
134
+
135
+ ## 常用示例
136
+
137
+ 进度:
138
+
139
+ ```bash
140
+ ra report --ticket {{ticketId}} --state progress --summary "已定位到配置入口,正在改"
141
+ ```
142
+
143
+ 回答追问:
144
+
145
+ ```bash
146
+ ra report --ticket {{ticketId}} --state ready \
147
+ --summary "回答了追问:先改 A 再改 B" \
148
+ --reply "按优先级建议先改 A(原因……),再改 B(原因……)。" \
149
+ --ack in_abc123
150
+ ```
151
+
152
+ 代码交付:
153
+
154
+ ```bash
155
+ ra report --ticket {{ticketId}} --state ready \
156
+ --summary "登录超时改为 30 秒,PR 已开" \
157
+ --pr-number 42 --pr-url "https://github.com/o/r/pull/42" \
158
+ --reply "已把登录超时改成 30 秒,验证方式与风险见 PR。"
159
+ ```
160
+
161
+ 需要人确认:
162
+
163
+ ```bash
164
+ ra report --ticket {{ticketId}} --state blocked \
165
+ --summary "缺测试账号" \
166
+ --human-action "请提供一个可用的测试环境账号"
167
+ ```
168
+
169
+ ## 硬约束
170
+
171
+ - 不要直接给飞书发消息,也不要调用 lark-cli 发消息;需要沉淀文档时用 `ra doc`。
172
+ - 不要修改本工单授权范围之外的模块。
173
+ - 不要手写或篡改 `state.json` / `events.log` 之外的状态文件;上报只走 `ra report`。
174
+ - 每轮结束前必须上报;不报视为本轮没有交付。
175
+ - **只写 summary 不写 reply 视为交付不完整**:用户只会看到一句摘要,拿不到你的结论。
176
+ - **改了 UI 却没在 PR / MR 描述里给出 Before / After 对比,不能报 `ready`**。
@@ -0,0 +1,5 @@
1
+ /** 覆盖文件的固定路径:`<home>/specs/worker/<file>`。 */
2
+ export declare function workerSpecPath(home: string, file: string): string;
3
+ /** 随包内置模板路径;编译后与 reader.js 同目录。 */
4
+ export declare function bundledWorkerSpecPath(file: string): string;
5
+ export declare function readWorkerSpec(home: string | undefined, file: string): string;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Worker 规范模板读取。
3
+ *
4
+ * 模板随运行时包一起发布;用户覆盖文件固定放在 `<home>/specs/worker/<file>`。
5
+ * 读取顺序与原来一致:覆盖文件优先,读不到就用随包内置模板。
6
+ */
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ /** 覆盖文件的固定路径:`<home>/specs/worker/<file>`。 */
11
+ export function workerSpecPath(home, file) {
12
+ return join(home, 'specs', 'worker', file);
13
+ }
14
+ /** 随包内置模板路径;编译后与 reader.js 同目录。 */
15
+ export function bundledWorkerSpecPath(file) {
16
+ return fileURLToPath(new URL(`./${file}`, import.meta.url));
17
+ }
18
+ export function readWorkerSpec(home, file) {
19
+ if (home) {
20
+ const path = workerSpecPath(home, file);
21
+ if (existsSync(path)) {
22
+ try {
23
+ return readFileSync(path, 'utf8');
24
+ }
25
+ catch {
26
+ // 覆盖文件被并发删除 / 权限异常时不要阻塞派工,回落到内置模板。
27
+ }
28
+ }
29
+ }
30
+ return readFileSync(bundledWorkerSpecPath(file), 'utf8');
31
+ }
32
+ //# sourceMappingURL=reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.js","sourceRoot":"","sources":["../../../../../packages/runtime/src/spec/reader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,8CAA8C;AAC9C,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,IAAY;IACvD,OAAO,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;AAC5C,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,OAAO,aAAa,CAAC,IAAI,GAAG,CAAC,KAAK,IAAI,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAC7D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAwB,EAAE,IAAY;IACnE,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QACvC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC;gBACH,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YACnC,CAAC;YAAC,MAAM,CAAC;gBACP,mCAAmC;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,YAAY,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAA;AAC1D,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { AgentStatus } from './port.js';
2
+ /** 状态分类:所有调用点只消费这个结果,避免各自解释 provider 字段。 */
3
+ export type AgentVerdict = 'keep' | 'lost' | 'transfer' | 'unknown';
4
+ export declare function classifyAgentStatus(status: AgentStatus | null): AgentVerdict;
5
+ /** 是否属于「进程还活着」的 keep 状态(blocked 也算活着)。 */
6
+ export declare function isAliveStatus(status: AgentStatus | null): boolean;
7
+ /** 取多个时间戳里最新的一个;ISO 字符串可直接按字典序比较。 */
8
+ export declare function latestActivityAt(...candidates: (string | undefined)[]): string | undefined;
package/dist/status.js ADDED
@@ -0,0 +1,22 @@
1
+ export function classifyAgentStatus(status) {
2
+ if (!status)
3
+ return 'unknown';
4
+ if (status.phase === 'lost')
5
+ return 'lost';
6
+ if (status.phase === 'exited')
7
+ return status.acceptingInput ? 'keep' : 'transfer';
8
+ if (status.phase === 'blocked')
9
+ return 'keep';
10
+ if (status.phase === 'unknown')
11
+ return 'unknown';
12
+ return 'keep'; // starting / running
13
+ }
14
+ /** 是否属于「进程还活着」的 keep 状态(blocked 也算活着)。 */
15
+ export function isAliveStatus(status) {
16
+ return Boolean(status && status.alive && (status.phase === 'running' || status.phase === 'blocked' || status.phase === 'starting'));
17
+ }
18
+ /** 取多个时间戳里最新的一个;ISO 字符串可直接按字典序比较。 */
19
+ export function latestActivityAt(...candidates) {
20
+ return candidates.filter((value) => Boolean(value)).sort().at(-1);
21
+ }
22
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../packages/runtime/src/status.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,mBAAmB,CAAC,MAA0B;IAC5D,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,IAAI,MAAM,CAAC,KAAK,KAAK,MAAM;QAAE,OAAO,MAAM,CAAA;IAC1C,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAA;IACjF,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IAC7C,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAChD,OAAO,MAAM,CAAA,CAAC,qBAAqB;AACrC,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,aAAa,CAAC,MAA0B;IACtD,OAAO,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAA;AACrI,CAAC;AAED,qCAAqC;AACrC,MAAM,UAAU,gBAAgB,CAAC,GAAG,UAAkC;IACpE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;AACpF,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { CommandRunner } from '@router-agent/infra/process.js';
2
+ import { type WorkspaceInfo, type WorkspaceProvider, type WorkspaceRequest, type Workspace } from '../port.js';
3
+ export interface GitWorktreeProviderOptions {
4
+ git: CommandRunner;
5
+ /** 允许创建工作区的根目录;用于防止路径越界。为空表示不限制。 */
6
+ worktreesRoot?: string;
7
+ }
8
+ /**
9
+ * 纯 git worktree 实现:只负责隔离目录与分支,不负责启动 Agent。
10
+ * 所有位置参数前加 `--`,分支名先过 check-ref-format,避免选项注入。
11
+ */
12
+ export declare class GitWorktreeProvider implements WorkspaceProvider {
13
+ readonly id = "git-worktree";
14
+ private git;
15
+ private worktreesRoot?;
16
+ constructor(opts: GitWorktreeProviderOptions);
17
+ create(req: WorkspaceRequest): Promise<Workspace>;
18
+ inspect(ref: string): Promise<WorkspaceInfo | null>;
19
+ /**
20
+ * 删除检出。
21
+ *
22
+ * `git worktree remove` 必须在 git 仓库上下文里跑,否则会报
23
+ * `fatal: not a git repository`(进程 cwd 通常不是仓库)。
24
+ * 所以优先用源仓库当 cwd;拿不到仓库时退回检出目录自己。
25
+ */
26
+ remove(ref: string, opts?: {
27
+ force?: boolean;
28
+ repo?: string;
29
+ }): Promise<void>;
30
+ private removeFailed;
31
+ /** 删除本次新建的分支;只用于 launch 失败回滚。 */
32
+ deleteBranch(branch: string, repoPath: string): Promise<void>;
33
+ version(): Promise<string | null>;
34
+ /** 从 worktree 的 origin 解析 GitHub owner/repo;解析失败返回 null。 */
35
+ remoteRepo(workdir: string): Promise<string | null>;
36
+ private branchExists;
37
+ private assertBranch;
38
+ private repoPathOf;
39
+ /** 宽松版:拿不到本地仓库绝对路径时返回 null(不抛错,交给调用方兜底)。 */
40
+ private repoPathOrNull;
41
+ private assertWorkdir;
42
+ }
43
+ /** 解析 GitHub remote:支持 git@github.com:o/r.git 与 https://github.com/o/r.git。 */
44
+ export declare function parseGitHubRepo(remote: string): string | null;
@@ -0,0 +1,183 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { isAbsolute, resolve } from 'node:path';
3
+ import { WorkerRuntimeError } from '../port.js';
4
+ /**
5
+ * 纯 git worktree 实现:只负责隔离目录与分支,不负责启动 Agent。
6
+ * 所有位置参数前加 `--`,分支名先过 check-ref-format,避免选项注入。
7
+ */
8
+ export class GitWorktreeProvider {
9
+ id = 'git-worktree';
10
+ git;
11
+ worktreesRoot;
12
+ constructor(opts) {
13
+ this.git = opts.git;
14
+ this.worktreesRoot = opts.worktreesRoot ? resolve(opts.worktreesRoot) : undefined;
15
+ }
16
+ async create(req) {
17
+ const repoPath = this.repoPathOf(req.repo);
18
+ const base = req.baseBranch.trim();
19
+ const requestedBranch = req.workBranch.trim();
20
+ if (!base)
21
+ throw new WorkerRuntimeError('workspace:missing-branch', '起始分支为空,拒绝创建 worktree');
22
+ if (!requestedBranch)
23
+ throw new WorkerRuntimeError('workspace:missing-branch', '工作分支为空,拒绝创建 worktree');
24
+ await this.assertBranch(base, repoPath);
25
+ const requestedWorkdir = this.assertWorkdir(req.workdir);
26
+ // 同名分支/目录已存在时加数字后缀,避免失联后重新派工永远撞冲突。
27
+ let branch = requestedBranch;
28
+ let workdir = requestedWorkdir;
29
+ let free = false;
30
+ for (let i = 1; i <= 20; i += 1) {
31
+ const exists = await this.branchExists(repoPath, branch);
32
+ if (!exists && !existsSync(workdir)) {
33
+ free = true;
34
+ break;
35
+ }
36
+ branch = `${requestedBranch}-${i + 1}`;
37
+ workdir = `${requestedWorkdir}-${i + 1}`;
38
+ }
39
+ if (!free) {
40
+ throw new WorkerRuntimeError('workspace:create-failed', `分支/目录已被占用,无法创建工作区: ${requestedBranch}`, {
41
+ detail: { workdir: requestedWorkdir, branch: requestedBranch },
42
+ });
43
+ }
44
+ await this.assertBranch(branch, repoPath);
45
+ const res = await this.git.run(['worktree', 'add', '-b', branch, '--', workdir, base], { cwd: repoPath });
46
+ if (res.code !== 0) {
47
+ throw new WorkerRuntimeError('workspace:create-failed', `git worktree add 失败: ${res.stderr || res.stdout}`, {
48
+ cause: res.stderr || res.stdout,
49
+ detail: { workdir, branch, repo: repoPath },
50
+ });
51
+ }
52
+ const actual = await this.git.run(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: workdir });
53
+ return {
54
+ workspaceRef: workdir,
55
+ workdir,
56
+ branch: actual.code === 0 ? actual.stdout.trim() : branch,
57
+ };
58
+ }
59
+ async inspect(ref) {
60
+ const workdir = this.assertWorkdir(ref);
61
+ const branchRes = await this.git.run(['-C', workdir, 'rev-parse', '--abbrev-ref', 'HEAD']);
62
+ if (branchRes.code !== 0)
63
+ return null;
64
+ const statusRes = await this.git.run(['-C', workdir, 'status', '--porcelain']);
65
+ return {
66
+ workdir,
67
+ branch: branchRes.stdout.trim(),
68
+ ...(statusRes.code === 0 ? { dirty: statusRes.stdout.trim().length > 0 } : {}),
69
+ };
70
+ }
71
+ /**
72
+ * 删除检出。
73
+ *
74
+ * `git worktree remove` 必须在 git 仓库上下文里跑,否则会报
75
+ * `fatal: not a git repository`(进程 cwd 通常不是仓库)。
76
+ * 所以优先用源仓库当 cwd;拿不到仓库时退回检出目录自己。
77
+ */
78
+ async remove(ref, opts = {}) {
79
+ const workdir = this.assertWorkdir(ref);
80
+ // 源仓库当 git 上下文;拿不到就用检出目录自己(worktree 本身也是 git 仓库)。
81
+ const repo = this.repoPathOrNull(opts.repo);
82
+ const cwd = repo ?? workdir;
83
+ // 依次尝试:源仓库 → 检出目录 → (允许时)强制删除。
84
+ const candidates = [
85
+ { argv: ['worktree', 'remove', '--', workdir], cwd },
86
+ ];
87
+ if (cwd !== workdir) {
88
+ candidates.push({ argv: ['worktree', 'remove', '--', workdir], cwd: workdir });
89
+ }
90
+ if (opts.force) {
91
+ candidates.push({ argv: ['worktree', 'remove', '--force', '--', workdir], cwd });
92
+ }
93
+ let last = null;
94
+ for (const candidate of candidates) {
95
+ const res = await this.git.run(candidate.argv, { cwd: candidate.cwd });
96
+ if (res.code === 0)
97
+ return;
98
+ last = res;
99
+ }
100
+ throw this.removeFailed(workdir, repo, last ?? { code: 1, stdout: '', stderr: '' });
101
+ }
102
+ removeFailed(workdir, repo, res) {
103
+ return new WorkerRuntimeError('workspace:remove-failed', `git worktree remove 失败: ${res.stderr || res.stdout}`, {
104
+ cause: res.stderr || res.stdout,
105
+ detail: { workdir, ...(repo ? { repo } : {}) },
106
+ });
107
+ }
108
+ /** 删除本次新建的分支;只用于 launch 失败回滚。 */
109
+ async deleteBranch(branch, repoPath) {
110
+ const path = this.repoPathOf(repoPath);
111
+ await this.assertBranch(branch, path);
112
+ const res = await this.git.run(['branch', '-D', '--', branch], { cwd: path });
113
+ if (res.code !== 0) {
114
+ throw new WorkerRuntimeError('workspace:remove-failed', `git branch -D 失败: ${res.stderr || res.stdout}`, {
115
+ cause: res.stderr || res.stdout,
116
+ detail: { branch, repo: path },
117
+ });
118
+ }
119
+ }
120
+ async version() {
121
+ const res = await this.git.run(['--version']);
122
+ return res.code === 0 ? res.stdout.trim() : null;
123
+ }
124
+ /** 从 worktree 的 origin 解析 GitHub owner/repo;解析失败返回 null。 */
125
+ async remoteRepo(workdir) {
126
+ const path = this.assertWorkdir(workdir);
127
+ const res = await this.git.run(['remote', 'get-url', 'origin'], { cwd: path });
128
+ if (res.code !== 0)
129
+ return null;
130
+ return parseGitHubRepo(res.stdout.trim());
131
+ }
132
+ async branchExists(repoPath, branch) {
133
+ const res = await this.git.run(['show-ref', '--verify', '--quiet', `refs/heads/${branch}`], { cwd: repoPath });
134
+ return res.code === 0;
135
+ }
136
+ async assertBranch(branch, repoPath) {
137
+ if (branch.startsWith('-')) {
138
+ throw new WorkerRuntimeError('workspace:missing-branch', `非法分支名: ${branch}`);
139
+ }
140
+ const res = await this.git.run(['check-ref-format', '--branch', branch], { cwd: repoPath });
141
+ if (res.code !== 0) {
142
+ throw new WorkerRuntimeError('workspace:missing-branch', `非法分支名: ${branch}`);
143
+ }
144
+ }
145
+ repoPathOf(repo) {
146
+ const path = repo.trim().replace(/^(id|name|path):/, '');
147
+ if (!path || !isAbsolute(path)) {
148
+ throw new WorkerRuntimeError('workspace:missing-repo', `git worktree 需要本地仓库绝对路径(path:/abs),收到: ${repo}`);
149
+ }
150
+ return resolve(path);
151
+ }
152
+ /** 宽松版:拿不到本地仓库绝对路径时返回 null(不抛错,交给调用方兜底)。 */
153
+ repoPathOrNull(repo) {
154
+ if (!repo)
155
+ return null;
156
+ const path = repo.trim().replace(/^(id|name|path):/, '');
157
+ if (!path || !isAbsolute(path))
158
+ return null;
159
+ return resolve(path);
160
+ }
161
+ assertWorkdir(input) {
162
+ if (!input)
163
+ throw new WorkerRuntimeError('workspace:create-failed', 'workdir 为空');
164
+ if (input.includes('..'))
165
+ throw new WorkerRuntimeError('workspace:create-failed', `workdir 不允许包含 ..: ${input}`);
166
+ const workdir = isAbsolute(input) ? resolve(input) : resolve(input);
167
+ if (this.worktreesRoot && !workdir.startsWith(`${this.worktreesRoot}/`) && workdir !== this.worktreesRoot) {
168
+ throw new WorkerRuntimeError('workspace:create-failed', `workdir 越界: ${workdir}`);
169
+ }
170
+ return workdir;
171
+ }
172
+ }
173
+ /** 解析 GitHub remote:支持 git@github.com:o/r.git 与 https://github.com/o/r.git。 */
174
+ export function parseGitHubRepo(remote) {
175
+ const ssh = remote.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
176
+ if (ssh)
177
+ return `${ssh[1]}/${ssh[2]}`;
178
+ const https = remote.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
179
+ if (https)
180
+ return `${https[1]}/${https[2]}`;
181
+ return null;
182
+ }
183
+ //# sourceMappingURL=git-worktree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-worktree.js","sourceRoot":"","sources":["../../../../../packages/runtime/src/workspaces/git-worktree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * 面板「批量清理工作区」的中立视图与端口。
3
+ *
4
+ * 这里只放纯类型与纯函数,不能依赖任何 Node 内置模块 —— Web 面板会把它打进前端包。
5
+ * 服务层负责扫磁盘、按运行时分组;HTTP 层只透传这些结构。
6
+ */
7
+ import type { Stage } from '@router-agent/core/model.js';
8
+ /** 一个仍在磁盘上的工单工作区。 */
9
+ export interface WorkspaceEntryView {
10
+ assignmentId: string;
11
+ ticketId: string;
12
+ title: string;
13
+ stage: Stage;
14
+ runtimeId: string;
15
+ workdir: string;
16
+ startedAt: string;
17
+ /** 工单关闭时间(ISO);未关闭为空串。定时任务用它算保留期。 */
18
+ closedAt: string;
19
+ /** 仅「工单已关闭 + 目录仍存在 + 当前运行时可回收」时为 true。 */
20
+ cleanable: boolean;
21
+ /** 不可清理的原因;cleanable=true 时为空。 */
22
+ reason?: string;
23
+ }
24
+ /** 按运行时聚合的工作区清单。 */
25
+ export interface WorkspaceRuntimeGroupView {
26
+ runtimeId: string;
27
+ /** 面板展示用短名,例如 orca / claude-code / Claude Code (ACP)。 */
28
+ label: string;
29
+ isDefault: boolean;
30
+ /** 当前服务是否注册了该运行时且支持回收。 */
31
+ supported: boolean;
32
+ entries: WorkspaceEntryView[];
33
+ cleanableCount: number;
34
+ }
35
+ export interface WorkspaceOverviewView {
36
+ runtimes: WorkspaceRuntimeGroupView[];
37
+ /** 全部可清理的工作区数量。 */
38
+ cleanableCount: number;
39
+ }
40
+ export interface WorkspaceRemovalView {
41
+ ticketId: string;
42
+ workdir: string;
43
+ }
44
+ export interface WorkspaceSkipView extends WorkspaceRemovalView {
45
+ reason: string;
46
+ }
47
+ export interface WorkspaceCleanupResultView {
48
+ removed: WorkspaceRemovalView[];
49
+ skipped: WorkspaceSkipView[];
50
+ }
51
+ /** 面板勾选后提交的清理请求:优先按工单;只传 runtimeId 时清理该运行时下全部可清理项。 */
52
+ export interface WorkspaceCleanupRequest {
53
+ ticketIds?: string[];
54
+ runtimeId?: string;
55
+ }
56
+ /** HTTP API 依赖的面板端口;由 RouterService 实现。 */
57
+ export interface WorkspaceAdminPort {
58
+ list(): Promise<WorkspaceOverviewView>;
59
+ /**
60
+ * 按工单逐个清理:每个工单由它自己的运行时决定怎么回收
61
+ * (Orca 关 worktree、Claude Code 删 agent view、ACP 直接删 worktree)。
62
+ */
63
+ cleanup(request: WorkspaceCleanupRequest): Promise<WorkspaceCleanupResultView>;
64
+ }
65
+ /** 运行时短名:目录里的长 label(`orca · Orca worktree + 终端`)只取第一段。 */
66
+ export declare function workspaceRuntimeLabel(runtimeId: string): string;
@@ -0,0 +1,12 @@
1
+ import { ACP_AGENTS } from '../runtimes/acp-agents.js';
2
+ import { WORKER_RUNTIMES, normalizeRuntimeId } from '../runtimes/catalog.js';
3
+ /** 运行时短名:目录里的长 label(`orca · Orca worktree + 终端`)只取第一段。 */
4
+ export function workspaceRuntimeLabel(runtimeId) {
5
+ const id = normalizeRuntimeId(runtimeId);
6
+ const builtin = WORKER_RUNTIMES.find(runtime => runtime.id === id);
7
+ if (builtin)
8
+ return builtin.label.split(' · ')[0]?.trim() || id;
9
+ const agent = ACP_AGENTS.find(item => item.runtimeId === id);
10
+ return agent?.label ?? id;
11
+ }
12
+ //# sourceMappingURL=overview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overview.js","sourceRoot":"","sources":["../../../../../packages/runtime/src/workspaces/overview.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAmE5E,2DAA2D;AAC3D,MAAM,UAAU,qBAAqB,CAAC,SAAiB;IACrD,MAAM,EAAE,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;IAClE,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAC/D,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,KAAK,EAAE,CAAC,CAAA;IAC5D,OAAO,KAAK,EAAE,KAAK,IAAI,EAAE,CAAA;AAC3B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,38 @@
1
1
  {
2
2
  "name": "@router-agent/runtime",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.32",
4
+ "type": "module",
5
+ "description": "Worker 运行时 SDK:端口、注册表、插件协议与共享工具",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/blankPen/router-agent.git",
10
+ "directory": "packages/runtime"
11
+ },
12
+ "homepage": "https://github.com/blankPen/router-agent#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/blankPen/router-agent/issues"
15
+ },
16
+ "main": "./dist/index.js",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ },
22
+ "./*.js": "./dist/*.js",
23
+ "./*": "./dist/*"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "types": "./dist/index.d.ts",
29
+ "publishConfig": {
30
+ "access": "public",
31
+ "registry": "https://registry.npmjs.org/"
32
+ },
33
+ "dependencies": {
34
+ "@router-agent/core": "0.3.32",
35
+ "@router-agent/infra": "0.3.32",
36
+ "zod": "4.4.3"
37
+ }
38
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.