@whyshu/argent-engine 0.0.0-stage → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.json +307 -0
- package/engine/VERSION +1 -0
- package/engine/agents/catalog.json +454 -0
- package/engine/configs/_aliases.proposed.json +651 -0
- package/engine/configs/_migration-plan.proposed.json +899 -0
- package/engine/configs/account.json +272 -0
- package/engine/configs/advanced.json +205 -0
- package/engine/configs/host.json +149 -0
- package/engine/configs/hr.json +27 -0
- package/engine/configs/notify.json +264 -0
- package/engine/core/cli/argent.mjs +277 -0
- package/engine/core/contracts/argent.contracts.json +1065 -0
- package/engine/core/contracts/verbs.schema.json +2539 -0
- package/engine/core/lib/argent-datadir.mjs +223 -0
- package/engine/core/lib/audit.mjs +187 -0
- package/engine/core/lib/claim.mjs +258 -0
- package/engine/core/lib/config-catalog.mjs +1172 -0
- package/engine/core/lib/doctor-customer.mjs +577 -0
- package/engine/core/lib/flow-list.mjs +138 -0
- package/engine/core/lib/flow-registry.mjs +48 -0
- package/engine/core/lib/init.mjs +247 -0
- package/engine/core/lib/ledger.mjs +124 -0
- package/engine/core/lib/license-activate.mjs +813 -0
- package/engine/core/lib/license-device.mjs +314 -0
- package/engine/core/lib/license-file.mjs +349 -0
- package/engine/core/lib/license-guard.mjs +123 -0
- package/engine/core/lib/license-ops.mjs +1645 -0
- package/engine/core/lib/license-verdict.mjs +133 -0
- package/engine/core/lib/license.mjs +577 -0
- package/engine/core/lib/notify/dedup.mjs +101 -0
- package/engine/core/lib/notify/feishu.mjs +38 -0
- package/engine/core/lib/notify/index.mjs +95 -0
- package/engine/core/lib/notify/verb.mjs +327 -0
- package/engine/core/lib/notify/webhook.mjs +135 -0
- package/engine/core/lib/notify/wecom.mjs +36 -0
- package/engine/core/lib/python.mjs +145 -0
- package/engine/core/lib/sop.mjs +116 -0
- package/engine/core/lib/versions.mjs +205 -0
- package/engine/core/mcp/client.mjs +67 -0
- package/engine/core/mcp/http.mjs +138 -0
- package/engine/core/mcp/server.mjs +702 -0
- package/engine/scripts/_engine_rules_probe.py +15 -0
- package/engine/scripts/analyze-full-day.py +568 -0
- package/engine/scripts/archive/feishu-bridge.js +246 -0
- package/engine/scripts/argent-agents.mjs +527 -0
- package/engine/scripts/argent-capabilities.mjs +776 -0
- package/engine/scripts/argent-catalog.mjs +186 -0
- package/engine/scripts/argent-datadir.py +62 -0
- package/engine/scripts/argent-doctor.mjs +624 -0
- package/engine/scripts/argent-env-write.mjs +302 -0
- package/engine/scripts/argent-schedule.mjs +620 -0
- package/engine/scripts/argent-setup-check.mjs +369 -0
- package/engine/scripts/argent-upgrade.mjs +1046 -0
- package/engine/scripts/base-bootstrap.sh +575 -0
- package/engine/scripts/billing-mode-daily.sh +12 -0
- package/engine/scripts/build-engine-package.mjs +123 -0
- package/engine/scripts/capability-packs.lock.json +484 -0
- package/engine/scripts/capability_pack.py +1242 -0
- package/engine/scripts/check-1.0-criteria.mjs +355 -0
- package/engine/scripts/check-agent-catalog.mjs +141 -0
- package/engine/scripts/check-billing-mode.sh +377 -0
- package/engine/scripts/check-child-sessions.mjs +83 -0
- package/engine/scripts/check-config-catalog.mjs +677 -0
- package/engine/scripts/check-docs-drift.mjs +522 -0
- package/engine/scripts/check-dsh-web-expose.sh +98 -0
- package/engine/scripts/check-judgment-drift.mjs +403 -0
- package/engine/scripts/check-license.mjs +199 -0
- package/engine/scripts/check-model-auth.py +443 -0
- package/engine/scripts/check-pack-closure.mjs +661 -0
- package/engine/scripts/check-portable-symbols.mjs +116 -0
- package/engine/scripts/check-preset-landing.mjs +124 -0
- package/engine/scripts/check-production-home.mjs +602 -0
- package/engine/scripts/check-role-mapping.mjs +461 -0
- package/engine/scripts/check-sample-redaction.mjs +320 -0
- package/engine/scripts/check-skill-classification.mjs +188 -0
- package/engine/scripts/check-sop-consistency.mjs +431 -0
- package/engine/scripts/check-test-env-safety.mjs +627 -0
- package/engine/scripts/check-version-consistency.mjs +452 -0
- package/engine/scripts/ci-run.sh +558 -0
- package/engine/scripts/config-explain.mjs +278 -0
- package/engine/scripts/configure-provider.rb +29 -0
- package/engine/scripts/customer-config-migrate.mjs +179 -0
- package/engine/scripts/customer-preflight.py +892 -0
- package/engine/scripts/customer-readiness.sh +134 -0
- package/engine/scripts/daily-report-inbox.py +242 -0
- package/engine/scripts/daily-report-reconcile.mjs +312 -0
- package/engine/scripts/daily-report.mjs +1310 -0
- package/engine/scripts/daily-report.sh +46 -0
- package/engine/scripts/data/lip-product-dict.json +306 -0
- package/engine/scripts/deliver-customer.mjs +932 -0
- package/engine/scripts/delivery-faces.txt +31 -0
- package/engine/scripts/desktop-trial.sh +146 -0
- package/engine/scripts/dev-window.sh +260 -0
- package/engine/scripts/drill-clean-machine.sh +315 -0
- package/engine/scripts/dsh-api-client.mjs +215 -0
- package/engine/scripts/dsh-backup-scope.sh +83 -0
- package/engine/scripts/dsh-offline-pack.mjs +77 -0
- package/engine/scripts/dsh-overlay-pin.sh +87 -0
- package/engine/scripts/dsh-plane.mjs +153 -0
- package/engine/scripts/dsh-portforward.py +223 -0
- package/engine/scripts/dsh-preflight.mjs +179 -0
- package/engine/scripts/dsh-release-watch-daily.sh +19 -0
- package/engine/scripts/dsh-release-watch.mjs +458 -0
- package/engine/scripts/dsh-remote-check.sh +99 -0
- package/engine/scripts/dsh-restart.sh +179 -0
- package/engine/scripts/dsh-restore.sh +111 -0
- package/engine/scripts/dsh-scope-rewire.mjs +803 -0
- package/engine/scripts/dsh-snapshot.mjs +116 -0
- package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
- package/engine/scripts/dsh-upgrade.mjs +612 -0
- package/engine/scripts/dsh-url.sh +86 -0
- package/engine/scripts/dsh-web-launch.mjs +155 -0
- package/engine/scripts/ensure-store-windows.py +509 -0
- package/engine/scripts/entitlements.py +458 -0
- package/engine/scripts/env_probe.py +411 -0
- package/engine/scripts/feishu-card.mjs +252 -0
- package/engine/scripts/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +720 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +86 -0
- package/engine/scripts/flow-timers.sh +182 -0
- package/engine/scripts/flow-trigger-wake.mjs +284 -0
- package/engine/scripts/flow-trigger.sh +571 -0
- package/engine/scripts/flow_billing.py +354 -0
- package/engine/scripts/flow_capability_stats.py +314 -0
- package/engine/scripts/flow_case.py +194 -0
- package/engine/scripts/flow_claim.py +497 -0
- package/engine/scripts/flow_config.py +1024 -0
- package/engine/scripts/flow_failure.py +588 -0
- package/engine/scripts/flow_learn.py +105 -0
- package/engine/scripts/flow_ledger.py +235 -0
- package/engine/scripts/flow_policy.py +238 -0
- package/engine/scripts/flow_preflight.py +339 -0
- package/engine/scripts/flow_registry.py +341 -0
- package/engine/scripts/flow_retry.py +304 -0
- package/engine/scripts/flow_return_to_ops.py +191 -0
- package/engine/scripts/flow_source.py +315 -0
- package/engine/scripts/flow_sources/__init__.py +53 -0
- package/engine/scripts/flow_sources/cases_json.py +106 -0
- package/engine/scripts/flow_sources/common.py +428 -0
- package/engine/scripts/flow_sources/feishu_base.py +273 -0
- package/engine/scripts/flow_stale.py +185 -0
- package/engine/scripts/flow_status_reconcile.py +344 -0
- package/engine/scripts/flow_triage.py +849 -0
- package/engine/scripts/flow_write_verify.py +496 -0
- package/engine/scripts/fulfillment.js +586 -0
- package/engine/scripts/gen-agent-bundles.mjs +276 -0
- package/engine/scripts/gen-preset-rows.mjs +259 -0
- package/engine/scripts/init-ml.sh +2275 -0
- package/engine/scripts/install-internal-role.sh +190 -0
- package/engine/scripts/install-license.mjs +641 -0
- package/engine/scripts/install-one-shot.mjs +665 -0
- package/engine/scripts/integration_contract.py +220 -0
- package/engine/scripts/integrations/wenyao/adapter.py +98 -0
- package/engine/scripts/integrations/xzh/adapter.py +75 -0
- package/engine/scripts/invoice-mapping.example.json +40 -0
- package/engine/scripts/invoice-table.sh +194 -0
- package/engine/scripts/invoice_carrier_notice.py +279 -0
- package/engine/scripts/invoice_fail_closeout.py +297 -0
- package/engine/scripts/invoice_lip_runner.py +1508 -0
- package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
- package/engine/scripts/invoice_ready_audit.py +191 -0
- package/engine/scripts/invoice_runner.py +675 -0
- package/engine/scripts/invoice_stageb.py +516 -0
- package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
- package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
- package/engine/scripts/lease.py +377 -0
- package/engine/scripts/lib/agent-install.mjs +649 -0
- package/engine/scripts/lib/argent-datadir.mjs +16 -0
- package/engine/scripts/lib/argent_datadir.py +262 -0
- package/engine/scripts/lib/argent_filelock.py +507 -0
- package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
- package/engine/scripts/lib/cdp_ports.py +197 -0
- package/engine/scripts/lib/customer-env.mjs +102 -0
- package/engine/scripts/lib/daily-report-core.mjs +517 -0
- package/engine/scripts/lib/dep-declaration.mjs +81 -0
- package/engine/scripts/lib/deps-migrate.mjs +310 -0
- package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
- package/engine/scripts/lib/dsh-scope.mjs +500 -0
- package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
- package/engine/scripts/lib/dsh-versions.mjs +96 -0
- package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
- package/engine/scripts/lib/is-direct-run.mjs +61 -0
- package/engine/scripts/lib/patch-compose.mjs +241 -0
- package/engine/scripts/lib/patch-target.mjs +35 -0
- package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
- package/engine/scripts/lib/plugin-packages.mjs +332 -0
- package/engine/scripts/lib/preset-compat.mjs +127 -0
- package/engine/scripts/lib/preset-install.mjs +575 -0
- package/engine/scripts/lib/preset-mechanism.mjs +56 -0
- package/engine/scripts/lib/preset-sync.mjs +59 -0
- package/engine/scripts/lib/profile-retire.mjs +97 -0
- package/engine/scripts/lib/python-runtime.mjs +190 -0
- package/engine/scripts/lib/registry.mjs +72 -0
- package/engine/scripts/lib/repo-root.sh +56 -0
- package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
- package/engine/scripts/lib/schedule-probe.mjs +835 -0
- package/engine/scripts/lib/scope-pointer.mjs +164 -0
- package/engine/scripts/lib/settings-panels.mjs +816 -0
- package/engine/scripts/lib/stdio_utf8.py +86 -0
- package/engine/scripts/lib/wake-auth.mjs +392 -0
- package/engine/scripts/lib/webdriver_ipc.py +264 -0
- package/engine/scripts/lib/zstd-frames.mjs +104 -0
- package/engine/scripts/license-e2e-drill.mjs +426 -0
- package/engine/scripts/license-gate.mjs +245 -0
- package/engine/scripts/lip_backlog.py +171 -0
- package/engine/scripts/lip_table.py +185 -0
- package/engine/scripts/local-usage.mjs +214 -0
- package/engine/scripts/migrate-providers.mjs +127 -0
- package/engine/scripts/migrate-skills.sh +65 -0
- package/engine/scripts/ml-oauth-setup.mjs +550 -0
- package/engine/scripts/ml-store-ids.json +26 -0
- package/engine/scripts/ml_artifact_attribution.py +333 -0
- package/engine/scripts/ml_capacity_probe.py +1158 -0
- package/engine/scripts/ml_hub_status.py +1556 -0
- package/engine/scripts/ml_schema_check.py +115 -0
- package/engine/scripts/ml_store_ids.py +139 -0
- package/engine/scripts/npm-publish-argent.mjs +499 -0
- package/engine/scripts/npm-publish-argent.sh +20 -0
- package/engine/scripts/orchestrator.py +13811 -0
- package/engine/scripts/pack-plugins.mjs +140 -0
- package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
- package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
- package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
- package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
- package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
- package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
- package/engine/scripts/patches/verify-hardening.mjs +167 -0
- package/engine/scripts/patches/verify-session-migration.mjs +149 -0
- package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
- package/engine/scripts/plan-role-rename.mjs +125 -0
- package/engine/scripts/plugin-bootcheck.mjs +451 -0
- package/engine/scripts/plugin-composition.json +15 -0
- package/engine/scripts/poc-mcp-host.mjs +455 -0
- package/engine/scripts/poll-fulfillment.sh +293 -0
- package/engine/scripts/poll-invoice.sh +74 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +70 -0
- package/engine/scripts/provider-switch.sh +146 -0
- package/engine/scripts/provider-sync-target.check.mjs +64 -0
- package/engine/scripts/python-runtime.sh +78 -0
- package/engine/scripts/query_runner.py +310 -0
- package/engine/scripts/release-argent.sh +574 -0
- package/engine/scripts/repair-unresolved-tools.mjs +178 -0
- package/engine/scripts/run-tests.sh +256 -0
- package/engine/scripts/self-sign-license.mjs +179 -0
- package/engine/scripts/session-health-scan.mjs +63 -0
- package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
- package/engine/scripts/setup.sh +22 -0
- package/engine/scripts/skill-install.mjs +629 -0
- package/engine/scripts/sku_translate.py +148 -0
- package/engine/scripts/sync-plugins.sh +90 -0
- package/engine/scripts/sync-presets.sh +27 -0
- package/engine/scripts/sync-scripts.sh +445 -0
- package/engine/scripts/telemetry.py +873 -0
- package/engine/scripts/ui-copy-count.mjs +220 -0
- package/engine/scripts/usage-anchor.sh +136 -0
- package/engine/scripts/usage_report.py +531 -0
- package/engine/scripts/validate-preset-schema.mjs +208 -0
- package/engine/scripts/vertical-init.mjs +323 -0
- package/engine/scripts/vertical-lint.mjs +482 -0
- package/engine/scripts/wenyao-browser.sh +73 -0
- package/engine/scripts/wenyao-fixture-export.py +116 -0
- package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
- package/engine/scripts/wenyao-login.sh +34 -0
- package/engine/scripts/wenyao-template-map.json +292 -0
- package/engine/scripts/wenyao_attach.py +131 -0
- package/engine/scripts/wenyao_download_template.py +144 -0
- package/engine/scripts/wenyao_fill_template.py +568 -0
- package/engine/scripts/wenyao_login.py +145 -0
- package/engine/scripts/wenyao_portal.py +633 -0
- package/engine/scripts/wenyao_recon.py +230 -0
- package/engine/scripts/wenyao_run.py +503 -0
- package/engine/scripts/wenyao_shadow_diff.py +160 -0
- package/engine/scripts/wenyao_skill_contract.py +156 -0
- package/engine/scripts/wenyao_template_fetch.py +175 -0
- package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
- package/engine/scripts/ziniao-auth-probe.sh +268 -0
- package/engine/scripts/ziniao-keepalive.sh +216 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
- package/engine/scripts/ziniao-windows.sh +153 -0
- package/engine/version.mjs +1 -0
- package/engine/verticals/README.md +103 -0
- package/engine/verticals/_engine-words.baseline.txt +237 -0
- package/engine/verticals/_engine-words.json +19 -0
- package/engine/verticals/mercadolibre/README.md +43 -0
- package/engine/verticals/mercadolibre/config.json +709 -0
- package/engine/verticals/mercadolibre/flows.json +706 -0
- package/engine/verticals/mercadolibre/manifest.json +538 -0
- package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
- package/engine/verticals/mercadolibre/preflight.py +143 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
- package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
- package/engine/verticals/mercadolibre/prompts/full.md +91 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
- package/package.json +21 -4
- package/README.md +0 -3
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argent 数据目录 —— **唯一判据**(三级解析链 + 隔离安全阀)。
|
|
3
|
+
*
|
|
4
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* 一、为什么要有这个文件(2026-10-01 桌面端隔离事故)
|
|
6
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
7
|
+
* 官方桌面端(`/Applications/DeepSeek Harness.app`,内置 dsh 0.2.0-rc.2)能承载 Argent,
|
|
8
|
+
* 但**首次隔离启动时误写了生产一行**:`plugins/argent-flow` 的 `dataDir` 只认
|
|
9
|
+
* `ARGENT_DATA_DIR`,未设时直接回退 `homedir()/.dsh/argent`,**完全不看 `DSH_HOME`**
|
|
10
|
+
* ⇒ 以 `DSH_HOME=~/.dsh-desktop-trial` 起的隔离实例把日志追加进了
|
|
11
|
+
* 生产 `~/.dsh/argent/flow-watch.log`。
|
|
12
|
+
*
|
|
13
|
+
* 更反直觉的风险在失败回路:**只隔离 `ARGENT_DATA_DIR` 而不放暂停开关更危险** ——
|
|
14
|
+
* 隔离目录里没有 `PAUSE_FLOW_WATCH`,泵会**真的去扫/派发生产流程**(抢客户机的活)。
|
|
15
|
+
* 故本文件除了统一解析链,还导出 `loopGate()` 安全阀:**数据目录一旦偏离默认
|
|
16
|
+
* `~/.dsh/argent`(被 `DSH_HOME` 或 `ARGENT_DATA_DIR` 改到别处),失败回路默认暂停**,
|
|
17
|
+
* 除非显式 `ARGENT_ALLOW_ISOLATED_PUMP=1`。
|
|
18
|
+
*
|
|
19
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
* 二、解析链(**全仓库唯一一份**,插件/脚本/内核都从这里取)
|
|
21
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
22
|
+
* ARGENT_DATA_DIR → $DSH_HOME/argent → 默认目录
|
|
23
|
+
* (显式隔离) (宿主 home 隔离) (见下"默认目录两步走")
|
|
24
|
+
*
|
|
25
|
+
* **默认目录(§6.2 Q6 选项 B,2026-10-03 批准;两步走:① 存在即沿用 → ② 新装 `~/.argent`)**
|
|
26
|
+
* · **存在即沿用**:`~/.dsh/argent` **已存在** ⇒ 它就是默认目录 —— **老机器**(装过 dsh/Argent、
|
|
27
|
+
* 数据都在这儿)绝不被误判成"隔离"(隔离态会让失败回路**默认暂停** ⇒ 老机器集体停摆);
|
|
28
|
+
* · 否则(**新装、无宿主**)⇒ `~/.argent`(宿主无关的 home 位置,不再借 `.dsh` 这个宿主目录名)。
|
|
29
|
+
* · 判据锚在 **OS home**(只认 `$HOME`):默认目录里**不含** `DSH_HOME`,否则隔离实例会把自己当默认。
|
|
30
|
+
* · **优先级链不变**:只改"默认"这一级的**取值**,不改链的结构(`DSH_HOME` 一级必须留着 ——
|
|
31
|
+
* dsh 多实例隔离靠它)。
|
|
32
|
+
* · ⚠️ **残留风险(如实登记,别当没有)**:判据是"老位置在不在",所以**先**在 `~/.argent` 落过
|
|
33
|
+
* 数据、**后来**才出现 `~/.dsh/argent`(例:先只用 CLI,之后才跑安装器)的机器,默认目录会
|
|
34
|
+
* 切回老位置 ⇒ 可能读到空台账。已知处置:`argent init --json` 会把**两个候选路径 + 是否在用**
|
|
35
|
+
* 如实打出来;要钉死目录用 `ARGENT_DATA_DIR`(或 `DSH_HOME`)显式指定。
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ **不允许**在任何插件/脚本里另写一份这条链(哪怕只有两级)。历史教训就是
|
|
38
|
+
* "每个文件各自 `ARGENT_DATA_DIR || ~/.dsh/argent`" ⇒ 有的看 `DSH_HOME`、有的不看,
|
|
39
|
+
* 隔离实例一半写隔离目录、一半写生产。
|
|
40
|
+
*
|
|
41
|
+
* 分层:本文件是 **L1 内核**(只用 node 内建,不 import 任何宿主模块)。
|
|
42
|
+
* · `core/**` → 相对路径直接 import(同层)。
|
|
43
|
+
* · `scripts/**` → 相对路径 import `../core/lib/argent-datadir.mjs`(既有先例:
|
|
44
|
+
* scripts/check-license.mjs 就是 import core/lib/license-file.mjs)。
|
|
45
|
+
* · `plugins/**`(L2 宿主壳)→ profile 里插件是 `node_modules/<pkg>` 下的独立包,
|
|
46
|
+
* **静态 import 不到内核** ⇒ 运行时 `await import()` 内核文件
|
|
47
|
+
* (见各插件的 `KERNEL` 加载段与 `docs/Argent-宿主适配契约.md` §3.3);
|
|
48
|
+
* 取不到内核 ⇒ 一律 **fail-closed**(阀门保持"暂停"),
|
|
49
|
+
* **绝不**退回本地重写的判据。
|
|
50
|
+
*
|
|
51
|
+
* 跑法(回归):`node scripts/tests/argent-datadir.test.mjs`
|
|
52
|
+
* `node scripts/tests/argent-datadir-parity.test.mjs`(JS ⟷ Python **逐字段**对拍)
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
import { existsSync } from 'node:fs'
|
|
56
|
+
import { homedir } from 'node:os'
|
|
57
|
+
import { join, resolve as resolvePath } from 'node:path'
|
|
58
|
+
|
|
59
|
+
/** 显式数据目录(最高优先级)。 */
|
|
60
|
+
export const ENV_DATA_DIR = 'ARGENT_DATA_DIR'
|
|
61
|
+
/** 宿主 home(dsh 多实例隔离开关)。 */
|
|
62
|
+
export const ENV_DSH_HOME = 'DSH_HOME'
|
|
63
|
+
/** 隔离目录下**显式**放开失败回路的开关(必须精确等于 `1`)。 */
|
|
64
|
+
export const ENV_ALLOW_ISOLATED_PUMP = 'ARGENT_ALLOW_ISOLATED_PUMP'
|
|
65
|
+
|
|
66
|
+
/** **老机器**的数据目录(`~/.dsh/argent`):存在即沿用 —— 这一级同时是"客户机现状"。 */
|
|
67
|
+
export const DATA_DIR_SEGMENTS = ['.dsh', 'argent']
|
|
68
|
+
/** **新装(无宿主)**的默认目录:`~/.argent`(§6.2 Q6 选项 B);只在老位置不存在时生效。 */
|
|
69
|
+
export const NO_HOST_DATA_DIR_SEGMENTS = ['.argent']
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* OS 用户 home(只认 `$HOME`,**故意不认 `DSH_HOME`**)。
|
|
73
|
+
* 判据"是否偏离默认目录"必须锚在 OS home 上,否则隔离实例会把自己当成默认。
|
|
74
|
+
*/
|
|
75
|
+
export function osHome(env = process.env) {
|
|
76
|
+
const h = env && env.HOME ? String(env.HOME) : ''
|
|
77
|
+
return h || homedir()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 宿主 home:`$DSH_HOME` → `~/.dsh`(dsh 多实例隔离:每个实例独立 home)。
|
|
82
|
+
* 注意这不是数据目录,是**数据目录的父目录**。
|
|
83
|
+
*/
|
|
84
|
+
export function dshHomeOf(env = process.env, home = osHome(env)) {
|
|
85
|
+
const v = env && env.DSH_HOME ? String(env.DSH_HOME) : ''
|
|
86
|
+
return v || join(home, '.dsh')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** **老位置** `~/.dsh/argent`(**只算路径,不看存在性**;存在性判据见 `legacyDataDirExists`)。 */
|
|
90
|
+
export function legacyDataDir(env = process.env, home = osHome(env)) {
|
|
91
|
+
return join(home, ...DATA_DIR_SEGMENTS)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* **"存在即沿用"的判据本体**:老位置 `~/.dsh/argent` 在不在?
|
|
96
|
+
*
|
|
97
|
+
* 为什么单独一个函数:这条判据决定"默认目录是哪一个",而默认目录又决定 `isolated`
|
|
98
|
+
* (安全阀据此暂停失败回路)⇒ 它必须**只有一处实现**,且能被夹具逐态验证。
|
|
99
|
+
* `exists` 可注入(夹具用),缺省 `fs.existsSync`(**不是** `statSync().isDirectory()`:
|
|
100
|
+
* 与 Python 侧 `os.path.exists` 逐字同解 —— 存在性判据不许两边各写一套)。
|
|
101
|
+
*/
|
|
102
|
+
export function legacyDataDirExists(env = process.env, home = osHome(env), exists = existsSync) {
|
|
103
|
+
return Boolean(exists(legacyDataDir(env, home)))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 默认数据目录(**两步走**,实施方案 P1③ / §6.2 Q6 选项 B,2026-10-03 已批准):
|
|
108
|
+
* · ① **存在即沿用**:`~/.dsh/argent` **已存在** ⇒ 它就是默认目录(**老机器永不被误判隔离**;
|
|
109
|
+
* 隔离态会让失败回路默认暂停 ⇒ 直接停摆);
|
|
110
|
+
* · ② 不存在(**新装、无宿主**)⇒ `~/.argent`。
|
|
111
|
+
*
|
|
112
|
+
* 两步走的意义(为什么不是"直接改默认值"):顺序反了会让**所有老机器**的目录瞬间被判成隔离、
|
|
113
|
+
* 失败回路集体停摆;故 ①(存在即沿用 + 不变量回归)先落,②(改这一行的取值)后落,两批都带
|
|
114
|
+
* JS/Python 对拍(`scripts/tests/argent-datadir-parity.test.mjs`)。
|
|
115
|
+
*/
|
|
116
|
+
export function defaultDataDir(env = process.env, home = osHome(env)) {
|
|
117
|
+
if (legacyDataDirExists(env, home)) return legacyDataDir(env, home) // ① 存在即沿用(老机器)
|
|
118
|
+
return join(home, ...NO_HOST_DATA_DIR_SEGMENTS) // ② 新装(无宿主)⇒ ~/.argent
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 三级链解析。
|
|
123
|
+
* @param {{ env?: Record<string, string|undefined>, home?: string }} [opts]
|
|
124
|
+
* @returns {{ dir: string, source: 'ARGENT_DATA_DIR'|'DSH_HOME'|'default', isolated: boolean,
|
|
125
|
+
* defaultDir: string, dshHome: string, raw: string }}
|
|
126
|
+
* `dir` 解析结果(绝对路径;`ARGENT_DATA_DIR` 是相对路径时按 cwd 解析)
|
|
127
|
+
* `source` 命中链上的哪一级(日志/面板用)
|
|
128
|
+
* `isolated` `dir` **不等于**默认目录(`defaultDataDir()`:老机器 = `~/.dsh/argent`)⇒ 隔离态(安全阀据此生效)
|
|
129
|
+
*/
|
|
130
|
+
export function resolveDataDir(opts = {}) {
|
|
131
|
+
const env = opts.env || process.env
|
|
132
|
+
const home = opts.home || osHome(env)
|
|
133
|
+
const defaultDir = defaultDataDir(env, home)
|
|
134
|
+
const dshHome = dshHomeOf(env, home)
|
|
135
|
+
const explicit = env && env[ENV_DATA_DIR] ? String(env[ENV_DATA_DIR]) : ''
|
|
136
|
+
const raw = explicit || (env && env[ENV_DSH_HOME] ? join(dshHome, 'argent') : defaultDir)
|
|
137
|
+
const dir = resolvePath(raw)
|
|
138
|
+
return {
|
|
139
|
+
dir,
|
|
140
|
+
raw,
|
|
141
|
+
source: explicit ? ENV_DATA_DIR : (env && env[ENV_DSH_HOME] ? ENV_DSH_HOME : 'default'),
|
|
142
|
+
isolated: dir !== resolvePath(defaultDir),
|
|
143
|
+
defaultDir: resolvePath(defaultDir),
|
|
144
|
+
dshHome,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 解析结果的单行描述(日志用)。 */
|
|
149
|
+
export function describeDataDir(resolved) {
|
|
150
|
+
if (!resolved || !resolved.dir) return '数据目录未解析(内核解析器不可用)'
|
|
151
|
+
return `${resolved.dir}(来源=${resolved.source}${resolved.isolated ? ',隔离' : ''})`
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 对**任意**数据目录求安全阀(宿主把 `dataDir` 放在插件配置里显式指定时用)。
|
|
156
|
+
* 判据仍在内核:这里只把"配置里的目录"包装成与 `resolveDataDir()` 同形的结果,
|
|
157
|
+
* 隔离判定**不**在调用方重写。
|
|
158
|
+
*/
|
|
159
|
+
export function loopGateForDir(dir, opts = {}) {
|
|
160
|
+
const env = opts.env || process.env
|
|
161
|
+
const home = opts.home || osHome(env)
|
|
162
|
+
const raw = String(dir || '')
|
|
163
|
+
if (!raw) return loopGate(null, { env })
|
|
164
|
+
const defaultDir = resolvePath(defaultDataDir(env, home))
|
|
165
|
+
const abs = resolvePath(raw)
|
|
166
|
+
return loopGate({
|
|
167
|
+
dir: abs,
|
|
168
|
+
raw,
|
|
169
|
+
source: 'config',
|
|
170
|
+
isolated: abs !== defaultDir,
|
|
171
|
+
defaultDir,
|
|
172
|
+
dshHome: dshHomeOf(env, home),
|
|
173
|
+
}, { env })
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* **隔离安全阀**:失败回路该不该跑。
|
|
178
|
+
*
|
|
179
|
+
* 规则(唯一判据,插件不得自行判断):
|
|
180
|
+
* · 解析器不可用(`resolved` 为空 / `dir` 为空)⇒ **暂停**(fail-closed);
|
|
181
|
+
* · `isolated`(数据目录 ≠ 默认目录,判据 = `defaultDataDir()`)且未显式
|
|
182
|
+
* `ARGENT_ALLOW_ISOLATED_PUMP=1` ⇒ **暂停**,并给出可照做的开启提示;
|
|
183
|
+
* · 其余 ⇒ 允许运行。
|
|
184
|
+
*
|
|
185
|
+
* @returns {{ enabled: boolean, paused: boolean, reason: string, isolated: boolean,
|
|
186
|
+
* dir: string, notice: string }}
|
|
187
|
+
*/
|
|
188
|
+
export function loopGate(resolved, opts = {}) {
|
|
189
|
+
const env = opts.env || process.env
|
|
190
|
+
const allow = String((env && env[ENV_ALLOW_ISOLATED_PUMP]) || '') === '1'
|
|
191
|
+
const enabledText = `设 ${ENV_ALLOW_ISOLATED_PUMP}=1 可开启`
|
|
192
|
+
if (!resolved || !resolved.dir) {
|
|
193
|
+
return {
|
|
194
|
+
enabled: false,
|
|
195
|
+
paused: true,
|
|
196
|
+
reason: 'datadir-unresolved',
|
|
197
|
+
isolated: true,
|
|
198
|
+
dir: '',
|
|
199
|
+
notice: `失败回路保持暂停:内核数据目录解析器不可用(core/lib/argent-datadir.mjs 没找到)⇒ 不敢猜目录。修好后重启即可。`,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (resolved.isolated && !allow) {
|
|
203
|
+
return {
|
|
204
|
+
enabled: false,
|
|
205
|
+
paused: true,
|
|
206
|
+
reason: 'isolated-data-dir',
|
|
207
|
+
isolated: true,
|
|
208
|
+
dir: resolved.dir,
|
|
209
|
+
notice: `失败回路因隔离数据目录而默认暂停(${describeDataDir(resolved)});`
|
|
210
|
+
+ `隔离目录通常没有 PAUSE_FLOW_WATCH,默认运行会误扫/派发生产流程 ⇒ ${enabledText}`,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
enabled: true,
|
|
215
|
+
paused: false,
|
|
216
|
+
reason: resolved.isolated ? 'isolated-allowed' : 'default-dir',
|
|
217
|
+
isolated: Boolean(resolved.isolated),
|
|
218
|
+
dir: resolved.dir,
|
|
219
|
+
notice: resolved.isolated
|
|
220
|
+
? `失败回路已按 ${ENV_ALLOW_ISOLATED_PUMP}=1 在隔离数据目录上开启(${describeDataDir(resolved)})——请确认它扫的是隔离源,不是生产`
|
|
221
|
+
: '',
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `argent audit` —— **审计只读汇总**(P1;§2.1 审计行、策划案⑮)。只读,永不写。
|
|
3
|
+
*
|
|
4
|
+
* 今天仓库里有 4 处审计 JSONL,散在数据目录的不同子目录里,谁也不知道"这台机器到底记了什么"。
|
|
5
|
+
* 本 verb 把"读审计"收口成一条命令:**先读全量 → 按 `--since` 过滤 → 合并排序 → 取末尾 N 行**
|
|
6
|
+
* (顺序照 D1 的教训钉死:**先截断后过滤**会让空结果分不清"真没有"和"被截掉了")。
|
|
7
|
+
*
|
|
8
|
+
* ## 审计源登记表(`SOURCES`)
|
|
9
|
+
* | id | 相对数据目录 | 写它的地方 | 判据来源 |
|
|
10
|
+
* |---|---|---|---|
|
|
11
|
+
* | `invoice-writeback` | `invoice-writeback/audit.jsonl` | `scripts/wenyao-invoice-writeback.mjs` | 判断⑨(审计与备份) |
|
|
12
|
+
* | `license-gate` | `license-gate.jsonl` | `scripts/license-gate.mjs` | 判断⑦(许可三态,observe 留证据) |
|
|
13
|
+
* | `notify-outbox` | `notify-outbox/audit.jsonl` | `core/lib/notify/dedup.mjs`(P2 新增) | 判断⑩(通知幂等 + 投递留痕) |
|
|
14
|
+
* | `env-writeback` | `env-writeback/audit.jsonl` | `scripts/argent-env-write.mjs` | 判断②⑨(受控 env 写入) |
|
|
15
|
+
*
|
|
16
|
+
* 新增审计源 = 在 `SOURCES` 加一行(**只登记路径与出处,不复制写它的判据**)。
|
|
17
|
+
*
|
|
18
|
+
* ## A1/A2
|
|
19
|
+
* · `0` 读成功(**没有匹配行 = `empty:true + no_match`**,不是错误 —— 新机器本来就没有审计);
|
|
20
|
+
* · `2` 用法错误(未知 `--source` / `--since` 解析不了 / `--tail` 不是正整数);
|
|
21
|
+
* · `1` 某个**已存在**的审计文件读不出来(权限/IO)⇒ 如实报 `unreadable`,**不得**降级成"空"。
|
|
22
|
+
* · 回显**实际生效值**:`sources`(请求的)/ `since`(归一化后的 ISO 或 null)/ `tail` /
|
|
23
|
+
* `scanned` / `matched` / `returned` + 每源计数 —— 空结果一眼可分。
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
26
|
+
import { join } from 'node:path'
|
|
27
|
+
import { resolveDataDir } from './argent-datadir.mjs'
|
|
28
|
+
|
|
29
|
+
/** 审计源登记表(id → 相对数据目录的路径 + 谁写的)。 */
|
|
30
|
+
export const SOURCES = [
|
|
31
|
+
{ id: 'invoice-writeback', path: 'invoice-writeback/audit.jsonl', writtenBy: 'scripts/wenyao-invoice-writeback.mjs', note: '回写:写前备份 + 字段 diff + 读回验收' },
|
|
32
|
+
{ id: 'license-gate', path: 'license-gate.jsonl', writtenBy: 'scripts/license-gate.mjs', note: '许可闸门:每次判定一行(observe 只留证据)' },
|
|
33
|
+
{ id: 'notify-outbox', path: 'notify-outbox/audit.jsonl', writtenBy: 'core/lib/notify/dedup.mjs', note: '通知投递(每次真发一行;dry-run 不落)' },
|
|
34
|
+
{ id: 'env-writeback', path: 'env-writeback/audit.jsonl', writtenBy: 'scripts/argent-env-write.mjs', note: '受控 env 写入:字段白名单 + 写前备份' },
|
|
35
|
+
{ id: 'license-activation', path: 'license-activation/audit.jsonl', writtenBy: 'core/lib/license-activate.mjs', note: '一次性激活码兑换(apply 的成败各一行;**只落 codePrefix**,绝不落明文码/密钥);dry-run 不落' },
|
|
36
|
+
{ id: 'install', path: 'install-audit.jsonl', writtenBy: 'install-argent.mjs', note: '安装**放行留痕**:`--allow-unlicensed`(送装/断网)、逃生阀 `ARGENT_LICENSE_ENFORCE=off`、以及宿主门禁逃生阀 `--allow-unsupported-host`(内部/验证用;行内含 hostVersion/supportedWindow/args)各一行;含 who/host/能力/包名/planSha,⛔ 不含任何密钥' },
|
|
37
|
+
]
|
|
38
|
+
export const SOURCE_IDS = SOURCES.map((s) => s.id)
|
|
39
|
+
export const DEFAULT_TAIL = 20
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `--since` 解析:ISO 8601 / `YYYY-MM-DD` / epoch 秒(10 位)/ epoch 毫秒(13 位)。
|
|
43
|
+
* @returns {{ok:true, since:number, iso:string} | {ok:false, error:string}}
|
|
44
|
+
*/
|
|
45
|
+
export function parseSince(raw, now = Date.now()) {
|
|
46
|
+
const s = String(raw == null ? '' : raw).trim()
|
|
47
|
+
if (!s) return { ok: false, error: '--since 缺值' }
|
|
48
|
+
if (/^\d{10}$/.test(s)) return { ok: true, since: Number(s) * 1000, iso: new Date(Number(s) * 1000).toISOString() }
|
|
49
|
+
if (/^\d{13}$/.test(s)) return { ok: true, since: Number(s), iso: new Date(Number(s)).toISOString() }
|
|
50
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(s)) {
|
|
51
|
+
// 纯日期按**本地**零点解释(人在看"今天之后"时就是这个意思),并回显归一化结果
|
|
52
|
+
const [y, m, d] = s.split('-').map(Number)
|
|
53
|
+
const t = new Date(y, m - 1, d, 0, 0, 0, 0).getTime()
|
|
54
|
+
return { ok: true, since: t, iso: new Date(t).toISOString() }
|
|
55
|
+
}
|
|
56
|
+
const t = Date.parse(s)
|
|
57
|
+
if (Number.isNaN(t)) return { ok: false, error: `--since 解析不了:${JSON.stringify(s)}(支持 ISO 8601 / YYYY-MM-DD / epoch 秒 / epoch 毫秒)` }
|
|
58
|
+
void now
|
|
59
|
+
return { ok: true, since: t, iso: new Date(t).toISOString() }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 单源读取(坏行保留为 `{raw}`,**绝不丢证据**)。 */
|
|
63
|
+
export function readSource(dataDir, src) {
|
|
64
|
+
const p = join(String(dataDir), src.path)
|
|
65
|
+
if (!existsSync(p)) return { ...src, file: p, exists: false, lines: 0, rows: [], error: null }
|
|
66
|
+
let text = ''
|
|
67
|
+
try { text = readFileSync(p, 'utf8') } catch (e) {
|
|
68
|
+
return { ...src, file: p, exists: true, lines: 0, rows: [], error: `读不出来:${(e && e.message) || e}` }
|
|
69
|
+
}
|
|
70
|
+
const rows = []
|
|
71
|
+
const raw = text.split('\n')
|
|
72
|
+
raw.forEach((l, i) => {
|
|
73
|
+
const s = l.trim()
|
|
74
|
+
if (!s) return
|
|
75
|
+
let data = null
|
|
76
|
+
try { data = JSON.parse(s) } catch { data = { raw: s } }
|
|
77
|
+
rows.push({ source: src.id, line: i + 1, at: data && typeof data.at === 'string' ? data.at : null, data })
|
|
78
|
+
})
|
|
79
|
+
return { ...src, file: p, exists: true, lines: raw.filter((l) => l.trim()).length, rows, error: null }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* verb 入口:`argent audit [--source <id>]… [--since <t>] [--tail <n>] [--json]`。
|
|
84
|
+
* @param {string[]} argv
|
|
85
|
+
* @param {{env?:object, dataDir?:string}} [deps]
|
|
86
|
+
*/
|
|
87
|
+
export function runAuditVerb(argv = [], deps = {}) {
|
|
88
|
+
const wantSources = []
|
|
89
|
+
let sinceRaw = null
|
|
90
|
+
let tailRaw = null
|
|
91
|
+
for (let i = 0; i < argv.length; i++) {
|
|
92
|
+
const t = argv[i]
|
|
93
|
+
if (t === '--json') continue
|
|
94
|
+
if (t === '--source') {
|
|
95
|
+
const v = argv[i + 1]
|
|
96
|
+
if (v === undefined || String(v).startsWith('--')) return auditFail(2, 'usage_error', '--source 缺值')
|
|
97
|
+
if (!SOURCE_IDS.includes(v)) return auditFail(2, 'usage_error', `未知审计源:${v}(可用:${SOURCE_IDS.join('、')})`)
|
|
98
|
+
wantSources.push(v); i++
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
if (t === '--since') { const v = argv[i + 1]; if (v === undefined || String(v).startsWith('--')) return auditFail(2, 'usage_error', '--since 缺值'); sinceRaw = v; i++; continue }
|
|
102
|
+
if (t === '--tail') { const v = argv[i + 1]; if (v === undefined || String(v).startsWith('--')) return auditFail(2, 'usage_error', '--tail 缺值'); tailRaw = v; i++; continue }
|
|
103
|
+
if (t === '--help' || t === '-h') return { json: { ok: true, empty: false, code: 0, reason: null, verb: 'audit', usage: AUDIT_USAGE }, human: AUDIT_USAGE, stderr: '', exit: 0 }
|
|
104
|
+
if (t.startsWith('--')) return auditFail(2, 'usage_error', `未知参数:${t}(用法:argent audit [--source <id>]… [--since <t>] [--tail <n>] [--json])`)
|
|
105
|
+
return auditFail(2, 'usage_error', `多余的位置参数:${t}`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let tail = DEFAULT_TAIL
|
|
109
|
+
if (tailRaw !== null) {
|
|
110
|
+
const n = Number(String(tailRaw).trim())
|
|
111
|
+
if (!Number.isFinite(n) || n < 1 || !Number.isInteger(n)) return auditFail(2, 'usage_error', `--tail 必须是 ≥1 的整数,实得 ${JSON.stringify(tailRaw)}`)
|
|
112
|
+
tail = n
|
|
113
|
+
}
|
|
114
|
+
let since = null
|
|
115
|
+
if (sinceRaw !== null) {
|
|
116
|
+
const p = parseSince(sinceRaw)
|
|
117
|
+
if (!p.ok) return auditFail(2, 'usage_error', p.error)
|
|
118
|
+
since = p
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const env = deps.env || process.env
|
|
122
|
+
const dataDir = deps.dataDir || resolveDataDir({ env }).dir
|
|
123
|
+
const requested = wantSources.length ? wantSources : SOURCE_IDS
|
|
124
|
+
const perSource = requested.map((id) => readSource(dataDir, SOURCES.find((s) => s.id === id)))
|
|
125
|
+
|
|
126
|
+
// 已存在却读不出来 ⇒ 如实报错(**不得**降级成"空",那会让"审计丢了"看起来像"本来就没有")
|
|
127
|
+
const broken = perSource.filter((s) => s.exists && s.error)
|
|
128
|
+
const all = perSource.flatMap((s) => s.rows)
|
|
129
|
+
const filtered = since
|
|
130
|
+
? all.filter((r) => {
|
|
131
|
+
if (!r.at) return false // 没有时间戳的行无法判定"是否在 since 之后" ⇒ 保守排除(并在回显说明)
|
|
132
|
+
const t = Date.parse(r.at)
|
|
133
|
+
return !Number.isNaN(t) && t >= since.since
|
|
134
|
+
})
|
|
135
|
+
: all
|
|
136
|
+
const merged = filtered.slice().sort((a, b) => {
|
|
137
|
+
const ta = a.at ? Date.parse(a.at) : NaN
|
|
138
|
+
const tb = b.at ? Date.parse(b.at) : NaN
|
|
139
|
+
if (Number.isNaN(ta) || Number.isNaN(tb)) return 0
|
|
140
|
+
return ta - tb
|
|
141
|
+
})
|
|
142
|
+
const returned = merged.slice(-tail)
|
|
143
|
+
|
|
144
|
+
const json = {
|
|
145
|
+
ok: broken.length === 0,
|
|
146
|
+
empty: returned.length === 0,
|
|
147
|
+
code: broken.length ? 1 : 0,
|
|
148
|
+
reason: broken.length ? 'unreadable' : (returned.length === 0 ? 'no_match' : null),
|
|
149
|
+
error: broken.length ? broken.map((b) => `${b.id}(${b.file}):${b.error}`).join(';') : null,
|
|
150
|
+
verb: 'audit',
|
|
151
|
+
dataDir,
|
|
152
|
+
sources: requested,
|
|
153
|
+
sourceCount: requested.length,
|
|
154
|
+
since: since ? since.iso : null,
|
|
155
|
+
sinceInput: sinceRaw,
|
|
156
|
+
tail,
|
|
157
|
+
scanned: all.length,
|
|
158
|
+
matched: filtered.length,
|
|
159
|
+
returned: returned.length,
|
|
160
|
+
rowsTruncated: Math.max(0, merged.length - returned.length),
|
|
161
|
+
perSource: perSource.map((s) => ({ id: s.id, file: s.file, exists: s.exists, lines: s.lines, error: s.error })),
|
|
162
|
+
rows: returned.map((r) => ({ source: r.source, line: r.line, at: r.at, data: r.data })),
|
|
163
|
+
note: since
|
|
164
|
+
? '`--since` 按行内 `at` 过滤;**没有 `at` 或 `at` 解析不了的行会被排除**(无法判定时间先后)。顺序 = 读全量 → 过滤 → 合并排序 → 取末尾 N 行。'
|
|
165
|
+
: '顺序 = 读全量 → 过滤(无 --since)→ 合并排序 → 取末尾 N 行;`--tail` 只截断尾部,不影响每源计数。',
|
|
166
|
+
}
|
|
167
|
+
const human = [
|
|
168
|
+
`${json.ok ? '📜' : '❌'} 审计(${requested.length} 源 | since=${json.since || '未过滤'} | tail=${tail})`,
|
|
169
|
+
...json.perSource.map((s) => ` · ${s.id.padEnd(18)} ${s.exists ? `${s.lines} 行` : '(无此文件)'}${s.error ? ` ❌ ${s.error}` : ''} ${s.file}`),
|
|
170
|
+
` 扫描 ${json.scanned} 行 → 过滤后 ${json.matched} 行 → 返回末尾 ${json.returned} 行${json.rowsTruncated ? `(截掉 ${json.rowsTruncated})` : ''}`,
|
|
171
|
+
...json.rows.map((r) => ` ${r.at || '(无 at)'} [${r.source}#${r.line}] ${JSON.stringify(r.data).slice(0, 160)}`),
|
|
172
|
+
json.error ? ` ❌ ${json.error}` : null,
|
|
173
|
+
].filter(Boolean).join('\n')
|
|
174
|
+
return { json, human, stderr: '', exit: Number(json.code) }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function auditFail(code, reason, error, echo = {}) {
|
|
178
|
+
return { json: { ok: false, empty: false, code, reason, error, verb: 'audit', ...echo }, human: `❌ ${reason}:${error}`, stderr: '', exit: code }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const AUDIT_USAGE = [
|
|
182
|
+
'用法:argent audit [--source <id>]… [--since <ISO|YYYY-MM-DD|epoch>] [--tail <n>] [--json]',
|
|
183
|
+
'',
|
|
184
|
+
`审计源:${SOURCES.map((s) => s.id).join('、')}(缺省=全部)`,
|
|
185
|
+
'只读:读全量 → 按 --since 过滤 → 按 at 合并排序 → 取末尾 --tail 行;每源计数一并回显。',
|
|
186
|
+
'没有匹配行 = exit 0 + empty:true(新机器本来就没有审计,不是错误)。',
|
|
187
|
+
].join('\n')
|