@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
package/bin/gotry-bootstrap.js
CHANGED
|
@@ -75,16 +75,98 @@ const HBCLI_NPM_INSTALL_CMD = 'npm install -g staicli --registry=https://registr
|
|
|
75
75
|
const HBCLI_INSTALL_CMD = HBCLI_NPM_INSTALL_CMD
|
|
76
76
|
const REACH_INSTALL_URL = 'git+https://github.com/Panniantong/Agent-Reach.git'
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
let activeInstallerChild = null
|
|
79
|
+
let activeInstallerGroupPid = null
|
|
80
|
+
let installerSignalHandling = false
|
|
81
|
+
const installerSignalListeners = new Map()
|
|
82
|
+
const INSTALLER_TERM_GRACE_MS = 2_000
|
|
83
|
+
const INSTALLER_KILL_WAIT_MS = 1_000
|
|
84
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
85
|
+
const waitChildClose = (child, ms) => new Promise((resolve) => {
|
|
86
|
+
if (!child) { resolve(false); return }
|
|
87
|
+
let done = false
|
|
88
|
+
const finish = (value) => {
|
|
89
|
+
if (done) return
|
|
90
|
+
done = true
|
|
91
|
+
clearTimeout(timer)
|
|
92
|
+
child.off('close', onClose)
|
|
93
|
+
child.off('error', onError)
|
|
94
|
+
resolve(value)
|
|
95
|
+
}
|
|
96
|
+
const onClose = () => finish(true)
|
|
97
|
+
const onError = () => finish(true)
|
|
98
|
+
const timer = setTimeout(() => finish(false), ms)
|
|
99
|
+
child.once('close', onClose)
|
|
100
|
+
child.once('error', onError)
|
|
101
|
+
})
|
|
102
|
+
const signalInstallerChild = (child, signal, groupPid = null) => {
|
|
103
|
+
if (!child) return
|
|
104
|
+
if (process.platform !== 'win32' && groupPid) {
|
|
105
|
+
try { process.kill(-groupPid, signal); return } catch { /* fallback */ }
|
|
106
|
+
}
|
|
107
|
+
try { child.kill(signal) } catch { /* ignore */ }
|
|
108
|
+
}
|
|
109
|
+
const terminateInstallerChild = async (child, signal, groupPid = null, graceMs = INSTALLER_TERM_GRACE_MS, killMs = INSTALLER_KILL_WAIT_MS) => {
|
|
110
|
+
if (!child) return
|
|
111
|
+
signalInstallerChild(child, signal, groupPid)
|
|
112
|
+
const closed = await waitChildClose(child, graceMs)
|
|
113
|
+
if (closed) return
|
|
114
|
+
signalInstallerChild(child, 'SIGKILL', groupPid)
|
|
115
|
+
await waitChildClose(child, killMs)
|
|
116
|
+
}
|
|
117
|
+
const registerInstallerSignalForwarding = () => {
|
|
118
|
+
if (installerSignalListeners.size) return
|
|
119
|
+
for (const sig of ['SIGINT', 'SIGTERM']) {
|
|
120
|
+
const listener = () => {
|
|
121
|
+
if (installerSignalHandling) return
|
|
122
|
+
installerSignalHandling = true
|
|
123
|
+
void (async () => {
|
|
124
|
+
await terminateInstallerChild(activeInstallerChild, sig, activeInstallerGroupPid)
|
|
125
|
+
for (const [s, l] of installerSignalListeners) process.off(s, l)
|
|
126
|
+
installerSignalListeners.clear()
|
|
127
|
+
process.kill(process.pid, sig)
|
|
128
|
+
await wait(2_000)
|
|
129
|
+
process.exit(128 + (sig === 'SIGINT' ? 2 : 15))
|
|
130
|
+
})()
|
|
131
|
+
}
|
|
132
|
+
process.on(sig, listener)
|
|
133
|
+
installerSignalListeners.set(sig, listener)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const unregisterInstallerSignalForwarding = () => {
|
|
137
|
+
for (const [s, l] of installerSignalListeners) process.off(s, l)
|
|
138
|
+
installerSignalListeners.clear()
|
|
139
|
+
installerSignalHandling = false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** 带超时的子进程(inherit stdio 让用户看见上游安装进度);POSIX 下用进程组约束 installer 子树 */
|
|
79
143
|
function run(cmd, args, { timeoutMs, cwd, env } = {}) {
|
|
80
144
|
return new Promise((resolve) => {
|
|
81
|
-
const
|
|
145
|
+
const detachedInstaller = process.platform !== 'win32'
|
|
146
|
+
const child = spawn(cmd, args, { stdio: 'inherit', cwd, env: env ?? process.env, detached: detachedInstaller })
|
|
147
|
+
activeInstallerChild = child
|
|
148
|
+
activeInstallerGroupPid = detachedInstaller ? child.pid : null
|
|
149
|
+
registerInstallerSignalForwarding()
|
|
82
150
|
let done = false
|
|
83
|
-
|
|
84
|
-
|
|
151
|
+
let timer = null
|
|
152
|
+
const finish = (result) => {
|
|
153
|
+
if (done) return
|
|
154
|
+
done = true
|
|
155
|
+
if (timer) clearTimeout(timer)
|
|
156
|
+
activeInstallerChild = null
|
|
157
|
+
activeInstallerGroupPid = null
|
|
158
|
+
unregisterInstallerSignalForwarding()
|
|
159
|
+
resolve(result)
|
|
160
|
+
}
|
|
161
|
+
timer = setTimeout(() => {
|
|
162
|
+
void (async () => {
|
|
163
|
+
if (done) return
|
|
164
|
+
await terminateInstallerChild(child, 'SIGTERM', activeInstallerGroupPid)
|
|
165
|
+
finish({ ok: false, error: 'timeout' })
|
|
166
|
+
})()
|
|
85
167
|
}, timeoutMs)
|
|
86
|
-
child.on('error', (e) =>
|
|
87
|
-
child.on('
|
|
168
|
+
child.on('error', (e) => finish({ ok: false, error: e.message }))
|
|
169
|
+
child.on('close', (code, signal) => finish({ ok: code === 0, error: code === 0 ? undefined : signal ? `signal ${signal}` : `exit ${code}` }))
|
|
88
170
|
})
|
|
89
171
|
}
|
|
90
172
|
|
|
@@ -93,7 +175,9 @@ function probe(cmd, args, timeoutMs = 10_000) {
|
|
|
93
175
|
return new Promise((resolve) => {
|
|
94
176
|
const child = spawn(cmd, args, { stdio: 'ignore' })
|
|
95
177
|
let done = false
|
|
96
|
-
const timer = setTimeout(() => {
|
|
178
|
+
const timer = setTimeout(() => {
|
|
179
|
+
if (!done) { done = true; try { child.kill('SIGKILL') } catch { /* ignore */ } resolve(false) }
|
|
180
|
+
}, timeoutMs)
|
|
97
181
|
child.on('error', () => { if (!done) { done = true; clearTimeout(timer); resolve(false) } })
|
|
98
182
|
child.on('exit', (code) => { if (!done) { done = true; clearTimeout(timer); resolve(code === 0) } })
|
|
99
183
|
})
|
|
@@ -113,7 +197,8 @@ function parseVersion(v) {
|
|
|
113
197
|
}
|
|
114
198
|
|
|
115
199
|
function versionAtLeast(v, min) {
|
|
116
|
-
|
|
200
|
+
// v 可能是 hbcliVersion 已解析的数组,也可能是原始字符串——两者都接受
|
|
201
|
+
const a = Array.isArray(v) ? v : parseVersion(v); const b = parseVersion(min)
|
|
117
202
|
if (!a || !b) return false
|
|
118
203
|
for (let i = 0; i < 3; i++) { if (a[i] !== b[i]) return a[i] > b[i] }
|
|
119
204
|
return true
|
|
@@ -154,7 +239,7 @@ async function setupHbcli() {
|
|
|
154
239
|
if (CHECK_ONLY) { say(' ✗ 未安装(--check-only 只报告)'); return { ok: true } }
|
|
155
240
|
say(` 安装中(npm,staicli@npmjs): ${HBCLI_INSTALL_CMD}`)
|
|
156
241
|
const r = await run('bash', ['-c', HBCLI_INSTALL_CMD], { timeoutMs: 120_000 })
|
|
157
|
-
if (!r.ok) { say(` ✗ 安装失败(${r.error})——不影响 gotry,酒店检索将用内置静态包;可稍后重试: npx gotry setup`); return { ok: false } }
|
|
242
|
+
if (!r.ok) { say(` ✗ 安装失败(${r.error})——不影响 gotry,酒店检索将用内置静态包;可稍后重试: npx @danceiny/gotry setup`); return { ok: false } }
|
|
158
243
|
const binDir = join(homedir(), '.local/bin')
|
|
159
244
|
if (!process.env.PATH.split(':').includes(binDir)) {
|
|
160
245
|
say(` ⚠ ${binDir} 不在当前 PATH —— gotry 工具已内建候选路径回退,无需手动处理;其他程序可用: export PATH="${binDir}:$PATH"`)
|
|
@@ -173,13 +258,13 @@ async function setupReach() {
|
|
|
173
258
|
if (existsSync(venvBin)) { say(' ✓ 已安装(.venv)'); return { ok: true } }
|
|
174
259
|
if (CHECK_ONLY) { say(' ✗ 未安装(--check-only 只报告)'); return { ok: true } }
|
|
175
260
|
const hasPy = await probe('python3', ['--version'], 10_000)
|
|
176
|
-
if (!hasPy) { say(' ✗ 跳过:未找到 python3(agent-reach 需 Python 3;装好后重跑 npx gotry setup)'); return { ok: false } }
|
|
261
|
+
if (!hasPy) { say(' ✗ 跳过:未找到 python3(agent-reach 需 Python 3;装好后重跑 npx @danceiny/gotry setup)'); return { ok: false } }
|
|
177
262
|
say(` 创建 .venv 并安装上游(${REACH_INSTALL_URL})`)
|
|
178
263
|
const venv = await run('python3', ['-m', 'venv', join(repoRoot, '.venv')], { timeoutMs: 120_000 })
|
|
179
264
|
if (!venv.ok) { say(` ✗ venv 创建失败(${venv.error})——不影响 gotry,gotry_agent_reach 将返回 not-installed 指引`); return { ok: false } }
|
|
180
265
|
const pip = join(repoRoot, '.venv/bin/pip')
|
|
181
266
|
const inst = await run(pip, ['install', '-q', REACH_INSTALL_URL], { timeoutMs: 300_000 })
|
|
182
|
-
if (!inst.ok) { say(` ✗ pip 安装失败(${inst.error})——可稍后重试: npx gotry setup`); return { ok: false } }
|
|
267
|
+
if (!inst.ok) { say(` ✗ pip 安装失败(${inst.error})——可稍后重试: npx @danceiny/gotry setup`); return { ok: false } }
|
|
183
268
|
say(' ✓ 安装完成(渠道凭证选配见 docs/tokens.md: .venv/bin/agent-reach configure --from-browser chrome --platform <渠道>)')
|
|
184
269
|
return { ok: true }
|
|
185
270
|
}
|
|
@@ -232,7 +317,7 @@ async function setupSidebar(attemptInstall) { say('[gotry-setup] dsh-better-sid
|
|
|
232
317
|
say(' ✓ 安装完成(npx 途径 exit 非 0,但按落盘状态复核已就位)')
|
|
233
318
|
return { ok: true }
|
|
234
319
|
}
|
|
235
|
-
say(' ✗ 安装失败——不影响 gotry:产物仍可在对话里说「看看我生成的行程」经 gotry_artifacts_list/read 查看;可稍后重试: npx gotry doctor --fix')
|
|
320
|
+
say(' ✗ 安装失败——不影响 gotry:产物仍可在对话里说「看看我生成的行程」经 gotry_artifacts_list/read 查看;可稍后重试: npx @danceiny/gotry doctor --fix')
|
|
236
321
|
return { ok: false }
|
|
237
322
|
}
|
|
238
323
|
|
|
@@ -243,6 +328,10 @@ async function setupSidebar(attemptInstall) { say('[gotry-setup] dsh-better-sid
|
|
|
243
328
|
|
|
244
329
|
const DOCTOR = process.argv.includes('doctor')
|
|
245
330
|
const DOCTOR_FIX = process.argv.includes('--fix')
|
|
331
|
+
// onboarding 子命令(issue #258):`gotry web` 启动前的显式可选能力配置 prompt——每次符合条件的
|
|
332
|
+
// 启动评估一次、至多问一次,无跨启动持久确认。inner 以 `onboarding --result-file=…` 同步 inherit
|
|
333
|
+
// 调用;`--scan` 只读查看计划(测试/调试)。
|
|
334
|
+
const ONBOARDING = process.argv.includes('onboarding')
|
|
246
335
|
// calendar 子命令(issue #106/D-9):可选日历挂载的 setup 状态管理面。
|
|
247
336
|
// `gotry setup calendar`=开启;`--off`=关闭(删状态文件恢复默认);`--status`=只读查看。
|
|
248
337
|
const CALENDAR_CMD = process.argv.includes('calendar')
|
|
@@ -276,12 +365,12 @@ async function runCalendar() {
|
|
|
276
365
|
const state = readCalendarState()
|
|
277
366
|
say(` 状态: ${calendarDetail(state)}`)
|
|
278
367
|
say(` 状态文件: ${calendarStatePath()}`)
|
|
279
|
-
say(' 说明: 挂载=`npx gotry setup calendar`;关闭=`npx gotry setup calendar --off`;配置在 dsh profile 的 cordis.patch.yml 覆盖 calendar 行 config 填 username')
|
|
368
|
+
say(' 说明: 挂载=`npx @danceiny/gotry setup calendar`;关闭=`npx @danceiny/gotry setup calendar --off`;配置在 dsh profile 的 cordis.patch.yml 覆盖 calendar 行 config 填 username')
|
|
280
369
|
return 0
|
|
281
370
|
}
|
|
282
371
|
if (CALENDAR_OFF) {
|
|
283
372
|
try { rmSync(calendarStatePath()) } catch { /* 本就未开启 */ }
|
|
284
|
-
say(' ✓ 已关闭——恢复默认不挂载(状态文件已删除;如需再开: npx gotry setup calendar)')
|
|
373
|
+
say(' ✓ 已关闭——恢复默认不挂载(状态文件已删除;如需再开: npx @danceiny/gotry setup calendar)')
|
|
285
374
|
return 0
|
|
286
375
|
}
|
|
287
376
|
mkdirSync(dirname(calendarStatePath()), { recursive: true })
|
|
@@ -317,7 +406,7 @@ async function doctorChecks() {
|
|
|
317
406
|
const reachBin = join(repoRoot, '.venv/bin/agent-reach')
|
|
318
407
|
const reachOk = existsSync(reachBin)
|
|
319
408
|
const reachLevel = reachOk ? 'ok' : existsSync(venvPython) ? 'degraded' : 'missing'
|
|
320
|
-
items.push({ label: 'Agent Reach(网页/社媒读取)', ok: reachOk, level: reachLevel, detail: reachOk ? `已安装(${reachBin})` : reachLevel === 'degraded' ? '.venv 在但缺 agent-reach 包——gotry_agent_reach / gotry_web_search 读页会失败' : '未装配——gotry_agent_reach / gotry_web_search(读网页)/ gotry_video_subtitle / gotry_github_search 全部不可用', fix: reachOk ? undefined : 'npx gotry doctor --fix' })
|
|
409
|
+
items.push({ label: 'Agent Reach(网页/社媒读取)', ok: reachOk, level: reachLevel, detail: reachOk ? `已安装(${reachBin})` : reachLevel === 'degraded' ? '.venv 在但缺 agent-reach 包——gotry_agent_reach / gotry_web_search 读页会失败' : '未装配——gotry_agent_reach / gotry_web_search(读网页)/ gotry_video_subtitle / gotry_github_search 全部不可用', fix: reachOk ? undefined : 'npx @danceiny/gotry doctor --fix' })
|
|
321
410
|
// hbcli(裸名靠 PATH 探测;绝对路径 existsSync;版本 < MIN_HBCLI_VERSION 视为 missing,见 issue #142)
|
|
322
411
|
let hbBin = ''
|
|
323
412
|
for (const p of ['hbcli', join(homedir(), '.local/bin/hbcli'), join(homedir(), '.staicli/current/hbcli')]) {
|
|
@@ -332,24 +421,25 @@ async function doctorChecks() {
|
|
|
332
421
|
items.push({ label: 'hbcli(酒店实时源)', ok: whoami, level: whoami ? 'ok' : 'degraded', detail: whoami ? `已安装且凭证有效(${hbBin}, v${v ? v.join('.') : '?'})` : '二进制在,但凭证未配置/失效——酒店检索将降级静态包(非实时)', fix: whoami ? undefined : 'hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo(快速试用沙箱;正式 key 向 HotelByte 申请)' })
|
|
333
422
|
}
|
|
334
423
|
} else {
|
|
335
|
-
items.push({ label: 'hbcli(酒店实时源)', ok: false, level: 'missing', detail: '未安装——酒店检索降级静态包(公开渠道估算,非实时,仅覆盖内置场景)', fix: 'npx gotry doctor --fix' })
|
|
424
|
+
items.push({ label: 'hbcli(酒店实时源)', ok: false, level: 'missing', detail: '未安装——酒店检索降级静态包(公开渠道估算,非实时,仅覆盖内置场景)', fix: 'npx @danceiny/gotry doctor --fix' })
|
|
336
425
|
}
|
|
337
426
|
// flyai key(匿名试用额度共享易达限;正式 key 即免)
|
|
338
427
|
const flyaiKey = (process.env.FLYAI_API_KEY ?? '').trim()
|
|
339
428
|
items.push({ label: 'FlyAI(飞猪官方检索)', ok: Boolean(flyaiKey), level: flyaiKey ? 'ok' : 'degraded', detail: flyaiKey ? 'FLYAI_API_KEY 已配(正式 key,无试用额度限制)' : '未配 FLYAI_API_KEY——走匿名试用额度(共享,易达限;达限报 "Trial limit reached")', fix: flyaiKey ? undefined : '到 flyai.open.fliggy.com 控制台申请正式 key,配进环境变量 FLYAI_API_KEY' })
|
|
340
429
|
// sidebar(状态面与 setupSidebar 的落盘复核同一口径)
|
|
341
430
|
const sbOk = sidebarInstalled()
|
|
342
|
-
items.push({ label: 'dsh-better-sidebar(侧栏工作台)', ok: sbOk, level: sbOk ? 'ok' : 'missing', detail: sbOk ? '已安装——web UI 右侧工作台可预览产物与 doctor 报告(gotry-state/doctor-report.md)' : '未安装——dsh web 无右侧工作台,产物与 doctor 报告只能在对话里看(gotry_artifacts_list)', fix: sbOk ? undefined : 'npx gotry doctor --fix' })
|
|
431
|
+
items.push({ label: 'dsh-better-sidebar(侧栏工作台)', ok: sbOk, level: sbOk ? 'ok' : 'missing', detail: sbOk ? '已安装——web UI 右侧工作台可预览产物与 doctor 报告(gotry-state/doctor-report.md)' : '未安装——dsh web 无右侧工作台,产物与 doctor 报告只能在对话里看(gotry_artifacts_list)', fix: sbOk ? undefined : 'npx @danceiny/gotry doctor --fix' })
|
|
343
432
|
// dsh-calendar(setup 状态面;默认不挂载=ok 是合法态,opt-in 未配置才 degraded)
|
|
344
433
|
const calState = readCalendarState()
|
|
345
434
|
const calOn = calState?.enabled === true
|
|
346
435
|
const calConfigured = calOn && calendarProfileConfigured()
|
|
347
|
-
items.push({ label: 'dsh-calendar(日历工作窗口)', ok: !calOn || calConfigured, level: !calOn ? 'ok' : calConfigured ? 'ok' : 'degraded', detail: calendarDetail(calState), fix: !calOn ? undefined : calConfigured ? undefined : '在 ~/.dsh/profiles/web/cordis.patch.yml 覆盖 calendar 行 config 填 username(或 npx gotry setup calendar --off 恢复默认不挂载)' })
|
|
436
|
+
items.push({ label: 'dsh-calendar(日历工作窗口)', ok: !calOn || calConfigured, level: !calOn ? 'ok' : calConfigured ? 'ok' : 'degraded', detail: calendarDetail(calState), fix: !calOn ? undefined : calConfigured ? undefined : '在 ~/.dsh/profiles/web/cordis.patch.yml 覆盖 calendar 行 config 填 username(或 npx @danceiny/gotry setup calendar --off 恢复默认不挂载)' })
|
|
348
437
|
// dsh-map-tools(patch 分发面宿主插件,issue #139):解析失败启动时整块静默剔除,
|
|
349
438
|
// doctor 把两态照亮。候选清单与 bin/gotry-inner.js 解析链、ts/capabilities/doctor.ts 同口径:
|
|
350
|
-
// tarball vendor 副本优先(随 files[] 分发,不进 npm
|
|
351
|
-
// dsh-settings/dsh-tools >=0.1.2-rc.1,与锁定的 0.1.2-alpha.3 家族在 npm 严格 peer
|
|
352
|
-
// 解析下 ERESOLVE
|
|
439
|
+
// tarball vendor 副本优先(随 files[] 分发,不进 npm 依赖——历史上游 peerDependencies
|
|
440
|
+
// 要求 dsh-settings/dsh-tools >=0.1.2-rc.1,与锁定的 0.1.2-alpha.3 家族在 npm 严格 peer
|
|
441
|
+
// 解析下 ERESOLVE;当前锁定 0.1.5-rc.1 家族,vendored 副本已对齐 0.1.5-rc.1,
|
|
442
|
+
// 保持 vendored 形态复用适配补丁),其余覆盖 source/提升/profile 布局。
|
|
353
443
|
const rootRequire = createRequire(join(repoRoot, 'package.json'))
|
|
354
444
|
const mapCandidates = [
|
|
355
445
|
join(repoRoot, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js'),
|
|
@@ -396,13 +486,13 @@ const doctorIcon = { ok: '✅', missing: '❌', degraded: '⚠️' }
|
|
|
396
486
|
const fixCell = (fix) => (!fix ? '—' : /^(npx|hbcli|curl|pip|python|\$)/.test(fix) ? `\`${fix}\`` : fix)
|
|
397
487
|
|
|
398
488
|
/** 启动一次性摘要行(issue #114,design §3.1③):全 ok 返回 null(静默零输出);
|
|
399
|
-
* 有待处理项给一行人话 + 指路(对话里 gotry_doctor 看详情 / 终端 npx gotry doctor)。
|
|
489
|
+
* 有待处理项给一行人话 + 指路(对话里 gotry_doctor 看详情 / 终端 npx @danceiny/gotry doctor)。
|
|
400
490
|
* 纯函数,bootstrap-tests 直接断言。 */
|
|
401
491
|
function startupDoctorLine(items) {
|
|
402
492
|
const broken = (items ?? []).filter((i) => i.level && i.level !== 'ok')
|
|
403
493
|
if (broken.length === 0) return null
|
|
404
494
|
const human = (lv) => (lv === 'missing' ? '缺' : '半可用')
|
|
405
|
-
return `[gotry] doctor: ${broken.length} 项待处理(${broken.map((i) => `${i.label}=${human(i.level)}`).join('、')})——对话里让助手调 gotry_doctor 看详情与指引,或终端跑 npx gotry doctor`
|
|
495
|
+
return `[gotry] doctor: ${broken.length} 项待处理(${broken.map((i) => `${i.label}=${human(i.level)}`).join('、')})——对话里让助手调 gotry_doctor 看详情与指引,或终端跑 npx @danceiny/gotry doctor`
|
|
406
496
|
}
|
|
407
497
|
|
|
408
498
|
/** 体检报告 markdown(与 ts/capabilities/doctor.ts renderDoctorReportMd 同形) */
|
|
@@ -411,18 +501,18 @@ function renderDoctorReportMd(items) {
|
|
|
411
501
|
const lines = [
|
|
412
502
|
'# GoTry 依赖体检报告(doctor)',
|
|
413
503
|
'',
|
|
414
|
-
`> 生成于 ${new Date().toISOString()};重新生成:终端 \`npx gotry doctor\`,或在对话里让助手调 gotry_doctor。`,
|
|
504
|
+
`> 生成于 ${new Date().toISOString()};重新生成:终端 \`npx @danceiny/gotry doctor\`,或在对话里让助手调 gotry_doctor。`,
|
|
415
505
|
'',
|
|
416
506
|
'| 状态 | 依赖 | 现状 | 修复指引 |',
|
|
417
507
|
'|---|---|---|---|',
|
|
418
508
|
...items.map((i) => `| ${doctorIcon[i.level]} | ${i.label} | ${i.detail} | ${fixCell(i.fix)} |`),
|
|
419
509
|
'',
|
|
420
|
-
`**结论**:${broken.length === 0 ? '全部就绪(可选依赖齐,LLM key 归 dsh 宿主管)。' : `${broken.length} 项待处理:${broken.map((i) => i.label).join('、')}。补装: npx gotry doctor --fix`}`,
|
|
510
|
+
`**结论**:${broken.length === 0 ? '全部就绪(可选依赖齐,LLM key 归 dsh 宿主管)。' : `${broken.length} 项待处理:${broken.map((i) => i.label).join('、')}。补装: npx @danceiny/gotry doctor --fix`}`,
|
|
421
511
|
'',
|
|
422
512
|
'---',
|
|
423
513
|
'',
|
|
424
|
-
'- `npx gotry doctor` 随时可重跑(只读,不改任何东西);',
|
|
425
|
-
'- `npx gotry doctor --fix` 按上表补装(hbcli 官方脚本 / agent-reach pip / dsh-better-sidebar 插件);',
|
|
514
|
+
'- `npx @danceiny/gotry doctor` 随时可重跑(只读,不改任何东西);',
|
|
515
|
+
'- `npx @danceiny/gotry doctor --fix` 按上表补装(hbcli 官方脚本 / agent-reach pip / dsh-better-sidebar 插件);',
|
|
426
516
|
'- LLM key 由 dsh 宿主 UI 管理,gotry 永不体检、不回显。',
|
|
427
517
|
'',
|
|
428
518
|
]
|
|
@@ -446,8 +536,8 @@ async function runDoctor() {
|
|
|
446
536
|
if (i.level !== 'ok' && i.fix) say(` ↳ 修复: ${i.fix}`)
|
|
447
537
|
}
|
|
448
538
|
if (DOCTOR_FIX) {
|
|
449
|
-
if (
|
|
450
|
-
say('[gotry-doctor] --fix 在 Windows 暂不支持自动安装(hbcli/agent-reach 上游无 win 安装面);请按各项修复指引手动处理')
|
|
539
|
+
if (!doctorFixAutoSupported()) {
|
|
540
|
+
say('[gotry-doctor] --fix 在 Windows 暂不支持自动安装(hbcli/agent-reach/sidebar 上游无 win 安装面);请按各项修复指引手动处理')
|
|
451
541
|
} else {
|
|
452
542
|
say('[gotry-doctor] 开始补装缺失项(--fix)…')
|
|
453
543
|
const results = []
|
|
@@ -455,7 +545,7 @@ async function runDoctor() {
|
|
|
455
545
|
if (process.env.GOTRY_SETUP_REACH !== '0') results.push(await setupReach())
|
|
456
546
|
if (process.env.GOTRY_SETUP_SIDEBAR !== '0') results.push(await setupSidebar())
|
|
457
547
|
const failedFix = results.filter((r) => !r.ok).length
|
|
458
|
-
say(failedFix === 0 ? '[gotry-doctor] 补装完成;下面是补装后复检。' : `[gotry-doctor] ${failedFix} 项补装失败——见上方安装器输出;可重跑 npx gotry doctor --fix`)
|
|
548
|
+
say(failedFix === 0 ? '[gotry-doctor] 补装完成;下面是补装后复检。' : `[gotry-doctor] ${failedFix} 项补装失败——见上方安装器输出;可重跑 npx @danceiny/gotry doctor --fix`)
|
|
459
549
|
items = await doctorChecks()
|
|
460
550
|
for (const i of items) {
|
|
461
551
|
say(` ${doctorIcon[i.level]} ${i.label}:${i.detail}`)
|
|
@@ -476,6 +566,339 @@ async function runDoctor() {
|
|
|
476
566
|
return stillMissing === 0 ? 0 : 1
|
|
477
567
|
}
|
|
478
568
|
|
|
569
|
+
// ---------------------------------------------------------------------------
|
|
570
|
+
// onboarding(issue #258):`npx @danceiny/gotry web` 启动前的显式可选能力配置 prompt——每次符合条件的启动
|
|
571
|
+
// 评估一次、至多问一次,无跨启动持久确认(不写「已问过」标记,每次 web 启动独立判定)。
|
|
572
|
+
// 契约:仅交互式 TTY + 有「可自动安装」缺项时问一次;y → 复用 doctor --fix 的幂等
|
|
573
|
+
// 安装器(setupHbcli/setupReach/setupSidebar,不建第二套);n → 立即继续 web。
|
|
574
|
+
// CI/benchmark/非 TTY/全健康/GOTRY_SETUP_SKIP=1/GOTRY_ONBOARDING_SKIP=1/--no-onboarding
|
|
575
|
+
// 均零 prompt 零安装,仍启 web;永不 postinstall 或后台任务里安装。结果三态:
|
|
576
|
+
// installed / needs-user-action / unavailable,各带具体原因;浏览器商店/凭证/账号/
|
|
577
|
+
// key/上游授权永不冒充自动完成。部分失败不挡 web 且给重试命令;再跑不重装已健康项
|
|
578
|
+
// (安装器自带存在性短路 + doctor 复检)。纯函数(classify/plan/skipReason)与
|
|
579
|
+
// runOnboardingFix(注入安装器)均导出供 bootstrap-tests 隔离单测,不跑真安装器。
|
|
580
|
+
// ---------------------------------------------------------------------------
|
|
581
|
+
|
|
582
|
+
/** 安装器 id 映射:doctor item label → 安装器 id(与 GOTRY_SETUP_* env 同名)。纯函数。 */
|
|
583
|
+
function installerIdFor(label) {
|
|
584
|
+
if (typeof label !== 'string') return null
|
|
585
|
+
if (label.startsWith('Agent Reach')) return 'reach'
|
|
586
|
+
if (label.startsWith('hbcli')) return 'hbcli'
|
|
587
|
+
if (label.startsWith('dsh-better-sidebar')) return 'sidebar'
|
|
588
|
+
return null
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** 该安装器是否被 GOTRY_SETUP_*=0 显式 opt-out(与 runDoctor 同口径)。纯函数。
|
|
592
|
+
* env 可注入:测试与可复用逻辑不依赖 ambient GOTRY_SETUP_*,生产 CLI 路径仍取 process.env。 */
|
|
593
|
+
function installerEnabled(id, env = process.env) {
|
|
594
|
+
if (id === 'hbcli') return env.GOTRY_SETUP_HBCLI !== '0'
|
|
595
|
+
if (id === 'reach') return env.GOTRY_SETUP_REACH !== '0'
|
|
596
|
+
if (id === 'sidebar') return env.GOTRY_SETUP_SIDEBAR !== '0'
|
|
597
|
+
return true
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** doctor --fix 自动安装的平台支持面(共享契约):win32 上 hbcli(staicli)/agent-reach(pip venv)
|
|
601
|
+
* 上游无 win 安装面,dsh-better-sidebar(dsh plugin → pnpm)在 win32 同样不自动跑——runDoctor
|
|
602
|
+
* 的 --fix 守卫与 onboarding 的 auto 分桶复用同一条边界,不在 win32 把这些缺项分到 auto
|
|
603
|
+
* (否则 prompt 了却装不上,不诚实)。纯函数,可注入 platform 供确定性单测。 */
|
|
604
|
+
function doctorFixAutoSupported(platform = process.platform) {
|
|
605
|
+
return platform !== 'win32'
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/** win32 上本可 auto 的缺项降为 unavailable 时的具体原因(诚实,不冒充 auto / 不给无效的
|
|
609
|
+
* `npx @danceiny/gotry doctor --fix` 指引)。纯函数。 */
|
|
610
|
+
function platformAutoUnavailableReason(id) {
|
|
611
|
+
if (id === 'hbcli') return 'Windows 暂不支持 hbcli 自动安装(staicli 上游无 win 安装面);请按 doctor 指引手动配置或切到 macOS/Linux'
|
|
612
|
+
if (id === 'reach') return 'Windows 暂不支持 agent-reach 自动安装(pip venv 上游无 win 安装面);请按 doctor 指引手动配置或切到 macOS/Linux'
|
|
613
|
+
if (id === 'sidebar') return 'Windows 暂不支持 dsh-better-sidebar 自动安装(dsh plugin → pnpm 在 win32 不自动跑);请按 doctor 指引手动配置或切到 macOS/Linux'
|
|
614
|
+
return 'Windows 暂不支持自动安装;请按 doctor 指引手动配置'
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* 把单个 doctor item 分到 onboarding 桶(纯函数,与 ts/capabilities/doctor.ts 同口径)。
|
|
619
|
+
* auto = 本机可自动安装(setupHbcli/setupReach/setupSidebar 且未被 env opt-out);
|
|
620
|
+
* user-action = 浏览器商店/凭证/账号/key/上游授权/手动配置——必须用户本人操作;
|
|
621
|
+
* unavailable = 本仓无自动安装面(重装 gotry / Node 升级 / env opt-out / 安装器不自动升级项)。
|
|
622
|
+
* LLM key 永远 ok,不进任何桶。
|
|
623
|
+
*/
|
|
624
|
+
function classifyDoctorGap(item, opts = {}) {
|
|
625
|
+
const platform = opts.platform ?? process.platform
|
|
626
|
+
const env = opts.env ?? process.env
|
|
627
|
+
if (!item || !item.level || item.level === 'ok') return 'ok'
|
|
628
|
+
const label = item.label ?? ''
|
|
629
|
+
if (label === 'LLM key') return 'ok'
|
|
630
|
+
const autoSupported = doctorFixAutoSupported(platform)
|
|
631
|
+
if (label.startsWith('Agent Reach')) return installerEnabled('reach', env) && autoSupported ? 'auto' : 'unavailable'
|
|
632
|
+
// hbcli:二进制缺失(missing)= auto(setupHbcli 装;win32 → unavailable,上游无 win 安装面);
|
|
633
|
+
// 凭证未配(degraded)= user-action(平台无关);旧版本(missing,安装器不自动升级)= auto 分桶
|
|
634
|
+
// 但安装会失败 → 结果兜底 unavailable,诚实不冒充。
|
|
635
|
+
if (label.startsWith('hbcli')) {
|
|
636
|
+
if (item.level === 'degraded') return 'user-action'
|
|
637
|
+
return installerEnabled('hbcli', env) && autoSupported ? 'auto' : 'unavailable'
|
|
638
|
+
}
|
|
639
|
+
if (label.startsWith('dsh-better-sidebar')) return installerEnabled('sidebar', env) && autoSupported ? 'auto' : 'unavailable'
|
|
640
|
+
if (label.startsWith('GoTry Session Bridge')) return 'user-action' // 浏览器商店一键装
|
|
641
|
+
if (label.startsWith('FlyAI')) return 'user-action' // 上游控制台申请 key
|
|
642
|
+
if (label.startsWith('dsh-calendar')) return 'user-action' // profile cordis.patch.yml 配置
|
|
643
|
+
if (label.startsWith('dsh-map-tools')) return 'unavailable' // 随包 vendor,缺失即重装 gotry
|
|
644
|
+
if (label.startsWith('dsh-tool-ask-user')) return 'unavailable' // 随 dsh 闭包,缺失即重装 gotry
|
|
645
|
+
if (label.startsWith('Node')) return 'unavailable' // 手动升级运行时
|
|
646
|
+
return 'unavailable'
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* 由 doctor items 构建 onboarding 计划(纯函数)。
|
|
651
|
+
* promptable = 存在可自动安装缺项(决定是否问那一次)。
|
|
652
|
+
*/
|
|
653
|
+
function buildOnboardingPlan(items, opts = {}) {
|
|
654
|
+
const platform = opts.platform ?? process.platform
|
|
655
|
+
const env = opts.env ?? process.env
|
|
656
|
+
const auto = []
|
|
657
|
+
const userAction = []
|
|
658
|
+
const unavailable = []
|
|
659
|
+
for (const item of items ?? []) {
|
|
660
|
+
const bucket = classifyDoctorGap(item, { platform, env })
|
|
661
|
+
if (bucket === 'ok') continue
|
|
662
|
+
const entry = { label: item.label, level: item.level, detail: item.detail, fix: item.fix }
|
|
663
|
+
if (bucket === 'auto') auto.push(entry)
|
|
664
|
+
else if (bucket === 'user-action') userAction.push(entry)
|
|
665
|
+
else {
|
|
666
|
+
// win32 平台把本可 auto 的缺项降为 unavailable 时,给具体平台原因(诚实,不冒充 auto,
|
|
667
|
+
// 也不给 win32 上无效的 `npx @danceiny/gotry doctor --fix` 指引);env opt-out / 随包缺失等保持原 detail。
|
|
668
|
+
const id = installerIdFor(item.label)
|
|
669
|
+
if (id && !doctorFixAutoSupported(platform) && installerEnabled(id, env)) {
|
|
670
|
+
entry.detail = platformAutoUnavailableReason(id)
|
|
671
|
+
}
|
|
672
|
+
unavailable.push(entry)
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return { promptable: auto.length > 0, auto, userAction, unavailable }
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* onboarding 跳过原因(纯函数;inner 与 bootstrap 共用,确保两边判定一致且可单测)。
|
|
680
|
+
* 返回原因字符串则跳过(零 prompt 零安装),null 则可继续。mode/benchmark 仅 inner
|
|
681
|
+
* 提供;bootstrap 直接调用时 mode 缺省(不检 non-web),只检 env/argv/isTTY。
|
|
682
|
+
*/
|
|
683
|
+
function onboardingSkipReason({ mode, benchmark, env = process.env, argv = process.argv, isTTY = process.stdin?.isTTY } = {}) {
|
|
684
|
+
if (mode !== undefined && mode !== 'web') return 'non-web-mode'
|
|
685
|
+
if (benchmark) return 'benchmark'
|
|
686
|
+
if (env.GOTRY_SETUP_SKIP === '1') return 'GOTRY_SETUP_SKIP=1'
|
|
687
|
+
if (env.GOTRY_ONBOARDING_SKIP === '1') return 'GOTRY_ONBOARDING_SKIP=1'
|
|
688
|
+
if (env.CI) return 'CI'
|
|
689
|
+
if (Array.isArray(argv) && argv.includes('--no-onboarding')) return '--no-onboarding'
|
|
690
|
+
if (!isTTY) return 'non-tty'
|
|
691
|
+
return null
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** 读取 stdin 一行(对 TTY 与注入流均工作;finish 后 pause,不干扰后续 dsh web 的 stdin)。 */
|
|
695
|
+
function readOneLine(input) {
|
|
696
|
+
return new Promise((resolve) => {
|
|
697
|
+
let buf = ''
|
|
698
|
+
let done = false
|
|
699
|
+
const finish = (line) => {
|
|
700
|
+
if (done) return
|
|
701
|
+
done = true
|
|
702
|
+
input.removeListener('data', onData)
|
|
703
|
+
input.removeListener('end', onEnd)
|
|
704
|
+
try { input.pause?.() } catch { /* ignore */ }
|
|
705
|
+
resolve(line ?? '')
|
|
706
|
+
}
|
|
707
|
+
const onData = (chunk) => {
|
|
708
|
+
buf += Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk)
|
|
709
|
+
const nl = buf.indexOf('\n')
|
|
710
|
+
if (nl >= 0) finish(buf.slice(0, nl).replace(/\r$/, ''))
|
|
711
|
+
}
|
|
712
|
+
const onEnd = () => finish(buf)
|
|
713
|
+
input.on('data', onData)
|
|
714
|
+
input.on('end', onEnd)
|
|
715
|
+
try { input.resume?.() } catch { /* ignore */ }
|
|
716
|
+
})
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* 交互 prompt:打印缺项概览 + 一次询问,读一行返回 'yes' | 'no'。
|
|
721
|
+
* input/output 可注入(测试);默认 process.stdin/process.stdout。
|
|
722
|
+
*/
|
|
723
|
+
async function promptOnboarding(plan, opts = {}) {
|
|
724
|
+
const input = opts.input ?? process.stdin
|
|
725
|
+
const output = opts.output ?? process.stdout
|
|
726
|
+
output.write('\n[gotry] 启动前检测到可选能力缺项,其中可自动配置:\n')
|
|
727
|
+
for (const g of plan.auto) output.write(` • ${g.label} —— ${g.detail}\n`)
|
|
728
|
+
if (plan.userAction.length || plan.unavailable.length) {
|
|
729
|
+
output.write('需你本人操作(浏览器商店 / 凭证 / key / 重装),gotry 不自动处理:\n')
|
|
730
|
+
for (const g of plan.userAction) output.write(` • ${g.label} —— ${g.detail}\n`)
|
|
731
|
+
for (const g of plan.unavailable) output.write(` • ${g.label} —— ${g.detail}\n`)
|
|
732
|
+
}
|
|
733
|
+
output.write('\n现在自动配置可安装项吗?(y/N,随后继续启动 web) ')
|
|
734
|
+
const answer = await readOneLine(input)
|
|
735
|
+
return /^\s*y(es)?\s*$/i.test(answer) ? 'yes' : 'no'
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* 渲染分类计划(无可自动安装项时):把 needs-user-action / unavailable 逐项带具体原因打印,
|
|
740
|
+
* 不 prompt 不安装。用于 win32 等无 auto 安装面、或仅凭证/key/重装缺口的交互式启动——
|
|
741
|
+
* 让用户看见分类与原因,而非只剩后台一行摘要。output 可注入(测试)。
|
|
742
|
+
*/
|
|
743
|
+
function renderClassifiedPlan(plan, output) {
|
|
744
|
+
output.write('\n[gotry] 启动前检测到可选能力缺项(无可自动安装项,以下需你本人操作或本机暂不支持):\n')
|
|
745
|
+
if (plan.userAction.length) {
|
|
746
|
+
output.write('需你本人操作(浏览器商店 / 凭证 / key / 配置),gotry 不自动处理:\n')
|
|
747
|
+
for (const g of plan.userAction) output.write(` • ${g.label} —— ${g.detail}\n`)
|
|
748
|
+
}
|
|
749
|
+
if (plan.unavailable.length) {
|
|
750
|
+
output.write('本机暂不支持自动安装(平台限制 / 重装 gotry / Node 升级):\n')
|
|
751
|
+
for (const g of plan.unavailable) output.write(` • ${g.label} —— ${g.detail}\n`)
|
|
752
|
+
}
|
|
753
|
+
output.write('详情与指引:对话里让助手调 gotry_doctor,或终端跑 npx @danceiny/gotry doctor\n')
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* 执行安装(复用 setupHbcli/setupReach/setupSidebar,不建第二套)。
|
|
758
|
+
* installers/recheck 可注入(测试用 fakes,永不跑真安装器)。返回逐项结果:
|
|
759
|
+
* installed / needs-user-action / unavailable(失败带重试命令)。部分失败不抛、不挡 web。
|
|
760
|
+
*/
|
|
761
|
+
const ONBOARDING_DEFAULT_INSTALLERS = { hbcli: setupHbcli, reach: setupReach, sidebar: setupSidebar }
|
|
762
|
+
|
|
763
|
+
async function runOnboardingFix(plan, opts = {}) {
|
|
764
|
+
const installers = { ...ONBOARDING_DEFAULT_INSTALLERS, ...(opts.installers ?? {}) }
|
|
765
|
+
const recheck = opts.recheck ?? doctorChecks
|
|
766
|
+
const attempts = {}
|
|
767
|
+
for (const gap of plan.auto) {
|
|
768
|
+
const id = installerIdFor(gap.label)
|
|
769
|
+
const installer = id ? installers[id] : null
|
|
770
|
+
if (!installer) { attempts[gap.label] = { ok: false, error: 'no installer mapped for this gap' }; continue }
|
|
771
|
+
try {
|
|
772
|
+
const r = await installer()
|
|
773
|
+
attempts[gap.label] = { ok: r?.ok === true, error: r?.error }
|
|
774
|
+
} catch (e) {
|
|
775
|
+
attempts[gap.label] = { ok: false, error: e?.message ?? String(e) }
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
// 复检决定 auto 项最终状态(安装器自带存在性短路 → 已健康项不重装;幂等)
|
|
779
|
+
const rechecked = await recheck()
|
|
780
|
+
const byLabel = new Map(rechecked.map((i) => [i.label, i]))
|
|
781
|
+
const results = []
|
|
782
|
+
for (const gap of plan.auto) {
|
|
783
|
+
const item = byLabel.get(gap.label)
|
|
784
|
+
if (item && item.level === 'ok') {
|
|
785
|
+
results.push({ label: gap.label, status: 'installed', reason: item.detail ?? '已就位' })
|
|
786
|
+
} else {
|
|
787
|
+
const a = attempts[gap.label]
|
|
788
|
+
results.push({ label: gap.label, status: 'unavailable', reason: a?.error ?? item?.detail ?? 'install failed', retry: 'npx @danceiny/gotry doctor --fix' })
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
for (const gap of plan.userAction) {
|
|
792
|
+
results.push({ label: gap.label, status: 'needs-user-action', reason: gap.fix ?? gap.detail ?? '需用户本人操作' })
|
|
793
|
+
}
|
|
794
|
+
for (const gap of plan.unavailable) {
|
|
795
|
+
results.push({ label: gap.label, status: 'unavailable', reason: gap.detail ?? '本仓无自动安装面' })
|
|
796
|
+
}
|
|
797
|
+
return results
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* `onboarding` 子命令(inner 以 `onboarding --result-file=…` 同步 inherit 调用;
|
|
802
|
+
* 亦可直接 `node gotry-bootstrap.js onboarding --scan` 只读查看计划)。
|
|
803
|
+
* 恒 exit 0(永不挡 web);result JSON 写到 --result-file 告知 inner 是否 prompt 过。
|
|
804
|
+
*
|
|
805
|
+
* opts(注入位,供 bootstrap-tests 在进程内跨 inner→onboarding→web 边界跑真实 onboarding 逻辑,
|
|
806
|
+
* 永不跑真安装器/开浏览器):input/output(注入流替 stdin/stdout)、isTTY/env/argv(覆盖跳过判定)、
|
|
807
|
+
* scan(注入 doctor items 替真机探测)、installers/recheck(注入假安装器/复检)、platform(注入平台)。
|
|
808
|
+
* 返回 result 对象({ prompted, answered?, results?, skipped?, plan? });CLI 仍恒 exit 0。
|
|
809
|
+
*/
|
|
810
|
+
async function runOnboarding(args, opts = {}) {
|
|
811
|
+
const resultFile = (args.find((a) => a.startsWith('--result-file=')) ?? '').slice('--result-file='.length) || null
|
|
812
|
+
const scanOnly = args.includes('--scan')
|
|
813
|
+
const input = opts.input ?? process.stdin
|
|
814
|
+
const output = opts.output ?? process.stdout
|
|
815
|
+
const isTTY = opts.isTTY ?? process.stdin?.isTTY
|
|
816
|
+
const env = opts.env ?? process.env
|
|
817
|
+
const argv = opts.argv ?? process.argv
|
|
818
|
+
const scan = opts.scan ?? doctorChecks
|
|
819
|
+
const platform = opts.platform ?? process.platform
|
|
820
|
+
// result 通道硬化:mode 0600 + flag wx——存在路径/符号链接永不覆盖(inner 在 0700
|
|
821
|
+
// mkdtemp 私有目录下传 result.json,这里 wx 防该确切路径被预置/符号链接互换攻击)。
|
|
822
|
+
const writeResult = (r) => { if (resultFile) { try { writeFileSync(resultFile, JSON.stringify(r), { mode: 0o600, flag: 'wx' }) } catch { /* 坏 result 不挡 web */ } } }
|
|
823
|
+
|
|
824
|
+
if (scanOnly) {
|
|
825
|
+
const plan = buildOnboardingPlan(await scan(), { platform, env })
|
|
826
|
+
say(JSON.stringify(plan))
|
|
827
|
+
return { prompted: false, scan: true, plan }
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const skipReason = onboardingSkipReason({ env, argv, isTTY })
|
|
831
|
+
if (skipReason) {
|
|
832
|
+
const result = { prompted: false, skipped: skipReason }
|
|
833
|
+
writeResult(result)
|
|
834
|
+
return result
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const plan = buildOnboardingPlan(await scan(), { platform, env })
|
|
838
|
+
if (!plan.promptable) {
|
|
839
|
+
// 无可自动安装缺项。两类:
|
|
840
|
+
// (1) 有需用户操作/不可用项(如 win32 平台不支持自动安装,或仅凭证/key/重装缺口):
|
|
841
|
+
// 渲染分类计划与具体原因,不 prompt 不安装;标记 reported 让 inner 抑制重复的
|
|
842
|
+
// detached doctor 摘要(design §3.1③,#114)——用户已看见分类,不再叠一行摘要。
|
|
843
|
+
// (2) 全健康:静默(后台摘要亦静默),不渲染。
|
|
844
|
+
const reportable = plan.userAction.length + plan.unavailable.length
|
|
845
|
+
if (reportable > 0) {
|
|
846
|
+
renderClassifiedPlan(plan, output)
|
|
847
|
+
const result = { prompted: false, reported: true, plan }
|
|
848
|
+
writeResult(result)
|
|
849
|
+
return result
|
|
850
|
+
}
|
|
851
|
+
const result = { prompted: false, plan }
|
|
852
|
+
writeResult(result)
|
|
853
|
+
return result
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const answer = await promptOnboarding(plan, { input, output })
|
|
857
|
+
if (answer === 'yes') {
|
|
858
|
+
say('[gotry] 开始自动配置可安装项(复用 doctor --fix 幂等安装器)…')
|
|
859
|
+
const results = await runOnboardingFix(plan, { installers: opts.installers, recheck: opts.recheck })
|
|
860
|
+
say('')
|
|
861
|
+
say('[gotry] 配置结果:')
|
|
862
|
+
for (const r of results) {
|
|
863
|
+
const tag = r.status === 'installed' ? '✅ installed' : r.status === 'needs-user-action' ? '↪ needs-user-action' : '✗ unavailable'
|
|
864
|
+
say(` ${tag} ${r.label} —— ${r.reason}${r.retry ? `(重试: ${r.retry})` : ''}`)
|
|
865
|
+
}
|
|
866
|
+
const result = { prompted: true, answered: 'yes', results }
|
|
867
|
+
writeResult(result)
|
|
868
|
+
return result
|
|
869
|
+
}
|
|
870
|
+
say(' 跳过——随时可跑: npx @danceiny/gotry doctor --fix(随后继续启动 web)')
|
|
871
|
+
const result = { prompted: true, answered: 'no' }
|
|
872
|
+
writeResult(result)
|
|
873
|
+
return result
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* web 模式的 onboarding + 启动编排(issue #258 E2E 边界缝):
|
|
878
|
+
* 跳过判定 →(可选)onboarding →(可选)后台 doctor 摘要 → dsh web 启动。抽成显式注入依赖的
|
|
879
|
+
* 函数,使 bootstrap-tests 能在不 spawn 真 dsh / 不跑真安装器的前提下,断言 inner 的真实编排跨过
|
|
880
|
+
* inner→onboarding→web 启动边界(yes/no/非 TTY/部分失败/summary 抑制均到达 web 启动标记)。
|
|
881
|
+
* 生产侧 inner 传入真实依赖(spawnSync 跑 bootstrap onboarding 子进程、spawn detached 跑 doctor
|
|
882
|
+
* --summary、spawn 跑 dsh web);launchWeb 即 web 启动边界标记。
|
|
883
|
+
* 摘要抑制契约:onboarding 已 prompt(用户交互过)或已 reported(渲染过分类计划,如 win32 无 auto
|
|
884
|
+
* 安装面)时,抑制重复的 detached doctor 摘要;跳过态(非 TTY/CI/opt-out)与全健康态仍走摘要
|
|
885
|
+
* (全健康时摘要本身静默)。返回 onboardingPrompted 供 inner 复用(本函数内已处理摘要抑制,返回值仅信息性)。
|
|
886
|
+
*/
|
|
887
|
+
async function orchestrateWebLaunch({ onboardingSkip, runOnboarding, runSummary, launchWeb }) {
|
|
888
|
+
let onboardingPrompted = false
|
|
889
|
+
let onboardingResult = null
|
|
890
|
+
if (!onboardingSkip) {
|
|
891
|
+
try { onboardingResult = await runOnboarding() } catch { /* onboarding 不可用不挡 web */ }
|
|
892
|
+
onboardingPrompted = onboardingResult?.prompted === true
|
|
893
|
+
}
|
|
894
|
+
// onboarding 已 prompt 或已 reported(渲染过分类计划)时抑制重复后台摘要(design §3.1③,#114)
|
|
895
|
+
if (!onboardingPrompted && !onboardingResult?.reported) {
|
|
896
|
+
try { runSummary() } catch { /* 体检不可用不挡启动 */ }
|
|
897
|
+
}
|
|
898
|
+
await launchWeb()
|
|
899
|
+
return { launched: true, onboardingPrompted, onboardingResult }
|
|
900
|
+
}
|
|
901
|
+
|
|
479
902
|
const EXTENSION_FILES = ['manifest.json', 'background.js', 'content-main.js', 'content-bridge.js', 'README.md']
|
|
480
903
|
|
|
481
904
|
function readManifestVersion(manifestPath) {
|
|
@@ -497,7 +920,7 @@ async function setupExtension() {
|
|
|
497
920
|
const srcDir = join(repoRoot, 'extension')
|
|
498
921
|
const srcManifest = join(srcDir, 'manifest.json')
|
|
499
922
|
if (!existsSync(srcManifest)) {
|
|
500
|
-
say(` ✗ 包内未找到扩展文件(${srcDir})——不影响 gotry:会话检索降级,重装 npx gotry 可恢复`)
|
|
923
|
+
say(` ✗ 包内未找到扩展文件(${srcDir})——不影响 gotry:会话检索降级,重装 npx @danceiny/gotry 可恢复`)
|
|
501
924
|
return { ok: false }
|
|
502
925
|
}
|
|
503
926
|
const srcVersion = readManifestVersion(srcManifest)
|
|
@@ -517,7 +940,7 @@ async function setupExtension() {
|
|
|
517
940
|
say(` ✓ 已落位 ${dstDir}(v${srcVersion};manifest 带固定 key,unpacked 扩展 ID 恒为 olpgkofjhhiiiahdkkbcninhjmegghfe)`)
|
|
518
941
|
say(' 推荐(免下面三步):Chrome 应用商店一键安装 GoTry Session Bridge(自动更新): https://chromewebstore.google.com/detail/gotry-session-bridge/oeajpiccmonococjcegddlooeeohlbgd')
|
|
519
942
|
say(' 本地加载(每台浏览器一次,约 30 秒):Chrome 打开 chrome://extensions → 右上角开启「开发者模式」→「加载已解压的扩展程序」→ 选择 ~/.gotry/extension')
|
|
520
|
-
say(' 获取/更新本地通道扩展可走 GitHub Releases:npx gotry setup --extension-from=github(自动下载校验落位;手动下载: github.com/Danceiny/gotry/releases 标签 ext-*)')
|
|
943
|
+
say(' 获取/更新本地通道扩展可走 GitHub Releases:npx @danceiny/gotry setup --extension-from=github(自动下载校验落位;手动下载: github.com/Danceiny/gotry/releases 标签 ext-*)')
|
|
521
944
|
say(' 装好即生效,零系统弹窗;扩展卡片开关=总闸(与 gotry 授权闸 sessionAccess 双重控制)')
|
|
522
945
|
return { ok: true }
|
|
523
946
|
}
|
|
@@ -758,6 +1181,9 @@ async function main() {
|
|
|
758
1181
|
// 只读体检零副作用(--fix 才装),win32 也可跑体检(fix 面另有提示)。
|
|
759
1182
|
if (DOCTOR) process.exit(await runDoctor())
|
|
760
1183
|
|
|
1184
|
+
// onboarding 子命令(issue #258):web 启动前的交互式可选能力配置(恒 exit 0,不挡 web)。
|
|
1185
|
+
if (ONBOARDING) { await runOnboarding(process.argv.slice(2)); process.exit(0) }
|
|
1186
|
+
|
|
761
1187
|
// wizard 子命令(2026-09-02 商店上架后退化):**只走 stdout 提示 + 健康探活等待**;
|
|
762
1188
|
// 不 spawn 任何 GUI 工具(不动 pbcopy / osascript / open / xdg-open / zenity),
|
|
763
1189
|
// 不打开 chrome://extensions,不动扩展路径——浏览器自己当安装器,gotry 不越界。
|
|
@@ -806,7 +1232,7 @@ if (WIZARD) {
|
|
|
806
1232
|
else say('[gotry-setup] GoTry Session Bridge 扩展:GOTRY_SETUP_EXTENSION=0 跳过')
|
|
807
1233
|
const failed = results.filter((r) => !r.ok).length
|
|
808
1234
|
if (failed > 0) {
|
|
809
|
-
say(`[gotry-setup] ${failed} 项未就绪——gotry 本体不受影响;可稍后重跑: npx gotry setup`)
|
|
1235
|
+
say(`[gotry-setup] ${failed} 项未就绪——gotry 本体不受影响;可稍后重跑: npx @danceiny/gotry setup`)
|
|
810
1236
|
process.exit(AUTO ? 0 : 1)
|
|
811
1237
|
}
|
|
812
1238
|
say('[gotry-setup] 扩展就绪检查完成。')
|
|
@@ -817,9 +1243,14 @@ if (WIZARD) {
|
|
|
817
1243
|
// bin/gotry-inner.js 始终以 `node …/gotry-bootstrap.js` 直接 spawn,不受影响)
|
|
818
1244
|
if (process.argv[1] && process.argv[1].endsWith('gotry-bootstrap.js')) {
|
|
819
1245
|
main().catch((e) => {
|
|
820
|
-
say(`[gotry-setup] 异常:${e.message}(不影响 gotry 本体;可重试 npx gotry setup)`)
|
|
1246
|
+
say(`[gotry-setup] 异常:${e.message}(不影响 gotry 本体;可重试 npx @danceiny/gotry setup)`)
|
|
821
1247
|
process.exit(AUTO ? 0 : 1)
|
|
822
1248
|
})
|
|
823
1249
|
}
|
|
824
1250
|
|
|
825
|
-
export {
|
|
1251
|
+
export {
|
|
1252
|
+
setupSidebar, sidebarInstalled,
|
|
1253
|
+
classifyDoctorGap, buildOnboardingPlan, onboardingSkipReason,
|
|
1254
|
+
runOnboardingFix, promptOnboarding, renderClassifiedPlan, runOnboarding, orchestrateWebLaunch,
|
|
1255
|
+
doctorFixAutoSupported, platformAutoUnavailableReason, installerEnabled,
|
|
1256
|
+
}
|