@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,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `webhook` 通道(P2 首发;§6.2 Q2 的建议选项 A:**零凭据、可离线桩测**)。
|
|
3
|
+
*
|
|
4
|
+
* 形态:一次 `POST <url>`,`application/json` 体,**不跟随重定向**(3xx 如实报失败 ——
|
|
5
|
+
* "被重定向到登录页"不是投递成功),`timeoutMs` 到点即失败(不挂住调用方)。
|
|
6
|
+
*
|
|
7
|
+
* ⛔ 本文件**不含任何"该不该发"的判据**(去重、开关、dry-run、审计全在 L1 的 verb 层)。
|
|
8
|
+
* 它只做一件事:把这份意图送出去,并如实回报**送到了/没送到、对方回了几多**。
|
|
9
|
+
*
|
|
10
|
+
* 为什么手写 `node:http(s)` 而不是 `fetch`:① 只依赖 Node 内置(无宿主、无第三方包);
|
|
11
|
+
* ② 需要"不跟随重定向 + 明确超时 + 拿到状态码与响应片段"这三件事的可判行为;
|
|
12
|
+
* ③ 在**无宿主·极简 PATH** 的冒烟环境里行为一致(P4 的 S6 用它打本地桩)。
|
|
13
|
+
*/
|
|
14
|
+
import { request as httpRequest } from 'node:http'
|
|
15
|
+
import { request as httpsRequest } from 'node:https'
|
|
16
|
+
|
|
17
|
+
export const CHANNEL = 'webhook'
|
|
18
|
+
export const IMPLEMENTED = true
|
|
19
|
+
export const TARGET = {
|
|
20
|
+
required: true,
|
|
21
|
+
label: '--url',
|
|
22
|
+
schemes: ['http:', 'https:'],
|
|
23
|
+
note: '通用 HTTP POST(JSON 体;不跟随重定向)',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 默认超时(ms)。10s 是"本地桩秒回、真实端点也够"的保守值。 */
|
|
27
|
+
export const DEFAULT_TIMEOUT_MS = 10000
|
|
28
|
+
/** 响应片段最多回显多少字符(诊断够用,不会把 payload 灌进日志)。 */
|
|
29
|
+
const RESPONSE_EXCERPT_CHARS = 400
|
|
30
|
+
/** 允许的头部名(避免注入 CRLF / 覆盖 Host 之类)。 */
|
|
31
|
+
const HEADER_NAME_RE = /^[A-Za-z0-9-]+$/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 目标校验(**用法**层面,不是业务判断):必须是绝对 http(s) URL。
|
|
35
|
+
* @returns {{ok:true, target:string} | {ok:false, reason:'target_required'|'bad_target', error:string}}
|
|
36
|
+
*/
|
|
37
|
+
export function validateTarget(target) {
|
|
38
|
+
const raw = String(target == null ? '' : target).trim()
|
|
39
|
+
if (!raw) return { ok: false, reason: 'target_required', error: `webhook 通道缺少目标:请给 ${TARGET.label} <http(s)://…>` }
|
|
40
|
+
let u = null
|
|
41
|
+
try { u = new URL(raw) } catch { return { ok: false, reason: 'bad_target', error: `目标不是合法 URL:${raw}` } }
|
|
42
|
+
if (!TARGET.schemes.includes(u.protocol)) {
|
|
43
|
+
return { ok: false, reason: 'bad_target', error: `webhook 只支持 ${TARGET.schemes.join(' / ')},实得 ${u.protocol}` }
|
|
44
|
+
}
|
|
45
|
+
return { ok: true, target: u.toString() }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** `--header 'K: V'` → 对象(坏头 ⇒ 明确报错,不静默丢)。 */
|
|
49
|
+
export function parseHeaders(list) {
|
|
50
|
+
const out = {}
|
|
51
|
+
for (const h of list || []) {
|
|
52
|
+
const s = String(h)
|
|
53
|
+
const i = s.indexOf(':')
|
|
54
|
+
if (i < 1) return { ok: false, error: `--header 形态应为 'Name: value',实得 ${JSON.stringify(s)}` }
|
|
55
|
+
const name = s.slice(0, i).trim()
|
|
56
|
+
const value = s.slice(i + 1).trim()
|
|
57
|
+
if (!HEADER_NAME_RE.test(name)) return { ok: false, error: `--header 头部名非法:${JSON.stringify(name)}` }
|
|
58
|
+
out[name] = value
|
|
59
|
+
}
|
|
60
|
+
return { ok: true, headers: out }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 去掉 URL 里的凭据与查询串(回显/审计用;**不改变实际投递目标**)。 */
|
|
64
|
+
export function sanitizeTarget(target) {
|
|
65
|
+
try {
|
|
66
|
+
const u = new URL(String(target))
|
|
67
|
+
u.username = ''
|
|
68
|
+
u.password = ''
|
|
69
|
+
u.search = ''
|
|
70
|
+
return u.toString()
|
|
71
|
+
} catch { return String(target || '') }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 投递(唯一副作用点:一次 HTTP 请求)。
|
|
76
|
+
* @param {{target:string, text:string, kind:string, summary:string, idempotencyKey:string, headers?:object, sentAt?:string}} intent
|
|
77
|
+
* @param {{timeoutMs?:number, deps?:object}} [opts] `deps.request` 可注入(夹具用,不联网)
|
|
78
|
+
*/
|
|
79
|
+
export function send(intent, opts = {}) {
|
|
80
|
+
const v = validateTarget(intent && intent.target)
|
|
81
|
+
if (!v.ok) return { ok: false, reason: v.reason, error: v.error }
|
|
82
|
+
const url = new URL(v.target)
|
|
83
|
+
const timeoutMs = Number(opts.timeoutMs || DEFAULT_TIMEOUT_MS)
|
|
84
|
+
const payload = {
|
|
85
|
+
source: 'argent',
|
|
86
|
+
channel: CHANNEL,
|
|
87
|
+
kind: intent.kind || 'generic',
|
|
88
|
+
summary: intent.summary || '',
|
|
89
|
+
text: intent.text == null ? '' : String(intent.text),
|
|
90
|
+
idempotencyKey: intent.idempotencyKey || null,
|
|
91
|
+
sentAt: intent.sentAt || new Date().toISOString(),
|
|
92
|
+
}
|
|
93
|
+
const body = Buffer.from(JSON.stringify(payload), 'utf8')
|
|
94
|
+
const headers = { 'content-type': 'application/json; charset=utf-8', 'content-length': String(body.length), ...(intent.headers || {}) }
|
|
95
|
+
const doRequest = (opts.deps && opts.deps.request) || (url.protocol === 'https:' ? httpsRequest : httpRequest)
|
|
96
|
+
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
let settled = false
|
|
99
|
+
const done = (r) => { if (!settled) { settled = true; resolve(r) } }
|
|
100
|
+
let req = null
|
|
101
|
+
try {
|
|
102
|
+
req = doRequest({
|
|
103
|
+
protocol: url.protocol,
|
|
104
|
+
hostname: url.hostname,
|
|
105
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
106
|
+
path: `${url.pathname}${url.search}`,
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers,
|
|
109
|
+
timeout: timeoutMs,
|
|
110
|
+
}, (res) => {
|
|
111
|
+
let text = ''
|
|
112
|
+
res.setEncoding('utf8')
|
|
113
|
+
res.on('data', (c) => { if (text.length < RESPONSE_EXCERPT_CHARS) text += c })
|
|
114
|
+
res.on('end', () => {
|
|
115
|
+
const status = Number(res.statusCode || 0)
|
|
116
|
+
const ok = status >= 200 && status < 300
|
|
117
|
+
done({
|
|
118
|
+
ok,
|
|
119
|
+
status,
|
|
120
|
+
responseExcerpt: text.slice(0, RESPONSE_EXCERPT_CHARS),
|
|
121
|
+
reason: ok ? null : 'delivery_failed',
|
|
122
|
+
error: ok ? null : `webhook 返回 ${status}(2xx 才算投递成功;**不跟随重定向**,3xx 如实算失败)`,
|
|
123
|
+
bytes: body.length,
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
res.on('error', (e) => done({ ok: false, status: null, reason: 'delivery_failed', error: `读取响应失败:${e && e.message}`, bytes: body.length }))
|
|
127
|
+
})
|
|
128
|
+
} catch (e) {
|
|
129
|
+
return done({ ok: false, status: null, reason: 'delivery_failed', error: `请求构造失败:${(e && e.message) || e}`, bytes: body.length })
|
|
130
|
+
}
|
|
131
|
+
req.on('timeout', () => { req.destroy(new Error(`超时(${timeoutMs}ms)`)) })
|
|
132
|
+
req.on('error', (e) => done({ ok: false, status: null, reason: 'delivery_failed', error: `请求失败:${(e && e.message) || e}`, bytes: body.length }))
|
|
133
|
+
req.end(body)
|
|
134
|
+
})
|
|
135
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `wecom`(企业微信)通道 —— **预留位**(P2 第二批;本批不实现)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不首发(§6.2 Q2 的选项 C 被否):它需要**企业微信应用凭据 + 客户配合**,
|
|
5
|
+
* 排期不可控;而 `webhook` 零凭据、可离线桩测(P4 的 S6 就靠它)。故本批 webhook 首发。
|
|
6
|
+
*
|
|
7
|
+
* 落地时的最小可用形态(写在这里,免得实现时再拍一次):
|
|
8
|
+
* · 群机器人 webhook:一次 POST `{"msgtype":"text","text":{"content":"…"}}` —— **零凭据**,
|
|
9
|
+
* 与 `webhook.mjs` 的差异只在**请求体形态**(故实现可直接复用 webhook 的传输层,只换 payload)。
|
|
10
|
+
* · 应用消息:需 `corpid/corpsecret/agentid`(走既有凭据槽位,⛔ 不新开明文文件、不进仓库)。
|
|
11
|
+
* · 目标形态、错误映射同上(`failureClasses` 为唯一权威)。
|
|
12
|
+
*/
|
|
13
|
+
export const CHANNEL = 'wecom'
|
|
14
|
+
export const IMPLEMENTED = false
|
|
15
|
+
export const NOTE = '企业微信(P2 第二批):群机器人 webhook 可零凭据起步,应用消息需凭据'
|
|
16
|
+
export const TARGET = {
|
|
17
|
+
required: true,
|
|
18
|
+
label: '--target',
|
|
19
|
+
schemes: ['http:', 'https:'],
|
|
20
|
+
note: '群机器人 webhook / 应用消息(待实现)',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function validateTarget(target) {
|
|
24
|
+
const raw = String(target == null ? '' : target).trim()
|
|
25
|
+
if (!raw) return { ok: false, reason: 'target_required', error: 'wecom 通道缺少目标(群机器人 webhook)' }
|
|
26
|
+
return { ok: true, target: raw }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 预留位:**永不**返回成功。 */
|
|
30
|
+
export function send() {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
reason: 'channel_not_implemented',
|
|
34
|
+
error: 'wecom 通道尚未实现(预留位)⇒ 本批请用 --channel webhook;实现落点见本文件头注释',
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python 解释器解析(0.3 P1,2026-10-03)—— **多候选 + 响亮失败**(修 D3)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么有它(D3 实测):`core/cli/argent.mjs` 与 `core/mcp/server.mjs` 都只有**一个**候选
|
|
5
|
+
* (`ARGENT_PY || <数据目录>/venv/bin/python3`),候选不存在时 exec 失败,而 `core/lib/sop.mjs`
|
|
6
|
+
* 的空 `catch {}` 把它吞掉 ⇒ `sop` 返回 `matchedBy:"none"` 且 **exit 0**:机器上"看起来没有流程",
|
|
7
|
+
* 实际是解释器不在。**静默降级比崩溃贵**(与 2026-09-22 触发器那次"每 5 分钟正常空转"同类)。
|
|
8
|
+
*
|
|
9
|
+
* 判据与 `scripts/flow-trigger.sh` 的 `pick_py()` **同源**(不另立第二套):
|
|
10
|
+
* · 候选顺序:ARGENT_PY → ARGENT_PYTHON → venv(数据目录 / 仓库)→ PATH 的 python3、python3.11
|
|
11
|
+
* · 可用 = 能启动 ∧ 版本 ≥ `scripts/lib/python-runtime.mjs#MIN_PYTHON` ∧ 能 `import openpyxl, yaml`
|
|
12
|
+
* (后两项逐字对齐 pick_py 的 `import openpyxl, yaml` 与 python-runtime 的版本门禁 ——
|
|
13
|
+
* 2026-09-28 客户机事故就是"只看 import 不看版本",把 3.9.6 的 venv 判成就绪)
|
|
14
|
+
* · 全不可用 ⇒ 调用方**响亮失败**(CLI exit 2 + 诊断),不得静默降级
|
|
15
|
+
*
|
|
16
|
+
* venv 只认**数据目录**(`resolveDataDir` 三级链 = 内核唯一判据:ARGENT_DATA_DIR → $DSH_HOME/argent
|
|
17
|
+
* → ~/.dsh/argent;`$DSH_HOME` 那一级已经折进它)—— 不再另加一条 `$HOME/.dsh/argent/venv` 同义候选:
|
|
18
|
+
* `ARGENT_DATA_DIR` 显式隔离时,宁可响亮失败,也不悄悄换用另一棵树的解释器(那正是 D3 要消灭的
|
|
19
|
+
* "看起来跑了、其实读的不是你以为的那份环境")。
|
|
20
|
+
*
|
|
21
|
+
* 纪律:候选只来自"显式指定 / 已知安装位置 / PATH",**不写死 `/usr/bin/python3` 这类绝对路径**。
|
|
22
|
+
* 这样"没有解释器的环境"(隔离冒烟、极简 PATH)会真的走到 fail-loud 分支,
|
|
23
|
+
* 而不是被某个恰好合格的系统解释器救活 —— 否则隔离用例会变成环境相关的假绿/假红。
|
|
24
|
+
*/
|
|
25
|
+
import { spawnSync } from 'node:child_process'
|
|
26
|
+
import { existsSync } from 'node:fs'
|
|
27
|
+
import { delimiter, isAbsolute, join } from 'node:path'
|
|
28
|
+
import { MIN_PYTHON, parseVersion, versionAtLeast } from '../../scripts/lib/python-runtime.mjs'
|
|
29
|
+
|
|
30
|
+
/** 解释器必须能 import 的模块(硬要求,与 `scripts/flow-trigger.sh` 的 pick_py() 一致)。 */
|
|
31
|
+
export const REQUIRED_MODULES = ['openpyxl', 'yaml']
|
|
32
|
+
|
|
33
|
+
/** PATH 上按名探测的解释器(顺序即优先级)。 */
|
|
34
|
+
export const PATH_NAMES = ['python3', 'python3.11']
|
|
35
|
+
|
|
36
|
+
/** 探测失败的原因码 → 给人看的中文(诊断信息里逐条打印)。 */
|
|
37
|
+
export const REASON_TEXT = {
|
|
38
|
+
not_found: '文件不存在',
|
|
39
|
+
not_executable: '不可执行',
|
|
40
|
+
start_failed: '启动失败',
|
|
41
|
+
version_unreadable: '版本读不出来',
|
|
42
|
+
version_too_old: '版本过低',
|
|
43
|
+
missing_modules: '缺依赖模块',
|
|
44
|
+
ok: '可用',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 真实探测(可注入,便于纯夹具测试):跑一次解释器,返回状态与输出。 */
|
|
48
|
+
export function realRun(bin, args) {
|
|
49
|
+
const r = spawnSync(bin, args, { encoding: 'utf8', timeout: 20000 })
|
|
50
|
+
return {
|
|
51
|
+
status: r.status,
|
|
52
|
+
out: `${r.stdout || ''}${r.stderr || ''}`.trim(),
|
|
53
|
+
spawnError: r.error ? String(r.error.code || r.error.message || 'error') : '',
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** PATH 里的候选(自己扫目录,**不** shell out 到 `which`:极简 PATH 下更可靠、零子进程)。 */
|
|
58
|
+
export function pathCandidates({ env = process.env, names = PATH_NAMES, exists = existsSync } = {}) {
|
|
59
|
+
const out = []
|
|
60
|
+
for (const dir of String(env.PATH || '').split(delimiter)) {
|
|
61
|
+
if (!dir) continue
|
|
62
|
+
for (const n of names) {
|
|
63
|
+
const p = join(dir, n)
|
|
64
|
+
if (exists(p)) out.push({ path: p, source: `PATH:${n}` })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return out
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 候选解释器(按优先级;同路径去重、保留首次出现的来源标签)。 */
|
|
71
|
+
export function pythonCandidates({ env = process.env, dataDir = '', repo = '' } = {}) {
|
|
72
|
+
const raw = []
|
|
73
|
+
const push = (path, source) => { if (path) raw.push({ path: String(path), source }) }
|
|
74
|
+
push(env.ARGENT_PY, 'ARGENT_PY')
|
|
75
|
+
push(env.ARGENT_PYTHON, 'ARGENT_PYTHON')
|
|
76
|
+
if (dataDir) push(join(dataDir, 'venv', 'bin', 'python3'), 'venv(data_dir)')
|
|
77
|
+
if (repo) push(join(repo, '.venv', 'bin', 'python3'), 'venv(repo)')
|
|
78
|
+
raw.push(...pathCandidates({ env }))
|
|
79
|
+
const seen = new Set()
|
|
80
|
+
return raw.filter((c) => (seen.has(c.path) ? false : (seen.add(c.path), true)))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 探测一个候选:能启动 + 版本达标 + 依赖可 import。
|
|
85
|
+
* 返回 `{ ok, reason, detail, version }` —— 失败原因**分类**("文件不存在"与"解析/依赖失败"不同)。
|
|
86
|
+
*/
|
|
87
|
+
export function probePython(bin, { run = realRun, min = MIN_PYTHON, requires = REQUIRED_MODULES, exists = existsSync } = {}) {
|
|
88
|
+
const p = String(bin || '')
|
|
89
|
+
if (!p) return { ok: false, reason: 'not_found', detail: '未指定' }
|
|
90
|
+
// 绝对/相对路径先看存在性:省一次 spawn,也让诊断更准("不存在" ≠ "启动失败")
|
|
91
|
+
if (isAbsolute(p) && !exists(p)) return { ok: false, reason: 'not_found', detail: p }
|
|
92
|
+
|
|
93
|
+
const v = run(p, ['-c', 'import sys; print("%d.%d.%d" % sys.version_info[:3])'])
|
|
94
|
+
if (v.spawnError) return { ok: false, reason: 'not_executable', detail: v.spawnError }
|
|
95
|
+
if (v.status !== 0) return { ok: false, reason: 'start_failed', detail: (v.out || `exit ${v.status}`).slice(0, 200) }
|
|
96
|
+
const version = parseVersion(v.out)
|
|
97
|
+
if (!version) return { ok: false, reason: 'version_unreadable', detail: v.out.slice(0, 120) }
|
|
98
|
+
if (!versionAtLeast(version, min)) {
|
|
99
|
+
return { ok: false, reason: 'version_too_old', detail: `${version.join('.')} < ${min.join('.')}`, version }
|
|
100
|
+
}
|
|
101
|
+
if ((requires || []).length) {
|
|
102
|
+
const m = run(p, ['-c', `import ${requires.join(', ')}`])
|
|
103
|
+
if (m.status !== 0) {
|
|
104
|
+
const last = (m.out || '').split('\n').filter(Boolean).pop() || `exit ${m.status}`
|
|
105
|
+
return { ok: false, reason: 'missing_modules', detail: `${requires.join('/')}:${last.slice(0, 160)}`, version }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { ok: true, reason: 'ok', detail: '', version }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 全不可用时的诊断(给人/给 agent:候选、拒绝原因、修复指引)。 */
|
|
112
|
+
export function pythonDiagnostics({ tried = [], min = MIN_PYTHON, requires = REQUIRED_MODULES, dataDir = '' } = {}) {
|
|
113
|
+
const lines = [
|
|
114
|
+
`找不到可用的 Python 解释器(要求 ≥ ${min.join('.')} 且能 import ${requires.join(', ')})——已探测 ${tried.length} 个候选:`,
|
|
115
|
+
]
|
|
116
|
+
for (const t of tried) {
|
|
117
|
+
const why = REASON_TEXT[t.reason] || t.reason
|
|
118
|
+
lines.push(` · ${t.source}=${t.path}:${why}${t.detail ? `(${t.detail})` : ''}`)
|
|
119
|
+
}
|
|
120
|
+
lines.push(`修复:① python3 -m venv ${dataDir ? `${dataDir}/venv` : '<数据目录>/venv'} && `
|
|
121
|
+
+ `${dataDir ? `${dataDir}/venv` : '<数据目录>/venv'}/bin/pip install openpyxl pyyaml;`)
|
|
122
|
+
lines.push(` ② 或显式指定:ARGENT_PY=/path/to/python3(须 ≥ ${min.join('.')} 且含 openpyxl + pyyaml)。`)
|
|
123
|
+
return lines.join('\n')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 按顺序探测,返回第一个可用者。
|
|
128
|
+
* @returns {{ok: boolean, path: string, source: string, version: number[]|null,
|
|
129
|
+
* tried: Array<{path: string, source: string, ok: boolean, reason: string, detail: string}>,
|
|
130
|
+
* diagnostics: string}}
|
|
131
|
+
*/
|
|
132
|
+
export function pickPython({ env = process.env, dataDir = '', repo = '',
|
|
133
|
+
candidates = null, run = realRun, min = MIN_PYTHON, requires = REQUIRED_MODULES } = {}) {
|
|
134
|
+
const list = candidates || pythonCandidates({ env, dataDir, repo })
|
|
135
|
+
const tried = []
|
|
136
|
+
for (const c of list) {
|
|
137
|
+
const p = probePython(c.path, { run, min, requires })
|
|
138
|
+
tried.push({ path: c.path, source: c.source, ok: p.ok, reason: p.reason, detail: p.detail, version: p.version || null })
|
|
139
|
+
if (p.ok) return { ok: true, path: c.path, source: c.source, version: p.version, tried, diagnostics: '' }
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
ok: false, path: '', source: '', version: null, tried,
|
|
143
|
+
diagnostics: pythonDiagnostics({ tried, min, requires, dataDir }),
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SOP 解析(0.3 共享实现,2026-09-29;技能判据 2026-10-03 修 D2/D3)—— **CLI 与 MCP 用同一套**,
|
|
3
|
+
* 宿主不必知道文件布局。
|
|
4
|
+
*
|
|
5
|
+
* 提示词选择顺序(实测修正):主提示词优先于失败/分支提示词 —— 曾因模糊匹配选中
|
|
6
|
+
* `daily-report.failure.md`(不是主提示词),会误导宿主。
|
|
7
|
+
*
|
|
8
|
+
* 技能目录(**D2**):只认注册表声明 `flows.<id>.skill`(单一真源,须 ∈ 业务线 manifest#skills)。
|
|
9
|
+
* 此前用 **role** 拼路径(`~/.dsh/skills/<role>`)⇒ 文鳐流程报 `ml-invoice-wy`(角色名)而真实技能
|
|
10
|
+
* 目录是 `ml-invoice` ⇒ 宿主被明确告知"没有踩坑文档",跳过最该读的知识(反向价值)。
|
|
11
|
+
* **没有声明就报错,绝不回退到 role**(角色名 ≠ 技能名,实测就是两个名字)。
|
|
12
|
+
*
|
|
13
|
+
* 失败口径(**D3**):注册表读不到(脚本缺失 / 合并失败 / 解释器不可用)**不再空 catch 吞掉**,
|
|
14
|
+
* 而是分类后 `ok:false` + `code:2`(CLI 非零退出 + 诊断)—— 静默降级会把"解释器不在"伪装成
|
|
15
|
+
* "这台机器上没有流程"。
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
18
|
+
import { join } from 'node:path'
|
|
19
|
+
import { loadRegistry, resolveFlow } from './flow-registry.mjs'
|
|
20
|
+
|
|
21
|
+
/** 数据目录无关的"没声明技能"错误文案(把该改哪一行说清楚)。 */
|
|
22
|
+
function skillNotDeclaredError(canonical, vertical) {
|
|
23
|
+
const where = vertical ? `verticals/${vertical}/flows.json` : 'verticals/<业务线>/flows.json'
|
|
24
|
+
return `流程 ${canonical} 未声明技能:请在 ${where} 的该流程里加 "skill": "<技能名>"`
|
|
25
|
+
+ '(须与 manifest.json#skills 一致,如 "ml-invoice")—— 技能目录的单一真源是这条声明,'
|
|
26
|
+
+ '不用角色名兜底(角色名与技能名是两个名字,实测 ml-invoice-wy ≠ ml-invoice)'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 解析某流程的 SOP(提示词 + 技能目录 + 踩坑清单)。
|
|
31
|
+
*
|
|
32
|
+
* @param {string} flow 流程 id 或别名(旧 id 经注册表别名解析)
|
|
33
|
+
* @param {{repo: string, py: string, pyFailure?: string|null}} opts
|
|
34
|
+
* `py`:**已选定的**解释器(`core/lib/python.mjs#pickPython` 的结果);
|
|
35
|
+
* `pyFailure`:解释器解析失败的诊断文本(给了就直接响亮失败,不再尝试 exec)。
|
|
36
|
+
*/
|
|
37
|
+
export function resolveSop(flow, { repo, py, pyFailure = null } = {}) {
|
|
38
|
+
const flowInput = String(flow || 'full')
|
|
39
|
+
if (pyFailure) {
|
|
40
|
+
return { ok: false, code: 2, flow: flowInput, reason: 'python_unavailable', error: pyFailure }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const reg = loadRegistry({ repo, py })
|
|
44
|
+
if (!reg.ok) {
|
|
45
|
+
return { ok: false, code: 2, flow: flowInput, reason: reg.reason, error: reg.error }
|
|
46
|
+
}
|
|
47
|
+
const doc = reg.doc
|
|
48
|
+
const r = resolveFlow({ input: flowInput, doc })
|
|
49
|
+
if (!r.known) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false, code: 2, flow: r.canonical, reason: 'unknown_flow',
|
|
52
|
+
error: `未知流程 \`${flowInput}\`(规范 id \`${r.canonical}\` 不在注册表里);`
|
|
53
|
+
+ '可用:python3 scripts/flow_registry.py --list',
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const f = doc.flows[r.canonical] || {}
|
|
57
|
+
const key = r.canonical
|
|
58
|
+
const vertical = key.includes('.') ? key.split('.')[0] : ''
|
|
59
|
+
const cap = String(f.capability || '')
|
|
60
|
+
|
|
61
|
+
const dir = join(repo, 'verticals', vertical, 'prompts')
|
|
62
|
+
let names = []
|
|
63
|
+
try { names = readdirSync(dir) } catch { /* 该业务线还没有提示词目录 ⇒ 下面的 pick 全部未命中,走 availablePrompts */ }
|
|
64
|
+
// 匹配顺序(每个候选:<x>.md > <x>.*.md(排除 .failure)> <x>*):
|
|
65
|
+
// 能力 → 流程局部 id → 角色。**找不到就明确未命中**(此前会回退成目录第一个文件,
|
|
66
|
+
// 实测把 `daily-report.failure.md` 当成了文鳐发票的 SOP —— 误导宿主,必须禁止)。
|
|
67
|
+
const pick = (x) => {
|
|
68
|
+
if (!x) return ''
|
|
69
|
+
return names.find((n) => n === `${x}.md`)
|
|
70
|
+
|| names.find((n) => n.startsWith(`${x}.`) && n.endsWith('.md') && !n.includes('failure'))
|
|
71
|
+
|| names.find((n) => n.startsWith(x))
|
|
72
|
+
|| ''
|
|
73
|
+
}
|
|
74
|
+
const chosen = pick(cap) || pick(r.local) || pick(String(f.role || '')) || ''
|
|
75
|
+
const matchedBy = pick(cap) ? 'capability' : (pick(r.local) ? 'localId' : (pick(String(f.role || '')) ? 'role' : 'none'))
|
|
76
|
+
const promptPath = chosen ? join(dir, chosen) : ''
|
|
77
|
+
const promptTail = promptPath && existsSync(promptPath) ? readFileSync(promptPath, 'utf8').slice(-1200) : ''
|
|
78
|
+
|
|
79
|
+
// ── 技能目录(D2):**只认声明** ─────────────────────────────────────────
|
|
80
|
+
const skill = String(f.skill || '')
|
|
81
|
+
if (!skill) {
|
|
82
|
+
return {
|
|
83
|
+
ok: false, code: 2, flow: key, role: String(f.role || ''), capability: cap, vertical,
|
|
84
|
+
reason: 'skill_not_declared', error: skillNotDeclaredError(key, vertical),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const home = process.env.HOME || ''
|
|
88
|
+
// 候选:① 已安装(宿主技能目录,agent 真正加载的那份)② 仓库 L0 源(没装宿主也要能给出判据)。
|
|
89
|
+
const skillCands = []
|
|
90
|
+
if (home) skillCands.push({ dir: join(home, '.dsh', 'skills', skill), source: 'installed' })
|
|
91
|
+
skillCands.push({ dir: join(repo, 'skills', skill), source: 'repo' })
|
|
92
|
+
const hasSkillMd = (c) => existsSync(join(c.dir, 'SKILL.md'))
|
|
93
|
+
const hit = skillCands.find(hasSkillMd) || skillCands.find((c) => existsSync(c.dir)) || null
|
|
94
|
+
const shown = (c) => (c.source === 'repo'
|
|
95
|
+
? join('skills', skill)
|
|
96
|
+
: (home && c.dir.startsWith(home) ? `~${c.dir.slice(home.length)}` : c.dir))
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
flow: key,
|
|
101
|
+
role: String(f.role || ''),
|
|
102
|
+
capability: cap,
|
|
103
|
+
vertical,
|
|
104
|
+
matchedBy,
|
|
105
|
+
availablePrompts: chosen ? undefined : names,
|
|
106
|
+
note: chosen ? undefined : 'no_prompt_for_capability:该能力在业务线 prompts 里没有对应提示词(未回退到无关文件)',
|
|
107
|
+
promptPath: promptPath.replace(`${repo}/`, ''),
|
|
108
|
+
promptTail,
|
|
109
|
+
skill,
|
|
110
|
+
skillDir: shown(hit || skillCands[0]),
|
|
111
|
+
skillDirSource: hit ? hit.source : null,
|
|
112
|
+
skillDirResolved: Boolean(hit),
|
|
113
|
+
skillDirCandidates: skillCands.map(shown),
|
|
114
|
+
pitfalls: hit ? existsSync(join(hit.dir, 'references', 'pitfalls.md')) : false,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `argent versions` —— **版本对账**(P1;策划案⑮、M2 判据③:`versions` 对账无差异)。只读。
|
|
3
|
+
*
|
|
4
|
+
* 回答一个问题:**"这台机器上跑的到底是哪一版"** —— 一次把五个面摊开,且每一面都点名它的来源文件:
|
|
5
|
+
*
|
|
6
|
+
* | 面 | 来源(唯一真源) | 为什么是它 |
|
|
7
|
+
* |---|---|---|
|
|
8
|
+
* | `cli.argent` | 仓库 `VERSION` | 版本线的唯一出口(`scripts/tests/argent-contracts.test.mjs` 已钉住它与契约一致) |
|
|
9
|
+
* | `contracts` | `core/contracts/{argent.contracts.json,verbs.schema.json}` 的 `argentVersion` + sha256(12) | 宿主只依赖契约 ⇒ 契约的**逐字节身份**必须可核("读到的契约是哪一份") |
|
|
10
|
+
* | `kernel` | `core/**` 的 `路径:sha12` 排序后取 sha256(**整树指纹**) | 内核是"换个宿主也不变"的那层 ⇒ 它的身份 = 整棵树的摘要,而不是某个文件 |
|
|
11
|
+
* | `capabilities` | 注册表 `capabilities.<c>.pack.version`(走 `scripts/flow_registry.py`,**唯一真源**) | 能力包版本被 `scripts/capability_pack.py --validate` 与锁文件绑死 |
|
|
12
|
+
* | `patches` | `scripts/patches/*.patch` 的清单(**宿主档事实**) | 判断"补丁在不在位"是**宿主侧检查**的职责;本 verb 只如实列清单,缺 dsh/缺目录 **不算 FAIL** |
|
|
13
|
+
* | `host.dsh` | `dsh --version`(可选探测,有则报、无则空串) | 无宿主是**设计**(§4.2 M1 判据③) |
|
|
14
|
+
*
|
|
15
|
+
* ## A1/A2
|
|
16
|
+
* · `0` 对账完成(**缺 dsh / 缺补丁目录都不算失败** —— 它们是事实)。
|
|
17
|
+
* · `2` **解释器不可用** ⇒ `python_unavailable`:能力包版本读不到时**不能**给"没有差异"的假绿
|
|
18
|
+
* (D3 的口径:不得静默 0);此时 JSON 仍给出能读到的那几面。
|
|
19
|
+
* · 回显实际生效的过滤条件(`--only`),空结果不可能与"没查"混淆。
|
|
20
|
+
*
|
|
21
|
+
* 纪律:本文件**不重新实现**任何合并/版本推导 —— 注册表走 `core/lib/flow-registry.mjs`,
|
|
22
|
+
* 解释器走 `core/lib/python.mjs`,数据目录走 `core/lib/argent-datadir.mjs`。
|
|
23
|
+
*/
|
|
24
|
+
import { execFileSync } from 'node:child_process'
|
|
25
|
+
import { createHash } from 'node:crypto'
|
|
26
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
27
|
+
import { join, relative, sep } from 'node:path'
|
|
28
|
+
import { resolveDataDir } from './argent-datadir.mjs'
|
|
29
|
+
import { pickPython } from './python.mjs'
|
|
30
|
+
import { loadRegistry } from './flow-registry.mjs'
|
|
31
|
+
|
|
32
|
+
/** 参与对账的面(`--only` 的取值域)。 */
|
|
33
|
+
export const VERSION_FACES = ['cli', 'contracts', 'kernel', 'capabilities', 'patches', 'host']
|
|
34
|
+
|
|
35
|
+
const toPosix = (p) => p.split(sep).join('/')
|
|
36
|
+
const sha12 = (buf) => createHash('sha256').update(buf).digest('hex').slice(0, 12)
|
|
37
|
+
|
|
38
|
+
/** 递归列文件(跳过 `.DS_Store`;**不**跳过 `node_modules` 之外的任何东西 —— core/ 下不该有)。 */
|
|
39
|
+
function walk(dir, base = dir, out = []) {
|
|
40
|
+
let entries = []
|
|
41
|
+
try { entries = readdirSync(dir, { withFileTypes: true }) } catch { return out }
|
|
42
|
+
for (const e of entries) {
|
|
43
|
+
if (e.name === '.DS_Store' || e.name === '.git') continue
|
|
44
|
+
const p = join(dir, e.name)
|
|
45
|
+
let st = null
|
|
46
|
+
try { st = statSync(p) } catch { continue }
|
|
47
|
+
if (st.isDirectory()) walk(p, base, out)
|
|
48
|
+
else if (st.isFile()) out.push(toPosix(relative(base, p)))
|
|
49
|
+
}
|
|
50
|
+
return out
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 内核整树指纹:`路径:sha12` 排序后求 sha256(与 `capability_pack.py#pack_digest` 同形态)。
|
|
55
|
+
* 为什么不是"版本号":`core/` 没有独立版本号,而它每天都在变 ⇒ 只有内容摘要能回答
|
|
56
|
+
* "客户机上的 core 与仓库那一份是不是同一棵"。
|
|
57
|
+
*/
|
|
58
|
+
export function kernelFingerprint(repo) {
|
|
59
|
+
const dir = join(repo, 'core')
|
|
60
|
+
if (!existsSync(dir)) return { ok: false, reason: 'core_missing', files: 0, digest: null, note: `仓库缺 core/:${dir}` }
|
|
61
|
+
const files = walk(dir).sort()
|
|
62
|
+
const lines = files.map((rel) => `${rel}:${sha12(readFileSync(join(dir, rel)))}`)
|
|
63
|
+
return {
|
|
64
|
+
ok: true, files: files.length,
|
|
65
|
+
digest: createHash('sha256').update(lines.join('\n')).digest('hex'),
|
|
66
|
+
digestShort: createHash('sha256').update(lines.join('\n')).digest('hex').slice(0, 16),
|
|
67
|
+
cliEntry: files.includes('cli/argent.mjs'),
|
|
68
|
+
mcpEntry: files.includes('mcp/server.mjs'),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 契约面:版本号 + 两份契约文件的 sha12("读到的契约是哪一份")。 */
|
|
73
|
+
export function contractsProfile(repo) {
|
|
74
|
+
const out = { dir: 'core/contracts', argentVersion: null, files: {}, ok: false, error: null }
|
|
75
|
+
for (const f of ['argent.contracts.json', 'verbs.schema.json']) {
|
|
76
|
+
const p = join(repo, 'core', 'contracts', f)
|
|
77
|
+
if (!existsSync(p)) { out.error = `契约文件缺失:core/contracts/${f}`; continue }
|
|
78
|
+
const buf = readFileSync(p)
|
|
79
|
+
out.files[f] = { bytes: buf.length, sha256_12: sha12(buf) }
|
|
80
|
+
if (f === 'argent.contracts.json') {
|
|
81
|
+
try { out.argentVersion = JSON.parse(buf.toString('utf8')).argentVersion || null } catch (e) { out.error = `argent.contracts.json 不可解析:${e.message}` }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
out.ok = out.error === null && Object.keys(out.files).length === 2
|
|
85
|
+
return out
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** CLI 面:仓库 `VERSION`。 */
|
|
89
|
+
export function cliProfile(repo) {
|
|
90
|
+
const p = join(repo, 'VERSION')
|
|
91
|
+
if (!existsSync(p)) return { argent: null, file: 'VERSION', error: '缺 VERSION 文件(仓库不完整)' }
|
|
92
|
+
return { argent: readFileSync(p, 'utf8').trim(), file: 'VERSION' }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 宿主补丁面(**只列清单**;在不在位由宿主侧检查判)。 */
|
|
96
|
+
export function patchesProfile(repo) {
|
|
97
|
+
const dir = join(repo, 'scripts', 'patches')
|
|
98
|
+
if (!existsSync(dir)) return { dir: 'scripts/patches', present: false, count: 0, patches: [], note: '缺补丁目录(非 dsh 交付形态?)—— 缺它**不算 FAIL**' }
|
|
99
|
+
const patches = readdirSync(dir).filter((f) => f.endsWith('.patch')).sort()
|
|
100
|
+
const verifiers = readdirSync(dir).filter((f) => /^verify-.*\.mjs$/.test(f)).sort()
|
|
101
|
+
return {
|
|
102
|
+
dir: 'scripts/patches', present: true, count: patches.length, patches, verifiers,
|
|
103
|
+
note: '**宿主档事实**:本 verb 只列清单;"补丁在不在位"的权威判据在宿主侧检查'
|
|
104
|
+
+ '(`node scripts/dsh-upgrade.mjs --patches-only`),本 verb 不重算。',
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** 能力包面(走注册表 = 唯一真源;解释器不可用 ⇒ 如实标 unavailable)。 */
|
|
109
|
+
export function capabilitiesProfile({ repo, py, pyFailure }) {
|
|
110
|
+
if (pyFailure || !py) return { ok: false, reason: 'python_unavailable', error: pyFailure || '解释器不可用', capabilities: {} }
|
|
111
|
+
const reg = loadRegistry({ repo, py })
|
|
112
|
+
if (!reg.ok) return { ok: false, reason: reg.reason, error: reg.error, capabilities: {} }
|
|
113
|
+
const caps = (reg.doc && reg.doc.capabilities) || {}
|
|
114
|
+
const out = {}
|
|
115
|
+
for (const [id, c] of Object.entries(caps)) {
|
|
116
|
+
if (id.startsWith('_')) continue
|
|
117
|
+
out[id] = { version: (c.pack && c.pack.version) || null, minPlan: c.minPlan || null, files: (c.pack && c.pack.scripts ? c.pack.scripts.length : 0) }
|
|
118
|
+
}
|
|
119
|
+
return { ok: true, reason: null, error: null, capabilities: out, source: 'scripts/flow_registry.py(合并后)' }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* verb 入口:`argent versions [--only <face>]… [--json]`。
|
|
124
|
+
* @param {string[]} argv
|
|
125
|
+
* @param {{repo?:string, env?:object, deps?:object}} [deps]
|
|
126
|
+
*/
|
|
127
|
+
export function runVersionsVerb(argv = [], deps = {}) {
|
|
128
|
+
const only = []
|
|
129
|
+
for (let i = 0; i < argv.length; i++) {
|
|
130
|
+
const t = argv[i]
|
|
131
|
+
if (t === '--json') continue
|
|
132
|
+
if (t === '--only') {
|
|
133
|
+
const v = argv[i + 1]
|
|
134
|
+
if (v === undefined || String(v).startsWith('--')) return versionsFail(2, 'usage_error', '--only 缺值')
|
|
135
|
+
if (!VERSION_FACES.includes(v)) return versionsFail(2, 'usage_error', `--only 取值非法:${v}(可用:${VERSION_FACES.join('、')})`)
|
|
136
|
+
only.push(v); i++
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
if (t === '--help' || t === '-h') return { json: { ok: true, empty: false, code: 0, reason: null, verb: 'versions', usage: VERSIONS_USAGE }, human: VERSIONS_USAGE, stderr: '', exit: 0 }
|
|
140
|
+
return versionsFail(2, 'usage_error', `未知参数:${t}(用法:argent versions [--only <${VERSION_FACES.join('|')}>]… [--json])`)
|
|
141
|
+
}
|
|
142
|
+
const env = deps.env || process.env
|
|
143
|
+
const repo = deps.repo || ''
|
|
144
|
+
const wanted = only.length ? only : VERSION_FACES
|
|
145
|
+
const has = (f) => wanted.includes(f)
|
|
146
|
+
|
|
147
|
+
const dataDir = resolveDataDir({ env })
|
|
148
|
+
const pyProbe = (has('capabilities') ? pickPython({ env, dataDir: dataDir.dir, repo }) : { ok: false, path: '', diagnostics: '未请求该面(--only)' })
|
|
149
|
+
const caps = has('capabilities') ? capabilitiesProfile({ repo, py: pyProbe.ok ? pyProbe.path : '', pyFailure: pyProbe.ok ? null : pyProbe.diagnostics }) : null
|
|
150
|
+
|
|
151
|
+
const json = {
|
|
152
|
+
ok: true, empty: false, code: 0, reason: null, verb: 'versions',
|
|
153
|
+
only: only.length ? only : null,
|
|
154
|
+
dataDir: { dir: dataDir.dir, source: dataDir.source, isolated: dataDir.isolated },
|
|
155
|
+
cli: has('cli') ? cliProfile(repo) : null,
|
|
156
|
+
contracts: has('contracts') ? contractsProfile(repo) : null,
|
|
157
|
+
kernel: has('kernel') ? kernelFingerprint(repo) : null,
|
|
158
|
+
capabilities: caps,
|
|
159
|
+
patches: has('patches') ? patchesProfile(repo) : null,
|
|
160
|
+
host: has('host') ? {
|
|
161
|
+
dsh: deps.dshVersion !== undefined ? deps.dshVersion : probeDsh(),
|
|
162
|
+
note: '缺 dsh = 设计(陪跑式档位),不是 FAIL',
|
|
163
|
+
} : null,
|
|
164
|
+
interpreter: { ok: pyProbe.ok, path: pyProbe.ok ? pyProbe.path : null, version: pyProbe.version || null },
|
|
165
|
+
note: '只读对账:每一面都点名来源文件;"补丁在不在位"与"定时器在不在册"是宿主档事实,权威判据在宿主侧检查。',
|
|
166
|
+
}
|
|
167
|
+
// A1:能力包版本读不到 ⇒ **不得**给"没有差异"的假绿(但保留已能读到的面)
|
|
168
|
+
if (caps && !caps.ok) {
|
|
169
|
+
json.ok = false
|
|
170
|
+
json.code = 2
|
|
171
|
+
json.reason = caps.reason === 'python_unavailable' ? 'python_unavailable' : (caps.reason || 'internal_error')
|
|
172
|
+
json.error = `能力包版本读不到:${caps.error}`
|
|
173
|
+
json.note = '解释器/注册表不可用 ⇒ 能力包面**未对账**(这不是"无差异",是"没查成");其余面照常给出。'
|
|
174
|
+
}
|
|
175
|
+
const human = [
|
|
176
|
+
`🧾 Argent 版本对账(${json.ok ? '完成' : `未完成:${json.reason}`})`,
|
|
177
|
+
json.cli ? ` CLI ${json.cli.argent || '(缺 VERSION)'}` : null,
|
|
178
|
+
json.contracts ? ` 契约 argentVersion=${json.contracts.argentVersion} | ${Object.entries(json.contracts.files).map(([f, v]) => `${f} sha256:${v.sha256_12}`).join(' | ')}` : null,
|
|
179
|
+
json.kernel ? ` 内核 core/ ${json.kernel.files} 文件 | 整树 sha256:${json.kernel.digestShort || '—'}` : null,
|
|
180
|
+
json.capabilities ? ` 能力包 ${Object.entries(json.capabilities.capabilities).map(([k, v]) => `${k}@${v.version}`).join(' | ') || `(未对账:${json.capabilities.error})`}` : null,
|
|
181
|
+
json.patches ? ` 宿主补丁 ${json.patches.count} 个${json.patches.present ? '' : '(缺目录)'}(只列清单;在不在位由宿主侧检查判)` : null,
|
|
182
|
+
json.host ? ` 宿主 dsh=${json.host.dsh || '(不在 PATH)'}` : null,
|
|
183
|
+
` 数据目录 ${json.dataDir.dir}(来源=${json.dataDir.source}${json.dataDir.isolated ? ',隔离' : ''})`,
|
|
184
|
+
json.error ? ` ❌ ${json.error}` : null,
|
|
185
|
+
].filter(Boolean).join('\n')
|
|
186
|
+
return { json, human, stderr: '', exit: Number(json.code) }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** `dsh --version`(可选探测;宿主缺失 ⇒ 空串而非报错,§P1 验收判据)。 */
|
|
190
|
+
export function probeDsh() {
|
|
191
|
+
// 与 `core/cli/argent.mjs#version` 同口径的**可选**探测:有则报、无则空串(裸 `dsh` 不算宿主依赖)
|
|
192
|
+
try { return String(execFileSync('dsh', ['--version'], { encoding: 'utf8', timeout: 15000 })).trim() } catch { return '' }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function versionsFail(code, reason, error, echo = {}) {
|
|
196
|
+
return { json: { ok: false, empty: false, code, reason, error, verb: 'versions', ...echo }, human: `❌ ${reason}:${error}`, stderr: '', exit: code }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export const VERSIONS_USAGE = [
|
|
200
|
+
'用法:argent versions [--only <face>]… [--json]',
|
|
201
|
+
`面:${VERSION_FACES.join('、')}`,
|
|
202
|
+
'',
|
|
203
|
+
'只读对账:CLI(VERSION) / 契约(版本号+sha) / 内核(core 整树 sha) / 能力包版本 / 宿主补丁清单 / 宿主版本。',
|
|
204
|
+
'⛔ 缺 dsh 或缺补丁目录**不算 FAIL**;解释器不可用 ⇒ exit 2 + python_unavailable(能力包面未对账,不是"无差异")。',
|
|
205
|
+
].join('\n')
|