@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-inner.js
CHANGED
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
selectDshRuntime,
|
|
30
30
|
supportsNodeVersion,
|
|
31
31
|
} from './gotry-runtime-resolution.js'
|
|
32
|
+
import { onboardingSkipReason, orchestrateWebLaunch } from './gotry-bootstrap.js'
|
|
33
|
+
import { spawnOwnedChild, terminateOwnedChild } from './gotry-process-liveness.js'
|
|
32
34
|
|
|
33
35
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
34
36
|
const repoRoot = join(here, '..')
|
|
@@ -81,6 +83,9 @@ if (help) {
|
|
|
81
83
|
|
|
82
84
|
Usage:
|
|
83
85
|
gotry web # dsh Web UI on http://127.0.0.1:3080
|
|
86
|
+
# (interactive TTY) 启动前若发现可自动安装的可选能力,问一次是否配置;
|
|
87
|
+
# 确认复用 doctor --fix 幂等安装,拒绝立即继续。CI/非 TTY/全健康不弹问。
|
|
88
|
+
# 跳过该问: --no-onboarding 或 GOTRY_ONBOARDING_SKIP=1
|
|
84
89
|
gotry setup # 扩展就位检查/指引(商店一键装)
|
|
85
90
|
gotry setup calendar # 可选日历(CalDAV 工作窗口)挂载开关:默认关;--off 关闭;--status 查看
|
|
86
91
|
gotry doctor # 可选依赖体检:扩展/agent-reach/hbcli/flyai/sidebar 状态 + 补装指引
|
|
@@ -248,6 +253,9 @@ function projectBenchmarkPatch(raw, entryPath, configPath) {
|
|
|
248
253
|
// can make dsh fail while assembling the first request. Require the exact
|
|
249
254
|
// source shape before replacing it; malformed or ambiguous prompt config must
|
|
250
255
|
// fail closed rather than being partially interpreted.
|
|
256
|
+
// Target dsh-system-prompt Config exposes only personaPrefix / personaSuffix;
|
|
257
|
+
// the legacy `persona:` key does not project through. Validate against the
|
|
258
|
+
// current personaPrefix key.
|
|
251
259
|
function projectBenchmarkSystemPrompt(lines) {
|
|
252
260
|
const rootStarts = lines.map((line, index) => /^-\s+/.test(line) ? index : -1).filter(index => index >= 0)
|
|
253
261
|
const rootItems = rootStarts.map((itemStart, index) => lines.slice(itemStart, rootStarts[index + 1] ?? lines.length))
|
|
@@ -260,7 +268,12 @@ function projectBenchmarkSystemPrompt(lines) {
|
|
|
260
268
|
while (end < lines.length && (lines[end].trim() === '' || /^\s/.test(lines[end]) || lines[end].trim().startsWith('#'))) end += 1
|
|
261
269
|
const item = lines.slice(start, end)
|
|
262
270
|
const configIndexes = item.map((line, index) => /^ {2}config:\s*$/.test(line) ? index : -1).filter(index => index >= 0)
|
|
263
|
-
const personaIndexes = item.map((line, index) => /^ {4}
|
|
271
|
+
const personaIndexes = item.map((line, index) => /^ {4}personaPrefix:\s*>-\s*$/.test(line) ? index : -1).filter(index => index >= 0)
|
|
272
|
+
// Reject any leftover legacy `persona:` field under config: — it would be
|
|
273
|
+
// silently dropped by the target Config and leave the kernel persona-less.
|
|
274
|
+
// The minimal kernel must project through personaPrefix only.
|
|
275
|
+
const legacyPersonaIndexes = item.map((line, index) => /^ {4}persona:\s*>-\s*$/.test(line) ? index : -1).filter(index => index >= 0)
|
|
276
|
+
if (legacyPersonaIndexes.length !== 0) throw new Error('benchmark system-prompt legacy persona key not allowed under target 0.1.5-rc.1')
|
|
264
277
|
if (configIndexes.length !== 1 || personaIndexes.length !== 1 || personaIndexes[0] <= configIndexes[0]) {
|
|
265
278
|
throw new Error('benchmark system-prompt config violation')
|
|
266
279
|
}
|
|
@@ -268,7 +281,7 @@ function projectBenchmarkSystemPrompt(lines) {
|
|
|
268
281
|
const blockEnd = item.slice(persona + 1).findIndex(line => line.trim() !== '' && !/^\s{6,}/.test(line))
|
|
269
282
|
const contentEnd = blockEnd < 0 ? item.length : persona + 1 + blockEnd
|
|
270
283
|
if (contentEnd === persona + 1 || item.slice(persona + 1, contentEnd).some(line => line.trim() !== '' && !/^\s{6,}/.test(line))) {
|
|
271
|
-
throw new Error('benchmark system-prompt
|
|
284
|
+
throw new Error('benchmark system-prompt personaPrefix block violation')
|
|
272
285
|
}
|
|
273
286
|
const before = item.slice(1, configIndexes[0]).filter(line => line.trim() !== '' && !line.trim().startsWith('#'))
|
|
274
287
|
const between = item.slice(configIndexes[0] + 1, persona).filter(line => line.trim() !== '' && !line.trim().startsWith('#'))
|
|
@@ -277,7 +290,7 @@ function projectBenchmarkSystemPrompt(lines) {
|
|
|
277
290
|
const replacement = [
|
|
278
291
|
'- id: system-prompt',
|
|
279
292
|
' config:',
|
|
280
|
-
'
|
|
293
|
+
' personaPrefix: >-',
|
|
281
294
|
' You are GoTry, a task-agnostic travel planning assistant.',
|
|
282
295
|
' Use only the current conversation and tools available in this benchmark session.',
|
|
283
296
|
]
|
|
@@ -367,9 +380,10 @@ if (benchmarkEnvironmentConfig) {
|
|
|
367
380
|
}
|
|
368
381
|
// dsh-map-tools 宿主插件(地图/路线/POI,零 key 走 OSM/OSRM):tarball 随包 vendor,
|
|
369
382
|
// repo 工作副本走 runtime workspace 链接/依赖解析;都找不到就整块剔除 patch 条目
|
|
370
|
-
// (缺地图不挡旅行规划)
|
|
371
|
-
// dsh-settings/dsh-tools >=0.1.2-rc.1
|
|
372
|
-
//
|
|
383
|
+
// (缺地图不挡旅行规划)。保持 vendored 形态而非 npm 依赖:历史上游 peerDependencies
|
|
384
|
+
// (dsh-settings/dsh-tools >=0.1.2-rc.1)与锁定的 0.1.2-alpha.3 家族在 npm 严格 peer
|
|
385
|
+
// 解析下 ERESOLVE;当前锁定 0.1.5-rc.1 家族,vendored 副本 peerDependencies 已
|
|
386
|
+
// 对齐 0.1.5-rc.1,继续以 vendored 形态复用同一份适配补丁与七工具接线。
|
|
373
387
|
let mapEntry = ''
|
|
374
388
|
if (!benchmarkEnvironmentConfig) {
|
|
375
389
|
const vendoredMap = join(repoRoot, 'ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js')
|
|
@@ -423,7 +437,7 @@ if (mapEntry) {
|
|
|
423
437
|
// 未配置的日历工具是纯负资产(会话中段才撞「未配置 username」报错),gotry 对它的
|
|
424
438
|
// 唯一诉求(工作窗口)由访谈首轮覆盖。挂载与否由 **setup 状态面**决定(founder
|
|
425
439
|
// 2026-09-03 纠偏:禁止环境变量控制产品行为;可选依赖进 setup 状态管理,与扩展
|
|
426
|
-
// manifest 同居 ~/.gotry)——`npx gotry setup calendar` 写 ~/.gotry/calendar.json,
|
|
440
|
+
// manifest 同居 ~/.gotry)——`npx @danceiny/gotry setup calendar` 写 ~/.gotry/calendar.json,
|
|
427
441
|
// `--off` 删除恢复默认;doctor 报告三态。
|
|
428
442
|
let calEnabled = false
|
|
429
443
|
try {
|
|
@@ -479,17 +493,86 @@ const cleanupPatch = () => {
|
|
|
479
493
|
rmSync(patchDir, { recursive: true, force: true })
|
|
480
494
|
}
|
|
481
495
|
process.once('exit', cleanupPatch)
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
496
|
+
// 信号转发(issue #267 P1):spawnSync 阻塞 JS,信号处理器无法服务、finally 也跑不到。
|
|
497
|
+
// 故 web-onboarding 子调用改 awaited spawn。SIGINT/SIGTERM 转发在 onboarding **之前**注册一次,
|
|
498
|
+
// 并贯穿后续 dsh web 子进程的整个生命周期——服务管理器只向父进程发 SIGTERM 时,无论当前活跃的是
|
|
499
|
+
// onboarding 子进程组还是 dsh 子,都收到同一信号。收到信号时:终止当前活跃子进程/进程组,
|
|
500
|
+
// 等待 bounded close/reap,清 patch + 私有 result 目录(均幂等),移除本监听器,再向自己重发信号恢复原生终止语义。
|
|
501
|
+
// signalHandling 护栏防递归/重复处理(重发信号不再二次进入本回调)。setup/doctor 的 spawnSync 不在此窗口,保持不变。
|
|
502
|
+
let child = null // dsh web 子进程(launchWeb 后)
|
|
503
|
+
let childGroupPid = null // POSIX dsh child process-group leader
|
|
504
|
+
let onboardingChild = null // onboarding 子进程(运行中时);POSIX 下独立进程组
|
|
505
|
+
let onboardingChildGroupPid = null
|
|
506
|
+
let onboardingResultDir = null // 私有结果目录;信号/正常/错误路径都清
|
|
507
|
+
// outer grace 必须大于 bootstrap installer cleanup budget(2s TERM + 1s SIGKILL wait)。
|
|
508
|
+
// 否则 stubborn installer 忽略 TERM 时,inner 可能先杀 bootstrap,导致 bootstrap 来不及 SIGKILL
|
|
509
|
+
// 它自己 detached 出去的 installer 进程组。
|
|
510
|
+
const ONBOARDING_TERM_GRACE_MS = 5_000
|
|
511
|
+
const ONBOARDING_KILL_WAIT_MS = 1_000
|
|
512
|
+
const cleanupOnboardingResult = () => {
|
|
513
|
+
if (!onboardingResultDir) return
|
|
514
|
+
const d = onboardingResultDir
|
|
515
|
+
onboardingResultDir = null
|
|
516
|
+
try { rmSync(d, { recursive: true, force: true }) } catch { /* ignore */ }
|
|
517
|
+
}
|
|
518
|
+
const onboardingChildTimeoutMs = (() => {
|
|
519
|
+
const raw = process.env.GOTRY_ONBOARDING_CHILD_TIMEOUT_MS
|
|
520
|
+
if (raw === undefined || raw === '') return 300_000
|
|
521
|
+
if (!/^[1-9]\d*$/.test(raw)) return 300_000
|
|
522
|
+
const n = Number(raw)
|
|
523
|
+
return Number.isSafeInteger(n) && n > 0 ? n : 300_000
|
|
524
|
+
})()
|
|
525
|
+
let signalHandling = false
|
|
526
|
+
const signalListeners = new Map()
|
|
527
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
528
|
+
const signalChild = (proc, sig, groupPid = null) => {
|
|
529
|
+
if (!proc) return
|
|
530
|
+
if (process.platform !== 'win32' && groupPid) {
|
|
531
|
+
try { process.kill(-groupPid, sig); return } catch { /* fallback */ }
|
|
532
|
+
}
|
|
533
|
+
try { proc.kill(sig) } catch { /* ignore */ }
|
|
534
|
+
}
|
|
535
|
+
const terminateChildBounded = async (proc, sig, groupPid = null, graceMs = ONBOARDING_TERM_GRACE_MS, killMs = ONBOARDING_KILL_WAIT_MS) => {
|
|
536
|
+
await terminateOwnedChild({ child: proc, groupPid, signal: sig, termGraceMs: graceMs, killWaitMs: killMs })
|
|
537
|
+
}
|
|
538
|
+
const registerSignalForwarding = () => {
|
|
539
|
+
if (signalListeners.size) return
|
|
540
|
+
for (const sig of ['SIGINT', 'SIGTERM']) {
|
|
541
|
+
const listener = () => {
|
|
542
|
+
if (signalHandling) return // 防递归:重发信号不再二次进入
|
|
543
|
+
signalHandling = true
|
|
544
|
+
void (async () => {
|
|
545
|
+
const active = onboardingChild || child
|
|
546
|
+
const groupPid = onboardingChild ? onboardingChildGroupPid : childGroupPid
|
|
547
|
+
cleanupPatch()
|
|
548
|
+
cleanupOnboardingResult()
|
|
549
|
+
await terminateChildBounded(active, sig, groupPid)
|
|
550
|
+
for (const [s, l] of signalListeners) process.off(s, l)
|
|
551
|
+
signalListeners.clear()
|
|
552
|
+
process.kill(process.pid, sig) // 移除监听器后重发 → 原生终止语义
|
|
553
|
+
await sleep(2_000)
|
|
554
|
+
process.exit(128 + (sig === 'SIGINT' ? 2 : 15))
|
|
555
|
+
})()
|
|
556
|
+
}
|
|
557
|
+
process.on(sig, listener)
|
|
558
|
+
signalListeners.set(sig, listener)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const unregisterSignalForwarding = () => {
|
|
563
|
+
for (const [s, l] of signalListeners) process.off(s, l)
|
|
564
|
+
signalListeners.clear()
|
|
488
565
|
}
|
|
489
566
|
// LLM key 由 dsh 宿主管理(凭证是用户资产,UI 在 dsh 里;gotry 不拦截启动期,
|
|
490
567
|
// 不在用户面前展示任何 key 配置引导),此处直接放手 spawn dsh。
|
|
568
|
+
const webPortArgs = (() => {
|
|
569
|
+
if (mode !== 'web') return []
|
|
570
|
+
const portIndex = rest.indexOf('--port')
|
|
571
|
+
const port = portIndex >= 0 ? rest[portIndex + 1] : undefined
|
|
572
|
+
return port !== undefined && /^(?:0|[1-9]\d*)$/.test(port) ? ['--port', port] : []
|
|
573
|
+
})()
|
|
491
574
|
const binJs = mode === 'web'
|
|
492
|
-
? ['web', '--patch', patchPath, ...(process.argv.includes('--no-open') ? ['--no-open'] : [])]
|
|
575
|
+
? ['web', '--patch', patchPath, ...(process.argv.includes('--no-open') ? ['--no-open'] : []), ...webPortArgs]
|
|
493
576
|
: ['--profile', 'headless', '--patch', patchPath, ...rest]
|
|
494
577
|
|
|
495
578
|
const childEnv = { ...process.env }
|
|
@@ -517,21 +600,106 @@ const childStdio = mode === 'web'
|
|
|
517
600
|
let benchmarkCapturedBytes = 0
|
|
518
601
|
let benchmarkDiagnosticBuffer = Buffer.alloc(0)
|
|
519
602
|
let benchmarkOutputTruncated = false
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
603
|
+
// issue #258:`gotry web` 启动前的交互式 onboarding——每次符合条件的启动评估一次、至多问一次,
|
|
604
|
+
// 无跨启动持久确认(每次 web 启动独立判定;不写「已问过」标记)。仅交互式 TTY + 有可自动安装
|
|
605
|
+
// 缺项时问一次;y 复用 doctor --fix 幂等安装器,不建第二套;n 立即继续 web)。
|
|
606
|
+
// CI/benchmark/非 TTY/全健康/GOTRY_SETUP_SKIP=1/GOTRY_ONBOARDING_SKIP=1/--no-onboarding
|
|
607
|
+
// 均零 prompt 零安装,仍启 web;永不 postinstall 或后台任务里安装。无 auto 但有需用户操作/
|
|
608
|
+
// 不可用缺项时(如 win32 平台不支持自动安装),onboarding 渲染分类计划与原因、不 prompt 不安装,
|
|
609
|
+
// 标记 reported;orchestrateWebLaunch 据此抑制重复的 detached doctor 摘要(design §3.1③,#114)。
|
|
610
|
+
// 编排走 orchestrateWebLaunch(显式注入依赖的 E2E 边界缝,bootstrap-tests 跨边界单测同款):
|
|
611
|
+
// bootstrap 以 `onboarding --result-file=…` 异步 inherit 跑(awaited spawn,POSIX 下独立进程组——
|
|
612
|
+
// 使 JS 能服务 SIGINT/SIGTERM 并在信号路径清 patch+result 目录、reap installer 子树);result JSON 告知是否 prompt /
|
|
613
|
+
// reported,用以抑制重复的启动摘要行。yes/no/失败/跳过/已 reported 均继续到 launchWeb(spawn dsh web)。
|
|
614
|
+
const onboardingSkip = onboardingSkipReason({
|
|
615
|
+
mode,
|
|
616
|
+
benchmark: Boolean(benchmarkEnvironmentConfig),
|
|
617
|
+
env: process.env,
|
|
618
|
+
argv: process.argv,
|
|
619
|
+
isTTY: process.stdin.isTTY,
|
|
620
|
+
})
|
|
621
|
+
// 一次性注册信号转发,覆盖 onboarding + dsh child 全生命周期(dsh close/error 时注销)。
|
|
622
|
+
registerSignalForwarding()
|
|
623
|
+
await orchestrateWebLaunch({
|
|
624
|
+
onboardingSkip,
|
|
625
|
+
runOnboarding: async () => {
|
|
626
|
+
// result 通道硬化:不在 os.tmpdir 下放可预测文件名——mkdtempSync 建 0700 私有目录,
|
|
627
|
+
// result.json 放其内;bootstrap 以 mode 0600 + flag wx 写(存在路径/符号链接永不覆盖,
|
|
628
|
+
// 防 symlink 互换攻击)。子调用改 awaited spawn(非 spawnSync)使 JS 能服务信号;
|
|
629
|
+
// 信号/正常/错误路径都清目录(幂等),详见上方 registerSignalForwarding/cleanupOnboardingResult。
|
|
630
|
+
let resultDir = null
|
|
631
|
+
try {
|
|
632
|
+
resultDir = mkdtempSync(join(tmpdir(), 'gotry-onboarding-'))
|
|
633
|
+
onboardingResultDir = resultDir
|
|
634
|
+
const resultFile = join(resultDir, 'result.json')
|
|
635
|
+
// 信号转发在 onboarding 之前注册一次,贯穿后续 dsh web 整个生命周期(见上方 registerSignalForwarding);
|
|
636
|
+
// onboarding 正常结束后不注销——服务管理器只向父进程发信号时,当前活跃子进程(onboarding 或 dsh)均收到。
|
|
637
|
+
registerSignalForwarding()
|
|
638
|
+
const detachedOnboarding = process.platform !== 'win32'
|
|
639
|
+
const oc = spawn(process.execPath, [join(here, 'gotry-bootstrap.js'), 'onboarding', `--result-file=${resultFile}`], {
|
|
640
|
+
stdio: 'inherit',
|
|
641
|
+
detached: detachedOnboarding,
|
|
642
|
+
})
|
|
643
|
+
onboardingChild = oc
|
|
644
|
+
onboardingChildGroupPid = detachedOnboarding ? oc.pid : null
|
|
645
|
+
// onboarding 永不挡 web:保留 spawnSync 原有的 300s 上限(可经 GOTRY_ONBOARDING_CHILD_TIMEOUT_MS 注入以供
|
|
646
|
+
// 确定性测试,生产默认 300_000)。超时即 SIGTERM(2s 后 SIGKILL 兜底),settle 恰好一次于 close/error/timeout;
|
|
647
|
+
// 之后继续 web(prompted:false),不留进程/目录(finally 清 result,信号路径清 patch+result)。
|
|
648
|
+
await new Promise((resolve) => {
|
|
649
|
+
let done = false
|
|
650
|
+
let killTimer = null
|
|
651
|
+
const timer = setTimeout(() => {
|
|
652
|
+
process.stderr.write(`[gotry] onboarding child timeout after ${onboardingChildTimeoutMs}ms; continuing to web\n`)
|
|
653
|
+
signalChild(oc, 'SIGTERM', onboardingChildGroupPid)
|
|
654
|
+
killTimer = setTimeout(() => { if (!done) signalChild(oc, 'SIGKILL', onboardingChildGroupPid) }, ONBOARDING_TERM_GRACE_MS)
|
|
655
|
+
}, onboardingChildTimeoutMs)
|
|
656
|
+
const finish = () => { if (done) return; done = true; clearTimeout(timer); if (killTimer) clearTimeout(killTimer); resolve() }
|
|
657
|
+
oc.once('close', finish)
|
|
658
|
+
oc.once('error', (error) => {
|
|
659
|
+
process.stderr.write(`[gotry] onboarding child spawn failed: ${error.code || error.name || 'error'}; continuing to web\n`)
|
|
660
|
+
finish()
|
|
661
|
+
})
|
|
662
|
+
})
|
|
663
|
+
if (signalHandling) await new Promise(() => {})
|
|
664
|
+
onboardingChild = null
|
|
665
|
+
onboardingChildGroupPid = null
|
|
666
|
+
if (existsSync(resultFile)) {
|
|
667
|
+
try { return JSON.parse(readFileSync(resultFile, 'utf-8')) }
|
|
668
|
+
catch { return { prompted: false } }
|
|
669
|
+
}
|
|
670
|
+
return { prompted: false }
|
|
671
|
+
} catch {
|
|
672
|
+
process.stderr.write('[gotry] onboarding setup failed; continuing to web\n')
|
|
673
|
+
return { prompted: false }
|
|
674
|
+
} finally {
|
|
675
|
+
onboardingChild = null
|
|
676
|
+
onboardingChildGroupPid = null
|
|
677
|
+
if (resultDir) { try { rmSync(resultDir, { recursive: true, force: true }) } catch { /* ignore */ } }
|
|
678
|
+
onboardingResultDir = null
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
// 启动一次性 doctor 摘要(issue #114,design §3.1③):分离子进程后台跑只读体检,
|
|
682
|
+
// 有待处理项打一行 stderr(stderr 继承,不污染 stdout;benchmark 面保持零杂音)。
|
|
683
|
+
// onboarding 已 prompt 时 orchestrateWebLaunch 已抑制本回调(不重复)。
|
|
684
|
+
runSummary: () => {
|
|
685
|
+
if (benchmarkEnvironmentConfig) return
|
|
686
|
+
try {
|
|
687
|
+
spawn(process.execPath, [join(here, 'gotry-bootstrap.js'), 'doctor', '--summary'], {
|
|
688
|
+
detached: true,
|
|
689
|
+
stdio: ['ignore', 'ignore', 'inherit'],
|
|
690
|
+
}).unref()
|
|
691
|
+
} catch { /* 体检不可用不挡启动 */ }
|
|
692
|
+
},
|
|
693
|
+
// dsh web 启动边界:launchWeb 即 inner 跨过 onboarding 后到达的 web 启动标记。
|
|
694
|
+
launchWeb: async () => {
|
|
695
|
+
const owned = spawnOwnedChild(process.execPath, [dshBin, ...binJs], {
|
|
696
|
+
stdio: childStdio,
|
|
697
|
+
env: childEnv,
|
|
698
|
+
cwd: dshCwd,
|
|
699
|
+
})
|
|
700
|
+
child = owned.child
|
|
701
|
+
childGroupPid = owned.groupPid
|
|
702
|
+
},
|
|
535
703
|
})
|
|
536
704
|
if (benchmarkStdout) {
|
|
537
705
|
const maxBytes = Math.max(1, Number(benchmarkTerminalConfig?.max_bytes ?? 1))
|
|
@@ -569,8 +737,37 @@ const reportBenchmarkFailure = reason => {
|
|
|
569
737
|
process.stderr.write(`[gotry] benchmark terminal output unavailable (${reason})\n`)
|
|
570
738
|
}
|
|
571
739
|
|
|
572
|
-
|
|
740
|
+
let childLifecycleHandled = false
|
|
741
|
+
let childCleanupPromise = null
|
|
742
|
+
// `close` may be delayed by a descendant which inherited dsh's stdio pipe.
|
|
743
|
+
// Always start bounded TERM→KILL cleanup from `exit` so the owned group is
|
|
744
|
+
// reaped before any close/parser wait. The benchmark zero-success close path
|
|
745
|
+
// still parses the already-captured stdout buffer unchanged; cleanup runs
|
|
746
|
+
// fire-and-forget in the background and stays bounded by ONBOARDING bounds.
|
|
747
|
+
const startChildCleanup = () => {
|
|
748
|
+
if (childCleanupPromise || !child) return childCleanupPromise
|
|
749
|
+
childCleanupPromise = terminateOwnedChild({
|
|
750
|
+
child,
|
|
751
|
+
groupPid: childGroupPid,
|
|
752
|
+
signal: 'SIGTERM',
|
|
753
|
+
termGraceMs: ONBOARDING_TERM_GRACE_MS,
|
|
754
|
+
killWaitMs: ONBOARDING_KILL_WAIT_MS,
|
|
755
|
+
})
|
|
756
|
+
return childCleanupPromise
|
|
757
|
+
}
|
|
758
|
+
child.on('exit', (code, signal) => {
|
|
759
|
+
void startChildCleanup()
|
|
760
|
+
})
|
|
761
|
+
child.on('close', async (code, signal) => {
|
|
762
|
+
if (childLifecycleHandled) return
|
|
763
|
+
childLifecycleHandled = true
|
|
573
764
|
cleanupPatch()
|
|
765
|
+
if (signalHandling) return
|
|
766
|
+
unregisterSignalForwarding()
|
|
767
|
+
// Non-benchmark web/headless children are owned through both normal and
|
|
768
|
+
// anomalous exits. The promise was normally started by `exit`; retaining
|
|
769
|
+
// this close fallback covers runtimes that report close without exit.
|
|
770
|
+
if (!benchmarkStdout || code !== 0 || signal) await startChildCleanup()
|
|
574
771
|
if (benchmarkStdout) {
|
|
575
772
|
const captured = Buffer.concat(benchmarkStdout).toString('utf8')
|
|
576
773
|
if (code !== 0 || signal || benchmarkOutputTruncated) {
|
|
@@ -613,21 +810,43 @@ child.on('close', (code, signal) => {
|
|
|
613
810
|
// D-NEW 护栏:dsh 异常退出也写一条 incident,留现场而非沉默
|
|
614
811
|
// dist 模式 import JS(node_modules 下的 .ts 会被 Node 拒 strip)
|
|
615
812
|
const incidentModule = distModuleMode && existsSync(join(repoRoot, 'dist/capabilities/incident-log.js'))
|
|
616
|
-
? '
|
|
617
|
-
: '
|
|
618
|
-
|
|
813
|
+
? join(repoRoot, 'dist/capabilities/incident-log.js')
|
|
814
|
+
: join(repoRoot, 'ts/capabilities/incident-log.ts')
|
|
815
|
+
try {
|
|
816
|
+
const { recordIncident } = await import(pathToFileURL(incidentModule).href)
|
|
619
817
|
recordIncident({
|
|
620
818
|
ts: new Date().toISOString(),
|
|
621
819
|
kind: 'plugin_error',
|
|
622
820
|
message: `gotry spawn exit: ${mode} code=${code} signal=${signal}`,
|
|
623
821
|
source: 'gotry-cli',
|
|
624
|
-
}, repoRoot)
|
|
625
|
-
}
|
|
822
|
+
}, repoRoot)
|
|
823
|
+
} catch { /* diagnostics remain best-effort; fatal exit is preserved */ }
|
|
824
|
+
}
|
|
825
|
+
if (signal) {
|
|
826
|
+
try { process.kill(process.pid, signal) } catch { process.exit(1) }
|
|
827
|
+
return
|
|
626
828
|
}
|
|
627
829
|
process.exit(code ?? 1)
|
|
628
830
|
})
|
|
629
|
-
child.on('error', (e) => {
|
|
831
|
+
child.on('error', async (e) => {
|
|
832
|
+
if (childLifecycleHandled) return
|
|
833
|
+
childLifecycleHandled = true
|
|
630
834
|
cleanupPatch()
|
|
835
|
+
if (signalHandling) return
|
|
836
|
+
unregisterSignalForwarding()
|
|
837
|
+
await startChildCleanup()
|
|
838
|
+
try {
|
|
839
|
+
const incidentModule = distModuleMode && existsSync(join(repoRoot, 'dist/capabilities/incident-log.js'))
|
|
840
|
+
? join(repoRoot, 'dist/capabilities/incident-log.js')
|
|
841
|
+
: join(repoRoot, 'ts/capabilities/incident-log.ts')
|
|
842
|
+
const { recordIncident } = await import(pathToFileURL(incidentModule).href)
|
|
843
|
+
recordIncident({
|
|
844
|
+
ts: new Date().toISOString(),
|
|
845
|
+
kind: 'plugin_error',
|
|
846
|
+
message: `gotry spawn error: ${e instanceof Error ? e.message : String(e)}`,
|
|
847
|
+
source: 'gotry-cli',
|
|
848
|
+
}, repoRoot)
|
|
849
|
+
} catch { /* diagnostics remain best-effort; fatal exit is preserved */ }
|
|
631
850
|
if (benchmarkEnvironmentConfig) {
|
|
632
851
|
reportBenchmarkFailure('child_spawn_failure')
|
|
633
852
|
return
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ChildProcess, SpawnOptions } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
export interface OwnedChild {
|
|
4
|
+
child: ChildProcess
|
|
5
|
+
groupPid: number | null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface OwnedChildCleanupResult {
|
|
9
|
+
childExited: boolean
|
|
10
|
+
groupEmpty: boolean
|
|
11
|
+
forced: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface OwnedChildCleanupOptions {
|
|
15
|
+
child?: ChildProcess | null
|
|
16
|
+
groupPid?: number | null
|
|
17
|
+
signal?: NodeJS.Signals
|
|
18
|
+
termGraceMs?: number
|
|
19
|
+
killWaitMs?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export declare function spawnOwnedChild(command: string, args: readonly string[], options?: SpawnOptions): OwnedChild
|
|
23
|
+
export declare function signalOwnedChild(child: ChildProcess | null | undefined, signal: NodeJS.Signals, groupPid?: number | null): void
|
|
24
|
+
export declare function isOwnedProcessGroupEmpty(groupPid: number | null): boolean
|
|
25
|
+
export declare function waitForOwnedChildQuiescence(child: ChildProcess, groupPid?: number | null, timeoutMs?: number): Promise<boolean>
|
|
26
|
+
export declare function terminateOwnedChild(options?: OwnedChildCleanupOptions): Promise<OwnedChildCleanupResult>
|
|
27
|
+
export declare const PROCESS_LIVENESS_BOUNDS: Readonly<{ termGraceMs: number; killWaitMs: number }>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GoTry-owned lifecycle for one externally spawned dsh child.
|
|
3
|
+
*
|
|
4
|
+
* The dsh CLI is an external process boundary. On POSIX we make it the
|
|
5
|
+
* leader of its own process group so a parent-only signal or a failed dsh
|
|
6
|
+
* child cannot strand descendants. This module owns only bounded signalling
|
|
7
|
+
* and quiescence observation; incident formatting remains in the existing
|
|
8
|
+
* GoTry incident writer.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn } from 'node:child_process'
|
|
12
|
+
|
|
13
|
+
const DEFAULT_TERM_GRACE_MS = 5_000
|
|
14
|
+
const DEFAULT_KILL_WAIT_MS = 1_000
|
|
15
|
+
const POLL_MS = 20
|
|
16
|
+
|
|
17
|
+
/** Spawn a child in a private process group where the host supports it. */
|
|
18
|
+
export function spawnOwnedChild(command, args, options = {}) {
|
|
19
|
+
const child = spawn(command, args, {
|
|
20
|
+
...options,
|
|
21
|
+
detached: process.platform !== 'win32',
|
|
22
|
+
})
|
|
23
|
+
return {
|
|
24
|
+
child,
|
|
25
|
+
groupPid: process.platform !== 'win32' ? (child.pid ?? null) : null,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Signal the child group, falling back to the direct child when necessary. */
|
|
30
|
+
export function signalOwnedChild(child, signal, groupPid = null) {
|
|
31
|
+
if (!child) return
|
|
32
|
+
if (process.platform !== 'win32' && groupPid !== null) {
|
|
33
|
+
try {
|
|
34
|
+
process.kill(-groupPid, signal)
|
|
35
|
+
return
|
|
36
|
+
} catch {
|
|
37
|
+
// The group can disappear between observation and delivery. Try the
|
|
38
|
+
// direct handle as a best-effort race fallback.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
try { child.kill(signal) } catch { /* exit races are contained */ }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function childExited(child) {
|
|
45
|
+
return child.exitCode !== null || child.signalCode !== null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function groupEmpty(groupPid) {
|
|
49
|
+
if (process.platform === 'win32' || groupPid === null) return true
|
|
50
|
+
try {
|
|
51
|
+
process.kill(-groupPid, 0)
|
|
52
|
+
return false
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return error?.code === 'ESRCH'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Expose the same group-emptiness fact used by the bounded cleanup proof. */
|
|
59
|
+
export function isOwnedProcessGroupEmpty(groupPid) {
|
|
60
|
+
return groupEmpty(groupPid)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Wait for the direct child and, on POSIX, its whole private group. */
|
|
64
|
+
export async function waitForOwnedChildQuiescence(child, groupPid = null, timeoutMs = DEFAULT_TERM_GRACE_MS) {
|
|
65
|
+
const deadline = Date.now() + timeoutMs
|
|
66
|
+
while (Date.now() <= deadline) {
|
|
67
|
+
if (childExited(child) && groupEmpty(groupPid)) return true
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_MS))
|
|
69
|
+
}
|
|
70
|
+
return childExited(child) && groupEmpty(groupPid)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Bounded TERM → KILL cleanup for one owned child group. It resolves only
|
|
75
|
+
* after the direct child and group are observed empty, or after the bound.
|
|
76
|
+
*/
|
|
77
|
+
export async function terminateOwnedChild({
|
|
78
|
+
child,
|
|
79
|
+
groupPid = null,
|
|
80
|
+
signal = 'SIGTERM',
|
|
81
|
+
termGraceMs = DEFAULT_TERM_GRACE_MS,
|
|
82
|
+
killWaitMs = DEFAULT_KILL_WAIT_MS,
|
|
83
|
+
} = {}) {
|
|
84
|
+
if (!child) return { childExited: true, groupEmpty: true, forced: false }
|
|
85
|
+
signalOwnedChild(child, signal, groupPid)
|
|
86
|
+
if (await waitForOwnedChildQuiescence(child, groupPid, termGraceMs)) {
|
|
87
|
+
return { childExited: true, groupEmpty: true, forced: false }
|
|
88
|
+
}
|
|
89
|
+
signalOwnedChild(child, 'SIGKILL', groupPid)
|
|
90
|
+
const empty = await waitForOwnedChildQuiescence(child, groupPid, killWaitMs)
|
|
91
|
+
return { childExited: childExited(child), groupEmpty: empty, forced: true }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const PROCESS_LIVENESS_BOUNDS = Object.freeze({
|
|
95
|
+
termGraceMs: DEFAULT_TERM_GRACE_MS,
|
|
96
|
+
killWaitMs: DEFAULT_KILL_WAIT_MS,
|
|
97
|
+
})
|
|
@@ -8,7 +8,7 @@ export type DshRuntime = {
|
|
|
8
8
|
source: 'root'
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export const REQUIRED_BENCHMARK_DSH_VERSION: '0.1.
|
|
11
|
+
export const REQUIRED_BENCHMARK_DSH_VERSION: '0.1.5-rc.1'
|
|
12
12
|
|
|
13
13
|
export function readDshPackage(bin: string): Omit<DshRuntime, 'source'> | null
|
|
14
14
|
export function resolveDshPackage(resolver: DshResolver): Omit<DshRuntime, 'source'> | null
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
3
|
|
|
4
|
-
export const REQUIRED_BENCHMARK_DSH_VERSION = '0.1.
|
|
4
|
+
export const REQUIRED_BENCHMARK_DSH_VERSION = '0.1.5-rc.1'
|
|
5
5
|
|
|
6
6
|
export function readDshPackage(bin) {
|
|
7
7
|
try {
|
|
@@ -34,7 +34,7 @@ export function selectDshRuntime({ rootResolver }) {
|
|
|
34
34
|
if (root) return { ...root, source: 'root' }
|
|
35
35
|
// D-27 清偿(issue #120):legacy vendored 回退路径已移除——该形态的 Node 兼容窗口
|
|
36
36
|
// 断裂(Round 5 起「不承诺可运行」),解析成功只会把「dsh 缺失」变成下游玄学失败。
|
|
37
|
-
// 唯一受支持形态 = root manifest / 依赖解析(
|
|
37
|
+
// 唯一受支持形态 = root manifest / 依赖解析(232 精确依赖闭包);找不到即 fail-closed,
|
|
38
38
|
// 由调用方给明确重装指引。benchmark 原本就禁用回退,语义不变(入参保留以稳调用面)。
|
|
39
39
|
return null
|
|
40
40
|
}
|