@whyshu/argent-engine 0.0.0-stage → 0.6.0
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/MANIFEST.json +307 -0
- package/engine/VERSION +1 -0
- package/engine/agents/catalog.json +454 -0
- package/engine/configs/_aliases.proposed.json +651 -0
- package/engine/configs/_migration-plan.proposed.json +899 -0
- package/engine/configs/account.json +272 -0
- package/engine/configs/advanced.json +205 -0
- package/engine/configs/host.json +149 -0
- package/engine/configs/hr.json +27 -0
- package/engine/configs/notify.json +264 -0
- package/engine/core/cli/argent.mjs +277 -0
- package/engine/core/contracts/argent.contracts.json +1065 -0
- package/engine/core/contracts/verbs.schema.json +2539 -0
- package/engine/core/lib/argent-datadir.mjs +223 -0
- package/engine/core/lib/audit.mjs +187 -0
- package/engine/core/lib/claim.mjs +258 -0
- package/engine/core/lib/config-catalog.mjs +1172 -0
- package/engine/core/lib/doctor-customer.mjs +577 -0
- package/engine/core/lib/flow-list.mjs +138 -0
- package/engine/core/lib/flow-registry.mjs +48 -0
- package/engine/core/lib/init.mjs +247 -0
- package/engine/core/lib/ledger.mjs +124 -0
- package/engine/core/lib/license-activate.mjs +813 -0
- package/engine/core/lib/license-device.mjs +314 -0
- package/engine/core/lib/license-file.mjs +349 -0
- package/engine/core/lib/license-guard.mjs +123 -0
- package/engine/core/lib/license-ops.mjs +1645 -0
- package/engine/core/lib/license-verdict.mjs +133 -0
- package/engine/core/lib/license.mjs +577 -0
- package/engine/core/lib/notify/dedup.mjs +101 -0
- package/engine/core/lib/notify/feishu.mjs +38 -0
- package/engine/core/lib/notify/index.mjs +95 -0
- package/engine/core/lib/notify/verb.mjs +327 -0
- package/engine/core/lib/notify/webhook.mjs +135 -0
- package/engine/core/lib/notify/wecom.mjs +36 -0
- package/engine/core/lib/python.mjs +145 -0
- package/engine/core/lib/sop.mjs +116 -0
- package/engine/core/lib/versions.mjs +205 -0
- package/engine/core/mcp/client.mjs +67 -0
- package/engine/core/mcp/http.mjs +138 -0
- package/engine/core/mcp/server.mjs +702 -0
- package/engine/scripts/_engine_rules_probe.py +15 -0
- package/engine/scripts/analyze-full-day.py +568 -0
- package/engine/scripts/archive/feishu-bridge.js +246 -0
- package/engine/scripts/argent-agents.mjs +527 -0
- package/engine/scripts/argent-capabilities.mjs +776 -0
- package/engine/scripts/argent-catalog.mjs +186 -0
- package/engine/scripts/argent-datadir.py +62 -0
- package/engine/scripts/argent-doctor.mjs +624 -0
- package/engine/scripts/argent-env-write.mjs +302 -0
- package/engine/scripts/argent-schedule.mjs +620 -0
- package/engine/scripts/argent-setup-check.mjs +369 -0
- package/engine/scripts/argent-upgrade.mjs +1046 -0
- package/engine/scripts/base-bootstrap.sh +575 -0
- package/engine/scripts/billing-mode-daily.sh +12 -0
- package/engine/scripts/build-engine-package.mjs +123 -0
- package/engine/scripts/capability-packs.lock.json +484 -0
- package/engine/scripts/capability_pack.py +1242 -0
- package/engine/scripts/check-1.0-criteria.mjs +355 -0
- package/engine/scripts/check-agent-catalog.mjs +141 -0
- package/engine/scripts/check-billing-mode.sh +377 -0
- package/engine/scripts/check-child-sessions.mjs +83 -0
- package/engine/scripts/check-config-catalog.mjs +677 -0
- package/engine/scripts/check-docs-drift.mjs +522 -0
- package/engine/scripts/check-dsh-web-expose.sh +98 -0
- package/engine/scripts/check-judgment-drift.mjs +403 -0
- package/engine/scripts/check-license.mjs +199 -0
- package/engine/scripts/check-model-auth.py +443 -0
- package/engine/scripts/check-pack-closure.mjs +661 -0
- package/engine/scripts/check-portable-symbols.mjs +116 -0
- package/engine/scripts/check-preset-landing.mjs +124 -0
- package/engine/scripts/check-production-home.mjs +602 -0
- package/engine/scripts/check-role-mapping.mjs +461 -0
- package/engine/scripts/check-sample-redaction.mjs +320 -0
- package/engine/scripts/check-skill-classification.mjs +188 -0
- package/engine/scripts/check-sop-consistency.mjs +431 -0
- package/engine/scripts/check-test-env-safety.mjs +627 -0
- package/engine/scripts/check-version-consistency.mjs +452 -0
- package/engine/scripts/ci-run.sh +558 -0
- package/engine/scripts/config-explain.mjs +278 -0
- package/engine/scripts/configure-provider.rb +29 -0
- package/engine/scripts/customer-config-migrate.mjs +179 -0
- package/engine/scripts/customer-preflight.py +892 -0
- package/engine/scripts/customer-readiness.sh +134 -0
- package/engine/scripts/daily-report-inbox.py +242 -0
- package/engine/scripts/daily-report-reconcile.mjs +312 -0
- package/engine/scripts/daily-report.mjs +1310 -0
- package/engine/scripts/daily-report.sh +46 -0
- package/engine/scripts/data/lip-product-dict.json +306 -0
- package/engine/scripts/deliver-customer.mjs +932 -0
- package/engine/scripts/delivery-faces.txt +31 -0
- package/engine/scripts/desktop-trial.sh +146 -0
- package/engine/scripts/dev-window.sh +260 -0
- package/engine/scripts/drill-clean-machine.sh +315 -0
- package/engine/scripts/dsh-api-client.mjs +215 -0
- package/engine/scripts/dsh-backup-scope.sh +83 -0
- package/engine/scripts/dsh-offline-pack.mjs +77 -0
- package/engine/scripts/dsh-overlay-pin.sh +87 -0
- package/engine/scripts/dsh-plane.mjs +153 -0
- package/engine/scripts/dsh-portforward.py +223 -0
- package/engine/scripts/dsh-preflight.mjs +179 -0
- package/engine/scripts/dsh-release-watch-daily.sh +19 -0
- package/engine/scripts/dsh-release-watch.mjs +458 -0
- package/engine/scripts/dsh-remote-check.sh +99 -0
- package/engine/scripts/dsh-restart.sh +179 -0
- package/engine/scripts/dsh-restore.sh +111 -0
- package/engine/scripts/dsh-scope-rewire.mjs +803 -0
- package/engine/scripts/dsh-snapshot.mjs +116 -0
- package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
- package/engine/scripts/dsh-upgrade.mjs +612 -0
- package/engine/scripts/dsh-url.sh +86 -0
- package/engine/scripts/dsh-web-launch.mjs +155 -0
- package/engine/scripts/ensure-store-windows.py +509 -0
- package/engine/scripts/entitlements.py +458 -0
- package/engine/scripts/env_probe.py +411 -0
- package/engine/scripts/feishu-card.mjs +252 -0
- package/engine/scripts/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +720 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +86 -0
- package/engine/scripts/flow-timers.sh +182 -0
- package/engine/scripts/flow-trigger-wake.mjs +284 -0
- package/engine/scripts/flow-trigger.sh +571 -0
- package/engine/scripts/flow_billing.py +354 -0
- package/engine/scripts/flow_capability_stats.py +314 -0
- package/engine/scripts/flow_case.py +194 -0
- package/engine/scripts/flow_claim.py +497 -0
- package/engine/scripts/flow_config.py +1024 -0
- package/engine/scripts/flow_failure.py +588 -0
- package/engine/scripts/flow_learn.py +105 -0
- package/engine/scripts/flow_ledger.py +235 -0
- package/engine/scripts/flow_policy.py +238 -0
- package/engine/scripts/flow_preflight.py +339 -0
- package/engine/scripts/flow_registry.py +341 -0
- package/engine/scripts/flow_retry.py +304 -0
- package/engine/scripts/flow_return_to_ops.py +191 -0
- package/engine/scripts/flow_source.py +315 -0
- package/engine/scripts/flow_sources/__init__.py +53 -0
- package/engine/scripts/flow_sources/cases_json.py +106 -0
- package/engine/scripts/flow_sources/common.py +428 -0
- package/engine/scripts/flow_sources/feishu_base.py +273 -0
- package/engine/scripts/flow_stale.py +185 -0
- package/engine/scripts/flow_status_reconcile.py +344 -0
- package/engine/scripts/flow_triage.py +849 -0
- package/engine/scripts/flow_write_verify.py +496 -0
- package/engine/scripts/fulfillment.js +586 -0
- package/engine/scripts/gen-agent-bundles.mjs +276 -0
- package/engine/scripts/gen-preset-rows.mjs +259 -0
- package/engine/scripts/init-ml.sh +2275 -0
- package/engine/scripts/install-internal-role.sh +190 -0
- package/engine/scripts/install-license.mjs +641 -0
- package/engine/scripts/install-one-shot.mjs +665 -0
- package/engine/scripts/integration_contract.py +220 -0
- package/engine/scripts/integrations/wenyao/adapter.py +98 -0
- package/engine/scripts/integrations/xzh/adapter.py +75 -0
- package/engine/scripts/invoice-mapping.example.json +40 -0
- package/engine/scripts/invoice-table.sh +194 -0
- package/engine/scripts/invoice_carrier_notice.py +279 -0
- package/engine/scripts/invoice_fail_closeout.py +297 -0
- package/engine/scripts/invoice_lip_runner.py +1508 -0
- package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
- package/engine/scripts/invoice_ready_audit.py +191 -0
- package/engine/scripts/invoice_runner.py +675 -0
- package/engine/scripts/invoice_stageb.py +516 -0
- package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
- package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
- package/engine/scripts/lease.py +377 -0
- package/engine/scripts/lib/agent-install.mjs +649 -0
- package/engine/scripts/lib/argent-datadir.mjs +16 -0
- package/engine/scripts/lib/argent_datadir.py +262 -0
- package/engine/scripts/lib/argent_filelock.py +507 -0
- package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
- package/engine/scripts/lib/cdp_ports.py +197 -0
- package/engine/scripts/lib/customer-env.mjs +102 -0
- package/engine/scripts/lib/daily-report-core.mjs +517 -0
- package/engine/scripts/lib/dep-declaration.mjs +81 -0
- package/engine/scripts/lib/deps-migrate.mjs +310 -0
- package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
- package/engine/scripts/lib/dsh-scope.mjs +500 -0
- package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
- package/engine/scripts/lib/dsh-versions.mjs +96 -0
- package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
- package/engine/scripts/lib/is-direct-run.mjs +61 -0
- package/engine/scripts/lib/patch-compose.mjs +241 -0
- package/engine/scripts/lib/patch-target.mjs +35 -0
- package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
- package/engine/scripts/lib/plugin-packages.mjs +332 -0
- package/engine/scripts/lib/preset-compat.mjs +127 -0
- package/engine/scripts/lib/preset-install.mjs +575 -0
- package/engine/scripts/lib/preset-mechanism.mjs +56 -0
- package/engine/scripts/lib/preset-sync.mjs +59 -0
- package/engine/scripts/lib/profile-retire.mjs +97 -0
- package/engine/scripts/lib/python-runtime.mjs +190 -0
- package/engine/scripts/lib/registry.mjs +72 -0
- package/engine/scripts/lib/repo-root.sh +56 -0
- package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
- package/engine/scripts/lib/schedule-probe.mjs +835 -0
- package/engine/scripts/lib/scope-pointer.mjs +164 -0
- package/engine/scripts/lib/settings-panels.mjs +816 -0
- package/engine/scripts/lib/stdio_utf8.py +86 -0
- package/engine/scripts/lib/wake-auth.mjs +392 -0
- package/engine/scripts/lib/webdriver_ipc.py +264 -0
- package/engine/scripts/lib/zstd-frames.mjs +104 -0
- package/engine/scripts/license-e2e-drill.mjs +426 -0
- package/engine/scripts/license-gate.mjs +245 -0
- package/engine/scripts/lip_backlog.py +171 -0
- package/engine/scripts/lip_table.py +185 -0
- package/engine/scripts/local-usage.mjs +214 -0
- package/engine/scripts/migrate-providers.mjs +127 -0
- package/engine/scripts/migrate-skills.sh +65 -0
- package/engine/scripts/ml-oauth-setup.mjs +550 -0
- package/engine/scripts/ml-store-ids.json +26 -0
- package/engine/scripts/ml_artifact_attribution.py +333 -0
- package/engine/scripts/ml_capacity_probe.py +1158 -0
- package/engine/scripts/ml_hub_status.py +1556 -0
- package/engine/scripts/ml_schema_check.py +115 -0
- package/engine/scripts/ml_store_ids.py +139 -0
- package/engine/scripts/npm-publish-argent.mjs +499 -0
- package/engine/scripts/npm-publish-argent.sh +20 -0
- package/engine/scripts/orchestrator.py +13811 -0
- package/engine/scripts/pack-plugins.mjs +140 -0
- package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
- package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
- package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
- package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
- package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
- package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
- package/engine/scripts/patches/verify-hardening.mjs +167 -0
- package/engine/scripts/patches/verify-session-migration.mjs +149 -0
- package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
- package/engine/scripts/plan-role-rename.mjs +125 -0
- package/engine/scripts/plugin-bootcheck.mjs +451 -0
- package/engine/scripts/plugin-composition.json +15 -0
- package/engine/scripts/poc-mcp-host.mjs +455 -0
- package/engine/scripts/poll-fulfillment.sh +293 -0
- package/engine/scripts/poll-invoice.sh +74 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +70 -0
- package/engine/scripts/provider-switch.sh +146 -0
- package/engine/scripts/provider-sync-target.check.mjs +64 -0
- package/engine/scripts/python-runtime.sh +78 -0
- package/engine/scripts/query_runner.py +310 -0
- package/engine/scripts/release-argent.sh +574 -0
- package/engine/scripts/repair-unresolved-tools.mjs +178 -0
- package/engine/scripts/run-tests.sh +256 -0
- package/engine/scripts/self-sign-license.mjs +179 -0
- package/engine/scripts/session-health-scan.mjs +63 -0
- package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
- package/engine/scripts/setup.sh +22 -0
- package/engine/scripts/skill-install.mjs +629 -0
- package/engine/scripts/sku_translate.py +148 -0
- package/engine/scripts/sync-plugins.sh +90 -0
- package/engine/scripts/sync-presets.sh +27 -0
- package/engine/scripts/sync-scripts.sh +445 -0
- package/engine/scripts/telemetry.py +873 -0
- package/engine/scripts/ui-copy-count.mjs +220 -0
- package/engine/scripts/usage-anchor.sh +136 -0
- package/engine/scripts/usage_report.py +531 -0
- package/engine/scripts/validate-preset-schema.mjs +208 -0
- package/engine/scripts/vertical-init.mjs +323 -0
- package/engine/scripts/vertical-lint.mjs +482 -0
- package/engine/scripts/wenyao-browser.sh +73 -0
- package/engine/scripts/wenyao-fixture-export.py +116 -0
- package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
- package/engine/scripts/wenyao-login.sh +34 -0
- package/engine/scripts/wenyao-template-map.json +292 -0
- package/engine/scripts/wenyao_attach.py +131 -0
- package/engine/scripts/wenyao_download_template.py +144 -0
- package/engine/scripts/wenyao_fill_template.py +568 -0
- package/engine/scripts/wenyao_login.py +145 -0
- package/engine/scripts/wenyao_portal.py +633 -0
- package/engine/scripts/wenyao_recon.py +230 -0
- package/engine/scripts/wenyao_run.py +503 -0
- package/engine/scripts/wenyao_shadow_diff.py +160 -0
- package/engine/scripts/wenyao_skill_contract.py +156 -0
- package/engine/scripts/wenyao_template_fetch.py +175 -0
- package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
- package/engine/scripts/ziniao-auth-probe.sh +268 -0
- package/engine/scripts/ziniao-keepalive.sh +216 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
- package/engine/scripts/ziniao-windows.sh +153 -0
- package/engine/version.mjs +1 -0
- package/engine/verticals/README.md +103 -0
- package/engine/verticals/_engine-words.baseline.txt +237 -0
- package/engine/verticals/_engine-words.json +19 -0
- package/engine/verticals/mercadolibre/README.md +43 -0
- package/engine/verticals/mercadolibre/config.json +709 -0
- package/engine/verticals/mercadolibre/flows.json +706 -0
- package/engine/verticals/mercadolibre/manifest.json +538 -0
- package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
- package/engine/verticals/mercadolibre/preflight.py +143 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
- package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
- package/engine/verticals/mercadolibre/prompts/full.md +91 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
- package/package.json +21 -4
- package/README.md +0 -3
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* plan-role-rename —— P2b(角色/preset/技能改名为 `<vertical>-*`)的**只读规划器**
|
|
4
|
+
*
|
|
5
|
+
* 为什么只读:P2b 要同时改**仓库**(presets/skills 目录名)与**机器配置**(gateway.json /
|
|
6
|
+
* roles.json / 角色会话引用),并且只有**重启服务**才能验证挂载成功。所以本工具**不写任何东西**:
|
|
7
|
+
* 它把"窗口来了要改哪些、改成什么、哪些地方会受影响"算清楚打印出来,供人过一眼再执行。
|
|
8
|
+
* (`--apply` 刻意不实现 —— 改名必须与重启窗口在一起,不能分两次做。)
|
|
9
|
+
*
|
|
10
|
+
* 判据来源(唯一真源,不写死第二份):
|
|
11
|
+
* · 业务线自报的迁移期豁免:`verticals/<id>/manifest.json#legacyNaming`
|
|
12
|
+
* · 仓库现状:`presets/`、`skills/` 目录名
|
|
13
|
+
* · 机器现状:`<DSH_HOME>/gateway.json`、`<DSH_HOME>/roles.json`(角色 → preset/技能引用)
|
|
14
|
+
*
|
|
15
|
+
* 用法:node scripts/plan-role-rename.mjs [--vertical <id>] [--json]
|
|
16
|
+
* 退出码:0 = 已算清(不表示"需要改"或"不需要改",看输出);2 = 参数/环境问题。
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
19
|
+
import { dirname, join, resolve } from 'node:path'
|
|
20
|
+
import { fileURLToPath } from 'node:url'
|
|
21
|
+
import { dshHome } from './lib/scope-pointer.mjs'
|
|
22
|
+
|
|
23
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
24
|
+
const REPO = resolve(HERE, '..')
|
|
25
|
+
const argOf = (n, d) => { const i = process.argv.indexOf(n); return i > -1 ? process.argv[i + 1] ?? d : d }
|
|
26
|
+
const AS_JSON = process.argv.includes('--json')
|
|
27
|
+
// 不写死任何业务线名(引擎文件里出现业务线名会被 vertical-lint 拦下 —— 这条门禁对
|
|
28
|
+
// 我自己的工具同样有效):缺省从 `verticals/*/manifest.json` 发现;多于一条时必须显式 --vertical。
|
|
29
|
+
const discovered = existsSync(join(REPO, 'verticals'))
|
|
30
|
+
? readdirSync(join(REPO, 'verticals'), { withFileTypes: true })
|
|
31
|
+
.filter((d) => d.isDirectory() && existsSync(join(REPO, 'verticals', d.name, 'manifest.json')))
|
|
32
|
+
.map((d) => d.name)
|
|
33
|
+
: []
|
|
34
|
+
const VERTICAL = argOf('--vertical', '') || (discovered.length === 1 ? discovered[0] : '')
|
|
35
|
+
if (!VERTICAL) {
|
|
36
|
+
console.error(discovered.length
|
|
37
|
+
? `✗ 有多条业务线,请显式指定:--vertical <${discovered.join('|')}>`
|
|
38
|
+
: '✗ verticals/ 下没有业务线')
|
|
39
|
+
process.exit(2)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const vdir = join(REPO, 'verticals', VERTICAL)
|
|
43
|
+
if (!existsSync(vdir)) {
|
|
44
|
+
console.error(`✗ 找不到业务线:${vdir}`)
|
|
45
|
+
process.exit(2)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const manifest = JSON.parse(readFileSync(join(vdir, 'manifest.json'), 'utf8'))
|
|
49
|
+
const legacy = manifest.legacyNaming || []
|
|
50
|
+
const prefix = manifest.namespace || manifest.id || VERTICAL
|
|
51
|
+
|
|
52
|
+
const rows = []
|
|
53
|
+
const note = (kind, from, to, where, extra = '') => rows.push({ kind, from, to, where, extra })
|
|
54
|
+
|
|
55
|
+
// ── ① 仓库:presets / skills 目录 ────────────────────────────────
|
|
56
|
+
const listDirs = (rel) => {
|
|
57
|
+
const p = join(REPO, rel)
|
|
58
|
+
return existsSync(p) ? readdirSync(p, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : []
|
|
59
|
+
}
|
|
60
|
+
if (legacy.includes('skills')) {
|
|
61
|
+
for (const d of listDirs('skills')) {
|
|
62
|
+
// 只规划"业务线专属"的技能:以 ml- 开头(历史命名),或 manifest 显式声明归属于本线
|
|
63
|
+
if (/^ml-/.test(d)) {
|
|
64
|
+
const to = d.replace(/^ml-/, `${prefix}-`)
|
|
65
|
+
note('skill', d, to, `skills/${d}`, '目录改名 + SKILL.md frontmatter 的 name(必须等于目录名)')
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (legacy.includes('roles') || legacy.includes('presets')) {
|
|
70
|
+
for (const d of listDirs('presets')) {
|
|
71
|
+
if (/^argent-ml-/.test(d)) {
|
|
72
|
+
note('preset', d, d.replace(/^argent-ml-/, `argent-${prefix}-`), `presets/${d}`, '目录改名(preset id 随之变化)')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── ② 机器:gateway.json / roles.json 里对这些名字的引用 ──────────
|
|
78
|
+
const home = dshHome()
|
|
79
|
+
// 真实位置:`<DSH_HOME>/argent/{gateway,roles}.json`(不是 <DSH_HOME>/ 根)——
|
|
80
|
+
// 用候选列表而不是猜一个(两种布局都认,找不到就明说"未找到",不谎报"无引用")。
|
|
81
|
+
const machineFiles = [
|
|
82
|
+
join(home, 'argent', 'gateway.json'), join(home, 'argent', 'roles.json'),
|
|
83
|
+
join(home, 'gateway.json'), join(home, 'roles.json'),
|
|
84
|
+
].filter((f) => existsSync(f))
|
|
85
|
+
const oldNames = new Set(rows.map((r) => r.from))
|
|
86
|
+
const hitCount = new Map()
|
|
87
|
+
for (const f of machineFiles) {
|
|
88
|
+
let doc
|
|
89
|
+
try { doc = JSON.parse(readFileSync(f, 'utf8')) } catch { continue }
|
|
90
|
+
const text = JSON.stringify(doc)
|
|
91
|
+
for (const name of oldNames) {
|
|
92
|
+
const n = text.split(name).length - 1
|
|
93
|
+
if (n) hitCount.set(`${f.replace(home, '<DSH_HOME>')} :: ${name}`, n)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── ③ 输出 ────────────────────────────────────────────────────
|
|
98
|
+
if (AS_JSON) {
|
|
99
|
+
console.log(JSON.stringify({
|
|
100
|
+
vertical: VERTICAL, prefix, legacyNaming: legacy, machineFiles: machineFiles.map((f) => f.replace(home, '<DSH_HOME>')),
|
|
101
|
+
plan: rows, machineHits: Object.fromEntries(hitCount),
|
|
102
|
+
}, null, 2))
|
|
103
|
+
process.exit(0)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(`\n【P2b 改名规划(只读)】业务线 ${VERTICAL}|前缀 ${prefix}|迁移期豁免:${legacy.join('、') || '(无)'}\n`)
|
|
107
|
+
if (!rows.length) {
|
|
108
|
+
console.log(' 无待改名项(仓库侧已是目标命名,或该线的 legacyNaming 未声明 roles/skills)')
|
|
109
|
+
} else {
|
|
110
|
+
const byKind = rows.reduce((m, r) => (m[r.kind] = (m[r.kind] || 0) + 1, m), {})
|
|
111
|
+
console.log(` 仓库侧待改 ${rows.length} 项(${Object.entries(byKind).map(([k, v]) => `${k} ${v}`).join(',')}):`)
|
|
112
|
+
for (const r of rows) console.log(` · ${r.kind.padEnd(6)} ${r.from} → ${r.to}\n 位置:${r.where}\n 连带:${r.extra}`)
|
|
113
|
+
}
|
|
114
|
+
console.log(`\n 机器侧引用(${machineFiles.length ? machineFiles.map((f) => f.replace(home, '<DSH_HOME>')).join('、') : '未找到 gateway.json / roles.json'}):`)
|
|
115
|
+
if (!hitCount.size) console.log(' (这些文件里未发现对上述旧名的引用 —— 仍需在改名后核对角色会话与 preset 挂载)')
|
|
116
|
+
else for (const [k, n] of hitCount) console.log(` · ${k} × ${n}`)
|
|
117
|
+
|
|
118
|
+
console.log(`
|
|
119
|
+
执行前提(**必须成对做,不能分两次**):
|
|
120
|
+
① 先改仓库(目录 + frontmatter name),跑门禁:node scripts/vertical-lint.mjs
|
|
121
|
+
+ python3 scripts/fix-skill-frontmatter.py --check + node scripts/validate-preset-schema.mjs --runtime
|
|
122
|
+
② **同一窗口内**改机器配置(gateway.json / roles.json)并重启服务验证角色挂载与会话唤醒
|
|
123
|
+
③ 业务线 manifest 的 legacyNaming 收敛(改完即删对应条目,保留就是长期债务)
|
|
124
|
+
工具刻意不提供 --apply:改名与重启窗口必须在一起,分两次做会出现"配置指旧名、仓库已新名"的窗口期。
|
|
125
|
+
`)
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* plugin-bootcheck — host 插件「启动冒烟」预检(v0.8 事故复盘 2026-09-05:
|
|
4
|
+
* 连续多次「改了 host 插件未验证即提交 → 用户重启才崩」。node --check 只查语法,
|
|
5
|
+
* 静态词法检查覆盖有限——本脚本用隔离 DSH_HOME + 临时端口真实启动一次 dsh web,
|
|
6
|
+
* 装配级错误(重复 provide / apply 路径未定义引用 / 模块初始化失败 / 语法)都会在
|
|
7
|
+
* 启动早期炸出;验证后立即杀进程,不影响运行中的 3080 实例。
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ 边界(诚实):只能覆盖「启动路径」执行的代码——事件回调内(如 @ 消息处理)
|
|
10
|
+
* 的函数体引用错误需另以运行期冒烟覆盖(发布清单含「重启后 @ 消息回归」项)。
|
|
11
|
+
*
|
|
12
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
13
|
+
* 2026-10-12 P1-0 重做(依据 `docs/dsh-0.2.x-适配审计.md` §B2/§D1 + 契约 §3.1/§3.3)
|
|
14
|
+
*
|
|
15
|
+
* ## 原来的假红
|
|
16
|
+
* 老实现把**真实 profile** 原样软链进隔离 home(`:41`),于是插件仍解析到**真实 profile
|
|
17
|
+
* 指的那个 scope**。在 0.2 宿主上那是 0.1.7 ⇒ 0.2 的 peer 门禁把**每一行** disable
|
|
18
|
+
* (实测 `disabling profile plugin row "…"` ×110 + `startup failed: …`)⇒ 与"Argent 坏了"
|
|
19
|
+
* 在输出上**无法区分**。而且它还顺手把 dsh 的组合产物 `cordis.yml` 写回**真实** profile。
|
|
20
|
+
*
|
|
21
|
+
* ## 现在
|
|
22
|
+
* ① 按**目标 scope**(`DSH_SCOPE`/`--scope-dir`;缺省 = 真源自动探测,见下)**重建**临时 profile 的
|
|
23
|
+
* `@deepseek-ai` 连接(浅镜像:node_modules 条目仍是只读软链,⛔ 不 `cp -R`、⛔ 不碰真实 profile);
|
|
24
|
+
* ② 重建后**自证**:`require.resolve('@deepseek-ai/dsh-base')` 必须落在目标 scope 内,
|
|
25
|
+
* 否则直接报「目标 scope 不可用」而不是继续跑出假红;
|
|
26
|
+
* ③ 判据从"三个哨兵任一命中"升级为**三条**(见 `scripts/lib/bootcheck-wiring.mjs`):
|
|
27
|
+
* 零条 `disabling profile plugin row` + **提供行判据表**命中 + `[preset-probe] DONE fail=0 total=N`;
|
|
28
|
+
* ④ 三态输出 `PASS` / `FAIL`(附原因与原文证据)/ `EVIDENCE-INSUFFICIENT`(拿不到日志 ⇒ ⛔ 不许判 PASS);
|
|
29
|
+
* ⑤ **硬前置**:被测 `dsh` 二进制**必须与目标 scope 同源**(版本一致)—— 解析顺序
|
|
30
|
+
* `DSH_BIN` → 目标 scope 推导 → `resolveDshBin({ scopeDir })` 兜底;不一致 ⇒ 当场判红(附双方版本 + 出路)。
|
|
31
|
+
*
|
|
32
|
+
* ## 目标 scope 怎么定(P1-3b 2026-10-12:**npx 感知** + 来源可打印)
|
|
33
|
+
* 唯一真源 = `scripts/lib/dsh-scope.mjs#resolveDshScopeDir()`(本文件⛔不自己实现候选序)。顺序:
|
|
34
|
+
* ① `--scope-dir` / `DSH_SCOPE`(显式)→ ② `--global-bin` / `DSH_BIN` 反推(显式指向哪个 dsh)→
|
|
35
|
+
* ③ 自动探测:`npm root -g` → PATH 上的 dsh → HOME 相对(邻居→标准)→ 当前 node 前缀 →
|
|
36
|
+
* **npx 缓存**(`~/.npm/_npx/<hash>/node_modules/@deepseek-ai`,= 官方首选运行方式
|
|
37
|
+
* `npx @deepseek-ai/dsh web` 留下的痕迹)→ homebrew / Intel 绝对兜底。
|
|
38
|
+
* ⛔ ③ 一个都不存在 ⇒ `null` ⇒ **响亮失败**(不是静默猜一个)。**本批修的缺陷**:老实现走遗留
|
|
39
|
+
* `resolveDshScope()`,它在"**纯 npx 机器**"上(PATH 无 dsh、无全局安装、只有 npx 缓存)
|
|
40
|
+
* **静默返回一个不存在的路径**(2026-10-12 用户 MBP 实测 `/usr/local/lib/node_modules/@deepseek-ai`,
|
|
41
|
+
* 存在=false)⇒ 连带 `resolveDshBin()` 定到不存在的 `/usr/local/bin/dsh` ⇒ 客户机上"选错 scope"。
|
|
42
|
+
* **scope 来源(`from`)每次都打印**(例:`npx 缓存(/Users/x/.npm/_npx/1e7f…/node_modules)`)——
|
|
43
|
+
* 让"选错了"在输出第一屏就可见,而不是等到 110 条禁用行。
|
|
44
|
+
*
|
|
45
|
+
* 用法:`node scripts/plugin-bootcheck.mjs [--profile web] [--scope-dir <…/@deepseek-ai>] [--print-scope]`
|
|
46
|
+
* 环境:`DSH_SCOPE`(目标 scope)/`DSH_BIN`(被测 dsh;缺省=**按目标 scope 推导**,再退 PATH/全局)/
|
|
47
|
+
* `BOOTCHECK_VERBOSE=1`(打印完整子进程日志)/`BOOTCHECK_ALLOW_VERSION_MISMATCH=1`(明知故犯地
|
|
48
|
+
* 跳过"bin 与 scope 必须同源"的硬前置,只告警)
|
|
49
|
+
* `--print-scope`(或 `BOOTCHECK_PRINT_SCOPE=1`):只跑**解析阶段**(零 spawn、零临时 home、
|
|
50
|
+
* 零真实 profile 读写)后 exit 0 —— 夹具与排障用("它到底选了哪个 scope、凭什么选的")。
|
|
51
|
+
* 退出码:0=PASS(或 `--print-scope` 解析成功) 1=FAIL / EVIDENCE-INSUFFICIENT / 硬前置不满足 / scope 解析不到
|
|
52
|
+
*
|
|
53
|
+
* ⚠️ **硬前置(2026-10-12 P1-0)**:被测二进制与目标 scope 的 dsh 版本必须一致 ——
|
|
54
|
+
* 否则隔离 profile 已重接线到目标 scope、宿主却是别的版本 ⇒ 0.2 的 peer 门禁把每行 disable
|
|
55
|
+
* (实测 110 条 `disabling profile plugin row`),输出与"Argent 坏了"无法区分。此时**当场判红**。
|
|
56
|
+
*/
|
|
57
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
58
|
+
import { mkdtempSync, mkdirSync, rmSync, symlinkSync, existsSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs'
|
|
59
|
+
import { createRequire } from 'node:module'
|
|
60
|
+
import { homedir, tmpdir } from 'node:os'
|
|
61
|
+
import { join } from 'node:path'
|
|
62
|
+
import { createServer } from 'node:net'
|
|
63
|
+
import { resolveDshBin, resolveBootcheckBin, resolveDshScopeDir, scopeBinCandidates } from './lib/dsh-scope.mjs'
|
|
64
|
+
import { MECHANISM, detectPresetMechanism, mechanismNote } from './lib/preset-mechanism.mjs'
|
|
65
|
+
import {
|
|
66
|
+
locateScopeFarm, planProfileWiring, planStaysInsideTmp, applyProfileWiring,
|
|
67
|
+
verifyScopeResolution, evaluateBootLog, readPackageVersion, compareBinScopeVersions,
|
|
68
|
+
SCOPE_PROBE_PKGS,
|
|
69
|
+
} from './lib/bootcheck-wiring.mjs'
|
|
70
|
+
|
|
71
|
+
/** 测哪个 profile(默认 web;隔离试跑用别的 profile 时传 --profile)。 */
|
|
72
|
+
const argVal = (n, d) => { const i = process.argv.indexOf(n); return i > -1 ? process.argv[i + 1] : d }
|
|
73
|
+
const PROFILE = argVal('--profile', 'web')
|
|
74
|
+
const PORT_PREF = Number(argVal('--port', process.env.BOOTCHECK_PORT || '3199'))
|
|
75
|
+
const KEEP_HOME = process.env.BOOTCHECK_KEEP_HOME === '1' || process.argv.includes('--keep-home')
|
|
76
|
+
|
|
77
|
+
/** 子进程句柄(异常退出时也要杀,否则会把固定端口占住 ⇒ 下一次假红 EADDRINUSE)。 */
|
|
78
|
+
let activeChild = null
|
|
79
|
+
process.on('exit', () => { try { activeChild?.kill('SIGKILL') } catch { /* 已退出 */ } })
|
|
80
|
+
for (const sig of ['SIGINT', 'SIGTERM']) process.on(sig, () => { try { activeChild?.kill('SIGKILL') } catch { /* 已退出 */ } process.exit(1) })
|
|
81
|
+
|
|
82
|
+
/** 选一个真正空闲的回环端口(固定端口被占时不再假红;2026-10-12 实测被上一次遗留进程占过)。 */
|
|
83
|
+
function pickFreePort(preferred) {
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
const probe = (p, left) => {
|
|
86
|
+
const s = createServer()
|
|
87
|
+
s.once('error', () => { s.close(); left > 0 ? probe(p + 1, left - 1) : resolve(0) })
|
|
88
|
+
s.once('listening', () => s.close(() => resolve(p)))
|
|
89
|
+
s.listen(p, '127.0.0.1')
|
|
90
|
+
}
|
|
91
|
+
probe(preferred, 20)
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2026-09-28 客户机教训:写死 /opt/homebrew/bin/dsh ⇒ 客户机(node 官方包布局)上
|
|
96
|
+
// 启动冒烟一律 ENOENT,把升级门禁的最后一道挡住。
|
|
97
|
+
//
|
|
98
|
+
// 2026-10-12 P1-0(**硬前置**):被测二进制必须与**目标 scope 同源**。解析顺序 =
|
|
99
|
+
// ① `DSH_BIN` / `--global-bin`(显式)
|
|
100
|
+
// ② **由目标 scope 推导**(`resolveBootcheckBin`:标准布局 `<prefix>/bin/dsh`/
|
|
101
|
+
// 邻居布局 `<dir>/bin/dsh`/**平铺布局 `<dir>/node_modules/.bin/dsh`**)
|
|
102
|
+
// ③ `resolveDshBin({ scopeDir })` 兜底(PATH → 目标 scope 候选 → 全局候选)
|
|
103
|
+
// 由来:老实现只走 ③,先 `command -v dsh` 命中本机全局 **0.1.7**,在平铺预检树上就出现
|
|
104
|
+
// 「profile 农场已重接线到 0.2、宿主仍是 0.1.7」⇒ 110 条 `disabling profile plugin row`。
|
|
105
|
+
// 版本不一致时**响亮判红**(判据见 `lib/bootcheck-wiring.mjs#compareBinScopeVersions`)。
|
|
106
|
+
// ⚠️ ② 的**平铺档**(`<dir>/node_modules/.bin/dsh`)在 **P1-3(2026-10-12)已上移到真源**
|
|
107
|
+
// `lib/dsh-scope.mjs#scopeBinCandidates()`(候选序:标准 → 邻居 → 平铺),本文件与
|
|
108
|
+
// `lib/bootcheck-wiring.mjs` 都**只转发**,⛔ 两处不再各自实现。防漂移判据:用例 C1/C7
|
|
109
|
+
// (bootcheck 侧与 `resolveDshBin()` **同解**)+ `scripts/tests/dsh-scope-resolve.test.mjs`(真源档位序)。
|
|
110
|
+
// ⚠️ ② 的**邻居档**对客户机(`~/.local/node/bin/dsh` → `~/.local/node/node_modules/@deepseek-ai`)有效。
|
|
111
|
+
// ⚠️ P1-3b(2026-10-12):③ 现在**带上 `scopeDir`** —— 不带参数时 `resolveDshBin()` 内部会退回遗留
|
|
112
|
+
// `resolveDshScope()`(会把"纯 npx 机器"带到一个不存在的路径),那是同一缺陷的第二个入口。
|
|
113
|
+
const explicitBin = argVal('--global-bin', null) || process.env.DSH_BIN || null
|
|
114
|
+
// ── 目标 scope 解析(P1-3b 2026-10-12:**npx 感知**;真源 = `lib/dsh-scope.mjs`)────────────
|
|
115
|
+
// 顺序(唯一实现在真源 `resolveDshScopeDir()`,本文件⛔不再自己拼候选):
|
|
116
|
+
// ① `--scope-dir` / `DSH_SCOPE`(显式) ② `--global-bin` / `DSH_BIN` 反推(显式指向哪个 dsh)
|
|
117
|
+
// ③ 自动探测:`npm root -g` → PATH 上的 dsh → HOME 相对(邻居→标准)→ 当前 node 前缀 →
|
|
118
|
+
// **npx 缓存**(`~/.npm/_npx/<hash>/node_modules/@deepseek-ai`)→ homebrew / Intel 绝对兜底。
|
|
119
|
+
// ⛔ ③ 一个都不存在 ⇒ `null` ⇒ 这里**响亮失败**(⛔ 不是静默猜一个)。**这就是本批修的缺陷**:
|
|
120
|
+
// 老实现走遗留 `resolveDshScope()`,它在"纯 npx 机器"上**静默返回一个不存在的路径**
|
|
121
|
+
// (用户 MBP 实测 `/usr/local/lib/node_modules/@deepseek-ai`,存在=false)。
|
|
122
|
+
// 被测 dsh 的 scope(**目标 scope**):它**参与**隔离 profile 的 `@deepseek-ai` 重建。
|
|
123
|
+
const scopeResolved = resolveDshScopeDir()
|
|
124
|
+
if (!scopeResolved) {
|
|
125
|
+
console.error('[FAIL] 目标 scope 解析失败:这台机器上找不到任何 @deepseek-ai 农场')
|
|
126
|
+
console.error(' 已按序试过:`--scope-dir`/`DSH_SCOPE` → `--global-bin`/`DSH_BIN` 反推 → `npm root -g` → ' +
|
|
127
|
+
'PATH 上的 dsh → HOME 相对布局(邻居→标准)→ 当前 node 前缀 → **npx 缓存(~/.npm/_npx/<hash>/…)** → homebrew / Intel 绝对路径。')
|
|
128
|
+
console.error(' ⛔ 旧实现(遗留 `resolveDshScope()`)会在这里**静默回一个不存在的路径**' +
|
|
129
|
+
'(实测 `/usr/local/lib/node_modules/@deepseek-ai`)—— 那正是本判据要消灭的误导性失败。')
|
|
130
|
+
console.error(' 出路:① `--scope-dir <…/@deepseek-ai>`;② `DSH_SCOPE=<…>`;③ `DSH_BIN=<该 scope 自带的 dsh>`(由它反推)。')
|
|
131
|
+
console.error(' 判据:该目录下应有 `@deepseek-ai/dsh`(npx 形态 = `<hash>/node_modules/@deepseek-ai`)。')
|
|
132
|
+
process.exit(1)
|
|
133
|
+
}
|
|
134
|
+
const scopeArg = scopeResolved.dir
|
|
135
|
+
const dshBinFrom = (() => {
|
|
136
|
+
if (explicitBin) return { bin: explicitBin, from: 'DSH_BIN / --global-bin(显式)' }
|
|
137
|
+
const derived = resolveBootcheckBin(scopeArg)
|
|
138
|
+
if (derived) return { bin: derived, from: '目标 scope 推导' }
|
|
139
|
+
// ⚠️ 兜底**必须**把已定下的目标 scope 传进去:不带参数的 `resolveDshBin()` 内部会退回遗留
|
|
140
|
+
// `resolveDshScope()`(同一缺陷入口)⇒ 又可能选出一个**不存在**的 bin。
|
|
141
|
+
return { bin: resolveDshBin({ scopeDir: scopeArg }), from: 'resolveDshBin({ scopeDir }) 兜底(PATH / 目标 scope 候选 / 全局)' }
|
|
142
|
+
})()
|
|
143
|
+
const dshBin = dshBinFrom.bin
|
|
144
|
+
// ⛔ 兜底也可能一个候选都不存在(纯 npx 机器:PATH 无 dsh,而目标 scope 里又没有 `.bin/dsh`)⇒ 响亮失败;
|
|
145
|
+
// ⛔ 绝不 spawn 一个不存在的路径(老实现会走到那儿,输出是 ENOENT / 看起来像"Argent 坏了")。
|
|
146
|
+
if (!existsSync(dshBin)) {
|
|
147
|
+
console.error(`[FAIL] 被测 dsh 可执行文件不存在:${dshBin}(来源:${dshBinFrom.from})`)
|
|
148
|
+
console.error(` 目标 scope=${scopeArg}(来源:${scopeResolved.from})`)
|
|
149
|
+
console.error(` 由该 scope 推导的 bin 候选:${scopeBinCandidates(scopeArg).join(' | ') || '(无)'}`)
|
|
150
|
+
console.error(' 出路:① `DSH_BIN=<目标 scope 自带的 dsh>`;② 把 `DSH_SCOPE`/`--scope-dir` 指向那个 dsh 所属的 scope。')
|
|
151
|
+
process.exit(1)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── `--print-scope`:只跑**解析阶段**就退出(零 spawn、零临时 home、⛔ 零真实 profile/clone 读写)──
|
|
155
|
+
// 用途:① 回归夹具 `scripts/tests/bootcheck-scope-resolution.test.mjs` 的"解析态"实跑;
|
|
156
|
+
// ② 排障一眼看清"它到底选了哪个 scope、**凭什么**选的、bin 是哪来的"。
|
|
157
|
+
if (process.argv.includes('--print-scope') || process.env.BOOTCHECK_PRINT_SCOPE === '1') {
|
|
158
|
+
console.log(`[bootcheck] 目标 scope=${scopeArg}`)
|
|
159
|
+
console.log(`[bootcheck] scope 来源=${scopeResolved.from}`)
|
|
160
|
+
console.log(`[bootcheck] 被测 dsh=${dshBin}`)
|
|
161
|
+
console.log(`[bootcheck] dsh 来源=${dshBinFrom.from}`)
|
|
162
|
+
console.log(`[bootcheck] bin 候选=${scopeBinCandidates(scopeArg).join(' | ') || '(无)'}`)
|
|
163
|
+
process.exit(0)
|
|
164
|
+
}
|
|
165
|
+
const realHome = process.env.REAL_DSH_HOME || join(homedir(), '.dsh')
|
|
166
|
+
|
|
167
|
+
/** 被测 dsh 的**自报版本**(`<bin> --version`,10s 超时)。拿不到 ⇒ `{version:null, why}`,⛔ 不静默。 */
|
|
168
|
+
function probeBinVersion(bin) {
|
|
169
|
+
try {
|
|
170
|
+
const r = spawnSync(bin, ['--version'], { encoding: 'utf8', timeout: 10000 })
|
|
171
|
+
if (r.error) return { version: null, why: `无法执行 ${bin}:${r.error.message}` }
|
|
172
|
+
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`
|
|
173
|
+
if (r.status !== 0 && !/\d+\.\d+\.\d+/.test(out)) return { version: null, why: `\`--version\` exit ${r.status}` }
|
|
174
|
+
const m = /(\d+\.\d+\.\d+[\w.+-]*)/.exec(out)
|
|
175
|
+
return m ? { version: m[1], why: null } : { version: null, why: `\`--version\` 输出里认不出语义版本:${out.trim().slice(0, 120)}` }
|
|
176
|
+
} catch (e) {
|
|
177
|
+
return { version: null, why: `无法执行 ${bin}:${e.message}` }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** 目标 scope 不可用时的统一出口(三态里的 EVIDENCE-INSUFFICIENT:连日志都拿不到)。 */
|
|
182
|
+
function bailScopeUnavailable(why) {
|
|
183
|
+
console.error(`[EVIDENCE-INSUFFICIENT] ${why}`)
|
|
184
|
+
console.error(` 目标 scope(${scopeArg};来源:${scopeResolved.from}):请确认它是 ` +
|
|
185
|
+
`<dir>/node_modules/@deepseek-ai(平铺 / npx 形态)或含 ` +
|
|
186
|
+
`${SCOPE_PROBE_PKGS.map((p) => `@deepseek-ai/${p}`).join(' / ')} 的农场目录;` +
|
|
187
|
+
'也可用 --scope-dir / DSH_SCOPE 显式指定(`--print-scope` 可先看清解析结果与来源)。')
|
|
188
|
+
process.exit(1)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const farm = locateScopeFarm(scopeArg)
|
|
192
|
+
if (!farm) bailScopeUnavailable(`目标 scope 不可用:${scopeArg} 下找不到 @deepseek-ai 农场`)
|
|
193
|
+
const mechanism = detectPresetMechanism(farm.farm)
|
|
194
|
+
/** 只读小工具(给 L1 纯函数注入 fs 用;本文件是 L2 装配档,允许直接读 fs)。 */
|
|
195
|
+
const roFs = {
|
|
196
|
+
join,
|
|
197
|
+
readText: (p) => { try { return readFileSync(p, 'utf8') } catch { return null } },
|
|
198
|
+
realpath: (p) => { try { return realpathSync(p) } catch { return null } },
|
|
199
|
+
}
|
|
200
|
+
const targetVersion = readPackageVersion(join(farm.farm, 'dsh'), roFs)
|
|
201
|
+
|
|
202
|
+
// ── 硬前置:被测 dsh 二进制必须与目标 scope **同源**(2026-10-12 P1-0)─────────
|
|
203
|
+
// 判据在 L1(`lib/bootcheck-wiring.mjs#compareBinScopeVersions`,纯函数、有回归);
|
|
204
|
+
// 这里只负责取数(`--version`)+ 打印 + 按判据出口。**两侧版本不一致 ⇒ 判红**,
|
|
205
|
+
// 而不是继续跑出一个"看起来像 Argent 坏了"的红(老实现就是这个坑,实测 110 条禁用行)。
|
|
206
|
+
const binVersionInfo = probeBinVersion(dshBin)
|
|
207
|
+
const binCheck = compareBinScopeVersions({
|
|
208
|
+
binVersion: binVersionInfo.version,
|
|
209
|
+
scopeVersion: targetVersion,
|
|
210
|
+
binPath: dshBin,
|
|
211
|
+
scopeDir: farm.farm,
|
|
212
|
+
allowMismatch: process.env.BOOTCHECK_ALLOW_VERSION_MISMATCH === '1',
|
|
213
|
+
})
|
|
214
|
+
console.log(`[bootcheck] 被测 dsh=${dshBin}(自报 ${binVersionInfo.version ?? `拿不到:${binVersionInfo.why}`};来源:${dshBinFrom.from})`)
|
|
215
|
+
console.log(`[bootcheck] bin↔scope 同源:${binCheck.level === 'ok' ? '✅' : binCheck.level === 'warn' ? '⚠️' : '❌'} ${binCheck.why}`)
|
|
216
|
+
if (binCheck.level === 'fail') {
|
|
217
|
+
console.error('[FAIL] 硬前置不满足:被测 dsh 二进制与目标 scope **不同源**(详见上一行)')
|
|
218
|
+
console.error(' 这不是目标 scope 的问题 —— 换一个与它同源的 dsh 可执行文件即可(实测:`DSH_BIN=<目标 scope 自带的 dsh>` ⇒ 全绿)。')
|
|
219
|
+
console.error(' (确要明知故犯地跑下去:BOOTCHECK_ALLOW_VERSION_MISMATCH=1 ⇒ 降级为告警。)')
|
|
220
|
+
process.exit(1)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── preset 挂载探针(2026-09-10 新增)─────────────────────────────────────
|
|
224
|
+
// 由来:0.1.5 起 preset 挂载失败会让 **session resume 直接失败**,而原 bootcheck 只验
|
|
225
|
+
// host 插件装配、完全不覆盖 preset 挂载(persona `prefix` 缺键事故即由此漏过)。
|
|
226
|
+
// 做法:把真实 .agent-presets 链接进隔离 home,并用 --patch 覆盖注入一个探针行,
|
|
227
|
+
// 探针对每个 argent-* preset 建一个 agent 作用域并**真实 mount**,逐个报 OK/FAIL。
|
|
228
|
+
// 角色清单**按被测 dsh 的机制**取(2026-09-28:0.1.7 起 preset 不再扫目录):
|
|
229
|
+
// rows → 从 profile 里装的 argent-presets 插件的 presets.json 取(= 真实声明处)
|
|
230
|
+
// directory → 扫 ~/.dsh/.agent-presets/argent-*(0.1.5 及更早)
|
|
231
|
+
const tmpHome = mkdtempSync(join(tmpdir(), 'dsh-bootcheck-'))
|
|
232
|
+
mkdirSync(join(tmpHome, 'profiles'), { recursive: true })
|
|
233
|
+
let presetIds = []
|
|
234
|
+
let idsFrom = '(无)'
|
|
235
|
+
try {
|
|
236
|
+
if (mechanism === MECHANISM.ROWS) {
|
|
237
|
+
// ⚠️ W9(2026-10-12):profile 里 bundle 的落点是**包名**(`@whyshu/argent-presets`),
|
|
238
|
+
// 存量机器上仍是旧裸名目录 ⇒ **两个名字都试**(⛔ 只认一个会在另一半机器上判成「没装」)。
|
|
239
|
+
// 真源 = `scripts/lib/preset-install.mjs#resolveBundleDir()`;bootcheck 刻意**零第三方/零宿主**
|
|
240
|
+
// 自持(要在半坏的机器上跑)⇒ 这里只做两名字候选,判据与真源一致(scoped 优先)。
|
|
241
|
+
const inProfileCands = [
|
|
242
|
+
join(realHome, 'profiles', PROFILE, 'node_modules', '@whyshu', 'argent-presets', 'presets.json'),
|
|
243
|
+
join(realHome, 'profiles', PROFILE, 'node_modules', 'argent-presets', 'presets.json'),
|
|
244
|
+
]
|
|
245
|
+
const inProfile = inProfileCands.find((f) => existsSync(f)) || ''
|
|
246
|
+
const inRepo = join(process.cwd(), 'plugins', 'argent-presets', 'presets.json')
|
|
247
|
+
const use = inProfile || (existsSync(inRepo) ? inRepo : null)
|
|
248
|
+
if (use) {
|
|
249
|
+
presetIds = Object.keys(JSON.parse(readFileSync(use, 'utf8')))
|
|
250
|
+
idsFrom = `行式清单(${use.includes('node_modules') ? 'profile 内' : '仓库内'},${presetIds.length} 个)`
|
|
251
|
+
}
|
|
252
|
+
} else if (existsSync(join(realHome, '.agent-presets'))) {
|
|
253
|
+
symlinkSync(join(realHome, '.agent-presets'), join(tmpHome, '.agent-presets'), 'dir')
|
|
254
|
+
presetIds = readdirSync(join(realHome, '.agent-presets')).filter((d) => {
|
|
255
|
+
try { return d.startsWith('argent-') && statSync(join(realHome, '.agent-presets', d)).isDirectory() } catch { return false }
|
|
256
|
+
})
|
|
257
|
+
idsFrom = `目录式 ~/.dsh/.agent-presets(${presetIds.length} 个)`
|
|
258
|
+
}
|
|
259
|
+
} catch (e) {
|
|
260
|
+
console.error('[bootcheck] 取角色清单失败:', e.message)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ── 隔离 profile:浅镜像 + 按目标 scope 重建 @deepseek-ai ────────────────────
|
|
264
|
+
const profileDir = join(realHome, 'profiles', PROFILE)
|
|
265
|
+
if (!existsSync(profileDir)) bailScopeUnavailable(`真实 profile 不存在:${profileDir}`)
|
|
266
|
+
const tmpProfileDir = join(tmpHome, 'profiles', PROFILE)
|
|
267
|
+
const plan = planProfileWiring({ profileDir, tmpProfileDir, targetFarm: farm.farm })
|
|
268
|
+
const guard = planStaysInsideTmp(plan, { profileDir, tmpProfileDir })
|
|
269
|
+
if (!guard.ok) {
|
|
270
|
+
console.error('[FAIL] 隔离装配计划越界(会写真实 profile)—— 拒绝执行:')
|
|
271
|
+
for (const v of guard.violations) console.error(' · ' + v)
|
|
272
|
+
process.exit(1)
|
|
273
|
+
}
|
|
274
|
+
applyProfileWiring(plan)
|
|
275
|
+
|
|
276
|
+
// 自证:从临时 profile 解析探针包,必须落到**目标 scope**内(解析不到 ⇒ 目标 scope 不可用)。
|
|
277
|
+
const req = createRequire(join(tmpProfileDir, 'package.json'))
|
|
278
|
+
const resolveFrom = (spec) => {
|
|
279
|
+
for (const s of [spec, `${spec}/package.json`]) {
|
|
280
|
+
try { return req.resolve(s) } catch { /* 试下一个 */ }
|
|
281
|
+
}
|
|
282
|
+
return null
|
|
283
|
+
}
|
|
284
|
+
const scopeCheck = verifyScopeResolution({ tmpProfileDir, targetFarm: farm.farm, resolveFrom }, roFs)
|
|
285
|
+
if (!scopeCheck.ok) bailScopeUnavailable(scopeCheck.why)
|
|
286
|
+
/** 目标 scope 供的版本(= 重建后临时 profile 会解析到的版本)——用于禁用行归因。 */
|
|
287
|
+
const targetVersionOf = (pkg) => {
|
|
288
|
+
if (!pkg || !pkg.startsWith('@deepseek-ai/')) return null
|
|
289
|
+
return readPackageVersion(join(farm.farm, pkg.slice('@deepseek-ai/'.length)), roFs)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log(`[bootcheck] profile=${PROFILE}|机制=${mechanismNote(mechanism)}|清单=${idsFrom}`)
|
|
293
|
+
console.log(`[bootcheck] 目标 scope=${farm.farm}(来源:${scopeResolved.from}|layout=${farm.layout},探针=${farm.probe},dsh=${targetVersion ?? '?'})`)
|
|
294
|
+
console.log(`[bootcheck] 隔离装配=${plan.mode}/${plan.scopeMode}(${plan.actions.length} 条动作,profile 浅镜像;` +
|
|
295
|
+
`${plan.sameFarm ? '目标 scope 与现役一致 ⇒ 不重接线' : '已重接线到目标 scope'})|自证:${scopeCheck.why}`)
|
|
296
|
+
for (const n of plan.notes) console.log(`[bootcheck] ⚠️ ${n}`)
|
|
297
|
+
|
|
298
|
+
const probeFile = join(tmpHome, 'preset-mount-probe.mjs')
|
|
299
|
+
const probePatch = join(tmpHome, 'preset-probe.patch.yml')
|
|
300
|
+
writeFileSync(probeFile, `export const name = 'preset-mount-probe'
|
|
301
|
+
export const inject = ['agentPresets', 'agents']
|
|
302
|
+
export async function apply(ctx) {
|
|
303
|
+
const ids = (process.env.DSH_PRESET_PROBE_IDS || '').split(',').filter(Boolean)
|
|
304
|
+
// ⚠️ 必须**等注册表就绪**再挂载(2026-09-28 实测):0.1.5 目录式启动即注册,
|
|
305
|
+
// 0.1.7 行式要到装配期才注册(本机实测 +0ms 为 0 个、+3s 才 17 个)⇒
|
|
306
|
+
// 立刻 mount 会一律 "Unknown agent preset",把"探针太早"误报成"preset 挂了"。
|
|
307
|
+
let known = new Set()
|
|
308
|
+
let broken = new Map()
|
|
309
|
+
const deadline = Date.now() + 25000
|
|
310
|
+
while (Date.now() < deadline) {
|
|
311
|
+
try {
|
|
312
|
+
const list = await ctx.agentPresets.list()
|
|
313
|
+
known = new Set(list.map((p) => p.id))
|
|
314
|
+
broken = new Map(list.filter((p) => p.broken).map((p) => [p.id, p.broken]))
|
|
315
|
+
} catch { /* 注册表还没起来 */ }
|
|
316
|
+
if (ids.every((id) => known.has(id))) break
|
|
317
|
+
await new Promise((r) => setTimeout(r, 500))
|
|
318
|
+
}
|
|
319
|
+
const unknown = ids.filter((id) => !known.has(id))
|
|
320
|
+
if (unknown.length) console.log('[preset-probe] 等注册表超时,仍未声明:' + unknown.join(', '))
|
|
321
|
+
let fail = 0
|
|
322
|
+
for (const id of ids) {
|
|
323
|
+
// 注册表已标记损坏时,直接报出**原始诊断**(比 mount 的泛化报错有用得多)
|
|
324
|
+
if (broken.has(id)) {
|
|
325
|
+
fail++
|
|
326
|
+
const b = broken.get(id)
|
|
327
|
+
console.log('[preset-probe] FAIL ' + id + '(注册表诊断): ' + (typeof b === 'string' ? b : JSON.stringify(b)))
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
await ctx.agents.create({
|
|
332
|
+
sessionId: 'bootcheck-preset-' + id,
|
|
333
|
+
meta: { cwd: process.env.HOME || '/tmp' },
|
|
334
|
+
setup: async (agentCtx) => { await ctx.agentPresets.mount(agentCtx, id) },
|
|
335
|
+
})
|
|
336
|
+
console.log('[preset-probe] OK ' + id)
|
|
337
|
+
} catch (e) {
|
|
338
|
+
fail++
|
|
339
|
+
console.log('[preset-probe] FAIL ' + id + ': ' + ((e && e.message) || e))
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
console.log('[preset-probe] DONE fail=' + fail + ' total=' + ids.length)
|
|
343
|
+
}
|
|
344
|
+
`)
|
|
345
|
+
writeFileSync(probePatch, `- insert:\n - id: preset-mount-probe\n name: '${probeFile}'\n`)
|
|
346
|
+
|
|
347
|
+
function waitForReady(url, timeoutMs, isDead) {
|
|
348
|
+
return new Promise((resolve) => {
|
|
349
|
+
const start = Date.now()
|
|
350
|
+
const tryOnce = async () => {
|
|
351
|
+
if (isDead()) return resolve(false) // 子进程已退出/崩溃 → 立即判失败
|
|
352
|
+
try {
|
|
353
|
+
const r = await fetch(url, { signal: AbortSignal.timeout(2000), redirect: 'manual' })
|
|
354
|
+
// 版本中立就绪判据:0.1.1 根路径 200;**0.1.5+ 根路径需 token(401)或 303 跳转**。
|
|
355
|
+
// 三者都说明 HTTP 服务已就绪;真正的成功判据是「判据表 + preset 探针 + 无装配级错误特征」。
|
|
356
|
+
if (r.status >= 200 && r.status < 400) return resolve(true)
|
|
357
|
+
if (r.status === 401 || r.status === 403) return resolve(true)
|
|
358
|
+
} catch {
|
|
359
|
+
/* 未就绪 */
|
|
360
|
+
}
|
|
361
|
+
if (Date.now() - start > timeoutMs) return resolve(false)
|
|
362
|
+
setTimeout(tryOnce, 500)
|
|
363
|
+
}
|
|
364
|
+
tryOnce()
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** 三条判据 + 三态的**统一渲染**(PASS/FAIL/EVIDENCE-INSUFFICIENT 都用它,避免判据在输出里漂移)。 */
|
|
369
|
+
function renderCriteria(ev, { expectedPresets }) {
|
|
370
|
+
const mark = (b) => (b ? '✅' : '❌')
|
|
371
|
+
console.log(`[criteria] ① 零条 profile 插件禁用行(peer 门禁):${mark(ev.criteria.zeroDisable)}(${ev.disable.total} 条)` +
|
|
372
|
+
` ② 提供行(判据表):${mark(ev.criteria.providerLine)}[命中 ${ev.providers.filter((p) => p.hit).map((p) => p.id).join(', ') || '无'}]` +
|
|
373
|
+
(expectedPresets > 0 ? ` ③ preset 探针:${mark(ev.criteria.presetProbe)}[fail=${ev.probe.fail ?? '?'} total=${ev.probe.total ?? '?'}/期望 ${expectedPresets}]` : ' ③ preset 探针:n/a'))
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function main() {
|
|
377
|
+
const PORT = await pickFreePort(PORT_PREF)
|
|
378
|
+
if (!PORT) { console.error('[EVIDENCE-INSUFFICIENT] 找不到空闲回环端口(3199 起试 20 个)⇒ 拿不到日志'); process.exit(1) }
|
|
379
|
+
if (PORT !== PORT_PREF) console.log(`[bootcheck] ⚠️ 端口 ${PORT_PREF} 被占用 ⇒ 改用 ${PORT}`)
|
|
380
|
+
// 注意:--patch 是**顶层**选项,必须在 web app 自己的参数(--port/--no-open)之前
|
|
381
|
+
const args = presetIds.length
|
|
382
|
+
? ['--profile', PROFILE, '--patch', probePatch, '--port', String(PORT), '--no-open']
|
|
383
|
+
: ['--profile', PROFILE, '--port', String(PORT), '--no-open']
|
|
384
|
+
const child = spawn(dshBin, args, {
|
|
385
|
+
env: { ...process.env, DSH_HOME: tmpHome, DSH_PRESET_PROBE_IDS: presetIds.join(',') },
|
|
386
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
387
|
+
})
|
|
388
|
+
activeChild = child
|
|
389
|
+
let log = ''
|
|
390
|
+
child.stdout.on('data', (d) => (log += d))
|
|
391
|
+
child.stderr.on('data', (d) => (log += d))
|
|
392
|
+
let exitCode = null
|
|
393
|
+
child.on('exit', (c) => { exitCode = c; log += `\n[child exited code=${c}]` })
|
|
394
|
+
|
|
395
|
+
const httpReady = await waitForReady(`http://127.0.0.1:${PORT}/`, 45000, () => exitCode !== null)
|
|
396
|
+
// ⚠️ 必须**等**判据面出现再判(2026-09-28):0.1.7 上 HTTP 就绪早于插件 apply 收尾,
|
|
397
|
+
// 采样一次会误报"缺少提供行/疑似静默 reject"。等待窗口与 probe 一致,不改变判据语义。
|
|
398
|
+
if (presetIds.length) {
|
|
399
|
+
const deadline = Date.now() + 30000
|
|
400
|
+
while (Date.now() < deadline && !/\[preset-probe\] DONE /.test(log)) await new Promise((r) => setTimeout(r, 300))
|
|
401
|
+
} else {
|
|
402
|
+
const deadline = Date.now() + 20000
|
|
403
|
+
while (Date.now() < deadline && !/\[argent-core\]\s*已启动/.test(log) && exitCode === null) await new Promise((r) => setTimeout(r, 300))
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const ev = evaluateBootLog(log, { expectedPresets: presetIds.length, httpReady, exitCode, resolveVersion: targetVersionOf })
|
|
407
|
+
|
|
408
|
+
// 证据(FAIL / EVIDENCE-INSUFFICIENT 时**逐条**给原文;PASS 时只给摘要)
|
|
409
|
+
if (ev.verdict !== 'PASS') {
|
|
410
|
+
renderCriteria(ev, { expectedPresets: presetIds.length })
|
|
411
|
+
console.error(`[${ev.verdict}] 启动冒烟未通过(隔离 home: ${tmpHome})`)
|
|
412
|
+
for (const r of ev.reasons) console.error(' · ' + r)
|
|
413
|
+
if (ev.disable.total) {
|
|
414
|
+
const all = [...ev.disable.mismatched, ...ev.disable.consistent, ...ev.disable.unresolved]
|
|
415
|
+
console.error(`[evidence] 原文匹配串 \`disabling profile plugin row\` 共 ${all.length} 条,逐条如下:`)
|
|
416
|
+
for (const r of all) console.error(' - ' + r.line)
|
|
417
|
+
console.error(`[evidence] 归因:${ev.disable.summary}`)
|
|
418
|
+
if (ev.disable.kind === 'scope-not-wired') {
|
|
419
|
+
console.error('[evidence] ⚠️ 这是**目标 scope 未接好**(不是 Argent 坏了):禁用行读到的版本与临时 profile 接的 scope 不一致。')
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (ev.probe.fails.length) {
|
|
423
|
+
console.error(`[evidence] preset 探针失败 ${ev.probe.fails.length} 条:`)
|
|
424
|
+
for (const f of ev.probe.fails.slice(0, 10)) console.error(' - ' + f)
|
|
425
|
+
}
|
|
426
|
+
console.error('[evidence] 提供行判据表:')
|
|
427
|
+
for (const p of ev.providers) console.error(` ${p.hit ? '✅' : '❌'} ${p.id}${p.required ? '(必需)' : ''} 期望样例:${p.sample}`)
|
|
428
|
+
const all = log.split('\n')
|
|
429
|
+
const tail = process.env.BOOTCHECK_VERBOSE === '1' ? all.join('\n') : all.slice(-25).join('\n')
|
|
430
|
+
console.error('--- 子进程日志' + (process.env.BOOTCHECK_VERBOSE === '1' ? '(完整)' : '尾部 25 行,BOOTCHECK_VERBOSE=1 看全部') + ' ---')
|
|
431
|
+
console.error(tail || '(无输出)')
|
|
432
|
+
} else {
|
|
433
|
+
renderCriteria(ev, { expectedPresets: presetIds.length })
|
|
434
|
+
console.log(`[PASS] 启动冒烟通过(隔离 home;目标 scope dsh=${targetVersion ?? '?'};` +
|
|
435
|
+
`${presetIds.length ? `preset 挂载 ${presetIds.length}/${presetIds.length}` : '未发现 argent-* preset'};` +
|
|
436
|
+
`零条禁用行、提供行 ${ev.providers.filter((p) => p.hit).map((p) => p.id).join('+')})`)
|
|
437
|
+
console.log('--- 插件日志 ---')
|
|
438
|
+
console.log(log.split('\n').filter((l) => /argent-|preset-probe|fatal/.test(l)).slice(-8).join('\n'))
|
|
439
|
+
if (process.env.BOOTCHECK_VERBOSE === '1') { console.log('--- 子进程完整日志 ---'); console.log(log) }
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
child.kill('SIGTERM')
|
|
443
|
+
setTimeout(() => child.kill('SIGKILL'), 3000).unref()
|
|
444
|
+
if (ev.verdict === 'PASS' && !KEEP_HOME) { try { rmSync(tmpHome, { recursive: true, force: true }) } catch { /* 留着也无害 */ } }
|
|
445
|
+
process.exit(ev.verdict === 'PASS' ? 0 : 1)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
main().catch((e) => {
|
|
449
|
+
console.error('[FAIL] bootcheck 异常:', e.message, '\n', e.stack)
|
|
450
|
+
process.exit(1)
|
|
451
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_doc": "profile 组合目标(唯一真源):patch 层行 vs bundle 层插件。JS 升级器与 Python 客户机预检都读这里 —— 2026-09-28 踩过:预检期望 argent-flow 挂载、升级目标组合里却没有它,于是预检红、升级器却以为正常(两边知识分叉)。",
|
|
3
|
+
"_history": [
|
|
4
|
+
"2026-09-28:把 argent-flow 补进 business。它提供 flow_schema_check / flow_failures 两个工具,是流程角色(判失败/查字段缺口)的生产依赖,客户机必须有。",
|
|
5
|
+
"2026-09-28:argent-skin 移到 bundle 层(dsh 0.1.5 起客户端 UI 插件走 dsh.profile.bundles;patch 层再来一行会 duplicate loader entry id ⇒ 启动即失败)。"
|
|
6
|
+
],
|
|
7
|
+
"business": {
|
|
8
|
+
"patch": ["argent-core", "argent-flow", "argent-gateway"],
|
|
9
|
+
"bundles": ["argent-skin"]
|
|
10
|
+
},
|
|
11
|
+
"chat": {
|
|
12
|
+
"patch": ["argent-core"],
|
|
13
|
+
"bundles": ["argent-skin"]
|
|
14
|
+
}
|
|
15
|
+
}
|