@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,275 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""新货代脚手架(迭代 2-步骤 3):把"接一个新货代 = 改 9 处"压到"3 处"。
|
|
3
|
+
|
|
4
|
+
## 生成什么(幂等:已存在的文件**不覆盖**,只报告)
|
|
5
|
+
1. `scripts/integrations/<name>/adapter.py` —— 适配器骨架,`STATUS = "scaffold"`
|
|
6
|
+
(契约门禁会**故意拦住**它,直到你填完门户细节并改成 `"ready"`);
|
|
7
|
+
2. `presets/argent-<role>/` —— 从 `--from-flow` 的 preset 复制(agent.cordis.yml / preset.yml /
|
|
8
|
+
format-version.txt),顶部加 TODO 提示:persona 里仍有源货代的文字,需要改;
|
|
9
|
+
3. `scripts/flow-prompts/<flow>.md` 与 `<flow>.failure.md` —— 从源流程复制。
|
|
10
|
+
⚠️ 这两份**已经是载体无关的模板**(迭代 2-步骤 2 起用 `{{integration}}`/`{{leaseMin}}` 变量),
|
|
11
|
+
所以复制即可用,通常不需要改;
|
|
12
|
+
4. 注册表片段(`--apply` 时合并进 `scripts/flow-requirements.json`,写前备份)。
|
|
13
|
+
|
|
14
|
+
## 你还要做的 3 件事
|
|
15
|
+
① 填门户细节:适配器的 `RUNNERS` / `MANUAL_GATES` / `ERROR_CLASSES` / `TEMPLATE`,
|
|
16
|
+
并实现/接入 runner;做完把 `STATUS` 改成 `"ready"`;
|
|
17
|
+
② 填模板映射(若该货代需要填模板);
|
|
18
|
+
③ 跑门禁:`integration_contract.py --validate` → `check-role-mapping.mjs` →
|
|
19
|
+
`check-sop-consistency.mjs` → `tests/run-all-tests.sh`。
|
|
20
|
+
|
|
21
|
+
用法:
|
|
22
|
+
python3 scripts/flow-scaffold.py --name xinlong --carrier "鑫隆" --title "鑫隆发票流程" [--apply]
|
|
23
|
+
python3 scripts/flow-scaffold.py --name demo --carrier "演示货代" --out-dir /tmp/scaffold --apply
|
|
24
|
+
退出码:0 成功;1 有已存在文件(未覆盖);2 参数错误。
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import json
|
|
30
|
+
import pathlib
|
|
31
|
+
import shutil
|
|
32
|
+
import sys
|
|
33
|
+
import time
|
|
34
|
+
|
|
35
|
+
HERE = pathlib.Path(__file__).resolve().parent
|
|
36
|
+
REPO = HERE.parent
|
|
37
|
+
REGISTRY = HERE / "flow-requirements.json"
|
|
38
|
+
|
|
39
|
+
ADAPTER_TMPL = '''#!/usr/bin/env python3
|
|
40
|
+
"""{title} 集成适配器(**脚手架生成,尚未实现**)。
|
|
41
|
+
|
|
42
|
+
门户:<TODO:门户地址/系统说明>
|
|
43
|
+
执行入口:<TODO:runner 脚本名(放 scripts/ 下,并在 RUNNERS 里声明)>
|
|
44
|
+
|
|
45
|
+
⚠️ 完成前请保持 `STATUS = "scaffold"` —— 契约门禁会拦住未完成的集成
|
|
46
|
+
(`python3 scripts/integration_contract.py --validate` 会点名缺什么)。
|
|
47
|
+
契约见 `scripts/integration_contract.py`。
|
|
48
|
+
"""
|
|
49
|
+
from __future__ import annotations
|
|
50
|
+
|
|
51
|
+
NAME = "{name}"
|
|
52
|
+
STATUS = "scaffold" # 完成后改为 "ready"
|
|
53
|
+
CARRIER = "{carrier}" # 必须与注册表 routing.match 完全一致
|
|
54
|
+
CAPABILITIES = {{"invoice": True, "labels": True, "portal_confirm": True}}
|
|
55
|
+
|
|
56
|
+
MANUAL_GATES = [
|
|
57
|
+
{{"what": "<TODO:门户账号由谁维护 / 是否有验证码或人工确认>", "who": "运营",
|
|
58
|
+
"why": "对方生产系统,凭据不进仓库/不进对话", "how": "<TODO>"}},
|
|
59
|
+
{{"what": "HS 编码等报关敏感字段由运营人工维护", "who": "运营",
|
|
60
|
+
"why": "红线:系统不预填/不写候选/不自动确认", "how": "运营在表里填好后重勾「发票就绪」"}},
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# 门户错误文案 → 失败类(retryable / needs_human / needs_agent):先抄通用几条,按门户实际报错补
|
|
64
|
+
ERROR_CLASSES = [
|
|
65
|
+
(r"net::ERR|ECONNRESET|ETIMEDOUT|503|502|429", "retryable"),
|
|
66
|
+
(r"登录|Login|账号|密码|未授权", "needs_human"),
|
|
67
|
+
(r"参考单号.*重复|已存在", "needs_agent"),
|
|
68
|
+
(r"等待选择器超时|选择器超时|locator.*(timeout|超时)", "needs_agent"),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
RUNNERS = {{}} # TODO:{{"全链路": "<你的 runner>.py"}}(脚本必须真实存在)
|
|
72
|
+
TEMPLATE = "" # TODO:模板/映射文件(不需要则留空)
|
|
73
|
+
WRITEBACK_FLOW = "{flow}"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def match(row: dict) -> bool:
|
|
77
|
+
return str((row or {{}}).get("货代公司") or "").strip() == CARRIER
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def preflight(row: dict, cfg: dict | None = None) -> dict:
|
|
81
|
+
"""缺口分型:TODO 按该货代的必填字段补(运营填 / 我方补)。"""
|
|
82
|
+
row = row or {{}}
|
|
83
|
+
ops = [n for n in ("HS编码", "产品名称", "SKU", "数量", "箱数") if not str(row.get(n) or "").strip()]
|
|
84
|
+
ours = [] if str(row.get("货件号") or "").strip() else ["货件号(应由 FULL 流程回填)"]
|
|
85
|
+
return {{"ours": ours, "ops": ops}}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def plan(row: dict, cfg: dict | None = None) -> list:
|
|
89
|
+
"""可执行计划:TODO 换成该货代真实的命令(先只读核对、再真跑)。"""
|
|
90
|
+
row = row or {{}}
|
|
91
|
+
rec = str(row.get("_id") or row.get("record_id") or "")
|
|
92
|
+
if not rec:
|
|
93
|
+
raise ValueError("plan() 需要 record_id")
|
|
94
|
+
return [
|
|
95
|
+
{{"phase": "核对(只读)", "script": "<TODO: runner>.py", "argv": ["--record-id", rec, "--dry-run"],
|
|
96
|
+
"writes": False, "why": "先只读核对,不写表、不碰门户"}},
|
|
97
|
+
{{"phase": "真跑", "script": "<TODO: runner>.py", "argv": ["--record-id", rec, "--yes"],
|
|
98
|
+
"writes": True, "why": "门户上传 → 下载标签 → 回写"}},
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def classify_error(text: str) -> str:
|
|
103
|
+
import re
|
|
104
|
+
for pat, kind in ERROR_CLASSES:
|
|
105
|
+
if re.search(pat, str(text or ""), re.I):
|
|
106
|
+
return kind
|
|
107
|
+
return "needs_agent"
|
|
108
|
+
'''
|
|
109
|
+
|
|
110
|
+
PRESET_TODO = """# ⚠️ 本 preset 由 `scripts/flow-scaffold.py` 从 `{src_role}` 复制({ts})
|
|
111
|
+
# TODO:persona 里仍有源货代的描述(门户地址/流程细节),请按 {carrier} 的实际情况改写;
|
|
112
|
+
# 并把流程 id 从 `{src_flow}` 全文替换为 `{flow}`(若还有遗漏)。
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def build_registry_fragment(flow: str, name: str, carrier: str, title: str, role: str, reg: dict) -> dict:
|
|
117
|
+
"""按能力层派生一条 flow 条目(required 的 role 从 capability 共同集 + 该货代额外项派生)。"""
|
|
118
|
+
caps = (reg.get("capabilities") or {}).get("invoice") or {}
|
|
119
|
+
common = list(caps.get("requiredCommon") or [])
|
|
120
|
+
states = list(caps.get("stateFields") or [])
|
|
121
|
+
required = [{"name": n, "role": "input", "owner": "运营"} for n in common]
|
|
122
|
+
required += [{"name": n, "role": "state", "owner": "运营"} for n in states]
|
|
123
|
+
return {
|
|
124
|
+
flow: {
|
|
125
|
+
"title": title,
|
|
126
|
+
"capability": "invoice",
|
|
127
|
+
"integration": name,
|
|
128
|
+
"role": role,
|
|
129
|
+
"routing": {"routeBy": "货代公司", "match": carrier},
|
|
130
|
+
"source": {
|
|
131
|
+
"kind": "feishu-base",
|
|
132
|
+
"envFile": "fulfillment.env",
|
|
133
|
+
"baseEnv": "FULL_BASE_TOKEN",
|
|
134
|
+
"tableEnv": "FULL_TABLE_ID",
|
|
135
|
+
"identityEnv": "FULL_LARK_PROFILE",
|
|
136
|
+
},
|
|
137
|
+
"trigger": {
|
|
138
|
+
"intervalSec": 300, "wake": "session", "mode": "real",
|
|
139
|
+
"pauseSwitch": "PAUSE_FLOW_" + flow.upper().replace("-", "_"),
|
|
140
|
+
"promptTemplate": f"flow-prompts/{flow}.md",
|
|
141
|
+
},
|
|
142
|
+
"required": required,
|
|
143
|
+
"overrides": {"requiredExtra": []},
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _guard_vertical_targeting(argv) -> None:
|
|
149
|
+
"""P2a 防呆(2026-09-29):本工具原本把新流程写进**引擎级 base 注册表**。
|
|
150
|
+
|
|
151
|
+
注册表已拆成「引擎级 base + 每条业务线一份」⇒ 新流程(尤其是各条业务线的货代流程)
|
|
152
|
+
应当落在业务线里,否则又变成"业务实现掺进引擎"。这里**响亮拦住**并指明正确入口;
|
|
153
|
+
真要写 base(例如平台级流程)需显式 `--allow-base-write`。
|
|
154
|
+
"""
|
|
155
|
+
if '--allow-base-write' in argv:
|
|
156
|
+
return
|
|
157
|
+
print("✗ flow-scaffold 不再直接写引擎级 base 注册表(P2a:注册表已按业务线拆分)。\n"
|
|
158
|
+
" · 新增**业务线流程**:node scripts/vertical-init.mjs --id <vertical> --flow <flow> …\n"
|
|
159
|
+
" · 修改**已有业务线**(如给某条线加一条货代流程):改 verticals/<id>/flows.json +\n"
|
|
160
|
+
" verticals/<id>/prompts/<flow>.md + presets/argent-<role>/,然后跑\n"
|
|
161
|
+
" python3 scripts/flow_registry.py --check && node scripts/vertical-lint.mjs\n"
|
|
162
|
+
" · 确实要写 base(平台级流程)时加 --allow-base-write。", file=sys.stderr)
|
|
163
|
+
raise SystemExit(2)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main(argv=None) -> int:
|
|
167
|
+
_guard_vertical_targeting(argv if argv is not None else sys.argv[1:])
|
|
168
|
+
ap = argparse.ArgumentParser(description="新货代脚手架(适配器 + preset + 提示词 + 注册表片段)")
|
|
169
|
+
ap.add_argument("--name", required=True, help="集成名(目录名,如 xinlong)")
|
|
170
|
+
ap.add_argument("--carrier", required=True, help='货代在业务表里的精确写法(= routing.match),如 "鑫隆"')
|
|
171
|
+
ap.add_argument("--title", help="流程标题(缺省「<carrier>发票流程」)")
|
|
172
|
+
ap.add_argument("--role", help="角色名(缺省 ml-invoice-<name>)")
|
|
173
|
+
ap.add_argument("--from-flow", default=None,
|
|
174
|
+
help="复制哪条既有发票流程的 preset/提示词(缺省:注册表里 invoice 能力的第一条流程)")
|
|
175
|
+
ap.add_argument("--out-dir", help="生成到该目录(默认仓库根;供测试/预演)")
|
|
176
|
+
ap.add_argument("--apply", action="store_true", help="真写(缺省只打印计划)")
|
|
177
|
+
ap.add_argument("--allow-base-write", action="store_true",
|
|
178
|
+
help="P2a 防呆:确需把流程写进**引擎级 base** 时才加(新流程应进 verticals/<id>/)")
|
|
179
|
+
ap.add_argument("--json", action="store_true")
|
|
180
|
+
a = ap.parse_args(argv)
|
|
181
|
+
|
|
182
|
+
flow = f"{a.name}-invoice"
|
|
183
|
+
role = a.role or f"ml-invoice-{a.name}"
|
|
184
|
+
title = a.title or f"{a.carrier}发票流程"
|
|
185
|
+
root = pathlib.Path(a.out_dir).resolve() if a.out_dir else REPO
|
|
186
|
+
# P2a:源流程可能在业务线里 ⇒ 经加载器合并 + 别名解析(`--from-flow xzh-invoice` 仍可用)
|
|
187
|
+
try:
|
|
188
|
+
from flow_registry import load_doc as _load_doc, resolve_flow_id as _resolve
|
|
189
|
+
reg = _load_doc()
|
|
190
|
+
if not a.from_flow:
|
|
191
|
+
# 缺省源:注册表里 invoice 能力的第一条流程(引擎侧不写死业务线名)
|
|
192
|
+
a.from_flow = next((fid for fid, spec in (reg.get("flows") or {}).items()
|
|
193
|
+
if (spec or {}).get("capability") == "invoice"), "")
|
|
194
|
+
src = (reg.get("flows") or {}).get(_resolve(a.from_flow, reg))
|
|
195
|
+
except Exception: # noqa: BLE001 —— 加载器不可用时退回单文件
|
|
196
|
+
reg = json.loads(REGISTRY.read_text(encoding="utf-8"))
|
|
197
|
+
src = reg["flows"].get(a.from_flow)
|
|
198
|
+
if not src:
|
|
199
|
+
print(f"❌ 源流程不存在:{a.from_flow}", file=sys.stderr)
|
|
200
|
+
return 2
|
|
201
|
+
src_role = str(src.get("role") or "")
|
|
202
|
+
ts = time.strftime("%Y-%m-%d %H:%M")
|
|
203
|
+
|
|
204
|
+
targets = {
|
|
205
|
+
"adapter": root / "scripts" / "integrations" / a.name / "adapter.py",
|
|
206
|
+
"prompt": root / "scripts" / "flow-prompts" / f"{flow}.md",
|
|
207
|
+
"prompt_failure": root / "scripts" / "flow-prompts" / f"{flow}.failure.md",
|
|
208
|
+
"preset": root / "presets" / f"argent-{role}" / "agent.cordis.yml",
|
|
209
|
+
"preset_meta": root / "presets" / f"argent-{role}" / "preset.yml",
|
|
210
|
+
"preset_fmt": root / "presets" / f"argent-{role}" / "format-version.txt",
|
|
211
|
+
}
|
|
212
|
+
fragment = build_registry_fragment(flow, a.name, a.carrier, title, role, reg)
|
|
213
|
+
plan = {
|
|
214
|
+
"flow": flow, "role": role, "title": title, "carrier": a.carrier,
|
|
215
|
+
"integration": a.name, "from_flow": a.from_flow, "root": str(root),
|
|
216
|
+
"files": {k: str(v) for k, v in targets.items()},
|
|
217
|
+
"registryFragment": fragment,
|
|
218
|
+
"next_steps": [
|
|
219
|
+
"① 填适配器:RUNNERS / MANUAL_GATES / ERROR_CLASSES / TEMPLATE + 接入 runner;完成后 STATUS 改 \"ready\"",
|
|
220
|
+
"② 填模板映射(需要填模板的货代)",
|
|
221
|
+
"③ 跑门禁:integration_contract.py --validate → check-role-mapping.mjs → check-sop-consistency.mjs → tests/run-all-tests.sh",
|
|
222
|
+
],
|
|
223
|
+
}
|
|
224
|
+
if not a.apply:
|
|
225
|
+
print(json.dumps(plan, ensure_ascii=False, indent=2))
|
|
226
|
+
print("\n[dry-run] 未写任何文件。加 --apply 落盘。", file=sys.stderr)
|
|
227
|
+
return 0
|
|
228
|
+
|
|
229
|
+
existed = [str(p) for p in targets.values() if p.exists()]
|
|
230
|
+
if existed:
|
|
231
|
+
print("❌ 以下文件已存在,脚手架**不覆盖**(请先处理):", file=sys.stderr)
|
|
232
|
+
for p in existed:
|
|
233
|
+
print(" " + p, file=sys.stderr)
|
|
234
|
+
return 1
|
|
235
|
+
|
|
236
|
+
targets["adapter"].parent.mkdir(parents=True, exist_ok=True)
|
|
237
|
+
targets["adapter"].write_text(
|
|
238
|
+
ADAPTER_TMPL.format(name=a.name, carrier=a.carrier, title=title, flow=flow), encoding="utf-8")
|
|
239
|
+
# P2a:源提示词可能在业务线里(注册表 trigger.promptTemplate 是唯一出处),两种基准都认
|
|
240
|
+
_tpl_rel = str(((src or {}).get("trigger") or {}).get("promptTemplate") or f"flow-prompts/{a.from_flow}.md")
|
|
241
|
+
src_prompt = next((x for x in (REPO / _tpl_rel, HERE / _tpl_rel) if x.exists()), HERE / _tpl_rel)
|
|
242
|
+
# ⚠️ 目标目录必须**先建**(2026-10-08 修):`scripts/flow-prompts/` 是 P2a 之前的提示词位置,
|
|
243
|
+
# 仓库里已无任何跟踪内容 ⇒ **git 不产生它** ⇒ 真·新克隆上这里 `shutil.copyfile` 会
|
|
244
|
+
# `FileNotFoundError`(开发机上它只是被"恰好存在"或测试夹具的 mkdirSync 盖住了)。
|
|
245
|
+
# ⛔ 只补父目录,**不**把写入目标迁到 `verticals/<id>/prompts/`(那属 P4 迁移,另开一笔)。
|
|
246
|
+
targets["prompt"].parent.mkdir(parents=True, exist_ok=True)
|
|
247
|
+
shutil.copyfile(src_prompt, targets["prompt"])
|
|
248
|
+
shutil.copyfile(src_prompt.with_name(src_prompt.name[:-3] + ".failure.md"), targets["prompt_failure"])
|
|
249
|
+
src_preset = REPO / "presets" / f"argent-{src_role}" # P2a:被上面误删的一行,补回
|
|
250
|
+
for key, srcname in (("preset", "agent.cordis.yml"), ("preset_meta", "preset.yml"), ("preset_fmt", "format-version.txt")):
|
|
251
|
+
sp = src_preset / srcname
|
|
252
|
+
if not sp.exists():
|
|
253
|
+
continue
|
|
254
|
+
targets[key].parent.mkdir(parents=True, exist_ok=True)
|
|
255
|
+
text = sp.read_text(encoding="utf-8")
|
|
256
|
+
if key == "preset":
|
|
257
|
+
text = PRESET_TODO.format(src_role=src_role, src_flow=a.from_flow, flow=flow,
|
|
258
|
+
carrier=a.carrier, ts=ts) + text.replace(a.from_flow, flow).replace(src_role, role)
|
|
259
|
+
targets[key].write_text(text, encoding="utf-8")
|
|
260
|
+
|
|
261
|
+
if root == REPO:
|
|
262
|
+
bak = REGISTRY.with_suffix(f".json.bak-{time.strftime('%Y%m%d-%H%M%S')}")
|
|
263
|
+
shutil.copyfile(REGISTRY, bak)
|
|
264
|
+
reg["flows"].update(fragment)
|
|
265
|
+
REGISTRY.write_text(json.dumps(reg, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
266
|
+
plan["registryBackup"] = str(bak)
|
|
267
|
+
plan["written"] = {k: str(v) for k, v in targets.items()}
|
|
268
|
+
print(json.dumps(plan, ensure_ascii=False, indent=2) if a.json else
|
|
269
|
+
"\n".join(["✅ 已生成:"] + [f" {k}: {v}" for k, v in plan["written"].items()]
|
|
270
|
+
+ ["", "接下来:"] + [f" {s}" for s in plan["next_steps"]]))
|
|
271
|
+
return 0
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == "__main__":
|
|
275
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# flow-timers-plist.sh —— **激活垫片的 plist 生成器**(纯 stdout,零副作用)。
|
|
3
|
+
#
|
|
4
|
+
# ⭐ 为什么单独一个文件(0.6.0 W8,2026-10-14):
|
|
5
|
+
# · 生产姿态**逐字段不许变**(4 条 `com.whyshu.flow-trigger-*` 常驻)⇒ 生成逻辑必须能被
|
|
6
|
+
# **逐字节回归**(夹具把参数钉死、比对生成物),而"装"(`launchctl bootstrap`)在本机是红线;
|
|
7
|
+
# · 声明面唯一处 = 注册表 `schedule` 段 ⇒ 本脚本**不自己读注册表**,只接收 `flow-timers.sh`
|
|
8
|
+
# 从 `flow_config.py`(唯一读取点)取来的参数 ⇒ 生成器 = 纯函数(同样的参数 ⇒ 同样的字节)。
|
|
9
|
+
#
|
|
10
|
+
# 用法(只打印,不写文件、不碰 launchctl):
|
|
11
|
+
# bash scripts/flow-timers-plist.sh --flow <id> [--label <label>] [--interval <秒>]
|
|
12
|
+
# [--repo <dir>] [--python <解释器>] [--node-dir <dir>] [--lark-dir <dir>] [--log-dir <dir>]
|
|
13
|
+
#
|
|
14
|
+
# 关键约定(缺一不可,踩过坑;原样保留自 flow-timers.sh):
|
|
15
|
+
# · `AbandonProcessGroup=true` —— 缺此键时 launchd 在脚本退出后清理其后台子进程
|
|
16
|
+
# (2026-09-03 事故:orchestrator spawn 即死、batch.log 0 字节);
|
|
17
|
+
# · `ARGENT_PY` —— 流程脚本用的解释器;不设会回退到开发机 venv(客户机上不存在);
|
|
18
|
+
# · `PATH` —— 必须含 node 与 lark-cli 所在目录(launchd 的 PATH 极简);
|
|
19
|
+
# · `RunAtLoad=false` —— 装完**不立刻跑**一轮。
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
FLOW=""; LABEL=""; INTERVAL=""; REPO=""; PY=""; NODE_DIR=""; LARK_DIR=""; LOG_DIR=""
|
|
23
|
+
while [ $# -gt 0 ]; do
|
|
24
|
+
case "$1" in
|
|
25
|
+
--flow) FLOW="${2:-}"; shift 2;;
|
|
26
|
+
--label) LABEL="${2:-}"; shift 2;;
|
|
27
|
+
--interval) INTERVAL="${2:-}"; shift 2;;
|
|
28
|
+
--repo) REPO="${2:-}"; shift 2;;
|
|
29
|
+
--python) PY="${2:-}"; shift 2;;
|
|
30
|
+
--node-dir) NODE_DIR="${2:-}"; shift 2;;
|
|
31
|
+
--lark-dir) LARK_DIR="${2:-}"; shift 2;;
|
|
32
|
+
--log-dir) LOG_DIR="${2:-}"; shift 2;;
|
|
33
|
+
-h|--help) sed -n '2,22p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0;;
|
|
34
|
+
*) echo "未知参数: $1" >&2; exit 2;;
|
|
35
|
+
esac
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
[ -n "$FLOW" ] || { echo "缺 --flow" >&2; exit 2; }
|
|
39
|
+
[ -n "$INTERVAL" ] || { echo "缺 --interval(⛔ 不在这里给缺省:周期只有一份真源 = 注册表 trigger.intervalSec)" >&2; exit 2; }
|
|
40
|
+
# ⛔ label **不给缺省**:前缀只有一处真源(注册表 `schedule.jobs.labelPrefix`,由 flow-timers.sh 传进来)。
|
|
41
|
+
# 在这里写一个 `com.whyshu.flow-trigger-` 的兜底 = 造出第二份"定时怎么装"。
|
|
42
|
+
[ -n "$LABEL" ] || { echo "缺 --label(⛔ 不在这里拼缺省前缀:label 前缀只有注册表 schedule.jobs.labelPrefix 一处真源)" >&2; exit 2; }
|
|
43
|
+
REPO="${REPO:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
44
|
+
# ⛔ 日志落点也不给缺省:数据目录有**唯一判据**(内核 `core/lib/argent-datadir.mjs` / `scripts/lib/argent_datadir.py`),
|
|
45
|
+
# 在这里拼一个 `$HOME/.dsh/argent` 就是**第二份数据目录判据** —— 隔离实例下会一半写隔离、一半写生产
|
|
46
|
+
# (门禁 `scripts/tests/argent-datadir-parity.test.mjs` 当场判红)。由 `flow-timers.sh` 传进来。
|
|
47
|
+
[ -n "$LOG_DIR" ] || { echo "缺 --log-dir(⛔ 数据目录有唯一判据,本生成器不拼缺省)" >&2; exit 2; }
|
|
48
|
+
NODE_DIR="${NODE_DIR:-$(dirname "$(command -v node || echo /usr/local/bin/node)")}"
|
|
49
|
+
LARK_DIR="${LARK_DIR:-$(dirname "$(command -v lark-cli || echo /usr/local/bin/lark-cli)")}"
|
|
50
|
+
PY="${PY:-${ARGENT_PY:-$HOME/.dsh/argent/venv/bin/python3}}"
|
|
51
|
+
|
|
52
|
+
cat <<PLIST
|
|
53
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
54
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
55
|
+
<!-- 由 scripts/flow-timers.sh 生成(勿手改:声明面 = 注册表 schedule 段;参数来自唯一读取点) -->
|
|
56
|
+
<plist version="1.0">
|
|
57
|
+
<dict>
|
|
58
|
+
<key>Label</key>
|
|
59
|
+
<string>$LABEL</string>
|
|
60
|
+
<key>ProgramArguments</key>
|
|
61
|
+
<array>
|
|
62
|
+
<string>/bin/bash</string>
|
|
63
|
+
<string>$REPO/scripts/flow-trigger.sh</string>
|
|
64
|
+
<string>--flow</string>
|
|
65
|
+
<string>$FLOW</string>
|
|
66
|
+
</array>
|
|
67
|
+
<key>StartInterval</key>
|
|
68
|
+
<integer>$INTERVAL</integer>
|
|
69
|
+
<key>RunAtLoad</key>
|
|
70
|
+
<false/>
|
|
71
|
+
<key>AbandonProcessGroup</key>
|
|
72
|
+
<true/>
|
|
73
|
+
<key>EnvironmentVariables</key>
|
|
74
|
+
<dict>
|
|
75
|
+
<key>ARGENT_PY</key>
|
|
76
|
+
<string>$PY</string>
|
|
77
|
+
<key>PATH</key>
|
|
78
|
+
<string>$NODE_DIR:$LARK_DIR:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
79
|
+
</dict>
|
|
80
|
+
<key>StandardOutPath</key>
|
|
81
|
+
<string>$LOG_DIR/flow-trigger-$FLOW.out.log</string>
|
|
82
|
+
<key>StandardErrorPath</key>
|
|
83
|
+
<string>$LOG_DIR/flow-trigger-$FLOW.err.log</string>
|
|
84
|
+
</dict>
|
|
85
|
+
</plist>
|
|
86
|
+
PLIST
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 定时触发器安装器(P5 客户机部署件,2026-09-17)—— **每流程一个 LaunchAgent**。
|
|
3
|
+
#
|
|
4
|
+
# ⭐ **0.6.0 W8(2026-10-14)起「定时怎么来」只有一处声明**:
|
|
5
|
+
# 注册表 `schedule` 段(`scripts/flow-requirements.json`)—— `mechanism`
|
|
6
|
+
# (`auto|native|activation-shim|none`)+ `jobs.labelPrefix` / `jobs.installCommand`。
|
|
7
|
+
# 本脚本是**激活垫片(0.1.7 线)的实现**,只**读**那份声明(经
|
|
8
|
+
# `flow_config.py --schedule-declaration`),⛔ 不在这里写第二份"定时怎么装";
|
|
9
|
+
# 判据(含"两条线同时在场 ⇒ 报冲突")在 `scripts/lib/schedule-mechanism.mjs`(唯一实现)。
|
|
10
|
+
# ⛔ 两条线**不许同时**调度同一条流程(单一调度者纪律)。
|
|
11
|
+
#
|
|
12
|
+
# 为什么用脚本生成而不是手写 plist:每流程的间隔/暂停开关/日志名都在**注册表**里
|
|
13
|
+
# (`scripts/flow_config.py` 是唯一读取点)⇒ 手写 plist 必然与注册表漂移。
|
|
14
|
+
#
|
|
15
|
+
# 关键约定(缺一不可,踩过坑):
|
|
16
|
+
# · `AbandonProcessGroup=true` —— 缺此键时 launchd 在脚本退出后清理其后台子进程
|
|
17
|
+
# (2026-09-03 事故:orchestrator spawn 即死、batch.log 0 字节)。本触发器会 spawn
|
|
18
|
+
# 角色会话投递进程,同样需要它。
|
|
19
|
+
# · `ARGENT_PY` —— 流程脚本用的解释器;不设会回退到开发机 venv
|
|
20
|
+
# (客户机上不存在 ⇒ 触发器 exec 失败)。默认取当前 `ARGENT_PY` 或 ~/.dsh/argent/venv/bin/python3。
|
|
21
|
+
# · `PATH` —— 必须含 node 与 lark-cli 所在目录(launchd 的 PATH 极简)。
|
|
22
|
+
# · 暂停开关 —— 由流程注册表的 `trigger.pauseSwitch` 决定;装完**先别删**,
|
|
23
|
+
# 观察影子轮没问题再逐流程摘(见 docs/客户机部署与上线.md §4.4)。
|
|
24
|
+
#
|
|
25
|
+
# 用法:
|
|
26
|
+
# bash scripts/flow-timers.sh status
|
|
27
|
+
# bash scripts/flow-timers.sh install --flow wenyao-invoice [--flow xzh-invoice] [--dry-run]
|
|
28
|
+
# bash scripts/flow-timers.sh install --all [--dry-run]
|
|
29
|
+
# bash scripts/flow-timers.sh uninstall --flow wenyao-invoice
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
|
|
32
|
+
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
33
|
+
# 解释器解析(2026-09-22 重启事故:默认值写死 ~/.dsh/argent/venv ⇒ 重启后静默空转)
|
|
34
|
+
pick_py() {
|
|
35
|
+
local c
|
|
36
|
+
# 候选(2026-09-22):ARGENT_PY → DSH_HOME/argent/venv → ~/.dsh/argent/venv → 仓库 .venv → PATH
|
|
37
|
+
for c in "${ARGENT_PY:-}" "${DSH_HOME:-$HOME/.dsh}/argent/venv/bin/python3" \
|
|
38
|
+
"$HOME/.dsh/argent/venv/bin/python3" "${REPO:-}/.venv/bin/python3" \
|
|
39
|
+
"$(command -v python3 2>/dev/null || true)"; do
|
|
40
|
+
[ -n "$c" ] || continue
|
|
41
|
+
[ -x "$c" ] || continue
|
|
42
|
+
"$c" -c 'import openpyxl, yaml' 2>/dev/null || continue # 硬要求:配置/发票生成必需
|
|
43
|
+
"$c" -c 'import PIL, playwright' 2>/dev/null || \
|
|
44
|
+
echo "[$(date '+%F %T')] ⚠️ 解释器 $c 缺 pillow/playwright ⇒ 浏览器流程会失败(修:node install-argent.mjs --venv-only)" >&2
|
|
45
|
+
printf '%s' "$c"; return 0
|
|
46
|
+
done
|
|
47
|
+
return 1
|
|
48
|
+
}
|
|
49
|
+
if ! PY="$(pick_py)"; then
|
|
50
|
+
echo "❌ 找不到可用解释器(需含 openpyxl + yaml):ARGENT_PY / ~/.dsh/argent/venv/bin/python3 / <repo>/.venv / PATH 的 python3" >&2
|
|
51
|
+
echo " 修复: python3 -m venv ~/.dsh/argent/venv && ~/.dsh/argent/venv/bin/pip install openpyxl pyyaml" >&2
|
|
52
|
+
exit 2
|
|
53
|
+
fi
|
|
54
|
+
AGENTS="$HOME/Library/LaunchAgents"
|
|
55
|
+
LOGDIR="$HOME/.dsh/argent"
|
|
56
|
+
# ── 声明面(唯一):注册表 `schedule` 段 ────────────────────────────────────────
|
|
57
|
+
# `DECL` 只在本进程读一次;`--dry-run` 与 `status` 也走同一份(⛔ 不再各自读一遍)。
|
|
58
|
+
_DECL_STATUS=0
|
|
59
|
+
if DECL="$("$PY" "$REPO/scripts/flow_config.py" --schedule-declaration 2>/dev/null)"; then
|
|
60
|
+
:
|
|
61
|
+
else
|
|
62
|
+
_DECL_STATUS=$?
|
|
63
|
+
DECL='{}'
|
|
64
|
+
echo "⚠️ 读不到注册表的 schedule 声明(flow_config.py --schedule-declaration exit ${_DECL_STATUS}):" >&2
|
|
65
|
+
echo " 本脚本按**内置口径**继续(label 前缀/命令模板),但请先修注册表 —— 声明面是唯一真源。" >&2
|
|
66
|
+
fi
|
|
67
|
+
decl() { # $1=点号路径(如 jobs.labelPrefix / mechanism)
|
|
68
|
+
printf '%s' "$DECL" | "$PY" -c 'import json,sys
|
|
69
|
+
try:
|
|
70
|
+
cur = json.load(sys.stdin)
|
|
71
|
+
except Exception:
|
|
72
|
+
print(""); raise SystemExit(0)
|
|
73
|
+
for part in sys.argv[1].split("."):
|
|
74
|
+
if not isinstance(cur, dict) or part not in cur:
|
|
75
|
+
print(""); raise SystemExit(0)
|
|
76
|
+
cur = cur[part]
|
|
77
|
+
print("" if cur is None else (("true" if cur else "false") if isinstance(cur, bool) else cur))' "$1"
|
|
78
|
+
}
|
|
79
|
+
LABEL_PREFIX="$(decl jobs.labelPrefix)"; [ -n "$LABEL_PREFIX" ] || LABEL_PREFIX="com.whyshu.flow-trigger"
|
|
80
|
+
|
|
81
|
+
usage() { sed -n '2,26p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
|
|
82
|
+
|
|
83
|
+
cfg() { "$PY" "$REPO/scripts/flow_config.py" --flow "$1" --get "$2"; }
|
|
84
|
+
|
|
85
|
+
flows_arg() { # 解析 --flow/--all
|
|
86
|
+
local out=() all=0
|
|
87
|
+
while [ $# -gt 0 ]; do
|
|
88
|
+
case "$1" in
|
|
89
|
+
--flow) out+=("$2"); shift 2;;
|
|
90
|
+
--all) all=1; shift;;
|
|
91
|
+
*) shift;;
|
|
92
|
+
esac
|
|
93
|
+
done
|
|
94
|
+
if [ "$all" = 1 ] || [ ${#out[@]} -eq 0 ]; then
|
|
95
|
+
out=()
|
|
96
|
+
while IFS= read -r f; do out+=("$f"); done < <("$PY" "$REPO/scripts/flow_config.py" --list 2>/dev/null | awk '{print $1}' | grep -v '^$')
|
|
97
|
+
fi
|
|
98
|
+
printf '%s\n' "${out[@]}"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
node_dir() { dirname "$(command -v node || echo /usr/local/bin/node)"; }
|
|
102
|
+
lark_dir() { dirname "$(command -v lark-cli || echo /usr/local/bin/lark-cli)"; }
|
|
103
|
+
|
|
104
|
+
# 生成器**独立成文件**(`flow-timers-plist.sh`):纯 stdout、零副作用 ⇒ 可被逐字节回归
|
|
105
|
+
# (本机 ⛔ 不许 `launchctl bootstrap`,所以"生成物与生产形态等价"只能这样证)。
|
|
106
|
+
# ⛔ 周期不给缺省:读不到就**响亮失败**(见 `gen_plist`)。
|
|
107
|
+
gen_plist() { # $1=flow ⇒ stdout=plist;失败 ⇒ 非零
|
|
108
|
+
local flow="$1" interval label
|
|
109
|
+
interval="$(cfg "$flow" trigger.intervalSec 2>/dev/null || true)"
|
|
110
|
+
if [ -z "$interval" ]; then
|
|
111
|
+
echo "❌ 流程 $flow 读不到 trigger.intervalSec(注册表声明缺失或 flow 名不对)⇒ 拒绝生成 plist" >&2
|
|
112
|
+
return 1
|
|
113
|
+
fi
|
|
114
|
+
label="$LABEL_PREFIX-$flow"
|
|
115
|
+
bash "$REPO/scripts/flow-timers-plist.sh" \
|
|
116
|
+
--flow "$flow" --label "$label" --interval "$interval" \
|
|
117
|
+
--repo "$REPO" --python "$PY" \
|
|
118
|
+
--node-dir "$(node_dir)" --lark-dir "$(lark_dir)" --log-dir "$LOGDIR"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
cmd_status() {
|
|
122
|
+
echo "── 本机定时器状态(label / 退出码 / PID)"
|
|
123
|
+
launchctl list | grep -i "whyshu" || echo " (无 whyshu agent)"
|
|
124
|
+
echo
|
|
125
|
+
echo "── 最近一次触发结果(launchd 捕获的 stdout 尾部;空=还没跑过)"
|
|
126
|
+
for f in "$LOGDIR"/flow-trigger-*.out.log; do
|
|
127
|
+
[ -f "$f" ] || continue
|
|
128
|
+
b="$(basename "$f")"; b="${b#flow-trigger-}"; b="${b%.out.log}"
|
|
129
|
+
printf ' %-16s %s\n' "$b" "$(tail -1 "$f" | cut -c1-120)"
|
|
130
|
+
done
|
|
131
|
+
echo
|
|
132
|
+
echo "── 每流程的暂停开关(存在=不唤起)"
|
|
133
|
+
while IFS= read -r f; do
|
|
134
|
+
[ -n "$f" ] || continue
|
|
135
|
+
local p; p="$(cfg "$f" paths.pauseSwitch 2>/dev/null || echo '?')"
|
|
136
|
+
if [ -f "$p" ]; then echo " ⏸ ${f}:已暂停(${p})"; else echo " ▶️ ${f}:未暂停"; fi
|
|
137
|
+
done < <(flows_arg --all)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
cmd_install() {
|
|
141
|
+
local dry=0; case " $* " in *" --dry-run "*) dry=1;; esac
|
|
142
|
+
mkdir -p "$AGENTS" "$LOGDIR"
|
|
143
|
+
while IFS= read -r f; do
|
|
144
|
+
[ -n "$f" ] || continue
|
|
145
|
+
local label plist ins
|
|
146
|
+
label="$LABEL_PREFIX-$f"; plist="$AGENTS/$label.plist"
|
|
147
|
+
if ! ins="$(gen_plist "$f")"; then
|
|
148
|
+
echo "❌ ${f}:生成 plist 失败(见上面那句)⇒ 本轮跳过这一条(⛔ 不写半成品 plist)" >&2
|
|
149
|
+
continue
|
|
150
|
+
fi
|
|
151
|
+
if [ "$dry" = 1 ]; then
|
|
152
|
+
echo "── [dry-run] $f → $plist"; printf '%s\n' "$ins" | sed -n '1,12p'; echo " …(完整 plist 见上方结构)"; continue
|
|
153
|
+
fi
|
|
154
|
+
printf '%s\n' "$ins" > "$plist"
|
|
155
|
+
launchctl bootout "gui/$(id -u)/$label" 2>/dev/null || true
|
|
156
|
+
launchctl enable "gui/$(id -u)/$label" 2>/dev/null || true
|
|
157
|
+
if launchctl bootstrap "gui/$(id -u)" "$plist" 2>/dev/null; then
|
|
158
|
+
echo " ✅ 已装并加载:${label}(间隔 $(cfg "$f" trigger.intervalSec 2>/dev/null || echo 300)s)"
|
|
159
|
+
else
|
|
160
|
+
echo " ⚠️ 已写 plist 但 bootstrap 失败:${plist}(看 launchctl print gui/$(id -u)/${label})"
|
|
161
|
+
fi
|
|
162
|
+
done < <(flows_arg "$@")
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
cmd_uninstall() {
|
|
166
|
+
while IFS= read -r f; do
|
|
167
|
+
[ -n "$f" ] || continue
|
|
168
|
+
local label; label="$LABEL_PREFIX-$f"
|
|
169
|
+
launchctl bootout "gui/$(id -u)/$label" 2>/dev/null || true
|
|
170
|
+
launchctl disable "gui/$(id -u)/$label" 2>/dev/null || true
|
|
171
|
+
rm -f "$AGENTS/$label.plist"
|
|
172
|
+
echo " ⏏️ 已卸载:$label"
|
|
173
|
+
done < <(flows_arg "$@")
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
case "${1:-}" in
|
|
177
|
+
status) shift; cmd_status ;;
|
|
178
|
+
install) shift; cmd_install "$@" ;;
|
|
179
|
+
uninstall) shift; cmd_uninstall "$@" ;;
|
|
180
|
+
-h|--help|"") usage ;;
|
|
181
|
+
*) echo "未知子命令: $1"; usage; exit 2 ;;
|
|
182
|
+
esac
|