@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.
Files changed (305) hide show
  1. package/MANIFEST.json +307 -0
  2. package/engine/VERSION +1 -0
  3. package/engine/agents/catalog.json +454 -0
  4. package/engine/configs/_aliases.proposed.json +651 -0
  5. package/engine/configs/_migration-plan.proposed.json +899 -0
  6. package/engine/configs/account.json +272 -0
  7. package/engine/configs/advanced.json +205 -0
  8. package/engine/configs/host.json +149 -0
  9. package/engine/configs/hr.json +27 -0
  10. package/engine/configs/notify.json +264 -0
  11. package/engine/core/cli/argent.mjs +277 -0
  12. package/engine/core/contracts/argent.contracts.json +1065 -0
  13. package/engine/core/contracts/verbs.schema.json +2539 -0
  14. package/engine/core/lib/argent-datadir.mjs +223 -0
  15. package/engine/core/lib/audit.mjs +187 -0
  16. package/engine/core/lib/claim.mjs +258 -0
  17. package/engine/core/lib/config-catalog.mjs +1172 -0
  18. package/engine/core/lib/doctor-customer.mjs +577 -0
  19. package/engine/core/lib/flow-list.mjs +138 -0
  20. package/engine/core/lib/flow-registry.mjs +48 -0
  21. package/engine/core/lib/init.mjs +247 -0
  22. package/engine/core/lib/ledger.mjs +124 -0
  23. package/engine/core/lib/license-activate.mjs +813 -0
  24. package/engine/core/lib/license-device.mjs +314 -0
  25. package/engine/core/lib/license-file.mjs +349 -0
  26. package/engine/core/lib/license-guard.mjs +123 -0
  27. package/engine/core/lib/license-ops.mjs +1645 -0
  28. package/engine/core/lib/license-verdict.mjs +133 -0
  29. package/engine/core/lib/license.mjs +577 -0
  30. package/engine/core/lib/notify/dedup.mjs +101 -0
  31. package/engine/core/lib/notify/feishu.mjs +38 -0
  32. package/engine/core/lib/notify/index.mjs +95 -0
  33. package/engine/core/lib/notify/verb.mjs +327 -0
  34. package/engine/core/lib/notify/webhook.mjs +135 -0
  35. package/engine/core/lib/notify/wecom.mjs +36 -0
  36. package/engine/core/lib/python.mjs +145 -0
  37. package/engine/core/lib/sop.mjs +116 -0
  38. package/engine/core/lib/versions.mjs +205 -0
  39. package/engine/core/mcp/client.mjs +67 -0
  40. package/engine/core/mcp/http.mjs +138 -0
  41. package/engine/core/mcp/server.mjs +702 -0
  42. package/engine/scripts/_engine_rules_probe.py +15 -0
  43. package/engine/scripts/analyze-full-day.py +568 -0
  44. package/engine/scripts/archive/feishu-bridge.js +246 -0
  45. package/engine/scripts/argent-agents.mjs +527 -0
  46. package/engine/scripts/argent-capabilities.mjs +776 -0
  47. package/engine/scripts/argent-catalog.mjs +186 -0
  48. package/engine/scripts/argent-datadir.py +62 -0
  49. package/engine/scripts/argent-doctor.mjs +624 -0
  50. package/engine/scripts/argent-env-write.mjs +302 -0
  51. package/engine/scripts/argent-schedule.mjs +620 -0
  52. package/engine/scripts/argent-setup-check.mjs +369 -0
  53. package/engine/scripts/argent-upgrade.mjs +1046 -0
  54. package/engine/scripts/base-bootstrap.sh +575 -0
  55. package/engine/scripts/billing-mode-daily.sh +12 -0
  56. package/engine/scripts/build-engine-package.mjs +123 -0
  57. package/engine/scripts/capability-packs.lock.json +484 -0
  58. package/engine/scripts/capability_pack.py +1242 -0
  59. package/engine/scripts/check-1.0-criteria.mjs +355 -0
  60. package/engine/scripts/check-agent-catalog.mjs +141 -0
  61. package/engine/scripts/check-billing-mode.sh +377 -0
  62. package/engine/scripts/check-child-sessions.mjs +83 -0
  63. package/engine/scripts/check-config-catalog.mjs +677 -0
  64. package/engine/scripts/check-docs-drift.mjs +522 -0
  65. package/engine/scripts/check-dsh-web-expose.sh +98 -0
  66. package/engine/scripts/check-judgment-drift.mjs +403 -0
  67. package/engine/scripts/check-license.mjs +199 -0
  68. package/engine/scripts/check-model-auth.py +443 -0
  69. package/engine/scripts/check-pack-closure.mjs +661 -0
  70. package/engine/scripts/check-portable-symbols.mjs +116 -0
  71. package/engine/scripts/check-preset-landing.mjs +124 -0
  72. package/engine/scripts/check-production-home.mjs +602 -0
  73. package/engine/scripts/check-role-mapping.mjs +461 -0
  74. package/engine/scripts/check-sample-redaction.mjs +320 -0
  75. package/engine/scripts/check-skill-classification.mjs +188 -0
  76. package/engine/scripts/check-sop-consistency.mjs +431 -0
  77. package/engine/scripts/check-test-env-safety.mjs +627 -0
  78. package/engine/scripts/check-version-consistency.mjs +452 -0
  79. package/engine/scripts/ci-run.sh +558 -0
  80. package/engine/scripts/config-explain.mjs +278 -0
  81. package/engine/scripts/configure-provider.rb +29 -0
  82. package/engine/scripts/customer-config-migrate.mjs +179 -0
  83. package/engine/scripts/customer-preflight.py +892 -0
  84. package/engine/scripts/customer-readiness.sh +134 -0
  85. package/engine/scripts/daily-report-inbox.py +242 -0
  86. package/engine/scripts/daily-report-reconcile.mjs +312 -0
  87. package/engine/scripts/daily-report.mjs +1310 -0
  88. package/engine/scripts/daily-report.sh +46 -0
  89. package/engine/scripts/data/lip-product-dict.json +306 -0
  90. package/engine/scripts/deliver-customer.mjs +932 -0
  91. package/engine/scripts/delivery-faces.txt +31 -0
  92. package/engine/scripts/desktop-trial.sh +146 -0
  93. package/engine/scripts/dev-window.sh +260 -0
  94. package/engine/scripts/drill-clean-machine.sh +315 -0
  95. package/engine/scripts/dsh-api-client.mjs +215 -0
  96. package/engine/scripts/dsh-backup-scope.sh +83 -0
  97. package/engine/scripts/dsh-offline-pack.mjs +77 -0
  98. package/engine/scripts/dsh-overlay-pin.sh +87 -0
  99. package/engine/scripts/dsh-plane.mjs +153 -0
  100. package/engine/scripts/dsh-portforward.py +223 -0
  101. package/engine/scripts/dsh-preflight.mjs +179 -0
  102. package/engine/scripts/dsh-release-watch-daily.sh +19 -0
  103. package/engine/scripts/dsh-release-watch.mjs +458 -0
  104. package/engine/scripts/dsh-remote-check.sh +99 -0
  105. package/engine/scripts/dsh-restart.sh +179 -0
  106. package/engine/scripts/dsh-restore.sh +111 -0
  107. package/engine/scripts/dsh-scope-rewire.mjs +803 -0
  108. package/engine/scripts/dsh-snapshot.mjs +116 -0
  109. package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
  110. package/engine/scripts/dsh-upgrade.mjs +612 -0
  111. package/engine/scripts/dsh-url.sh +86 -0
  112. package/engine/scripts/dsh-web-launch.mjs +155 -0
  113. package/engine/scripts/ensure-store-windows.py +509 -0
  114. package/engine/scripts/entitlements.py +458 -0
  115. package/engine/scripts/env_probe.py +411 -0
  116. package/engine/scripts/feishu-card.mjs +252 -0
  117. package/engine/scripts/fix-skill-frontmatter.py +209 -0
  118. package/engine/scripts/flow-auto-retry.py +454 -0
  119. package/engine/scripts/flow-requirements.json +720 -0
  120. package/engine/scripts/flow-scaffold.py +275 -0
  121. package/engine/scripts/flow-timers-plist.sh +86 -0
  122. package/engine/scripts/flow-timers.sh +182 -0
  123. package/engine/scripts/flow-trigger-wake.mjs +284 -0
  124. package/engine/scripts/flow-trigger.sh +571 -0
  125. package/engine/scripts/flow_billing.py +354 -0
  126. package/engine/scripts/flow_capability_stats.py +314 -0
  127. package/engine/scripts/flow_case.py +194 -0
  128. package/engine/scripts/flow_claim.py +497 -0
  129. package/engine/scripts/flow_config.py +1024 -0
  130. package/engine/scripts/flow_failure.py +588 -0
  131. package/engine/scripts/flow_learn.py +105 -0
  132. package/engine/scripts/flow_ledger.py +235 -0
  133. package/engine/scripts/flow_policy.py +238 -0
  134. package/engine/scripts/flow_preflight.py +339 -0
  135. package/engine/scripts/flow_registry.py +341 -0
  136. package/engine/scripts/flow_retry.py +304 -0
  137. package/engine/scripts/flow_return_to_ops.py +191 -0
  138. package/engine/scripts/flow_source.py +315 -0
  139. package/engine/scripts/flow_sources/__init__.py +53 -0
  140. package/engine/scripts/flow_sources/cases_json.py +106 -0
  141. package/engine/scripts/flow_sources/common.py +428 -0
  142. package/engine/scripts/flow_sources/feishu_base.py +273 -0
  143. package/engine/scripts/flow_stale.py +185 -0
  144. package/engine/scripts/flow_status_reconcile.py +344 -0
  145. package/engine/scripts/flow_triage.py +849 -0
  146. package/engine/scripts/flow_write_verify.py +496 -0
  147. package/engine/scripts/fulfillment.js +586 -0
  148. package/engine/scripts/gen-agent-bundles.mjs +276 -0
  149. package/engine/scripts/gen-preset-rows.mjs +259 -0
  150. package/engine/scripts/init-ml.sh +2275 -0
  151. package/engine/scripts/install-internal-role.sh +190 -0
  152. package/engine/scripts/install-license.mjs +641 -0
  153. package/engine/scripts/install-one-shot.mjs +665 -0
  154. package/engine/scripts/integration_contract.py +220 -0
  155. package/engine/scripts/integrations/wenyao/adapter.py +98 -0
  156. package/engine/scripts/integrations/xzh/adapter.py +75 -0
  157. package/engine/scripts/invoice-mapping.example.json +40 -0
  158. package/engine/scripts/invoice-table.sh +194 -0
  159. package/engine/scripts/invoice_carrier_notice.py +279 -0
  160. package/engine/scripts/invoice_fail_closeout.py +297 -0
  161. package/engine/scripts/invoice_lip_runner.py +1508 -0
  162. package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
  163. package/engine/scripts/invoice_ready_audit.py +191 -0
  164. package/engine/scripts/invoice_runner.py +675 -0
  165. package/engine/scripts/invoice_stageb.py +516 -0
  166. package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
  167. package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
  168. package/engine/scripts/lease.py +377 -0
  169. package/engine/scripts/lib/agent-install.mjs +649 -0
  170. package/engine/scripts/lib/argent-datadir.mjs +16 -0
  171. package/engine/scripts/lib/argent_datadir.py +262 -0
  172. package/engine/scripts/lib/argent_filelock.py +507 -0
  173. package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
  174. package/engine/scripts/lib/cdp_ports.py +197 -0
  175. package/engine/scripts/lib/customer-env.mjs +102 -0
  176. package/engine/scripts/lib/daily-report-core.mjs +517 -0
  177. package/engine/scripts/lib/dep-declaration.mjs +81 -0
  178. package/engine/scripts/lib/deps-migrate.mjs +310 -0
  179. package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
  180. package/engine/scripts/lib/dsh-scope.mjs +500 -0
  181. package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
  182. package/engine/scripts/lib/dsh-versions.mjs +96 -0
  183. package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
  184. package/engine/scripts/lib/is-direct-run.mjs +61 -0
  185. package/engine/scripts/lib/patch-compose.mjs +241 -0
  186. package/engine/scripts/lib/patch-target.mjs +35 -0
  187. package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
  188. package/engine/scripts/lib/plugin-packages.mjs +332 -0
  189. package/engine/scripts/lib/preset-compat.mjs +127 -0
  190. package/engine/scripts/lib/preset-install.mjs +575 -0
  191. package/engine/scripts/lib/preset-mechanism.mjs +56 -0
  192. package/engine/scripts/lib/preset-sync.mjs +59 -0
  193. package/engine/scripts/lib/profile-retire.mjs +97 -0
  194. package/engine/scripts/lib/python-runtime.mjs +190 -0
  195. package/engine/scripts/lib/registry.mjs +72 -0
  196. package/engine/scripts/lib/repo-root.sh +56 -0
  197. package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
  198. package/engine/scripts/lib/schedule-probe.mjs +835 -0
  199. package/engine/scripts/lib/scope-pointer.mjs +164 -0
  200. package/engine/scripts/lib/settings-panels.mjs +816 -0
  201. package/engine/scripts/lib/stdio_utf8.py +86 -0
  202. package/engine/scripts/lib/wake-auth.mjs +392 -0
  203. package/engine/scripts/lib/webdriver_ipc.py +264 -0
  204. package/engine/scripts/lib/zstd-frames.mjs +104 -0
  205. package/engine/scripts/license-e2e-drill.mjs +426 -0
  206. package/engine/scripts/license-gate.mjs +245 -0
  207. package/engine/scripts/lip_backlog.py +171 -0
  208. package/engine/scripts/lip_table.py +185 -0
  209. package/engine/scripts/local-usage.mjs +214 -0
  210. package/engine/scripts/migrate-providers.mjs +127 -0
  211. package/engine/scripts/migrate-skills.sh +65 -0
  212. package/engine/scripts/ml-oauth-setup.mjs +550 -0
  213. package/engine/scripts/ml-store-ids.json +26 -0
  214. package/engine/scripts/ml_artifact_attribution.py +333 -0
  215. package/engine/scripts/ml_capacity_probe.py +1158 -0
  216. package/engine/scripts/ml_hub_status.py +1556 -0
  217. package/engine/scripts/ml_schema_check.py +115 -0
  218. package/engine/scripts/ml_store_ids.py +139 -0
  219. package/engine/scripts/npm-publish-argent.mjs +499 -0
  220. package/engine/scripts/npm-publish-argent.sh +20 -0
  221. package/engine/scripts/orchestrator.py +13811 -0
  222. package/engine/scripts/pack-plugins.mjs +140 -0
  223. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
  224. package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
  225. package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
  226. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
  227. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
  228. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
  229. package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
  230. package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
  231. package/engine/scripts/patches/verify-hardening.mjs +167 -0
  232. package/engine/scripts/patches/verify-session-migration.mjs +149 -0
  233. package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
  234. package/engine/scripts/plan-role-rename.mjs +125 -0
  235. package/engine/scripts/plugin-bootcheck.mjs +451 -0
  236. package/engine/scripts/plugin-composition.json +15 -0
  237. package/engine/scripts/poc-mcp-host.mjs +455 -0
  238. package/engine/scripts/poll-fulfillment.sh +293 -0
  239. package/engine/scripts/poll-invoice.sh +74 -0
  240. package/engine/scripts/profile-doctor.mjs +686 -0
  241. package/engine/scripts/provider-select.sh +70 -0
  242. package/engine/scripts/provider-switch.sh +146 -0
  243. package/engine/scripts/provider-sync-target.check.mjs +64 -0
  244. package/engine/scripts/python-runtime.sh +78 -0
  245. package/engine/scripts/query_runner.py +310 -0
  246. package/engine/scripts/release-argent.sh +574 -0
  247. package/engine/scripts/repair-unresolved-tools.mjs +178 -0
  248. package/engine/scripts/run-tests.sh +256 -0
  249. package/engine/scripts/self-sign-license.mjs +179 -0
  250. package/engine/scripts/session-health-scan.mjs +63 -0
  251. package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
  252. package/engine/scripts/setup.sh +22 -0
  253. package/engine/scripts/skill-install.mjs +629 -0
  254. package/engine/scripts/sku_translate.py +148 -0
  255. package/engine/scripts/sync-plugins.sh +90 -0
  256. package/engine/scripts/sync-presets.sh +27 -0
  257. package/engine/scripts/sync-scripts.sh +445 -0
  258. package/engine/scripts/telemetry.py +873 -0
  259. package/engine/scripts/ui-copy-count.mjs +220 -0
  260. package/engine/scripts/usage-anchor.sh +136 -0
  261. package/engine/scripts/usage_report.py +531 -0
  262. package/engine/scripts/validate-preset-schema.mjs +208 -0
  263. package/engine/scripts/vertical-init.mjs +323 -0
  264. package/engine/scripts/vertical-lint.mjs +482 -0
  265. package/engine/scripts/wenyao-browser.sh +73 -0
  266. package/engine/scripts/wenyao-fixture-export.py +116 -0
  267. package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
  268. package/engine/scripts/wenyao-login.sh +34 -0
  269. package/engine/scripts/wenyao-template-map.json +292 -0
  270. package/engine/scripts/wenyao_attach.py +131 -0
  271. package/engine/scripts/wenyao_download_template.py +144 -0
  272. package/engine/scripts/wenyao_fill_template.py +568 -0
  273. package/engine/scripts/wenyao_login.py +145 -0
  274. package/engine/scripts/wenyao_portal.py +633 -0
  275. package/engine/scripts/wenyao_recon.py +230 -0
  276. package/engine/scripts/wenyao_run.py +503 -0
  277. package/engine/scripts/wenyao_shadow_diff.py +160 -0
  278. package/engine/scripts/wenyao_skill_contract.py +156 -0
  279. package/engine/scripts/wenyao_template_fetch.py +175 -0
  280. package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
  281. package/engine/scripts/ziniao-auth-probe.sh +268 -0
  282. package/engine/scripts/ziniao-keepalive.sh +216 -0
  283. package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
  284. package/engine/scripts/ziniao-windows.sh +153 -0
  285. package/engine/version.mjs +1 -0
  286. package/engine/verticals/README.md +103 -0
  287. package/engine/verticals/_engine-words.baseline.txt +237 -0
  288. package/engine/verticals/_engine-words.json +19 -0
  289. package/engine/verticals/mercadolibre/README.md +43 -0
  290. package/engine/verticals/mercadolibre/config.json +709 -0
  291. package/engine/verticals/mercadolibre/flows.json +706 -0
  292. package/engine/verticals/mercadolibre/manifest.json +538 -0
  293. package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
  294. package/engine/verticals/mercadolibre/preflight.py +143 -0
  295. package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
  296. package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
  297. package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
  298. package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
  299. package/engine/verticals/mercadolibre/prompts/full.md +91 -0
  300. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
  301. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
  302. package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
  303. package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
  304. package/package.json +21 -4
  305. package/README.md +0 -3
@@ -0,0 +1,372 @@
1
+ # Argent on DSH - Windows logon autostart registrar for the `dsh web` host (ASCII-only on purpose).
2
+ #
3
+ # WHY TASK SCHEDULER AND NOT A SERVICE
4
+ # A *user-level* task with a LogonTrigger runs inside the interactive session (session 1) under a
5
+ # limited token: %USERPROFILE%\.dsh credentials stay visible and NO administrator rights are
6
+ # needed. SYSTEM / S4U tasks run in session 0 (USERPROFILE becomes systemprofile) which is a dead
7
+ # end for anything browser-driven. Measured 2026-10-08 on the reference Win11 box; see the
8
+ # Windows feasibility study in docs/ dated 2026-10-08 (sections 2.1 / 3.2 / 3.5).
9
+ #
10
+ # THE THREE HARMFUL TASK SCHEDULER DEFAULTS, WRITTEN EXPLICITLY
11
+ # 1. ExecutionTimeLimit defaults to PT72H -> set to PT0S (unlimited). Without this the host is
12
+ # force-killed on day 3.
13
+ # 2. DisallowStartIfOnBatteries defaults to true (StopIfGoingOnBatteries too) -> both cleared.
14
+ # Without this the host never starts on a laptop, or is cut off when unplugged.
15
+ # 3. Task Scheduler has NO stdout/stderr sink -> the action redirects both into a stable
16
+ # directory: <dsh home>\logs\dsh-web.log (default %USERPROFILE%\.dsh\logs\dsh-web.log).
17
+ #
18
+ # WHY THE ACTION PINS AN ABSOLUTE NODE PATH
19
+ # A bare `node` resolves to the *system* Node (measured: D:\Program Files\nodejs\node.exe =
20
+ # v22.12.0 on the reference box) where `dsh --version` prints NOTHING and exits 0 - a silent
21
+ # failure that looks like success. So the action embeds the Node >= 24.2 absolute path, and this
22
+ # script refuses to install unless that interpreter really answers (`<node> <dsh bin.js> --version`
23
+ # returns a non-empty version). `dsh.cmd` is never used: it calls a bare `node` internally.
24
+ #
25
+ # NO CRASH SELF-HEALING (honest limitation, do not paper over it)
26
+ # RestartOnFailure is deliberately NOT set. It only reacts to non-zero exits (launchd KeepAlive
27
+ # reacts to any exit), and it was measured NOT to fire at all on this platform under both
28
+ # at-trigger and on-demand starts (feasibility doc 3.4). This task therefore gives you
29
+ # "start at logon", NOT "restart when it dies". Do not read it as a KeepAlive equivalent.
30
+ #
31
+ # Usage (run these IN POWERSHELL, from any directory; the default install location is quoted below).
32
+ # `%USERPROFILE%` is a cmd.exe variable and does NOT expand in PowerShell.
33
+ # WRONG (cmd-only syntax, kept here as the counter-example):
34
+ # -File %USERPROFILE%\...\dsh-web-autostart.ps1 -> "The argument '...' to the -File parameter
35
+ # does not exist" (measured on Win11 / PowerShell 5.1, 2026-10-09).
36
+ # RIGHT: PowerShell needs "$env:USERPROFILE\..." QUOTED (same shape install.ps1 prints):
37
+ # powershell -NoProfile -ExecutionPolicy Bypass -File "$env:USERPROFILE\.argent-dsh\scripts\win\dsh-web-autostart.ps1" install
38
+ # powershell -NoProfile -ExecutionPolicy Bypass -File "$env:USERPROFILE\.argent-dsh\scripts\win\dsh-web-autostart.ps1" status
39
+ # powershell -NoProfile -ExecutionPolicy Bypass -File "$env:USERPROFILE\.argent-dsh\scripts\win\dsh-web-autostart.ps1" uninstall
40
+ # (already inside this file's directory? then the relative form is fine: -File ".\dsh-web-autostart.ps1" <verb>)
41
+ #
42
+ # Options: -TaskName <name> (default WhyshuDshWeb), -Port <n> (default 3080),
43
+ # -NodePath <node.exe>, -DshBin <...\@deepseek-ai\dsh\lib\bin.js>,
44
+ # -DshHome <dir> (default %USERPROFILE%\.dsh), -LogDir <dir> (default <DshHome>\logs)
45
+ #
46
+ # Exit codes: 0 ok / 1 failure / 2 usage error.
47
+ #
48
+ # NOTE for maintainers: *.ps1 is intentionally NOT enrolled in scripts/tests/kernel-purity.test.mjs
49
+ # LAYER_RULES. That gate only walks SCRIPT_CODE_RE = .mjs/.js/.py/.sh/.rb, and it fails a rule
50
+ # that matches zero files ("dead rule"), so adding a `scripts/win/*.ps1` glob there would turn
51
+ # the gate RED. This registrar is host-side (L2) by construction; it holds no engine judgement.
52
+
53
+ [CmdletBinding()]
54
+ param(
55
+ [Parameter(Position = 0)][string]$Command = 'status',
56
+ [string]$TaskName = 'WhyshuDshWeb',
57
+ [int]$Port = 3080,
58
+ [string]$NodePath = '',
59
+ [string]$DshBin = '',
60
+ [string]$DshHome = '',
61
+ [string]$LogDir = ''
62
+ )
63
+
64
+ $ErrorActionPreference = 'Stop'
65
+ $MIN_NODE_MAJOR = 24
66
+ $MIN_NODE_MINOR = 2
67
+
68
+ function Fail {
69
+ param([string]$Message)
70
+ Write-Output ''
71
+ Write-Output ("ERROR: " + $Message)
72
+ exit 1
73
+ }
74
+ function Show-Usage {
75
+ Write-Output 'usage: dsh-web-autostart.ps1 <install|status|uninstall> [-TaskName n] [-Port n]'
76
+ Write-Output ' [-NodePath node.exe] [-DshBin bin.js] [-DshHome dir] [-LogDir dir]'
77
+ exit 2
78
+ }
79
+
80
+ # --- resolution ------------------------------------------------------------
81
+
82
+ function Resolve-NodeExe {
83
+ $cands = New-Object System.Collections.ArrayList
84
+ if ($NodePath) { [void]$cands.Add($NodePath) }
85
+ [void]$cands.Add((Join-Path $env:USERPROFILE '.local\node\node.exe'))
86
+ $onPath = Get-Command node -ErrorAction SilentlyContinue
87
+ if ($onPath -and $onPath.Source) { [void]$cands.Add($onPath.Source) }
88
+ $tried = New-Object System.Collections.ArrayList
89
+ foreach ($c in $cands) {
90
+ if (-not $c) { continue }
91
+ if (-not (Test-Path -LiteralPath $c)) { [void]$tried.Add($c + ' (missing)'); continue }
92
+ $ver = ''
93
+ try { $ver = ((& $c --version 2>&1) | Out-String).Trim() } catch { $ver = '' }
94
+ if ($ver -match '^v?(\d+)\.(\d+)') {
95
+ $maj = [int]$Matches[1]; $min = [int]$Matches[2]
96
+ if ($maj -gt $MIN_NODE_MAJOR -or ($maj -eq $MIN_NODE_MAJOR -and $min -ge $MIN_NODE_MINOR)) {
97
+ return @{ Path = $c; Version = $ver; Tried = $tried }
98
+ }
99
+ [void]$tried.Add($c + ' (' + $ver + ', too old)')
100
+ } else {
101
+ [void]$tried.Add($c + ' (no version output)')
102
+ }
103
+ }
104
+ return @{ Path = ''; Version = ''; Tried = $tried }
105
+ }
106
+
107
+ function Resolve-DshBin {
108
+ $cands = New-Object System.Collections.ArrayList
109
+ if ($DshBin) { [void]$cands.Add($DshBin) }
110
+ if ($env:APPDATA) { [void]$cands.Add((Join-Path $env:APPDATA 'npm\node_modules\@deepseek-ai\dsh\lib\bin.js')) }
111
+ $npm = Get-Command npm -ErrorAction SilentlyContinue
112
+ if ($npm) {
113
+ try {
114
+ $prefix = ((& npm prefix -g 2>$null) | Out-String).Trim()
115
+ if ($prefix) { [void]$cands.Add((Join-Path $prefix 'node_modules\@deepseek-ai\dsh\lib\bin.js')) }
116
+ } catch { }
117
+ }
118
+ $shim = Get-Command dsh -ErrorAction SilentlyContinue
119
+ if ($shim -and $shim.Source) {
120
+ $dir = Split-Path -Parent $shim.Source
121
+ [void]$cands.Add((Join-Path $dir 'node_modules\@deepseek-ai\dsh\lib\bin.js'))
122
+ }
123
+ $tried = New-Object System.Collections.ArrayList
124
+ foreach ($c in $cands) {
125
+ if (-not $c) { continue }
126
+ if (Test-Path -LiteralPath $c) { return @{ Path = $c; Tried = $tried } }
127
+ [void]$tried.Add($c + ' (missing)')
128
+ }
129
+ return @{ Path = ''; Tried = $tried }
130
+ }
131
+
132
+ # --- task text helpers ------------------------------------------------------------
133
+
134
+ function Get-TaskXml {
135
+ param([string]$Name)
136
+ try { return (Export-ScheduledTask -TaskName $Name -ErrorAction Stop) } catch { return '' }
137
+ }
138
+ function Rx {
139
+ param([string]$Text, [string]$Pattern)
140
+ $m = [regex]::Match($Text, $Pattern)
141
+ if ($m.Success) { return $m.Groups[1].Value }
142
+ return ''
143
+ }
144
+
145
+ function Get-TaskPath {
146
+ param([string]$Name, [string]$DshHomeResolved)
147
+ return (Join-Path $DshHomeResolved 'logs\dsh-web.log')
148
+ }
149
+
150
+ # --- status (read-only) ------------------------------------------------------------
151
+
152
+ function Show-Status {
153
+ param([string]$Name, [string]$DshHomeResolved)
154
+ $logFile = Get-TaskPath -Name $Name -DshHomeResolved $DshHomeResolved
155
+ Write-Output '=== dsh web autostart - status (read-only) ==='
156
+ Write-Output ("task name : " + $Name)
157
+ $task = Get-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue
158
+ if (-not $task) {
159
+ Write-Output 'installed : no'
160
+ Write-Output ("log file : " + $logFile + " (exists=" + (Test-Path -LiteralPath $logFile) + ")")
161
+ Write-Output 'self-healing : none (RestartOnFailure deliberately not set; measured not to fire)'
162
+ Write-Output ("install with : powershell -NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" install")
163
+ return
164
+ }
165
+ $xml = Get-TaskXml -Name $Name
166
+ Write-Output 'installed : yes'
167
+ Write-Output ("state : " + $task.State)
168
+ $trigBlock = [regex]::Match($xml, '<LogonTrigger>.*?</LogonTrigger>', 'Singleline').Value
169
+ $trigKind = 'LogonTrigger'
170
+ if (-not $trigBlock) { $trigKind = 'OTHER (no LogonTrigger found in XML)' }
171
+ $trigUser = Rx -Text $trigBlock -Pattern '<UserId>(.*?)</UserId>'
172
+ $trigDelay = Rx -Text $trigBlock -Pattern '<Delay>(.*?)</Delay>'
173
+ if (-not $trigDelay) { $trigDelay = '(none => starts immediately at logon)' }
174
+ Write-Output ("trigger : " + $trigKind + " UserId=" + $trigUser + " Delay=" + $trigDelay)
175
+ $logonType = Rx -Text $xml -Pattern '<LogonType>(.*?)</LogonType>'
176
+ $runLevel = Rx -Text $xml -Pattern '<RunLevel>(.*?)</RunLevel>'
177
+ if (-not $runLevel) { $runLevel = 'LeastPrivilege (absent in XML = schema default)' }
178
+ Write-Output ("principal : " + $trigUser + " LogonType=" + $logonType + " RunLevel=" + $runLevel)
179
+ $actionArgs = ''
180
+ foreach ($a in $task.Actions) {
181
+ Write-Output ("action execute : " + $a.Execute)
182
+ Write-Output ("action arguments : " + $a.Arguments)
183
+ Write-Output ("action workdir : " + $a.WorkingDirectory)
184
+ $actionArgs = [string]$a.Arguments
185
+ }
186
+ $etl = Rx -Text $xml -Pattern '<ExecutionTimeLimit>(.*?)</ExecutionTimeLimit>'
187
+ $batt = Rx -Text $xml -Pattern '<DisallowStartIfOnBatteries>(.*?)</DisallowStartIfOnBatteries>'
188
+ $stopb = Rx -Text $xml -Pattern '<StopIfGoingOnBatteries>(.*?)</StopIfGoingOnBatteries>'
189
+ $idle = Rx -Text $xml -Pattern '<StopOnIdleEnd>(.*?)</StopOnIdleEnd>'
190
+ $mip = Rx -Text $xml -Pattern '<MultipleInstancesPolicy>(.*?)</MultipleInstancesPolicy>'
191
+ Write-Output ("settings : ExecutionTimeLimit=" + $etl + " DisallowStartIfOnBatteries=" + $batt + " StopIfGoingOnBatteries=" + $stopb + " StopOnIdleEnd=" + $idle + " MultipleInstancesPolicy=" + $mip)
192
+ $restart = [regex]::Match($xml, '<RestartOnFailure>.*?</RestartOnFailure>', 'Singleline')
193
+ if ($restart.Success) {
194
+ Write-Output ("self-healing : RestartOnFailure present in XML -> " + $restart.Value.Replace("`r", ' ').Replace("`n", ' '))
195
+ } else {
196
+ Write-Output 'self-healing : none (RestartOnFailure deliberately not set; measured not to fire on this platform)'
197
+ }
198
+ $dshHomeInAction = Rx -Text $actionArgs -Pattern 'DSH_HOME=([^"]+)"'
199
+ Write-Output ("dsh home : " + $dshHomeInAction)
200
+ $nodeInAction = Rx -Text $actionArgs -Pattern '&&\s+"([^"]+)"'
201
+ Write-Output ("pinned node : " + $nodeInAction + " exists=" + (Test-Path -LiteralPath $nodeInAction))
202
+ $binInAction = Rx -Text $actionArgs -Pattern '\.exe"\s+"([^"]+)"'
203
+ Write-Output ("dsh entry : " + $binInAction + " exists=" + (Test-Path -LiteralPath $binInAction))
204
+ $info = $null
205
+ try { $info = Get-ScheduledTaskInfo -TaskName $Name -ErrorAction Stop } catch { $info = $null }
206
+ if ($info) {
207
+ $res = [int]$info.LastTaskResult
208
+ $resNote = ''
209
+ if ($res -eq 267011) { $resNote = ' = 0x41303 SCHED_S_TASK_HAS_NOT_RUN (never started yet)' }
210
+ elseif ($res -eq 267009) { $resNote = ' = 0x41301 SCHED_S_TASK_RUNNING (it is running right now)' }
211
+ Write-Output ("last run : " + $info.LastRunTime + " result=" + $res + " (0x" + ('{0:X}' -f $res) + ")" + $resNote + " missed=" + $info.NumberOfMissedRuns)
212
+ Write-Output ("next run : " + $info.NextRunTime + " (empty is normal for a logon-only trigger)")
213
+ }
214
+ $listen = 0
215
+ try { $listen = (Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue | Measure-Object).Count } catch { $listen = -1 }
216
+ Write-Output ("port " + $Port + " listeners : " + $listen + " (0 = dsh web is not running right now)")
217
+ $exists = Test-Path -LiteralPath $logFile
218
+ $size = -1
219
+ $mtime = ''
220
+ if ($exists) {
221
+ $fi = Get-Item -LiteralPath $logFile
222
+ $size = $fi.Length
223
+ $mtime = $fi.LastWriteTime
224
+ }
225
+ Write-Output ("log file : " + $logFile + " exists=" + $exists + " size=" + $size + " mtime=" + $mtime)
226
+ Write-Output ("uninstall with : powershell -NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" uninstall -TaskName " + $Name)
227
+ }
228
+
229
+ # --- install ------------------------------------------------------------
230
+
231
+ function Install-Task {
232
+ param([string]$Name, [int]$Port, [string]$DshHomeArg, [string]$LogDirArg)
233
+ $dshHomeResolved = $DshHomeArg
234
+ if (-not $dshHomeResolved) { $dshHomeResolved = Join-Path $env:USERPROFILE '.dsh' }
235
+ $logDirResolved = $LogDirArg
236
+ if (-not $logDirResolved) { $logDirResolved = Join-Path $dshHomeResolved 'logs' }
237
+ $logFile = Join-Path $logDirResolved 'dsh-web.log'
238
+
239
+ Write-Output '=== dsh web autostart - install (user-level logon task; no administrator needed) ==='
240
+
241
+ $node = Resolve-NodeExe
242
+ if (-not $node.Path) {
243
+ Write-Output ' candidates tried:'
244
+ foreach ($t in $node.Tried) { Write-Output (' - ' + $t) }
245
+ Fail ("no Node.js >= $MIN_NODE_MAJOR.$MIN_NODE_MINOR found. The action must pin an absolute Node path: a bare 'node' resolves to the system Node where 'dsh --version' prints nothing and exits 0 (silent failure). Install Node 24 (e.g. unzip node-v24.x-win-x64.zip to %USERPROFILE%\.local\node) or pass -NodePath.")
246
+ }
247
+ Write-Output (" node : " + $node.Path + " (" + $node.Version + ")")
248
+
249
+ $bin = Resolve-DshBin
250
+ if (-not $bin.Path) {
251
+ Write-Output ' candidates tried:'
252
+ foreach ($t in $bin.Tried) { Write-Output (' - ' + $t) }
253
+ Fail 'dsh entry point (lib\bin.js) not found. Install dsh first, or pass -DshBin.'
254
+ }
255
+ Write-Output (" dsh entry : " + $bin.Path)
256
+
257
+ # The silent-failure gate: the pinned interpreter must really answer.
258
+ $verOut = ''
259
+ $verCode = -1
260
+ try {
261
+ $verOut = ((& $node.Path $bin.Path --version 2>&1) | Out-String).Trim()
262
+ $verCode = $LASTEXITCODE
263
+ } catch {
264
+ $verOut = ''
265
+ $verCode = -1
266
+ }
267
+ if (-not $verOut) {
268
+ Fail ("'" + $node.Path + " " + $bin.Path + " --version' returned EMPTY output (exit " + $verCode + "). That is the documented silent-failure mode (Node too old for import.meta.main): the task would look registered but never serve. Refusing to install.")
269
+ }
270
+ Write-Output (" dsh version : " + $verOut + " (non-empty => the pinned interpreter really runs dsh)")
271
+ if ($verCode -ne 0) { Write-Output (" note : --version exited " + $verCode + "; continuing because output was non-empty") }
272
+
273
+ if (-not (Test-Path -LiteralPath $logDirResolved)) {
274
+ New-Item -ItemType Directory -Force -Path $logDirResolved | Out-Null
275
+ Write-Output (" log dir : " + $logDirResolved + " (created)")
276
+ } else {
277
+ Write-Output (" log dir : " + $logDirResolved + " (already existed)")
278
+ }
279
+
280
+ $listening = 0
281
+ try { $listening = (Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue | Measure-Object).Count } catch { $listening = -1 }
282
+ if ($listening -gt 0) {
283
+ Write-Output (" note : port " + $Port + " already has a listener - a dsh web instance is running. The new task will not be able to bind until that one exits (MultipleInstancesPolicy=IgnoreNew only de-duplicates this task).")
284
+ }
285
+
286
+ # Action: cmd.exe handles the redirection that Task Scheduler cannot do itself.
287
+ $cmdExe = Join-Path $env:SystemRoot 'System32\cmd.exe'
288
+ $inner = 'set "DSH_HOME=' + $dshHomeResolved + '" && "' + $node.Path + '" "' + $bin.Path + '" web --port ' + $Port + ' --no-open >> "' + $logFile + '" 2>&1'
289
+ $actionArg = '/c "' + $inner + '"'
290
+
291
+ $action = New-ScheduledTaskAction -Execute $cmdExe -Argument $actionArg -WorkingDirectory $env:USERPROFILE
292
+ $userName = [Security.Principal.WindowsIdentity]::GetCurrent().Name
293
+ $trigger = New-ScheduledTaskTrigger -AtLogOn -User $userName
294
+ # The three harmful defaults are overridden here. RestartCount is deliberately absent.
295
+ $settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew `
296
+ -ExecutionTimeLimit ([TimeSpan]::Zero) `
297
+ -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd
298
+ $principal = New-ScheduledTaskPrincipal -UserId $userName -LogonType Interactive -RunLevel Limited
299
+
300
+ Register-ScheduledTask -TaskName $Name -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force | Out-Null
301
+ Write-Output (" registered : " + $Name + " (LogonTrigger UserId=" + $userName + ", LogonType=Interactive, RunLevel=Limited)")
302
+
303
+ # Verify what actually landed; roll back rather than leave a mis-shaped task behind.
304
+ $xml = Get-TaskXml -Name $Name
305
+ $problems = New-Object System.Collections.ArrayList
306
+ if ($xml -notmatch '<LogonTrigger>') { [void]$problems.Add('no LogonTrigger in the registered XML') }
307
+ if ($xml -notmatch '<LogonType>InteractiveToken</LogonType>') { [void]$problems.Add('LogonType is not InteractiveToken (session 0 risk)') }
308
+ if (-not $xml.Contains([System.Security.SecurityElement]::Escape($node.Path))) { [void]$problems.Add('action does not contain the pinned node path ' + $node.Path) }
309
+ if ($xml -notmatch '<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>') { [void]$problems.Add('ExecutionTimeLimit is not PT0S (default PT72H kills the host on day 3)') }
310
+ if ($xml -notmatch '<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>') { [void]$problems.Add('DisallowStartIfOnBatteries is not false') }
311
+ if ($xml -notmatch '<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>') { [void]$problems.Add('StopIfGoingOnBatteries is not false') }
312
+ if ($xml -notmatch '<StopOnIdleEnd>false</StopOnIdleEnd>') { [void]$problems.Add('StopOnIdleEnd is not false (a long-running host must not be stopped on idle-end)') }
313
+ if ($xml -match '<RunOnlyIfIdle>true</RunOnlyIfIdle>') { [void]$problems.Add('RunOnlyIfIdle is true - the host would only start on an idle machine') }
314
+ if ($xml -match '<RestartOnFailure>') { [void]$problems.Add('RestartOnFailure is present - remove it, it is measured not to fire and must not be sold as self-healing') }
315
+ if ($problems.Count -gt 0) {
316
+ foreach ($p in $problems) { Write-Output (' VERIFY FAILED : ' + $p) }
317
+ Unregister-ScheduledTask -TaskName $Name -Confirm:$false
318
+ Fail ('registered task did not match the required shape => rolled back (task ' + $Name + ' removed)')
319
+ }
320
+ Write-Output ' verified : LogonTrigger + pinned node path + ExecutionTimeLimit=PT0S + battery/idle flags false + no RestartOnFailure'
321
+
322
+ Write-Output ''
323
+ Write-Output ' RESULT : REGISTERED (logon autostart). It will start at the NEXT logon of this user.'
324
+ Write-Output (' start it now : schtasks /run /tn "' + $Name + '"')
325
+ Write-Output (" log : " + $logFile)
326
+ Write-Output ' WARNING: the host prints its access URL with a ?token=... into this log.'
327
+ Write-Output ' Treat the log as a credential-bearing file: do not paste it into chats,'
328
+ Write-Output ' tickets or repos; redact the token if you must quote a line.'
329
+ Write-Output ' self-healing : NONE. RestartOnFailure is deliberately not set (measured not to fire);'
330
+ Write-Output ' if the process dies, nothing restarts it until the next logon.'
331
+ Write-Output (' remove it with : powershell -NoProfile -ExecutionPolicy Bypass -File "' + $PSCommandPath + '" uninstall -TaskName ' + $Name)
332
+ }
333
+
334
+ # --- uninstall (idempotent) ------------------------------------------------------------
335
+
336
+ function Uninstall-Task {
337
+ param([string]$Name, [string]$DshHomeResolved)
338
+ $logFile = Get-TaskPath -Name $Name -DshHomeResolved $DshHomeResolved
339
+ Write-Output '=== dsh web autostart - uninstall ==='
340
+ $task = Get-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue
341
+ if (-not $task) {
342
+ Write-Output (" " + $Name + " is not registered - nothing to do (idempotent).")
343
+ Write-Output ' RESULT : NOT INSTALLED'
344
+ return
345
+ }
346
+ Unregister-ScheduledTask -TaskName $Name -Confirm:$false
347
+ $still = Get-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue
348
+ if ($still) { Fail ('unregister reported success but ' + $Name + ' is still present') }
349
+ Write-Output (" removed : " + $Name + " (verified absent)")
350
+ Write-Output (" log kept : " + $logFile + " (exists=" + (Test-Path -LiteralPath $logFile) + ") - logs are evidence, not cleaned up")
351
+ Write-Output ' note : an already-running dsh web process is NOT stopped by uninstall.'
352
+ Write-Output ' RESULT : UNINSTALLED'
353
+ }
354
+
355
+ # --- dispatch ------------------------------------------------------------
356
+
357
+ $dshHomeDefault = $DshHome
358
+ if (-not $dshHomeDefault) { $dshHomeDefault = Join-Path $env:USERPROFILE '.dsh' }
359
+
360
+ switch ($Command.ToLower()) {
361
+ 'install' { Install-Task -Name $TaskName -Port $Port -DshHomeArg $DshHome -LogDirArg $LogDir }
362
+ 'status' { Show-Status -Name $TaskName -DshHomeResolved $dshHomeDefault }
363
+ 'uninstall' { Uninstall-Task -Name $TaskName -DshHomeResolved $dshHomeDefault }
364
+ 'help' { Show-Usage }
365
+ '-h' { Show-Usage }
366
+ '--help' { Show-Usage }
367
+ default {
368
+ Write-Output ("unknown command: " + $Command)
369
+ Show-Usage
370
+ }
371
+ }
372
+ exit 0
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env bash
2
+ # ziniao-auth-probe.sh —— 紫鸟**凭证/授权**心跳(W2 常驻 WebDriver 模式的配套)
3
+ #
4
+ # ## 回答的问题
5
+ # 「常驻 WebDriver 模式后,还需要人工每 30 天点一次登录吗?」
6
+ # WebDriver 模式的鉴权是**随请求走的**:每次调用都带 company/username/password,并用
7
+ # `applyAuth` 申请/续期设备授权(官方 api-core.md)。所以自动化**不依赖 GUI 登录态**;
8
+ # 而 GUI/Bridge 侧的登录在 ziniao-cli 1.1.3 起也能**程序化恢复**(`ziniao-cli zclaw login`
9
+ # 用 API Key 登录本地浏览器,无需人工点击)。
10
+ #
11
+ # 但"不用人工"这件事不能靠推断:供应商文档里**没有**任何有效期/续期字样,我们也没有实测过
12
+ # 30 天窗口。所以本脚本把这件事变成**可观测**:每天(去重)跑一次凭据链路探针,
13
+ # ① 先探免凭据的 `getRunningInfo`(WebDriver IPC 活着吗)
14
+ # ② 再探带凭据的 applyAuth + getBrowserList(授权链路活着吗)
15
+ # ③ 失败 ⇒ **先自己修**:`ziniao-cli zclaw login`(API Key 登录)→ 复探
16
+ # ④ 仍失败 ⇒ 响亮输出 + 落台账 + 遥测(**提前叫人**,而不是等下一票跑失败)
17
+ # 这样 30 天窗口是"数据说的",不是"猜的";真需要人工时也会提前几天暴露(而不是当天才发现)。
18
+ # 另外先探**客户端启动模式**:人工打开客户端 = GUI 模式 ⇒ 常驻 WebDriver IPC 必然不存在,
19
+ # 这类"IPC 失败"的首因是模式不对(一句话可修),不要误报成"登录过期要人工登录"。
20
+ #
21
+ # 用法:
22
+ # bash scripts/ziniao-auth-probe.sh # 带 12h 去重(默认;给定时器用)
23
+ # bash scripts/ziniao-auth-probe.sh --force # 忽略去重,立刻探一次(排障用)
24
+ # bash scripts/ziniao-auth-probe.sh --quiet # 只输出结论行
25
+ # 退出码:0=正常 / 2=需要人工(授权链路不可用且自愈失败)/ 3=跳过(未配置凭据或无 CLI)
26
+ set -uo pipefail
27
+
28
+ # 仓库根:共享解析(脚本自身位置优先)—— 写死 ~/Code/argent-dsh 会让客户机
29
+ # (克隆在 ~/.argent-dsh)上找不到本仓文件(2026-09-28 实测)
30
+ . "$(dirname "${BASH_SOURCE[0]}")/lib/repo-root.sh"
31
+ REPO="$(argent_repo_root "${BASH_SOURCE[0]}")"
32
+ TTL_H="${ZINIAO_AUTH_PROBE_TTL_H:-12}"
33
+ FORCE=0; QUIET=0
34
+ for a in "$@"; do
35
+ case "$a" in
36
+ --force) FORCE=1 ;;
37
+ --quiet) QUIET=1 ;;
38
+ esac
39
+ done
40
+
41
+ log() { [ "$QUIET" = "1" ] || echo "[$(date '+%F %T')] $*"; }
42
+ pick_py() {
43
+ local c
44
+ for c in "${ARGENT_PY:-}" "$HOME/.dsh/argent/venv/bin/python3" "$REPO/.venv/bin/python3" \
45
+ "$(command -v python3 2>/dev/null || true)"; do
46
+ [ -n "$c" ] && [ -x "$c" ] && { printf '%s' "$c"; return 0; }
47
+ done
48
+ return 1
49
+ }
50
+ PY="$(pick_py)" || { echo "❌ 找不到 python3" >&2; exit 3; }
51
+
52
+ # 数据目录:**唯一判据**在 Python 单一实现(scripts/lib/argent_datadir.py,经 CLI 包装
53
+ # scripts/argent-datadir.py)——⛔ 不在 bash 里重写三级链。取不到 ⇒ 响亮停住(fail-closed)。
54
+ # 用**已解析好的 $PY**(上面 pick_py 验过可执行):客户机 cron/launchd 的 PATH 极简,
55
+ # 这里再 `command -v python3` 只会平白多一种失败模式。
56
+ DATA="$("$PY" "$REPO/scripts/argent-datadir.py" 2>/dev/null || true)"
57
+ if [ -z "$DATA" ]; then
58
+ echo "❌ $(basename "${BASH_SOURCE[0]}"): 数据目录判据不可用($REPO/scripts/argent-datadir.py 取不到)⇒ 停住,不猜目录" >&2
59
+ exit 2
60
+ fi
61
+ STAMP="$DATA/.ziniao-auth-probe.stamp"
62
+ command -v ziniao-cli >/dev/null 2>&1 || { log "未安装 ziniao-cli ⇒ 跳过(非紫鸟环境)"; exit 3; }
63
+
64
+ # 去重:默认 12h 一次(定时器每 5 分钟跑,不必每轮都打供应商接口)
65
+ if [ "$FORCE" != "1" ] && [ -f "$STAMP" ]; then
66
+ if [ "$(find "$STAMP" -mmin -$(( TTL_H * 60 )) 2>/dev/null | wc -l | tr -d ' ')" = "1" ]; then
67
+ log "授权探针 ${TTL_H}h 内已跑过 ⇒ 跳过(--force 可强制)"
68
+ exit 0
69
+ fi
70
+ fi
71
+ mkdir -p "$DATA"; : > "$STAMP"
72
+
73
+ # ── ⓪-1 客户端**启动模式**:GUI 模式下 WebDriver IPC 本来就不存在 ──
74
+ # 同一实例不能既是 GUI 又是 WebDriver;人工打开客户端会把常驻自动化模式顶掉,
75
+ # 此时"IPC 不可用"的**首因是模式不对**,不是授权失效——先分清,再决定叫不叫人。
76
+ client_mode="未运行"
77
+ _main_pid="$(pgrep -f "[A]pplications/ziniao.app/Contents/MacOS/ziniao" 2>/dev/null | head -1 || true)"
78
+ if [ -n "$_main_pid" ]; then
79
+ _args="$(ps -o command= -p "$_main_pid" 2>/dev/null || true)"
80
+ case "$_args" in
81
+ *--run_type=web_driver*) client_mode="web_driver(常驻自动化模式)" ;;
82
+ *) client_mode="GUI(人工模式,无 WebDriver IPC)" ;;
83
+ esac
84
+ fi
85
+ log "⓪-0 客户端启动模式:${client_mode}"
86
+
87
+ # ── ⓪ 客户端登录态(用户 2026-09-24:登录按钮下方写的是"30 天")──
88
+ # 这一层是**客户端会话**(GUI/Bridge 用),与 WebDriver 的凭据链路是两回事:
89
+ # 30 天到期只影响前者;后者每次调用自带 company/username/password + applyAuth。
90
+ # 把两层分开报告,叫人时才能一句话说清"该点哪里"。
91
+ # 瞬态容忍(2026-09-24 实测):客户端**刚启动**的那几秒,doctor 会报
92
+ # 「客户端已启动,但尚未登录」(登录态还没恢复)⇒ 立刻断言"未登录"是假警报。
93
+ # keepalive 拉起客户端后会**马上**调本探针,正好踩这个窗口 ⇒ 这里复探最多 3 次(5s/10s 退避)。
94
+ _probe_login_state() {
95
+ ziniao-cli doctor 2>&1 | grep -oE "客户端登录用户: [^ ]+|客户端已启动,但尚未登录|CLIENT_LOGGED_OUT" | head -1
96
+ }
97
+ _doctor_out="$(ziniao-cli doctor 2>&1 || true)"
98
+ client_login="$(printf '%s' "$_doctor_out" | grep -oE "客户端登录用户: [^ ]+|客户端已启动,但尚未登录|CLIENT_LOGGED_OUT" | head -1)"
99
+ _transient=0
100
+ case "$client_login" in
101
+ *尚未登录*|*CLIENT_LOGGED_OUT*)
102
+ for _w in 5 10; do
103
+ sleep "$_w"
104
+ _again="$(_probe_login_state)"
105
+ case "$_again" in
106
+ *尚未登录*|*CLIENT_LOGGED_OUT*) : ;;
107
+ *) client_login="$_again"; _transient=1; _doctor_out="$(ziniao-cli doctor 2>&1 || true)"; break ;;
108
+ esac
109
+ done
110
+ [ "$_transient" = "1" ] && log "⓪ 首探为「未登录」,${_w}s 后复探已恢复 ⇒ 客户端刚启动的瞬态(不是真失效)"
111
+ ;;
112
+ esac
113
+ [ -n "$client_login" ] || client_login="(未取到)"
114
+ # 官方文档未见有效期条款:api-core.md 只规定"随请求带凭据 + applyAuth",没有任何"到期/续期"字样
115
+ # ⇒ 30 天窗口只能靠观测(本探针),不能靠推断。
116
+ # 终端绑定:客户端 6.28+ 才支持该项检查(旧客户端 doctor 会写"版本不支持",不是故障)
117
+ if printf '%s' "$_doctor_out" | grep -q "终端已绑定"; then terminal_bind="已绑定"
118
+ elif printf '%s' "$_doctor_out" | grep -q "不支持终端绑定检查"; then terminal_bind="客户端过旧(不支持该检查)"
119
+ elif printf '%s' "$_doctor_out" | grep -q "未绑定"; then terminal_bind="**未绑定**"
120
+ else terminal_bind="未知"; fi
121
+ log "⓪ 客户端登录态:${client_login}|终端绑定:${terminal_bind}"
122
+ log " 口径:界面上的「30 天」是**记住密码**(客户端控件缓存,不是会话有效期);"
123
+ log " 会话真掉了也不用人点——③ 会用 API Key 走 zclaw login 程序化恢复(客户端 6.28+ 起可用)"
124
+
125
+ # ── ① WebDriver IPC(免凭据)──
126
+ wd_out="$("$PY" - "$REPO" <<'PY' 2>&1
127
+ import importlib.util, pathlib, sys
128
+ repo = sys.argv[1]
129
+ spec = importlib.util.spec_from_file_location("orch", pathlib.Path(repo) / "scripts" / "orchestrator.py")
130
+ m = importlib.util.module_from_spec(spec); sys.modules["orch"] = m; spec.loader.exec_module(m)
131
+ cfg = m.load_config()
132
+ try:
133
+ items = m.wd_running_info(cfg)
134
+ print(f"OK ipc windows={len(items)} {m.window_inventory(cfg)}")
135
+ except Exception as exc:
136
+ print(f"IPC_FAIL {type(exc).__name__}: {exc}")
137
+ PY
138
+ )"
139
+ case "$wd_out" in
140
+ OK\ ipc*) log "① WebDriver IPC 正常:${wd_out#OK ipc }" ;;
141
+ *) log "① WebDriver IPC **不可用**:${wd_out}(常驻模式应先启动客户端;本轮先尝试自愈)" ;;
142
+ esac
143
+
144
+ # ── ② 带凭据的授权链路(applyAuth + getBrowserList)──
145
+ cred_out="$("$PY" - "$REPO" <<'PY' 2>&1
146
+ import importlib.util, pathlib, sys
147
+ repo = sys.argv[1]
148
+ spec = importlib.util.spec_from_file_location("orch", pathlib.Path(repo) / "scripts" / "orchestrator.py")
149
+ m = importlib.util.module_from_spec(spec); sys.modules["orch"] = m; spec.loader.exec_module(m)
150
+ cfg = m.load_config()
151
+ creds = m._load_wd_creds()
152
+ if creds is None:
153
+ print("SKIP 未配置 ziniao-webdriver.env(ZINIAO_WD_COMPANY/USERNAME/PASSWORD)")
154
+ raise SystemExit(0)
155
+ try:
156
+ m.wd_apply_auth(cfg, creds) # 授权(幂等:等价于"续期/确认")
157
+ stores = m.wd_list_stores(cfg, creds)
158
+ print(f"OK auth stores={len(stores)}")
159
+ except Exception as exc:
160
+ print(f"AUTH_FAIL {type(exc).__name__}: {exc}")
161
+ PY
162
+ )"
163
+ case "$cred_out" in
164
+ OK\ auth*) log "② 授权链路正常:${cred_out#OK auth }" ;;
165
+ SKIP*) log "② 跳过:${cred_out#SKIP }"; exit 3 ;;
166
+ *) log "② 授权链路**失败**:$cred_out" ;;
167
+ esac
168
+ case "$wd_out$cred_out" in
169
+ *OK\ ipc*OK\ auth*) log "✅ 紫鸟凭证/授权心跳正常(无需人工干预)"; exit 0 ;;
170
+ esac
171
+
172
+ # ── ③ 自愈:可用 API Key 程序化登录(ziniao-cli 1.1.3+ 的 zclaw login,无需人工点击)──
173
+ # ⚠️ 供应商对该接口**限流**:实测连续调用会返回
174
+ # `{"ok":false,"error":{"type":"auth","message":"apiKey 登录验证次数过多,请1分钟后再试"}}`(exit 3)。
175
+ # 这类失败**不代表 Key 失效**,但会被误读成"自愈失败"⇒ 必须两件事:
176
+ # ① 同一个机器 N 秒内只试一次(默认 600s,可用 ZINIAO_LOGIN_MIN_INTERVAL_S 调);
177
+ # ② 命中限流时单独归类,不写成"API Key 无效/终端未绑定"。
178
+ LOGIN_STAMP="$DATA/.ziniao-auth-probe.login-attempt"
179
+ LOGIN_MIN_S="${ZINIAO_LOGIN_MIN_INTERVAL_S:-600}"
180
+ login_limited=0
181
+ if ziniao-cli zclaw login --help >/dev/null 2>&1; then
182
+ _since=999999
183
+ if [ -f "$LOGIN_STAMP" ]; then
184
+ _since="$(find "$LOGIN_STAMP" -mmin -$(( (LOGIN_MIN_S + 59) / 60 )) 2>/dev/null | wc -l | tr -d ' ')"
185
+ _since=$([ "$_since" = "1" ] && echo 0 || echo 999999)
186
+ fi
187
+ if [ "$_since" = "0" ]; then
188
+ log "③ 跳过程序化登录:${LOGIN_MIN_S}s 内已试过(避免触发供应商限流);需强制:ZINIAO_LOGIN_MIN_INTERVAL_S=0"
189
+ login_limited=2 # 2 = 本轮未判定(既不是成功也不是失败)
190
+ else
191
+ log "③ 尝试程序化登录(ziniao-cli zclaw login,使用本地 API Key)…"
192
+ mkdir -p "$DATA"; : > "$LOGIN_STAMP"
193
+ login_out="$(ziniao-cli zclaw login 2>&1)"; login_rc=$?
194
+ if [ "$login_rc" = "0" ]; then
195
+ log "③ 程序化登录返回成功 ⇒ 复探一次"
196
+ retry="$("$PY" - "$REPO" <<'PY' 2>&1
197
+ import importlib.util, pathlib, sys
198
+ repo = sys.argv[1]
199
+ spec = importlib.util.spec_from_file_location("orch", pathlib.Path(repo) / "scripts" / "orchestrator.py")
200
+ m = importlib.util.module_from_spec(spec); sys.modules["orch"] = m; spec.loader.exec_module(m)
201
+ cfg = m.load_config(); creds = m._load_wd_creds()
202
+ if creds is None:
203
+ print("SKIP"); raise SystemExit(0)
204
+ try:
205
+ m.wd_apply_auth(cfg, creds); stores = m.wd_list_stores(cfg, creds)
206
+ print(f"OK auth stores={len(stores)}")
207
+ except Exception as exc:
208
+ print(f"AUTH_FAIL {type(exc).__name__}: {exc}")
209
+ PY
210
+ )"
211
+ case "$retry" in
212
+ OK\ auth*) log "✅ 自愈成功(zclaw login)⇒ 无需人工"; exit 0 ;;
213
+ *) log "✗ 自愈后仍失败:$retry" ;;
214
+ esac
215
+ else
216
+ case "$login_out" in
217
+ *次数过多*|*请1分钟后再试*) # ⚠️ 模式里不能带空格(bash 会把空格当分隔符)
218
+ login_limited=1
219
+ log "⚠️ 程序化登录被**限流**(${login_out##*message\":\"})⇒ 这不是 Key 失效;"
220
+ log " ${LOGIN_MIN_S}s 内本机不再重试。要立刻重判:ZINIAO_LOGIN_MIN_INTERVAL_S=0 bash scripts/ziniao-auth-probe.sh --force" ;;
221
+ *)
222
+ log "✗ 程序化登录失败(API Key 无效/终端未绑定?):$(printf '%s' "$login_out" | tr -d '\n' | head -c 200)" ;;
223
+ esac
224
+ fi
225
+ fi
226
+ else
227
+ log "③ 本机 ziniao-cli 无 zclaw login(<1.1.3)⇒ 无法程序化登录"
228
+ fi
229
+
230
+ # ── ④ 需要人工:响亮输出 + 台账 + 遥测(提前叫人,而不是等下一票跑失败)──
231
+ _gui_mode=0
232
+ case "$client_mode" in *GUI*) _gui_mode=1 ;; esac
233
+ {
234
+ if [ "$_gui_mode" = "1" ] && case "$wd_out" in *FAIL*) true ;; *) false ;; esac; then
235
+ echo "❌ 紫鸟**当前是 GUI 模式**(人工打开过客户端)⇒ 常驻 WebDriver 模式被顶掉,IPC 不存在"
236
+ echo " 这不是授权/登录过期:同一客户端实例不能同时是 GUI 与 WebDriver。"
237
+ echo " 处置:关掉这个 GUI 实例,再按部署约定重启为常驻模式:"
238
+ echo " rm -f ~/.dsh/argent/PAUSE_ZINIAO_KEEPALIVE && bash scripts/ziniao-keepalive.sh run"
239
+ else
240
+ echo "❌ 紫鸟**授权链路不可用且自愈失败** ⇒ 需要人工处置(客户端登录窗口的提前预警)"
241
+ fi
242
+ echo " 分层结论:客户端启动模式=${client_mode}"
243
+ echo " 客户端登录态=${client_login}(GUI/Bridge 会话)"
244
+ echo " WebDriver IPC=${wd_out}(免凭据)"
245
+ echo " 授权链路=${cred_out}(applyAuth/getBrowserList,随请求带凭据)"
246
+ case "$client_mode" in
247
+ GUI*) echo " ⇒ 判定:**启动模式不对**(GUI 实例占位,WebDriver IPC 不会起来)⇒ 先按上面第一行处置" ;;
248
+ *) case "$client_login" in
249
+ *尚未登录*|*CLIENT_LOGGED_OUT*) echo " ⇒ 判定:**客户端登录态失效**(这一层需要人点一次;WebDriver 自动化本身未必受影响)" ;;
250
+ *) echo " ⇒ 判定:客户端登录态看起来正常 ⇒ 问题在 WebDriver 侧(看上面 IPC/授权两行)" ;;
251
+ esac ;;
252
+ esac
253
+ echo " 处置顺序:① 打开紫鸟客户端确认登录态;② 若被登出,先试 ziniao-cli zclaw login;"
254
+ echo " ③ 仍不行再人工登录一次(密码/短信验证无法自动化)"
255
+ echo " 影响:常驻 WebDriver 模式下,授权失效会让所有票在开跑前失败(不是票的问题)"
256
+ if [ "$login_limited" = "1" ]; then
257
+ echo " ⚠️ 注意:本次「程序化登录」被供应商限流,**未真正判定**登录态;1 分钟后可复跑确认"
258
+ elif [ "$login_limited" = "2" ]; then
259
+ echo " ⚠️ 注意:本次跳过了程序化登录(${LOGIN_MIN_S}s 内已试过)⇒ 登录态结论为**未判定**"
260
+ fi
261
+ } >&2
262
+ "$PY" "$REPO/scripts/flow_ledger.py" --case-id "auth__ziniao" --event blocked \
263
+ --by ziniao-auth-probe --detail "紫鸟授权链路不可用且 zclaw login 自愈失败:IPC=${wd_out:0:60} AUTH=${cred_out:0:60}" \
264
+ >/dev/null 2>&1 || true
265
+ "$PY" "$REPO/scripts/telemetry.py" --emit --kind engine --flow full --error-class needs_human \
266
+ --detail "紫鸟授权链路不可用(需人工;已尝试 zclaw login)" >/dev/null 2>&1 || true
267
+ "$PY" "$REPO/scripts/telemetry.py" --flush >/dev/null 2>&1 || true
268
+ exit 2