@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24
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/README.md +102 -194
- package/README.zh-CN.md +115 -210
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-bootstrap.js +466 -35
- package/bin/gotry-inner.js +255 -36
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +2 -2
- package/client/client.js +174 -0
- package/cordis.gotry-patch.yml +70 -93
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +63 -148
- package/dist/capabilities/anything.js +89 -75
- package/dist/capabilities/artifacts.js +123 -104
- package/dist/capabilities/channel-health.js +65 -45
- package/dist/capabilities/channel-registry.js +108 -87
- package/dist/capabilities/doctor.js +357 -235
- package/dist/capabilities/effect.js +223 -309
- package/dist/capabilities/fact-log.js +28 -14
- package/dist/capabilities/flyai.js +245 -156
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +144 -148
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/model-override.js +30 -10
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +50 -32
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
- package/dist/capabilities/session/adapters/dida-portal.js +206 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +169 -112
- package/dist/capabilities/session/benchmark.js +85 -149
- package/dist/capabilities/session/extension-bridge.js +320 -259
- package/dist/capabilities/session/extension-channel.js +57 -76
- package/dist/capabilities/session/extension-distribution.js +115 -119
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +55 -33
- package/dist/capabilities/session/health-watch.js +146 -154
- package/dist/capabilities/session/read-guard.js +55 -47
- package/dist/capabilities/session/static-flight-golden.js +49 -36
- package/dist/capabilities/session/transport.js +54 -61
- package/dist/capabilities/session/wizard.js +63 -93
- package/dist/capabilities/session-consent.js +143 -47
- package/dist/capabilities/session-login.js +95 -126
- package/dist/capabilities/session-search.js +391 -253
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +87 -73
- package/dist/capabilities/weather.js +163 -235
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
- package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
- package/dist/scripts/anything-tests.js +58 -62
- package/dist/scripts/artifact-client-contract-tests.js +69 -0
- package/dist/scripts/artifacts-capability-tests.js +235 -0
- package/dist/scripts/async-collect.js +26 -15
- package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
- package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
- package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
- package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
- package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
- package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +61 -123
- package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
- package/dist/scripts/bootstrap-tests.js +1383 -128
- package/dist/scripts/build-changelog.js +89 -130
- package/dist/scripts/build-metrics-report.js +135 -143
- package/dist/scripts/changelog-tests.js +35 -82
- package/dist/scripts/channel-probe-tests.js +44 -116
- package/dist/scripts/channel-probe.js +84 -167
- package/dist/scripts/channel-registry-tests.js +97 -185
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +428 -120
- package/dist/scripts/dsh-artifact-e2e.js +150 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
- package/dist/scripts/dsh-runtime-closure.js +75 -63
- package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +165 -552
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +69 -242
- package/dist/scripts/evaluation-contract-tests.js +193 -499
- package/dist/scripts/extension-distribution-cli.js +19 -15
- package/dist/scripts/extension-distribution-tests.js +98 -211
- package/dist/scripts/extension-tests.js +366 -389
- package/dist/scripts/fact-gate-tests.js +725 -263
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-tests.js +325 -68
- package/dist/scripts/g5-guard-tests.js +131 -0
- package/dist/scripts/g5-guard.js +189 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +71 -90
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +41 -115
- package/dist/scripts/health-watch-cli.js +42 -28
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +16 -9
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +547 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +325 -340
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +474 -222
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +62 -199
- package/dist/scripts/model-override-e2e.js +66 -105
- package/dist/scripts/nightly-evidence-tests.js +64 -89
- package/dist/scripts/nightly-evidence.js +108 -111
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/onboarding-tests.js +63 -88
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +37 -12
- package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +126 -227
- package/dist/scripts/price-drift-watch.js +209 -253
- package/dist/scripts/probe-poi-tests.js +52 -45
- package/dist/scripts/product-metrics.js +187 -273
- package/dist/scripts/publish-preverify.js +47 -41
- package/dist/scripts/realtime-pricing-tests.js +37 -76
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +16 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +16 -12
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +66 -147
- package/dist/scripts/session-dida-live-e2e.js +151 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-login.js +14 -13
- package/dist/scripts/session-tests.js +545 -583
- package/dist/scripts/sf-live-benchmark.js +92 -110
- package/dist/scripts/sf-live-cli-tests.js +2 -9
- package/dist/scripts/sf-soft-score-tests.js +19 -60
- package/dist/scripts/sf-summary-tests.js +149 -0
- package/dist/scripts/sf-summary.js +466 -84
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +11 -10
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +12 -20
- package/dist/scripts/smoke.js +541 -422
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +549 -212
- package/dist/scripts/static-golden-tests.js +46 -180
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +46 -93
- package/dist/scripts/turn-handoff-collect.js +71 -99
- package/dist/scripts/turn-policy-tests.js +28 -29
- package/dist/scripts/typed-contract-canary.js +77 -162
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +23 -19
- package/dist/scripts/visa-policy-tests.js +31 -44
- package/dist/scripts/weather-tests.js +121 -543
- package/dist/scripts/wish-channel-gate-tests.js +29 -86
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +18 -12
- package/dist/src/artifact-gate.js +652 -221
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +55 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +194 -0
- package/dist/src/benchmark-agent-conformance.js +296 -262
- package/dist/src/benchmark-environment-bridge.js +298 -413
- package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
- package/dist/src/benchmark-tool-isolation.js +94 -67
- package/dist/src/bookable-facts.js +299 -174
- package/dist/src/booking-saga.js +64 -120
- package/dist/src/booking-surface/availability-policy.js +443 -576
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/contracts.js +24 -80
- package/dist/src/booking-surface/dsh-planner.js +510 -281
- package/dist/src/booking-surface/dsh-plugin.js +17 -1
- package/dist/src/booking-surface/error-codes.js +82 -10
- package/dist/src/booking-surface/index.js +0 -3
- package/dist/src/booking-surface/profile.js +10 -54
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +765 -891
- package/dist/src/booking-surface/server.js +200 -251
- package/dist/src/booking-surface/startup.js +58 -54
- package/dist/src/booking-surface/validation.js +150 -218
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +82 -120
- package/dist/src/engine.js +253 -239
- package/dist/src/evaluation-cadence.js +72 -209
- package/dist/src/evaluation-contracts.js +220 -843
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/gotry-backend.js +70 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +18 -7
- package/dist/src/index.js +1425 -1629
- package/dist/src/journey.js +132 -81
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +257 -166
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/realtime-pricing.js +63 -72
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +310 -471
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +121 -127
- package/dist/src/turn-policy.js +61 -89
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +504 -381
- package/dist/src/wish-pool.js +39 -14
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +273 -7
- package/extension/README.md +4 -3
- package/extension/background.js +125 -14
- package/extension/content-bridge.js +23 -1
- package/extension/content-main.js +15 -4
- package/extension/manifest.json +16 -8
- package/package.json +251 -220
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +1 -1
- package/ts/capabilities/artifacts.ts +46 -9
- package/ts/capabilities/flyai.ts +156 -37
- package/ts/capabilities/hbcli.ts +1 -1
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
- package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
- package/ts/capabilities/session/extension-bridge.ts +226 -101
- package/ts/capabilities/session/extension-channel.ts +22 -9
- package/ts/capabilities/session/wizard.ts +2 -2
- package/ts/capabilities/session-consent.ts +98 -14
- package/ts/capabilities/session-login.ts +1 -0
- package/ts/capabilities/session-search.ts +315 -31
- package/ts/dsh-runtime/vendor/README.md +8 -4
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
- package/ts/package.json +236 -16
- package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
- package/ts/scripts/state-cli.ts +418 -51
- package/ts/src/artifact-gate.ts +561 -15
- package/ts/src/benchmark-environment-bridge.ts +37 -20
- package/ts/src/bookable-facts.ts +183 -7
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/dsh-planner.ts +240 -32
- package/ts/src/booking-surface/dsh-plugin.js +17 -1
- package/ts/src/booking-surface/error-codes.ts +75 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/server.ts +84 -51
- package/ts/src/booking-surface/validation.ts +2 -0
- package/ts/src/contracts.ts +12 -2
- package/ts/src/dsh-llm.ts +4 -8
- package/ts/src/index.ts +609 -90
- package/ts/src/loop.ts +116 -7
- package/ts/src/memory-capture.ts +227 -3
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/state-ledger.ts +47 -29
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +217 -13
- package/ts/src/unified.ts +316 -54
|
@@ -1,379 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 可选依赖体检(doctor)——工具层与 CLI(npx @danceiny/gotry doctor)共用的状态面。
|
|
3
|
+
*
|
|
4
|
+
* 背景(2026-09-02 迪拜 session 轨迹复盘):8953be5 把 hbcli/agent-reach 装配从
|
|
5
|
+
* setup 挪出后,工具层仍按「包内 .venv 已装配」运行 → gotry_agent_reach /
|
|
6
|
+
* gotry_web_search(web.read)全量 not-installed,且报错指引指向已失效的文档。
|
|
7
|
+
* founder 拍板:可选依赖不撒手——doctor 统一显示状态、给精确补装指引。
|
|
8
|
+
*
|
|
9
|
+
* 契约(与 capabilities/* 同构):
|
|
10
|
+
* - `runDoctorChecks` 永远只读;显式 repair 另经会话 scope 审批后复用 bootstrap 幂等安装器;
|
|
11
|
+
* - 永不抛错:单项检查失败降级为 status='degraded',不拖垮整体报告;
|
|
12
|
+
* - 可注入:repoRoot/homeDir/env 可替换(CI/离线确定性测试);
|
|
13
|
+
* - LLM key 永不体检——那是 dsh 宿主的管辖面(founder 2026-09-02 明确)。
|
|
14
|
+
*
|
|
15
|
+
* @module capabilities/doctor
|
|
16
|
+
*/
|
|
1
17
|
import { spawn } from 'node:child_process';
|
|
2
18
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
19
|
import { createRequire } from 'node:module';
|
|
4
20
|
import { homedir } from 'node:os';
|
|
5
21
|
import { join, resolve } from 'node:path';
|
|
6
22
|
import { readLatestChannelEvents } from './channel-health.js';
|
|
23
|
+
/** hbcli 已知安装位(与 capabilities/hbcli.ts hbcliBinCandidates 同清单) */
|
|
7
24
|
function hbcliCandidates(homeDir) {
|
|
8
|
-
return [
|
|
9
|
-
'hbcli',
|
|
10
|
-
join(homeDir, '.local/bin/hbcli'),
|
|
11
|
-
join(homeDir, '.staicli/current/hbcli')
|
|
12
|
-
];
|
|
25
|
+
return ['hbcli', join(homeDir, '.local/bin/hbcli'), join(homeDir, '.staicli/current/hbcli')];
|
|
13
26
|
}
|
|
27
|
+
/** 静默探测命令可执行(带超时;探测失败不抛错) */
|
|
14
28
|
function probe(bin, args, timeoutMs = 8_000) {
|
|
15
|
-
return new Promise((resolveProbe)=>{
|
|
16
|
-
const child = spawn(bin, args, {
|
|
17
|
-
stdio: 'ignore',
|
|
18
|
-
env: process.env
|
|
19
|
-
});
|
|
29
|
+
return new Promise((resolveProbe) => {
|
|
30
|
+
const child = spawn(bin, args, { stdio: 'ignore', env: process.env });
|
|
20
31
|
let done = false;
|
|
21
|
-
const timer = setTimeout(()=>{
|
|
32
|
+
const timer = setTimeout(() => {
|
|
22
33
|
if (!done) {
|
|
23
34
|
done = true;
|
|
24
35
|
try {
|
|
25
36
|
child.kill('SIGKILL');
|
|
26
|
-
}
|
|
37
|
+
}
|
|
38
|
+
catch { /* ignore */ }
|
|
27
39
|
resolveProbe(false);
|
|
28
40
|
}
|
|
29
41
|
}, timeoutMs);
|
|
30
|
-
child.on('error', ()=>{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
clearTimeout(timer);
|
|
41
|
-
resolveProbe(code === 0);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
42
|
+
child.on('error', () => { if (!done) {
|
|
43
|
+
done = true;
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
resolveProbe(false);
|
|
46
|
+
} });
|
|
47
|
+
child.on('exit', (code) => { if (!done) {
|
|
48
|
+
done = true;
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
resolveProbe(code === 0);
|
|
51
|
+
} });
|
|
44
52
|
});
|
|
45
53
|
}
|
|
54
|
+
/** 探测命令 stdout(--version),失败返回 null */
|
|
46
55
|
function probeStdout(bin, args, timeoutMs = 8_000) {
|
|
47
|
-
return new Promise((resolveProbe)=>{
|
|
48
|
-
const child = spawn(bin, args, {
|
|
49
|
-
stdio: [
|
|
50
|
-
'ignore',
|
|
51
|
-
'pipe',
|
|
52
|
-
'ignore'
|
|
53
|
-
],
|
|
54
|
-
env: process.env
|
|
55
|
-
});
|
|
56
|
+
return new Promise((resolveProbe) => {
|
|
57
|
+
const child = spawn(bin, args, { stdio: ['ignore', 'pipe', 'ignore'], env: process.env });
|
|
56
58
|
let out = '';
|
|
57
59
|
let done = false;
|
|
58
|
-
const timer = setTimeout(()=>{
|
|
60
|
+
const timer = setTimeout(() => {
|
|
59
61
|
if (!done) {
|
|
60
62
|
done = true;
|
|
61
63
|
try {
|
|
62
64
|
child.kill('SIGKILL');
|
|
63
|
-
}
|
|
65
|
+
}
|
|
66
|
+
catch { /* ignore */ }
|
|
64
67
|
resolveProbe(null);
|
|
65
68
|
}
|
|
66
69
|
}, timeoutMs);
|
|
67
|
-
child.on('error', ()=>{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!done) {
|
|
79
|
-
done = true;
|
|
80
|
-
clearTimeout(timer);
|
|
81
|
-
resolveProbe(code === 0 ? out.trim() : null);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
70
|
+
child.on('error', () => { if (!done) {
|
|
71
|
+
done = true;
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
resolveProbe(null);
|
|
74
|
+
} });
|
|
75
|
+
child.stdout?.on('data', (d) => { out += d.toString(); });
|
|
76
|
+
child.on('exit', (code) => { if (!done) {
|
|
77
|
+
done = true;
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
resolveProbe(code === 0 ? out.trim() : null);
|
|
80
|
+
} });
|
|
84
81
|
});
|
|
85
82
|
}
|
|
83
|
+
/** 解析语义化版本号 "1.2.3" → [1,2,3];失败 null */
|
|
86
84
|
function parseVersion(v) {
|
|
87
85
|
const m = String(v || '').match(/(\d+)\.(\d+)\.(\d+)/);
|
|
88
|
-
return m ? [
|
|
89
|
-
Number(m[1]),
|
|
90
|
-
Number(m[2]),
|
|
91
|
-
Number(m[3])
|
|
92
|
-
] : null;
|
|
86
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
93
87
|
}
|
|
94
88
|
function versionAtLeast(v, min) {
|
|
95
89
|
const a = v;
|
|
96
90
|
const b = parseVersion(min);
|
|
97
|
-
if (!a || !b)
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
if (!a || !b)
|
|
92
|
+
return false;
|
|
93
|
+
for (let i = 0; i < 3; i++) {
|
|
94
|
+
if (a[i] !== b[i])
|
|
95
|
+
return a[i] > b[i];
|
|
100
96
|
}
|
|
101
97
|
return true;
|
|
102
98
|
}
|
|
99
|
+
/** Node ≥ 22.15(与 bin/gotry-inner.js supportsNodeVersion 同口径) */
|
|
103
100
|
export function nodeOk(version) {
|
|
104
101
|
const [maj = '0', min = '0'] = version.split('.');
|
|
105
|
-
return Number(maj) > 22 || Number(maj) === 22 && Number(min) >= 15;
|
|
102
|
+
return Number(maj) > 22 || (Number(maj) === 22 && Number(min) >= 15);
|
|
106
103
|
}
|
|
104
|
+
/** hbcli 最低版本(可用 GOTRY_MIN_HBCLI_VERSION 覆盖)。0.0.3 修了 portal-ticket
|
|
105
|
+
* fallback bug(issue #142)——低于该版本的 hbcli 会把 literal "stored-ticket"
|
|
106
|
+
* 当 bearer 发到 hotel-be,让 trade.* / search/checkAvail 全部 401。 */
|
|
107
107
|
const MIN_HBCLI_VERSION = process.env.GOTRY_MIN_HBCLI_VERSION ?? '0.0.3';
|
|
108
|
+
/** 单项检查:全部只读、永不抛错;env/homeDir/repoRoot 可注入(测试确定性) */
|
|
108
109
|
export async function runDoctorChecks(opts = {}) {
|
|
109
110
|
const repoRoot = opts.repoRoot ? resolve(opts.repoRoot) : resolve(import.meta.dirname, '..', '..');
|
|
110
111
|
const home = opts.homeDir ?? homedir();
|
|
111
112
|
const env = opts.env ?? process.env;
|
|
112
113
|
const items = [];
|
|
114
|
+
// 0. Node 运行时
|
|
113
115
|
const nodeVersion = process.versions.node;
|
|
114
|
-
items.push(nodeOk(nodeVersion)
|
|
115
|
-
id: 'node',
|
|
116
|
-
label: 'Node 运行时',
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
id: 'node',
|
|
121
|
-
label: 'Node 运行时',
|
|
122
|
-
status: 'missing',
|
|
123
|
-
detail: `Node ${nodeVersion} 过旧(gotry 需 ≥22.15)`,
|
|
124
|
-
fix: '升级 Node.js 至 22.15+(https://nodejs.org)'
|
|
125
|
-
});
|
|
116
|
+
items.push(nodeOk(nodeVersion)
|
|
117
|
+
? { id: 'node', label: 'Node 运行时', status: 'ok', detail: `Node ${nodeVersion}(≥22.15)` }
|
|
118
|
+
: { id: 'node', label: 'Node 运行时', status: 'missing', detail: `Node ${nodeVersion} 过旧(gotry 需 ≥22.15)`, fix: '升级 Node.js 至 22.15+(https://nodejs.org)' });
|
|
119
|
+
// 1. GoTry Session Bridge 扩展(账号会话通道的传输层;装在哪由 bootstrap setup 管理)
|
|
120
|
+
// D-24 自适应(#117):离线只能探测本地 unpacked 通道;商店版无本地文件面——
|
|
121
|
+
// missing detail 明示「商店版用户可忽略本项」,不再把商店版用户误导成本地通道缺失
|
|
126
122
|
const extManifest = join(home, '.gotry', 'extension', 'manifest.json');
|
|
127
|
-
items.push(existsSync(extManifest)
|
|
128
|
-
id: 'extension',
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
status: 'missing',
|
|
136
|
-
detail: '本地通道未落位(~/.gotry/extension/manifest.json 不存在)。若你已从 Chrome 商店安装(自动更新,商店版 ID oeajpicc…),本项可忽略——会话检索可用性以运行时为准(gotry_session_search 的 verdict)。若未安装:应用商店一键装即可',
|
|
137
|
-
fix: '在 Chrome 应用商店一键安装(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd'
|
|
138
|
-
});
|
|
123
|
+
items.push(existsSync(extManifest)
|
|
124
|
+
? { id: 'extension', label: 'GoTry Session Bridge 扩展', status: 'ok', detail: `已就位(本地通道:${extManifest})` }
|
|
125
|
+
: {
|
|
126
|
+
id: 'extension', label: 'GoTry Session Bridge 扩展', status: 'missing',
|
|
127
|
+
detail: '本地通道未落位(~/.gotry/extension/manifest.json 不存在)。若你已从 Chrome 商店安装(自动更新,商店版 ID oeajpicc…),本项可忽略——会话检索可用性以运行时为准(gotry_session_search 的 verdict)。若未安装:应用商店一键装即可',
|
|
128
|
+
fix: '在 Chrome 应用商店一键安装(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd',
|
|
129
|
+
});
|
|
130
|
+
// 2. agent-reach(网页/社媒读取;.venv 装在包内,与 agent-reach.ts venvPython 同位)
|
|
139
131
|
const venvPython = join(repoRoot, '.venv/bin/python');
|
|
140
132
|
const reachBin = join(repoRoot, '.venv/bin/agent-reach');
|
|
141
133
|
if (existsSync(reachBin)) {
|
|
134
|
+
items.push({ id: 'agent-reach', label: 'Agent Reach(网页/社媒读取)', status: 'ok', detail: `已安装(${reachBin});渠道凭证选配体检:在对话里调 gotry_agent_reach {action:"status"}` });
|
|
135
|
+
}
|
|
136
|
+
else if (existsSync(venvPython)) {
|
|
142
137
|
items.push({
|
|
143
|
-
id: 'agent-reach',
|
|
144
|
-
label: 'Agent Reach(网页/社媒读取)',
|
|
145
|
-
status: 'ok',
|
|
146
|
-
detail: `已安装(${reachBin});渠道凭证选配体检:在对话里调 gotry_agent_reach {action:"status"}`
|
|
147
|
-
});
|
|
148
|
-
} else if (existsSync(venvPython)) {
|
|
149
|
-
items.push({
|
|
150
|
-
id: 'agent-reach',
|
|
151
|
-
label: 'Agent Reach(网页/社媒读取)',
|
|
152
|
-
status: 'degraded',
|
|
138
|
+
id: 'agent-reach', label: 'Agent Reach(网页/社媒读取)', status: 'degraded',
|
|
153
139
|
detail: '.venv 存在但缺 agent-reach 包——gotry_agent_reach / gotry_web_search 读页会失败',
|
|
154
|
-
fix: `npx gotry doctor --fix(或在包根执行: ${venvPython} -m pip install git+https://github.com/Panniantong/Agent-Reach.git)
|
|
140
|
+
fix: `npx @danceiny/gotry doctor --fix(或在包根执行: ${venvPython} -m pip install git+https://github.com/Panniantong/Agent-Reach.git)`,
|
|
155
141
|
});
|
|
156
|
-
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
157
144
|
items.push({
|
|
158
|
-
id: 'agent-reach',
|
|
159
|
-
label: 'Agent Reach(网页/社媒读取)',
|
|
160
|
-
status: 'missing',
|
|
145
|
+
id: 'agent-reach', label: 'Agent Reach(网页/社媒读取)', status: 'missing',
|
|
161
146
|
detail: '未安装——gotry_agent_reach / gotry_web_search(读网页)/ gotry_video_subtitle / gotry_github_search 全部不可用',
|
|
162
|
-
fix: 'npx gotry doctor --fix'
|
|
147
|
+
fix: 'npx @danceiny/gotry doctor --fix',
|
|
163
148
|
});
|
|
164
149
|
}
|
|
150
|
+
// 3. hbcli(酒店实时源;静态包自动降级,缺了不致命)
|
|
151
|
+
// 裸名 'hbcli' 靠 PATH 解析(existsSync 对裸名是 cwd 相对,无意义)——先探测再落位
|
|
152
|
+
// 版本 < MIN_HBCLI_VERSION 视为 missing(issue #142:portal-ticket fallback 401)
|
|
165
153
|
let hbPresent = '';
|
|
166
|
-
for (const p of hbcliCandidates(home)){
|
|
154
|
+
for (const p of hbcliCandidates(home)) {
|
|
167
155
|
if (p !== 'hbcli' && existsSync(p)) {
|
|
168
156
|
hbPresent = p;
|
|
169
157
|
break;
|
|
170
158
|
}
|
|
171
|
-
if (p === 'hbcli' && await probe(p, [
|
|
172
|
-
'version'
|
|
173
|
-
])) {
|
|
159
|
+
if (p === 'hbcli' && await probe(p, ['version'])) {
|
|
174
160
|
hbPresent = 'hbcli(PATH)';
|
|
175
161
|
break;
|
|
176
162
|
}
|
|
177
163
|
}
|
|
178
164
|
if (hbPresent) {
|
|
179
165
|
const hbCmd = hbPresent === 'hbcli(PATH)' ? 'hbcli' : hbPresent;
|
|
180
|
-
const v = parseVersion(await probeStdout(hbCmd, [
|
|
181
|
-
'--version'
|
|
182
|
-
]));
|
|
166
|
+
const v = parseVersion(await probeStdout(hbCmd, ['--version']));
|
|
183
167
|
if (v && !versionAtLeast(v, MIN_HBCLI_VERSION)) {
|
|
184
168
|
items.push({
|
|
185
|
-
id: 'hbcli',
|
|
186
|
-
label: 'hbcli(酒店实时源)',
|
|
187
|
-
status: 'missing',
|
|
169
|
+
id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'missing',
|
|
188
170
|
detail: `版本过旧(v${v.join('.')} < v${MIN_HBCLI_VERSION})——trade.* / search/checkAvail 会 401(issue #142)`,
|
|
189
|
-
fix: 'npm install -g staicli --registry=https://registry.npmjs.org/'
|
|
190
|
-
});
|
|
191
|
-
} else {
|
|
192
|
-
const whoami = await probe(hbCmd, [
|
|
193
|
-
'auth',
|
|
194
|
-
'whoami'
|
|
195
|
-
]);
|
|
196
|
-
items.push(whoami ? {
|
|
197
|
-
id: 'hbcli',
|
|
198
|
-
label: 'hbcli(酒店实时源)',
|
|
199
|
-
status: 'ok',
|
|
200
|
-
detail: `已安装且凭证有效(${hbPresent}, v${v ? v.join('.') : '?'})`
|
|
201
|
-
} : {
|
|
202
|
-
id: 'hbcli',
|
|
203
|
-
label: 'hbcli(酒店实时源)',
|
|
204
|
-
status: 'degraded',
|
|
205
|
-
detail: '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)',
|
|
206
|
-
fix: 'hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo(快速试用沙箱;正式 key 向 HotelByte 申请)'
|
|
171
|
+
fix: 'npm install -g staicli --registry=https://registry.npmjs.org/',
|
|
207
172
|
});
|
|
208
173
|
}
|
|
209
|
-
|
|
174
|
+
else {
|
|
175
|
+
const whoami = await probe(hbCmd, ['auth', 'whoami']);
|
|
176
|
+
items.push(whoami
|
|
177
|
+
? { id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'ok', detail: `已安装且凭证有效(${hbPresent}, v${v ? v.join('.') : '?'})` }
|
|
178
|
+
: {
|
|
179
|
+
id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'degraded',
|
|
180
|
+
detail: '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)',
|
|
181
|
+
fix: 'hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo(快速试用沙箱;正式 key 向 HotelByte 申请)',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
210
186
|
items.push({
|
|
211
|
-
id: 'hbcli',
|
|
212
|
-
label: 'hbcli(酒店实时源)',
|
|
213
|
-
status: 'missing',
|
|
187
|
+
id: 'hbcli', label: 'hbcli(酒店实时源)', status: 'missing',
|
|
214
188
|
detail: '未安装——酒店检索降级静态包(公开渠道估算,非实时,仅覆盖内置场景)',
|
|
215
|
-
fix: 'npx gotry doctor --fix'
|
|
189
|
+
fix: 'npx @danceiny/gotry doctor --fix',
|
|
216
190
|
});
|
|
217
191
|
}
|
|
192
|
+
// 4. flyai(飞猪官方只读通道;匿名试用额度共享,易达限)
|
|
193
|
+
// 配额状态可见(通道健康持久面):最近一次达限时间进 detail——
|
|
194
|
+
// 「易达限却不可见」是 issue #107 的病灶之一。
|
|
218
195
|
const flyaiKey = env.FLYAI_API_KEY?.trim();
|
|
219
|
-
const flyaiQuotaNote = !flyaiKey && opts.stateRoot
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
status: 'ok',
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
fix: '到 flyai.open.fliggy.com 控制台申请正式 key,配进环境变量 FLYAI_API_KEY;无 key 期间机/火/酒检索请以 gotry_session_search(账号会话)为主'
|
|
234
|
-
});
|
|
196
|
+
const flyaiQuotaNote = !flyaiKey && opts.stateRoot
|
|
197
|
+
? await (async () => {
|
|
198
|
+
const ev = (await readLatestChannelEvents(opts.stateRoot)).get('flyai');
|
|
199
|
+
return ev?.state === 'down' ? `;最近一次试用达限: ${ev.at}(匿名共享池,正式 key 可解除)` : '';
|
|
200
|
+
})()
|
|
201
|
+
: '';
|
|
202
|
+
items.push(flyaiKey
|
|
203
|
+
? { id: 'flyai', label: 'FlyAI(飞猪官方检索)', status: 'ok', detail: 'FLYAI_API_KEY 已配(正式 key,无试用额度限制)' }
|
|
204
|
+
: {
|
|
205
|
+
id: 'flyai', label: 'FlyAI(飞猪官方检索)', status: 'degraded',
|
|
206
|
+
detail: `未配 FLYAI_API_KEY——走匿名试用额度(共享,易达限;达限报 "Trial limit reached")${flyaiQuotaNote}`,
|
|
207
|
+
fix: '到 flyai.open.fliggy.com 控制台申请正式 key,配进环境变量 FLYAI_API_KEY;无 key 期间机/火/酒检索请以 gotry_session_search(账号会话)为主',
|
|
208
|
+
});
|
|
209
|
+
// 5. dsh-better-sidebar(dsh web 侧栏工作台;产物预览面 + doctor 报告的查看面)
|
|
235
210
|
const sidebarPkg = join(home, '.dsh/profiles/web/node_modules/dsh-better-sidebar/package.json');
|
|
236
|
-
items.push(existsSync(sidebarPkg)
|
|
237
|
-
id: 'sidebar',
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
211
|
+
items.push(existsSync(sidebarPkg)
|
|
212
|
+
? { id: 'sidebar', label: 'dsh-better-sidebar(侧栏工作台)', status: 'ok', detail: '已安装——web UI 右侧工作台可预览产物与 doctor 报告(gotry-state/doctor-report.md)' }
|
|
213
|
+
: {
|
|
214
|
+
id: 'sidebar', label: 'dsh-better-sidebar(侧栏工作台)', status: 'missing',
|
|
215
|
+
detail: '未安装——dsh web 无右侧工作台,产物与 doctor 报告只能在对话里看(gotry_artifacts_list)',
|
|
216
|
+
fix: 'npx @danceiny/gotry doctor --fix',
|
|
217
|
+
});
|
|
218
|
+
// 6. dsh-calendar(patch 分发面宿主插件;D-9 拍板:默认不挂载)
|
|
219
|
+
// 未配置的日历工具是纯负资产(issue #106:会话中段才撞「未配置 username」),
|
|
220
|
+
// 工作窗口由 persona (1) 访谈覆盖;挂载与否由 **setup 状态面**决定
|
|
221
|
+
// (`~/.gotry/calendar.json`,`npx @danceiny/gotry setup calendar` on/off——founder
|
|
222
|
+
// 2026-09-03 纠偏:禁止环境变量控制产品行为,可选依赖进 setup 状态管理)。
|
|
248
223
|
const calStatePath = join(home, '.gotry', 'calendar.json');
|
|
249
|
-
const calEnabled = (()=>{
|
|
224
|
+
const calEnabled = (() => {
|
|
250
225
|
try {
|
|
251
226
|
return JSON.parse(readFileSync(calStatePath, 'utf-8'))?.enabled === true;
|
|
252
|
-
}
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
253
229
|
return false;
|
|
254
230
|
}
|
|
255
231
|
})();
|
|
256
232
|
const calProfilePatch = join(home, '.dsh/profiles/web/cordis.patch.yml');
|
|
257
|
-
const calConfigured = calEnabled && existsSync(calProfilePatch) && (()=>{
|
|
233
|
+
const calConfigured = calEnabled && existsSync(calProfilePatch) && (() => {
|
|
258
234
|
try {
|
|
259
235
|
const content = readFileSync(calProfilePatch, 'utf-8');
|
|
260
236
|
return /calendar/.test(content) && /username\s*:/.test(content);
|
|
261
|
-
}
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
262
239
|
return false;
|
|
263
240
|
}
|
|
264
241
|
})();
|
|
265
|
-
items.push(!calEnabled
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
242
|
+
items.push(!calEnabled
|
|
243
|
+
? {
|
|
244
|
+
id: 'calendar', label: 'dsh-calendar(日历工作窗口)', status: 'ok',
|
|
245
|
+
detail: '默认未挂载(D-9:未配置的日历工具不进工具箱;工作窗口由访谈覆盖,不影响任何检索)',
|
|
246
|
+
}
|
|
247
|
+
: calConfigured
|
|
248
|
+
? { id: 'calendar', label: 'dsh-calendar(日历工作窗口)', status: 'ok', detail: `已挂载且已配置(${calStatePath})` }
|
|
249
|
+
: {
|
|
250
|
+
id: 'calendar', label: 'dsh-calendar(日历工作窗口)', status: 'degraded',
|
|
251
|
+
detail: '已挂载但 calendar 未配置 username——日历工具会话中会报「未配置」',
|
|
252
|
+
fix: `npx @danceiny/gotry setup calendar --off(恢复默认不挂载),或在 ${calProfilePatch} 覆盖 calendar 行 config 填 username(指引: npx @danceiny/gotry setup calendar --status)`,
|
|
253
|
+
});
|
|
254
|
+
// 7. patch 分发面宿主插件(issue #113 L1 残量,design §3.1:初始化可见取代会话中段撞错)。
|
|
255
|
+
// 这类插件在 cordis patch 里是占位行,bin/gotry-inner.js 运行时解析——**解析失败整块
|
|
256
|
+
// 静默剔除,不挡启动**:模型只觉得「没有这个工具」,没人告诉它为什么。doctor 把两态照亮。
|
|
257
|
+
// 候选清单与 bin 解析逻辑同口径(map:tarball vendor 优先→runtime workspace→npm 提升解析;
|
|
258
|
+
// ask-user:dsh 闭包上下文解析)。map 保持随包 vendor 分发而非 npm 依赖:历史上游
|
|
259
|
+
// peerDependencies 要求 dsh-settings/dsh-tools >=0.1.2-rc.1,与锁定的 0.1.2-alpha.3 家族
|
|
260
|
+
// 在 npm 严格 peer 解析下 ERESOLVE;当前锁定 0.1.5-rc.1 家族,vendored 副本已对齐
|
|
261
|
+
// 0.1.5-rc.1,继续以 vendor 形态复用适配补丁,避免弄坏 npx 主安装路径。
|
|
282
262
|
const rootRequire = createRequire(join(repoRoot, 'package.json'));
|
|
283
263
|
const mapCandidates = [
|
|
284
264
|
join(repoRoot, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js'),
|
|
285
265
|
join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js'),
|
|
286
266
|
join(repoRoot, 'node_modules/dsh-map-tools/package.json'),
|
|
287
267
|
join(repoRoot, 'ts/node_modules/dsh-map-tools/package.json'),
|
|
288
|
-
join(home, '.dsh/profiles/web/node_modules/dsh-map-tools/package.json')
|
|
268
|
+
join(home, '.dsh/profiles/web/node_modules/dsh-map-tools/package.json'),
|
|
289
269
|
];
|
|
290
|
-
let mapHit = mapCandidates.find(
|
|
270
|
+
let mapHit = mapCandidates.find(p => existsSync(p));
|
|
271
|
+
// npm/npx 提升布局:gotry 的依赖在包目录外的同级提升位,existsSync 候选打不中——
|
|
272
|
+
// 与 inner 的 require_.resolve 同机制,从包位置向上走查解析
|
|
291
273
|
if (!mapHit) {
|
|
292
274
|
try {
|
|
293
275
|
mapHit = rootRequire.resolve('dsh-map-tools');
|
|
294
|
-
}
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
295
278
|
mapHit = undefined;
|
|
296
279
|
}
|
|
297
280
|
}
|
|
298
|
-
items.push(mapHit
|
|
299
|
-
id: 'map-tools',
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
label: 'dsh-map-tools(地图/路线/POI)',
|
|
306
|
-
status: 'missing',
|
|
307
|
-
detail: '未随包解析——启动时该 patch 条目被静默剔除,地图/路线/POI 工具不会出现在模型工具箱(缺地图不挡旅行规划,只少能力)',
|
|
308
|
-
fix: '重装 @danceiny/gotry(地图插件随包 vendor 分发,缺失多为安装不完整)'
|
|
309
|
-
});
|
|
281
|
+
items.push(mapHit
|
|
282
|
+
? { id: 'map-tools', label: 'dsh-map-tools(地图/路线/POI)', status: 'ok', detail: `已就位(${mapHit})——地图工具可用(零 key,走 OSM/OSRM)` }
|
|
283
|
+
: {
|
|
284
|
+
id: 'map-tools', label: 'dsh-map-tools(地图/路线/POI)', status: 'missing',
|
|
285
|
+
detail: '未随包解析——启动时该 patch 条目被静默剔除,地图/路线/POI 工具不会出现在模型工具箱(缺地图不挡旅行规划,只少能力)',
|
|
286
|
+
fix: '重装 @danceiny/gotry(地图插件随包 vendor 分发,缺失多为安装不完整)',
|
|
287
|
+
});
|
|
310
288
|
const askCandidates = [
|
|
311
289
|
join(repoRoot, 'ts/dsh-runtime/vendor/deepseek-ai-dsh-tool-ask-user/package.json'),
|
|
312
290
|
join(repoRoot, 'node_modules/@deepseek-ai/dsh-tool-ask-user/package.json'),
|
|
313
|
-
join(home, '.dsh/profiles/web/node_modules/@deepseek-ai/dsh-tool-ask-user/package.json')
|
|
291
|
+
join(home, '.dsh/profiles/web/node_modules/@deepseek-ai/dsh-tool-ask-user/package.json'),
|
|
314
292
|
];
|
|
315
|
-
let askHit = askCandidates.find(
|
|
293
|
+
let askHit = askCandidates.find(p => existsSync(p));
|
|
294
|
+
// 与 inner 同口径:优先从 dsh 包上下文解析(pnpm 嵌套布局只有 dsh 看得见闭包成员),
|
|
295
|
+
// 再从包根上下文(npm/npx 提升布局),最后静态候选(source vendored / profile)
|
|
316
296
|
if (!askHit) {
|
|
317
297
|
try {
|
|
318
298
|
const reqFromDsh = createRequire(rootRequire.resolve('@deepseek-ai/dsh/lib/bin.js'));
|
|
319
299
|
askHit = reqFromDsh.resolve('@deepseek-ai/dsh-tool-ask-user');
|
|
320
|
-
}
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
321
302
|
try {
|
|
322
303
|
askHit = rootRequire.resolve('@deepseek-ai/dsh-tool-ask-user');
|
|
323
|
-
}
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
324
306
|
askHit = undefined;
|
|
325
307
|
}
|
|
326
308
|
}
|
|
327
309
|
}
|
|
328
|
-
items.push(askHit
|
|
329
|
-
id: 'ask-user',
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
310
|
+
items.push(askHit
|
|
311
|
+
? { id: 'ask-user', label: 'dsh-tool-ask-user(结构化澄清卡)', status: 'ok', detail: `已就位(${askHit})——ask_user_question 澄清卡可用(web 原生卡片;headless+TTY 用 stdio 提供方)` }
|
|
312
|
+
: {
|
|
313
|
+
id: 'ask-user', label: 'dsh-tool-ask-user(结构化澄清卡)', status: 'missing',
|
|
314
|
+
detail: '未解析——启动时澄清卡注入被静默剔除,模型只能散文追问(人格契约 (5) 退化文本形态)',
|
|
315
|
+
fix: '重装 @danceiny/gotry——该依赖随 dsh 闭包自带,缺失多为安装不完整',
|
|
316
|
+
});
|
|
317
|
+
// 8. LLM key:显式让渡给 dsh 宿主(founder 2026-09-02:doctor 不管 key)
|
|
318
|
+
items.push({ id: 'llm-key', label: 'LLM key', status: 'ok', detail: '由 dsh 宿主 UI 管理——不在 doctor 体检范围(gotry 不接触、不回显凭证)' });
|
|
319
|
+
const broken = items.filter(i => i.status !== 'ok' && i.id !== 'llm-key');
|
|
320
|
+
const summary = broken.length === 0
|
|
321
|
+
? `体检通过:${items.length} 项全部就绪(可选依赖齐,LLM key 归 dsh 宿主管)。`
|
|
322
|
+
: `体检发现 ${broken.length} 项待处理:${broken.map(i => `${i.label}(${i.status === 'missing' ? '未装' : '半可用'})`).join('、')}。补装:终端跑 npx @danceiny/gotry doctor --fix,或按各项 fix 指引逐项处理。`;
|
|
323
|
+
return { ok: broken.length === 0, items, summary };
|
|
324
|
+
}
|
|
325
|
+
/** 修复指引入表:命令类才加反引号(prose 类如「到控制台申请 key」原样) */
|
|
326
|
+
const fixCell = (fix) => (!fix ? '—' : /^(npx|hbcli|curl|pip|python|\$)/.test(fix) ? `\`${fix}\`` : fix);
|
|
327
|
+
/** 稳定 scope key(item ids 排序去重逗号串,空 = '')—批准缓存 / 审计 / 测试断言 */
|
|
328
|
+
export function scopeKeyFor(ids) {
|
|
329
|
+
if (!Array.isArray(ids) || ids.length === 0)
|
|
330
|
+
return '';
|
|
331
|
+
return [...new Set(ids)].sort().join(',');
|
|
332
|
+
}
|
|
333
|
+
/** DoctorItem → bootstrap 形状:status 改名为 level(关键适配,见 BootstrapItem 注释) */
|
|
334
|
+
function toBootstrapItem(i) {
|
|
335
|
+
return { label: i.label, level: i.status, detail: i.detail, fix: i.fix };
|
|
336
|
+
}
|
|
337
|
+
const KNOWN_IDS = new Set(['node', 'extension', 'agent-reach', 'hbcli', 'flyai', 'sidebar', 'llm-key', 'calendar', 'map-tools', 'ask-user']);
|
|
338
|
+
let cachedBootstrap = null;
|
|
339
|
+
async function loadBootstrapBridge() {
|
|
340
|
+
if (cachedBootstrap)
|
|
341
|
+
return cachedBootstrap;
|
|
342
|
+
const url = new URL('../../bin/gotry-bootstrap.js', import.meta.url);
|
|
343
|
+
const mod = await import(url.href);
|
|
344
|
+
cachedBootstrap = mod;
|
|
345
|
+
return mod;
|
|
346
|
+
}
|
|
347
|
+
export function createRepairApprovalGate(opts) {
|
|
348
|
+
const store = opts.store ?? new WeakMap();
|
|
349
|
+
const getState = (agent) => {
|
|
350
|
+
if (!agent)
|
|
351
|
+
return { granted: new Set(), denied: new Map() };
|
|
352
|
+
let s = store.get(agent);
|
|
353
|
+
if (!s) {
|
|
354
|
+
s = { granted: new Set(), denied: new Map() };
|
|
355
|
+
store.set(agent, s);
|
|
356
|
+
}
|
|
357
|
+
return s;
|
|
358
|
+
};
|
|
359
|
+
const has = (agent, scopeKey) => {
|
|
360
|
+
if (!scopeKey)
|
|
361
|
+
return 'not-needed';
|
|
362
|
+
const s = getState(agent);
|
|
363
|
+
if (s.granted.has(scopeKey))
|
|
364
|
+
return 'granted';
|
|
365
|
+
return s.denied.get(scopeKey) ?? 'not-needed'; // 保留具体 rejected/cancelled
|
|
366
|
+
};
|
|
367
|
+
const request = async (agent, scopeKey, reason, requestMeta = {}) => {
|
|
368
|
+
const cached = has(agent, scopeKey);
|
|
369
|
+
if (cached !== 'not-needed')
|
|
370
|
+
return cached;
|
|
371
|
+
const seam = opts.approval?.();
|
|
372
|
+
if (!seam || typeof seam.request !== 'function' || !agent)
|
|
373
|
+
return 'unavailable';
|
|
374
|
+
let raw;
|
|
375
|
+
try {
|
|
376
|
+
raw = await seam.request({ agent, toolName: 'gotry_doctor', callId: requestMeta.callId, reason, signal: requestMeta.signal });
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
raw = 'unavailable';
|
|
380
|
+
}
|
|
381
|
+
const mapped = raw === 'allowed-once' ? 'granted' :
|
|
382
|
+
raw === 'rejected' || raw === 'cancelled' ? raw : 'unavailable';
|
|
383
|
+
const s = getState(agent);
|
|
384
|
+
if (mapped === 'granted')
|
|
385
|
+
s.granted.add(scopeKey);
|
|
386
|
+
else if (mapped === 'rejected' || mapped === 'cancelled')
|
|
387
|
+
s.denied.set(scopeKey, mapped);
|
|
388
|
+
// unavailable 不记(通道回来还能再问一次)
|
|
389
|
+
return mapped;
|
|
390
|
+
};
|
|
391
|
+
return { has, request, store };
|
|
392
|
+
}
|
|
393
|
+
/** 修复运行(主入口):diagnosis → bootstrap.buildOnboardingPlan(status→level 适配)
|
|
394
|
+
* → scope-keyed 审批 → bootstrap.runOnboardingFix(真安装器 + 内部复检)
|
|
395
|
+
* → per-item verdict。**生产**:不传 opts,走 bootstrap 真实导出。
|
|
396
|
+
* **tests**:注入 installers / recheck / bridge 跑 fakes,永不真安装命令。
|
|
397
|
+
* user-action / unavailable 项绝不进 selected(分类器守住),scope 过滤后被剔除
|
|
398
|
+
* 的项从 selected 排除(永不装)。 */
|
|
399
|
+
export async function runDoctorRepair(report, approve, opts = {}) {
|
|
400
|
+
// 1. 选 bridge:tests 注入覆盖;生产用 bootstrap 真实导出(懒加载并缓存)
|
|
401
|
+
const bridge = opts.bridge
|
|
402
|
+
? { ...await loadBootstrapBridge(), ...opts.bridge }
|
|
403
|
+
: await loadBootstrapBridge();
|
|
404
|
+
const runInstalls = opts.runOnboardingFix ?? bridge.runOnboardingFix;
|
|
405
|
+
// 2. status→level 适配(关键)—直传 DoctorItem 会让 bootstrap.classifyDoctorGap
|
|
406
|
+
// 因 !item.level 把所有项分到 ok,plan 始终为空(原版真 bug)。
|
|
407
|
+
const bItems = (report.items ?? []).map(toBootstrapItem);
|
|
408
|
+
const plan = bridge.buildOnboardingPlan(bItems, { platform: opts.platform, env: opts.env });
|
|
409
|
+
const labelToId = new Map();
|
|
410
|
+
for (const i of report.items ?? [])
|
|
411
|
+
if (i?.id && i?.label)
|
|
412
|
+
labelToId.set(i.label, i.id);
|
|
413
|
+
// 3. selected = plan.auto ∩ 已知 doctor item id(防御:未知 id 静默剔除不冒充 auto)
|
|
414
|
+
const selected = (plan.auto ?? [])
|
|
415
|
+
.filter((g) => KNOWN_IDS.has(labelToId.get(g.label) ?? ''))
|
|
416
|
+
.map((g) => ({ id: labelToId.get(g.label), label: g.label }));
|
|
417
|
+
const skipped = [
|
|
418
|
+
...(plan.userAction ?? []).map((g) => ({ id: labelToId.get(g.label) ?? '', label: g.label, bucket: 'user-action', reason: g.fix ?? g.detail ?? '需用户本人操作' })),
|
|
419
|
+
...(plan.unavailable ?? []).map((g) => ({ id: labelToId.get(g.label) ?? '', label: g.label, bucket: 'unavailable', reason: g.detail ?? '本仓无自动安装面' })),
|
|
420
|
+
];
|
|
421
|
+
const scopeKey = scopeKeyFor(selected.map((s) => s.id));
|
|
422
|
+
// 4. 无 selected → 早退(零审批请求,零副作用)
|
|
423
|
+
if (selected.length === 0) {
|
|
424
|
+
const healthy = (report.items ?? []).every((item) => item.status === 'ok');
|
|
425
|
+
return {
|
|
426
|
+
ok: healthy, verdict: 'no-repair-needed', approval: 'not-needed',
|
|
427
|
+
selected, scopeKey,
|
|
428
|
+
repairs: [], skipped,
|
|
429
|
+
summary: healthy
|
|
430
|
+
? '诊断:所选项目已经全部就位,无需修复。'
|
|
431
|
+
: '诊断:无可自动补装的缺项(其余项需用户本人操作或本机暂不支持)。',
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
// 5. scope-keyed 审批(rejected 与 cancelled 在 denial 缓存里分开记)
|
|
435
|
+
const approval = await approve(scopeKey, `将自动补装 ${selected.length} 项可选依赖:${selected.map((s) => s.label).join('、')};复用 doctor --fix 幂等安装器。`);
|
|
436
|
+
if (approval !== 'granted') {
|
|
437
|
+
return {
|
|
438
|
+
ok: false, verdict: 'approval-denied', approval,
|
|
439
|
+
selected, scopeKey,
|
|
440
|
+
repairs: [], skipped,
|
|
441
|
+
summary: approval === 'rejected' ? '修复未执行:用户拒绝了这次补装(本会话吊销,不再询问)。'
|
|
442
|
+
: approval === 'cancelled' ? '修复未执行:用户取消了这次补装(本会话吊销,不再询问)。'
|
|
443
|
+
: '修复未执行:当前无可用审批通道(headless / 无 UI)。',
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
// 6. 真执行:bootstrap.runOnboardingFix(默认 installers = real setupHbcli/etc.;
|
|
447
|
+
// 默认 recheck = bootstrap 内置 doctorChecks,与 runDoctorChecks 同源)。tests 注入 fakes。
|
|
448
|
+
const fullPlan = {
|
|
449
|
+
promptable: true, auto: plan.auto ?? [], userAction: plan.userAction ?? [], unavailable: plan.unavailable ?? [],
|
|
450
|
+
};
|
|
451
|
+
const runResults = await runInstalls(fullPlan, {
|
|
452
|
+
...(opts.installers ? { installers: opts.installers } : {}),
|
|
453
|
+
...(opts.recheck ? { recheck: opts.recheck } : {}),
|
|
339
454
|
});
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
455
|
+
// 7. per-item 终态:bootstrap 已含内部复检的 installed / needs-user-action / unavailable;
|
|
456
|
+
// needs-user-action 与 unavailable 都视为 failed(给重试命令)。
|
|
457
|
+
const repairs = selected.map((s) => {
|
|
458
|
+
const r = runResults.find((x) => x.label === s.label);
|
|
459
|
+
if (r?.status === 'installed')
|
|
460
|
+
return { id: s.id, label: s.label, status: 'installed', reason: r.reason ?? '已就位' };
|
|
461
|
+
return {
|
|
462
|
+
id: s.id, label: s.label, status: 'failed',
|
|
463
|
+
reason: r?.reason ?? '安装器未确认成功',
|
|
464
|
+
nextAction: r?.retry ?? 'npx @danceiny/gotry doctor --fix',
|
|
465
|
+
};
|
|
345
466
|
});
|
|
346
|
-
const
|
|
347
|
-
const
|
|
467
|
+
const failed = repairs.filter((r) => r.status === 'failed').length;
|
|
468
|
+
const installed = repairs.length - failed;
|
|
469
|
+
const verdict = failed === 0 ? 'repaired' : installed === 0 ? 'repair-blocked' : 'partial-repair';
|
|
348
470
|
return {
|
|
349
|
-
ok:
|
|
350
|
-
|
|
351
|
-
|
|
471
|
+
ok: failed === 0, verdict, approval, selected, scopeKey, repairs, skipped,
|
|
472
|
+
summary: failed === 0
|
|
473
|
+
? `修复完成:${installed} 项已就位(其余项见 skipped,需要时手动处理)。`
|
|
474
|
+
: installed === 0
|
|
475
|
+
? `修复失败:${failed} 项复检仍未就位(${repairs.filter((r) => r.status === 'failed').map((r) => r.label).join('、')});重试: npx @danceiny/gotry doctor --fix。`
|
|
476
|
+
: `修复部分成功:${installed}/${selected.length} 项已就位,${failed} 项失败(${repairs.filter((r) => r.status === 'failed').map((r) => r.label).join('、')});重试: npx @danceiny/gotry doctor --fix。`,
|
|
352
477
|
};
|
|
353
478
|
}
|
|
354
|
-
|
|
479
|
+
/** 报告 → 侧栏可预览 markdown(写盘由调用方决定;本函数纯渲染) */
|
|
355
480
|
export function renderDoctorReportMd(report, now = new Date()) {
|
|
356
|
-
const icon = (s)=>s === 'ok' ? '✅' : s === 'degraded' ? '⚠️' : '❌';
|
|
481
|
+
const icon = (s) => (s === 'ok' ? '✅' : s === 'degraded' ? '⚠️' : '❌');
|
|
357
482
|
const lines = [
|
|
358
483
|
'# GoTry 依赖体检报告(doctor)',
|
|
359
484
|
'',
|
|
360
|
-
`> 生成于 ${now.toISOString()};重新生成:终端 \`npx gotry doctor\`,或在对话里让助手调 gotry_doctor。`,
|
|
485
|
+
`> 生成于 ${now.toISOString()};重新生成:终端 \`npx @danceiny/gotry doctor\`,或在对话里让助手调 gotry_doctor。`,
|
|
361
486
|
'',
|
|
362
487
|
'| 状态 | 依赖 | 现状 | 修复指引 |',
|
|
363
488
|
'|---|---|---|---|',
|
|
364
|
-
...report.items.map(
|
|
489
|
+
...report.items.map(i => `| ${icon(i.status)} | ${i.label} | ${i.detail} | ${fixCell(i.fix)} |`),
|
|
365
490
|
'',
|
|
366
491
|
`**结论**:${report.summary}`,
|
|
367
492
|
'',
|
|
368
493
|
'---',
|
|
369
494
|
'',
|
|
370
|
-
'- `npx gotry doctor` 随时可重跑(只读,不改任何东西);',
|
|
371
|
-
'- `npx gotry doctor --fix` 按上表补装(hbcli 官方脚本 / agent-reach pip / dsh-better-sidebar 插件);',
|
|
495
|
+
'- `npx @danceiny/gotry doctor` 随时可重跑(只读,不改任何东西);',
|
|
496
|
+
'- `npx @danceiny/gotry doctor --fix` 按上表补装(hbcli 官方脚本 / agent-reach pip / dsh-better-sidebar 插件);',
|
|
372
497
|
'- LLM key 由 dsh 宿主 UI 管理,gotry 永不体检、不回显。',
|
|
373
|
-
''
|
|
498
|
+
'',
|
|
374
499
|
];
|
|
375
500
|
return lines.join('\n');
|
|
376
501
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
//# sourceURL=ts/capabilities/doctor.ts
|