@whyshu/argent-engine 0.7.2 → 0.7.4

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 (57) hide show
  1. package/MANIFEST.json +46 -40
  2. package/engine/VERSION +1 -1
  3. package/engine/scripts/appointment-create.mjs +34 -0
  4. package/engine/scripts/appointment-stop.mjs +18 -0
  5. package/engine/scripts/appointment-worker.mjs +96 -0
  6. package/engine/scripts/appointment_grab.py +327 -0
  7. package/engine/scripts/argent-runner.mjs +223 -0
  8. package/engine/scripts/argent-setup-check.mjs +3 -3
  9. package/engine/scripts/capability-packs.lock.json +34 -40
  10. package/engine/scripts/check-agent-catalog.mjs +1 -1
  11. package/engine/scripts/check-config-catalog.mjs +0 -1
  12. package/engine/scripts/collect-evidence.mjs +2 -5
  13. package/engine/scripts/daily-report.mjs +315 -12
  14. package/engine/scripts/drill-clean-room.sh +45 -0
  15. package/engine/scripts/drill-handover.sh +4 -1
  16. package/engine/scripts/dsh-upgrade.mjs +16 -20
  17. package/engine/scripts/flow-requirements.json +5 -7
  18. package/engine/scripts/flow-timers.sh +1 -1
  19. package/engine/scripts/flow-trigger.sh +1 -1
  20. package/engine/scripts/gen-preset-rows.mjs +20 -0
  21. package/engine/scripts/handover-sheet.mjs +9 -1
  22. package/engine/scripts/invoice_stageb.py +20 -1
  23. package/engine/scripts/lib/agent-install.mjs +10 -1
  24. package/engine/scripts/lib/daily-report-core.mjs +124 -16
  25. package/engine/scripts/lib/dsh-versions.mjs +17 -1
  26. package/engine/scripts/lib/ml-ads-core.mjs +204 -0
  27. package/engine/scripts/lib/plugin-packages.mjs +1 -0
  28. package/engine/scripts/ml-ads.mjs +181 -0
  29. package/engine/scripts/ml-oauth-setup.mjs +1 -1
  30. package/engine/scripts/orchestrator.py +91 -10
  31. package/engine/scripts/p2-report-run.mjs +102 -0
  32. package/engine/scripts/patches/dsh-session--append-message-shape.patch +34 -9
  33. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +32 -8
  34. package/engine/scripts/patches/retire-check.sh +0 -4
  35. package/engine/scripts/patches/verify-hardening.mjs +4 -1
  36. package/engine/scripts/patches/verify-turn-outside-replace.mjs +64 -0
  37. package/engine/scripts/poll-fulfillment.sh +1 -1
  38. package/engine/scripts/profile-doctor.mjs +1 -1
  39. package/engine/scripts/python-runtime.sh +16 -4
  40. package/engine/scripts/release-argent.sh +8 -7
  41. package/engine/scripts/session-handoff.mjs +111 -0
  42. package/engine/scripts/session-load-check.mjs +105 -0
  43. package/engine/scripts/sync-scripts.sh +1 -1
  44. package/engine/scripts/xzh-browser.sh +37 -0
  45. package/engine/scripts/xzh_browser.py +49 -0
  46. package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +30 -0
  47. package/engine/skills/ml-full-appointment/SKILL.md +98 -0
  48. package/engine/version.mjs +1 -1
  49. package/package.json +1 -1
  50. package/engine/scripts/drill-clean-machine.sh +0 -315
  51. package/engine/scripts/install-internal-role.sh +0 -190
  52. package/engine/scripts/install-one-shot.mjs +0 -665
  53. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +0 -297
  54. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +0 -11
  55. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +0 -39
  56. package/engine/scripts/patches/verify-bundled-migrators.mjs +0 -79
  57. package/engine/scripts/patches/verify-transport-diagnostics.mjs +0 -227
@@ -1,227 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * TRANSPORT 诊断补丁校验(2026-10-02,任务 D)。
4
- *
5
- * 补丁:`dsh-llm-deepseek--transport-diagnostics.patch`
6
- * 目标:`@deepseek-ai/dsh-llm-deepseek/lib/index.js`(官方 Messages 适配器;
7
- * **问述代理复用同一份**,见 `plugins/argent-core/src/llm-whyshu.js`)。
8
- *
9
- * ## 为什么要有它
10
- * 线上现象:Web 回合偶发 `This turn failed / DeepSeek Messages transport failed`。
11
- * 实测(session-06732069 / session-878f3e3b 的会话日志)该 failure 落进 `llm/retry`
12
- * 事件时**只有 `{message, code}`**,没有 cause、没有请求标识、没有"已产出多少"
13
- * ⇒ 事后既分不清 ECONNRESET / 提前关闭 / 5xx 断流,也无法判断"这次到底产出了没有"。
14
- * 补丁只做**可观测性**(判定语义不变)。
15
- *
16
- * ## 本脚本验什么
17
- * ① 源码语义断言:三分支判定**未被改动**(顺序、code、`instanceof LlmError` 直通都还在);
18
- * ② 行为断言:把补丁插入的纯函数**取出来真跑**,逐个覆盖
19
- * ECONNRESET / socket hang up / ETIMEDOUT / 非 2xx + 响应体片段 / AbortError,
20
- * 以及"已产出长度"的累加(文本/思维链/工具参数/块);
21
- * ③ 装配断言:`registerDeepSeekProvider` 里确实把 `onTransportFailure` 接上了 logger;
22
- * ④ 覆盖断言:`failure.requestId` 会被写进 LlmError(从而随 `llm/retry` 落进会话日志)。
23
- *
24
- * 用法:`node scripts/patches/verify-transport-diagnostics.mjs`(只读,不碰任何 session 文件)
25
- */
26
- import { readFileSync, existsSync } from 'node:fs'
27
- import { requireDshScope } from '../lib/dsh-scope.mjs'
28
-
29
- // 0.6.0 F1 修复批(2026-10-14):探不到 scope ⇒ 响亮失败(⛔ 不用假路径 —— 否则本脚本会
30
- // 一路"目标不在 ⇒ 跳过",把"没验"报成"通过")。
31
- let SCOPE
32
- try { SCOPE = requireDshScope({ what: '补丁校验(verify-transport-diagnostics)' }) }
33
- catch (e) { console.error(` ❌ ${e.message}`); process.exit(1) }
34
- const TARGET = `${SCOPE}/dsh-llm-deepseek/lib/index.js`
35
- const LLM = `${SCOPE}/dsh-llm/lib/index.js`
36
-
37
- let pass = 0, fail = 0
38
- const ok = (c, n) => { c ? (pass++, console.log(` ✅ ${n}`)) : (fail++, console.log(` ❌ ${n}`)) }
39
-
40
- console.log('【TRANSPORT 诊断补丁】dsh-llm-deepseek/lib/index.js')
41
-
42
- if (!existsSync(TARGET)) {
43
- ok(false, `目标存在(${TARGET})`)
44
- } else {
45
- const src = readFileSync(TARGET, 'utf8')
46
-
47
- // ── ① 判定语义不变(这是补丁的红线:只加可观测字段,不动三分支)────────────────
48
- console.log('\n ── ① 判定语义未变 ──')
49
- const catchBlock = (src.match(/\/\* Argent hotfix \(2026-10-02\):\*\*判定不变\*\*[\s\S]*?\n\t\t\t\} finally \{/) || [''])[0]
50
- ok(/Argent hotfix \(2026-10-02\)/.test(src), '补丁标记存在')
51
- const branches = [
52
- /if \(timeoutOf\(watchdog\.signal, "MESSAGES_IDLE"\) !== void 0\) throw new LlmError\("DeepSeek Messages stream idle timeout", "TIMEOUT", \{ cause: error \}\);/,
53
- /if \(options\.signal\?\.aborted\) throw new LlmError\("DeepSeek Messages request aborted", "ABORTED", \{ cause: error \}\);/,
54
- /if \(error instanceof LlmError\) throw error;/,
55
- ]
56
- ok(branches.every((re) => re.test(src)), 'TIMEOUT / ABORTED / LlmError-直通 三个分支**逐字未变**')
57
- const order = [
58
- src.indexOf('if (timeoutOf(watchdog.signal, "MESSAGES_IDLE") !== void 0) throw new LlmError("DeepSeek Messages stream idle timeout"'),
59
- src.indexOf('if (options.signal?.aborted) throw new LlmError("DeepSeek Messages request aborted"'),
60
- src.indexOf('if (error instanceof LlmError) throw error;'),
61
- ]
62
- ok(order.every((i) => i > -1) && order[0] < order[1] && order[1] < order[2],
63
- `三分支**顺序不变**(TIMEOUT → ABORTED → LlmError 直通;实测 ${JSON.stringify(order)})`)
64
- ok(/throw new LlmError\("DeepSeek Messages transport failed", "TRANSPORT", \{/.test(src),
65
- 'TRANSPORT 仍是"其余一律"的兜底(message 与 code 逐字未变)')
66
- ok(catchBlock !== '', 'catch 块里带"判定不变"注释(人改这里时会被提醒)')
67
-
68
- // ── ② 行为断言:把补丁插入的纯函数取出来真跑 ────────────────────────────────
69
- console.log('\n ── ② cause 形态与"已产出长度"(真跑补丁插入的函数) ──')
70
- const start = src.indexOf('let argentTransportCallSeq = 0;')
71
- const end = src.indexOf('/** DeepSeek provider using Messages content and native thinking replay. */', start)
72
- const body = start === -1 || end === -1 ? '' : src.slice(start, end)
73
- let H = null
74
- try {
75
- H = new Function(`${body}
76
- const ProviderRequestId = (id) => id;
77
- return { argentTransportDiagnostics, argentRecordProducedChunk, argentProducedSummary, argentRequestIdOf, argentNoteResponse, argentNoteHttpFailure, argentDescribeTransportCause, argentTransportReport, argentTransportLogLine };`)()
78
- } catch (e) { ok(false, `补丁函数可独立求值(${e.message})`) }
79
- ok(H !== null && typeof H.argentTransportReport === 'function',
80
- '补丁插入的纯函数可独立求值(用它们做行为断言,不依赖完整 ctx)')
81
-
82
- if (H !== null) {
83
- const conn = { streamIdleTimeoutMs: 300000 }
84
- const diagFor = (opts = { provider: 'whyshu', model: 'deepseek-flash' }) => H.argentTransportDiagnostics(opts, conn)
85
- const resp = (status, headers = {}) => ({ status, headers: new Headers(headers) })
86
-
87
- // ── cause 分类 ──
88
- const cases = {
89
- reset: Object.assign(new TypeError('fetch failed'), { cause: Object.assign(new Error('read ECONNRESET'), { code: 'ECONNRESET', errno: -54, syscall: 'read' }) }),
90
- hangup: Object.assign(new Error('socket hang up'), { code: 'ECONNRESET' }),
91
- etimedout: Object.assign(new Error('connect ETIMEDOUT 1.2.3.4:443'), { code: 'ETIMEDOUT', syscall: 'connect', errno: -60 }),
92
- earlyClose: Object.assign(new TypeError('terminated'), { cause: Object.assign(new Error('other side closed'), { code: 'UND_ERR_SOCKET' }) }),
93
- aborted: Object.assign(new Error('This operation was aborted'), { name: 'AbortError', code: 'ABORT_ERR' }),
94
- refused: Object.assign(new TypeError('fetch failed'), { cause: Object.assign(new Error('connect ECONNREFUSED'), { code: 'ECONNREFUSED' }) }),
95
- }
96
- // `report.cause` = 描述 `error.cause ?? error` —— undici 常把真因藏在 cause 上,
97
- // 所以断言一律走 report(这才是真正写进日志的那个形态)。
98
- const kindOf = (e) => H.argentTransportReport(diagFor(), e, conn).cause.kind
99
- const kinds = Object.fromEntries(Object.entries(cases).map(([k, e]) => [k, kindOf(e)]))
100
- ok(kinds.reset === 'ECONNRESET', `ECONNRESET(undici 常见形态:TypeError fetch failed + cause.code)⇒ kind=${kinds.reset}`)
101
- ok(kinds.hangup === 'ECONNRESET', `socket hang up ⇒ kind=${kinds.hangup}`)
102
- ok(kinds.etimedout === 'ETIMEDOUT', `ETIMEDOUT ⇒ kind=${kinds.etimedout}`)
103
- ok(kinds.earlyClose === 'EARLY_CLOSE', `提前关闭(terminated / other side closed / UND_ERR_SOCKET)⇒ kind=${kinds.earlyClose}`)
104
- ok(H.argentTransportReport(diagFor(), cases.earlyClose, conn).causeChain[1]?.code === 'UND_ERR_SOCKET',
105
- '内层原始 code(UND_ERR_SOCKET)仍逐字保留在 causeChain 里(kind 只是给人看的上位分类)')
106
- ok(kinds.aborted === 'AbortError', `AbortError 可区分(不误判成网络错)⇒ kind=${kinds.aborted}`)
107
- ok(kinds.refused === 'ECONNREFUSED', `ECONNREFUSED ⇒ kind=${kinds.refused}`)
108
- // 单节点版只看自己:外层 `fetch failed` 没有 code 时归 FETCH_FAILED(链在 report.causeChain 里)
109
- ok(H.argentDescribeTransportCause(cases.reset).kind === 'FETCH_FAILED',
110
- '单节点描述不被内层 cause 污染(外层 fetch failed 归 FETCH_FAILED,真因在 causeChain 里)')
111
- const chain = H.argentTransportReport(diagFor(), cases.reset, conn).causeChain
112
- ok(chain.length === 2 && chain[0].kind === 'FETCH_FAILED' && chain[1].kind === 'ECONNRESET',
113
- `cause 链被完整摊开(外层 fetch failed → 内层 ECONNRESET;实测 ${JSON.stringify(chain.map((c) => c.kind))})`)
114
- ok(chain[1].errno === '-54' && chain[1].syscall === 'read', 'errno/syscall 被带上(定位连接层用)')
115
-
116
- // ── 已产出长度 ──
117
- const d0 = diagFor()
118
- const r0 = H.argentTransportReport(d0, cases.hangup, conn)
119
- ok(r0.produced.chunks === 0 && r0.produced.anyOutput === false,
120
- '**尚无任何输出**的回合:chunks=0 且 anyOutput=false(这正是"可安全重试"的判据)')
121
-
122
- const d1 = diagFor()
123
- H.argentRecordProducedChunk(d1, { type: 'block-start', index: 0, blockType: 'text' })
124
- H.argentRecordProducedChunk(d1, { type: 'text-delta', index: 0, text: '你好世界' })
125
- H.argentRecordProducedChunk(d1, { type: 'reasoning-delta', index: 1, text: '想一想' })
126
- H.argentRecordProducedChunk(d1, { type: 'tool-call-delta', index: 2, id: 'c1', name: 'flow_failures', argumentsDelta: '{"flow":' })
127
- const r1 = H.argentTransportReport(d1, cases.earlyClose, conn)
128
- ok(r1.produced.chunks === 4 && r1.produced.anyOutput === true,
129
- `**已产出内容**的回合被识别出来(${JSON.stringify(r1.produced)})`)
130
- ok(r1.produced.textChars === 4 && r1.produced.reasoningChars === 3 && r1.produced.toolCallChars === 8,
131
- `分类计数正确:text=${r1.produced.textChars} reasoning=${r1.produced.reasoningChars} toolArgs=${r1.produced.toolCallChars}`)
132
- ok(r1.produced.toolCallNames.length === 1 && r1.produced.toolCallNames[0] === 'flow_failures',
133
- '工具名被记下(判断"是否已经开始产生副作用"用)')
134
- H.argentRecordProducedChunk(d1, { type: 'finish', reason: { kind: 'stop' } })
135
- ok(H.argentProducedSummary(d1).settled === true, 'finish/usage 会标记 settled')
136
-
137
- // ── 请求标识(streamId)与 HTTP 状态 ──
138
- const d2 = diagFor()
139
- H.argentNoteResponse(d2, resp(200, { 'x-request-id': 'req-abc-123' }))
140
- const r2 = H.argentTransportReport(d2, cases.earlyClose, conn)
141
- ok(r2.requestId === 'req-abc-123' && r2.httpStatus === 200,
142
- `响应头里的 provider 请求标识被抓到(requestId=${r2.requestId}, status=${r2.httpStatus})`)
143
- const d3 = diagFor()
144
- H.argentNoteResponse(d3, resp(200, { 'request-id': 'rid-1' }))
145
- H.argentNoteResponse(d3, resp(200, { 'x-deepseek-request-id': 'rid-2' }))
146
- ok(H.argentRequestIdOf(new Headers({ 'request-id': 'a' })) === 'a'
147
- && H.argentRequestIdOf(new Headers({ 'x-request-id': 'b' })) === 'b'
148
- && H.argentRequestIdOf(new Headers({})) === undefined,
149
- '三个头按同一优先级取值(request-id → x-request-id → x-deepseek-request-id)')
150
-
151
- // ── 非 2xx + 响应体片段 ──
152
- const d4 = diagFor()
153
- H.argentNoteHttpFailure(d4, resp(502, { 'x-request-id': 'gw-1' }), '<html><body>502 Bad Gateway</body></html>')
154
- const r4 = H.argentTransportReport(d4, Object.assign(new Error('invalid response status'), { code: 'HPE_INVALID_STATUS' }), conn)
155
- ok(r4.httpStatus === 502 && /502 Bad Gateway/.test(r4.bodySnippet),
156
- `非 2xx 的状态码与响应体片段被记下(status=${r4.httpStatus}, snippet=${JSON.stringify(r4.bodySnippet)})`)
157
- const longBody = 'x'.repeat(5000)
158
- const d5 = diagFor()
159
- H.argentNoteHttpFailure(d5, resp(500), longBody)
160
- ok(H.argentTransportReport(d5, cases.hangup, conn).bodySnippet.length === 400,
161
- '响应体片段被截断到 400 字符(不刷屏)')
162
-
163
- // ── 日志行:可 grep + 关键字段齐全 ──
164
- const line = H.argentTransportLogLine(r2)
165
- ok(line.startsWith('[argent-transport] '), '日志行以固定前缀 `[argent-transport]` 开头(便于 grep)')
166
- for (const field of ['route=whyshu/deepseek-flash', 'callId=', 'providerRequestId=req-abc-123', 'httpStatus=200', 'cause=EARLY_CLOSE', 'produced={chunks:', 'elapsedMs=', 'idleTimeoutMs=300000']) {
167
- ok(line.includes(field), `日志行含字段 ${field}`)
168
- }
169
- ok(!line.includes('\n'), '单行输出(不会被日志系统按多行拆散)')
170
- const line4 = H.argentTransportLogLine(r4)
171
- ok(line4.includes('bodySnippet='), '非 2xx 时日志行附 bodySnippet')
172
- const line0 = H.argentTransportLogLine(r0)
173
- ok(line0.includes('providerRequestId=(none)') && line0.includes('httpStatus=(no response)'),
174
- '连响应都没拿到时也明确标注 (none)/(no response),不假装有值')
175
- }
176
-
177
- // ── ③ 装配:onTransportFailure 接到 logger ────────────────────────────────
178
- console.log('\n ── ③ 装配与落盘 ──')
179
- ok(/onTransportFailure: \(report\) => \{/.test(src) && /ctx\.logger\.warn\(argentTransportLogLine\(report\)\)/.test(src),
180
- 'registerDeepSeekProvider 把 onTransportFailure 接到 ctx.logger.warn')
181
- ok(/ctx\.logger\.warn\("llm-deepseek: TRANSPORT detail %o"/.test(src),
182
- '同时打一份结构化 detail(%o,供日志详情/工具解析)')
183
- ok(/this\.dependencies\.onTransportFailure\?\.\(transportReport\)/.test(src),
184
- '适配器侧用可选链调用(第三方复用 registerDeepSeekProvider 时不会因缺回调而炸)')
185
- ok(/\.\.\.transportReport\.requestId === void 0 \? \{\} : \{ requestId: transportReport\.requestId \}/.test(src),
186
- 'requestId 被写进 LlmError 的字段 ⇒ `failure.requestId` 随 `llm/retry` 事件落进会话日志')
187
- ok(/const transportDiagnostics = argentTransportDiagnostics\(options, connection\);/.test(src)
188
- && /argentRecordProducedChunk\(transportDiagnostics, next\.value\);/.test(src),
189
- 'generate() 里每产出一片就记一次(`yield` 之前,故计数与消费端所见一致)')
190
-
191
- // ── ④ LlmError 真的保留 requestId(跨包契约,非本补丁可改)──────────────────
192
- if (existsSync(LLM)) {
193
- const llmSrc = readFileSync(LLM, 'utf8')
194
- ok(/\.\.\.options\?\.requestId === void 0 \? \{\} : \{ requestId: options\.requestId \}/.test(llmSrc),
195
- 'dsh-llm 的 LlmError 会把 requestId 保留进 .failure(本补丁依赖该既有契约,未改动它)')
196
- // 但"落进会话日志"还差三段链路,逐一钉住(否则 requestId 会停在内存里):
197
- // 适配器 throw ⇒ LlmRuntime 的 adapterFailureChunk 转成终态 finish 块
198
- // ⇒ normalizeLlmFailure 取 error.failure 快照 ⇒ failureSnapshot 白名单里必须有 requestId
199
- ok(/function adapterFailureChunk\(error, signal\)/.test(llmSrc)
200
- && /kind: "error",\s*\n\s*failure/.test(llmSrc),
201
- '抛出的适配器错误被 LlmRuntime 转成终态 finish 块(这才是 `agent/request-error` 被触发的路径)')
202
- ok(/function normalizeLlmFailure\(value\)/.test(llmSrc)
203
- && /const carried = ownFailureSnapshot\(error\);\s*\n\s*if \(carried !== void 0 && carried\.code === ownErrorCode\(error\)\) return carried;/.test(llmSrc),
204
- 'normalizeLlmFailure 直接复用 LlmError 自带的 .failure 快照(code 一致时)—— requestId 不会被丢掉')
205
- ok(/const requestId = candidate\.requestId;/.test(llmSrc)
206
- && /\.\.\.requestId === void 0 \? \{\} : \{ requestId \}/.test(llmSrc),
207
- 'failureSnapshot 的白名单里有 requestId(也校验类型,非空字符串才放行)')
208
- // 端到端(真造一个 LlmError,走同一段 normalize 逻辑):确认 requestId 真的留在 failure 里
209
- const { LlmError } = await import(LLM)
210
- const probe = new LlmError('DeepSeek Messages transport failed', 'TRANSPORT', {
211
- cause: new Error('read ECONNRESET'),
212
- requestId: 'req_probe_1',
213
- })
214
- ok(probe.failure.requestId === 'req_probe_1' && probe.failure.code === 'TRANSPORT' && probe.failure.message === 'DeepSeek Messages transport failed',
215
- `端到端:TRANSPORT 的 LlmError 带出 {code, message, requestId}(实测 ${JSON.stringify(probe.failure)})`)
216
- const noId = new LlmError('DeepSeek Messages transport failed', 'TRANSPORT', { cause: new Error('x') })
217
- ok(noId.failure.requestId === undefined && Object.keys(noId.failure).sort().join(',') === 'code,message',
218
- '没拿到请求标识时不会伪造:failure 仍是 {message, code}(与既有行为一致)')
219
- }
220
- }
221
-
222
- console.log(`\n── 合计:通过 ${pass} / 失败 ${fail}`)
223
- if (fail > 0) {
224
- console.log('❌ TRANSPORT 诊断补丁校验未通过:补丁可能没打上或上游已改动 —— 先别重启,见 docs / AGENTS.md')
225
- process.exit(1)
226
- }
227
- console.log('✅ TRANSPORT 诊断补丁校验通过:cause / streamId / 已产出长度均可观测,判定语义未变')