@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,620 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* argent-schedule —— **宿主原生排程面**的 CLI(P1-A,2026-10-12)
|
|
4
|
+
*
|
|
5
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* 它是什么 / 它不是什么
|
|
7
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
8
|
+
* 是:把「排程还在不在跑」变成**可复制的命令**(`probe`/`status`/`check`),
|
|
9
|
+
* 并把 `PAUSE_*` 文件与宿主任务状态之间那座桥(`pause`/`apply`)做成**默认 dry-run** 的收敛器。
|
|
10
|
+
* 不是:装机器(⛔ 不改 profile、不装包)、不是宿主管理器(⛔ 不重启宿主、⛔ 不 bootstrap 任何计划任务)、
|
|
11
|
+
* 不是第二份判据 —— 四道闸的**判据全在** `scripts/lib/schedule-probe.mjs`,本文件只做
|
|
12
|
+
* 「取数(注册表 / 凭据 / HTTP)→ 调用判据 → 排版 / 落审计」。
|
|
13
|
+
*
|
|
14
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
15
|
+
* 子命令
|
|
16
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
17
|
+
* ```bash
|
|
18
|
+
* node scripts/argent-schedule.mjs probe [--base URL] [--json] # ① 挂载闸(只读;404 ⇒ 红)
|
|
19
|
+
* node scripts/argent-schedule.mjs status [--flow <id>] [--json] # 声明 vs 任务(只读)
|
|
20
|
+
* node scripts/argent-schedule.mjs check [--flow <id>] [--json] # 五道闸汇总(退出码 0/1/2)
|
|
21
|
+
* node scripts/argent-schedule.mjs mechanism [--json] # 定时机制判据(native/shim/none/conflict/unknown)
|
|
22
|
+
* node scripts/argent-schedule.mjs pause [--flow <id>] [--apply] # PAUSE 文件 ⇒ 停(默认 dry-run)
|
|
23
|
+
* node scripts/argent-schedule.mjs apply [--flow <id>] [--apply] # 双向收敛(默认 dry-run)
|
|
24
|
+
* ```
|
|
25
|
+
* 公共开关:`--base <url>`(缺省 `http://127.0.0.1:3080`)、`--flow <id>`、`--dsh-home <dir>`、
|
|
26
|
+
* `--data-dir <dir>`、`--root <dir>`(仓库根;注册表加载器用)、`--registry-json <file>`(离线夹具)、
|
|
27
|
+
* `--log-path <file>`、`--ledger-path <file>`、`--now <ISO>`、`--period-sec <n>`、`--grace-sec <n>`、
|
|
28
|
+
* `--json`、`--apply`(pause/apply 才认)、`--allow-real-home`。
|
|
29
|
+
*
|
|
30
|
+
* 退出码(**三态**,与判据同源):`0` 全绿/已收敛 | `1` **有红**(必须有人处置)|
|
|
31
|
+
* `2` **证据不足或需要会话内动作**(例:宿主连不上;`create` 不是远端端点 ⇒ 只能由会话里的工具建任务)。
|
|
32
|
+
* ⛔ 不用 `3`:按 D4 裁定 `3` 只表示"许可宽限放行"。
|
|
33
|
+
*
|
|
34
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
35
|
+
* `pause` / `apply` 的两条**如实声明**(都别当成实现细节)
|
|
36
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
37
|
+
* 1. **收敛窗口**:`PAUSE_*` 文件与任务状态之间**不是原子的**。文件刚落地、`apply` 还没跑的那段窗口里,
|
|
38
|
+
* 定时**可能照跑一轮**(零模型成本做不到,除非窗口内正好被宿主投递)。⇒ 运维改开关后**要主动跑一次收敛**,
|
|
39
|
+
* 并把这一条写进 SOP(上游设计 §5.3 同口径)。
|
|
40
|
+
* 2. **`inactive` 置不了**:宿主**没有**任何把任务置为 `inactive` 的远端端点(见 `schedule-probe.mjs` 的
|
|
41
|
+
* `STATUS_SETTER_AVAILABLE` 注释)⇒ 「文件在 ⇒ `inactive`」这条声明**无法逐字实现**。
|
|
42
|
+
* 本 CLI 用**唯一可用的停法**:`schedule/delete`(删行 ⇒ 绝不再触发;代价:投递历史随行消失),
|
|
43
|
+
* 并把**整条被删记录**写进审计(`<数据目录>/schedule-audit.jsonl`,0600)——**"关停"这个动作因此不会消失**。
|
|
44
|
+
* 反向(文件不在 ⇒ 应有任务)**无远端端点可做**(`create` 只在会话内由工具调用)⇒ 本 CLI 记
|
|
45
|
+
* `create-required` + 打印该会话应当调用的工具参数,exit 2。
|
|
46
|
+
*/
|
|
47
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
48
|
+
import { hostname, userInfo } from 'node:os'
|
|
49
|
+
import { dirname, join, resolve } from 'node:path'
|
|
50
|
+
import { fileURLToPath } from 'node:url'
|
|
51
|
+
import {
|
|
52
|
+
absOf, ENDPOINTS, MOUNT_ENDPOINT, STATUS_SETTER_AVAILABLE,
|
|
53
|
+
domainPaths, formatGates, gateExecution, gateFreshness, gateMount, gatePersistence,
|
|
54
|
+
gateSchedulerConflict,
|
|
55
|
+
inspectDomain, matchTask, normalizeEntry, periodSecondsOf, postRpc, readL1Evidence,
|
|
56
|
+
storageRoot, summarize, verdictLine,
|
|
57
|
+
} from './lib/schedule-probe.mjs'
|
|
58
|
+
import { detectScheduleMechanism, mechanismVerdictLine } from './lib/schedule-mechanism.mjs'
|
|
59
|
+
// 0.6.0 F1 修复批(2026-10-14):native 探针的 scope 候选必须**npx 感知**
|
|
60
|
+
// (`npx @deepseek-ai/dsh web` 的宿主树在 `~/.npm/_npx/<hash>/node_modules/@deepseek-ai`)。
|
|
61
|
+
// npx 感知的候选链**唯一实现**在 L2 `scripts/lib/dsh-scope.mjs#autoScopeCandidates()`;
|
|
62
|
+
// L1 判据(`schedule-mechanism.mjs`)⛔ 不 import 宿主档 ⇒ 由本文件**注入**(`scopeDirsProbe`)。
|
|
63
|
+
import { autoScopeCandidates, resolveDshScope, scopeSourceOf } from './lib/dsh-scope.mjs'
|
|
64
|
+
import { npxCacheDirs } from './lib/dsh-host-resolve.mjs'
|
|
65
|
+
import { resolveDataDir } from './lib/argent-datadir.mjs'
|
|
66
|
+
import { acquireWakeAuth, authFailureText, authSources } from './lib/wake-auth.mjs'
|
|
67
|
+
|
|
68
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
69
|
+
export const REPO = resolve(HERE, '..')
|
|
70
|
+
const DEFAULT_BASE = 'http://127.0.0.1:3080'
|
|
71
|
+
const AUDIT_NAME = 'schedule-audit.jsonl'
|
|
72
|
+
|
|
73
|
+
// ── 参数 ────────────────────────────────────────────────────────────────────
|
|
74
|
+
const argv = process.argv.slice(2)
|
|
75
|
+
const CMD = ['probe', 'status', 'check', 'mechanism', 'pause', 'apply'].includes(argv[0]) ? argv[0] : ''
|
|
76
|
+
const has = (f) => argv.includes(f)
|
|
77
|
+
const val = (f, dflt = '') => { const i = argv.indexOf(f); return i > -1 ? (argv[i + 1] ?? dflt) : dflt }
|
|
78
|
+
const USAGE = `用法:node scripts/argent-schedule.mjs <probe|status|check|mechanism|pause|apply> [选项]
|
|
79
|
+
|
|
80
|
+
probe ① 挂载闸:POST /api/${MOUNT_ENDPOINT}(404 ⇒ 没挂载)
|
|
81
|
+
status 声明 vs 宿主任务(只读)
|
|
82
|
+
check 五道闸汇总(挂载/新鲜度/执行/持久化/**单一调度者**)⇒ 退出码 0 全绿 / 1 有红 / 2 证据不足
|
|
83
|
+
mechanism 定时机制判据(native / activation-shim / none / conflict / unknown)—— **唯一实现**在
|
|
84
|
+
scripts/lib/schedule-mechanism.mjs(声明面 = 注册表 \`schedule.mechanism\`);本子命令只排版
|
|
85
|
+
pause PAUSE_* 文件 ⇒ 停任务(默认 dry-run;--apply 才写)
|
|
86
|
+
apply 双向收敛(默认 dry-run;--apply 才写)
|
|
87
|
+
|
|
88
|
+
选项:
|
|
89
|
+
--base <url> 宿主地址(缺省 ${DEFAULT_BASE})
|
|
90
|
+
--flow <id> 只看/只收敛一条流程(缺省:全部声明了 trigger.schedule 的流程)
|
|
91
|
+
--dsh-home <dir> 宿主 home(缺省 $DSH_HOME 或 ~/.dsh)—— 决定排程域文件的落点
|
|
92
|
+
--data-dir <dir> Argent 数据目录(缺省走内核三级链)—— 审计与流程日志的落点
|
|
93
|
+
--root <dir> 仓库根(注册表加载器用;缺省本仓库)
|
|
94
|
+
--registry-json <file> 离线夹具:直接读一份**已合并**的注册表 JSON(⛔ 不做合并,只读文件)
|
|
95
|
+
--log-path <file> L1 轮次日志(缺省按注册表 trigger.log 推)
|
|
96
|
+
--ledger-path <file> L1 台账(缺省 <数据目录>/flow-ledger.jsonl)
|
|
97
|
+
--now <ISO> 把"现在"钉住(夹具/复算用)
|
|
98
|
+
--period-sec <n> 覆盖周期(cron 等推不出周期的选择器用)
|
|
99
|
+
--grace-sec <n> 执行闸宽限秒(缺省 180)
|
|
100
|
+
--json 机器可读
|
|
101
|
+
--launch-agents-dir <dir> 激活垫片计划任务目录(缺省 ~/Library/LaunchAgents;夹具用)
|
|
102
|
+
--scope-dir <dir> 宿主作用域(可重复;缺省由 --dsh-home/--profile 推)—— 原生排程探测用
|
|
103
|
+
--apply pause/apply 真的写(默认 dry-run)
|
|
104
|
+
--allow-real-home --apply 时允许落在**真实**数据目录(缺省拒绝,防误写生产)
|
|
105
|
+
-h, --help 本帮助
|
|
106
|
+
`
|
|
107
|
+
if (has('-h') || has('--help') || !CMD) {
|
|
108
|
+
process.stdout.write(USAGE)
|
|
109
|
+
process.exit(CMD ? 0 : 2)
|
|
110
|
+
}
|
|
111
|
+
const AS_JSON = has('--json')
|
|
112
|
+
const APPLY = has('--apply')
|
|
113
|
+
const ALLOW_REAL = has('--allow-real-home')
|
|
114
|
+
const BASE = val('--base', DEFAULT_BASE).replace(/\/+$/, '')
|
|
115
|
+
const FLOW_ARG = val('--flow')
|
|
116
|
+
const NOW_MS = val('--now') ? Date.parse(val('--now')) : Date.now()
|
|
117
|
+
const PERIOD_ARG = val('--period-sec') ? Number(val('--period-sec')) : null
|
|
118
|
+
const GRACE_SEC = val('--grace-sec') ? Number(val('--grace-sec')) : undefined
|
|
119
|
+
const DSH_HOME = val('--dsh-home') || process.env.DSH_HOME || ''
|
|
120
|
+
const LAUNCH_AGENTS_DIR = val('--launch-agents-dir')
|
|
121
|
+
const PROFILE = val('--profile') || process.env.DSH_PROFILE || 'web'
|
|
122
|
+
const SCOPE_DIRS = argv.reduce((acc, a, i) => (a === '--scope-dir' && argv[i + 1] ? [...acc, argv[i + 1]] : acc), [])
|
|
123
|
+
const DATA_DIR = val('--data-dir') || resolveDataDir({ env: process.env }).dir
|
|
124
|
+
const IS_REAL_DATA_DIR = !val('--data-dir') && !process.env.ARGENT_DATA_DIR && DATA_DIR === resolveDataDir({ env: {} }).dir
|
|
125
|
+
|
|
126
|
+
const out = (obj) => { process.stdout.write(`${JSON.stringify(obj, null, 2)}\n`) }
|
|
127
|
+
const say = (s) => { if (!AS_JSON) process.stdout.write(`${s}\n`) }
|
|
128
|
+
|
|
129
|
+
// ── 注册表声明面(**只读**;合并的唯一真源仍是 flow_registry.py)─────────────────
|
|
130
|
+
/**
|
|
131
|
+
* 读合并后的注册表:`--registry-json` 给文件就直接读(离线夹具/半坏机器),
|
|
132
|
+
* 否则走 `scripts/lib/registry.mjs` 的加载器(= `flow_registry.py --json`,**唯一合并实现**)。
|
|
133
|
+
*/
|
|
134
|
+
async function readRegistry() {
|
|
135
|
+
const file = val('--registry-json')
|
|
136
|
+
if (file) {
|
|
137
|
+
try { return { doc: JSON.parse(readFileSync(absOf(file), 'utf8')), how: `--registry-json ${file}` } }
|
|
138
|
+
catch (e) { return { doc: null, how: `--registry-json ${file}`, error: String(e.message || e) } }
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const { loadRegistryDoc } = await import('./lib/registry.mjs')
|
|
142
|
+
return { doc: loadRegistryDoc({ root: val('--root') || REPO }), how: `flow_registry.py(root=${val('--root') || REPO})` }
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return { doc: null, how: 'flow_registry.py', error: String((e && (e.stderr || e.message)) || e) }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 把注册表里**声明了 `trigger.schedule`** 的流程摊平(含本地 id:台账 caseId 用短名前缀)。 */
|
|
149
|
+
export function declaredSchedules(doc) {
|
|
150
|
+
const out = []
|
|
151
|
+
if (!doc) return out
|
|
152
|
+
const aliases = doc.aliases || {}
|
|
153
|
+
const back = {}
|
|
154
|
+
for (const [short, canon] of Object.entries(aliases)) back[canon] = short
|
|
155
|
+
for (const [flowId, spec] of Object.entries(doc.flows || {})) {
|
|
156
|
+
const t = (spec && spec.trigger) || {}
|
|
157
|
+
const s = t.schedule
|
|
158
|
+
if (!s || typeof s !== 'object') continue
|
|
159
|
+
const localId = back[flowId] || String(flowId).split('.').pop()
|
|
160
|
+
out.push({
|
|
161
|
+
flowId,
|
|
162
|
+
localId,
|
|
163
|
+
title: String(spec.title || flowId),
|
|
164
|
+
schedule: s,
|
|
165
|
+
enabled: s.enabled === true,
|
|
166
|
+
taskTitle: String(s.taskTitle || ''),
|
|
167
|
+
kind: String(s.kind || ''),
|
|
168
|
+
timeZone: s.timeZone ? String(s.timeZone) : null,
|
|
169
|
+
promptFile: String(s.promptFile || ''),
|
|
170
|
+
onPause: String(s.onPause || ''),
|
|
171
|
+
pauseSwitch: String(t.pauseSwitch || ''),
|
|
172
|
+
pauseSwitchLegacy: String(t.pauseSwitchLegacy || ''),
|
|
173
|
+
logRel: String(t.log || ''),
|
|
174
|
+
intervalSec: Number.isFinite(t.intervalSec) ? t.intervalSec : null,
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
return out
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** 选中的流程(`--flow` 给定时按 id/别名/本地名解析)。 */
|
|
181
|
+
function pickFlows(all) {
|
|
182
|
+
if (!FLOW_ARG) return all.filter((f) => f.enabled)
|
|
183
|
+
const want = FLOW_ARG
|
|
184
|
+
const hit = all.filter((f) => f.flowId === want || f.localId === want)
|
|
185
|
+
return hit
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** 暂停开关的**两个**候选路径(规范名 + 历史兼容名;与 `flow_config` 的 paths 同口径)。 */
|
|
189
|
+
function pauseFiles(flow) {
|
|
190
|
+
const mk = (rel) => (rel ? join(DATA_DIR, rel) : '')
|
|
191
|
+
return [mk(flow.pauseSwitch), mk(flow.pauseSwitchLegacy)].filter(Boolean)
|
|
192
|
+
}
|
|
193
|
+
const pausePresent = (flow) => pauseFiles(flow).find((p) => existsSync(p)) || ''
|
|
194
|
+
|
|
195
|
+
function l1Paths(flow) {
|
|
196
|
+
const logPath = val('--log-path') || (flow && flow.logRel ? join(DATA_DIR, flow.logRel) : '')
|
|
197
|
+
const ledgerPath = val('--ledger-path') || join(DATA_DIR, 'flow-ledger.jsonl')
|
|
198
|
+
return { logPath, ledgerPath }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── HTTP 取数(带鉴权;**只读**)────────────────────────────────────────────
|
|
202
|
+
/** 用仓库既有的唤醒鉴权链拿 cookie(**同一份实现**,不新造第二套)。 */
|
|
203
|
+
async function getCookie() {
|
|
204
|
+
const sources = authSources({ base: BASE, env: process.env, dshHome: DSH_HOME || undefined })
|
|
205
|
+
const got = await acquireWakeAuth({ sources, base: BASE })
|
|
206
|
+
return got
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function readCatalog() {
|
|
210
|
+
const auth = await getCookie()
|
|
211
|
+
if (!auth.ok) {
|
|
212
|
+
return { mount: { state: 'unauthorized', httpStatus: 0, message: auth.unreachable || '鉴权链全部失败', url: `${BASE}/api/${MOUNT_ENDPOINT}` }, auth }
|
|
213
|
+
}
|
|
214
|
+
const res = await postRpc({ base: BASE, endpoint: ENDPOINTS.catalog, args: {}, cookie: auth.cookie })
|
|
215
|
+
return { mount: res, catalog: Array.isArray(res.value) ? res.value.map(normalizeEntry).filter(Boolean) : null, auth }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** 拿不到 catalog 时退化为"只有挂载结论"(`entries: null` = 读不到,**不是**空)。 */
|
|
219
|
+
async function mountOnly() {
|
|
220
|
+
const auth = await getCookie()
|
|
221
|
+
if (!auth.ok) return { mount: { state: 'unauthorized', httpStatus: 0, message: auth.unreachable || '鉴权链全部失败' }, auth }
|
|
222
|
+
const res = await postRpc({ base: BASE, endpoint: MOUNT_ENDPOINT, args: {}, cookie: auth.cookie })
|
|
223
|
+
return { mount: res, auth }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ── 审计("关停"这个动作不能从文件系统上消失)────────────────────────────────
|
|
227
|
+
const auditPath = () => join(DATA_DIR, AUDIT_NAME)
|
|
228
|
+
const AUDIT_HEADER = { _doc: '排程变更审计(P1-A):谁在什么时候把定时停了/试图停。JSONL 追加 + 0600;与 flow-ledger.jsonl 同风格。' }
|
|
229
|
+
|
|
230
|
+
/** 追加一条审计(0600)。⛔ 只在**有动作**时写;dry-run 也写(如实记 mode)。 */
|
|
231
|
+
export function writeAudit(rec) {
|
|
232
|
+
const p = auditPath()
|
|
233
|
+
try { mkdirSync(dirname(p), { recursive: true }) } catch { /* 已存在 */ }
|
|
234
|
+
if (!existsSync(p)) {
|
|
235
|
+
try { writeFileSync(p, `${JSON.stringify(AUDIT_HEADER)}\n`, { mode: 0o600 }) } catch { /* 由下面统一报 */ }
|
|
236
|
+
}
|
|
237
|
+
const line = `${JSON.stringify(rec)}\n`
|
|
238
|
+
appendFileSync(p, line, { mode: 0o600 })
|
|
239
|
+
return p
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function auditOf({ cmd, flow, action, pause, task, stored, result, why }) {
|
|
243
|
+
return {
|
|
244
|
+
ts: new Date().toISOString(),
|
|
245
|
+
tool: 'argent-schedule',
|
|
246
|
+
cmd,
|
|
247
|
+
mode: APPLY ? 'apply' : 'dry-run',
|
|
248
|
+
flow: flow ? flow.flowId : '',
|
|
249
|
+
localId: flow ? flow.localId : '',
|
|
250
|
+
action,
|
|
251
|
+
pauseFile: pause || '',
|
|
252
|
+
pausePresent: Boolean(pause),
|
|
253
|
+
taskId: task ? task.id : '',
|
|
254
|
+
sessionId: task ? task.sessionId : '',
|
|
255
|
+
taskStatus: task ? task.status : '',
|
|
256
|
+
// ⚠️ catalog 条目**不是**存储里的那一条记录(字段形状不同)⇒ 两样都留:
|
|
257
|
+
// · `taskStored` = 排程域文件里的**整条记录**(`{sessionId, record, status, lastDelivery, deliveryHistory}`)
|
|
258
|
+
// —— 这才是"关停之后还能照着重建"的东西(`schedule/delete` 会把行连投递历史一起删掉);
|
|
259
|
+
// · `catalogEntry` = 当时宿主 `catalog()` 看到的那条(回执/状态的第一手证据)。
|
|
260
|
+
taskStored: stored || null,
|
|
261
|
+
catalogEntry: task || null,
|
|
262
|
+
result: result || null,
|
|
263
|
+
why: why || '',
|
|
264
|
+
by: `${(() => { try { return userInfo().username } catch { return 'unknown' } })()}@${hostname()}`,
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** 从排程域文件里取某条任务的**完整存储记录**(找不到 ⇒ null;只读)。 */
|
|
269
|
+
function storedTask(taskId) {
|
|
270
|
+
if (!taskId) return null
|
|
271
|
+
try {
|
|
272
|
+
const paths = domainPaths(storageRoot({ dshHome: DSH_HOME || undefined }))
|
|
273
|
+
const hit = inspectDomain(paths).tasks.find((t) => t.id === taskId)
|
|
274
|
+
return hit ? hit.task : null
|
|
275
|
+
} catch { return null }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 跑一次**机制判据**(唯一实现 = `scripts/lib/schedule-mechanism.mjs`)。
|
|
280
|
+
* ⛔ 本函数只**取数 + 传参**:声明面(注册表 `schedule` 段)与证据(原生包 / 已装计划任务)
|
|
281
|
+
* 都由 L1 判据读;本文件不写第二份判据。
|
|
282
|
+
*/
|
|
283
|
+
async function readMechanism() {
|
|
284
|
+
return detectScheduleMechanism({
|
|
285
|
+
repo: REPO,
|
|
286
|
+
dshHome: DSH_HOME || undefined,
|
|
287
|
+
profile: PROFILE,
|
|
288
|
+
scopeDirs: SCOPE_DIRS.length ? SCOPE_DIRS : null,
|
|
289
|
+
// ⭐ F1 修复的接线点:`--scope-dir` 一个都没给 ⇒ 注入 npx 感知的候选链(L2 真源)。
|
|
290
|
+
// ⛔ 纯探针(零写盘、零 spawn);探不到仍如实 `unknown`(见 L1 探针的三条硬纪律)。
|
|
291
|
+
scopeDirsProbe: SCOPE_DIRS.length ? null : () => runtimeHostScopeDirs(),
|
|
292
|
+
launchAgentsDir: LAUNCH_AGENTS_DIR || '',
|
|
293
|
+
env: process.env,
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* **运行时宿主 scope 候选**(npx 感知;给 native 探针用)。
|
|
299
|
+
*
|
|
300
|
+
* 顺序(决定"哪一棵算这台机器在跑的那棵"):
|
|
301
|
+
* ① 真源解析出的那棵(`resolveDshScope()` —— 显式 > PATH 反推 > profile/global > **npx 缓存**);
|
|
302
|
+
* ② 链上**其余的 profile 形态**候选(保持"现役机器 profile 优先"的老语义);
|
|
303
|
+
* ③ 链上其余候选(node 前缀 / homebrew / Intel 兜底 —— 现役判据里它们本来就在)。
|
|
304
|
+
* ⛔ 本函数只**产出候选**,不参与判据(判据在 L1 `probeNativeSchedule`)。
|
|
305
|
+
*/
|
|
306
|
+
function runtimeHostScopeDirs() {
|
|
307
|
+
let hit = null
|
|
308
|
+
try { hit = resolveDshScope() } catch { hit = null }
|
|
309
|
+
const npxRoots = npxCacheDirs({ env: process.env })
|
|
310
|
+
const chain = autoScopeCandidates()
|
|
311
|
+
const profiles = chain.filter((c) => scopeSourceOf(c.dir, npxRoots) === 'profile').map((c) => c.dir)
|
|
312
|
+
const rest = chain.filter((c) => scopeSourceOf(c.dir, npxRoots) !== 'profile').map((c) => c.dir)
|
|
313
|
+
const resolved = hit && !hit.stale ? [hit.scope] : []
|
|
314
|
+
const out = []
|
|
315
|
+
for (const p of [...resolved, ...profiles, ...rest]) if (p && !out.includes(p)) out.push(p)
|
|
316
|
+
return out
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function cmdMechanism() {
|
|
320
|
+
const r = await readMechanism()
|
|
321
|
+
const gate = gateSchedulerConflict(r)
|
|
322
|
+
const sum = summarize([gate])
|
|
323
|
+
const payload = {
|
|
324
|
+
ok: gate.state === 'green', tool: 'argent-schedule', cmd: 'mechanism',
|
|
325
|
+
state: r.state, declared: r.declared, reason: r.reason, next: r.next || [],
|
|
326
|
+
judge: 'scripts/lib/schedule-mechanism.mjs#resolveScheduleMechanism(唯一实现)',
|
|
327
|
+
registryHow: r.how || '', flows: r.flows || [],
|
|
328
|
+
native: r.native || null, shim: r.shim || null, conflict: r.conflict || null,
|
|
329
|
+
gate, exitCode: gate.state === 'red' ? 1 : (gate.state === 'yellow' ? 2 : 0),
|
|
330
|
+
}
|
|
331
|
+
if (AS_JSON) out(payload)
|
|
332
|
+
else {
|
|
333
|
+
say(`🔎 定时机制(唯一判据:${payload.judge})`)
|
|
334
|
+
say(` 声明面:${payload.registryHow || '(读不到)'}`)
|
|
335
|
+
say(` ${mechanismVerdictLine(r)}`)
|
|
336
|
+
if (r.native) say(` · 原生探针:state=${r.native.state}${r.native.source ? `|候选来源=${r.native.source}` : ''}|${r.native.reason}`)
|
|
337
|
+
if (r.shim) say(` · 垫片探针:strength=${r.shim.strength}|${r.shim.reason}`)
|
|
338
|
+
if (r.state === 'conflict') {
|
|
339
|
+
say(` ${gate.headline}`)
|
|
340
|
+
for (const d of gate.detail || []) say(` · ${d}`)
|
|
341
|
+
if (gate.closeOne) say(` ↳ 关掉一条(二选一,⛔ 别两条都留):\n${gate.closeOne}`)
|
|
342
|
+
}
|
|
343
|
+
for (const n of r.next || []) say(` ↳ 下一步:${n}`)
|
|
344
|
+
}
|
|
345
|
+
return payload.exitCode
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ── 子命令 ─────────────────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
async function cmdProbe() {
|
|
351
|
+
const { mount, auth } = await mountOnly()
|
|
352
|
+
const gate = gateMount(mount)
|
|
353
|
+
const payload = {
|
|
354
|
+
ok: gate.state === 'green', tool: 'argent-schedule', cmd: 'probe', base: BASE,
|
|
355
|
+
endpoint: MOUNT_ENDPOINT, gate, authSource: auth.ok ? auth.source : null,
|
|
356
|
+
authTried: auth.ok ? undefined : auth.tried,
|
|
357
|
+
}
|
|
358
|
+
if (AS_JSON) out(payload)
|
|
359
|
+
else {
|
|
360
|
+
say(`🔎 排程挂载探针(${BASE})`)
|
|
361
|
+
say(formatGates(summarize([gate])))
|
|
362
|
+
if (!auth.ok) say(`\n⚠️ 鉴权链全部失败(下面列出试过的来源):\n${authFailureText({ tried: auth.tried, base: BASE })}`)
|
|
363
|
+
}
|
|
364
|
+
return gate.state === 'green' ? 0 : (gate.state === 'red' ? 1 : 2)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function cmdStatus() {
|
|
368
|
+
const reg = await readRegistry()
|
|
369
|
+
const all = declaredSchedules(reg.doc)
|
|
370
|
+
const flows = pickFlows(all)
|
|
371
|
+
const cat = await readCatalog()
|
|
372
|
+
const mountGate = gateMount(cat.mount)
|
|
373
|
+
const rows = flows.map((f) => {
|
|
374
|
+
const m = cat.catalog ? matchTask(cat.catalog, { flowId: f.flowId, alternatives: [f.localId], taskTitle: f.taskTitle }) : { entry: null, by: 'none' }
|
|
375
|
+
const e = m.entry
|
|
376
|
+
return {
|
|
377
|
+
flow: f.flowId, localId: f.localId, enabled: f.enabled, declaredKind: f.kind, taskTitle: f.taskTitle,
|
|
378
|
+
matchBy: m.by, found: Boolean(e),
|
|
379
|
+
task: e ? { id: e.id, kind: e.kind, status: e.status, scheduledAt: e.scheduledAt, time: e.time, timeZone: e.timeZone, everySeconds: e.everySeconds, expression: e.expression, sessionId: e.sessionId, lastDelivery: e.lastDelivery } : null,
|
|
380
|
+
periodSec: e ? (Number.isFinite(PERIOD_ARG) ? PERIOD_ARG : periodSecondsOf(e)) : null,
|
|
381
|
+
pauseFile: pausePresent(f) || '',
|
|
382
|
+
nextAction: e ? '' : '宿主里还没有这条任务(P1-B:装机/升级时建;create 只在会话内可用)',
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
const payload = {
|
|
386
|
+
ok: mountGate.state === 'green', tool: 'argent-schedule', cmd: 'status', base: BASE,
|
|
387
|
+
registryHow: reg.how, registryError: reg.error || null,
|
|
388
|
+
declared: all.length, enabled: all.filter((f) => f.enabled).length, rows,
|
|
389
|
+
mount: mountGate, catalogReadable: Boolean(cat.catalog),
|
|
390
|
+
statusSetterAvailable: STATUS_SETTER_AVAILABLE,
|
|
391
|
+
}
|
|
392
|
+
if (AS_JSON) out(payload)
|
|
393
|
+
else {
|
|
394
|
+
say(`🔎 排程状态(${BASE})|注册表:${reg.how}${reg.error ? `(❌ ${reg.error.split('\n')[0]})` : ''}`)
|
|
395
|
+
if (!cat.catalog) say(` ⚠️ ${mountGate.headline} ⇒ 读不到任务清单(下面只列声明面)`)
|
|
396
|
+
say(` 声明了 trigger.schedule 的流程:${all.length} 条(enabled:${all.filter((f) => f.enabled).length})`)
|
|
397
|
+
for (const r of rows) {
|
|
398
|
+
say(` · ${r.flow}${r.enabled ? '' : '(enabled=false)'}|kind=${r.declaredKind}${r.timeZone ? ` @${r.timeZone}` : ''}`)
|
|
399
|
+
if (!r.found) say(` ⛔ 宿主里没有对应任务(匹配方式:${r.matchBy})—— ${r.nextAction}`)
|
|
400
|
+
else {
|
|
401
|
+
say(` ✅ 任务 ${r.task.id}|status=${r.task.status}|${r.task.kind}${r.task.time ? ` ${r.task.time}@${r.task.timeZone}` : ''}${r.task.everySeconds ? ` every=${r.task.everySeconds}s` : ''}`)
|
|
402
|
+
say(` 下一跳 ${r.task.scheduledAt}|周期 ${r.periodSec === null ? '推不出(用 --period-sec)' : `${r.periodSec}s`}|会话 ${r.task.sessionId}`)
|
|
403
|
+
say(` 上次投递 ${r.task.lastDelivery ? r.task.lastDelivery.deliveredAt : '(还没有)'}`)
|
|
404
|
+
}
|
|
405
|
+
if (r.pauseFile) say(` ⏸ 暂停开关在:${r.pauseFile}`)
|
|
406
|
+
}
|
|
407
|
+
if (!STATUS_SETTER_AVAILABLE) say(' ℹ️ 宿主没有"置 inactive"的远端端点 ⇒ 停 = `schedule/delete`(见 pause --apply 的说明)')
|
|
408
|
+
}
|
|
409
|
+
return mountGate.state === 'green' ? 0 : (mountGate.state === 'red' ? 1 : 2)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function cmdCheck() {
|
|
413
|
+
const reg = await readRegistry()
|
|
414
|
+
const all = declaredSchedules(reg.doc)
|
|
415
|
+
const flows = pickFlows(all)
|
|
416
|
+
const cat = await readCatalog()
|
|
417
|
+
const gates = [gateMount(cat.mount)]
|
|
418
|
+
const perFlow = []
|
|
419
|
+
|
|
420
|
+
const paths = domainPaths(storageRoot({ dshHome: DSH_HOME || undefined }))
|
|
421
|
+
const inspection = inspectDomain(paths)
|
|
422
|
+
const backupRoots = [join(DATA_DIR, 'backup'), join(DATA_DIR, 'backups')]
|
|
423
|
+
|
|
424
|
+
if (!flows.length) {
|
|
425
|
+
gates.push({
|
|
426
|
+
gate: 'freshness', state: 'green',
|
|
427
|
+
headline: all.length
|
|
428
|
+
? `没有 enabled=true 的流程声明(声明了 ${all.length} 条但都是 enabled=false)⇒ 无需判新鲜度`
|
|
429
|
+
: '本机没有任何流程声明 trigger.schedule ⇒ 排程面未启用(无需判新鲜度)',
|
|
430
|
+
detail: [], next: [],
|
|
431
|
+
})
|
|
432
|
+
gates.push({
|
|
433
|
+
gate: 'execution', state: 'green',
|
|
434
|
+
headline: all.length ? '同上(没有启用的声明)' : '排程面未启用 ⇒ 没有"投递了没人干活"的对象',
|
|
435
|
+
detail: [], next: [],
|
|
436
|
+
})
|
|
437
|
+
} else {
|
|
438
|
+
for (const f of flows) {
|
|
439
|
+
const m = cat.catalog ? matchTask(cat.catalog, { flowId: f.flowId, alternatives: [f.localId], taskTitle: f.taskTitle }) : { entry: null, by: 'none' }
|
|
440
|
+
const e = m.entry
|
|
441
|
+
const period = Number.isFinite(PERIOD_ARG) ? PERIOD_ARG : (e ? periodSecondsOf(e) : null)
|
|
442
|
+
const label = `${f.flowId}(${f.taskTitle || f.kind})`
|
|
443
|
+
const fr = gateFreshness({ entry: e, nowMs: NOW_MS, periodSec: period, label })
|
|
444
|
+
if (!e && cat.catalog) {
|
|
445
|
+
fr.detail = [`声明:kind=${f.kind}${f.timeZone ? ` @${f.timeZone}` : ''}${f.time ? ` ${f.time}` : ''};宿主 catalog 里 ${cat.catalog.length} 条任务都没有匹配上(匹配方式 ${m.by})`,
|
|
446
|
+
'现状 = 试点**声明已就位、任务未建**(P1-A 明确不切投递)⇒ 这一条**不是红**,但它说明"排程还没真的在跑"']
|
|
447
|
+
fr.next = ['P1-B:先确认宿主已挂载(① 绿)⇒ 再由**绑定的会话**用 schedule_create 建任务(title 必须等于注册表 taskTitle)']
|
|
448
|
+
}
|
|
449
|
+
const { logPath, ledgerPath } = l1Paths(f)
|
|
450
|
+
const ev = readL1Evidence({ logPath, ledgerPath, flowIds: [f.flowId, f.localId] })
|
|
451
|
+
const ex = gateExecution({ entry: e, evidence: ev, nowMs: NOW_MS, graceSec: GRACE_SEC, label })
|
|
452
|
+
gates.push(fr, ex)
|
|
453
|
+
perFlow.push({ flow: f.flowId, matchBy: m.by, taskId: e ? e.id : null, periodSec: period, l1: { log: ev.log, ledger: ev.ledger, newestMs: ev.newestMs } })
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
gates.push(gatePersistence(inspection, { backupRoots }))
|
|
457
|
+
// ⑤ 单一调度者(0.6.0 W8)—— 判据唯一实现在 scripts/lib/schedule-mechanism.mjs
|
|
458
|
+
let mechanism = null
|
|
459
|
+
try {
|
|
460
|
+
mechanism = await readMechanism()
|
|
461
|
+
gates.push(gateSchedulerConflict(mechanism))
|
|
462
|
+
} catch (e) {
|
|
463
|
+
gates.push(gateSchedulerConflict(null, { label: `判据跑不起来(${String((e && e.message) || e).slice(0, 120)})` }))
|
|
464
|
+
}
|
|
465
|
+
const sum = summarize(gates)
|
|
466
|
+
const payload = {
|
|
467
|
+
ok: sum.exitCode === 0, tool: 'argent-schedule', cmd: 'check', base: BASE, now: new Date(NOW_MS).toISOString(),
|
|
468
|
+
exitCode: sum.exitCode, verdict: verdictLine(sum),
|
|
469
|
+
counts: { green: sum.green.length, red: sum.red.length, yellow: sum.yellow.length, unknown: sum.unknown.length },
|
|
470
|
+
gates, perFlow, registryHow: reg.how, registryError: reg.error || null,
|
|
471
|
+
domain: { state: inspection.state, paths: inspection.paths, files: inspection.files.map((x) => x.path), problems: inspection.problems },
|
|
472
|
+
mechanism: mechanism ? { state: mechanism.state, declared: mechanism.declared, reason: mechanism.reason, registryHow: mechanism.how } : null,
|
|
473
|
+
statusSetterAvailable: STATUS_SETTER_AVAILABLE,
|
|
474
|
+
}
|
|
475
|
+
if (AS_JSON) out(payload)
|
|
476
|
+
else {
|
|
477
|
+
say(`🔎 排程面四道闸(${BASE})|现在 ${payload.now}`)
|
|
478
|
+
say(formatGates(sum))
|
|
479
|
+
say(`\n${verdictLine(sum)}(exit ${sum.exitCode})`)
|
|
480
|
+
if (reg.error) say(` ⚠️ 注册表读不到(${reg.how}):${String(reg.error).split('\n')[0]}`)
|
|
481
|
+
}
|
|
482
|
+
return sum.exitCode
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** `pause` / `apply` 的公共收敛核(**默认 dry-run**;`--apply` 才写)。 */
|
|
486
|
+
async function converge(cmd) {
|
|
487
|
+
const reg = await readRegistry()
|
|
488
|
+
const all = declaredSchedules(reg.doc)
|
|
489
|
+
const flows = pickFlows(all)
|
|
490
|
+
if (APPLY && IS_REAL_DATA_DIR && !ALLOW_REAL) {
|
|
491
|
+
return {
|
|
492
|
+
exitCode: 2,
|
|
493
|
+
payload: {
|
|
494
|
+
ok: false, tool: 'argent-schedule', cmd, error: 'refused',
|
|
495
|
+
why: `--apply 会写真实数据目录(${DATA_DIR})的审计并**删宿主任务**;本批纪律是"真实 ~/.dsh 零增量"`,
|
|
496
|
+
next: ['夹具/隔离:显式给 --data-dir <临时目录>(并配 --dsh-home <临时目录>)',
|
|
497
|
+
'确实要在本机收敛:显式加 --allow-real-home(并先 `probe`/`status` 看清楚要动哪条任务)'],
|
|
498
|
+
},
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const cat = await readCatalog()
|
|
502
|
+
const actions = []
|
|
503
|
+
for (const f of flows) {
|
|
504
|
+
const pause = pausePresent(f)
|
|
505
|
+
const m = cat.catalog ? matchTask(cat.catalog, { flowId: f.flowId, alternatives: [f.localId], taskTitle: f.taskTitle }) : { entry: null, by: 'none' }
|
|
506
|
+
const e = m.entry
|
|
507
|
+
const wantStopped = Boolean(pause)
|
|
508
|
+
// ── 停:文件在 ⇒ 任务不该再触发 ──
|
|
509
|
+
if (wantStopped && e && e.status === 'active') {
|
|
510
|
+
let result = null
|
|
511
|
+
let action = 'stop'
|
|
512
|
+
if (APPLY) {
|
|
513
|
+
const r = await postRpc({ base: BASE, endpoint: ENDPOINTS.delete, args: { request: { id: e.id, sessionId: e.sessionId } }, cookie: (await getCookie()).cookie })
|
|
514
|
+
result = { state: r.state, httpStatus: r.httpStatus, value: r.value, code: r.code, message: r.message }
|
|
515
|
+
if (r.state === 'ok' && r.value && r.value.deleted === true) action = 'stop'
|
|
516
|
+
else if (r.state === 'ok') action = 'stop-noop' // schedule_not_found:任务已不在(幂等)
|
|
517
|
+
else action = 'stop-failed'
|
|
518
|
+
}
|
|
519
|
+
const rec = auditOf({ cmd, flow: f, action, pause, task: e, stored: storedTask(e.id), result, why: wantStopped ? `暂停开关存在(${pause})⇒ 收敛为"停"` : '' })
|
|
520
|
+
// ⛔ dry-run **绝不写盘**(否则"默认 dry-run"会在真实数据目录留下文件 —— 本批纪律是"真实 ~/.dsh 零增量")
|
|
521
|
+
const p = APPLY ? writeAudit(rec) : ''
|
|
522
|
+
actions.push({ flow: f.flowId, action, pauseFile: pause, taskId: e.id, applied: APPLY, result, audit: p, auditPreview: rec })
|
|
523
|
+
continue
|
|
524
|
+
}
|
|
525
|
+
if (wantStopped && !e) {
|
|
526
|
+
const rec = auditOf({ cmd, flow: f, action: 'noop', pause, task: null, stored: null, result: null, why: '暂停开关存在,但宿主里没有这条任务 ⇒ 无需收敛(幂等)' })
|
|
527
|
+
actions.push({ flow: f.flowId, action: 'noop', pauseFile: pause, taskId: '', applied: APPLY, audit: APPLY ? writeAudit(rec) : '', auditPreview: rec })
|
|
528
|
+
continue
|
|
529
|
+
}
|
|
530
|
+
// ── 启:文件不在 ⇒ 任务应当存在且 active ──
|
|
531
|
+
if (!wantStopped && !e) {
|
|
532
|
+
const args = scheduleCreateArgs(f)
|
|
533
|
+
const rec = auditOf({ cmd, flow: f, action: 'create-required', pause, task: null, stored: null, result: { toolArgs: args }, why: '没有暂停开关,宿主里也没有任务;但 schedule/create **不是远端端点**(只在会话内由工具调用)⇒ CLI 建不了' })
|
|
534
|
+
const p = APPLY ? writeAudit(rec) : ''
|
|
535
|
+
actions.push({ flow: f.flowId, action: 'create-required', pauseFile: '', taskId: '', applied: APPLY, toolArgs: args, audit: p, auditPreview: rec })
|
|
536
|
+
continue
|
|
537
|
+
}
|
|
538
|
+
if (!wantStopped && e && e.status !== 'active') {
|
|
539
|
+
const rec = auditOf({ cmd, flow: f, action: 'dead-task', pause, task: e, stored: storedTask(e.id), result: null, why: `任务 status=${e.status}(重复型任务唯一的 inactive 来源是"算不出下一次发生")⇒ 需要重建;CLI 建不了(create 不是远端端点)` })
|
|
540
|
+
const p = APPLY ? writeAudit(rec) : ''
|
|
541
|
+
actions.push({ flow: f.flowId, action: 'dead-task', pauseFile: '', taskId: e.id, applied: APPLY, toolArgs: scheduleCreateArgs(f), audit: p, auditPreview: rec })
|
|
542
|
+
continue
|
|
543
|
+
}
|
|
544
|
+
const rec = auditOf({ cmd, flow: f, action: 'noop', pause, task: e, stored: storedTask(e.id), result: null, why: wantStopped ? '已停(任务不在或非 active)' : '任务在且 active ⇒ 与开关一致' })
|
|
545
|
+
actions.push({ flow: f.flowId, action: 'noop', pauseFile: pause, taskId: e ? e.id : '', applied: APPLY, audit: APPLY ? writeAudit(rec) : '', auditPreview: rec })
|
|
546
|
+
}
|
|
547
|
+
const needSession = actions.some((a) => ['create-required', 'dead-task'].includes(a.action))
|
|
548
|
+
const failed = actions.some((a) => a.action === 'stop-failed')
|
|
549
|
+
const exitCode = failed ? 1 : (needSession ? 2 : 0)
|
|
550
|
+
return {
|
|
551
|
+
exitCode,
|
|
552
|
+
payload: {
|
|
553
|
+
ok: exitCode === 0, tool: 'argent-schedule', cmd, mode: APPLY ? 'apply' : 'dry-run', base: BASE,
|
|
554
|
+
dataDir: DATA_DIR, audit: APPLY ? auditPath() : null, actions,
|
|
555
|
+
statusSetterAvailable: STATUS_SETTER_AVAILABLE,
|
|
556
|
+
convergenceWindow: '⚠️ PAUSE 文件与任务状态之间**不是原子的**:文件刚落地、收敛还没跑的窗口里,定时可能照跑一轮 ⇒ 改开关后要主动跑一次收敛(写进 SOP)',
|
|
557
|
+
notes: [
|
|
558
|
+
`停 = \`schedule/delete\`(宿主没有"置 inactive"的远端端点);被删的**整条记录**${APPLY ? '已写进审计(可据此重建)' : '会在 --apply 时写进审计(dry-run ⛔ 不写盘)'}`,
|
|
559
|
+
needSession && cmd === 'apply'
|
|
560
|
+
? '启 = **需要会话内动作**:`schedule_create` 只在会话里可用(本 CLI 无远端端点)⇒ 见 actions[].toolArgs'
|
|
561
|
+
: (needSession ? '(pause 只做"停"方向;"启"方向由 `apply` 负责)' : ''),
|
|
562
|
+
].filter(Boolean),
|
|
563
|
+
},
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** 会话内建任务时该调用的工具参数(**从注册表声明生成** —— 不在这里另写一份真源)。 */
|
|
568
|
+
export function scheduleCreateArgs(flow) {
|
|
569
|
+
const s = flow.schedule || {}
|
|
570
|
+
const args = { title: flow.taskTitle || `${flow.title} · 宿主排程` }
|
|
571
|
+
switch (flow.kind) {
|
|
572
|
+
case 'every': args.every_seconds = flow.intervalSec; break
|
|
573
|
+
case 'daily': args.daily = { time: s.time, time_zone: s.timeZone }; break
|
|
574
|
+
case 'weekly': args.weekly = { time: s.time, time_zone: s.timeZone, weekdays: s.weekdays }; break
|
|
575
|
+
case 'cron': args.cron = { expression: s.expression, time_zone: s.timeZone }; break
|
|
576
|
+
case 'at': args.at = s.at; break
|
|
577
|
+
default: break
|
|
578
|
+
}
|
|
579
|
+
return { tool: 'schedule_create', args, promptFile: flow.promptFile, note: '提示词由宿主在**创建时固定**(静态)⇒ prompt 必须是"调用一条 L1 命令",⛔ 不要带本轮票号' }
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async function cmdConverge(cmd) {
|
|
583
|
+
const { exitCode, payload } = await converge(cmd)
|
|
584
|
+
if (AS_JSON) out(payload)
|
|
585
|
+
else {
|
|
586
|
+
say(`🔎 排程收敛(${cmd},${APPLY ? '**apply**' : 'dry-run'})|数据目录 ${DATA_DIR}`)
|
|
587
|
+
if (payload.error) {
|
|
588
|
+
say(` ⛔ 拒绝执行:${payload.why}`)
|
|
589
|
+
for (const n of payload.next || []) say(` ↳ ${n}`)
|
|
590
|
+
} else {
|
|
591
|
+
if (!payload.actions.length) say(' (没有 enabled=true 的流程声明;用 --flow <id> 指定单条)')
|
|
592
|
+
for (const a of payload.actions) {
|
|
593
|
+
const tag = { stop: '⏹ 停', 'stop-noop': '⏹ 已是停(幂等)', 'stop-failed': '❌ 停失败', 'create-required': '🆕 需要建任务', 'dead-task': '💀 任务已死', noop: '✅ 无需动作' }[a.action] || a.action
|
|
594
|
+
say(` ${tag} ${a.flow}${a.taskId ? `(任务 ${a.taskId})` : ''}${a.pauseFile ? `|开关 ${a.pauseFile}` : ''}`)
|
|
595
|
+
if (a.result) say(` 结果:${JSON.stringify(a.result)}`)
|
|
596
|
+
if (a.toolArgs) say(` 会话内建任务参数:${JSON.stringify(a.toolArgs.args)}(prompt 取 ${a.toolArgs.promptFile})`)
|
|
597
|
+
if (a.audit) say(` 审计:${a.audit}`)
|
|
598
|
+
}
|
|
599
|
+
say(`\n ${payload.convergenceWindow}`)
|
|
600
|
+
for (const n of payload.notes || []) say(` ℹ️ ${n}`)
|
|
601
|
+
if (!APPLY) say(' (默认 dry-run:加 --apply 才真的删任务/写审计)')
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return exitCode
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// ── main ───────────────────────────────────────────────────────────────────
|
|
608
|
+
try {
|
|
609
|
+
const code = CMD === 'probe' ? await cmdProbe()
|
|
610
|
+
: CMD === 'status' ? await cmdStatus()
|
|
611
|
+
: CMD === 'check' ? await cmdCheck()
|
|
612
|
+
: CMD === 'mechanism' ? await cmdMechanism()
|
|
613
|
+
: await cmdConverge(CMD)
|
|
614
|
+
process.exit(code)
|
|
615
|
+
} catch (e) {
|
|
616
|
+
const payload = { ok: false, tool: 'argent-schedule', cmd: CMD, error: String((e && e.stack) || e) }
|
|
617
|
+
if (AS_JSON) out(payload)
|
|
618
|
+
else process.stderr.write(`❌ argent-schedule ${CMD} 失败:${payload.error}\n`)
|
|
619
|
+
process.exit(2)
|
|
620
|
+
}
|