@whyshu/argent-engine 0.0.0-stage → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.json +341 -0
- package/engine/VERSION +1 -0
- package/engine/agents/catalog.json +454 -0
- package/engine/configs/_aliases.proposed.json +651 -0
- package/engine/configs/_migration-plan.proposed.json +899 -0
- package/engine/configs/account.json +272 -0
- package/engine/configs/advanced.json +205 -0
- package/engine/configs/host.json +149 -0
- package/engine/configs/hr.json +27 -0
- package/engine/configs/notify.json +264 -0
- package/engine/core/cli/argent.mjs +277 -0
- package/engine/core/contracts/argent.contracts.json +1065 -0
- package/engine/core/contracts/verbs.schema.json +2539 -0
- package/engine/core/lib/argent-datadir.mjs +223 -0
- package/engine/core/lib/audit.mjs +187 -0
- package/engine/core/lib/claim.mjs +258 -0
- package/engine/core/lib/config-catalog.mjs +1172 -0
- package/engine/core/lib/doctor-customer.mjs +577 -0
- package/engine/core/lib/flow-list.mjs +138 -0
- package/engine/core/lib/flow-registry.mjs +48 -0
- package/engine/core/lib/init.mjs +247 -0
- package/engine/core/lib/ledger.mjs +124 -0
- package/engine/core/lib/license-activate.mjs +813 -0
- package/engine/core/lib/license-device.mjs +314 -0
- package/engine/core/lib/license-file.mjs +349 -0
- package/engine/core/lib/license-guard.mjs +123 -0
- package/engine/core/lib/license-ops.mjs +1645 -0
- package/engine/core/lib/license-verdict.mjs +133 -0
- package/engine/core/lib/license.mjs +577 -0
- package/engine/core/lib/notify/dedup.mjs +101 -0
- package/engine/core/lib/notify/feishu.mjs +38 -0
- package/engine/core/lib/notify/index.mjs +95 -0
- package/engine/core/lib/notify/verb.mjs +327 -0
- package/engine/core/lib/notify/webhook.mjs +135 -0
- package/engine/core/lib/notify/wecom.mjs +36 -0
- package/engine/core/lib/python.mjs +145 -0
- package/engine/core/lib/sop.mjs +116 -0
- package/engine/core/lib/versions.mjs +205 -0
- package/engine/core/mcp/client.mjs +67 -0
- package/engine/core/mcp/http.mjs +138 -0
- package/engine/core/mcp/server.mjs +702 -0
- package/engine/scripts/_engine_rules_probe.py +15 -0
- package/engine/scripts/analyze-full-day.py +568 -0
- package/engine/scripts/archive/feishu-bridge.js +246 -0
- package/engine/scripts/argent-agents.mjs +527 -0
- package/engine/scripts/argent-capabilities.mjs +776 -0
- package/engine/scripts/argent-catalog.mjs +186 -0
- package/engine/scripts/argent-datadir.py +62 -0
- package/engine/scripts/argent-doctor.mjs +624 -0
- package/engine/scripts/argent-env-write.mjs +302 -0
- package/engine/scripts/argent-schedule.mjs +620 -0
- package/engine/scripts/argent-setup-check.mjs +369 -0
- package/engine/scripts/argent-upgrade.mjs +1046 -0
- package/engine/scripts/base-bootstrap.sh +576 -0
- package/engine/scripts/billing-mode-daily.sh +13 -0
- package/engine/scripts/build-engine-package.mjs +126 -0
- package/engine/scripts/capability-packs.lock.json +484 -0
- package/engine/scripts/capability_pack.py +1242 -0
- package/engine/scripts/check-1.0-criteria.mjs +355 -0
- package/engine/scripts/check-agent-catalog.mjs +141 -0
- package/engine/scripts/check-billing-mode.sh +378 -0
- package/engine/scripts/check-child-sessions.mjs +83 -0
- package/engine/scripts/check-config-catalog.mjs +677 -0
- package/engine/scripts/check-docs-drift.mjs +522 -0
- package/engine/scripts/check-dsh-web-expose.sh +98 -0
- package/engine/scripts/check-judgment-drift.mjs +403 -0
- package/engine/scripts/check-license.mjs +199 -0
- package/engine/scripts/check-model-auth.py +443 -0
- package/engine/scripts/check-pack-closure.mjs +661 -0
- package/engine/scripts/check-portable-symbols.mjs +116 -0
- package/engine/scripts/check-preset-landing.mjs +124 -0
- package/engine/scripts/check-production-home.mjs +602 -0
- package/engine/scripts/check-role-mapping.mjs +461 -0
- package/engine/scripts/check-sample-redaction.mjs +320 -0
- package/engine/scripts/check-skill-classification.mjs +188 -0
- package/engine/scripts/check-sop-consistency.mjs +431 -0
- package/engine/scripts/check-test-env-safety.mjs +627 -0
- package/engine/scripts/check-version-consistency.mjs +452 -0
- package/engine/scripts/ci-run.sh +558 -0
- package/engine/scripts/collect-evidence.mjs +42 -0
- package/engine/scripts/config-explain.mjs +278 -0
- package/engine/scripts/configure-provider.rb +29 -0
- package/engine/scripts/customer-config-migrate.mjs +179 -0
- package/engine/scripts/customer-preflight.py +892 -0
- package/engine/scripts/customer-readiness.sh +135 -0
- package/engine/scripts/daily-report-inbox.py +242 -0
- package/engine/scripts/daily-report-reconcile.mjs +312 -0
- package/engine/scripts/daily-report.mjs +1310 -0
- package/engine/scripts/daily-report.sh +47 -0
- package/engine/scripts/data/lip-product-dict.json +306 -0
- package/engine/scripts/deliver-customer.mjs +932 -0
- package/engine/scripts/delivery-faces.txt +31 -0
- package/engine/scripts/desktop-trial.sh +146 -0
- package/engine/scripts/dev-window.sh +261 -0
- package/engine/scripts/drill-clean-machine.sh +315 -0
- package/engine/scripts/dsh-api-client.mjs +215 -0
- package/engine/scripts/dsh-backup-scope.sh +83 -0
- package/engine/scripts/dsh-offline-pack.mjs +77 -0
- package/engine/scripts/dsh-overlay-pin.sh +87 -0
- package/engine/scripts/dsh-plane.mjs +153 -0
- package/engine/scripts/dsh-portforward.py +223 -0
- package/engine/scripts/dsh-preflight.mjs +179 -0
- package/engine/scripts/dsh-release-watch-daily.sh +19 -0
- package/engine/scripts/dsh-release-watch.mjs +458 -0
- package/engine/scripts/dsh-remote-check.sh +99 -0
- package/engine/scripts/dsh-restart.sh +179 -0
- package/engine/scripts/dsh-restore.sh +111 -0
- package/engine/scripts/dsh-scope-rewire.mjs +803 -0
- package/engine/scripts/dsh-snapshot.mjs +116 -0
- package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
- package/engine/scripts/dsh-upgrade.mjs +612 -0
- package/engine/scripts/dsh-url.sh +86 -0
- package/engine/scripts/dsh-web-launch.mjs +155 -0
- package/engine/scripts/ensure-store-windows.py +509 -0
- package/engine/scripts/entitlements.py +458 -0
- package/engine/scripts/env_probe.py +411 -0
- package/engine/scripts/feishu-card.mjs +252 -0
- package/engine/scripts/final-check.mjs +43 -0
- package/engine/scripts/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +735 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +87 -0
- package/engine/scripts/flow-timers.sh +184 -0
- package/engine/scripts/flow-trigger-wake.mjs +284 -0
- package/engine/scripts/flow-trigger.sh +571 -0
- package/engine/scripts/flow_billing.py +354 -0
- package/engine/scripts/flow_capability_stats.py +314 -0
- package/engine/scripts/flow_case.py +194 -0
- package/engine/scripts/flow_claim.py +497 -0
- package/engine/scripts/flow_config.py +1025 -0
- package/engine/scripts/flow_failure.py +588 -0
- package/engine/scripts/flow_learn.py +105 -0
- package/engine/scripts/flow_ledger.py +235 -0
- package/engine/scripts/flow_policy.py +238 -0
- package/engine/scripts/flow_preflight.py +339 -0
- package/engine/scripts/flow_registry.py +341 -0
- package/engine/scripts/flow_retry.py +304 -0
- package/engine/scripts/flow_return_to_ops.py +191 -0
- package/engine/scripts/flow_source.py +315 -0
- package/engine/scripts/flow_sources/__init__.py +53 -0
- package/engine/scripts/flow_sources/cases_json.py +106 -0
- package/engine/scripts/flow_sources/common.py +428 -0
- package/engine/scripts/flow_sources/feishu_base.py +273 -0
- package/engine/scripts/flow_stale.py +185 -0
- package/engine/scripts/flow_status_reconcile.py +344 -0
- package/engine/scripts/flow_triage.py +849 -0
- package/engine/scripts/flow_write_verify.py +496 -0
- package/engine/scripts/fulfillment.js +586 -0
- package/engine/scripts/gen-agent-bundles.mjs +276 -0
- package/engine/scripts/gen-preset-rows.mjs +259 -0
- package/engine/scripts/init-ml.sh +2278 -0
- package/engine/scripts/install-internal-role.sh +190 -0
- package/engine/scripts/install-license.mjs +641 -0
- package/engine/scripts/install-one-shot.mjs +665 -0
- package/engine/scripts/integration_contract.py +220 -0
- package/engine/scripts/integrations/wenyao/adapter.py +98 -0
- package/engine/scripts/integrations/xzh/adapter.py +75 -0
- package/engine/scripts/invoice-mapping.example.json +40 -0
- package/engine/scripts/invoice-table.sh +195 -0
- package/engine/scripts/invoice_carrier_notice.py +279 -0
- package/engine/scripts/invoice_fail_closeout.py +297 -0
- package/engine/scripts/invoice_lip_runner.py +1508 -0
- package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
- package/engine/scripts/invoice_ready_audit.py +191 -0
- package/engine/scripts/invoice_runner.py +675 -0
- package/engine/scripts/invoice_stageb.py +516 -0
- package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
- package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
- package/engine/scripts/lease.py +377 -0
- package/engine/scripts/lib/agent-install.mjs +649 -0
- package/engine/scripts/lib/argent-datadir.mjs +16 -0
- package/engine/scripts/lib/argent-datadir.sh +18 -0
- package/engine/scripts/lib/argent_datadir.py +285 -0
- package/engine/scripts/lib/argent_filelock.py +507 -0
- package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
- package/engine/scripts/lib/cdp_ports.py +197 -0
- package/engine/scripts/lib/customer-env.mjs +102 -0
- package/engine/scripts/lib/daily-report-core.mjs +517 -0
- package/engine/scripts/lib/dep-declaration.mjs +81 -0
- package/engine/scripts/lib/deps-migrate.mjs +310 -0
- package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
- package/engine/scripts/lib/dsh-scope.mjs +500 -0
- package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
- package/engine/scripts/lib/dsh-versions.mjs +96 -0
- package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
- package/engine/scripts/lib/is-direct-run.mjs +61 -0
- package/engine/scripts/lib/patch-compose.mjs +241 -0
- package/engine/scripts/lib/patch-target.mjs +35 -0
- package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
- package/engine/scripts/lib/plugin-packages.mjs +332 -0
- package/engine/scripts/lib/preset-compat.mjs +127 -0
- package/engine/scripts/lib/preset-install.mjs +575 -0
- package/engine/scripts/lib/preset-mechanism.mjs +56 -0
- package/engine/scripts/lib/preset-sync.mjs +59 -0
- package/engine/scripts/lib/profile-retire.mjs +97 -0
- package/engine/scripts/lib/python-runtime.mjs +190 -0
- package/engine/scripts/lib/registry.mjs +72 -0
- package/engine/scripts/lib/repo-root.sh +56 -0
- package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
- package/engine/scripts/lib/schedule-probe.mjs +835 -0
- package/engine/scripts/lib/scope-pointer.mjs +164 -0
- package/engine/scripts/lib/settings-panels.mjs +816 -0
- package/engine/scripts/lib/stdio_utf8.py +86 -0
- package/engine/scripts/lib/wake-auth.mjs +392 -0
- package/engine/scripts/lib/webdriver_ipc.py +264 -0
- package/engine/scripts/lib/zstd-frames.mjs +104 -0
- package/engine/scripts/license-e2e-drill.mjs +426 -0
- package/engine/scripts/license-gate.mjs +245 -0
- package/engine/scripts/lip_backlog.py +171 -0
- package/engine/scripts/lip_table.py +185 -0
- package/engine/scripts/local-usage.mjs +214 -0
- package/engine/scripts/migrate-providers.mjs +127 -0
- package/engine/scripts/migrate-skills.sh +65 -0
- package/engine/scripts/ml-oauth-setup.mjs +550 -0
- package/engine/scripts/ml-store-ids.json +26 -0
- package/engine/scripts/ml_artifact_attribution.py +333 -0
- package/engine/scripts/ml_capacity_probe.py +1158 -0
- package/engine/scripts/ml_hub_status.py +1556 -0
- package/engine/scripts/ml_schema_check.py +115 -0
- package/engine/scripts/ml_store_ids.py +139 -0
- package/engine/scripts/npm-publish-argent.mjs +499 -0
- package/engine/scripts/npm-publish-argent.sh +20 -0
- package/engine/scripts/orchestrator.py +13811 -0
- package/engine/scripts/pack-plugins.mjs +140 -0
- package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
- package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
- package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
- package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
- package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
- package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
- package/engine/scripts/patches/verify-hardening.mjs +167 -0
- package/engine/scripts/patches/verify-session-migration.mjs +149 -0
- package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
- package/engine/scripts/plan-role-rename.mjs +125 -0
- package/engine/scripts/plugin-bootcheck.mjs +451 -0
- package/engine/scripts/plugin-composition.json +15 -0
- package/engine/scripts/poc-mcp-host.mjs +455 -0
- package/engine/scripts/poll-fulfillment.sh +295 -0
- package/engine/scripts/poll-invoice.sh +76 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +71 -0
- package/engine/scripts/provider-switch.sh +146 -0
- package/engine/scripts/provider-sync-target.check.mjs +64 -0
- package/engine/scripts/python-runtime.sh +78 -0
- package/engine/scripts/query_runner.py +310 -0
- package/engine/scripts/release-argent.sh +574 -0
- package/engine/scripts/repair-unresolved-tools.mjs +178 -0
- package/engine/scripts/retire-inventory.mjs +40 -0
- package/engine/scripts/run-tests.sh +257 -0
- package/engine/scripts/s1-audit.mjs +85 -0
- package/engine/scripts/self-sign-license.mjs +179 -0
- package/engine/scripts/session-health-scan.mjs +63 -0
- package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
- package/engine/scripts/setup.sh +22 -0
- package/engine/scripts/skill-install.mjs +629 -0
- package/engine/scripts/sku_translate.py +148 -0
- package/engine/scripts/sync-plugins.sh +90 -0
- package/engine/scripts/sync-presets.sh +27 -0
- package/engine/scripts/sync-scripts.sh +445 -0
- package/engine/scripts/telemetry.py +873 -0
- package/engine/scripts/ui-copy-count.mjs +220 -0
- package/engine/scripts/usage-anchor.sh +137 -0
- package/engine/scripts/usage_report.py +531 -0
- package/engine/scripts/validate-preset-schema.mjs +208 -0
- package/engine/scripts/vertical-init.mjs +323 -0
- package/engine/scripts/vertical-lint.mjs +482 -0
- package/engine/scripts/wenyao-browser.sh +74 -0
- package/engine/scripts/wenyao-fixture-export.py +116 -0
- package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
- package/engine/scripts/wenyao-login.sh +35 -0
- package/engine/scripts/wenyao-template-map.json +292 -0
- package/engine/scripts/wenyao_attach.py +131 -0
- package/engine/scripts/wenyao_download_template.py +144 -0
- package/engine/scripts/wenyao_fill_template.py +568 -0
- package/engine/scripts/wenyao_login.py +145 -0
- package/engine/scripts/wenyao_portal.py +633 -0
- package/engine/scripts/wenyao_recon.py +230 -0
- package/engine/scripts/wenyao_run.py +503 -0
- package/engine/scripts/wenyao_shadow_diff.py +160 -0
- package/engine/scripts/wenyao_skill_contract.py +156 -0
- package/engine/scripts/wenyao_template_fetch.py +175 -0
- package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
- package/engine/scripts/ziniao-auth-probe.sh +268 -0
- package/engine/scripts/ziniao-keepalive.sh +217 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +121 -0
- package/engine/scripts/ziniao-windows.sh +154 -0
- package/engine/skills/README.md +86 -0
- package/engine/skills/argent-dev/SKILL.md +41 -0
- package/engine/skills/internal.json +37 -0
- package/engine/skills/lktop-pm/SKILL.md +35 -0
- package/engine/skills/lktopapp-dev/SKILL.md +34 -0
- package/engine/skills/ml-config-guide/SKILL.md +50 -0
- package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
- package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
- package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
- package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
- package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
- package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
- package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
- package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
- package/engine/skills/ml-invoice/SKILL.md +506 -0
- package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
- package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
- package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
- package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
- package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
- package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
- package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
- package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
- package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
- package/engine/skills/pet-dev/SKILL.md +42 -0
- package/engine/skills/whyshu-pm/SKILL.md +35 -0
- package/engine/skills/whyshu-web-dev/SKILL.md +50 -0
- package/engine/version.mjs +1 -0
- package/engine/verticals/README.md +103 -0
- package/engine/verticals/_engine-words.baseline.txt +237 -0
- package/engine/verticals/_engine-words.json +19 -0
- package/engine/verticals/mercadolibre/README.md +43 -0
- package/engine/verticals/mercadolibre/config.json +709 -0
- package/engine/verticals/mercadolibre/flows.json +738 -0
- package/engine/verticals/mercadolibre/manifest.json +538 -0
- package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
- package/engine/verticals/mercadolibre/preflight.py +143 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
- package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
- package/engine/verticals/mercadolibre/prompts/full.md +91 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
- package/package.json +22 -4
- package/requirements.txt +5 -0
- package/README.md +0 -3
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""stdout/stderr 的 UTF-8 安全(**唯一真源**):中文 Windows 上别让"打印"把入口脚本打崩。
|
|
3
|
+
|
|
4
|
+
## 事故(2026-10-08,Windows 装机验证)
|
|
5
|
+
`flow_registry.py --json` 用 `ensure_ascii=False` 打印合并后的注册表(里面有 `⇒` U+21D2、
|
|
6
|
+
`✅` U+2705 这类字符),而中文 Windows 上 **stdout 被重定向/管道**时 Python 用的是
|
|
7
|
+
**locale 编码(cp936/GBK)**——PEP 528 只把**交互式控制台**改成 UTF-8,重定向后的
|
|
8
|
+
stdout/stderr 仍按 locale 编码 ⇒
|
|
9
|
+
|
|
10
|
+
UnicodeEncodeError: 'gbk' codec can't encode character '\\u21d2'
|
|
11
|
+
|
|
12
|
+
⇒ 脚本 exit 1、stdout 半截 ⇒ 调用它的 `scripts/argent-catalog.mjs`(以及任何
|
|
13
|
+
`JSON.parse(stdout)` 的 Node 侧)**连带全挂**。同类受害面见下"覆盖范围"。
|
|
14
|
+
|
|
15
|
+
## 口径:为什么是 reconfigure 而不是 `ensure_ascii=True`
|
|
16
|
+
`sys.stdout.reconfigure(encoding="utf-8")` 把"用哪个编码写出去"钉死,而在**本来就是 UTF-8**
|
|
17
|
+
的机器/管道上**字节层面完全不变**(同一段 JSON、同样的转义形式、同样的键序)⇒ 下游
|
|
18
|
+
(`JSON.parse`、留痕比对、`sha256(stdout)` 类断言)**零感知**。
|
|
19
|
+
换成 `ensure_ascii=True` 虽然也是合法 JSON,但会把非 ASCII 改成 `\\uXXXX` ⇒ **改变了
|
|
20
|
+
下游看到的字节**(这也是为什么本模块不采用它)。
|
|
21
|
+
`errors="backslashreplace"` 是最后一道保险:宁可把个别怪异字符(如孤立代理对)写成转义,
|
|
22
|
+
也不要在**打印时崩** —— 打印崩掉是整条调用链断,代价远大于一个字符的显示问题。
|
|
23
|
+
|
|
24
|
+
## 覆盖范围("逐处核"的结论,2026-10-08)
|
|
25
|
+
**已接入**(stdout 会被 Node/工具链 `JSON.parse`/解析的入口;括号内=调用方):
|
|
26
|
+
· `scripts/flow_registry.py`(`scripts/lib/registry.mjs`、`core/lib/*.mjs`、`argent-catalog.mjs`)
|
|
27
|
+
· `scripts/capability_pack.py`(`install-argent.mjs#describeCapability`、`profile-doctor.mjs`、
|
|
28
|
+
`check-pack-closure.mjs`、`core/lib/versions.mjs`)—— 它的 `--validate/--stamp/--build`
|
|
29
|
+
会打印 ✅❌📦 等非 GBK 字符
|
|
30
|
+
· `scripts/check-model-auth.py`(装机第 7d 步,`install-argent.mjs`;`--json` 载荷里就有 U+21D2)
|
|
31
|
+
· `scripts/argent-datadir.py`(`core/lib/argent-datadir.mjs`)
|
|
32
|
+
· `scripts/fix-skill-frontmatter.py`(`core/lib/init.mjs` 安装/升级期调用)
|
|
33
|
+
· `scripts/entitlements.py`(网关插件 `--json`/`--validate`)
|
|
34
|
+
· `scripts/flow_capability_stats.py`(`plugins/argent-flow` 的 `--json`)
|
|
35
|
+
**未接入**(有意,附理由):**流程引擎运行期**脚本(`scripts/**` 里除上表之外的 Python 入口:
|
|
36
|
+
流程引擎本体、各门户适配器、环境探测/遥测/用量统计等)—— 它们同样打印非 GBK 字符,
|
|
37
|
+
但 **Windows 目前根本跑不了流程引擎**(venv 是 POSIX-only、也没有定时器机制 ⇒ 见
|
|
38
|
+
`docs/Argent-Windows-安装与HR能力验证-2026-10-08.md` §6-③/§7),且它们的 stdout 由
|
|
39
|
+
`flow-trigger.sh`(POSIX)重定向到日志。等"流程引擎上 Windows"那件事做时,逐个入口加三行即可
|
|
40
|
+
(本模块就是为那次准备的单点)。
|
|
41
|
+
|
|
42
|
+
## 用法(入口脚本,`main()` 的第一句)
|
|
43
|
+
HERE = pathlib.Path(__file__).resolve().parent
|
|
44
|
+
sys.path.insert(0, str(HERE / "lib"))
|
|
45
|
+
from stdio_utf8 import use_utf8_stdio # noqa: E402
|
|
46
|
+
|
|
47
|
+
def main(argv=None) -> int:
|
|
48
|
+
use_utf8_stdio()
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
回归:`node scripts/tests/py-stdio-gbk.test.mjs`(在 `PYTHONIOENCODING=gbk` 下逐个入口实跑,
|
|
52
|
+
并断言 `--json` 的输出**与 UTF-8 下逐字节相同**)。
|
|
53
|
+
"""
|
|
54
|
+
from __future__ import annotations
|
|
55
|
+
|
|
56
|
+
import sys
|
|
57
|
+
|
|
58
|
+
__all__ = ["use_utf8_stdio"]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def use_utf8_stdio() -> bool:
|
|
62
|
+
"""把 stdout / stderr 钉成 UTF-8(幂等;已是 UTF-8 时是**无操作**)。
|
|
63
|
+
|
|
64
|
+
返回是否"两条流都已确认是 UTF-8"。被替换成非 `TextIOWrapper`(测试夹具、嵌入宿主)
|
|
65
|
+
而无法 `reconfigure` 时返回 False —— 调用方不必据此判死(那是宿主的事),
|
|
66
|
+
但**不允许**因为这里失败就跳过本函数:它是唯一的出口编码判据。
|
|
67
|
+
"""
|
|
68
|
+
ok = True
|
|
69
|
+
for stream in (sys.stdout, sys.stderr):
|
|
70
|
+
if stream is None: # pythonw/GUI 宿主:没有控制台流
|
|
71
|
+
continue
|
|
72
|
+
if _is_utf8(stream):
|
|
73
|
+
continue
|
|
74
|
+
try:
|
|
75
|
+
stream.reconfigure(encoding="utf-8", errors="backslashreplace") # py3.7+
|
|
76
|
+
except Exception: # noqa: BLE001 —— 流被换成了不认识的对象:只能如实报告
|
|
77
|
+
ok = False
|
|
78
|
+
continue
|
|
79
|
+
if not _is_utf8(stream):
|
|
80
|
+
ok = False
|
|
81
|
+
return ok
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _is_utf8(stream) -> bool:
|
|
85
|
+
enc = getattr(stream, "encoding", "") or ""
|
|
86
|
+
return enc.lower().replace("-", "").replace("_", "") == "utf8"
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* wake-auth —— 「拿什么去唤醒 dsh 会话」的**唯一鉴权来源链**(2026-10-08)。
|
|
4
|
+
*
|
|
5
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* 事故(本模块的由来)
|
|
7
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
8
|
+
* 定时触发的唤醒端(`scripts/flow-trigger-wake.mjs`)**只从 `/tmp/dsh-web.log` 读 token**;
|
|
9
|
+
* launchd 的 dsh-web 日志恰好是**唯一**落在 `/tmp` 的日志 ⇒ 该文件被系统清理后(进程仍持有
|
|
10
|
+
* 已 unlink 的 inode),门控/认领一切正常、**下一行**就 `❌ 取不到 token` ⇒ 唤醒 exit 2
|
|
11
|
+
* ⇒ 角色会话没起、当轮活没人干,**而全程没有任何失败信号**(静默故障)。
|
|
12
|
+
*
|
|
13
|
+
* ## 为什么 token 只能"从日志捡"
|
|
14
|
+
* dsh 的启动 token 是**每进程随机**的(`dsh-client-connection#processLaunchToken`
|
|
15
|
+
* = `base64url(randomBytes(32))`,见 `AUTH_RECORD_KEY`/`authenticatedUrl`):不进任何配置文件,
|
|
16
|
+
* 只在启动时打印的那行 URL 里(`dsh web: http://127.0.0.1:3080/?token=…`)⇒ 日志没了就"捡"不到。
|
|
17
|
+
*
|
|
18
|
+
* ## 但有**不依赖 /tmp** 的既有机制:browser-session 记录的签名 cookie
|
|
19
|
+
* `~/.dsh/.credentials.yaml` 的 `records.<client-connection/browser-session>`(kind=grant)里
|
|
20
|
+
* 持久化着 cookie 的 **HMAC 密钥**(dsh 首次激活 web 时写入,重启不变);cookie 形如
|
|
21
|
+
* `dsh-auth-<base64url(sha256(authority))> = v1.<base64url(payload)>.<base64url(hmac-sha256)>`,
|
|
22
|
+
* payload 只需要 `{version, authority, issuedAt, expiresAt}` ⇒ 本模块用**同一个密钥、同一种格式**
|
|
23
|
+
* 现签一个(⛔ 不是新协议:格式取自 `dsh-client-connection/lib/index.js` 的 encodeCookie/decodeCookie)。
|
|
24
|
+
*
|
|
25
|
+
* ## 来源优先级(**先验后用**:每个来源都真打一次服务,验不过就换下一个)
|
|
26
|
+
* | # | 来源 | 依赖 | 说明 |
|
|
27
|
+
* |---|---|---|---|
|
|
28
|
+
* | 1 | `ARGENT_WAKE_TOKEN_FILE` | 显式配置 | 运维指定的 token 文件(含 `token=…` 或裸 token) |
|
|
29
|
+
* | 2 | **凭据签名 cookie** | `~/.dsh/.credentials.yaml` | **主源**:不依赖 /tmp、不依赖当前进程的日志 |
|
|
30
|
+
* | 3 | `DSH_WEB_LOG` | 显式配置 | 既有约定(`argent-core` 的 `/url` 也读它) |
|
|
31
|
+
* | 4 | plist 的 `StandardOutPath` | launchd | 自动跟随"日志被挪到哪儿了"(`com.whyshu.dsh-web.plist`) |
|
|
32
|
+
* | 5 | `$DSH_HOME/dsh-web.log`、`~/Library/Logs/dsh-web.log` | 稳定目录 | 运维把日志挪到稳定位置的常见落点 |
|
|
33
|
+
* | 6 | `/tmp/dsh-web.log` | 旧机器 | **兜底兼容**(历史形态;不再是唯一来源) |
|
|
34
|
+
* `ARGENT_WAKE_LOG_PATHS`(`:` 分隔)可**整表覆盖**第 3–6 项(运维把日志挪走、或测试夹具用)。
|
|
35
|
+
*
|
|
36
|
+
* ## 纪律
|
|
37
|
+
* · 只读凭据文件、**绝不打印/落盘** secret 或 cookie(日志只打"来源名");
|
|
38
|
+
* · 每个来源的失败都**分类**(不存在 / 取到了但解析不出 / 已失效)——"文件不存在"与
|
|
39
|
+
* "有文件没 token"是两种故障,混成一句就没法修;
|
|
40
|
+
* · 全部失败 ⇒ 由调用方(`flow-trigger-wake.mjs`)**响亮失败 + 台账 blocked + 遥测**(不许静默)。
|
|
41
|
+
*/
|
|
42
|
+
import { execFileSync } from 'node:child_process'
|
|
43
|
+
import { createHash, createHmac } from 'node:crypto'
|
|
44
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
45
|
+
import { homedir } from 'node:os'
|
|
46
|
+
import { isAbsolute, join } from 'node:path'
|
|
47
|
+
import { isDirectRun } from './is-direct-run.mjs'
|
|
48
|
+
|
|
49
|
+
/** 历史形态:launchd 把 dsh-web 的 stdout/stderr 都指到这里(**兜底**,不再是唯一来源)。 */
|
|
50
|
+
export const LEGACY_LOG = '/tmp/dsh-web.log'
|
|
51
|
+
|
|
52
|
+
/** cookie 前缀与签名算法(取自 dsh-client-connection;改动这里必须同步核该实现)。 */
|
|
53
|
+
export const COOKIE_PREFIX = 'dsh-auth-'
|
|
54
|
+
export const COOKIE_PAYLOAD_VERSION = 1
|
|
55
|
+
export const SECRET_BYTES = 32
|
|
56
|
+
|
|
57
|
+
/** 现签 cookie 的有效期:6 小时(够一轮唤醒用;**远短于** dsh 的 cookieMaxAgeDays,必被接受)。 */
|
|
58
|
+
export const DEFAULT_MAX_AGE_MS = 6 * 60 * 60 * 1000
|
|
59
|
+
|
|
60
|
+
const NET_TIMEOUT_MS = 8000
|
|
61
|
+
|
|
62
|
+
// ── base64url / 摘要 ────────────────────────────────────────────────────────
|
|
63
|
+
const b64url = (buf) => Buffer.from(buf).toString('base64').replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/u, '')
|
|
64
|
+
const unb64url = (s) => Buffer.from(String(s).replaceAll('-', '+').replaceAll('_', '/'), 'base64')
|
|
65
|
+
const sha256 = (s) => createHash('sha256').update(s).digest()
|
|
66
|
+
const hmac256 = (secret, body) => createHmac('sha256', secret).update(body).digest()
|
|
67
|
+
|
|
68
|
+
/** `--base` 的 authority(cookie 的签名绑定的就是它:换 Host 必须重签)。 */
|
|
69
|
+
export function authorityOf(base) {
|
|
70
|
+
try { return new URL(String(base)).host } catch { return '' }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 用持久化密钥现签一个 dsh browser-session cookie(格式同 `dsh-client-connection`)。
|
|
75
|
+
* @param {{authority: string, secret: string, nowMs?: number, maxAgeMs?: number}} o
|
|
76
|
+
* @returns {{name: string, value: string, expiresAt: number}}
|
|
77
|
+
*/
|
|
78
|
+
export function mintCookie({ authority, secret, nowMs = Date.now(), maxAgeMs = DEFAULT_MAX_AGE_MS }) {
|
|
79
|
+
if (!authority) throw new Error('mintCookie 需要 authority(来自 --base 的 host)')
|
|
80
|
+
const key = unb64url(secret)
|
|
81
|
+
if (key.byteLength !== SECRET_BYTES) throw new Error(`browser-session secret 不是 ${SECRET_BYTES} 字节(实际 ${key.byteLength})`)
|
|
82
|
+
const expiresAt = nowMs + maxAgeMs
|
|
83
|
+
const body = b64url(Buffer.from(JSON.stringify({
|
|
84
|
+
version: COOKIE_PAYLOAD_VERSION, authority, issuedAt: nowMs, expiresAt,
|
|
85
|
+
}), 'utf8'))
|
|
86
|
+
return {
|
|
87
|
+
name: COOKIE_PREFIX + b64url(sha256(authority)),
|
|
88
|
+
value: `v1.${body}.${b64url(hmac256(key, body))}`,
|
|
89
|
+
expiresAt,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── 来源①:token 文件(日志形态 或 裸 token)────────────────────────────────
|
|
94
|
+
/**
|
|
95
|
+
* 从一个文本里取**最后一次**出现的 `token=…`(= 启动时打印的那个;日志是追加型的)。
|
|
96
|
+
* @returns {string} 取不到返回空串
|
|
97
|
+
*/
|
|
98
|
+
export function parseTokenFromText(text) {
|
|
99
|
+
const m = String(text || '').match(/token=([A-Za-z0-9_-]{8,})/g) || []
|
|
100
|
+
return m.length ? m[m.length - 1].slice('token='.length) : ''
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 读一个"可能是日志、也可能是裸 token"的文件。
|
|
105
|
+
* 三种结果**必须区分**:`missing`(文件不在)/ `unparsable`(取到了文件但没 token)/ `ok`。
|
|
106
|
+
* @returns {{status:'ok',token:string}|{status:'missing'|'unparsable',detail:string}}
|
|
107
|
+
*/
|
|
108
|
+
export function readTokenFile(path) {
|
|
109
|
+
let text
|
|
110
|
+
try {
|
|
111
|
+
text = readFileSync(path, 'utf8')
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return { status: 'missing', detail: e && e.code === 'ENOENT' ? '文件不存在' : `读不了(${(e && (e.code || e.message)) || e})` }
|
|
114
|
+
}
|
|
115
|
+
const token = parseTokenFromText(text)
|
|
116
|
+
if (token) return { status: 'ok', token }
|
|
117
|
+
const bare = String(text).trim()
|
|
118
|
+
if (/^[A-Za-z0-9_-]{20,}$/.test(bare)) return { status: 'ok', token: bare }
|
|
119
|
+
return { status: 'unparsable', detail: `取到了文件(${String(text).length} 字节)但没解析出 token=(既不是启动日志形态,也不是裸 token)` }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── 来源②:credentials 的 browser-session 记录 ──────────────────────────────
|
|
123
|
+
/** credentials 文件位置(`$DSH_HOME/.credentials.yaml`,缺省 `~/.dsh/.credentials.yaml`)。 */
|
|
124
|
+
export function credentialsPath({ env = process.env, dshHome, home = homedir() } = {}) {
|
|
125
|
+
const h = (dshHome || (env && env.DSH_HOME) || join(home, '.dsh'))
|
|
126
|
+
return join(h, '.credentials.yaml')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 极小 YAML 子集:**只解析"缩进映射"**(`键: 值` / `键:` + 更深缩进的子映射)。
|
|
131
|
+
* 为什么不用通用 YAML:内核(L1)不引第三方依赖;dsh 写这份文件是**定形**的
|
|
132
|
+
* (三段 `version`/`refs`/`records`,见 ~/.dsh/AGENTS.md)。遇到不认识的形态 ⇒ 报告,绝不猜。
|
|
133
|
+
*/
|
|
134
|
+
function parseIndentMap(lines, start, baseIndent) {
|
|
135
|
+
const root = {}
|
|
136
|
+
const stack = [{ indent: baseIndent, node: root }]
|
|
137
|
+
for (let i = start; i < lines.length; i++) {
|
|
138
|
+
const raw = lines[i]
|
|
139
|
+
const t = raw.trim()
|
|
140
|
+
if (!t || t.startsWith('#')) continue
|
|
141
|
+
const ind = raw.length - raw.trimStart().length
|
|
142
|
+
if (ind <= baseIndent) break // 回到同级(下一条记录)或更外层
|
|
143
|
+
while (stack.length > 1 && ind <= stack[stack.length - 1].indent) stack.pop()
|
|
144
|
+
const m = t.match(/^([^:]+):(?:\s*(.*))?$/)
|
|
145
|
+
if (!m) return { error: `第 ${i + 1} 行不是「键: 值」形态:${t.slice(0, 60)}` }
|
|
146
|
+
const key = m[1].trim()
|
|
147
|
+
const rest = (m[2] ?? '').trim().replace(/^["']|["']$/g, '')
|
|
148
|
+
const parent = stack[stack.length - 1].node
|
|
149
|
+
if (rest === '') {
|
|
150
|
+
const child = {}
|
|
151
|
+
parent[key] = child
|
|
152
|
+
stack.push({ indent: ind, node: child })
|
|
153
|
+
} else {
|
|
154
|
+
parent[key] = rest
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { value: root }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 从 credentials 文本里取 `records.<scope>/<id>` 的 grant 载荷密钥(只认缩进结构)。
|
|
162
|
+
* @returns {{status:'ok',secret:string}|{status:'no-record'|'malformed',detail:string}}
|
|
163
|
+
*/
|
|
164
|
+
export function readBrowserSecret(text, recordId = 'client-connection/browser-session') {
|
|
165
|
+
const lines = String(text || '').split(/\r?\n/)
|
|
166
|
+
let inRecords = false
|
|
167
|
+
let recIndent = -1
|
|
168
|
+
for (let i = 0; i < lines.length; i++) {
|
|
169
|
+
const raw = lines[i]
|
|
170
|
+
const t = raw.trim()
|
|
171
|
+
if (!t || t.startsWith('#')) continue
|
|
172
|
+
const ind = raw.length - raw.trimStart().length
|
|
173
|
+
if (ind === 0) {
|
|
174
|
+
inRecords = /^records\s*:/.test(t)
|
|
175
|
+
recIndent = -1
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
178
|
+
if (!inRecords) continue
|
|
179
|
+
if (recIndent < 0) {
|
|
180
|
+
const m = t.match(/^([^:]+):\s*$/) // 记录块头必须是 `<scope>/<id>:`(无值)
|
|
181
|
+
if (!m) return { status: 'malformed', detail: `records 段首个条目不是「<scope>/<id>:」形态:${t.slice(0, 60)}` }
|
|
182
|
+
recIndent = ind
|
|
183
|
+
}
|
|
184
|
+
if (ind !== recIndent) continue
|
|
185
|
+
const m = t.match(/^([^:]+):\s*$/)
|
|
186
|
+
if (!m || m[1].trim() !== recordId) continue
|
|
187
|
+
const parsed = parseIndentMap(lines, i + 1, recIndent)
|
|
188
|
+
if (parsed.error) return { status: 'malformed', detail: `记录 ${recordId} 的缩进结构坏了:${parsed.error}` }
|
|
189
|
+
const rec = parsed.value
|
|
190
|
+
if (rec.kind !== 'grant') return { status: 'malformed', detail: `记录 ${recordId} 的 kind=${JSON.stringify(rec.kind)}(期望 grant)` }
|
|
191
|
+
const payload = rec.payload
|
|
192
|
+
if (!payload || typeof payload !== 'object') return { status: 'malformed', detail: `记录 ${recordId} 缺 payload` }
|
|
193
|
+
if (Number(payload.version) !== 1) return { status: 'malformed', detail: `记录 ${recordId} 的 payload.version=${JSON.stringify(payload.version)}(期望 1)` }
|
|
194
|
+
const secret = String(payload.secret || '')
|
|
195
|
+
if (!secret) return { status: 'malformed', detail: `记录 ${recordId} 缺 payload.secret` }
|
|
196
|
+
if (unb64url(secret).byteLength !== SECRET_BYTES) return { status: 'malformed', detail: `记录 ${recordId} 的 payload.secret 不是 ${SECRET_BYTES} 字节` }
|
|
197
|
+
return { status: 'ok', secret }
|
|
198
|
+
}
|
|
199
|
+
return { status: 'no-record', detail: `没有 records.${recordId} 记录(dsh web 首次启动会写入;非 web 部署本就没有)` }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── 来源④:plist 声明的日志落点(自动跟随"日志挪到哪儿了")───────────────────
|
|
203
|
+
/**
|
|
204
|
+
* 读 `com.whyshu.dsh-web.plist` 的 `StandardOutPath`(回退 `StandardErrorPath`)。
|
|
205
|
+
* 为什么要有:运维把日志从 /tmp 挪走的**正确做法**就是改 plist ⇒ 这里直接跟随声明,
|
|
206
|
+
* 不必再让人来改代码/加环境变量。plutil 不可用(非 macOS)或没这 plist ⇒ 返回空串。
|
|
207
|
+
*/
|
|
208
|
+
export function plistLogPath(launchAgentsDir = join(homedir(), 'Library', 'LaunchAgents')) {
|
|
209
|
+
const plist = join(launchAgentsDir, 'com.whyshu.dsh-web.plist')
|
|
210
|
+
if (!existsSync(plist)) return ''
|
|
211
|
+
try {
|
|
212
|
+
const out = execFileSync('plutil', ['-convert', 'json', '-o', '-', '--', plist],
|
|
213
|
+
{ encoding: 'utf8', timeout: 4000, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
214
|
+
const doc = JSON.parse(out)
|
|
215
|
+
const p = String(doc.StandardOutPath || doc.StandardErrorPath || '')
|
|
216
|
+
return p && isAbsolute(p) ? p : ''
|
|
217
|
+
} catch {
|
|
218
|
+
return ''
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 日志候选路径(**有序**,含历史 `/tmp` 兜底)。
|
|
224
|
+
* `ARGENT_WAKE_LOG_PATHS`(`:` 分隔)给了就**整表覆盖**(运维把日志挪到稳定目录、测试夹具)。
|
|
225
|
+
*/
|
|
226
|
+
export function logPaths({ env = process.env, home = homedir(), dshHome, launchAgentsDir } = {}) {
|
|
227
|
+
const explicit = String((env && env.ARGENT_WAKE_LOG_PATHS) || '').trim()
|
|
228
|
+
if (explicit) return explicit.split(':').map((s) => s.trim()).filter(Boolean)
|
|
229
|
+
const out = []
|
|
230
|
+
const push = (p) => { if (p && !out.includes(p)) out.push(p) }
|
|
231
|
+
push(env && env.DSH_WEB_LOG) // 既有约定(argent-core 也读它)
|
|
232
|
+
push(plistLogPath(launchAgentsDir || join(home, 'Library', 'LaunchAgents')))
|
|
233
|
+
push(join(dshHome || (env && env.DSH_HOME) || join(home, '.dsh'), 'dsh-web.log'))
|
|
234
|
+
push(join(home, 'Library', 'Logs', 'dsh-web.log'))
|
|
235
|
+
push(LEGACY_LOG) // 旧机器兜底(**最后**)
|
|
236
|
+
return out
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 组装**有序**来源表(每个来源带 `label` 与 `obtain()`;`label` 会原样出现在失败文案里)。
|
|
241
|
+
* 优先级见文件头表格;顺序即"先试谁"。
|
|
242
|
+
*/
|
|
243
|
+
export function authSources({ env = process.env, home = homedir(), dshHome, base, logs } = {}) {
|
|
244
|
+
const authority = authorityOf(base)
|
|
245
|
+
const sources = []
|
|
246
|
+
const tokenFile = String((env && env.ARGENT_WAKE_TOKEN_FILE) || '').trim()
|
|
247
|
+
if (tokenFile) {
|
|
248
|
+
sources.push({
|
|
249
|
+
id: 'env-token-file',
|
|
250
|
+
kind: 'token-file',
|
|
251
|
+
label: `ARGENT_WAKE_TOKEN_FILE=${tokenFile}`,
|
|
252
|
+
obtain: () => readTokenFile(tokenFile),
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
const cred = credentialsPath({ env, dshHome, home })
|
|
256
|
+
sources.push({
|
|
257
|
+
id: 'credentials',
|
|
258
|
+
kind: 'credentials',
|
|
259
|
+
label: `凭据文件 ${cred}(records.client-connection/browser-session)`,
|
|
260
|
+
obtain: () => {
|
|
261
|
+
let text
|
|
262
|
+
try {
|
|
263
|
+
text = readFileSync(cred, 'utf8')
|
|
264
|
+
} catch (e) {
|
|
265
|
+
return { status: 'missing', detail: e && e.code === 'ENOENT' ? '文件不存在' : `读不了(${(e && (e.code || e.message)) || e})` }
|
|
266
|
+
}
|
|
267
|
+
const rec = readBrowserSecret(text)
|
|
268
|
+
if (rec.status !== 'ok') return { status: rec.status === 'no-record' ? 'missing' : 'unparsable', detail: rec.detail }
|
|
269
|
+
try {
|
|
270
|
+
return { status: 'ok', cookie: mintCookie({ authority, secret: rec.secret }), detail: '用持久化密钥现签 browser-session cookie(同 dsh-client-connection 格式,不依赖日志)' }
|
|
271
|
+
} catch (e) {
|
|
272
|
+
return { status: 'unparsable', detail: `签不出 cookie:${e.message}` }
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
})
|
|
276
|
+
for (const p of (logs || logPaths({ env, home, dshHome }))) {
|
|
277
|
+
sources.push({
|
|
278
|
+
id: `log:${p}`,
|
|
279
|
+
kind: 'log-file',
|
|
280
|
+
label: `启动日志 ${p}${p === LEGACY_LOG ? '(历史兜底位置)' : ''}`,
|
|
281
|
+
obtain: () => readTokenFile(p),
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
return sources
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ── 取用(先验后用)─────────────────────────────────────────────────────────
|
|
288
|
+
/** 一次 HTTP 探活:区分「服务不可达」(网络层)与「服务拒绝」(401/403)。 */
|
|
289
|
+
async function probe(url, { fetchImpl, cookie, timeoutMs = NET_TIMEOUT_MS, redirect }) {
|
|
290
|
+
const ac = new AbortController()
|
|
291
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs)
|
|
292
|
+
try {
|
|
293
|
+
const res = await fetchImpl(url, {
|
|
294
|
+
redirect: redirect || 'follow',
|
|
295
|
+
headers: cookie ? { cookie: `${cookie.name}=${cookie.value}` } : {},
|
|
296
|
+
signal: ac.signal,
|
|
297
|
+
})
|
|
298
|
+
return { res }
|
|
299
|
+
} catch (e) {
|
|
300
|
+
return { unreachable: String((e && (e.message || e.code)) || e) }
|
|
301
|
+
} finally {
|
|
302
|
+
clearTimeout(timer)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 按优先级逐个试来源,**每个都真打一次服务**(验不过就换下一个)。
|
|
308
|
+
* @returns {Promise<{ok:true,cookie:{name:string,value:string},source:string,tried:Array}|{ok:false,tried:Array,unreachable?:string}>}
|
|
309
|
+
*/
|
|
310
|
+
export async function acquireWakeAuth({ sources, base, fetchImpl = fetch, timeoutMs = NET_TIMEOUT_MS } = {}) {
|
|
311
|
+
const tried = []
|
|
312
|
+
for (const s of sources || []) {
|
|
313
|
+
let got
|
|
314
|
+
try {
|
|
315
|
+
got = await s.obtain()
|
|
316
|
+
} catch (e) {
|
|
317
|
+
got = { status: 'error', detail: String((e && e.message) || e) }
|
|
318
|
+
}
|
|
319
|
+
if (!got || got.status !== 'ok') {
|
|
320
|
+
tried.push({ id: s.id, label: s.label, status: (got && got.status) || 'error', detail: (got && got.detail) || '未知' })
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
if (got.cookie) {
|
|
324
|
+
// 现签 cookie:直接验证服务是否认它
|
|
325
|
+
const { res, unreachable } = await probe(`${base}/`, { fetchImpl, cookie: got.cookie, timeoutMs })
|
|
326
|
+
if (unreachable) return { ok: false, tried, unreachable }
|
|
327
|
+
if (res && res.status === 200) {
|
|
328
|
+
tried.push({ id: s.id, label: s.label, status: 'ok', detail: got.detail })
|
|
329
|
+
return { ok: true, cookie: got.cookie, source: s.label, tried }
|
|
330
|
+
}
|
|
331
|
+
tried.push({ id: s.id, label: s.label, status: 'rejected', detail: `${got.detail} ⇒ 服务返回 HTTP ${res ? res.status : '?'}` })
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
// 启动 token:换 cookie(303 + Set-Cookie 才算数)
|
|
335
|
+
const { res, unreachable } = await probe(`${base}/?token=${encodeURIComponent(got.token)}`, { fetchImpl, timeoutMs, redirect: 'manual' })
|
|
336
|
+
if (unreachable) return { ok: false, tried, unreachable }
|
|
337
|
+
const raw = res ? ((typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : null) || [res.headers.get('set-cookie')] || []).join('; ') : ''
|
|
338
|
+
const pair = raw.split(';')[0].trim()
|
|
339
|
+
const at = pair.indexOf('=')
|
|
340
|
+
if (res && res.status === 303 && at > 0) {
|
|
341
|
+
tried.push({ id: s.id, label: s.label, status: 'ok', detail: `用启动 token 换到 cookie(HTTP 303)` })
|
|
342
|
+
return { ok: true, cookie: { name: pair.slice(0, at), value: pair.slice(at + 1) }, source: s.label, tried }
|
|
343
|
+
}
|
|
344
|
+
tried.push({ id: s.id, label: s.label, status: 'rejected', detail: `token 换不到 cookie(HTTP ${res ? res.status : '?'})—— 多半是旧进程留下的 token` })
|
|
345
|
+
}
|
|
346
|
+
return { ok: false, tried }
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** 来源失败状态 → 人读文案(失败文案与"跳过摘要"共用同一份,避免两处口径漂移)。 */
|
|
350
|
+
export const SOURCE_STATUS_ZH = {
|
|
351
|
+
missing: '取不到', unparsable: '取到了但解析不出', rejected: '被服务拒绝(过期/不匹配)', error: '尝试时出错',
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** 失败文案:**点名所有尝试过的来源**(逐个带原因 + 状态)+ 可执行修法。 */
|
|
355
|
+
export function authFailureText({ tried, base, home = homedir(), dshHome } = {}) {
|
|
356
|
+
const statusZh = SOURCE_STATUS_ZH
|
|
357
|
+
const lines = [`取不到唤醒鉴权(dsh web 的启动 token / browser-session cookie)——已尝试 ${tried.length} 个来源,全部不可用:`]
|
|
358
|
+
tried.forEach((t, i) => lines.push(` ${i + 1}) ❌ ${t.label}:${statusZh[t.status] || t.status}|${t.detail}`))
|
|
359
|
+
const cred = credentialsPath({ dshHome, home })
|
|
360
|
+
lines.push(
|
|
361
|
+
'',
|
|
362
|
+
`服务地址:${base}(鉴权失败 → 角色会话起不来 → 本轮已认领的活没人干;台账 case=wake 已留痕)`,
|
|
363
|
+
'修法(按代价从低到高):',
|
|
364
|
+
' · 把 dsh-web 的启动日志从 /tmp 挪到**稳定目录**(/tmp 会被系统清理,进程还握着已删除的 inode ⇒ 日志"看着有、其实已成孤儿"):',
|
|
365
|
+
` plutil -replace StandardOutPath -string "$HOME/Library/Logs/dsh-web.log" ~/Library/LaunchAgents/com.whyshu.dsh-web.plist`,
|
|
366
|
+
` plutil -replace StandardErrorPath -string "$HOME/Library/Logs/dsh-web.log" ~/Library/LaunchAgents/com.whyshu.dsh-web.plist`,
|
|
367
|
+
` 然后重启 dsh web:bash ~/Code/argent-dsh/scripts/dsh-restart.sh(本模块会自动跟随 plist 的新落点,无需改代码)`,
|
|
368
|
+
` · 或显式指定来源:ARGENT_WAKE_TOKEN_FILE=<含 token= 的文件>;ARGENT_WAKE_LOG_PATHS=<日志路径,":" 分隔>`,
|
|
369
|
+
` · 或确保 ${cred} 里有 records.client-connection/browser-session(dsh web 首次启动即写入 —— 这条**完全不依赖 /tmp**)`,
|
|
370
|
+
)
|
|
371
|
+
return lines.join('\n')
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ── CLI:把"落点链"暴露给 shell 消费者(**唯一真源**,⛔ 别在 .sh 里再抄一份顺序)──────
|
|
375
|
+
// `node scripts/lib/wake-auth.mjs --log-paths` ⇒ 逐行打印候选日志落点(有序,末项是 /tmp 兜底)。
|
|
376
|
+
// 用途:`scripts/dsh-url.sh` / `scripts/check-dsh-web-expose.sh` 等 shell 侧消费者遍历同一条链,
|
|
377
|
+
// 命中即停(因此"有 DSH_WEB_LOG ⇒ 不碰 /tmp"是**同一个循环**的自然结果,不是各写一份判据)。
|
|
378
|
+
if (isDirectRun(import.meta.url)) {
|
|
379
|
+
const args = process.argv.slice(2)
|
|
380
|
+
if (args.includes('--log-paths')) {
|
|
381
|
+
for (const p of logPaths()) console.log(p)
|
|
382
|
+
process.exit(0)
|
|
383
|
+
}
|
|
384
|
+
if (args.includes('--help') || args.length === 0) {
|
|
385
|
+
console.log('用法:node scripts/lib/wake-auth.mjs --log-paths')
|
|
386
|
+
console.log(' 逐行打印 dsh web 启动日志的候选落点(有序;DSH_WEB_LOG → plist → $DSH_HOME → ~/Library/Logs → /tmp)')
|
|
387
|
+
console.log(' ARGENT_WAKE_LOG_PATHS(":" 分隔)可整表覆盖;本文件是"落点顺序"的唯一真源。')
|
|
388
|
+
process.exit(args.length === 0 ? 2 : 0)
|
|
389
|
+
}
|
|
390
|
+
console.error(`未知参数:${args.join(' ')}(只有 --log-paths)`)
|
|
391
|
+
process.exit(2)
|
|
392
|
+
}
|