@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,315 +0,0 @@
1
- #!/usr/bin/env bash
2
- # drill-clean-machine.sh —— **干净机器一条命令**的隔离演练(D1/D2 的可复现证据;2026-10-05)
3
- #
4
- # 用途:把 `scripts/install-one-shot.mjs` 放进一个**与真实机器无关的夹具**里真跑一遍,
5
- # 并按"真实目录零变化"哨兵自证**没有**碰过 `~/.dsh/**`、`~/.argent-dsh/**`。
6
- #
7
- # 隔离构造(三条,缺一不可):
8
- # ① `HOME` 指向 `$FIX/home`(全新空目录)⇒ `~/.dsh`、`~/.argent-dsh`、`~/.pnpm` 全落在夹具里;
9
- # ② 子进程一律 `env -i`(**极简环境**:只给 HOME/PATH,清掉 DSH_HOME/ARGENT_DATA_DIR/密钥等实例级开关);
10
- # ③ PATH 只含**夹具 bin**(node/git/dsh 的软链)+ 系统目录 ⇒ 包管理器**不在** PATH 上
11
- # (演练刻意不跑 pnpm —— 装配走安装器的 `--from-packs` 离线面)。
12
- # 能力来源 = **离线包目录**(`capability_pack.py --build` 现场构建),不依赖 `~/.argent-dsh` 克隆。
13
- #
14
- # 演练内容(六跑):
15
- # A. dry-run ⇒ 退出码 0,且夹具**零写入**(dry-run 语义)
16
- # B. --apply(无许可) ⇒ 退出码 0,装配 + 技能同步真跑;许可段 skipped("未装许可"不算失败)
17
- # C. 重跑 B ⇒ 退出码 0,逐段结论与 B 一致(**幂等**)
18
- # D. --only license --apply(夹具许可)⇒ 退出码 0,许可真落到夹具数据目录
19
- # F. 在线装配面(本地镜像克隆 file://<仓库>、零网络、无包管理器 ⇒ 安装器既有"直拷"回退)
20
- # E. 多包一起装(full,invoice)⇒ **预期退出码 0**(2026-10-09 起):注册表投影在计划阶段与落盘阶段
21
- # 用**同一个**判据豁免(`isRegistryProjection()`),落盘后盘上注册表 = 两份投影的并集。
22
- # 收尾:哨兵比对(真实目录零变化)+ 夹具树/清单快照
23
- #
24
- # 用法:
25
- # bash scripts/drill-clean-machine.sh [--keep] [--dir <夹具根>] [--caps <单个能力>] [--timeout <秒>]
26
- # `--caps` 可给**多个**(逗号分隔):同批发布的包本就该一起装(跨包共用引擎文件必须同版);
27
- # E 步仍单独用全新树演示一次"两个包一起装"的完整判据(退出码 + 装机清单 + 注册表并集)。
28
- # 退出码:0 四跑与哨兵均符合预期 | 1 有不符合预期项(逐条点名)| 2 用法/环境错误
29
- set -uo pipefail
30
-
31
- REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
32
- KEEP=0
33
- FIX=""
34
- CAPS="full"
35
- GUARD_CAPS="full,invoice" # E 步:多包一起装(回归判据,见文档 §E)
36
- TIMEOUT=1800
37
- while [ $# -gt 0 ]; do
38
- case "$1" in
39
- --keep) KEEP=1; shift ;;
40
- --dir) FIX="$2"; shift 2 ;;
41
- --caps) CAPS="$2"; shift 2 ;;
42
- --timeout) TIMEOUT="$2"; shift 2 ;;
43
- -h|--help) sed -n '2,26p' "$0"; exit 0 ;;
44
- *) echo "✗ 未知参数:$1" >&2; exit 2 ;;
45
- esac
46
- done
47
- case "$CAPS" in
48
- *,*) : ;; # 多能力(逗号分隔)自 2026-10-09 起支持:一起装是**推荐**路径
49
- '') echo "✗ --caps 不能为空" >&2; exit 2 ;;
50
- esac
51
- [ -n "$FIX" ] || FIX="$(mktemp -d "${TMPDIR:-/tmp}/argent-drill.XXXXXX")"
52
- FIX="$(cd "$FIX" && pwd -P)"
53
- mkdir -p "$FIX/bin" "$FIX/home" "$FIX/packs"
54
-
55
- ONE="$REPO/scripts/install-one-shot.mjs"
56
- [ -f "$ONE" ] || { echo "✗ 缺 $ONE" >&2; exit 2; }
57
-
58
- FAIL=0
59
- note() { printf '%s\n' "$*"; }
60
- ok() { printf ' ✅ %s\n' "$*"; }
61
- bad() { printf ' ✗ %s\n' "$*"; FAIL=1; }
62
- warn() { printf ' ⚠️ %s\n' "$*"; }
63
- hdr() { printf '\n── %s\n' "$*"; }
64
-
65
- # ── 夹具 bin:只放 node / git / dsh(**不放** pnpm/npm/corepack)──────────────
66
- for t in node git dsh; do
67
- p="$(command -v "$t" 2>/dev/null || true)"
68
- [ -n "$p" ] && ln -sf "$p" "$FIX/bin/$t"
69
- done
70
- MINPATH="$FIX/bin:/usr/bin:/bin:/usr/sbin:/sbin"
71
- RUN=(env -i "HOME=$FIX/home" "PATH=$MINPATH" "TMPDIR=$FIX/tmp" \
72
- "ARGENT_REPO_URL=file://$REPO" # 在线面用**本地镜像**克隆(不是 whyshu 私服):零网络
73
- "ARGENT_SKIP_MODEL_PROBE=1") # 联网凭证探针在夹具里没有 Key ⇒ 用安装器既有逃逸阀(它会打警告)
74
- mkdir -p "$FIX/tmp"
75
-
76
- note "═══════════════════════════════════════════════════"
77
- note " 干净机器演练夹具:$FIX"
78
- note " HOME=$FIX/home(隔离)| PATH=$MINPATH"
79
- note " node=$(node --version 2>/dev/null)|git=$(git --version 2>/dev/null | awk '{print $3}')|dsh=$(command -v dsh || echo 无)"
80
- note " ⛔ 包管理器不在 PATH(演练刻意不跑 pnpm)"
81
- note "═══════════════════════════════════════════════════"
82
-
83
- # ── 哨兵(真实目录)────────────────────────────────────────────────────────
84
- # 立场:演练**只读**真实目录。哨兵盯"本次演练最可能误写的面",逐条 sha 比对。
85
- # 刻意**不**盯会话/日志(常驻 dsh web 在写,盘它必然恒红,属噪声不属判据)。
86
- # 真实数据目录:**只调内核的唯一判据**(`core/lib/argent-datadir.mjs#resolveDataDir()`),
87
- # 本脚本里**不写第二份**(`ARGENT_DATA_DIR`/`DSH_HOME` 一律清掉 ⇒ 解析出的就是"本机默认位置")。
88
- # 为什么哨兵也必须这样:门禁 scripts/tests/argent-datadir-parity.test.mjs【4】对 .sh 里的
89
- # `$HOME/.dsh/argent` 式硬编码判据**判红**(2026-10-01 桌面端事故形态)。
90
- # 用 `env -i` 清掉整份环境(**不点名任何**实例级开关变量 —— 点名它本身就是"第二份三级链",
91
- # 会被同一条门禁的 `ARGENT_DATA_DIR` 规则判红)。
92
- REAL_DATA_DIR="$(env -i "HOME=$HOME" "PATH=$PATH" node -e \
93
- 'import(process.argv[1]).then(m=>process.stdout.write(m.resolveDataDir().dir))' \
94
- "$REPO/core/lib/argent-datadir.mjs" 2>/dev/null)"
95
- [ -n "$REAL_DATA_DIR" ] || { echo "✗ 解析不出真实数据目录(缺 core/lib/argent-datadir.mjs?)—— 哨兵无法自证,演练中止" >&2; exit 2; }
96
-
97
- sentinel() {
98
- local out="$1"
99
- {
100
- echo "# $(date -u +%FT%TZ) 真实目录哨兵(演练前后必须逐字节一致)|数据目录=${REAL_DATA_DIR}"
101
- for f in "$REAL_DATA_DIR/install.json" "$REAL_DATA_DIR/license.json" \
102
- "$REAL_DATA_DIR/license.secret" "$REAL_DATA_DIR/skin.json" \
103
- "$REAL_DATA_DIR/gateway.json" "$REAL_DATA_DIR/roles.json" \
104
- "$HOME/.dsh/settings.yaml" "$HOME/.dsh/.credentials.yaml" \
105
- "$HOME/.dsh/profiles/web/package.json" "$HOME/.dsh/profiles/web/cordis.patch.yml"; do
106
- if [ -f "$f" ]; then printf '%s %s\n' "$(shasum -a 256 "$f" | cut -c1-16)" "${f/#$HOME/~}"; else printf 'MISSING %s\n' "${f/#$HOME/~}"; fi
107
- done
108
- for d in "$HOME/.dsh/skills" "$HOME/.dsh/.agent-presets"; do
109
- if [ -d "$d" ]; then
110
- printf 'DIR %s files=%s hash=%s\n' "${d/#$HOME/~}" \
111
- "$(find "$d" -type f | wc -l | tr -d ' ')" \
112
- "$(find "$d" -type f | LC_ALL=C sort | xargs shasum -a 256 2>/dev/null | shasum -a 256 | cut -c1-16)"
113
- else printf 'DIR-MISSING %s\n' "${d/#$HOME/~}"; fi
114
- done
115
- if [ -d "$HOME/.argent-dsh" ]; then
116
- printf 'ARGENT-DSH head=%s dirty=%s files=%s\n' \
117
- "$(git -C "$HOME/.argent-dsh" rev-parse --short HEAD 2>/dev/null || echo 非git)" \
118
- "$(git -C "$HOME/.argent-dsh" status --porcelain 2>/dev/null | wc -l | tr -d ' ')" \
119
- "$(find "$HOME/.argent-dsh" -type f -not -path '*/.git/*' | wc -l | tr -d ' ')"
120
- else printf 'ARGENT-DSH MISSING\n'; fi
121
- } > "$out" 2>/dev/null
122
- }
123
- sentinel "$FIX/sentinel-before.txt"
124
- node "$REPO/scripts/check-test-env-safety.mjs" --snapshot "$FIX/sentinel-datadir-before.json" >/dev/null 2>&1 || true
125
- note ""
126
- note "哨兵已拍:$FIX/sentinel-before.txt(+ 真实数据目录三层快照)"
127
-
128
- # ── 离线包(能力来源;不依赖克隆)───────────────────────────────────────────
129
- # 演练主线可多包(`--caps a,b`):自 2026-10-09 起"一起装"是支持路径(E 步另有显式判据)。
130
- BUILD_CAPS="$CAPS,$GUARD_CAPS" # 去重后逐个构建(重复目录会撞 --force 守卫)
131
- hdr "构建离线能力包(主线 --caps ${CAPS};E 步多包 ${GUARD_CAPS})"
132
- PACKARGS=""
133
- GUARDPACKARGS=""
134
- OLDIFS="$IFS"; IFS=','
135
- BUILT=","
136
- for cap in $BUILD_CAPS; do
137
- IFS="$OLDIFS"
138
- case "$BUILT" in *",$cap,"*) IFS=','; continue ;; esac
139
- if [ -n "$cap" ]; then
140
- if python3 "$REPO/scripts/capability_pack.py" --build "$cap" --out-dir "$FIX/packs/$cap" >"$FIX/packs/$cap.build.log" 2>&1; then
141
- ok "$cap → $FIX/packs/${cap}($(tail -1 "$FIX/packs/$cap.build.log" | sed 's/^ *//'))"
142
- BUILT="$BUILT$cap,"
143
- else
144
- bad "$cap 打包失败,见 $FIX/packs/$cap.build.log"; tail -5 "$FIX/packs/$cap.build.log"
145
- fi
146
- fi
147
- IFS=','
148
- done
149
- IFS="$OLDIFS"
150
- IFS=','
151
- for cap in $CAPS; do
152
- IFS="$OLDIFS"; [ -n "$cap" ] || { IFS=','; continue; }
153
- if [ -z "$PACKARGS" ]; then PACKARGS="$FIX/packs/$cap"; else PACKARGS="$PACKARGS,$FIX/packs/$cap"; fi
154
- IFS=','
155
- done
156
- for cap in $GUARD_CAPS; do
157
- IFS="$OLDIFS"; [ -n "$cap" ] || { IFS=','; continue; }
158
- if [ -z "$GUARDPACKARGS" ]; then GUARDPACKARGS="$FIX/packs/$cap"; else GUARDPACKARGS="$GUARDPACKARGS,$FIX/packs/$cap"; fi
159
- IFS=','
160
- done
161
- IFS="$OLDIFS"
162
- [ "$FAIL" = 0 ] || { echo "✗ 打包失败,演练中止"; exit 1; }
163
-
164
- one() { # one <标签> <期望退出码> <参数…>
165
- local label="$1" want="$2"; shift 2
166
- local t0 t1
167
- t0=$(date +%s)
168
- "${RUN[@]}" node "$ONE" "$@" >"$FIX/run-$label.out" 2>&1
169
- local rc=$?
170
- t1=$(date +%s)
171
- if [ "$rc" = "$want" ]; then ok "$label 退出码 ${rc}(期望 ${want},耗时 $((t1-t0))s)"
172
- else bad "$label 退出码 ${rc}(期望 ${want})—— 见 $FIX/run-$label.out"; tail -25 "$FIX/run-$label.out"; fi
173
- return 0
174
- }
175
-
176
- BASE=(--tree "$FIX/home/.argent-dsh" --dsh-home "$FIX/home/.dsh" --from-packs "$PACKARGS" --timeout "$TIMEOUT")
177
-
178
- # ── A. dry-run ─────────────────────────────────────────────────────────────
179
- hdr "A. dry-run(默认;必须零写入)"
180
- one A 0 "${BASE[@]}" --report "$FIX/report-A.json"
181
- if [ -e "$FIX/home/.dsh" ] || [ -e "$FIX/home/.argent-dsh" ]; then
182
- bad "dry-run 竟然写了夹具($FIX/home 下出现 .dsh/.argent-dsh)"
183
- else ok "dry-run 零写入(夹具 home 下没有 .dsh / .argent-dsh)"; fi
184
-
185
- # ── B. --apply(无许可)────────────────────────────────────────────────────
186
- hdr "B. --apply(无 --license-file ⇒ 许可段 skipped,不算失败)"
187
- one B 0 "${BASE[@]}" --apply --report "$FIX/report-B.json"
188
- [ -f "$FIX/home/.dsh/argent/install.json" ] && ok "装机清单在位:$FIX/home/.dsh/argent/install.json" \
189
- || bad "装机清单不在位(--from-packs 装配判据)"
190
- [ -d "$FIX/home/.dsh/skills" ] && ok "技能已同步到夹具:$FIX/home/.dsh/skills($(find "$FIX/home/.dsh/skills" -maxdepth 1 -mindepth 1 | wc -l | tr -d ' ') 项)" \
191
- || bad "技能目录没生成(技能同步段判据)"
192
- TREE_HASH_B="$( { [ -d "$FIX/home/.argent-dsh" ] && find "$FIX/home/.argent-dsh" -type f; } | LC_ALL=C sort | xargs shasum -a 256 2>/dev/null | shasum -a 256 | cut -c1-16)"
193
-
194
- # ── C. 重跑(幂等)─────────────────────────────────────────────────────────
195
- hdr "C. 重跑 --apply(幂等:逐段结论必须与 B 一致)"
196
- one C 0 "${BASE[@]}" --apply --report "$FIX/report-C.json"
197
- python3 - "$FIX/report-B.json" "$FIX/report-C.json" <<'PY' && ok "B/C 逐段状态一致(幂等)" || bad "B/C 逐段状态不一致(幂等被破)"
198
- import json,sys
199
- b=json.load(open(sys.argv[1])); c=json.load(open(sys.argv[2]))
200
- sb={s['id']:s['status'] for s in b['steps']}; sc={s['id']:s['status'] for s in c['steps']}
201
- if sb==sc: print(' B/C 状态:',sb)
202
- else:
203
- print(' B:',sb); print(' C:',sc); sys.exit(1)
204
- PY
205
- TREE_HASH_C="$( { [ -d "$FIX/home/.argent-dsh" ] && find "$FIX/home/.argent-dsh" -type f; } | LC_ALL=C sort | xargs shasum -a 256 2>/dev/null | shasum -a 256 | cut -c1-16)"
206
- [ "$TREE_HASH_B" = "$TREE_HASH_C" ] && ok "重跑后目标树逐字节不变(${TREE_HASH_C})" \
207
- || warn "重跑后目标树有变化($TREE_HASH_B → ${TREE_HASH_C})—— 逐条比对是 churn 还是缺陷"
208
-
209
- # ── D. 只跑许可段(夹具许可)───────────────────────────────────────────────
210
- hdr "D. --only license --apply(夹具密钥自签的许可;验的是「许可段能不能真装」)"
211
- SECRET="$FIX/fixture-license-secret"
212
- printf '%s' 'drill-fixture-secret-not-a-real-key' > "$SECRET"; chmod 600 "$SECRET"
213
- if "${RUN[@]}" node "$REPO/scripts/self-sign-license.mjs" --user drill-fixture --secret-file "$SECRET" \
214
- --out "$FIX/fixture-license.json" --sign-v2 --device-from-local --cap full --seats 1 --days 30 \
215
- >"$FIX/selfsign.out" 2>&1; then
216
- ok "夹具许可已自签:$FIX/fixture-license.json(夹具密钥,⛔ 不是任何真实环境的密钥)"
217
- else bad "夹具许可自签失败,见 $FIX/selfsign.out"; tail -5 "$FIX/selfsign.out"; fi
218
- one D 0 --tree "$FIX/home/.argent-dsh" --dsh-home "$FIX/home/.dsh" \
219
- --license-file "$FIX/fixture-license.json" --license-secret-file "$SECRET" \
220
- --only license --apply --report "$FIX/report-D.json"
221
- [ -f "$FIX/home/.dsh/argent/license.json" ] && ok "许可已落到夹具数据目录(真实数据目录不动)" \
222
- || bad "许可没落盘(许可段判据)"
223
-
224
- # ── F. 在线装配面(本地镜像克隆;零网络;无包管理器 ⇒ 走安装器既有"直拷"回退)──────
225
- hdr "F. 在线装配面(ARGENT_REPO_URL=file://<仓库>,零网络;无包管理器 ⇒ 直拷回退)"
226
- note " ⛔ 全程不跑 pnpm:夹具 PATH 里没有 pnpm/npm/corepack ⇒ 安装器走它自己的回退分支。"
227
- note " ⚠️ 该回退曾**不可达**(ensurePnpm 的 \`npm install -g pnpm\` 不在 try 里 ⇒ spawn npm ENOENT 未捕获崩),"
228
- note " 2026-10-05 已修(install-argent.mjs,见文档「失败与修法」)。"
229
- one F 0 --tree "$FIX/home-f/.argent-dsh" --dsh-home "$FIX/home-f/.dsh" --profile web \
230
- --skip-provider --no-venv --apply --report "$FIX/report-F.json"
231
- [ -f "$FIX/home-f/.dsh/profiles/web/cordis.patch.yml" ] && ok "在线面产物:profile patch 在位(profiles/web/cordis.patch.yml)" \
232
- || bad "在线面产物缺失:profiles/web/cordis.patch.yml"
233
- [ -d "$FIX/home-f/.dsh/.agent-presets" ] && ok "在线面产物:角色已落位(.agent-presets/$(ls "$FIX/home-f/.dsh/.agent-presets" | head -3 | tr '\n' ' '))" \
234
- || warn "在线面没落 preset(机制可能不是目录式;见 report-F.json)"
235
- [ -d "$FIX/home-f/.argent-dsh/plugins" ] && ok "在线面克隆到夹具树:$FIX/home-f/.argent-dsh($(find "$FIX/home-f/.argent-dsh" -maxdepth 1 -mindepth 1 | wc -l | tr -d ' ') 个顶层条目)" \
236
- || bad "在线面没克隆出树"
237
-
238
- # ── E. 多包一起装(2026-10-09 起**支持**,本步验证它真能装成)────────────────
239
- hdr "E. 多包一起装 --from-packs ${GUARD_CAPS}(期望退出码 0;注册表 = 两份投影的并集)"
240
- # ⚠ 必须用**全新**的树/home:B 已把主线能力铺好,复用会让安装器走"升级"路径,测不到"首次多包装"。
241
- one E 0 --tree "$FIX/home-e/.argent-dsh" --dsh-home "$FIX/home-e/.dsh" --profile web \
242
- --from-packs "$GUARDPACKARGS" --apply --report "$FIX/report-E.json"
243
- # 判据三条:① 不再报"多包校验失败";② 装机清单里两个能力都在且 integrity=verified;③ 盘上注册表是并集
244
- if grep -q "多包校验失败" "$FIX/run-E.out"; then
245
- bad "多包一起装仍被拦下(见 $FIX/run-E.out);拦下原因:$(grep -m1 '多包校验失败' "$FIX/run-E.out" | sed 's/^ *· *//')"
246
- else
247
- ok "不再报「多包校验失败」(注册表投影已被判据豁免:计划阶段与落盘阶段同一个 isRegistryProjection())"
248
- fi
249
- python3 - "$FIX/home-e/.dsh/argent/install.json" "$FIX/home-e/.argent-dsh/scripts/flow-requirements.json" <<'PY'
250
- import json, os, sys
251
- ij, reg = sys.argv[1], sys.argv[2]
252
- if not os.path.exists(ij):
253
- print(f' ✗ 装机清单不在位:{ij}'); sys.exit(1)
254
- doc = json.load(open(ij))
255
- caps = {c['id']: c.get('integrity') for c in doc.get('capabilities', [])}
256
- want = {'full', 'invoice'}
257
- if want <= set(caps):
258
- print(f" ✅ 装机清单两个能力都在({', '.join(f'{k}={v}' for k, v in sorted(caps.items()))})")
259
- else:
260
- print(f" ✗ 装机清单缺能力:{sorted(want - set(caps))}(实际 {sorted(caps)})"); sys.exit(1)
261
- if all(caps[k] == 'verified' for k in want):
262
- print(' ✅ 两个能力的 integrity 都是 verified')
263
- else:
264
- print(f" ✗ integrity 不是全 verified:{caps}"); sys.exit(1)
265
- if not os.path.exists(reg):
266
- print(f' ✗ 盘上注册表不在位:{reg}'); sys.exit(1)
267
- d = json.load(open(reg))
268
- c = sorted(d.get('capabilities') or {})
269
- if {'full', 'invoice'} <= set(c):
270
- print(f" ✅ 盘上注册表 = 两份投影的并集(capabilities:{', '.join(c)})")
271
- else:
272
- print(f" ✗ 注册表缺能力条目(实际 {c})—— 并集没写成"); sys.exit(1)
273
- PY
274
- [ "$?" = 0 ] || bad "E 步判据未过(装机清单 / 注册表并集,见上)"
275
-
276
- # ── 哨兵比对 ───────────────────────────────────────────────────────────────
277
- hdr "哨兵:真实目录零变化"
278
- sentinel "$FIX/sentinel-after.txt"
279
- tail -n +2 "$FIX/sentinel-before.txt" > "$FIX/sentinel-before.body"
280
- tail -n +2 "$FIX/sentinel-after.txt" > "$FIX/sentinel-after.body"
281
- if diff -u "$FIX/sentinel-before.body" "$FIX/sentinel-after.body" > "$FIX/sentinel.diff" 2>&1; then
282
- ok "真实目录哨兵逐字节一致($FIX/sentinel-before.txt)"
283
- else
284
- bad "真实目录哨兵有变化(本次演练本应零写入):"; cat "$FIX/sentinel.diff"
285
- fi
286
- if node "$REPO/scripts/check-test-env-safety.mjs" --verify "$FIX/sentinel-datadir-before.json" >"$FIX/sentinel-datadir.out" 2>&1; then
287
- ok "真实数据目录三层判据(关键文件/新增文件/配置内容)无本次演练引起的改动"
288
- else
289
- warn "真实数据目录有变化(可能是常驻服务的正常写入;逐条判断):"; tail -12 "$FIX/sentinel-datadir.out"
290
- fi
291
-
292
- # ── 汇总 ───────────────────────────────────────────────────────────────────
293
- hdr "汇总"
294
- python3 - "$FIX" <<'PY'
295
- import json,os,sys
296
- fix=sys.argv[1]
297
- for tag in ('A','B','C','D','E','F'):
298
- p=os.path.join(fix,f'report-{tag}.json')
299
- if not os.path.exists(p): print(f' {tag}: (无报告)'); continue
300
- d=json.load(open(p))
301
- st={s['id']:s['status'] for s in d['steps']}
302
- print(f" {tag}: ok={d['ok']} mode={d['mode']} 耗时={d['elapsedSeconds']}s 逐段={st}")
303
- print(f" 装配={d['segments']['install']}")
304
- print(f" 技能={d['segments']['skills']}")
305
- print(f" 许可={d['segments']['license']}")
306
- print(f" 自检={d['selfCheck']}")
307
- PY
308
- note ""
309
- if [ "$FAIL" = 0 ]; then
310
- note "✅ 演练全部符合预期(夹具:${FIX})"
311
- else
312
- note "✗ 演练有不符合预期项(夹具:${FIX})—— 逐条见上"
313
- fi
314
- if [ "$KEEP" = 0 ] && [ "$FAIL" = 0 ]; then rm -rf "$FIX"; note "(夹具已清理;要留证据加 --keep)"; else note "(夹具保留:${FIX})"; fi
315
- exit "$FAIL"
@@ -1,190 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ══════════════════════════════════════════════════════════════════════════════
3
- # install-internal-role.sh — 协作者内置角色一键装配(KB-078 短期路径)
4
- #
5
- # 用途:在同事 PC 上执行后,获得「内置角色」完整开发环境(preset + skill +
6
- # 默认 preset 配置),让协作者本周即可开工官网/项目开发。
7
- #
8
- # 使用方式(需先安装 Argent,安装器见 https://whyshu.com/dl/install-argent.mjs):
9
- # bash install-internal-role.sh --role whyshu-web-dev # 装配官网开发者角色
10
- # bash install-internal-role.sh --role pet-dev # 预留:云宠乐园开发者
11
- # bash install-internal-role.sh --role argent-dev # 预留:Argent 开发者
12
- # bash install-internal-role.sh --role whyshu-web-dev --dry-run # 只打印计划,不执行
13
- # bash install-internal-role.sh --role whyshu-web-dev --repo /path/to/argent-dsh # 指定仓库源
14
- #
15
- # 本脚本做什么:
16
- # 1. 从 argent-dsh 仓库复制角色 preset → ~/.dsh/.agent-presets/argent-<role>/
17
- # (agent.cordis.yml + preset.yml + format-version.txt)
18
- # 2. 复制角色 skill → ~/.dsh/skills/<role>/(SKILL.md,若仓库存在该角色技能)
19
- # 3. 写入/更新 ~/.dsh/settings.yaml 的 agent-presets.default = argent-<role>
20
- # (新会话默认挂载该角色 preset)
21
- # 4. 幂等:重复执行不重复拷贝、不破坏已有配置;输出成功清单
22
- #
23
- # 说明(决策㉛㉜㉝):
24
- # - 不注册飞书 Bot(真人驱动模式:人 @ 使用,不配独立 Bot/事件订阅)
25
- # - 部署走 GitHub Actions(协作者无服务器权限,push main 自动部署官网)
26
- # - 源仓库定位顺序:--repo 参数 > ${ARGENT_REPO} > ~/.argent-dsh(git clone)>
27
- # ~/Code/argent-dsh > 脚本自身所在目录的上级
28
- # ══════════════════════════════════════════════════════════════════════════════
29
- set -euo pipefail
30
-
31
- # ── 参数 ──────────────────────────────────────────────────────────────────────
32
- ROLE=""
33
- DRY_RUN=0
34
- SOURCE=""
35
-
36
- usage() {
37
- sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
38
- exit 0
39
- }
40
-
41
- while [[ $# -gt 0 ]]; do
42
- case "$1" in
43
- --role) ROLE="${2:-}"; shift 2 ;;
44
- --dry-run) DRY_RUN=1; shift ;;
45
- --repo) SOURCE="${2:-}"; shift 2 ;;
46
- -h|--help) usage ;;
47
- *) echo "✗ 未知参数: $1"; usage ;;
48
- esac
49
- done
50
-
51
- if [[ -z "${ROLE}" ]]; then
52
- echo "✗ 缺少 --role 参数(如 --role whyshu-web-dev)"
53
- usage
54
- fi
55
-
56
- # ── 路径 ──────────────────────────────────────────────────────────────────────
57
- DSH_HOME="${DSH_HOME:-${HOME}/.dsh}"
58
- PRESET_TARGET="${DSH_HOME}/.agent-presets/argent-${ROLE}"
59
- SKILL_TARGET="${DSH_HOME}/skills/${ROLE}"
60
- SETTINGS="${DSH_HOME}/settings.yaml"
61
-
62
- # 源仓库定位
63
- if [[ -z "${SOURCE}" ]]; then
64
- for cand in "${ARGENT_REPO:-}" "${HOME}/.argent-dsh" "${HOME}/Code/argent-dsh" "$(cd "$(dirname "$0")/.." && pwd)"; do
65
- if [[ -n "$cand" && -d "$cand/presets" && -d "$cand/scripts" ]]; then
66
- SOURCE="$cand"
67
- break
68
- fi
69
- done
70
- fi
71
- if [[ -z "${SOURCE}" || ! -d "${SOURCE}/presets" ]]; then
72
- echo "✗ 未找到 argent-dsh 仓库源(可用 --repo <路径> 指定)"
73
- exit 1
74
- fi
75
- echo "· 源仓库: ${SOURCE}"
76
-
77
- PRESET_SRC="${SOURCE}/presets/argent-${ROLE}"
78
- if [[ ! -d "${PRESET_SRC}" ]]; then
79
- echo "✗ 仓库中不存在角色 preset: presets/argent-${ROLE}(可用 --role whyshu-web-dev / pet-dev / argent-dev 等)"
80
- exit 1
81
- fi
82
- SKILL_SRC="${SOURCE}/skills/${ROLE}"
83
-
84
- # ── 计划 ──────────────────────────────────────────────────────────────────────
85
- plan_preset="复制 preset → ${PRESET_TARGET}"
86
- plan_skill="无 skill 可复制"
87
- if [[ -d "${SKILL_SRC}" && -f "${SKILL_SRC}/SKILL.md" ]]; then
88
- plan_skill="复制 skill → ${SKILL_TARGET}"
89
- fi
90
- plan_settings="设置 agent-presets.default = argent-${ROLE}(${SETTINGS})"
91
-
92
- echo
93
- echo "装配计划(角色 ${ROLE}):"
94
- echo " 1. $plan_preset"
95
- echo " 2. $plan_skill"
96
- echo " 3. $plan_settings"
97
- echo
98
-
99
- # ── 执行 ──────────────────────────────────────────────────────────────────────
100
- done_list=()
101
- skip_list=()
102
-
103
- copy_dir() { # src dst label —— 幂等:已存在且内容一致则跳过
104
- local src="$1" dst="$2" label="$3"
105
- if [[ -d "$dst" ]] && diff -rq "$src" "$dst" >/dev/null 2>&1; then
106
- skip_list+=("$label 已存在且一致(幂等跳过)")
107
- else
108
- mkdir -p "$(dirname "$dst")"
109
- if [[ -d "$dst" ]]; then rm -rf "$dst"; fi
110
- cp -R "$src" "$dst"
111
- if diff -rq "$src" "$dst" >/dev/null 2>&1; then
112
- done_list+=("$label → $dst")
113
- else
114
- echo "✗ $label 复制后校验不一致: $dst"
115
- exit 1
116
- fi
117
- fi
118
- }
119
-
120
- # 1) preset
121
- echo "① preset"
122
- if [[ ${DRY_RUN} -eq 1 ]]; then
123
- echo " [dry-run] $plan_preset"
124
- else
125
- copy_dir "${PRESET_SRC}" "${PRESET_TARGET}" "preset argent-${ROLE}"
126
- fi
127
-
128
- # 2) skill
129
- echo "② skill"
130
- if [[ -d "${SKILL_SRC}" && -f "${SKILL_SRC}/SKILL.md" ]]; then
131
- if [[ ${DRY_RUN} -eq 1 ]]; then
132
- echo " [dry-run] $plan_skill"
133
- else
134
- copy_dir "${SKILL_SRC}" "${SKILL_TARGET}" "skill ${ROLE}"
135
- fi
136
- else
137
- echo " · 仓库无 ${ROLE} 技能目录(跳过;preset 内已含能力定义)"
138
- fi
139
-
140
- # 3) settings.yaml(node 处理:幂等 + 备份)
141
- echo "③ settings.yaml"
142
- if [[ ${DRY_RUN} -eq 1 ]]; then
143
- echo " [dry-run] $plan_settings"
144
- else
145
- if command -v node >/dev/null 2>&1; then
146
- if [[ -f "${SETTINGS}" ]]; then
147
- cp "${SETTINGS}" "${SETTINGS}.bak-$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
148
- fi
149
- out=$(node -e '
150
- const fs = require("fs");
151
- const path = process.argv[1], want = "argent-" + process.argv[2];
152
- let t = ""; try { t = fs.readFileSync(path, "utf8"); } catch {}
153
- if (!/agent-presets:/.test(t)) {
154
- t = t.trimEnd() + "\n\nagent-presets:\n default: " + want + "\n";
155
- fs.writeFileSync(path, t);
156
- console.log("新增 agent-presets.default = " + want);
157
- } else if (new RegExp("^\\s*default:\\s*" + want + "\\s*$", "m").test(t)) {
158
- console.log("已是目标值(幂等跳过)");
159
- } else {
160
- const before = t;
161
- t = t.replace(/^(\s*default:)(\s*)(\S*)(\s*)$/m, "$1$2" + want + "$4");
162
- if (t === before) { console.log("agent-presets 段无 default 行,追加"); t = t.trimEnd() + "\n default: " + want + "\n"; }
163
- fs.writeFileSync(path, t);
164
- console.log("更新 agent-presets.default = " + want);
165
- }
166
- ' "${SETTINGS}" "${ROLE}")
167
- echo " · $out"
168
- if [[ "$out" == *"幂等跳过"* ]]; then
169
- skip_list+=("settings.yaml 已是目标值(幂等跳过)")
170
- else
171
- done_list+=("settings.yaml agent-presets.default = argent-${ROLE}")
172
- fi
173
- else
174
- echo " ✗ 未找到 node(Argent 环境需 Node.js,请先安装)"
175
- exit 1
176
- fi
177
- fi
178
-
179
- # ── 成功清单 ──────────────────────────────────────────────────────────────────
180
- echo
181
- if [[ ${DRY_RUN} -eq 1 ]]; then
182
- echo "✅ [dry-run] 计划完成(未做任何改动)。去掉 --dry-run 执行装配。"
183
- exit 0
184
- fi
185
- echo "✅ 装配完成(角色 ${ROLE})"
186
- for s in "${done_list[@]}"; do echo " ✔ $s"; done
187
- for s in "${skip_list[@]}"; do echo " · $s"; done
188
- echo
189
- echo "下一步:重新打开 Argent 会话(新会话默认挂载 argent-${ROLE} preset),即可开始开发。"
190
- echo "说明:未注册飞书 Bot(真人驱动模式);部署走 GitHub Actions,无需服务器权限。"