@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.
- package/CHANGELOG.md +44 -0
- package/README.md +197 -26
- package/codex-plugin/plugins/dsh/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/plugins/dsh/skills/dsh-collab/SKILL.md +102 -6
- package/codex-plugin/plugins/dsh/skills/dsh-collab/scripts/review.mjs +91 -6
- package/codex-plugin/plugins/dsh/skills/dsh-collab/scripts/task.mjs +571 -44
- package/docs/CODEX_DSH_APPROVAL_DELEGATION_PLAN.md +130 -0
- package/docs/CODEX_DSH_APPROVAL_M0_EVIDENCE.md +285 -0
- package/docs/CODEX_DSH_DELIVERY_CONTROL_PLAN.md +81 -0
- package/docs/CODEX_DSH_MODEL_ROUTING_PLAN.md +75 -0
- package/docs/CODEX_DSH_TASK_REPLY_PLAN.md +206 -0
- package/docs/DSH_APPROVAL_DELEGATION.md +142 -0
- package/docs/DSH_DELIVERY_M0_EVIDENCE.md +69 -0
- package/docs/DSH_MODEL_ROUTING_M0_EVIDENCE.md +166 -0
- package/docs/DSH_TO_CODEX_INBOX.md +167 -0
- package/docs/DSH_TO_CODEX_MESSAGING_PLAN.md +140 -0
- package/docs/codex-desktop-attachment-evidence.md +116 -0
- package/harness/audit-tail.mjs +32 -0
- package/harness/codex-approvals.mjs +1078 -0
- package/harness/codex-associations.mjs +297 -0
- package/harness/codex-outbox.mjs +432 -0
- package/harness/dsh-bridge.mjs +2429 -167
- package/harness/fs-lock.mjs +208 -0
- package/harness/model-route-host.mjs +530 -0
- package/harness/model-routing.mjs +500 -0
- package/harness/session-observer.mjs +217 -0
- package/harness/session-target.mjs +291 -2
- package/harness/task-table.mjs +34 -0
- package/mcp/codex-desktop-probe.mjs +407 -0
- package/mcp/codex-inbox.mjs +271 -0
- package/mcp/dsh-mcp.mjs +616 -24
- 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 (
|
|
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
|
|