@danceiny/gotry 0.0.1-rc.8 → 0.2.0-rc.27
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 +172 -217
- package/README.zh-CN.md +222 -0
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-booking-copilot.js +53 -0
- package/bin/gotry-bootstrap.js +1744 -0
- package/bin/gotry-inner.js +733 -83
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +27 -0
- package/bin/gotry-runtime-resolution.js +49 -0
- package/bin/gotry.js +1 -1
- package/client/client.js +203 -0
- package/cordis.gotry-patch.yml +80 -49
- package/data/airline-airports.json +39 -0
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +101 -146
- package/dist/capabilities/anything.js +135 -94
- package/dist/capabilities/artifacts.js +370 -0
- package/dist/capabilities/channel-health.js +131 -0
- package/dist/capabilities/channel-registry.js +256 -0
- package/dist/capabilities/doctor.js +546 -0
- package/dist/capabilities/effect.js +394 -0
- package/dist/capabilities/external-event.js +149 -0
- package/dist/capabilities/fact-log.js +55 -0
- package/dist/capabilities/flyai-config.js +337 -0
- package/dist/capabilities/flyai.js +968 -0
- package/dist/capabilities/fx-contract.js +293 -0
- package/dist/capabilities/geo-atlas.js +246 -0
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +260 -123
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/itinerary-artifact.js +190 -0
- package/dist/capabilities/itinerary-deck-artifact.js +194 -0
- package/dist/capabilities/itinerary-deck-export.js +339 -0
- package/dist/capabilities/lavish-local.js +924 -0
- package/dist/capabilities/model-override.js +38 -0
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +191 -0
- package/dist/capabilities/session/action-cache.js +126 -0
- package/dist/capabilities/session/adapters/ctrip-flight.js +141 -0
- package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
- package/dist/capabilities/session/adapters/dida-portal.js +274 -0
- package/dist/capabilities/session/adapters/meituan-local.js +54 -0
- package/dist/capabilities/session/adapters/rail-12306.js +342 -0
- package/dist/capabilities/session/benchmark.js +189 -0
- package/dist/capabilities/session/bridge-ledger.js +224 -0
- package/dist/capabilities/session/extension-bridge.js +636 -0
- package/dist/capabilities/session/extension-channel.js +109 -0
- package/dist/capabilities/session/extension-distribution.js +230 -0
- package/dist/capabilities/session/extract.js +50 -0
- package/dist/capabilities/session/golden-score.js +114 -0
- package/dist/capabilities/session/health-watch.js +215 -0
- package/dist/capabilities/session/read-guard.js +107 -0
- package/dist/capabilities/session/static-flight-golden.js +150 -0
- package/dist/capabilities/session/transport.js +109 -0
- package/dist/capabilities/session/wizard.js +117 -0
- package/dist/capabilities/session-consent.js +178 -0
- package/dist/capabilities/session-login.js +170 -0
- package/dist/capabilities/session-search.js +686 -0
- package/dist/capabilities/spawn-bounded.js +203 -0
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +140 -0
- package/dist/capabilities/weather.js +243 -138
- package/dist/data/session-golden-20.json +25 -0
- package/dist/data/sf-golden-manifest.json +103 -0
- package/dist/data/sf-static-routes.json +91 -0
- package/dist/scripts/action-cache-tests.js +72 -0
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-error-tests.js +354 -0
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
- package/dist/scripts/anything-tests.js +116 -60
- package/dist/scripts/artifact-client-contract-tests.js +220 -0
- package/dist/scripts/artifacts-capability-tests.js +727 -0
- package/dist/scripts/async-collect.js +68 -10
- package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
- package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
- package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
- package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
- package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
- package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
- package/dist/scripts/booking-copilot-dsh-warmer-proof.js +123 -0
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
- package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
- package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
- package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
- package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
- package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
- 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 +125 -0
- package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
- package/dist/scripts/bootstrap-tests.js +1479 -0
- package/dist/scripts/bridge-events-tests.js +239 -0
- package/dist/scripts/bridge-ledger-tests.js +428 -0
- package/dist/scripts/build-changelog.js +238 -0
- package/dist/scripts/build-metrics-report.js +331 -0
- package/dist/scripts/changelog-tests.js +118 -0
- package/dist/scripts/channel-probe-tests.js +103 -0
- package/dist/scripts/channel-probe.js +169 -0
- package/dist/scripts/channel-registry-tests.js +208 -0
- package/dist/scripts/companion-tests.js +67 -0
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-runner-stop-tests.js +311 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +564 -0
- package/dist/scripts/dsh-artifact-e2e.js +279 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
- package/dist/scripts/dsh-runtime-closure.js +162 -0
- package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +539 -0
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +174 -0
- package/dist/scripts/evaluation-contract-tests.js +268 -0
- package/dist/scripts/extension-distribution-cli.js +39 -0
- package/dist/scripts/extension-distribution-tests.js +443 -0
- package/dist/scripts/extension-tests.js +988 -0
- package/dist/scripts/external-event-tests.js +350 -0
- package/dist/scripts/fact-gate-tests.js +869 -0
- 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-setup-tests.js +228 -0
- package/dist/scripts/flyai-setup-tool-tests.js +54 -0
- package/dist/scripts/flyai-tests.js +692 -0
- package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
- package/dist/scripts/fx-contract-tests.js +201 -0
- 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/geo-atlas-tests.js +283 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
- package/dist/scripts/gotry-try-demo-tests.js +79 -0
- package/dist/scripts/gotry-try-demo.js +49 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +159 -0
- 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 +121 -69
- package/dist/scripts/health-watch-cli.js +73 -0
- 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 +41 -0
- 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 +569 -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/issue436-persisted-routing-e2e.js +247 -0
- package/dist/scripts/itinerary-artifact-tests.js +368 -0
- package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
- package/dist/scripts/itinerary-deck-export-tests.js +552 -0
- package/dist/scripts/itinerary-deck-tests.js +435 -0
- package/dist/scripts/itinerary-html-tests.js +426 -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/lavish-local-tests.js +1226 -0
- package/dist/scripts/lavish-product-tools-tests.js +537 -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 +480 -0
- package/dist/scripts/ledger-workflow-crash.js +32 -0
- package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
- package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
- package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
- package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
- package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +49 -0
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +29 -31
- package/dist/scripts/memory-value-report.js +631 -0
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +151 -0
- package/dist/scripts/model-override-e2e.js +137 -0
- package/dist/scripts/nightly-evidence-tests.js +161 -0
- package/dist/scripts/nightly-evidence.js +286 -0
- package/dist/scripts/nudge-digest.js +43 -42
- package/dist/scripts/observability-tests.js +381 -0
- package/dist/scripts/onboarding-tests.js +161 -0
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +49 -0
- package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +340 -0
- package/dist/scripts/price-drift-watch.js +447 -0
- package/dist/scripts/probe-poi-tests.js +53 -45
- package/dist/scripts/product-metrics.js +483 -0
- package/dist/scripts/publish-preverify.js +110 -0
- package/dist/scripts/realtime-pricing-tests.js +76 -0
- package/dist/scripts/recall-tests.js +513 -0
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +18 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +31 -0
- package/dist/scripts/session-attach-poc.js +62 -0
- package/dist/scripts/session-benchmark.js +180 -0
- package/dist/scripts/session-dida-live-e2e.js +199 -0
- package/dist/scripts/session-extract-tests.js +60 -0
- package/dist/scripts/session-link-tests.js +478 -0
- package/dist/scripts/session-login.js +29 -0
- package/dist/scripts/session-tests.js +862 -0
- package/dist/scripts/session-zones-tests.js +358 -0
- package/dist/scripts/sf-live-benchmark.js +367 -0
- package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
- package/dist/scripts/sf-live-cli-tests.js +92 -0
- package/dist/scripts/sf-live-options.js +46 -0
- package/dist/scripts/sf-manifest-tests.js +30 -0
- package/dist/scripts/sf-manifest.js +33 -0
- package/dist/scripts/sf-soft-score-tests.js +67 -0
- package/dist/scripts/sf-summary-tests.js +324 -0
- package/dist/scripts/sf-summary.js +525 -0
- package/dist/scripts/share-tests.js +432 -0
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +28 -11
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +21 -0
- package/dist/scripts/smoke.js +703 -219
- package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +261 -0
- package/dist/scripts/state-cli.js +585 -0
- package/dist/scripts/state-repair-cli.js +66 -0
- package/dist/scripts/state-repair-tests.js +140 -0
- package/dist/scripts/static-golden-tests.js +165 -0
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +59 -0
- package/dist/scripts/turn-handoff-collect-tests.js +169 -0
- package/dist/scripts/turn-handoff-collect.js +161 -0
- package/dist/scripts/turn-policy-tests.js +55 -0
- package/dist/scripts/typed-contract-canary.js +176 -0
- 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 +62 -0
- package/dist/scripts/weather-tests.js +276 -41
- package/dist/scripts/wish-channel-gate-tests.js +54 -0
- 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 +41 -0
- package/dist/src/artifact-gate.js +829 -0
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +92 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +551 -0
- package/dist/src/benchmark-agent-conformance.js +578 -0
- package/dist/src/benchmark-environment-bridge.js +515 -0
- package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
- package/dist/src/benchmark-tool-isolation.js +151 -0
- package/dist/src/bookable-facts.js +533 -0
- package/dist/src/booking-saga.js +97 -0
- package/dist/src/booking-surface/availability-policy.js +697 -0
- package/dist/src/booking-surface/booking-intent.js +43 -0
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/canonical-schema.js +116 -0
- package/dist/src/booking-surface/contracts.js +75 -0
- package/dist/src/booking-surface/dsh-planner.js +1854 -0
- package/dist/src/booking-surface/dsh-plugin.js +378 -0
- package/dist/src/booking-surface/dsh-worker.js +98 -0
- package/dist/src/booking-surface/error-codes.js +231 -0
- package/dist/src/booking-surface/index.js +9 -0
- package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
- package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
- package/dist/src/booking-surface/profile.js +24 -0
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +1968 -0
- package/dist/src/booking-surface/server.js +674 -0
- package/dist/src/booking-surface/startup.js +168 -0
- package/dist/src/booking-surface/validation.js +261 -0
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +86 -0
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +97 -111
- package/dist/src/engine.js +276 -193
- package/dist/src/evaluation-cadence.js +97 -0
- package/dist/src/evaluation-contracts.js +283 -0
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/flyai-setup-tool.js +70 -0
- package/dist/src/gotry-backend.js +125 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +88 -0
- package/dist/src/index.js +2305 -731
- package/dist/src/itinerary-deck.js +139 -0
- package/dist/src/itinerary-doc-shared.js +661 -0
- package/dist/src/itinerary-html.js +131 -0
- package/dist/src/journey.js +155 -108
- package/dist/src/lavish-tools.js +400 -0
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +358 -162
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +47 -0
- 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 +103 -0
- package/dist/src/recall/card.js +74 -0
- package/dist/src/recall/evaluator.js +140 -0
- package/dist/src/recall/integration.js +17 -0
- package/dist/src/recall/tick.js +166 -0
- package/dist/src/session-link/plan-it-action.js +48 -0
- package/dist/src/session-link/session-link.js +248 -0
- package/dist/src/session-zones.js +507 -0
- package/dist/src/share/adapters.js +51 -0
- package/dist/src/share/share-consent.js +76 -0
- package/dist/src/share/share-deck.js +58 -0
- package/dist/src/share/share-token.js +146 -0
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +767 -0
- package/dist/src/state-repair.js +299 -0
- 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 +59 -0
- package/dist/src/turn-deadline.js +287 -0
- package/dist/src/turn-policy.js +118 -0
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +538 -360
- package/dist/src/wish-pool.js +42 -12
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +284 -0
- package/extension/README.md +21 -0
- package/extension/README.zh-CN.md +21 -0
- package/extension/background.js +463 -0
- package/extension/content-bridge.js +270 -0
- package/extension/content-main.js +111 -0
- package/extension/manifest.json +60 -0
- package/package.json +407 -14
- package/schemas/booking.intent.schema.json +193 -0
- package/schemas/booking.surface.schema.json +2622 -0
- package/ts/capabilities/agent-reach-deep.ts +114 -0
- package/ts/capabilities/agent-reach.ts +198 -0
- package/ts/capabilities/anything.ts +255 -0
- package/ts/capabilities/artifacts.ts +428 -0
- package/ts/capabilities/fact-log.ts +54 -0
- package/ts/capabilities/flyai-config.ts +388 -0
- package/ts/capabilities/flyai.ts +1274 -0
- package/ts/capabilities/hbcli.ts +253 -71
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/itinerary-artifact.ts +214 -0
- package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
- package/ts/capabilities/itinerary-deck-export.ts +378 -0
- package/ts/capabilities/opensky.ts +121 -0
- package/ts/capabilities/session/action-cache.ts +148 -0
- package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
- package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
- package/ts/capabilities/session/benchmark.ts +276 -0
- package/ts/capabilities/session/bridge-ledger.ts +333 -0
- package/ts/capabilities/session/extension-bridge.ts +794 -0
- package/ts/capabilities/session/extension-channel.ts +144 -0
- package/ts/capabilities/session/extension-distribution.ts +266 -0
- package/ts/capabilities/session/extract.ts +54 -0
- package/ts/capabilities/session/golden-score.ts +139 -0
- package/ts/capabilities/session/health-watch.ts +264 -0
- package/ts/capabilities/session/read-guard.ts +141 -0
- package/ts/capabilities/session/static-flight-golden.ts +209 -0
- package/ts/capabilities/session/transport.ts +146 -0
- package/ts/capabilities/session/wizard.ts +140 -0
- package/ts/capabilities/session-consent.ts +211 -0
- package/ts/capabilities/session-login.ts +208 -0
- package/ts/capabilities/session-search.ts +893 -0
- package/ts/capabilities/weather.ts +330 -0
- package/ts/dsh-runtime/vendor/README.md +68 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
- package/ts/package.json +296 -3
- package/ts/scripts/async-collect.ts +91 -0
- package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
- package/ts/scripts/skeleton-check.ts +1 -1
- package/ts/scripts/skeleton-integration-test.ts +23 -1
- package/ts/scripts/state-cli.ts +568 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +991 -0
- package/ts/src/benchmark-agent-conformance.ts +619 -0
- package/ts/src/benchmark-environment-bridge.ts +538 -0
- package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
- package/ts/src/benchmark-tool-isolation.ts +166 -0
- package/ts/src/bookable-facts.ts +837 -0
- package/ts/src/booking-saga.ts +133 -0
- package/ts/src/booking-surface/availability-policy.ts +522 -0
- package/ts/src/booking-surface/booking-intent.ts +86 -0
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/canonical-schema.js +116 -0
- package/ts/src/booking-surface/contracts.ts +475 -0
- package/ts/src/booking-surface/dsh-planner.ts +1949 -0
- package/ts/src/booking-surface/dsh-plugin.js +378 -0
- package/ts/src/booking-surface/dsh-worker.ts +110 -0
- package/ts/src/booking-surface/error-codes.ts +241 -0
- package/ts/src/booking-surface/index.ts +9 -0
- package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
- package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
- package/ts/src/booking-surface/profile.ts +42 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/runtime.ts +1793 -0
- package/ts/src/booking-surface/server.ts +733 -0
- package/ts/src/booking-surface/startup.ts +201 -0
- package/ts/src/booking-surface/validation.ts +217 -0
- package/ts/src/companions.ts +113 -0
- package/ts/src/contracts.ts +13 -3
- package/ts/src/dsh-llm.ts +44 -11
- package/ts/src/flyai-setup-tool.ts +77 -0
- package/ts/src/index.ts +2107 -224
- package/ts/src/itinerary-deck.ts +189 -0
- package/ts/src/itinerary-doc-shared.ts +741 -0
- package/ts/src/itinerary-html.ts +171 -0
- package/ts/src/loop.ts +278 -40
- package/ts/src/memory-capture.ts +283 -0
- package/ts/src/memory-decay.ts +55 -0
- package/ts/src/memory-utility.ts +70 -0
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/recall/card.ts +106 -0
- package/ts/src/recall/evaluator.ts +170 -0
- package/ts/src/recall/integration.ts +35 -0
- package/ts/src/recall/tick.ts +213 -0
- package/ts/src/session-link/plan-it-action.ts +78 -0
- package/ts/src/session-link/session-link.ts +275 -0
- package/ts/src/share/adapters.ts +93 -0
- package/ts/src/share/share-consent.ts +105 -0
- package/ts/src/share/share-deck.ts +85 -0
- package/ts/src/share/share-token.ts +166 -0
- package/ts/src/slot-spec.ts +172 -0
- package/ts/src/state-ledger.ts +912 -0
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +338 -0
- package/ts/src/tool-packet.ts +32 -0
- package/ts/src/travel-slots.ts +219 -0
- package/ts/src/travel-timeline.ts +83 -0
- package/ts/src/turn-deadline.ts +366 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/unified.ts +360 -89
- package/ts/src/wish-pool.ts +79 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turn-handoff 收集器闭环测试(ADR-24 v2 收集半段)。
|
|
3
|
+
* 假 planner(node 脚本)驱动:成功/失败/空输出/幂等复诵零重算/--all/
|
|
4
|
+
* 缺单;并断言递归防护真的传进了子进程(GOTRY_HANDOFF_CHILD=1 且父进程
|
|
5
|
+
* 的数值 pin/工单根被清除)。全程隔离 stateRoot,不触真实 gotry-state。
|
|
6
|
+
*/
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { execFileSync } from 'node:child_process';
|
|
9
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { listTurnHandoffTickets, settleTurnHandoffTicket, writeTurnHandoffTicket } from '../src/turn-deadline.js';
|
|
13
|
+
const TS_DIR = join(import.meta.dirname, '..');
|
|
14
|
+
const TSX = join(TS_DIR, 'node_modules', 'tsx', 'dist', 'cli.mjs');
|
|
15
|
+
function runCollector(args, env = {}) {
|
|
16
|
+
// 用 execFileSync 的同步抓取替代 spawn 手工聚合,行为等价且断言更直接。
|
|
17
|
+
try {
|
|
18
|
+
const stdout = execFileSync(process.execPath, [TSX, 'scripts/turn-handoff-collect.ts', ...args], {
|
|
19
|
+
cwd: TS_DIR,
|
|
20
|
+
env: { ...process.env, ...env },
|
|
21
|
+
encoding: 'utf-8',
|
|
22
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
23
|
+
});
|
|
24
|
+
return { status: 0, stdout, stderr: '' };
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
const e = error;
|
|
28
|
+
return { status: e.status ?? null, stdout: e.stdout ?? '', stderr: e.stderr ?? '' };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const root = mkdtempSync(join(tmpdir(), 'gotry-handoff-collect-'));
|
|
32
|
+
const planners = mkdtempSync(join(tmpdir(), 'gotry-handoff-planners-'));
|
|
33
|
+
const plannerCalls = join(planners, 'calls.log');
|
|
34
|
+
// ok:记录调用环境(递归防护证据)→ 输出交付物
|
|
35
|
+
const okPlanner = join(planners, 'ok.mjs');
|
|
36
|
+
writeFileSync(okPlanner, `import { appendFileSync } from 'node:fs'
|
|
37
|
+
appendFileSync(${JSON.stringify(plannerCalls)}, [
|
|
38
|
+
'call child=' + process.env.GOTRY_HANDOFF_CHILD,
|
|
39
|
+
'pin=' + (process.env.GOTRY_TURN_DEADLINE_SOFT_MS ?? 'CLEARED'),
|
|
40
|
+
'root=' + (process.env.GOTRY_TURN_HANDOFF_ROOT ?? 'CLEARED'),
|
|
41
|
+
'msg=' + process.argv[2]?.slice(0, 12),
|
|
42
|
+
].join(' | ') + '\\n')
|
|
43
|
+
console.log('# 规划结果\\n方案A:9.26 DXB→CAN,10.3 衡阳婚礼,10.5 返程')
|
|
44
|
+
`);
|
|
45
|
+
const failPlanner = join(planners, 'fail.mjs');
|
|
46
|
+
writeFileSync(failPlanner, `import { appendFileSync } from 'node:fs'
|
|
47
|
+
appendFileSync(${JSON.stringify(plannerCalls)}, 'call fail\\n')
|
|
48
|
+
process.exit(3)
|
|
49
|
+
`);
|
|
50
|
+
const emptyPlanner = join(planners, 'empty.mjs');
|
|
51
|
+
writeFileSync(emptyPlanner, `import { appendFileSync } from 'node:fs'
|
|
52
|
+
appendFileSync(${JSON.stringify(plannerCalls)}, 'call empty\\n')
|
|
53
|
+
`);
|
|
54
|
+
const calls = () => (existsSync(plannerCalls) ? readFileSync(plannerCalls, 'utf-8').trim().split('\n') : []);
|
|
55
|
+
try {
|
|
56
|
+
// 1) 缺单 → exit 1
|
|
57
|
+
assert.equal(runCollector(['th-missing', root]).status, 1);
|
|
58
|
+
// 2) 成功回收:planner 恰跑一次,工单 settled,交付物含方案,终态 JSON
|
|
59
|
+
const t1 = await writeTurnHandoffTicket(root, '帮我规划10.3衡阳婚礼十几天行程');
|
|
60
|
+
{
|
|
61
|
+
const r = runCollector([t1.id, root], { GOTRY_HANDOFF_PLANNER_BIN: okPlanner });
|
|
62
|
+
assert.equal(r.status, 0, `stderr=${r.stderr}`);
|
|
63
|
+
const terminal = JSON.parse(r.stdout.trim().split('\n').at(-1));
|
|
64
|
+
assert.equal(terminal['schema'], 'gotry_turn_handoff_terminal.v1');
|
|
65
|
+
assert.equal(terminal['status'], 'succeeded');
|
|
66
|
+
assert.ok(String(terminal['deliverable_path']).includes(`${t1.id}.deliverable.md`));
|
|
67
|
+
}
|
|
68
|
+
const t1After = JSON.parse(readFileSync(join(root, 'gotry-state', 'turn-handoffs', `${t1.id}.json`), 'utf8'));
|
|
69
|
+
assert.equal(t1After['status'], 'settled');
|
|
70
|
+
assert.ok(t1After['settledAt']);
|
|
71
|
+
const deliverable1 = readFileSync(join(root, 'gotry-state', 'turn-handoffs', `${t1.id}.deliverable.md`), 'utf8');
|
|
72
|
+
assert.ok(deliverable1.includes('方案A') && deliverable1.includes(t1.id));
|
|
73
|
+
// 3) 幂等复诵:重跑 exit 0,同交付物,planner 零重算
|
|
74
|
+
{
|
|
75
|
+
const before = calls().length;
|
|
76
|
+
const r = runCollector([t1.id, root], { GOTRY_HANDOFF_PLANNER_BIN: okPlanner });
|
|
77
|
+
assert.equal(r.status, 0);
|
|
78
|
+
const terminal = JSON.parse(r.stdout.trim().split('\n').at(-1));
|
|
79
|
+
assert.equal(terminal['status'], 'succeeded');
|
|
80
|
+
assert.equal(calls().length, before, 'settled ticket must not re-run the planner');
|
|
81
|
+
}
|
|
82
|
+
// 4) 递归防护证据:ok planner 记录的子环境
|
|
83
|
+
{
|
|
84
|
+
const last = [...calls()].reverse().find(l => l.includes('child='));
|
|
85
|
+
assert.ok(last, 'planner ran');
|
|
86
|
+
assert.ok(last.includes('child=1'), `GOTRY_HANDOFF_CHILD must be 1: ${last}`);
|
|
87
|
+
assert.ok(last.includes('pin=CLEARED'), `parent deadline pins must be cleared: ${last}`);
|
|
88
|
+
assert.ok(last.includes('root=CLEARED'), `parent handoff root must be cleared: ${last}`);
|
|
89
|
+
assert.ok(last.includes('msg=帮我规划10.3衡阳婚礼'), 'planner receives the verbatim user ask');
|
|
90
|
+
}
|
|
91
|
+
// 5) 失败回收:exit 2,工单 failed + error,交付物是诚实失败说明
|
|
92
|
+
const t2 = await writeTurnHandoffTicket(root, '另一趟失败的规划');
|
|
93
|
+
{
|
|
94
|
+
const r = runCollector([t2.id, root], { GOTRY_HANDOFF_PLANNER_BIN: failPlanner });
|
|
95
|
+
assert.equal(r.status, 2);
|
|
96
|
+
const terminal = JSON.parse(r.stdout.trim().split('\n').at(-1));
|
|
97
|
+
assert.equal(terminal['status'], 'failed');
|
|
98
|
+
assert.ok(String(terminal['error']).startsWith('planner exited 3'), `error shape: ${terminal['error']}`);
|
|
99
|
+
}
|
|
100
|
+
const t2After = JSON.parse(readFileSync(join(root, 'gotry-state', 'turn-handoffs', `${t2.id}.json`), 'utf8'));
|
|
101
|
+
assert.equal(t2After['status'], 'failed');
|
|
102
|
+
assert.ok(String(t2After['error']).startsWith('planner exited 3'));
|
|
103
|
+
const deliverable2 = readFileSync(join(root, 'gotry-state', 'turn-handoffs', `${t2.id}.deliverable.md`), 'utf8');
|
|
104
|
+
assert.ok(deliverable2.includes('没有完成') && deliverable2.includes('planner exited 3'), 'honest failure note');
|
|
105
|
+
// 失败复诵:exit 2,零重算
|
|
106
|
+
{
|
|
107
|
+
const before = calls().length;
|
|
108
|
+
assert.equal(runCollector([t2.id, root], { GOTRY_HANDOFF_PLANNER_BIN: failPlanner }).status, 2);
|
|
109
|
+
assert.equal(calls().length, before);
|
|
110
|
+
}
|
|
111
|
+
// 6) 空输出 → failed
|
|
112
|
+
const t3 = await writeTurnHandoffTicket(root, '空输出的规划');
|
|
113
|
+
{
|
|
114
|
+
const r = runCollector([t3.id, root], { GOTRY_HANDOFF_PLANNER_BIN: emptyPlanner });
|
|
115
|
+
assert.equal(r.status, 2);
|
|
116
|
+
const terminal = JSON.parse(r.stdout.trim().split('\n').at(-1));
|
|
117
|
+
assert.equal(terminal['error'], 'planner produced empty output');
|
|
118
|
+
}
|
|
119
|
+
// 7) --all:只收 open(t4/t5),已终态的 t1/t2/t3 零重算。
|
|
120
|
+
// t4/t5 同毫秒创建会让「最新优先」断言 flaky(requestedAt 毫秒精度);
|
|
121
|
+
// 把 t5 钉到 now+5s,排序严格确定。
|
|
122
|
+
const t4 = await writeTurnHandoffTicket(root, '批量规划一');
|
|
123
|
+
const t5 = await writeTurnHandoffTicket(root, '批量规划二');
|
|
124
|
+
{
|
|
125
|
+
const dir = join(root, 'gotry-state', 'turn-handoffs');
|
|
126
|
+
const t5json = JSON.parse(readFileSync(join(dir, `${t5.id}.json`), 'utf8'));
|
|
127
|
+
writeFileSync(join(dir, `${t5.id}.json`), JSON.stringify({
|
|
128
|
+
...t5json,
|
|
129
|
+
requestedAt: new Date(Date.now() + 5_000).toISOString(),
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
{
|
|
133
|
+
const before = calls().length;
|
|
134
|
+
const r = runCollector(['--all', root], { GOTRY_HANDOFF_PLANNER_BIN: okPlanner });
|
|
135
|
+
assert.equal(r.status, 0, `stderr=${r.stderr}`);
|
|
136
|
+
const terminal = JSON.parse(r.stdout.trim().split('\n').at(-1));
|
|
137
|
+
assert.equal(terminal['status'], 'succeeded');
|
|
138
|
+
assert.equal(calls().length, before + 2, 'exactly the two open tickets collected');
|
|
139
|
+
}
|
|
140
|
+
// 8) --all 无 open → exit 0
|
|
141
|
+
assert.equal(runCollector(['--all', root], { GOTRY_HANDOFF_PLANNER_BIN: okPlanner }).status, 0);
|
|
142
|
+
// 9) 复访视图:倒序 + settled 摘录 + failed 错误
|
|
143
|
+
{
|
|
144
|
+
const views = await listTurnHandoffTickets(root);
|
|
145
|
+
assert.equal(views.length, 5);
|
|
146
|
+
assert.equal(views[0].id, t5.id, 'newest first');
|
|
147
|
+
const settled = views.find(v => v.id === t1.id);
|
|
148
|
+
assert.equal(settled.status, 'settled');
|
|
149
|
+
assert.ok(settled.deliverableExcerpt?.includes('方案A'));
|
|
150
|
+
const failed = views.find(v => v.id === t2.id);
|
|
151
|
+
assert.equal(failed.status, 'failed');
|
|
152
|
+
assert.ok(failed.error?.startsWith('planner exited 3'));
|
|
153
|
+
}
|
|
154
|
+
console.log('turn handoff collector tests: OK (settle/fail/empty/idempotent replay/--all/recursion guard env/revisit views)');
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
rmSync(root, { recursive: true, force: true });
|
|
158
|
+
rmSync(planners, { recursive: true, force: true });
|
|
159
|
+
}
|
|
160
|
+
// settleTurnHandoffTicket 直接契约(不经 collector):原子写 + 字段完整
|
|
161
|
+
{
|
|
162
|
+
const r = mkdtempSync(join(tmpdir(), 'gotry-handoff-settle-'));
|
|
163
|
+
const t = await writeTurnHandoffTicket(r, '直调结算');
|
|
164
|
+
const { deliverablePath } = await settleTurnHandoffTicket(r, t, 'settled', '交付内容');
|
|
165
|
+
assert.ok(existsSync(deliverablePath));
|
|
166
|
+
const views = await listTurnHandoffTickets(r);
|
|
167
|
+
assert.equal(views[0].deliverableExcerpt, '交付内容');
|
|
168
|
+
rmSync(r, { recursive: true, force: true });
|
|
169
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 后台深度规划工单收集器(ADR-24 v2 收集闭环;async-collect 的 turn-handoff 对位)。
|
|
3
|
+
*
|
|
4
|
+
* 「一小时后回来看看」的后台半段:加载 open 工单 → 以 GOTRY_HANDOFF_CHILD=1
|
|
5
|
+
* 派生 headless 规划会话(唯一出口 converge,不回递归)→ 捕获最终答复为
|
|
6
|
+
* 交付物 → settleTurnHandoffTicket 结算(交付物 .deliverable.md + 工单状态
|
|
7
|
+
* 原子更新)。终态幂等:已 settled/failed 的工单复诵交付物与终态 JSON,零
|
|
8
|
+
* 重算、零再花。最后一行固定输出 gotry_turn_handoff_terminal.v1 JSON;
|
|
9
|
+
* succeeded exit 0,failed exit 2。
|
|
10
|
+
*
|
|
11
|
+
* 运行(在 ts/ 下):
|
|
12
|
+
* npx tsx scripts/turn-handoff-collect.ts <ticketId> [stateRoot]
|
|
13
|
+
* npx tsx scripts/turn-handoff-collect.ts --all [stateRoot]
|
|
14
|
+
* 环境变量:
|
|
15
|
+
* GOTRY_HANDOFF_PLANNER_BIN 规划器二进制(默认 <repo>/bin/gotry-inner.js)
|
|
16
|
+
* GOTRY_HANDOFF_PLANNER_TIMEOUT_MS 规划器硬上限(默认 900_000)
|
|
17
|
+
*
|
|
18
|
+
* v1 诚实边界:工单只携带用户原文,规划会话是全新上下文(fresh cwd/DSH_HOME)
|
|
19
|
+
* ——原会话中已读的工作区文件/日历结论不随单迁移,由规划器按需重取。
|
|
20
|
+
*/
|
|
21
|
+
import { spawn } from 'node:child_process';
|
|
22
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
23
|
+
import { tmpdir } from 'node:os';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import { listTurnHandoffTickets, settleTurnHandoffTicket, TURN_HANDOFF_SCHEMA, } from '../src/turn-deadline.js';
|
|
26
|
+
const TURN_HANDOFF_TERMINAL_SCHEMA = 'gotry_turn_handoff_terminal.v1';
|
|
27
|
+
const ROOT = join(import.meta.dirname, '..', '..');
|
|
28
|
+
function emitTerminal(outcome) {
|
|
29
|
+
console.log(JSON.stringify(outcome));
|
|
30
|
+
process.exit(outcome.status === 'succeeded' ? 0 : 2);
|
|
31
|
+
}
|
|
32
|
+
function loadTicket(stateRoot, ticketId) {
|
|
33
|
+
try {
|
|
34
|
+
const path = join(stateRoot === '.' ? process.cwd() : stateRoot, 'gotry-state', 'turn-handoffs', `${ticketId}.json`);
|
|
35
|
+
const ticket = JSON.parse(readFileSync(path, 'utf-8'));
|
|
36
|
+
return ticket?.schema === TURN_HANDOFF_SCHEMA ? ticket : null;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function replay(ticket, stateRoot) {
|
|
43
|
+
const dir = join(stateRoot === '.' ? process.cwd() : stateRoot, 'gotry-state', 'turn-handoffs');
|
|
44
|
+
emitTerminal({
|
|
45
|
+
schema: TURN_HANDOFF_TERMINAL_SCHEMA,
|
|
46
|
+
ticket_id: ticket.id,
|
|
47
|
+
status: ticket.status === 'settled' ? 'succeeded' : 'failed',
|
|
48
|
+
...(ticket.deliverableFile ? { deliverable_path: join(dir, ticket.deliverableFile) } : {}),
|
|
49
|
+
...(ticket.error ? { error: ticket.error } : {}),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function runPlanner(ticket) {
|
|
53
|
+
const plannerBin = process.env.GOTRY_HANDOFF_PLANNER_BIN ?? join(ROOT, 'bin', 'gotry-inner.js');
|
|
54
|
+
if (!existsSync(plannerBin)) {
|
|
55
|
+
return { ok: false, output: '', error: `planner binary unavailable: ${plannerBin}` };
|
|
56
|
+
}
|
|
57
|
+
const timeoutMs = Number.parseInt(process.env.GOTRY_HANDOFF_PLANNER_TIMEOUT_MS ?? '', 10) || 900_000;
|
|
58
|
+
const dshHome = mkdtempSync(join(tmpdir(), 'gotry-handoff-child-dsh-'));
|
|
59
|
+
const childCwd = mkdtempSync(join(tmpdir(), 'gotry-handoff-child-cwd-'));
|
|
60
|
+
try {
|
|
61
|
+
const childEnv = { ...process.env };
|
|
62
|
+
childEnv.DSH_HOME = dshHome;
|
|
63
|
+
// 递归防护(核心):子会话唯一出口 converge + 长 leash;同时清掉父进程的
|
|
64
|
+
// 数值 pin 与工单根,子会话不得再 handoff、也不受测试阈值影响。
|
|
65
|
+
childEnv.GOTRY_HANDOFF_CHILD = '1';
|
|
66
|
+
delete childEnv.GOTRY_TURN_DEADLINE_SOFT_MS;
|
|
67
|
+
delete childEnv.GOTRY_TURN_DEADLINE_HARD_MS;
|
|
68
|
+
delete childEnv.GOTRY_TURN_HANDOFF_ROOT;
|
|
69
|
+
// .js/.mjs/.cjs 经当前 node 派生(测试假 planner 无执行位也成立);
|
|
70
|
+
// 其余(gotry-inner.js 之外的 .bin shim、全局命令)直接执行。
|
|
71
|
+
const isJsScript = /\.(js|mjs|cjs)$/.test(plannerBin);
|
|
72
|
+
const child = isJsScript
|
|
73
|
+
? spawn(process.execPath, [plannerBin, ticket.userMessage], { cwd: childCwd, env: childEnv, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
74
|
+
: spawn(plannerBin, [ticket.userMessage], { cwd: childCwd, env: childEnv, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
75
|
+
let output = '';
|
|
76
|
+
child.stdout.on('data', chunk => { output += chunk.toString(); });
|
|
77
|
+
child.stderr.on('data', chunk => { output += chunk.toString(); });
|
|
78
|
+
const code = await new Promise(resolve => {
|
|
79
|
+
const timer = setTimeout(() => { child.kill('SIGKILL'); resolve(null); }, timeoutMs);
|
|
80
|
+
child.once('exit', c => { clearTimeout(timer); resolve(c); });
|
|
81
|
+
});
|
|
82
|
+
if (code === null)
|
|
83
|
+
return { ok: false, output: '', error: `planner timed out after ${timeoutMs}ms` };
|
|
84
|
+
if (code !== 0)
|
|
85
|
+
return { ok: false, output: '', error: `planner exited ${code}: ${output.trim().slice(-300)}` };
|
|
86
|
+
const deliverable = output.trim();
|
|
87
|
+
if (!deliverable)
|
|
88
|
+
return { ok: false, output: '', error: 'planner produced empty output' };
|
|
89
|
+
return { ok: true, output: deliverable };
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
93
|
+
rmSync(dshHome, { recursive: true, force: true });
|
|
94
|
+
rmSync(childCwd, { recursive: true, force: true });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function collectOne(stateRoot, ticketId) {
|
|
98
|
+
const ticket = loadTicket(stateRoot, ticketId);
|
|
99
|
+
if (!ticket) {
|
|
100
|
+
console.error(`工单 ${ticketId} 不存在(${stateRoot}/gotry-state/turn-handoffs/)`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
if (ticket.status !== 'open') {
|
|
104
|
+
console.error(`工单 ${ticketId} 已是终态 ${ticket.status},复诵不重算:`);
|
|
105
|
+
replay(ticket, stateRoot);
|
|
106
|
+
}
|
|
107
|
+
const result = await runPlanner(ticket);
|
|
108
|
+
const deliverable = result.ok
|
|
109
|
+
? `# 后台深度规划交付:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n${result.output}`
|
|
110
|
+
: `# 后台深度规划失败:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n未产出交付物:${result.error ?? '未知原因'}。这趟规划没有完成——请重新发起,失败原因如上,无部分结果可交付。`;
|
|
111
|
+
const { deliverablePath } = await settleTurnHandoffTicket(stateRoot, ticket, result.ok ? 'settled' : 'failed', deliverable, result.ok ? undefined : result.error);
|
|
112
|
+
console.error(`工单 ${ticket.id} ${result.ok ? '已交付' : '失败'}:${deliverablePath}`);
|
|
113
|
+
emitTerminal({
|
|
114
|
+
schema: TURN_HANDOFF_TERMINAL_SCHEMA,
|
|
115
|
+
ticket_id: ticket.id,
|
|
116
|
+
status: result.ok ? 'succeeded' : 'failed',
|
|
117
|
+
...(result.ok
|
|
118
|
+
? { deliverable_path: deliverablePath }
|
|
119
|
+
: { error: result.error ?? 'unknown' }),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async function collectAll(stateRoot) {
|
|
123
|
+
const open = (await listTurnHandoffTickets(stateRoot)).filter(t => t.status === 'open');
|
|
124
|
+
if (open.length === 0) {
|
|
125
|
+
console.error('无待回收的后台规划工单(open=0)');
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
let failed = 0;
|
|
129
|
+
for (const view of open) {
|
|
130
|
+
const ticket = loadTicket(stateRoot, view.id);
|
|
131
|
+
if (!ticket || ticket.status !== 'open')
|
|
132
|
+
continue;
|
|
133
|
+
const result = await runPlanner(ticket);
|
|
134
|
+
const deliverable = result.ok
|
|
135
|
+
? `# 后台深度规划交付:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n${result.output}`
|
|
136
|
+
: `# 后台深度规划失败:${ticket.objective.slice(0, 60)}\n(工单 ${ticket.id},原请求 ${ticket.requestedAt})\n\n未产出交付物:${result.error ?? '未知原因'}。这趟规划没有完成——请重新发起,失败原因如上,无部分结果可交付。`;
|
|
137
|
+
const { deliverablePath } = await settleTurnHandoffTicket(stateRoot, ticket, result.ok ? 'settled' : 'failed', deliverable, result.ok ? undefined : result.error);
|
|
138
|
+
console.error(`工单 ${ticket.id} ${result.ok ? '已交付' : '失败'}:${deliverablePath}`);
|
|
139
|
+
if (!result.ok)
|
|
140
|
+
failed += 1;
|
|
141
|
+
}
|
|
142
|
+
console.log(JSON.stringify({
|
|
143
|
+
schema: TURN_HANDOFF_TERMINAL_SCHEMA,
|
|
144
|
+
ticket_id: `--all(${open.length})`,
|
|
145
|
+
status: failed === 0 ? 'succeeded' : 'failed',
|
|
146
|
+
error: failed === 0 ? undefined : `${failed}/${open.length} 张工单回收失败`,
|
|
147
|
+
}));
|
|
148
|
+
process.exit(failed === 0 ? 0 : 2);
|
|
149
|
+
}
|
|
150
|
+
const [target, stateRootArg] = process.argv.slice(2);
|
|
151
|
+
const stateRoot = stateRootArg ?? '.';
|
|
152
|
+
if (target === '--all') {
|
|
153
|
+
await collectAll(stateRoot);
|
|
154
|
+
}
|
|
155
|
+
else if (target) {
|
|
156
|
+
await collectOne(stateRoot, target);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.error('用法:npx tsx scripts/turn-handoff-collect.ts <ticketId|--all> [stateRoot]');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn routing & policy table tests (ADR-24 v2).
|
|
3
|
+
* 分类器是纯函数(Tier 0,零 LLM 零 IO),表测锁定 v1 规则面;
|
|
4
|
+
* 首条 fixture 是 2026-09-02 失败轨迹的用户原文——canonical 深规划
|
|
5
|
+
* case 必须命中 deep-planning,这是整个重设计的回归锚点。
|
|
6
|
+
*/
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { classifyTurn, extractTurnSignals, TURN_POLICIES, turnPolicyFor, } from '../src/turn-policy.js';
|
|
9
|
+
// ---- 分类表(规则面快照) ---------------------------------------------------
|
|
10
|
+
// 失败轨迹原文:13 天行程 + 10.3 婚礼 + 请假 + IRW → 必须转后台。
|
|
11
|
+
const TRAJECTORY_MESSAGE = '2026年我还有6天IRW额度 ,我准备再请几天假,在国内待个十几天。'
|
|
12
|
+
+ '其中10.3要去湖南衡阳参加同学婚礼。其他我没有特别的要求,请根据我的实际情况,偏好,外部环境等因素,给我安排行程';
|
|
13
|
+
assert.equal(classifyTurn(TRAJECTORY_MESSAGE), 'deep-planning');
|
|
14
|
+
assert.equal(classifyTurn('9.26到10.12在国内玩,中间要参加同学婚礼'), 'deep-planning'); // 双绝对日期+约束,跨度16天
|
|
15
|
+
assert.equal(classifyTurn('国庆请假带父母去新疆,大概十几天'), 'deep-planning'); // 跨度词+双约束
|
|
16
|
+
assert.equal(classifyTurn('帮我查一下明天深圳的天气'), 'quick');
|
|
17
|
+
assert.equal(classifyTurn('你好'), 'quick');
|
|
18
|
+
assert.equal(classifyTurn('帮我看看10.1去杭州的高铁票'), 'sync-planning'); // 单日期零约束 → 默认中间档
|
|
19
|
+
assert.equal(classifyTurn('3天后出发,帮我查下天气'), 'sync-planning'); // 短跨度词 → 不进 quick 也不进 deep
|
|
20
|
+
assert.equal(classifyTurn('把这段翻译成英文:会议纪要如下……省略若干字'.repeat(3)), 'sync-planning'); // 长但零信号
|
|
21
|
+
// ---- 信号提取 ---------------------------------------------------------------
|
|
22
|
+
const full = extractTurnSignals('2026-10-03 出发');
|
|
23
|
+
assert.deepEqual(full.absoluteDates, [{ year: 2026, month: 10, day: 3 }]);
|
|
24
|
+
const md = extractTurnSignals('10月3日到衡阳,10.5回深圳');
|
|
25
|
+
assert.equal(md.absoluteDates.length, 2);
|
|
26
|
+
assert.equal(md.spanDays, 2);
|
|
27
|
+
assert.deepEqual(md.absoluteDates[0], { year: null, month: 10, day: 3 });
|
|
28
|
+
// 无效月/日过滤;时间(冒号)与版本号外的数字不产日期
|
|
29
|
+
assert.equal(extractTurnSignals('13.45 与 22:40 都是数字').absoluteDates.length, 0);
|
|
30
|
+
// 跨度词:按强度取先
|
|
31
|
+
assert.equal(extractTurnSignals('待十几天').spanDays, 12);
|
|
32
|
+
assert.equal(extractTurnSignals('待半个月').spanDays, 15);
|
|
33
|
+
assert.equal(extractTurnSignals('待一个月').spanDays, 30);
|
|
34
|
+
assert.equal(extractTurnSignals('大概两周').spanDays, 14);
|
|
35
|
+
assert.equal(extractTurnSignals('大概一周').spanDays, 7);
|
|
36
|
+
assert.equal(extractTurnSignals('还有6天额度').spanDays, 6);
|
|
37
|
+
assert.equal(extractTurnSignals('没有跨度').spanDays, null);
|
|
38
|
+
// 绝对日期 ≥2 时跨度词不参与
|
|
39
|
+
const both = extractTurnSignals('10.1 到 10.9 玩十几天');
|
|
40
|
+
assert.equal(both.spanDays, 8);
|
|
41
|
+
// 约束词:命中去重、按词表序
|
|
42
|
+
assert.deepEqual(extractTurnSignals('婚礼加IRW,还要请假').constraintHits, ['婚礼', 'IRW', '请假']);
|
|
43
|
+
assert.deepEqual(extractTurnSignals('没有任何约束词在这里').constraintHits, []);
|
|
44
|
+
// user 消息里最常见的误触:quick 判据必须同时满足零日期/零约束/零跨度/短
|
|
45
|
+
assert.equal(classifyTurn('10.3 婚礼那天衡阳天气怎么样?穿什么'), 'sync-planning'); // 有日期+约束 → 不冒进 quick
|
|
46
|
+
// ---- policy 表 ---------------------------------------------------------------
|
|
47
|
+
assert.equal(TURN_POLICIES['deep-planning'].exit, 'handoff');
|
|
48
|
+
assert.equal(TURN_POLICIES['sync-planning'].exit, 'converge');
|
|
49
|
+
assert.equal(TURN_POLICIES['quick'].exit, 'converge');
|
|
50
|
+
for (const policy of Object.values(TURN_POLICIES)) {
|
|
51
|
+
assert.ok(policy.hardMs > policy.softMs, 'hard must exceed soft');
|
|
52
|
+
assert.ok(policy.softMs > 0 && policy.hardMs > 0);
|
|
53
|
+
}
|
|
54
|
+
assert.deepEqual(turnPolicyFor('quick'), TURN_POLICIES['quick']);
|
|
55
|
+
console.log('turn policy tests: OK (routing table incl. trajectory verbatim, date/span extraction, policy shape)');
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D-30 普通模型 canary 运行器(issue #112 验收,tool-orchestration-design §4⑥)。
|
|
3
|
+
*
|
|
4
|
+
* 量什么:普通 LLM 面对投影后的工具 JSON Schema,能否**一次成型**产出合法参数——
|
|
5
|
+
* 这是 typed 参数契约迁移(D-30)要解决的核心问题(blob 时代强弱模型差距全部
|
|
6
|
+
* 暴露在「猜参数形状」上)。
|
|
7
|
+
*
|
|
8
|
+
* 怎么量:真实 LLM(env 三件套 LLM_API_KEY/LLM_BASE_URL/LLM_MODEL,或仓库 .env)
|
|
9
|
+
* + 工具投影 schema(defineTool 后的 parameters,与模型所见完全一致)+ tool_choice
|
|
10
|
+
* 强制单工具 → 模型出参 → `validateJsonSchemaValue`(与 dsh defineTool execute 前
|
|
11
|
+
* 同款校验)→ 违例为零 = 该例一次成型。
|
|
12
|
+
*
|
|
13
|
+
* 不执行工具:零检索副作用、零站点请求——canary 只量参数形态,不量业务结果。
|
|
14
|
+
*
|
|
15
|
+
* 达标线:工具调用一次成型率 ≥90%(10 例容 1 失);语义正确性(城市/日期/kind)
|
|
16
|
+
* 作为二级信号同报,不进门。
|
|
17
|
+
*
|
|
18
|
+
* 运行(在仓根或 ts/):LLM 三件套已配 → npx tsx ts/scripts/typed-contract-canary.ts
|
|
19
|
+
*/
|
|
20
|
+
import assert from 'node:assert/strict';
|
|
21
|
+
import { readFileSync, existsSync, mkdtempSync } from 'node:fs';
|
|
22
|
+
import { tmpdir } from 'node:os';
|
|
23
|
+
import { join, dirname } from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
import { validateJsonSchemaValue } from '@deepseek-ai/dsh-tools';
|
|
26
|
+
import { apply } from '../src/index.js';
|
|
27
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
28
|
+
// --- LLM 三件套:env 优先,回退仓库 .env(只取值,永不回显) ---
|
|
29
|
+
function loadEnvTriple() {
|
|
30
|
+
const env = process.env;
|
|
31
|
+
let key = env.LLM_API_KEY ?? '';
|
|
32
|
+
let base = env.LLM_BASE_URL ?? '';
|
|
33
|
+
let model = env.LLM_MODEL ?? '';
|
|
34
|
+
const envFile = join(ROOT, '.env');
|
|
35
|
+
if (existsSync(envFile)) {
|
|
36
|
+
for (const line of readFileSync(envFile, 'utf-8').split('\n')) {
|
|
37
|
+
const m = /^([A-Z_]+)=(.*)$/.exec(line.trim());
|
|
38
|
+
if (!m)
|
|
39
|
+
continue;
|
|
40
|
+
const [, k, v] = m;
|
|
41
|
+
if (k === 'LLM_API_KEY' && !key)
|
|
42
|
+
key = v.trim();
|
|
43
|
+
if (k === 'LLM_BASE_URL' && !base)
|
|
44
|
+
base = v.trim();
|
|
45
|
+
if (k === 'LLM_MODEL' && !model)
|
|
46
|
+
model = v.trim();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
assert.ok(key && base && model, 'LLM_API_KEY/LLM_BASE_URL/LLM_MODEL 必须已配(env 或 .env)');
|
|
50
|
+
return { key, base, model };
|
|
51
|
+
}
|
|
52
|
+
// --- 捕获插件注册表(隔离 stateRoot:canary 不写任何共享状态;sessionAccess=off 免审批) ---
|
|
53
|
+
const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-canary-state-'));
|
|
54
|
+
const registered = [];
|
|
55
|
+
const ctx = {
|
|
56
|
+
tools: { register: (t) => registered.push(t) },
|
|
57
|
+
systemPrompt: { variable: () => () => '' },
|
|
58
|
+
on: () => () => { },
|
|
59
|
+
};
|
|
60
|
+
apply(ctx, { stateRoot, timeoutMs: 30_000, hbcliBin: 'hbcli-not-on-path', sessionAccess: 'off' });
|
|
61
|
+
const byName = (n) => {
|
|
62
|
+
const t = registered.find(x => x.name === n);
|
|
63
|
+
assert.ok(t, `工具 ${n} 应已注册`);
|
|
64
|
+
return t;
|
|
65
|
+
};
|
|
66
|
+
const CASES = [
|
|
67
|
+
{ label: '机·票(城市对+日期)', tool: 'gotry_flyai_search', prompt: '帮我查一下 10 月 1 日从上海飞丽江的机票' },
|
|
68
|
+
{ label: '火车票', tool: 'gotry_flyai_search', prompt: '10 月 1 日上海到昆明的火车票帮我查一下' },
|
|
69
|
+
{ label: '酒店(带档期)', tool: 'gotry_flyai_search', prompt: '查一下大理 10 月 1 日入住、10 月 3 日退房的酒店' },
|
|
70
|
+
{ label: '酒店(摸底,无档期)', tool: 'gotry_flyai_search', prompt: '先看看大理有哪些酒店' },
|
|
71
|
+
{ label: '天气预报', tool: 'gotry_weather_check', prompt: '大理这几天天气怎么样' },
|
|
72
|
+
{ label: '酒店实时源(hbcli)', tool: 'gotry_hotel_search', prompt: '查一下大理的酒店,入住下周五,住三晚' },
|
|
73
|
+
{ label: '会话面机票(登录态)', tool: 'gotry_session_search', prompt: '用我的携程账号查 10 月 1 日上海到丽江的机票' },
|
|
74
|
+
{ label: '目的地候选库', tool: 'gotry_anything_search', prompt: '大连在你们的目的地库里吗?帮我查一下' },
|
|
75
|
+
{ label: 'GitHub 搜索', tool: 'gotry_github_search', prompt: '在 GitHub 上搜一下 agent-reach 这个项目' },
|
|
76
|
+
{ label: '网页读取', tool: 'gotry_web_search', prompt: '帮我读一下 https://example.com 这个页面的内容' },
|
|
77
|
+
];
|
|
78
|
+
async function callLlmMessages(cfg, toolName, messages) {
|
|
79
|
+
const tool = byName(toolName);
|
|
80
|
+
const url = `${cfg.base.replace(/\/+$/, '')}/chat/completions`;
|
|
81
|
+
const body = {
|
|
82
|
+
model: cfg.model,
|
|
83
|
+
messages,
|
|
84
|
+
tools: [{ type: 'function', function: { name: tool.name, description: tool.description, parameters: tool.parameters } }],
|
|
85
|
+
tool_choice: { type: 'function', function: { name: tool.name } },
|
|
86
|
+
temperature: 0,
|
|
87
|
+
};
|
|
88
|
+
const res = await fetch(url, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${cfg.key}` },
|
|
91
|
+
body: JSON.stringify(body),
|
|
92
|
+
signal: AbortSignal.timeout(60_000),
|
|
93
|
+
});
|
|
94
|
+
const bodyText = await res.text();
|
|
95
|
+
assert.equal(res.status, 200, `LLM HTTP ${res.status}: ${bodyText.slice(0, 200)}`);
|
|
96
|
+
const j = JSON.parse(bodyText);
|
|
97
|
+
const msg = j.choices?.[0]?.message;
|
|
98
|
+
const tc = msg.tool_calls?.[0];
|
|
99
|
+
if (!tc?.function?.arguments) {
|
|
100
|
+
const text = String(msg.content ?? '');
|
|
101
|
+
assert.ok(/年份|日期|确认|2024|2025|2026/.test(text), `模型既未调工具也未按拒绝指引向用户确认: ${text.slice(0, 120)}`);
|
|
102
|
+
return { args: undefined, callId: 'text-reply', text, textReply: true };
|
|
103
|
+
}
|
|
104
|
+
return { args: JSON.parse(tc.function.arguments), callId: tc.id ?? 'canary-call', textReply: false };
|
|
105
|
+
}
|
|
106
|
+
async function main() {
|
|
107
|
+
const cfg = loadEnvTriple();
|
|
108
|
+
console.log(`typed-contract canary:model=${cfg.model} 已配(值不回显);用例 ${CASES.length} 条,达标线 ≥90% 一次成型\n`);
|
|
109
|
+
let pass = 0;
|
|
110
|
+
const failures = [];
|
|
111
|
+
for (const c of CASES) {
|
|
112
|
+
const tool = byName(c.tool);
|
|
113
|
+
process.stdout.write(`- ${c.label} → ${c.tool} … `);
|
|
114
|
+
try {
|
|
115
|
+
const { args } = await callLlmMessages(cfg, c.tool, [
|
|
116
|
+
{ role: 'system', content: '你是 GoTry 旅行助手。需要查数据时必须调用提供的工具,参数从用户话里取,不要编造。' },
|
|
117
|
+
{ role: 'user', content: c.prompt },
|
|
118
|
+
]);
|
|
119
|
+
const violations = validateJsonSchemaValue(tool.parameters, args);
|
|
120
|
+
if (violations.length === 0) {
|
|
121
|
+
pass++;
|
|
122
|
+
console.log(`PASS 一次成型 ${JSON.stringify(args).slice(0, 120)}`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
failures.push(`${c.label}: schema 违例 ${violations.join(';')}`);
|
|
126
|
+
console.log(`FAIL schema 违例: ${violations.join(';').slice(0, 160)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
failures.push(`${c.label}: ${e.message.slice(0, 160)}`);
|
|
131
|
+
console.log(`FAIL ${e.message.slice(0, 140)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------
|
|
135
|
+
// 验收②(issue #140):畸形/越界调用被宿主权拒绝后,模型按 violations 自纠。
|
|
136
|
+
// 确定性播种:预置一次带过去日期的被拒调用与结构化拒绝结果(tool 角色),
|
|
137
|
+
// 验证模型从拒绝中自纠出「schema 合规 + 日期非过去」的参数。只校验不执行(零副作用)。
|
|
138
|
+
// ---------------------------------------------------------------
|
|
139
|
+
{
|
|
140
|
+
const tool = byName('gotry_flyai_search');
|
|
141
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
142
|
+
try {
|
|
143
|
+
const messages = [
|
|
144
|
+
{ role: 'system', content: '你是 GoTry 旅行助手。需要查数据时必须调用提供的工具,参数从用户话里取,不要编造。' },
|
|
145
|
+
{ role: 'user', content: '帮我查一下 10 月 1 日从上海飞丽江的机票' },
|
|
146
|
+
{ role: 'assistant', content: null, tool_calls: [{ id: 'seed-reject', type: 'function', function: { name: 'gotry_flyai_search', arguments: JSON.stringify({ kind: 'flight', from: '上海', to: '丽江', date: '2024-10-01' }) } }] },
|
|
147
|
+
{ role: 'tool', tool_call_id: 'seed-reject', content: JSON.stringify({ ok: false, verdict: 'error', summary: `未发起查询:日期 2024-10-01 已是过去(今天 ${today}),过去不存在在售机票。多为用户时间表达未带年份所致——向用户确认年份(或按未来最近的同月日修正)后再查。` }) },
|
|
148
|
+
];
|
|
149
|
+
const reply = await callLlmMessages(cfg, 'gotry_flyai_search', messages);
|
|
150
|
+
const violations = reply.args ? validateJsonSchemaValue(tool.parameters, reply.args) : [];
|
|
151
|
+
if (reply.textReply) {
|
|
152
|
+
assert.match(reply.text ?? '', /年份|日期|确认/, '文字形态应向用户确认年份/日期(契约允许的自纠)');
|
|
153
|
+
console.log('自纠回路(验收②):模型按拒绝指引转为向用户确认年份(文字形态,契约允许)');
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
assert.equal(violations.length, 0, `自纠出参 schema 违例: ${violations.join(';')}`);
|
|
157
|
+
assert.ok(reply.args && reply.args.date && reply.args.date >= today, `自纠后日期应为今天或未来,实际 ${JSON.stringify(reply.args).slice(0, 80)}`);
|
|
158
|
+
console.log(`自纠回路(验收②):模型按拒绝自纠出参 → PASS(${JSON.stringify(reply.args).slice(0, 100)})`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
failures.push(`验收② 自纠回路异常: ${e.message.slice(0, 140)}`);
|
|
163
|
+
console.log(`FAIL 自纠回路异常(计入失败,不中断报告): ${e.message.slice(0, 140)}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const rate = Math.round((pass / CASES.length) * 100);
|
|
167
|
+
console.log(`\n一次成型率:${pass}/${CASES.length} = ${rate}%(达标线 90%)`);
|
|
168
|
+
if (failures.length)
|
|
169
|
+
console.log('失败明细:\n' + failures.map(f => ` - ${f}`).join('\n'));
|
|
170
|
+
if (rate < 90) {
|
|
171
|
+
console.error('TYPED CONTRACT CANARY FAIL(不达标不合入)');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
console.log('TYPED CONTRACT CANARY OK');
|
|
175
|
+
}
|
|
176
|
+
await main();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Quick sanity check for tz-resolver. Run with: TZ=UTC npx tsx scripts/tz-resolver-sanity.ts
|
|
2
|
+
import { resolveOffsetForLocalDate } from '../src/tz-resolver.js';
|
|
3
|
+
const cases = [
|
|
4
|
+
// Asia/Shanghai has no DST throughout the year
|
|
5
|
+
['Asia/Shanghai', '2026-07-18', '14:45', 480],
|
|
6
|
+
['Asia/Shanghai', '2026-01-01', '09:00', 480],
|
|
7
|
+
['Asia/Tokyo', '2026-07-04', '09:00', 540],
|
|
8
|
+
['Asia/Hong_Kong', '2026-07-18', '14:45', 480],
|
|
9
|
+
['Asia/Bangkok', '2026-08-09', '23:55', 420],
|
|
10
|
+
['Asia/Singapore', '2026-08-09', '23:55', 480],
|
|
11
|
+
['Asia/Dubai', '2026-07-18', '00:45', 240],
|
|
12
|
+
// America/New_York DST: EST = -5, EDT = -4. 2026 spring-forward = 2026-03-08, fall-back = 2026-11-01
|
|
13
|
+
['America/New_York', '2026-03-07', '14:00', -300], // day before spring forward -> EST
|
|
14
|
+
['America/New_York', '2026-03-09', '14:00', -240], // day after spring forward -> EDT
|
|
15
|
+
['America/New_York', '2026-06-15', '09:00', -240], // summer -> EDT
|
|
16
|
+
['America/New_York', '2026-12-15', '09:00', -300], // winter -> EST
|
|
17
|
+
['America/Los_Angeles', '2026-06-15', '14:00', -420],
|
|
18
|
+
['UTC', '2026-07-18', '12:00', 0],
|
|
19
|
+
['Europe/London', '2026-06-15', '14:00', 60],
|
|
20
|
+
['Europe/London', '2026-01-15', '14:00', 0],
|
|
21
|
+
];
|
|
22
|
+
let ok = 0, fail = 0;
|
|
23
|
+
for (const [zone, ymd, hhmm, expected] of cases) {
|
|
24
|
+
const r = resolveOffsetForLocalDate(zone, ymd, hhmm);
|
|
25
|
+
const pass = r.ok && r.offsetMin === expected;
|
|
26
|
+
if (pass)
|
|
27
|
+
ok++;
|
|
28
|
+
else {
|
|
29
|
+
fail++;
|
|
30
|
+
console.log('FAIL', zone, ymd, hhmm, 'expected', expected, 'got', r.ok ? r.offsetMin : r);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const negatives = [
|
|
34
|
+
['Foo/Bar', '2026-07-18', '14:45', 'unknown_zone'],
|
|
35
|
+
['America/New_York', '2026-03-08', '02:30', 'gap_nonexistent'], // spring forward day
|
|
36
|
+
['America/New_York', '2026-11-01', '01:30', 'overlap_ambiguous'], // fall back day
|
|
37
|
+
];
|
|
38
|
+
for (const [zone, ymd, hhmm, expected] of negatives) {
|
|
39
|
+
const r = resolveOffsetForLocalDate(zone, ymd, hhmm);
|
|
40
|
+
const pass = !r.ok && r.kind === expected;
|
|
41
|
+
if (pass)
|
|
42
|
+
ok++;
|
|
43
|
+
else {
|
|
44
|
+
fail++;
|
|
45
|
+
console.log('FAIL', zone, ymd, hhmm, 'expected', expected, 'got', r.ok ? `ok(${r.offsetMin})` : r.kind);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
console.log(`\n${ok}/${ok + fail} sanity ok`);
|
|
49
|
+
if (fail > 0)
|
|
50
|
+
process.exit(1);
|