@whyshu/argent-engine 0.0.0-stage → 0.6.1
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 +341 -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 +576 -0
- package/engine/scripts/billing-mode-daily.sh +13 -0
- package/engine/scripts/build-engine-package.mjs +126 -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 +378 -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/collect-evidence.mjs +42 -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 +135 -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 +47 -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 +261 -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/final-check.mjs +43 -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 +735 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +87 -0
- package/engine/scripts/flow-timers.sh +184 -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 +1025 -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 +2278 -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 +195 -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.sh +18 -0
- package/engine/scripts/lib/argent_datadir.py +285 -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 +295 -0
- package/engine/scripts/poll-invoice.sh +76 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +71 -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/retire-inventory.mjs +40 -0
- package/engine/scripts/run-tests.sh +257 -0
- package/engine/scripts/s1-audit.mjs +85 -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 +137 -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 +74 -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 +35 -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 +217 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +121 -0
- package/engine/scripts/ziniao-windows.sh +154 -0
- package/engine/skills/README.md +86 -0
- package/engine/skills/argent-dev/SKILL.md +41 -0
- package/engine/skills/internal.json +37 -0
- package/engine/skills/lktop-pm/SKILL.md +35 -0
- package/engine/skills/lktopapp-dev/SKILL.md +34 -0
- package/engine/skills/ml-config-guide/SKILL.md +50 -0
- package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
- package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
- package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
- package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
- package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
- package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
- package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
- package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
- package/engine/skills/ml-invoice/SKILL.md +506 -0
- package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
- package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
- package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
- package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
- package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
- package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
- package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
- package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
- package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
- package/engine/skills/pet-dev/SKILL.md +42 -0
- package/engine/skills/whyshu-pm/SKILL.md +35 -0
- package/engine/skills/whyshu-web-dev/SKILL.md +50 -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 +738 -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 +22 -4
- package/requirements.txt +5 -0
- package/README.md +0 -3
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* argent-agents —— 「按**智能体**装」的声明解析与清单构造(A 批,2026-10-10)。
|
|
4
|
+
*
|
|
5
|
+
* ## 为什么单独一个文件
|
|
6
|
+
* 与 `scripts/argent-capabilities.mjs` 同因:`install-argent.mjs` 是**单文件分发物**
|
|
7
|
+
* (`release-argent.sh` 的 CORE_FILES),只能在克隆完仓库之后**动态**加载本模块,
|
|
8
|
+
* 不能静态 import(否则单文件分发断链)。而门禁 `scripts/check-agent-catalog.mjs`
|
|
9
|
+
* 要读**同一份**目录声明并做更强的交叉校验 ⇒ 解析/校验/清单构造必须是**一份实现**。
|
|
10
|
+
*
|
|
11
|
+
* ## 分工
|
|
12
|
+
* - **声明**:`agents/catalog.json`(L0 货架声明:agent → capability + flows 子集 + 角色 + 插件/技能/集成/凭据槽)。
|
|
13
|
+
* - **本模块**:纯函数(`validateCatalog` / `planForAgents` / `presetSelectionForAgents` / `composeReceipt`)
|
|
14
|
+
* + 轻 IO(`loadCatalog` / `buildContext` / 账本读写)。
|
|
15
|
+
* - **落盘**:`install-argent.mjs`(`--agent` / `--list-agents` / `--capability remove`)。
|
|
16
|
+
*
|
|
17
|
+
* ## 三条红线
|
|
18
|
+
* 1. **⛔ 凭据槽不得含值**:`credentials[]` 只写 `{slot,target,why}`;`validateCatalog` 递归扫描
|
|
19
|
+
* 禁止键(value/secret/default/token/password…)与密钥形态串 ⇒ 报红。
|
|
20
|
+
* 2. **⛔ 不含判据逻辑**:本模块只做「声明 → 清单」的机械投影,业务判据仍在 L1(scripts/flow_*.py)。
|
|
21
|
+
* 3. **⛔ 单一真源**:能力/流程/档位一律读**既有**声明(`scripts/capability-packs.lock.json` +
|
|
22
|
+
* `scripts/flow-requirements.json` + `plugins/argent-presets/presets.json`),本模块与目录
|
|
23
|
+
* **都不新建第二份**;门禁侧再拿**活注册表**(`flow_registry.py --json`)核对锁是否过期。
|
|
24
|
+
*
|
|
25
|
+
* 测试:`scripts/tests/agent-catalog.test.mjs`
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
28
|
+
import { dirname, join } from 'node:path'
|
|
29
|
+
import { fileURLToPath } from 'node:url'
|
|
30
|
+
|
|
31
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
32
|
+
export const REPO = join(HERE, '..')
|
|
33
|
+
|
|
34
|
+
/** 目录声明(L0)的相对路径 —— **只此一处**;改名要同时改本模块、门禁与安装器。 */
|
|
35
|
+
export const CATALOG_REL = 'agents/catalog.json'
|
|
36
|
+
/** 装机账本(智能体维度):`<dshHome>/argent/agents.json`。 */
|
|
37
|
+
export const LEDGER_REL = join('argent', 'agents.json')
|
|
38
|
+
|
|
39
|
+
/** 共享壳插件:**任何**智能体都必须带它们,且移除时**永不**删(其它智能体还要用)。 */
|
|
40
|
+
export const SHARED_SHELL_PLUGINS = ['argent-core', 'argent-skin']
|
|
41
|
+
/** 恒装入口角色(每个智能体的 `roles` 必须含它)。 */
|
|
42
|
+
export const ALWAYS_ROLE = 'argent-general'
|
|
43
|
+
|
|
44
|
+
/** 档位序(判据:智能体档位不得低于其能力包的档位下限)。 */
|
|
45
|
+
export const PLAN_RANK = { free: 0, pro: 1, enterprise: 2 }
|
|
46
|
+
|
|
47
|
+
/** `credentials[]` 的**唯一**合法键集(多一个键即红)。 */
|
|
48
|
+
export const CREDENTIAL_KEYS = ['slot', 'target', 'why']
|
|
49
|
+
/** 凭据槽里**禁止出现**的键(写进去就等于把密钥带进仓库/收据)。 */
|
|
50
|
+
export const FORBIDDEN_CRED_KEYS = ['value', 'values', 'secret', 'secrets', 'default', 'token', 'password', 'pass', 'key', 'apikey', 'apiKey']
|
|
51
|
+
/** 密钥形态串(扫描凭据槽的**所有**字符串值;命中即红)。 */
|
|
52
|
+
export const SECRET_SHAPED = [
|
|
53
|
+
/sk-[A-Za-z0-9]{16,}/, // DeepSeek 官方形态
|
|
54
|
+
/\beyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}/, // JWT
|
|
55
|
+
/\b[0-9a-f]{32,}\b/i, // 长十六进制
|
|
56
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
57
|
+
]
|
|
58
|
+
/** 智能体条目**允许**的键(未知键 ⇒ 红)。 */
|
|
59
|
+
export const AGENT_KEYS = [
|
|
60
|
+
'id', 'title', 'summary', 'capability', 'capabilities', 'flows', 'roles',
|
|
61
|
+
'plugins', 'skills', 'integrations', 'credentials', 'minPlan', 'scheduler', 'notes',
|
|
62
|
+
]
|
|
63
|
+
/** 目录文档**允许**的顶层键。 */
|
|
64
|
+
export const DOC_KEYS = ['_doc', '_layer', '_why', '_invariants', 'version', 'plans', 'plugins', 'integrations', 'agents']
|
|
65
|
+
|
|
66
|
+
// ── 能力声明的**宿主资产**字段(`capabilities.<c>.pack.hostAssets`)──────────────
|
|
67
|
+
// 由来(A 批):能力包此前**没有任何宿主侧资产的声明位** —— "要不要定时器 / 怎么装"只能由安装器
|
|
68
|
+
// **写死**推断(写死的判据散在安装器文案里,没有单一真源可查)。这里把它变成**声明 + 门禁**。
|
|
69
|
+
/** `hostAssets` 允许的资产键。 */
|
|
70
|
+
export const HOST_ASSET_KEYS = ['_note', 'scheduler', 'loginAutostart', 'desktopShortcut']
|
|
71
|
+
/** 每项资产允许的子键(`_note` 之外的键必须落在各自的白名单里 —— 未知子键 ⇒ 红)。 */
|
|
72
|
+
export const HOST_ASSET_FIELDS = {
|
|
73
|
+
scheduler: ['required', 'install', 'perFlow', 'command', '_note'],
|
|
74
|
+
loginAutostart: ['required', 'install', 'flag', '_note'],
|
|
75
|
+
desktopShortcut: ['required', 'install', '_note'],
|
|
76
|
+
}
|
|
77
|
+
/** `install` 的合法取值(**现状只有这三种**;新增必须同时改门禁与安装器文案)。 */
|
|
78
|
+
export const HOST_ASSET_INSTALL = ['print-only', 'auto', 'none', 'opt-in-flag']
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 校验能力声明的宿主资产字段(⛔ 与智能体目录的 `scheduler` 必须一致 —— 那是本批"不装定时器"的判据源)。
|
|
82
|
+
* @param {object} ctx `buildContext()` 输出
|
|
83
|
+
* @returns {{errors: string[], warnings: string[]}}
|
|
84
|
+
*/
|
|
85
|
+
export function validateHostAssets(ctx) {
|
|
86
|
+
const errors = []
|
|
87
|
+
const warnings = []
|
|
88
|
+
for (const [cap, c] of Object.entries(ctx.capabilities || {})) {
|
|
89
|
+
const ha = c.hostAssets
|
|
90
|
+
const at = `capabilities.${cap}.pack.hostAssets`
|
|
91
|
+
if (!ha || typeof ha !== 'object') { errors.push(`${at} 缺声明(每个能力必须声明宿主侧资产:定时器/自启/桌面项)`); continue }
|
|
92
|
+
for (const k of Object.keys(ha)) if (!HOST_ASSET_KEYS.includes(k)) errors.push(`${at} 出现未知键 \`${k}\`(允许:${HOST_ASSET_KEYS.join('、')})`)
|
|
93
|
+
for (const k of HOST_ASSET_KEYS) {
|
|
94
|
+
if (k === '_note') continue
|
|
95
|
+
const a = ha[k]
|
|
96
|
+
if (!a || typeof a !== 'object') { errors.push(`${at}.${k} 缺声明`); continue }
|
|
97
|
+
const allow = HOST_ASSET_FIELDS[k]
|
|
98
|
+
for (const kk of Object.keys(a)) if (!allow.includes(kk)) errors.push(`${at}.${k} 出现未知键 \`${kk}\`(允许:${allow.join('、')})`)
|
|
99
|
+
if (typeof a.required !== 'boolean') errors.push(`${at}.${k} 缺 \`required\`(布尔:该能力是否需要这项宿主资产)`)
|
|
100
|
+
if (!HOST_ASSET_INSTALL.includes(a.install)) errors.push(`${at}.${k} 的 \`install\` 非法:${a.install}(允许:${HOST_ASSET_INSTALL.join('、')})`)
|
|
101
|
+
}
|
|
102
|
+
const s = ha.scheduler
|
|
103
|
+
if (s && typeof s === 'object') {
|
|
104
|
+
const nFlows = (c.flows || []).length
|
|
105
|
+
if (s.required === true && nFlows === 0) errors.push(`${at}.scheduler.required=true 但该能力没有 flow —— 没有可定时的东西`)
|
|
106
|
+
if (s.required === false && nFlows > 0) errors.push(`${at}.scheduler.required=false 但该能力有 ${nFlows} 条 flow —— 声明与事实矛盾`)
|
|
107
|
+
if (s.required === true && !isStr(s.command)) errors.push(`${at}.scheduler 需要 \`command\`(用户可照抄的安装命令模板)`)
|
|
108
|
+
if (s.required === true && s.install !== 'print-only') warnings.push(`${at}.scheduler.install=${s.install} —— 今天安装器**只打印**命令(⛔ 不自动装);若确实要自动装,请同时改安装器并写进设计文档`)
|
|
109
|
+
}
|
|
110
|
+
if (ha.loginAutostart && ha.loginAutostart.required === true && ha.loginAutostart.install !== 'auto') {
|
|
111
|
+
warnings.push(`${at}.loginAutostart.required=true 但 install=${ha.loginAutostart.install} —— 今天只有 \`--win-autostart\` 这条显式 opt-in 通道`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { errors, warnings }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── 轻 IO ────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
function readJson(p) {
|
|
120
|
+
try { return JSON.parse(readFileSync(p, 'utf8')) } catch { return null }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 读目录声明(找不到 ⇒ 返回 null,由调用方**响亮**报错,⛔ 不静默补一份)。 */
|
|
124
|
+
export function loadCatalog(root = REPO) {
|
|
125
|
+
return readJson(join(root, CATALOG_REL))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** 装机账本路径。 */
|
|
129
|
+
export function agentLedgerPath(dshHome) {
|
|
130
|
+
return join(dshHome || join(process.env.HOME || '', '.dsh'), LEDGER_REL)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** 读装机账本(缺失/坏文件 ⇒ `{agents: []}`,由调用方决定是否告警)。 */
|
|
134
|
+
export function readAgentLedger(dshHome) {
|
|
135
|
+
const d = readJson(agentLedgerPath(dshHome))
|
|
136
|
+
if (!d || typeof d !== 'object') return { agents: [] }
|
|
137
|
+
return { ...d, agents: Array.isArray(d.agents) ? d.agents.filter((a) => a && a.id) : [] }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 由**既有声明**拼出校验上下文(⛔ 本模块不新建名单)。
|
|
142
|
+
* @param {string} root 仓库根
|
|
143
|
+
* @param {(s: string) => void} [warn] 缺件时的告警出口(缺件不静默)
|
|
144
|
+
*/
|
|
145
|
+
export function buildContext(root = REPO, warn = () => {}) {
|
|
146
|
+
const lock = readJson(join(root, 'scripts', 'capability-packs.lock.json')) || {}
|
|
147
|
+
const base = readJson(join(root, 'scripts', 'flow-requirements.json')) || {}
|
|
148
|
+
const shelf = readJson(join(root, 'plugins', 'argent-presets', 'presets.json')) || {}
|
|
149
|
+
const lockCaps = (lock && lock.capabilities) || {}
|
|
150
|
+
const baseCaps = (base && base.capabilities) || {}
|
|
151
|
+
if (!Object.keys(lockCaps).length) warn(`读不到能力包版本锁(${join(root, 'scripts', 'capability-packs.lock.json')})——能力清单为空`)
|
|
152
|
+
if (!Object.keys(shelf).length) warn(`读不到角色清单(${join(root, 'plugins', 'argent-presets', 'presets.json')})——角色清单为空`)
|
|
153
|
+
|
|
154
|
+
const capabilities = {}
|
|
155
|
+
const flows = {}
|
|
156
|
+
for (const [id, c] of Object.entries(lockCaps)) {
|
|
157
|
+
const files = Object.keys(c.files || {})
|
|
158
|
+
const packPresets = [...new Set(files.filter((f) => f.startsWith('presets/')).map((f) => f.split('/')[1]).filter(Boolean))]
|
|
159
|
+
const packSkills = [...new Set(files.filter((f) => f.startsWith('skills/')).map((f) => f.split('/')[1]).filter(Boolean))]
|
|
160
|
+
const declared = ((baseCaps[id] || {}).pack || {})
|
|
161
|
+
capabilities[id] = {
|
|
162
|
+
id,
|
|
163
|
+
version: c.version || '',
|
|
164
|
+
minPlan: (baseCaps[id] || {}).minPlan || '',
|
|
165
|
+
hostAssets: declared.hostAssets || null,
|
|
166
|
+
flows: Array.isArray(c.flows) ? [...c.flows] : [],
|
|
167
|
+
packPresets: [...new Set([...packPresets, ...((declared.presets || []).map((p) => String(p).replace(/^presets\//, '')))])],
|
|
168
|
+
packSkills: [...new Set([...packSkills, ...(declared.skills || []).map(String)])],
|
|
169
|
+
files: c.files || {},
|
|
170
|
+
}
|
|
171
|
+
for (const f of capabilities[id].flows) flows[f] = { ...(flows[f] || {}), capability: id }
|
|
172
|
+
}
|
|
173
|
+
// 业务线侧的角色/技能声明(flow → role/skill):注册表才是合并真源,这里只读**单线文件**的原始字段,
|
|
174
|
+
// 供门禁做"flow ↔ role"双射交叉校验;装机器不需要它(见 validateCatalog 的 opts.flowRole)。
|
|
175
|
+
// ⚠️ `flows.json#role` 是**短角色名**(`ml-fulfillment`),而 preset id 是 `argent-<短名>`
|
|
176
|
+
// (唯一派生规则见 `scripts/capability_pack.py#manifest()`:`presets/argent-{role}`)⇒ 这里归一成
|
|
177
|
+
// **preset id**,否则交叉校验会用短名去比 preset 清单(恒不命中 ⇒ 判据形同虚设)。
|
|
178
|
+
const presetIdOfRole = (r) => (String(r || '').startsWith('argent-') ? String(r) : `argent-${r}`)
|
|
179
|
+
const verticalsDir = join(root, 'verticals')
|
|
180
|
+
if (existsSync(verticalsDir)) {
|
|
181
|
+
for (const d of readdirSync(verticalsDir)) {
|
|
182
|
+
const f = join(verticalsDir, d, 'flows.json')
|
|
183
|
+
if (!existsSync(f)) continue
|
|
184
|
+
const doc = readJson(f)
|
|
185
|
+
for (const [local, spec] of Object.entries((doc && doc.flows) || {})) {
|
|
186
|
+
const canonical = `${d}.${local}`
|
|
187
|
+
flows[canonical] = { ...(flows[canonical] || {}), role: presetIdOfRole((spec || {}).role), skill: (spec || {}).skill || '' }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const plugins = existsSync(join(root, 'plugins')) ? readdirSync(join(root, 'plugins')).filter((d) => !d.startsWith('.')) : []
|
|
193
|
+
const skillsDir = join(root, 'skills')
|
|
194
|
+
const skills = existsSync(skillsDir)
|
|
195
|
+
? readdirSync(skillsDir).filter((d) => {
|
|
196
|
+
try { return statSync(join(skillsDir, d)).isDirectory() && existsSync(join(skillsDir, d, 'SKILL.md')) } catch { return false }
|
|
197
|
+
})
|
|
198
|
+
: []
|
|
199
|
+
|
|
200
|
+
return { capabilities, flows, presets: shelf, plugins, skills }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── 纯函数:校验 ──────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
const isStr = (x) => typeof x === 'string' && x.trim() !== ''
|
|
206
|
+
const isArr = (x) => Array.isArray(x)
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 目录 schema 校验(**门禁与安装器共用同一份**)。
|
|
210
|
+
*
|
|
211
|
+
* @param {object} doc 目录文档(`agents/catalog.json` 解析结果)
|
|
212
|
+
* @param {object} ctx `buildContext()` 的输出
|
|
213
|
+
* @param {{flowRole?: Record<string,string>}} [opts] `flowRole`:规范 flow id → 角色名(**可选的更强交叉校验**,
|
|
214
|
+
* 只有拿得到活注册表时才给;给了就查 "每条 flow 的角色必须在 roles 里 ∧ 每个专属角色必须对应一条已声明 flow")。
|
|
215
|
+
* @returns {{errors: string[], warnings: string[]}}
|
|
216
|
+
*/
|
|
217
|
+
export function validateCatalog(doc, ctx, opts = {}) {
|
|
218
|
+
const errors = []
|
|
219
|
+
const warnings = []
|
|
220
|
+
const E = (m) => errors.push(m)
|
|
221
|
+
const W = (m) => warnings.push(m)
|
|
222
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) {
|
|
223
|
+
E(`${CATALOG_REL} 不是对象(或读不到)`)
|
|
224
|
+
return { errors, warnings }
|
|
225
|
+
}
|
|
226
|
+
for (const k of Object.keys(doc)) if (!DOC_KEYS.includes(k)) E(`目录文档出现未知字段 \`${k}\`(允许:${DOC_KEYS.join('、')})`)
|
|
227
|
+
if (!Array.isArray(doc.agents)) {
|
|
228
|
+
E('目录文档缺 `agents` 数组')
|
|
229
|
+
return { errors, warnings }
|
|
230
|
+
}
|
|
231
|
+
const plans = isArr(doc.plans) && doc.plans.length ? doc.plans : Object.keys(PLAN_RANK)
|
|
232
|
+
const declPlugins = (doc.plugins && typeof doc.plugins === 'object') ? doc.plugins : {}
|
|
233
|
+
const declIntegrations = (doc.integrations && typeof doc.integrations === 'object') ? doc.integrations : {}
|
|
234
|
+
const capIds = Object.keys(ctx.capabilities || {})
|
|
235
|
+
const allFlows = Object.keys(ctx.flows || {})
|
|
236
|
+
const presetIds = Object.keys(ctx.presets || {})
|
|
237
|
+
|
|
238
|
+
const seen = new Set()
|
|
239
|
+
doc.agents.forEach((a, i) => {
|
|
240
|
+
const at = `agents[${i}]${a && a.id ? `(${a.id})` : ''}`
|
|
241
|
+
if (!a || typeof a !== 'object' || Array.isArray(a)) { E(`${at} 不是对象`); return }
|
|
242
|
+
for (const k of Object.keys(a)) if (!AGENT_KEYS.includes(k)) E(`${at} 出现未知字段 \`${k}\`(允许:${AGENT_KEYS.join('、')})`)
|
|
243
|
+
if (!isStr(a.id)) E(`${at} 缺 \`id\``)
|
|
244
|
+
else if (seen.has(a.id)) E(`${at} \`id\` 重复:${a.id}`)
|
|
245
|
+
else seen.add(a.id)
|
|
246
|
+
if (!isStr(a.title)) E(`${at} 缺 \`title\``)
|
|
247
|
+
if (!isStr(a.summary)) E(`${at} 缺 \`summary\``)
|
|
248
|
+
|
|
249
|
+
// ① capability / capabilities 二选一
|
|
250
|
+
const hasOne = isStr(a.capability)
|
|
251
|
+
const hasMany = isArr(a.capabilities)
|
|
252
|
+
if (hasOne === hasMany) E(`${at} \`capability\` 与 \`capabilities\` 必须**二选一**(单能力用前者、组合用后者)`)
|
|
253
|
+
const caps = hasMany ? a.capabilities.map(String) : (hasOne ? [String(a.capability)] : [])
|
|
254
|
+
if (!caps.length) E(`${at} 没有声明任何能力`)
|
|
255
|
+
for (const c of caps) if (!capIds.includes(c)) E(`${at} 未知能力 \`${c}\`(在册:${capIds.join('、') || '(无)'})`)
|
|
256
|
+
if (hasMany && caps.length < 2) E(`${at} 用了 \`capabilities\` 却只有 ${caps.length} 项 —— 单能力请写 \`capability\``)
|
|
257
|
+
if (caps.some((c) => !capIds.includes(c))) return // 未知能力 ⇒ 后续推导不可靠,早退(错误已记)
|
|
258
|
+
|
|
259
|
+
// ② flows ⊆ 已声明能力名下的规范 id 子集;scheduler 一致性
|
|
260
|
+
if (!isArr(a.flows)) { E(`${at} 缺 \`flows\` 数组(没有流程就给 \`[]\`)`); return }
|
|
261
|
+
const own = new Set(caps.flatMap((c) => ctx.capabilities[c].flows || []))
|
|
262
|
+
for (const f of a.flows.map(String)) {
|
|
263
|
+
if (!allFlows.includes(f)) E(`${at} 未知 flow \`${f}\`(在册:${allFlows.join('、') || '(无)'})`)
|
|
264
|
+
else if (!own.has(f)) E(`${at} 的 flow \`${f}\` 不属于它声明的能力(${caps.join('、')})`)
|
|
265
|
+
}
|
|
266
|
+
if (a.scheduler === true && !a.flows.length) E(`${at} \`scheduler=true\` 但 \`flows\` 为空 —— 没有流程就没有定时器`)
|
|
267
|
+
if (a.scheduler === false && a.flows.length) E(`${at} \`scheduler=false\` 但声明了 ${a.flows.length} 条 flow —— 二者自相矛盾`)
|
|
268
|
+
if (typeof a.scheduler !== 'boolean') E(`${at} 缺 \`scheduler\`(布尔:该智能体是否要定时流程)`)
|
|
269
|
+
// 与**能力声明的宿主资产**逐条对齐(判据源 = `capabilities.<c>.pack.hostAssets.scheduler.required`):
|
|
270
|
+
// 两处不一致 ⇒ 智能体说"有定时器"而能力说"不需要"(或反过来)⇒ 安装器会打印/漏打印命令。
|
|
271
|
+
{
|
|
272
|
+
const need = caps.some((c) => {
|
|
273
|
+
const ha = (ctx.capabilities[c] || {}).hostAssets
|
|
274
|
+
return !!(ha && ha.scheduler && ha.scheduler.required === true)
|
|
275
|
+
})
|
|
276
|
+
if (typeof a.scheduler === 'boolean' && a.scheduler !== need) {
|
|
277
|
+
E(`${at} 的 \`scheduler=${a.scheduler}\` 与能力声明的宿主资产不一致(能力侧 scheduler.required=${need})`)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const capFlows = caps.reduce((n, c) => n + (ctx.capabilities[c].flows || []).length, 0)
|
|
281
|
+
if (capFlows > 0 && a.flows.length === 0) E(`${at} 声明的能力有 ${capFlows} 条流程,但 \`flows\` 为空 —— 那等于"装了不跑"`)
|
|
282
|
+
|
|
283
|
+
// ③ roles:必须含恒装入口;每个角色必须归属本智能体;flow 的 role 必须在内(opts.flowRole 给了才查)
|
|
284
|
+
if (!isArr(a.roles) || !a.roles.length) E(`${at} 缺 \`roles\` 数组(至少含 \`${ALWAYS_ROLE}\`)`)
|
|
285
|
+
else {
|
|
286
|
+
const roles = a.roles.map(String)
|
|
287
|
+
if (!roles.includes(ALWAYS_ROLE)) E(`${at} 的 \`roles\` 缺恒装入口 \`${ALWAYS_ROLE}\``)
|
|
288
|
+
if (new Set(roles).size !== roles.length) E(`${at} 的 \`roles\` 有重复项`)
|
|
289
|
+
const flowRoles = new Set((a.flows || []).map((f) => (opts.flowRole || {})[f]).filter(Boolean))
|
|
290
|
+
for (const r of roles) {
|
|
291
|
+
if (!presetIds.includes(r)) { E(`${at} 未知角色 \`${r}\`(在册 preset:${presetIds.join('、') || '(无)'})`); continue }
|
|
292
|
+
const meta = ctx.presets[r] || {}
|
|
293
|
+
if (r === ALWAYS_ROLE) continue
|
|
294
|
+
const byCap = isStr(meta.capability) && caps.includes(String(meta.capability))
|
|
295
|
+
const byList = isArr(meta.capabilities) && meta.capabilities.map(String).some((c) => caps.includes(c))
|
|
296
|
+
const byRolesFor = isArr(meta.rolesFor) && meta.rolesFor.map(String).includes(String(a.id))
|
|
297
|
+
const byFlow = flowRoles.has(r)
|
|
298
|
+
if (!(byCap || byList || byRolesFor || byFlow)) {
|
|
299
|
+
E(`${at} 的角色 \`${r}\` 归属不上(preset 声明 capability=${meta.capability || '(无)'};也不在 rolesFor/[flow.role] 里)`)
|
|
300
|
+
}
|
|
301
|
+
if (isStr(meta.visibility) && meta.visibility === 'internal' && !byRolesFor) {
|
|
302
|
+
W(`${at} 引用了内部角色 \`${r}\`(默认不落客户机;要装需 \`--internal-roles\`)`)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// 反向:每条已声明 flow 的角色必须在 roles 里
|
|
306
|
+
for (const f of a.flows || []) {
|
|
307
|
+
const fr = (opts.flowRole || {})[f]
|
|
308
|
+
if (fr && !roles.includes(fr)) E(`${at} 声明了 flow \`${f}\`(角色 ${fr})却没把 \`${fr}\` 放进 \`roles\``)
|
|
309
|
+
}
|
|
310
|
+
// 反向:同能力里**别的** flow 的角色不得混进来(本批的核心诉求:发票两条 flow 分属两个智能体)
|
|
311
|
+
if (capFlows > 1 && (a.flows || []).length < capFlows) {
|
|
312
|
+
const excluded = caps.flatMap((c) => (ctx.capabilities[c].flows || [])).filter((f) => !(a.flows || []).includes(f))
|
|
313
|
+
for (const f of excluded) {
|
|
314
|
+
const fr = (opts.flowRole || {})[f]
|
|
315
|
+
if (fr && roles.includes(fr)) E(`${at} 未声明 flow \`${f}\`,却把它的角色 \`${fr}\` 放进了 \`roles\` —— 子集过滤会让它落不了地`)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ④ plugins:至少含共享壳;未知插件 ⇒ 红
|
|
321
|
+
if (!isArr(a.plugins) || !a.plugins.length) E(`${at} 缺 \`plugins\` 数组(至少含 ${SHARED_SHELL_PLUGINS.join(' + ')})`)
|
|
322
|
+
else {
|
|
323
|
+
const ps = a.plugins.map(String)
|
|
324
|
+
for (const p of SHARED_SHELL_PLUGINS) if (!ps.includes(p)) E(`${at} 的 \`plugins\` 缺共享壳 \`${p}\``)
|
|
325
|
+
for (const p of ps) {
|
|
326
|
+
if (!(ctx.plugins || []).includes(p)) E(`${at} 未知插件 \`${p}\`(仓库 plugins/ 下没有它)`)
|
|
327
|
+
else if (!(p in declPlugins)) W(`${at} 的插件 \`${p}\` 未在目录文档的 \`plugins\` 说明表里登记`)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ⑤ skills:必须真实存在 ∧ ∈ 已声明能力包的技能集
|
|
332
|
+
if (!isArr(a.skills)) E(`${at} 缺 \`skills\` 数组(不需要就给 \`[]\`)`)
|
|
333
|
+
else {
|
|
334
|
+
const packSkills = new Set(caps.flatMap((c) => ctx.capabilities[c].packSkills || []))
|
|
335
|
+
for (const s of a.skills.map(String)) {
|
|
336
|
+
if (!(ctx.skills || []).includes(s)) E(`${at} 未知技能 \`${s}\`(skills/ 下没有含 SKILL.md 的该目录)`)
|
|
337
|
+
else if (!packSkills.has(s)) E(`${at} 的技能 \`${s}\` 不在已声明能力包的技能集里(${[...packSkills].join('、') || '(空)'})⇒ 装了也拿不到它`)
|
|
338
|
+
}
|
|
339
|
+
const flowSkills = new Set((a.flows || []).map((f) => (ctx.flows[f] || {}).skill).filter(Boolean))
|
|
340
|
+
for (const s of flowSkills) if (!a.skills.map(String).includes(s)) W(`${at} 的 flow 声明了技能 \`${s}\`,但 \`skills\` 没列它`)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ⑥ integrations:必须在目录文档的集成说明表里登记
|
|
344
|
+
if (!isArr(a.integrations)) E(`${at} 缺 \`integrations\` 数组(不需要就给 \`[]\`)`)
|
|
345
|
+
else for (const g of a.integrations.map(String)) {
|
|
346
|
+
if (!(g in declIntegrations)) E(`${at} 未知系统集成 \`${g}\`(在册:${Object.keys(declIntegrations).join('、') || '(无)'})`)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ⑦ credentials:只写槽位/落点/用途,⛔ 不含值
|
|
350
|
+
if (!isArr(a.credentials)) E(`${at} 缺 \`credentials\` 数组(不需要就给 \`[]\`)`)
|
|
351
|
+
else a.credentials.forEach((c, j) => {
|
|
352
|
+
const ct = `${at}.credentials[${j}]`
|
|
353
|
+
if (!c || typeof c !== 'object' || Array.isArray(c)) { E(`${ct} 不是对象`); return }
|
|
354
|
+
for (const k of Object.keys(c)) {
|
|
355
|
+
if (FORBIDDEN_CRED_KEYS.includes(k)) E(`${ct} 出现禁止键 \`${k}\` —— 凭据槽**只写槽位名与落点**,⛔ 绝不写值`)
|
|
356
|
+
else if (!CREDENTIAL_KEYS.includes(k)) E(`${ct} 未知键 \`${k}\`(允许:${CREDENTIAL_KEYS.join('、')})`)
|
|
357
|
+
}
|
|
358
|
+
for (const k of CREDENTIAL_KEYS) if (!isStr(c[k])) E(`${ct} 缺 \`${k}\``)
|
|
359
|
+
if (isStr(c.slot) && !/^[A-Za-z][A-Za-z0-9_.-]*$/.test(c.slot)) E(`${ct} 的 \`slot\` 不像凭据槽名:${c.slot}`)
|
|
360
|
+
// 落点白名单(**只认既有约定的两处**):`config.json`(~/.dsh/argent/config.json 的槽位)
|
|
361
|
+
// 或 `**/env/*.env`(业务线 env 文件,`scope: vertical` 的既有落点)。
|
|
362
|
+
if (isStr(c.target) && c.target !== 'config.json' && !/(^|\/)env\/[^/]+\.env$/.test(c.target)) {
|
|
363
|
+
E(`${ct} 的 \`target\` 不是约定的凭据落点:${c.target}(只认 \`config.json\` 或 \`<…>/env/<名>.env\`)`)
|
|
364
|
+
}
|
|
365
|
+
for (const v of Object.values(c)) {
|
|
366
|
+
if (typeof v !== 'string') continue
|
|
367
|
+
for (const re of SECRET_SHAPED) if (re.test(v)) E(`${ct} 的字段出现**密钥形态**串(${re})—— 凭据槽里不许有值`)
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
// ⑧ minPlan:合法 ∧ 不低于已声明能力包的档位下限
|
|
372
|
+
if (!isStr(a.minPlan) || !plans.includes(a.minPlan)) E(`${at} 未知档位 \`${a.minPlan}\`(在册:${plans.join('、')})`)
|
|
373
|
+
else for (const c of caps) {
|
|
374
|
+
const need = ctx.capabilities[c].minPlan
|
|
375
|
+
if (need && PLAN_RANK[a.minPlan] < PLAN_RANK[need]) {
|
|
376
|
+
E(`${at} 档位 ${a.minPlan} 低于能力 \`${c}\` 的下限 ${need}`)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
// 每个能力都应至少被一个智能体覆盖(否则货架上买不到它)
|
|
382
|
+
const covered = new Set(doc.agents.flatMap((a) => (isArr(a.capabilities) ? a.capabilities : (isStr(a.capability) ? [a.capability] : []))))
|
|
383
|
+
for (const c of capIds) if (!covered.has(c)) W(`能力 \`${c}\` 没有任何智能体覆盖 —— 货架上买不到它`)
|
|
384
|
+
return { errors, warnings }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ── 纯函数:清单构造 ──────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
/** 按 id 取智能体(找不到 ⇒ null)。 */
|
|
390
|
+
export function findAgent(doc, id) {
|
|
391
|
+
return (doc && Array.isArray(doc.agents) ? doc.agents : []).find((a) => a && String(a.id) === String(id)) || null
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* 把若干智能体投影成**安装清单**("装什么"),并算出"**刻意没装什么**"(universe − 装)。
|
|
396
|
+
*
|
|
397
|
+
* @param {object} doc 目录文档
|
|
398
|
+
* @param {string[]} ids 点名的智能体 id
|
|
399
|
+
* @param {object} ctx buildContext() 输出
|
|
400
|
+
* @returns {{ok: boolean, unknown: string[], capabilities: string[], flows: string[], roles: string[],
|
|
401
|
+
* plugins: string[], skills: string[], integrations: string[], credentials: object[], scheduler: boolean,
|
|
402
|
+
* minPlan: string, notInstalled: object}}
|
|
403
|
+
*/
|
|
404
|
+
export function planForAgents(doc, ids, ctx) {
|
|
405
|
+
const want = [...new Set((ids || []).map(String))]
|
|
406
|
+
const agents = want.map((id) => findAgent(doc, id))
|
|
407
|
+
const unknown = want.filter((_, i) => !agents[i])
|
|
408
|
+
const found = agents.filter(Boolean)
|
|
409
|
+
const capabilities = [...new Set(found.flatMap((a) => (isArr(a.capabilities) ? a.capabilities : [a.capability]).map(String)))]
|
|
410
|
+
const declaredFlows = [...new Set(found.flatMap((a) => (a.flows || []).map(String)))]
|
|
411
|
+
// flow 子集过滤:只保留**已声明能力**名下的 flow(能力包会带全部流程的资产 ⇒ 落盘侧再按此收窄)
|
|
412
|
+
const ownFlows = new Set(capabilities.flatMap((c) => (ctx.capabilities[c] || {}).flows || []))
|
|
413
|
+
const flows = declaredFlows.filter((f) => ownFlows.has(f))
|
|
414
|
+
const roles = [...new Set(found.flatMap((a) => (a.roles || []).map(String)))]
|
|
415
|
+
const plugins = [...new Set(found.flatMap((a) => (a.plugins || []).map(String)))]
|
|
416
|
+
const shellPlugins = plugins.filter((p) => SHARED_SHELL_PLUGINS.includes(p))
|
|
417
|
+
// 「声明的额外插件」= 网关/流程控制台这类**不是**共享壳的。⚠️ 它们**不在本安装器的落盘面内**
|
|
418
|
+
// (安装器的壳插件名单是固定两个:`allowedPlugins`;profile 装配面由整装/设置页负责)⇒
|
|
419
|
+
// 回执里必须把"恒装"与"声明需要"分开讲,⛔ 不许把它们混成一句"装了"。
|
|
420
|
+
const extraPlugins = plugins.filter((p) => !SHARED_SHELL_PLUGINS.includes(p))
|
|
421
|
+
const skills = [...new Set(found.flatMap((a) => (a.skills || []).map(String)))]
|
|
422
|
+
const integrations = [...new Set(found.flatMap((a) => (a.integrations || []).map(String)))]
|
|
423
|
+
// 凭据槽按 slot 去重(同一槽被多个智能体需要 ⇒ 只列一次;why 取第一条)
|
|
424
|
+
const credMap = new Map()
|
|
425
|
+
for (const a of found) for (const c of a.credentials || []) if (!credMap.has(c.slot)) credMap.set(c.slot, { ...c, agents: [a.id] })
|
|
426
|
+
else credMap.get(c.slot).agents.push(a.id)
|
|
427
|
+
const credentials = [...credMap.values()]
|
|
428
|
+
const scheduler = found.some((a) => a.scheduler === true)
|
|
429
|
+
const minPlan = found.reduce((acc, a) => (PLAN_RANK[a.minPlan] > PLAN_RANK[acc] ? a.minPlan : acc), 'free')
|
|
430
|
+
|
|
431
|
+
const allCaps = Object.keys(ctx.capabilities || {})
|
|
432
|
+
const allFlows = Object.keys(ctx.flows || {})
|
|
433
|
+
const allRoles = Object.keys(ctx.presets || {})
|
|
434
|
+
// 货架上"别的插件":排除共享壳、排除**角色承载**(`argent-presets` 是所有角色的挂载面,不是某个智能体的插件,
|
|
435
|
+
// 把它列进"刻意没装"是误导 —— 它恰恰是这次要落的那一个)。
|
|
436
|
+
// ⚠️ W3(0.6.0)追加两条**同类**排除(同一条理由:它们不是"可以另外装的插件"):
|
|
437
|
+
// · `argent-shell`(W2 的壳 bundle)—— 基座,不是可选项;列进"刻意没装"是口径错(W2 遗留的假条目);
|
|
438
|
+
// · `argent-agent-<id>`(W3 的智能体 bundle)—— **它们就是智能体本身**(货架上的另一件商品),
|
|
439
|
+
// 不是"插件";⛔ 更不能让 `--agent hr` 的回执把另外 5 个智能体列成"故意不装的插件"。
|
|
440
|
+
const allPlugins = (ctx.plugins || []).filter((p) => p.startsWith('argent-') && !SHARED_SHELL_PLUGINS.includes(p)
|
|
441
|
+
&& p !== 'argent-presets' && p !== 'argent-shell' && !p.startsWith('argent-agent-'))
|
|
442
|
+
const allSkills = Object.keys(ctx.capabilities || {}).flatMap((c) => (ctx.capabilities[c].packSkills || []))
|
|
443
|
+
.concat(((doc && doc.agents) || []).flatMap((a) => a.skills || []))
|
|
444
|
+
const notInstalled = {
|
|
445
|
+
capabilities: allCaps.filter((c) => !capabilities.includes(c)),
|
|
446
|
+
flows: allFlows.filter((f) => !flows.includes(f)),
|
|
447
|
+
roles: allRoles.filter((r) => !roles.includes(r)),
|
|
448
|
+
plugins: [...new Set(allPlugins)].filter((p) => !extraPlugins.includes(p)),
|
|
449
|
+
skills: [...new Set(allSkills)].filter((s) => !skills.includes(s)),
|
|
450
|
+
}
|
|
451
|
+
return { ok: unknown.length === 0, unknown, capabilities, flows, roles, plugins, shellPlugins, extraPlugins, skills, integrations, credentials, scheduler, minPlan, notInstalled }
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* **preset 落盘集合**(唯一实现;安装器与升级器共用)。
|
|
456
|
+
*
|
|
457
|
+
* 口径(A 批,2026-10-10):
|
|
458
|
+
* · `internal` 为真 ⇒ **全部**(显式 `--internal-roles`;本机开发/内部角色用);
|
|
459
|
+
* · `agentScoped` 为真(`--agent`)⇒ `恒装(always)` ∪ `智能体点名的角色` ∪ `previous`
|
|
460
|
+
* —— 这才是"选一个智能体 ⇒ **只落它的角色**"(**同能力的兄弟角色不落**:
|
|
461
|
+
* 共享能力包里有两条流程时,各自的角色只随各自那一个智能体走);
|
|
462
|
+
* · 否则(默认整装)⇒ `客户可见集(visibility=customer)` ∪ `previous`
|
|
463
|
+
* —— **内部角色默认不落客户机**;
|
|
464
|
+
* · `previous`(本机**已经装过**的角色)三档都并入。
|
|
465
|
+
*
|
|
466
|
+
* ⚠️ 为什么每档都要并上 `previous`:默认集从"全部"收紧成"客户可见/本智能体"后,若不做并集,
|
|
467
|
+
* **重跑安装器/升级会把本机既有角色一起抹掉**(本机 7 个内部角色正在被内部会话使用;HR 的
|
|
468
|
+
* `argent-hr-office` 墓碑也靠这条留在老机器上)。收紧的目标是"**全新机器**不许默认拿到不该拿的",
|
|
469
|
+
* 不是"把已有机器改坏"。全新机器 `previous` 为空 ⇒ 落盘集合**精确等于**上面那一档。
|
|
470
|
+
*
|
|
471
|
+
* @param {{presets: object, agents?: object[], internal?: boolean, agentScoped?: boolean, previous?: Iterable<string>}} o
|
|
472
|
+
* @returns {string[]} 允许落盘的 preset id(保序 = 货架顺序)
|
|
473
|
+
*/
|
|
474
|
+
export function presetSelectionForAgents({ presets, agents = [], internal = false, agentScoped = false, previous = [] }) {
|
|
475
|
+
const all = Object.keys(presets || {})
|
|
476
|
+
if (internal) return all
|
|
477
|
+
const keep = new Set(previous || [])
|
|
478
|
+
if (agentScoped && (agents || []).length) {
|
|
479
|
+
for (const id of all) if ((presets[id] || {}).always === true) keep.add(id)
|
|
480
|
+
} else {
|
|
481
|
+
for (const id of all) {
|
|
482
|
+
const meta = presets[id] || {}
|
|
483
|
+
const vis = isStr(meta.visibility) ? meta.visibility : 'customer'
|
|
484
|
+
if (vis === 'customer' || meta.always === true) keep.add(id)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
for (const a of agents || []) for (const r of (a && a.roles) || []) keep.add(String(r))
|
|
488
|
+
return all.filter((id) => keep.has(id))
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** 「装了什么 / 刻意没装什么」的可复核回执(纯文本行;⛔ 不含凭据值)。 */
|
|
492
|
+
export function composeReceipt(plan, ctx, doc, { dshHome = '', internal = false } = {}) {
|
|
493
|
+
const L = []
|
|
494
|
+
const list = (xs) => (xs.length ? xs.join('、') : '(无)')
|
|
495
|
+
L.push(` 装了什么:`)
|
|
496
|
+
L.push(` 能力包:${list(plan.capabilities)}`)
|
|
497
|
+
L.push(` 流程:${list(plan.flows)}${plan.scheduler ? '' : '(本智能体无定时流程)'}`)
|
|
498
|
+
L.push(` 角色:${list(plan.roles)}${internal ? '(含内部角色)' : ''}`)
|
|
499
|
+
L.push(` 插件(共享壳,恒装):${list(plan.shellPlugins || plan.plugins.filter((p) => SHARED_SHELL_PLUGINS.includes(p)))}`)
|
|
500
|
+
if ((plan.extraPlugins || []).length) {
|
|
501
|
+
L.push(` 插件(**声明需要**,不在本安装器的落盘面内):${plan.extraPlugins.join('、')}`)
|
|
502
|
+
L.push(` ℹ 网关/流程控制台属 profile 装配面(整装 + 设置页),本命令只做"智能体面":能力包 + 角色 + 技能`)
|
|
503
|
+
}
|
|
504
|
+
L.push(` 技能:${list(plan.skills)}`)
|
|
505
|
+
L.push(` 系统集成:${list(plan.integrations)}`)
|
|
506
|
+
L.push(` 宿主资产(判据 = 能力声明的 hostAssets):定时器 ${plan.scheduler ? '需要' : '**不需要**'}`
|
|
507
|
+
+ `${plan.scheduler ? '(安装器只**打印**命令,⛔ 不自动装)' : '(无 flow ⇒ 没有任何可定时的东西)'}`
|
|
508
|
+
+ ` |登录自启:默认不建 |桌面项:无`)
|
|
509
|
+
if (plan.credentials.length) {
|
|
510
|
+
L.push(` 凭据**槽位**(⛔ 安装器不写任何值;值只由你/设置页填):`)
|
|
511
|
+
for (const c of plan.credentials) L.push(` · ${c.slot} → ${c.target} —— ${c.why}`)
|
|
512
|
+
} else {
|
|
513
|
+
L.push(` 凭据槽位:(无 —— 本智能体不需要外部凭据)`)
|
|
514
|
+
}
|
|
515
|
+
L.push(` 档位:${plan.minPlan}${dshHome ? ` 装机 home:${dshHome}` : ''}`)
|
|
516
|
+
L.push(` 刻意没装什么:`)
|
|
517
|
+
L.push(` 能力包:${list(plan.notInstalled.capabilities)}`)
|
|
518
|
+
L.push(` 流程:${list(plan.notInstalled.flows)}`)
|
|
519
|
+
L.push(` 角色:${list(plan.notInstalled.roles)}`)
|
|
520
|
+
L.push(` 其它插件:${list(plan.notInstalled.plugins)}`)
|
|
521
|
+
if (!internal) {
|
|
522
|
+
const internalRoles = Object.keys((ctx && ctx.presets) || {}).filter((r) => (((ctx.presets || {})[r] || {}).visibility === 'internal'))
|
|
523
|
+
if (internalRoles.length) L.push(` 内部角色(需 --internal-roles 才装):${internalRoles.join('、')}`)
|
|
524
|
+
}
|
|
525
|
+
L.push(` ⛔ 共享壳 ${SHARED_SHELL_PLUGINS.join(' / ')} 恒装、且移除时永不删(其它智能体还要用)`)
|
|
526
|
+
return L
|
|
527
|
+
}
|