@whaletalk/dsh-codex-collab 0.1.6 → 0.1.9
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 +35 -0
- package/README.md +107 -7
- package/codex-plugin/plugins/dsh/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/plugins/dsh/skills/dsh-collab/SKILL.md +50 -7
- package/codex-plugin/plugins/dsh/skills/dsh-collab/scripts/task.mjs +175 -15
- package/docs/CODEX_DSH_TASK_REPLY_PLAN.md +206 -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/codex-associations.mjs +265 -0
- package/harness/codex-outbox.mjs +432 -0
- package/harness/dsh-bridge.mjs +686 -43
- package/harness/fs-lock.mjs +208 -0
- package/harness/session-target.mjs +187 -0
- package/harness/task-table.mjs +32 -0
- package/mcp/codex-desktop-probe.mjs +407 -0
- package/mcp/codex-inbox.mjs +271 -0
- package/mcp/dsh-mcp.mjs +160 -20
- package/package.json +3 -1
package/mcp/dsh-mcp.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// 客户端(Secure MCP Tunnel / cloudflared)桥接给 ChatGPT 等远程产品。
|
|
7
7
|
const GATEWAY = process.env.DSH_BRIDGE_URL || 'http://127.0.0.1:3080'
|
|
8
8
|
const SERVER_NAME = 'dsh-bridge'
|
|
9
|
-
const SERVER_VERSION = '0.1.
|
|
9
|
+
const SERVER_VERSION = '0.1.9'
|
|
10
10
|
const PROTOCOL_VERSION = '2024-11-05'
|
|
11
11
|
|
|
12
12
|
const log = (...args) => console.error('[dsh-mcp]', ...args)
|
|
@@ -20,17 +20,24 @@ const tools = [
|
|
|
20
20
|
'参数: instruction(必填,完整中文指令:目标/产出目录/验收标准/约束), cwd(工作目录,默认 D:\\Harness,通常传当前项目目录), ' +
|
|
21
21
|
'lane(并行通道名,默认 main), model(fast=deepseek-v4-flash 快而省 / pro=deepseek-v4-pro 质量优先), ' +
|
|
22
22
|
'sessionId(已有会话 id), sessionQuery(按标题/内容搜已有会话,需唯一命中,否则返回候选), deliver(queue=排队(默认) / steer=插入当前回合), ' +
|
|
23
|
-
'
|
|
23
|
+
'dryRun(true 时只解析目标并回显, 不投递任何消息), ' +
|
|
24
|
+
'originThreadId(发起本任务的 Codex 对话 id,必须显式传入), ' +
|
|
25
|
+
'wait(默认 true,阻塞至任务完成并返回 DeepSeek 汇报), timeoutSec(默认 600,最长 900)。' +
|
|
26
|
+
'每个回合派活都要显式传 originThreadId=当前对话 id(传入本工具调用的那个对话):它把任务与发起对话持久绑定,' +
|
|
27
|
+
'之后任何回合都能用 originThreadId+taskId 精确取回这次任务的结果。**不要**用常驻进程环境变量或"最近对话"推断来源。' +
|
|
28
|
+
'注意: 验证"这条查询会命中哪条会话"必须用 dryRun 或 dsh_sessions——**绝不要投递一条消息去试探**(那会污染用户会话)。',
|
|
24
29
|
inputSchema: {
|
|
25
30
|
type: 'object',
|
|
26
31
|
properties: {
|
|
27
|
-
instruction: { type: 'string', description: '完整中文编码指令(目标、产出目录、验收标准、约束)。' },
|
|
32
|
+
instruction: { type: 'string', description: '完整中文编码指令(目标、产出目录、验收标准、约束)。dryRun 时可省略。' },
|
|
28
33
|
cwd: { type: 'string', description: '工作目录(绝对路径)。默认 D:\\Harness。与 sessionId/sessionQuery 互斥。' },
|
|
29
34
|
lane: { type: 'string', description: '并行通道名(默认 main)。' },
|
|
30
35
|
model: { type: 'string', enum: ['fast', 'pro'], description: '模型别名:fast=deepseek-v4-flash,pro=deepseek-v4-pro。' },
|
|
31
36
|
sessionId: { type: 'string', description: '已有会话 id:把指令投递进这条会话,而不是新建子代理。' },
|
|
32
|
-
sessionQuery: { type: 'string', description: '
|
|
37
|
+
sessionQuery: { type: 'string', description: '按标题或内容搜索已有会话;唯一命中才派发,多条或落空会返回 candidates 让你改用 sessionId。' },
|
|
33
38
|
deliver: { type: 'string', enum: ['queue', 'steer'], description: '投递方式:queue 排队(默认),steer 插入当前回合。' },
|
|
39
|
+
dryRun: { type: 'boolean', description: '只解析目标并回显(含命中路径 matchedBy 与候选),不投递任何消息。用来确认命中,替代"发消息探测"。' },
|
|
40
|
+
originThreadId: { type: 'string', description: '发起本任务的 Codex 对话 id(来源关联,显式传入)。用于之后按 originThreadId+taskId 精确收取结果。' },
|
|
34
41
|
wait: { type: 'boolean', description: '默认 true:阻塞直到任务完成并返回汇报。' },
|
|
35
42
|
timeoutSec: { type: 'number', description: '最长等待秒数(默认 600,最大 900)。' },
|
|
36
43
|
},
|
|
@@ -54,21 +61,29 @@ const tools = [
|
|
|
54
61
|
},
|
|
55
62
|
{
|
|
56
63
|
name: 'dsh_task_status',
|
|
57
|
-
description:
|
|
64
|
+
description:
|
|
65
|
+
'查询 DeepSeek 协作任务的状态与结果。两种用法:' +
|
|
66
|
+
'(1) taskId + originThreadId → 精确取这次任务的状态/结果(跨回合、网关重载后都能按来源从持久关联记录恢复;' +
|
|
67
|
+
'恢复到的未完成任务会返回 status=unknown 并明确"执行/交付状态未能确认、请勿重发");' +
|
|
68
|
+
'(2) 只给 originThreadId → 列出该对话登记过的任务,用来找回丢失的 taskId。' +
|
|
69
|
+
'originThreadId 必须显式传当前对话 id;只给 taskId 时保持旧的兼容查询。',
|
|
58
70
|
inputSchema: {
|
|
59
71
|
type: 'object',
|
|
60
|
-
properties: {
|
|
61
|
-
|
|
72
|
+
properties: {
|
|
73
|
+
taskId: { type: 'string', description: '任务 ID(dsh_task 返回)。与 originThreadId 一起用时为精确取值。' },
|
|
74
|
+
originThreadId: { type: 'string', description: '发起该任务的 Codex 对话 id(显式传入当前对话)。只给来源时列出该对话的任务。' },
|
|
75
|
+
},
|
|
62
76
|
},
|
|
63
77
|
},
|
|
64
78
|
{
|
|
65
79
|
name: 'dsh_task_cancel',
|
|
66
|
-
description: '取消一个运行中的 DeepSeek 协作任务。参数: taskId(必填), force(session 目标的任务默认拒绝取消,因为它会打断用户自己的回合;确认要打断时传 true)。',
|
|
80
|
+
description: '取消一个运行中的 DeepSeek 协作任务。参数: taskId(必填), force(session 目标的任务默认拒绝取消,因为它会打断用户自己的回合;确认要打断时传 true), originThreadId(可选,已关联任务必须传同一个来源)。',
|
|
67
81
|
inputSchema: {
|
|
68
82
|
type: 'object',
|
|
69
83
|
properties: {
|
|
70
84
|
taskId: { type: 'string', description: '任务 ID。' },
|
|
71
85
|
force: { type: 'boolean', description: '仅对投递进已有会话的任务有意义:true 才真正打断该会话。' },
|
|
86
|
+
originThreadId: { type: 'string', description: '该任务的来源对话 id(已关联任务必须一致,避免别的对话取消它)。' },
|
|
72
87
|
},
|
|
73
88
|
required: ['taskId'],
|
|
74
89
|
},
|
|
@@ -77,13 +92,14 @@ const tools = [
|
|
|
77
92
|
name: 'dsh_review',
|
|
78
93
|
description:
|
|
79
94
|
'让独立评审子代理审查某工作目录的代码变更。评审员会读磁盘真实文件、静态审查,并实际运行构建/测试验证可跑通,返回结构化评审报告。' +
|
|
80
|
-
'参数: cwd(必填), diff(变更 diff 文本), focus(审查重点), wait(默认 true), timeoutSec(默认 900)。',
|
|
95
|
+
'参数: cwd(必填), diff(变更 diff 文本), focus(审查重点), originThreadId(发起本次评审的当前对话 id,显式传入;评审同样产生 taskId,因此同样支持按 origin+taskId 收取), wait(默认 true), timeoutSec(默认 900)。',
|
|
81
96
|
inputSchema: {
|
|
82
97
|
type: 'object',
|
|
83
98
|
properties: {
|
|
84
99
|
cwd: { type: 'string', description: '评审目标所在工作目录(绝对路径)。' },
|
|
85
100
|
diff: { type: 'string', description: '变更 diff 文本(git diff 输出)。省略时评审员自行读取目录现状。' },
|
|
86
101
|
focus: { type: 'string', description: '审查重点。' },
|
|
102
|
+
originThreadId: { type: 'string', description: '发起本次评审的 Codex 对话 id(来源关联,显式传入当前对话)。' },
|
|
87
103
|
wait: { type: 'boolean', description: '默认 true:阻塞直到评审完成。' },
|
|
88
104
|
timeoutSec: { type: 'number', description: '最长等待秒数(默认 900)。' },
|
|
89
105
|
},
|
|
@@ -107,6 +123,82 @@ const tools = [
|
|
|
107
123
|
]
|
|
108
124
|
|
|
109
125
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
126
|
+
const MAX_ORIGIN_LENGTH = 200
|
|
127
|
+
|
|
128
|
+
/** 显式来源必须合法:非空字符串、≤200 字符、无控制字符。**不合法就拒绝,绝不丢掉或改用环境变量。** */
|
|
129
|
+
const isValidOrigin = (value) => typeof value === 'string'
|
|
130
|
+
&& value.trim() !== ''
|
|
131
|
+
&& value.trim().length <= MAX_ORIGIN_LENGTH
|
|
132
|
+
&& !/[\u0000-\u001f\u007f]/.test(value)
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 只读核验网关是否真的支持"持久来源绑定"。
|
|
136
|
+
* **不能靠包版本猜**:旧网关会忽略 originThreadId 并照常 accepted,从而产生未绑定任务。
|
|
137
|
+
* 这里要求网关在 /status(无参数)里明确上报 `capabilities.originBinding.supported === true`;
|
|
138
|
+
* 拿不到(旧网关 / 关联存储不可用)一律视为不支持。
|
|
139
|
+
*/
|
|
140
|
+
async function originBindingSupported() {
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(GATEWAY + '/api/dsh-bridge/status')
|
|
143
|
+
const body = await res.json()
|
|
144
|
+
return body !== null && typeof body === 'object'
|
|
145
|
+
&& body.capabilities !== null && typeof body.capabilities === 'object'
|
|
146
|
+
&& body.capabilities.originBinding !== null && typeof body.capabilities.originBinding === 'object'
|
|
147
|
+
&& body.capabilities.originBinding.supported === true
|
|
148
|
+
} catch (e) {
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const ORIGIN_UNSUPPORTED = {
|
|
154
|
+
ok: false,
|
|
155
|
+
reason: 'origin-binding-unsupported',
|
|
156
|
+
error: 'this gateway does not support persistent origin binding (it does not advertise capabilities.originBinding); refusing to dispatch an unbound task. Upgrade the dsh-codex-collab plugin in the DSH profile, or dispatch without originThreadId.',
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const INVALID_ORIGIN = {
|
|
160
|
+
ok: false,
|
|
161
|
+
reason: 'invalid-origin',
|
|
162
|
+
error: 'originThreadId must be a non-empty string (<= 200 characters, no control characters); omit it entirely for the legacy unbound mode',
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** 网关自己给出的"明确拒绝"reason:原样透传(这些不是"成功了但没确认")。 */
|
|
166
|
+
const REFUSAL_REASONS = new Set([
|
|
167
|
+
'origin-required',
|
|
168
|
+
'origin-mismatch',
|
|
169
|
+
'invalid-origin',
|
|
170
|
+
'unknown-task',
|
|
171
|
+
'execution-state-unconfirmed',
|
|
172
|
+
])
|
|
173
|
+
|
|
174
|
+
/** 网关必须**回显并匹配**我们请求的 originThreadId,才算确认了这次绑定/取值。 */
|
|
175
|
+
const originConfirmed = (payload, originThreadId) => payload !== null && typeof payload === 'object' && payload.originThreadId === originThreadId
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 能力检查通过 ≠ 这次绑定被确认:网关可能在中途换了处理者(重载/旧 handler),接受了请求却没写关联。
|
|
179
|
+
* 此时任务**可能已经执行**,所以既不能说成功,也绝不能重发;只如实上报未确认。
|
|
180
|
+
* 注意:只挑 taskId/status,**绝不**把网关响应里的 result 透出去。
|
|
181
|
+
*/
|
|
182
|
+
const unconfirmedPayload = (payload) => ({
|
|
183
|
+
ok: false,
|
|
184
|
+
phase: 'unconfirmed',
|
|
185
|
+
// 网关自称的 status 不可信(它可能压根没绑定):一律按"未确认"上报。
|
|
186
|
+
status: 'unknown',
|
|
187
|
+
reason: 'origin-not-confirmed',
|
|
188
|
+
resend: false,
|
|
189
|
+
error: 'the gateway accepted the request but did not confirm the source binding (no matching originThreadId in its response); it may have executed unbound — do not resend',
|
|
190
|
+
...(payload !== null && typeof payload === 'object' && typeof payload.taskId === 'string' ? { taskId: payload.taskId } : {}),
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
/** 解析显式来源:undefined=未传(旧模式);否则必须合法,不合法直接拒绝(不回落、不改用 ENV)。 */
|
|
194
|
+
function readOrigin(args) {
|
|
195
|
+
if (args === null || typeof args !== 'object') return { ok: true, originThreadId: undefined }
|
|
196
|
+
if (!Object.prototype.hasOwnProperty.call(args, 'originThreadId')) return { ok: true, originThreadId: undefined }
|
|
197
|
+
const raw = args.originThreadId
|
|
198
|
+
if (raw === undefined) return { ok: true, originThreadId: undefined }
|
|
199
|
+
if (!isValidOrigin(raw)) return { ok: false, value: INVALID_ORIGIN }
|
|
200
|
+
return { ok: true, originThreadId: raw.trim() }
|
|
201
|
+
}
|
|
110
202
|
|
|
111
203
|
async function callGateway(method, body) {
|
|
112
204
|
const res = await fetch(GATEWAY + method, {
|
|
@@ -117,19 +209,25 @@ async function callGateway(method, body) {
|
|
|
117
209
|
return res.json()
|
|
118
210
|
}
|
|
119
211
|
|
|
120
|
-
async function waitTask(taskId, timeoutSec) {
|
|
212
|
+
async function waitTask(taskId, timeoutSec, originThreadId) {
|
|
121
213
|
const deadline = Date.now() + Math.min(Math.max(timeoutSec || 600, 5), 900) * 1000
|
|
214
|
+
const suffix = typeof originThreadId === 'string' && originThreadId !== '' ? '&originThreadId=' + encodeURIComponent(originThreadId) : ''
|
|
122
215
|
while (Date.now() < deadline) {
|
|
123
216
|
await sleep(3000)
|
|
124
|
-
const res = await fetch(GATEWAY + '/api/dsh-bridge/status?taskId=' + encodeURIComponent(taskId))
|
|
217
|
+
const res = await fetch(GATEWAY + '/api/dsh-bridge/status?taskId=' + encodeURIComponent(taskId) + suffix)
|
|
125
218
|
const status = await res.json()
|
|
126
|
-
|
|
219
|
+
// unknown:恢复到的任务执行/交付状态未能确认——**必须停止轮询**,不能无限等。终态同理。
|
|
220
|
+
if (status.status === 'done' || status.status === 'error' || status.status === 'cancelled' || status.status === 'unknown') return status
|
|
127
221
|
}
|
|
128
|
-
return { taskId, status: 'waiting', note: '仍在后台执行,可用 dsh_task_status 查询' }
|
|
222
|
+
return { taskId, status: 'waiting', note: '仍在后台执行,可用 dsh_task_status 查询', ...(suffix === '' ? {} : { originThreadId }) }
|
|
129
223
|
}
|
|
130
224
|
|
|
131
225
|
async function handleToolCall(name, args) {
|
|
132
226
|
if (name === 'dsh_task') {
|
|
227
|
+
const origin = readOrigin(args)
|
|
228
|
+
if (!origin.ok) return origin.value
|
|
229
|
+
// 显式来源:先只读核验网关能力(GET,不会派活),不支持就**零执行**拒绝。
|
|
230
|
+
if (origin.originThreadId !== undefined && !(await originBindingSupported())) return ORIGIN_UNSUPPORTED
|
|
133
231
|
const task = await callGateway('/api/dsh-bridge/task', {
|
|
134
232
|
instruction: args.instruction,
|
|
135
233
|
cwd: args.cwd,
|
|
@@ -138,12 +236,26 @@ async function handleToolCall(name, args) {
|
|
|
138
236
|
sessionId: args.sessionId,
|
|
139
237
|
sessionQuery: args.sessionQuery,
|
|
140
238
|
deliver: args.deliver,
|
|
239
|
+
dryRun: args.dryRun === true,
|
|
240
|
+
originThreadId: origin.originThreadId,
|
|
141
241
|
})
|
|
142
|
-
//
|
|
242
|
+
// dryRun 只解析目标,不产生任务:原样回显给你看命中结果。
|
|
243
|
+
if (args.dryRun === true) return { ok: task.resolved === true, phase: 'dry-run', ...task }
|
|
244
|
+
// 4xx 拒绝(缺目标 / 会话不存在 / 搜索结果不唯一 / 通道忙 / 非法来源)不带 taskId,绝不静默新建。
|
|
143
245
|
if (task.taskId === undefined) return { ok: false, phase: 'rejected', ...task }
|
|
246
|
+
// 能力检查通过 ≠ 这次绑定被确认:网关必须回显匹配的 originThreadId。没有 → 未确认、不要重发。
|
|
247
|
+
if (origin.originThreadId !== undefined && !originConfirmed(task, origin.originThreadId)) return unconfirmedPayload(task)
|
|
144
248
|
if (task.status === 'error') return { ok: false, ...task }
|
|
145
249
|
if (args.wait === false) return { ok: true, phase: 'submitted', ...task }
|
|
146
|
-
const done = await waitTask(task.taskId, args.timeoutSec || 600)
|
|
250
|
+
const done = await waitTask(task.taskId, args.timeoutSec || 600, origin.originThreadId)
|
|
251
|
+
// 会话目标报错但 delivered=true:消息已在目标会话队列里,不要重发。
|
|
252
|
+
if (done.delivered === true && done.status === 'error') {
|
|
253
|
+
return { ok: false, phase: 'queued-then-failed', resend: false, ...done }
|
|
254
|
+
}
|
|
255
|
+
// 恢复到的任务:执行/交付状态未知——如实上报并明确"不要重发"。
|
|
256
|
+
if (done.status === 'unknown') {
|
|
257
|
+
return { ok: false, phase: 'unconfirmed', resend: false, ...done }
|
|
258
|
+
}
|
|
147
259
|
return { ok: done.status === 'done', phase: 'finished', ...done }
|
|
148
260
|
}
|
|
149
261
|
if (name === 'dsh_sessions') {
|
|
@@ -160,17 +272,45 @@ async function handleToolCall(name, args) {
|
|
|
160
272
|
return { ok: false, ...found }
|
|
161
273
|
}
|
|
162
274
|
if (name === 'dsh_task_status') {
|
|
163
|
-
|
|
164
|
-
|
|
275
|
+
// 两种用法:
|
|
276
|
+
// taskId [+ originThreadId] → 精确取值(跨回合/网关重载后也能按 origin 从持久记录恢复)
|
|
277
|
+
// 只给 originThreadId → 列出该对话登记过的任务,用来找回丢失的 taskId
|
|
278
|
+
const origin = readOrigin(args)
|
|
279
|
+
if (!origin.ok) return origin.value
|
|
280
|
+
// 显式来源=精确恢复语义:网关不支持时必须明确失败,绝不能把旧网关的普通响应当成精确恢复。
|
|
281
|
+
if (origin.originThreadId !== undefined && !(await originBindingSupported())) return ORIGIN_UNSUPPORTED
|
|
282
|
+
const params = []
|
|
283
|
+
if (typeof args.taskId === 'string' && args.taskId !== '') params.push('taskId=' + encodeURIComponent(args.taskId))
|
|
284
|
+
if (origin.originThreadId !== undefined) params.push('originThreadId=' + encodeURIComponent(origin.originThreadId))
|
|
285
|
+
if (params.length === 0) return { ok: false, error: 'taskId or originThreadId is required' }
|
|
286
|
+
const res = await fetch(GATEWAY + '/api/dsh-bridge/status?' + params.join('&'))
|
|
287
|
+
const payload = await res.json()
|
|
288
|
+
// 精确取值必须由网关**确认来源**:没有匹配的 originThreadId 就不算精确恢复(也绝不透出 result)。
|
|
289
|
+
if (origin.originThreadId !== undefined
|
|
290
|
+
&& !(payload !== null && typeof payload === 'object' && typeof payload.reason === 'string' && REFUSAL_REASONS.has(payload.reason))
|
|
291
|
+
&& !originConfirmed(payload, origin.originThreadId)) {
|
|
292
|
+
return unconfirmedPayload(payload)
|
|
293
|
+
}
|
|
294
|
+
return payload
|
|
165
295
|
}
|
|
166
296
|
if (name === 'dsh_task_cancel') {
|
|
167
|
-
|
|
297
|
+
const origin = readOrigin(args)
|
|
298
|
+
if (!origin.ok) return origin.value
|
|
299
|
+
if (origin.originThreadId !== undefined && !(await originBindingSupported())) return ORIGIN_UNSUPPORTED
|
|
300
|
+
const payload = await callGateway('/api/dsh-bridge/cancel', { taskId: args.taskId, force: args.force === true, ...(origin.originThreadId === undefined ? {} : { originThreadId: origin.originThreadId }) })
|
|
301
|
+
if (origin.originThreadId !== undefined && payload !== null && typeof payload === 'object' && payload.cancelled === true && !originConfirmed(payload, origin.originThreadId)) return unconfirmedPayload(payload)
|
|
302
|
+
return payload
|
|
168
303
|
}
|
|
169
304
|
if (name === 'dsh_review') {
|
|
170
|
-
const
|
|
305
|
+
const origin = readOrigin(args)
|
|
306
|
+
if (!origin.ok) return origin.value
|
|
307
|
+
if (origin.originThreadId !== undefined && !(await originBindingSupported())) return ORIGIN_UNSUPPORTED
|
|
308
|
+
const task = await callGateway('/api/dsh-bridge/review', { cwd: args.cwd, diff: args.diff, focus: args.focus, originThreadId: origin.originThreadId })
|
|
309
|
+
if (origin.originThreadId !== undefined && task.taskId !== undefined && !originConfirmed(task, origin.originThreadId)) return unconfirmedPayload(task)
|
|
171
310
|
if (task.status === 'error') return { ok: false, ...task }
|
|
172
311
|
if (args.wait === false) return { ok: true, phase: 'submitted', ...task }
|
|
173
|
-
const done = await waitTask(task.taskId, args.timeoutSec || 900)
|
|
312
|
+
const done = await waitTask(task.taskId, args.timeoutSec || 900, origin.originThreadId)
|
|
313
|
+
if (done.status === 'unknown') return { ok: false, phase: 'unconfirmed', resend: false, ...done }
|
|
174
314
|
return { ok: done.status === 'done', phase: 'finished', ...done }
|
|
175
315
|
}
|
|
176
316
|
if (name === 'dsh_read_file') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whaletalk/dsh-codex-collab",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Codex × DeepSeek Harness 双向编码协作:Codex 主导派活与验收,DeepSeek Harness 编码子代理在共享工作目录执行并回传,独立评审子代理实际跑通构建/测试后出具报告。提供 DSH 宿主网关插件、零依赖 MCP 服务器与 Codex 技能三件套。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
"mcp/",
|
|
25
25
|
"codex-plugin/",
|
|
26
26
|
"templates/",
|
|
27
|
+
"docs/",
|
|
27
28
|
"cordis.patch.yml",
|
|
28
29
|
"README.md",
|
|
30
|
+
"CHANGELOG.md",
|
|
29
31
|
"LICENSE",
|
|
30
32
|
"dsh-banner.png.png"
|
|
31
33
|
],
|