@whaletalk/dsh-codex-collab 0.1.7 → 0.1.10

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 (32) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +197 -26
  3. package/codex-plugin/plugins/dsh/.codex-plugin/plugin.json +1 -1
  4. package/codex-plugin/plugins/dsh/skills/dsh-collab/SKILL.md +102 -6
  5. package/codex-plugin/plugins/dsh/skills/dsh-collab/scripts/review.mjs +91 -6
  6. package/codex-plugin/plugins/dsh/skills/dsh-collab/scripts/task.mjs +571 -44
  7. package/docs/CODEX_DSH_APPROVAL_DELEGATION_PLAN.md +130 -0
  8. package/docs/CODEX_DSH_APPROVAL_M0_EVIDENCE.md +285 -0
  9. package/docs/CODEX_DSH_DELIVERY_CONTROL_PLAN.md +81 -0
  10. package/docs/CODEX_DSH_MODEL_ROUTING_PLAN.md +75 -0
  11. package/docs/CODEX_DSH_TASK_REPLY_PLAN.md +206 -0
  12. package/docs/DSH_APPROVAL_DELEGATION.md +142 -0
  13. package/docs/DSH_DELIVERY_M0_EVIDENCE.md +69 -0
  14. package/docs/DSH_MODEL_ROUTING_M0_EVIDENCE.md +166 -0
  15. package/docs/DSH_TO_CODEX_INBOX.md +167 -0
  16. package/docs/DSH_TO_CODEX_MESSAGING_PLAN.md +140 -0
  17. package/docs/codex-desktop-attachment-evidence.md +116 -0
  18. package/harness/audit-tail.mjs +32 -0
  19. package/harness/codex-approvals.mjs +1078 -0
  20. package/harness/codex-associations.mjs +297 -0
  21. package/harness/codex-outbox.mjs +432 -0
  22. package/harness/dsh-bridge.mjs +2429 -167
  23. package/harness/fs-lock.mjs +208 -0
  24. package/harness/model-route-host.mjs +530 -0
  25. package/harness/model-routing.mjs +500 -0
  26. package/harness/session-observer.mjs +217 -0
  27. package/harness/session-target.mjs +291 -2
  28. package/harness/task-table.mjs +34 -0
  29. package/mcp/codex-desktop-probe.mjs +407 -0
  30. package/mcp/codex-inbox.mjs +271 -0
  31. package/mcp/dsh-mcp.mjs +616 -24
  32. package/package.json +4 -2
@@ -2,6 +2,13 @@
2
2
  // Codex → DeepSeek Harness 评审脚本:把代码变更 diff 交给独立评审子代理。
3
3
  // 评审员会读 cwd 真实文件、静态审查、并实际运行构建/测试验证可跑通。
4
4
  // 用法: node review.mjs --cwd <目录> [--diff <diff 文本|@文件|git|git-staged>] [--focus <审查重点>] [--timeout 秒]
5
+ // [--origin <Codex 对话 id>] [--model auto|fast|pro|default|<精确 model id>] [--provider <provider id>]
6
+ // [--model-fallback | --no-model-fallback]
7
+ //
8
+ // 评审与编码使用**同一套模型契约**(服务端任务 kind=review、执行目标 worker):
9
+ // - 默认 auto(优先兼容 DeepSeek);
10
+ // - 显式 provider+model 是一对身份,必须成对发送;
11
+ // - 旧网关没有 capabilities.modelRouting / originBinding 时 → **零派活**拒绝,绝不忽略新参数照常执行。
5
12
  import fs from 'node:fs'
6
13
  import http from 'node:http'
7
14
  import { execFileSync } from 'node:child_process'
@@ -9,7 +16,7 @@ import { execFileSync } from 'node:child_process'
9
16
  const GATEWAY = process.env.DSH_BRIDGE_URL || 'http://127.0.0.1:3080'
10
17
 
11
18
  function parseArgs(argv) {
12
- const out = { timeoutSec: 900, cwd: '', diff: '', focus: '' }
19
+ const out = { timeoutSec: 900, cwd: '', diff: '', focus: '', origin: '', model: '', provider: '', valueError: undefined }
13
20
  const rest = []
14
21
  for (let i = 0; i < argv.length; i++) {
15
22
  const a = argv[i]
@@ -17,6 +24,25 @@ function parseArgs(argv) {
17
24
  else if (a === '--diff') { out.diff = argv[++i] || '' }
18
25
  else if (a === '--focus') { out.focus = argv[++i] || '' }
19
26
  else if (a === '--timeout') { const v = Number(argv[++i]); if (Number.isFinite(v)) out.timeoutSec = v }
27
+ // ---- 模型路由(与 task.mjs 同一契约)----
28
+ else if (a === '--model') {
29
+ const v = argv[++i]
30
+ if (v === undefined || v.trim() === '' || v.startsWith('--')) { out.valueError = '--model 需要一个值(auto/fast/pro/default 或精确 model id)'; continue }
31
+ out.model = v.trim()
32
+ }
33
+ else if (a === '--provider') {
34
+ const v = argv[++i]
35
+ if (v === undefined || v.trim() === '' || v.startsWith('--')) { out.valueError = '--provider 需要一个值(已注册的 provider id)'; continue }
36
+ out.provider = v.trim()
37
+ }
38
+ else if (a === '--model-fallback') { out.modelFallback = true }
39
+ else if (a === '--no-model-fallback') { out.modelFallback = false }
40
+ // ---- 来源(显式,跨回合精确收取)----
41
+ else if (a === '--origin') {
42
+ const v = argv[++i]
43
+ if (v === undefined || v.trim() === '' || v.startsWith('--')) { out.valueError = '--origin 需要一个值(Codex 对话 id)'; continue }
44
+ out.origin = v.trim()
45
+ }
20
46
  else { rest.push(a) }
21
47
  }
22
48
  return out
@@ -76,41 +102,100 @@ function resolveDiff(cwd, diffSpec) {
76
102
  return diffSpec
77
103
  }
78
104
 
105
+ /** 来源解析:显式 --origin 优先,其次本次调用的 CODEX_THREAD_ID(绝不推断"最近对话")。 */
106
+ function resolveOrigin(opts) {
107
+ if (typeof opts.origin === 'string' && opts.origin.trim() !== '') return { originThreadId: opts.origin.trim(), source: 'explicit' }
108
+ const fromEnv = typeof process.env.CODEX_THREAD_ID === 'string' ? process.env.CODEX_THREAD_ID.trim() : ''
109
+ if (fromEnv !== '') return { originThreadId: fromEnv, source: 'env' }
110
+ return { source: 'none' }
111
+ }
112
+
113
+ /** 只读能力探测(GET /status,不派活)。 */
114
+ async function capabilityOf() {
115
+ try {
116
+ const res = await request('GET', GATEWAY + '/api/dsh-bridge/status')
117
+ const body = res.json
118
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: 'status-http-' + res.status }
119
+ if (body === null || typeof body !== 'object' || body.capabilities === null || typeof body.capabilities !== 'object') return { ok: false, reason: 'no-capabilities' }
120
+ return { ok: true, capabilities: body.capabilities }
121
+ } catch (e) {
122
+ return { ok: false, reason: 'status-unreachable' }
123
+ }
124
+ }
125
+
79
126
  async function main() {
80
127
  const opts = parseArgs(process.argv.slice(2))
128
+ if (opts.valueError !== undefined) { console.error('review.mjs: ' + opts.valueError); finish(2) }
81
129
  const cwd = (opts.cwd || '').trim()
82
130
  if (!cwd) {
83
- console.error('用法: node review.mjs --cwd <目录> [--diff git|git-staged|@文件|"diff 文本"] [--focus <重点>] [--timeout 秒]')
131
+ console.error('用法: node review.mjs --cwd <目录> [--diff git|git-staged|@文件|"diff 文本"] [--focus <重点>] [--timeout 秒] [--origin <id>] [--model ...] [--provider <id>] [--model-fallback|--no-model-fallback]')
84
132
  finish(2)
85
133
  }
86
134
  if (!fs.existsSync(cwd)) {
87
135
  console.error('review.mjs: 目录不存在: ' + cwd)
88
136
  finish(2)
89
137
  }
138
+ const origin = resolveOrigin(opts)
139
+ const usesModelControl = opts.model !== '' || opts.provider !== '' || opts.modelFallback !== undefined
140
+ const cap = await capabilityOf()
141
+ if (!cap.ok) {
142
+ // 连能力都读不到:派活前拒绝(旧网关/未装载插件),绝不忽略新参数照常执行。
143
+ console.error('review.mjs: 无法确认网关能力(' + cap.reason + ')——评审派活拒绝,未执行任何投递。')
144
+ finish(2)
145
+ }
146
+ if (origin.originThreadId === undefined) {
147
+ console.error('review.mjs: 评审派活需要**显式来源**(--origin 或本次调用的 CODEX_THREAD_ID),否则无法跨回合精确收取。')
148
+ finish(2)
149
+ }
150
+ if (cap.capabilities.originBinding === undefined || cap.capabilities.originBinding.supported !== true) {
151
+ console.error('review.mjs: 该网关不支持持久来源绑定(capabilities.originBinding)——评审派活拒绝,未执行任何投递。')
152
+ finish(2)
153
+ }
154
+ if (usesModelControl && (cap.capabilities.modelRouting === undefined || cap.capabilities.modelRouting.supported !== true)) {
155
+ console.error('review.mjs: 该网关未声明 capabilities.modelRouting(旧网关)——带模型参数的评审派活拒绝,未执行任何投递。')
156
+ finish(2)
157
+ }
90
158
  const diff = resolveDiff(cwd, opts.diff)
91
- console.log('[review] diff 长度: ' + diff.length + ' 字符')
159
+ console.log('[review] diff 长度: ' + diff.length + ' 字符;来源 ' + origin.originThreadId + '(' + origin.source + ')')
92
160
  const posted = await request('POST', GATEWAY + '/api/dsh-bridge/review', {
93
161
  cwd, diff,
94
162
  ...(opts.focus ? { focus: opts.focus } : {}),
163
+ originThreadId: origin.originThreadId,
164
+ ...(opts.provider !== '' ? { provider: opts.provider } : {}),
165
+ ...(opts.model !== '' ? { model: opts.model } : {}),
166
+ ...(opts.modelFallback === undefined ? {} : { modelFallback: opts.modelFallback }),
95
167
  })
96
168
  const task = posted.json
97
169
  console.log(JSON.stringify({ phase: 'submitted', ...task }))
98
- if (posted.status !== 202 || task.status === 'error') finish(posted.status === 202 ? 0 : 1)
170
+ if (task !== null && typeof task === 'object' && task.taskId !== undefined && task.originThreadId !== origin.originThreadId) {
171
+ // 网关接受了请求但没回显我们的来源:绑定**未被确认**(可能已执行)→ 不重发。
172
+ console.error('review.mjs: 网关未回显匹配的来源 originThreadId —— 绑定未被确认,**不要重发**。')
173
+ finish(1)
174
+ }
175
+ if (posted.status !== 202 || task === null || typeof task !== 'object' || task.status === 'error') finish(posted.status === 202 ? 0 : 1)
99
176
 
100
177
  const deadline = Date.now() + opts.timeoutSec * 1000
101
178
  while (Date.now() < deadline) {
102
179
  await sleep(3000)
103
- const res = await request('GET', GATEWAY + '/api/dsh-bridge/status?taskId=' + encodeURIComponent(task.taskId))
180
+ const res = await request('GET', GATEWAY + '/api/dsh-bridge/status?taskId=' + encodeURIComponent(task.taskId) + '&originThreadId=' + encodeURIComponent(origin.originThreadId))
104
181
  const status = res.json
182
+ if (status === null || typeof status !== 'object') continue
183
+ if (status.status === 'unknown' && status.recovered === true) {
184
+ // 执行状态未知:如实报告,绝不重发、不无限等
185
+ console.error('review.mjs: 任务执行状态未确认(网关重启/重载),不重发;原任务: ' + task.taskId)
186
+ console.log(JSON.stringify({ phase: 'unconfirmed', ...status }))
187
+ finish(1)
188
+ }
105
189
  if (status.status === 'done' || status.status === 'error' || status.status === 'cancelled') {
106
190
  console.log(JSON.stringify({ phase: 'finished', ...status }))
191
+ if (status.route !== undefined) console.log('[review] route: ' + JSON.stringify(status.route))
107
192
  console.log('=== 评审报告 ===')
108
193
  console.log(status.result || status.error || '(无内容)')
109
194
  finish(status.status === 'done' ? 0 : 1)
110
195
  }
111
196
  }
112
197
  console.error('等待超时(' + opts.timeoutSec + 's),评审仍在后台执行:')
113
- console.error(GATEWAY + '/api/dsh-bridge/status?taskId=' + encodeURIComponent(task.taskId))
198
+ console.error(GATEWAY + '/api/dsh-bridge/status?taskId=' + encodeURIComponent(task.taskId) + '&originThreadId=' + encodeURIComponent(origin.originThreadId))
114
199
  finish(3)
115
200
  }
116
201