@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/dist/src/loop.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage 1 对话循环(S2 段1,docs/design/stage1-top-down-design.md §2.3)。
|
|
3
|
+
*
|
|
4
|
+
* 自顶向下:循环只依赖两个端口——LlmPort(mock/真)与确定性工具(interview_next)。
|
|
5
|
+
* 求解/渲染接线是 S2 后续段;本段验收:重放用户开场白,系统一轮内完成
|
|
6
|
+
* 日历断言 + 访谈补全启动(问出工作窗口与已订资源——Kimi 第 6 轮才问的事)。
|
|
7
|
+
*/
|
|
8
|
+
import { buildTimeAnchor, isRealIsoDate, planningWindowBounds, parsePlanningWindow } from './time-anchor.js';
|
|
1
9
|
import { anythingSearch } from '../capabilities/anything.js';
|
|
2
10
|
export function newState(year = 2026) {
|
|
3
11
|
return {
|
|
4
|
-
calendar: {
|
|
5
|
-
year,
|
|
6
|
-
assertedWeekdays: {}
|
|
7
|
-
},
|
|
12
|
+
calendar: { year, assertedWeekdays: {} },
|
|
8
13
|
profile: {},
|
|
9
14
|
gates: [],
|
|
10
|
-
wishes: []
|
|
15
|
+
wishes: [],
|
|
11
16
|
};
|
|
12
17
|
}
|
|
18
|
+
/** ADR-9:访谈由缺失字段驱动(确定性,无 LLM 即兴) */
|
|
13
19
|
export function interviewNext(state) {
|
|
14
20
|
const p = state.profile;
|
|
15
21
|
const questions = [];
|
|
@@ -18,14 +24,14 @@ export function interviewNext(state) {
|
|
|
18
24
|
questions.push({
|
|
19
25
|
key: 'workWindow',
|
|
20
26
|
text: '你这两周要远程办公——工作时间是几点到几点、按哪个时区?这决定每天的可玩时段。',
|
|
21
|
-
why: '工作窗口直接决定每日节奏(普吉 13:00-22:00 与 9:00-18:00 是两种人生)'
|
|
27
|
+
why: '工作窗口直接决定每日节奏(普吉 13:00-22:00 与 9:00-18:00 是两种人生)',
|
|
22
28
|
});
|
|
23
29
|
}
|
|
24
30
|
if (!p.bookedResources) {
|
|
25
31
|
questions.push({
|
|
26
32
|
key: 'bookedResources',
|
|
27
33
|
text: '已经有订好的航班或酒店吗?(哪怕只是意向)——已订资源是硬锚点,先告诉我再规划。',
|
|
28
|
-
why: '已订资源决定哪些段不可动,Kimi 式规划会绕开它们重排'
|
|
34
|
+
why: '已订资源决定哪些段不可动,Kimi 式规划会绕开它们重排',
|
|
29
35
|
});
|
|
30
36
|
}
|
|
31
37
|
if (!p.budgetTier) {
|
|
@@ -34,35 +40,28 @@ export function interviewNext(state) {
|
|
|
34
40
|
text: '预算档位:经济(¥12.6k 级)/ 舒适(¥16.3k 级)/ 便利优先?',
|
|
35
41
|
why: '分层预算决定班次与住宿的选型口径',
|
|
36
42
|
options: [
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
label: '舒适',
|
|
43
|
-
tradeOff: 'workation 办公质量优先'
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
label: '便利优先',
|
|
47
|
-
tradeOff: '时间最省'
|
|
48
|
-
}
|
|
49
|
-
]
|
|
43
|
+
{ label: '经济', tradeOff: '省钱,接驳多走路' },
|
|
44
|
+
{ label: '舒适', tradeOff: 'workation 办公质量优先' },
|
|
45
|
+
{ label: '便利优先', tradeOff: '时间最省' },
|
|
46
|
+
],
|
|
50
47
|
});
|
|
51
48
|
}
|
|
52
|
-
return {
|
|
53
|
-
questions,
|
|
54
|
-
missing: questions.map((q)=>q.key)
|
|
55
|
-
};
|
|
49
|
+
return { questions, missing: questions.map(q => q.key) };
|
|
56
50
|
}
|
|
51
|
+
/** spec 校验闸:LLM 翻译产物进求解器前的确定性形状检查(责任边界的执行点)。 */
|
|
57
52
|
export function validateSpec(spec) {
|
|
58
|
-
if (!Array.isArray(spec.segments) || spec.segments.length === 0)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (!
|
|
62
|
-
|
|
53
|
+
if (!Array.isArray(spec.segments) || spec.segments.length === 0)
|
|
54
|
+
return '没有段(segments)';
|
|
55
|
+
for (const seg of spec.segments) {
|
|
56
|
+
if (!seg.id)
|
|
57
|
+
return '存在缺少 id 的段';
|
|
58
|
+
if (!Array.isArray(seg.options) || seg.options.length === 0)
|
|
59
|
+
return `段 ${seg.id} 没有可选方案`;
|
|
60
|
+
for (const opt of seg.options) {
|
|
63
61
|
const svcs = opt.move?.services;
|
|
64
|
-
if (!Array.isArray(svcs) || svcs.length === 0)
|
|
65
|
-
|
|
62
|
+
if (!Array.isArray(svcs) || svcs.length === 0)
|
|
63
|
+
return `段 ${seg.id} 的方案 ${opt.id} 缺少班次(services)`;
|
|
64
|
+
for (const s of svcs) {
|
|
66
65
|
if (typeof s.depMin !== 'number' || typeof s.arrMin !== 'number') {
|
|
67
66
|
return `段 ${seg.id}/${opt.id} 的班次 ${s.id ?? '?'} 缺少时刻(depMin/arrMin)`;
|
|
68
67
|
}
|
|
@@ -71,9 +70,78 @@ export function validateSpec(spec) {
|
|
|
71
70
|
}
|
|
72
71
|
return null;
|
|
73
72
|
}
|
|
73
|
+
function isoDateInBounds(date, bounds) {
|
|
74
|
+
const m = date.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
75
|
+
if (!m || !isRealIsoDate(Number(m[1]), Number(m[2]), Number(m[3])))
|
|
76
|
+
return false;
|
|
77
|
+
return date >= bounds.start && date <= bounds.end;
|
|
78
|
+
}
|
|
79
|
+
function hasDatedCandidates(spec) {
|
|
80
|
+
return spec.segments.some(segment => segment.date !== undefined || segment.options.some(option => option.date !== undefined));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Enforce a named-year future window at the planner boundary. Historical
|
|
84
|
+
* lookup/backtesting remains an explicit bypass; an expired named year fails
|
|
85
|
+
* instead of being silently rolled into a different year.
|
|
86
|
+
*/
|
|
87
|
+
export function applyPlanningWindow(spec, window, anchor, options = {}) {
|
|
88
|
+
// Dated recommendations are future-oriented even without optional named-year context.
|
|
89
|
+
// Fully dateless legacy calculations remain untouched; historical bypass is explicit.
|
|
90
|
+
if (window?.intent === 'historical')
|
|
91
|
+
return { spec, rejected: [] };
|
|
92
|
+
const implicitFutureFloor = !window && options.defaultDatedFuture === true && hasDatedCandidates(spec);
|
|
93
|
+
if (!window && !implicitFutureFloor)
|
|
94
|
+
return { spec, rejected: [] };
|
|
95
|
+
const currentYear = Number(anchor.today.slice(0, 4));
|
|
96
|
+
if (window && window.requestedYear < currentYear) {
|
|
97
|
+
return {
|
|
98
|
+
spec,
|
|
99
|
+
rejected: [],
|
|
100
|
+
error: `${window.requestedYear} 年已结束(当前规划参考日 ${anchor.today}),不会自动改用其他年份;如需历史查询请明确说明“历史/回测”。`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const bounds = window
|
|
104
|
+
? planningWindowBounds(window, anchor)
|
|
105
|
+
: { start: anchor.today, end: '9999-12-31' };
|
|
106
|
+
if (!bounds)
|
|
107
|
+
return { spec, rejected: [], error: `无法建立 ${window.requestedYear} 年的未来规划窗口。` };
|
|
108
|
+
const rejected = [];
|
|
109
|
+
const guardedSegments = spec.segments.map(segment => ({
|
|
110
|
+
...segment,
|
|
111
|
+
options: segment.options.filter(option => {
|
|
112
|
+
const date = option.date ?? segment.date;
|
|
113
|
+
if (date && isoDateInBounds(date, bounds))
|
|
114
|
+
return true;
|
|
115
|
+
rejected.push({ segmentId: segment.id, optionId: option.id, date: date ?? '未提供明确日期' });
|
|
116
|
+
return false;
|
|
117
|
+
}),
|
|
118
|
+
}));
|
|
119
|
+
if (guardedSegments.some(segment => segment.options.length === 0)) {
|
|
120
|
+
return {
|
|
121
|
+
spec: { ...spec, segments: guardedSegments },
|
|
122
|
+
rejected,
|
|
123
|
+
error: implicitFutureFloor
|
|
124
|
+
? `带日期的未来推荐必须不早于当前规划参考日 ${bounds.start};没有带明确日期且在未来窗口内的可接受方案。`
|
|
125
|
+
: `${window.requestedYear} 年未来规划窗口为 ${bounds.start} 至 ${bounds.end};没有带明确日期且落在窗口内的可接受方案。`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return { spec: { ...spec, segments: guardedSegments }, rejected };
|
|
129
|
+
}
|
|
130
|
+
function latestPlanningWindow(history, userMsg, anchor) {
|
|
131
|
+
const turns = [...history, { role: 'user', text: userMsg }];
|
|
132
|
+
for (let i = turns.length - 1; i >= 0; i--) {
|
|
133
|
+
const turn = turns[i];
|
|
134
|
+
if (turn?.role !== 'user')
|
|
135
|
+
continue;
|
|
136
|
+
const parsed = parsePlanningWindow(turn.text, anchor);
|
|
137
|
+
if (parsed)
|
|
138
|
+
return parsed;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
74
142
|
export const ASYNC_TERMINAL_SCHEMA = 'gotry_async_terminal.v1';
|
|
75
143
|
function asyncTerminalOutcome(ticketId, checks) {
|
|
76
|
-
const failedChecks = Object.entries(checks).filter(([, passed])
|
|
144
|
+
const failedChecks = Object.entries(checks).filter(([, passed]) => !passed).map(([name]) => name);
|
|
77
145
|
return {
|
|
78
146
|
schema: ASYNC_TERMINAL_SCHEMA,
|
|
79
147
|
ticket_id: ticketId,
|
|
@@ -81,7 +149,7 @@ function asyncTerminalOutcome(ticketId, checks) {
|
|
|
81
149
|
passed: Object.values(checks).filter(Boolean).length,
|
|
82
150
|
total: 4,
|
|
83
151
|
checks,
|
|
84
|
-
failed_checks: failedChecks
|
|
152
|
+
failed_checks: failedChecks,
|
|
85
153
|
};
|
|
86
154
|
}
|
|
87
155
|
function terminalOutcomeFromState(ticketId, state) {
|
|
@@ -90,17 +158,20 @@ function terminalOutcomeFromState(ticketId, state) {
|
|
|
90
158
|
'1_承诺时间后必有明确产物': false,
|
|
91
159
|
'2_产物通过自检清单': false,
|
|
92
160
|
'3_待决问题全部是简单选择题': false,
|
|
93
|
-
'4_做不到的诚实说': false
|
|
161
|
+
'4_做不到的诚实说': false,
|
|
94
162
|
});
|
|
95
163
|
}
|
|
96
164
|
return asyncTerminalOutcome(ticketId, {
|
|
97
165
|
'1_承诺时间后必有明确产物': Boolean(state.solve.legs?.length || state.solve.verdicts?.length),
|
|
98
|
-
'2_产物通过自检清单': state.solve.feasible
|
|
99
|
-
|
|
100
|
-
|
|
166
|
+
'2_产物通过自检清单': state.solve.feasible
|
|
167
|
+
? (state.solve.legs?.every(l => l['energy_pct'] !== undefined) ?? false)
|
|
168
|
+
: Boolean(state.solve.unsat_core?.length),
|
|
169
|
+
'3_待决问题全部是简单选择题': state.gates.every(g => g.id === 'budget' || g.options.length >= 2),
|
|
170
|
+
'4_做不到的诚实说': state.solve.feasible || Boolean(state.solve.suggestions?.length),
|
|
101
171
|
});
|
|
102
172
|
}
|
|
103
173
|
export function isComplex(state) {
|
|
174
|
+
/** 复杂度判据(架构占位,后续可细化):多段或多约束即深规划 */
|
|
104
175
|
const p = state.profile;
|
|
105
176
|
return Boolean(p.workWindow && p.bookedResources) && (state.gates.length > 0 || Boolean(state.spec));
|
|
106
177
|
}
|
|
@@ -109,135 +180,135 @@ export async function requestDeepPlanning(state) {
|
|
|
109
180
|
id: `dp-${Date.now().toString(36)}`,
|
|
110
181
|
objective: '生成已验证的行程方案:全成本核算 + 动机/约束匹配 + 不失望四条',
|
|
111
182
|
requestedAt: new Date().toISOString(),
|
|
112
|
-
etaLabel: '约 1 小时(mock 期:秒级)'
|
|
113
|
-
};
|
|
114
|
-
state.gates.push({
|
|
115
|
-
id: `async-${ticket.id}`,
|
|
116
|
-
question: `深度规划已启动(${ticket.etaLabel})`,
|
|
117
|
-
options: []
|
|
118
|
-
});
|
|
119
|
-
const reply = '这趟行程跨度大(两周 workation + 三城 + 红眼返程),我切到**深度规划模式**:后台做多轮校验' + '(锚点/工作窗口/全成本/负例排查),做好后通知你。**先留一道选择题给你**,回来直接定:' + '\n1. 预算档位?(经济/舒适/便利优先)';
|
|
120
|
-
return {
|
|
121
|
-
reply,
|
|
122
|
-
ticket
|
|
183
|
+
etaLabel: '约 1 小时(mock 期:秒级)',
|
|
123
184
|
};
|
|
185
|
+
state.gates.push({ id: `async-${ticket.id}`, question: `深度规划已启动(${ticket.etaLabel})`, options: [] });
|
|
186
|
+
const reply = '这趟行程跨度大(两周 workation + 三城 + 红眼返程),我切到**深度规划模式**:后台做多轮校验'
|
|
187
|
+
+ '(锚点/工作窗口/全成本/负例排查),做好后通知你。**先留一道选择题给你**,回来直接定:'
|
|
188
|
+
+ '\n1. 预算档位?(经济/舒适/便利优先)';
|
|
189
|
+
return { reply, ticket };
|
|
124
190
|
}
|
|
125
191
|
export async function collectDeepPlanning(state, ticket, solve) {
|
|
126
192
|
const spec = state.spec;
|
|
127
193
|
if (!spec) {
|
|
128
194
|
const outcome = terminalOutcomeFromState(ticket.id, state);
|
|
129
|
-
return {
|
|
130
|
-
reply: '(内部状态缺失 spec——深度规划未就绪,这是不应发生的路径)',
|
|
131
|
-
state,
|
|
132
|
-
outcome
|
|
133
|
-
};
|
|
195
|
+
return { reply: '(内部状态缺失 spec——深度规划未就绪,这是不应发生的路径)', state, outcome };
|
|
134
196
|
}
|
|
135
197
|
state.solve = await solve(spec);
|
|
136
|
-
state.gates = state.gates.filter(
|
|
198
|
+
state.gates = state.gates.filter(g => !g.id.startsWith('async-'));
|
|
199
|
+
// 不失望四条自检(交付物自带,总纲 3.6)
|
|
137
200
|
const outcome = terminalOutcomeFromState(ticket.id, state);
|
|
138
201
|
const head = `# 回访交付:${ticket.objective}\n(工单 ${ticket.id},不失望四条:${outcome.status === 'succeeded' ? '4/4 ✅' : '有未达项 ❌'})`;
|
|
139
|
-
return {
|
|
140
|
-
reply: `${head}\n\n${renderSolve(state)}`,
|
|
141
|
-
state,
|
|
142
|
-
outcome
|
|
143
|
-
};
|
|
202
|
+
return { reply: `${head}\n\n${renderSolve(state)}`, state, outcome };
|
|
144
203
|
}
|
|
204
|
+
/** 求解结果 → 人话(模板;S4 可由 LLM 润色,数字与判定不可改) */
|
|
145
205
|
export function renderSolve(state) {
|
|
146
206
|
const s = state.solve;
|
|
147
|
-
if (!s)
|
|
207
|
+
if (!s)
|
|
208
|
+
return '(无求解结果)';
|
|
148
209
|
const lines = [];
|
|
149
210
|
if (s.feasible) {
|
|
150
211
|
lines.push(`**方案可行,机票合计 ¥${s.money_cny}**`);
|
|
151
|
-
for (const lg of s.legs ?? []){
|
|
212
|
+
for (const lg of s.legs ?? []) {
|
|
152
213
|
const l = lg;
|
|
153
|
-
lines.push(`- ${l['leg']} ${l['service']}:${l['dep']} 起飞,${l['wake']} 出发,${l['arrive_stay']} 到,`
|
|
154
|
-
|
|
155
|
-
for (const e of s.work_window_exclusions ?? []){
|
|
156
|
-
lines.push(`- 已排除 ${e.option}(工作窗口):${e.reason}`);
|
|
214
|
+
lines.push(`- ${l['leg']} ${l['service']}:${l['dep']} 起飞,${l['wake']} 出发,${l['arrive_stay']} 到,`
|
|
215
|
+
+ `门到门 ${l['door_to_door']},落地精力 ${l['energy_pct']}%,¥${l['price_cny']}`);
|
|
157
216
|
}
|
|
158
|
-
for (const n of s.skeleton_notes ?? [])
|
|
159
|
-
|
|
160
|
-
|
|
217
|
+
for (const n of s.skeleton_notes ?? [])
|
|
218
|
+
lines.push(`- ${n}`);
|
|
219
|
+
for (const f of s.red_flags ?? [])
|
|
220
|
+
lines.push(`- ⚠️ ${f}`);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
161
223
|
lines.push(`**当前约束下不可行——冲突:${(s.unsat_core ?? []).join('、')}`);
|
|
162
|
-
for (const sg of s.suggestions ?? [])
|
|
224
|
+
for (const sg of s.suggestions ?? [])
|
|
225
|
+
lines.push(`- 放宽「${sg.relax}」可解(约 ¥${sg.money_cny})`);
|
|
226
|
+
}
|
|
227
|
+
for (const e of s.work_window_exclusions ?? []) {
|
|
228
|
+
lines.push(`- 已排除 ${e.option}(工作窗口):${e.reason}`);
|
|
163
229
|
}
|
|
164
230
|
if (state.gates.length) {
|
|
165
231
|
lines.push('', '**待你决定(选择题)**');
|
|
166
|
-
for (const g of state.gates){
|
|
167
|
-
lines.push(`- ${g.question} → ${g.options.map(
|
|
232
|
+
for (const g of state.gates) {
|
|
233
|
+
lines.push(`- ${g.question} → ${g.options.map(o => o.label + (o.tradeOff ? `(${o.tradeOff})` : '')).join(' / ')}`);
|
|
168
234
|
}
|
|
169
235
|
}
|
|
170
236
|
return lines.join('\n');
|
|
171
237
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
role: 'user',
|
|
177
|
-
text: userMsg
|
|
178
|
-
}
|
|
179
|
-
], state);
|
|
238
|
+
/** 一次对话回合:抽取事实(冲突即指出)→ 增量访谈 → 约束齐备则求解+渲染。 */
|
|
239
|
+
export async function runTurn(state, userMsg, llm, history = [], solve = null, now = new Date()) {
|
|
240
|
+
const anchor = buildTimeAnchor(now);
|
|
241
|
+
const facts = await llm.extractFacts([...history, { role: 'user', text: userMsg }], state);
|
|
180
242
|
const conflicts = [];
|
|
181
243
|
if (facts.calendar?.assertedWeekdays) {
|
|
182
|
-
for (const [date, wd] of Object.entries(facts.calendar.assertedWeekdays)){
|
|
244
|
+
for (const [date, wd] of Object.entries(facts.calendar.assertedWeekdays)) {
|
|
183
245
|
const prev = state.calendar.assertedWeekdays[date];
|
|
184
246
|
if (prev && prev !== wd) {
|
|
185
247
|
conflicts.push(`${date}:已有断言 ${prev},新说法 ${wd}——以先断言为准,如需改请明确说`);
|
|
186
|
-
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
187
250
|
state.calendar.assertedWeekdays[date] = wd;
|
|
188
251
|
}
|
|
189
252
|
}
|
|
190
253
|
}
|
|
191
254
|
Object.assign(state.profile, facts.profile ?? {});
|
|
255
|
+
// 访谈:workWindow/bookedResources 是求解前置;budgetTier 转为 gate(不阻塞规划)
|
|
192
256
|
const { questions } = interviewNext(state);
|
|
193
|
-
const blocking = questions.filter(
|
|
194
|
-
const budgetQ = questions.find(
|
|
195
|
-
if (budgetQ && !state.gates.some(
|
|
257
|
+
const blocking = questions.filter(q => q.key !== 'budgetTier');
|
|
258
|
+
const budgetQ = questions.find(q => q.key === 'budgetTier');
|
|
259
|
+
if (budgetQ && !state.gates.some(g => g.id === 'budget')) {
|
|
196
260
|
state.gates.push({
|
|
197
261
|
id: 'budget',
|
|
198
262
|
question: budgetQ.text,
|
|
199
|
-
options: budgetQ.options ?? [
|
|
200
|
-
{
|
|
201
|
-
label: '经济'
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
label: '舒适'
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
label: '便利优先'
|
|
208
|
-
}
|
|
209
|
-
]
|
|
263
|
+
options: budgetQ.options ?? [{ label: '经济' }, { label: '舒适' }, { label: '便利优先' }],
|
|
210
264
|
});
|
|
211
265
|
}
|
|
212
266
|
const parts = [];
|
|
213
|
-
if (conflicts.length)
|
|
267
|
+
if (conflicts.length)
|
|
268
|
+
parts.push(`⚠️ 日历冲突:\n${conflicts.map(c => `- ${c}`).join('\n')}`);
|
|
269
|
+
// D-7a 例外: blocking>0 时,用户消息含"查+地名/酒店/天气"信号时,直接调 anything_search
|
|
270
|
+
// 走 PoI 真相(datasources 层主动调,不动 polling 状态机,不改求解器)
|
|
214
271
|
const poiProbe = probePoi(userMsg);
|
|
215
272
|
if (poiProbe) {
|
|
216
|
-
const ar = await anythingSearch({
|
|
217
|
-
keyword: poiProbe
|
|
218
|
-
});
|
|
273
|
+
const ar = await anythingSearch({ keyword: poiProbe });
|
|
219
274
|
if (ar.verdict === 'hit' && ar.hits && ar.hits.length > 0) {
|
|
220
275
|
const top = ar.hits.slice(0, 5);
|
|
221
|
-
const lines = top.map((h, i)=>{
|
|
222
|
-
const latlng = h.latitude !== undefined && h.longitude !== undefined
|
|
276
|
+
const lines = top.map((h, i) => {
|
|
277
|
+
const latlng = h.latitude !== undefined && h.longitude !== undefined
|
|
278
|
+
? ` @ (${h.latitude.toFixed(3)},${h.longitude.toFixed(3)})`
|
|
279
|
+
: '';
|
|
223
280
|
return ` ${i + 1}. [${h.type}] ${h.name}${latlng}`;
|
|
224
281
|
}).join('\n');
|
|
225
282
|
parts.push(`**${poiProbe} → hit (${ar.hits.length} 候选项)**\n${lines}\n${ar.evidence}`);
|
|
226
|
-
}
|
|
283
|
+
}
|
|
284
|
+
else if (ar.verdict === 'miss') {
|
|
227
285
|
parts.push(`**${poiProbe} → miss** (酒店-be Anything 一切正常但无候选)\n${ar.evidence}`);
|
|
228
|
-
}
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
229
288
|
parts.push(`**${poiProbe} → unavailable** (${ar.error ?? 'hbcli 不可达'})\n${ar.evidence}`);
|
|
230
289
|
}
|
|
231
290
|
}
|
|
232
|
-
for (const q of blocking)
|
|
291
|
+
for (const q of blocking)
|
|
292
|
+
parts.push(await llm.polishQuestion(q));
|
|
293
|
+
// 约束齐备(无阻塞问题)→ 翻译 spec → **校验闸 + 日期一致性闸** → 求解 → 渲染
|
|
233
294
|
if (blocking.length === 0 && solve) {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
295
|
+
const extractedSpec = await llm.extractSpec([...history, { role: 'user', text: userMsg }], state);
|
|
296
|
+
const planning = latestPlanningWindow(history, userMsg, anchor);
|
|
297
|
+
const planningCheck = extractedSpec ? applyPlanningWindow(extractedSpec, planning, anchor) : null;
|
|
298
|
+
if (planningCheck?.error) {
|
|
299
|
+
state.spec = undefined;
|
|
300
|
+
state.solve = undefined;
|
|
301
|
+
parts.push(`⚠️ ${planningCheck.error}`);
|
|
302
|
+
return { reply: parts.join('\n\n'), state };
|
|
303
|
+
}
|
|
304
|
+
if (planningCheck?.rejected.length) {
|
|
305
|
+
const rejected = planningCheck.rejected
|
|
306
|
+
.map(r => `${r.segmentId}/${r.optionId}(${r.date})`)
|
|
307
|
+
.join('、');
|
|
308
|
+
parts.push(`已排除规划窗口外的候选:${rejected}`);
|
|
309
|
+
}
|
|
310
|
+
const spec = planningCheck?.spec ?? extractedSpec;
|
|
311
|
+
// 场景路由:erhai 候选标记 → 候选求解(洱海金标准的引擎判定)——纯 TS unify 路径
|
|
241
312
|
if (spec && spec.note === 'erhai-candidates') {
|
|
242
313
|
const { readFile } = await import('node:fs/promises');
|
|
243
314
|
const { join } = await import('node:path');
|
|
@@ -251,56 +322,52 @@ export async function runTurn(state, userMsg, llm, history = [], solve = null) {
|
|
|
251
322
|
const result = solveChoiceSegment(erhaiSpec, req);
|
|
252
323
|
state.solve = result;
|
|
253
324
|
parts.push(result.answer_md ?? '(候选求解完成)');
|
|
254
|
-
}
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
255
327
|
parts.push('(洱海候选求解暂不可用——退回访谈)');
|
|
256
328
|
}
|
|
257
|
-
}
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
258
331
|
const invalid = spec ? validateSpec(spec) : '翻译器未产出 spec';
|
|
259
332
|
if (spec && !invalid) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
text: userMsg
|
|
265
|
-
}
|
|
266
|
-
]);
|
|
333
|
+
// D-10 切片 C:spec↔槽位日期一致性闸(ADR-10 翻译≠造数的执行点)。
|
|
334
|
+
// LLM 翻译的 spec 日期与代码层换算的槽位日期分歧时不求解、不猜,追问确认——
|
|
335
|
+
// 日期是求解关键输入,错日期 = 错判决。槽位缺失/unresolved 不参与(不造假阳性)。
|
|
336
|
+
const ext = await llm.extractSlots([...history, { role: 'user', text: userMsg }], now);
|
|
267
337
|
if (ext) {
|
|
268
338
|
const { resolveSlots, specDateMismatches } = await import('./slot-spec.js');
|
|
269
|
-
const
|
|
270
|
-
const mismatches = specDateMismatches(spec, resolveSlots(ext, buildTimeAnchor()));
|
|
339
|
+
const mismatches = specDateMismatches(spec, resolveSlots(ext, anchor));
|
|
271
340
|
if (mismatches.length > 0) {
|
|
272
|
-
const lines = mismatches.map(
|
|
341
|
+
const lines = mismatches.map(m => `- ${m.specAt}:翻译说 ${m.specDate},你的原话换算是 ${m.slotDate}(${m.field})`);
|
|
273
342
|
parts.push(`⚠️ 日期分歧,确认后我再算(以你为准):\n${lines.join('\n')}`);
|
|
274
|
-
return {
|
|
275
|
-
reply: parts.join('\n\n'),
|
|
276
|
-
state
|
|
277
|
-
};
|
|
343
|
+
return { reply: parts.join('\n\n'), state };
|
|
278
344
|
}
|
|
279
345
|
}
|
|
280
346
|
state.spec = spec;
|
|
281
347
|
state.solve = await solve(spec);
|
|
282
348
|
parts.push(llm.render ? await llm.render(state) : renderSolve(state));
|
|
283
|
-
}
|
|
349
|
+
}
|
|
350
|
+
else if (blocking.length === 0 && invalid) {
|
|
284
351
|
parts.push(`(行程骨架还不完整:${invalid}——请补充对应信息,我不猜)`);
|
|
285
352
|
}
|
|
286
353
|
}
|
|
287
|
-
}
|
|
354
|
+
}
|
|
355
|
+
else if (blocking.length === 0) {
|
|
288
356
|
parts.push('(约束齐备——进入规划,S3 接线后此处产出方案与选择题)');
|
|
289
357
|
}
|
|
290
|
-
return {
|
|
291
|
-
reply: parts.join('\n\n'),
|
|
292
|
-
state
|
|
293
|
-
};
|
|
358
|
+
return { reply: parts.join('\n\n'), state };
|
|
294
359
|
}
|
|
360
|
+
// ---- 异步工单持久化(S5 编排半段 → ADR-15 durable 工单):真正的「一小时后」必须跨进程存续 ----
|
|
361
|
+
// 权威 = 账本 workflow_runs/workflow_steps(单事务;崩溃后 done 步骤不重执行);
|
|
362
|
+
// {id}.json / {id}.deliverable.md 降级为视图(AGENTS.md 清扫合同 + 人工检视),
|
|
363
|
+
// tmp+rename 原子写——此前裸 writeFile 的非原子缺口(RFC §1.1)就此关闭。
|
|
295
364
|
import { join } from 'node:path';
|
|
296
365
|
import { mkdir, rename, writeFile, readFile } from 'node:fs/promises';
|
|
297
366
|
import { ensureLedger, openLedgerIfExists } from './state-ledger.js';
|
|
298
367
|
async function asyncDir(stateRoot) {
|
|
299
368
|
const root = stateRoot === '.' ? process.cwd() : stateRoot;
|
|
300
369
|
const dir = join(root, 'gotry-state', 'async');
|
|
301
|
-
await mkdir(dir, {
|
|
302
|
-
recursive: true
|
|
303
|
-
});
|
|
370
|
+
await mkdir(dir, { recursive: true });
|
|
304
371
|
return dir;
|
|
305
372
|
}
|
|
306
373
|
async function atomicWrite(path, text) {
|
|
@@ -310,33 +377,27 @@ async function atomicWrite(path, text) {
|
|
|
310
377
|
}
|
|
311
378
|
export async function persistAsyncTicket(ticket, state, stateRoot = '.') {
|
|
312
379
|
const ledger = ensureLedger(stateRoot);
|
|
313
|
-
ledger.createWorkflowRun({
|
|
314
|
-
id: ticket.id,
|
|
315
|
-
goal: ticket.objective,
|
|
316
|
-
ticket,
|
|
317
|
-
state
|
|
318
|
-
});
|
|
380
|
+
ledger.createWorkflowRun({ id: ticket.id, goal: ticket.objective, ticket, state });
|
|
319
381
|
const dir = await asyncDir(stateRoot);
|
|
320
382
|
const p = join(dir, `${ticket.id}.json`);
|
|
321
|
-
await atomicWrite(p, JSON.stringify({
|
|
322
|
-
ticket,
|
|
323
|
-
state
|
|
324
|
-
}, null, 2));
|
|
383
|
+
await atomicWrite(p, JSON.stringify({ ticket, state }, null, 2));
|
|
325
384
|
return p;
|
|
326
385
|
}
|
|
327
386
|
export async function loadAsyncTicket(ticketId, stateRoot = '.') {
|
|
387
|
+
// 账本优先;无账本的 root 回退旧 json 文件(只读,兼容存量已交付工单)
|
|
328
388
|
const ledger = openLedgerIfExists(stateRoot);
|
|
329
389
|
const run = ledger?.getWorkflowRun(ticketId);
|
|
330
390
|
if (run) {
|
|
331
391
|
return {
|
|
332
392
|
ticket: JSON.parse(run.ticket_json),
|
|
333
|
-
state: JSON.parse(run.state_json)
|
|
393
|
+
state: JSON.parse(run.state_json),
|
|
334
394
|
};
|
|
335
395
|
}
|
|
336
396
|
try {
|
|
337
397
|
const p = join(await asyncDir(stateRoot), `${ticketId}.json`);
|
|
338
398
|
return JSON.parse(await readFile(p, 'utf-8'));
|
|
339
|
-
}
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
340
401
|
return null;
|
|
341
402
|
}
|
|
342
403
|
}
|
|
@@ -346,31 +407,43 @@ export async function settleAsyncTicket(ticketId, reply, stateRoot = '.', outcom
|
|
|
346
407
|
if (ledger && !terminalOutcome) {
|
|
347
408
|
throw new Error(`workflow ${ticketId} 缺少可恢复的 ${ASYNC_TERMINAL_SCHEMA},拒绝误结算`);
|
|
348
409
|
}
|
|
349
|
-
if (terminalOutcome?.status === 'failed')
|
|
350
|
-
|
|
410
|
+
if (terminalOutcome?.status === 'failed')
|
|
411
|
+
ledger?.failWorkflowRun(ticketId, reply, terminalOutcome);
|
|
412
|
+
else
|
|
413
|
+
ledger?.settleWorkflowRun(ticketId, reply, terminalOutcome);
|
|
351
414
|
const dir = await asyncDir(stateRoot);
|
|
352
415
|
const p = join(dir, `${ticketId}.deliverable.md`);
|
|
353
416
|
await atomicWrite(p, reply);
|
|
354
417
|
return p;
|
|
355
418
|
}
|
|
356
419
|
function recoverAsyncTerminalOutcome(ledger, ticketId) {
|
|
357
|
-
if (!ledger)
|
|
420
|
+
if (!ledger)
|
|
421
|
+
return undefined;
|
|
358
422
|
const run = ledger.getWorkflowRun(ticketId);
|
|
359
|
-
if (!run)
|
|
423
|
+
if (!run)
|
|
424
|
+
return undefined;
|
|
360
425
|
try {
|
|
361
426
|
const state = JSON.parse(run.state_json);
|
|
362
|
-
if (!state.spec)
|
|
427
|
+
if (!state.spec)
|
|
428
|
+
return terminalOutcomeFromState(ticketId, state);
|
|
363
429
|
const solveStep = ledger.getWorkflowStep(ticketId, 'solve');
|
|
364
|
-
if (solveStep?.status !== 'done' || !solveStep.result)
|
|
430
|
+
if (solveStep?.status !== 'done' || !solveStep.result)
|
|
431
|
+
return undefined;
|
|
365
432
|
state.solve = JSON.parse(solveStep.result);
|
|
366
|
-
state.gates = state.gates.filter(
|
|
433
|
+
state.gates = state.gates.filter(g => !g.id.startsWith('async-'));
|
|
367
434
|
return terminalOutcomeFromState(ticketId, state);
|
|
368
|
-
}
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
369
437
|
return undefined;
|
|
370
438
|
}
|
|
371
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* 可日志化 solve 端口(ADR-15 步骤日志,intent-before-execute):
|
|
442
|
+
* 先记 intent 再执行,done 后结果落账本——任意进程恢复时 done 的步骤直接复用
|
|
443
|
+
* 结果不重执行(exactly-once:LLM/求解调用不重复花钱),intent 悬挂的重试。
|
|
444
|
+
*/
|
|
372
445
|
export function makeJournaledSolvePort(ledger, runId, solve, opts) {
|
|
373
|
-
return async (spec)=>{
|
|
446
|
+
return async (spec) => {
|
|
374
447
|
const step = ledger.getWorkflowStep(runId, 'solve');
|
|
375
448
|
if (step?.status === 'done' && step.result) {
|
|
376
449
|
return JSON.parse(step.result);
|
|
@@ -382,30 +455,48 @@ export function makeJournaledSolvePort(ledger, runId, solve, opts) {
|
|
|
382
455
|
return r;
|
|
383
456
|
};
|
|
384
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* probePoi:从 user msg 探测"查 POI/酒店" 信号,返回关键词。
|
|
460
|
+
* 触发的不是关键词,是**结构**;关键词抓取有方向性——
|
|
461
|
+
* 1) 显式搜索动词(查/搜/找/看看/推荐/告诉我/检索)+ 宾语在动词后;
|
|
462
|
+
* 2) 住宿名词(酒店/民宿/客栈/饭店):优先取名词**之后**的名称段(须含拉丁/数字,
|
|
463
|
+
* 「我订了酒店:The Title…」→ The Title…);退路是名词前 2-6 字紧邻地名
|
|
464
|
+
* (「大理酒店」→ 大理;「机票和」类垃圾后缀拒);
|
|
465
|
+
* 3) 开放问(有什么/玩什么/有哪些):取名词前的地名;
|
|
466
|
+
* 4) 短裸地名:≤12 字、无标点、无陈述动词(「大理」✓;「明天有空」「我的工作时间是…」✗——
|
|
467
|
+
* 2026-08-28 巡检收紧:旧版 ≤24 直通把访谈答案整句当关键词,垃圾 hbcli 调用+证据噪音)。
|
|
468
|
+
* 不是意图(改求解器),只是 datasources 编排层的"提早调 anything"提示。
|
|
469
|
+
*/
|
|
385
470
|
export function probePoi(msg) {
|
|
386
471
|
const trimmed = msg.trim();
|
|
387
|
-
if (!trimmed)
|
|
388
|
-
|
|
472
|
+
if (!trimmed)
|
|
473
|
+
return null;
|
|
474
|
+
const cutAtPunct = (s) => s.split(/[,,。.;;!!??]/)[0]?.trim() ?? '';
|
|
475
|
+
// 1) 显式搜索动词 + 宾语
|
|
389
476
|
const m = trimmed.match(/(查一下|查|搜|找|看看|推荐|告诉我|检索)\s*(.{2,24})/);
|
|
390
477
|
if (m?.[2]) {
|
|
391
478
|
const obj = cutAtPunct(m[2]);
|
|
392
|
-
if (obj.length >= 2)
|
|
479
|
+
if (obj.length >= 2)
|
|
480
|
+
return obj.slice(0, 24);
|
|
393
481
|
}
|
|
482
|
+
// 2) 住宿名词:名称段在名词后
|
|
394
483
|
if (/(酒店|民宿|客栈|饭店)/.test(trimmed)) {
|
|
395
484
|
const parts = trimmed.split(/(酒店|民宿|客栈|饭店)/);
|
|
396
485
|
const after = cutAtPunct((parts[2] ?? '').replace(/^[的::\s]+/, ''));
|
|
397
|
-
if (after.length >= 2 && /[A-Za-z0-9]/.test(after))
|
|
486
|
+
if (after.length >= 2 && /[A-Za-z0-9]/.test(after))
|
|
487
|
+
return after.slice(0, 24);
|
|
398
488
|
const nounAt = trimmed.search(/(酒店|民宿|客栈|饭店)/);
|
|
399
489
|
const pre = trimmed.slice(Math.max(0, nounAt - 6), nounAt).match(/[一-龥]{2,6}$/);
|
|
400
|
-
if (pre && !/(机票|车票|和|做|订|的)$/.test(pre[0]))
|
|
490
|
+
if (pre && !/(机票|车票|和|做|订|的)$/.test(pre[0]))
|
|
491
|
+
return pre[0];
|
|
401
492
|
}
|
|
493
|
+
// 3) 开放问:地名在疑问词前
|
|
402
494
|
const q = trimmed.match(/([一-龥a-zA-Z]{2,12})\s*(?:有什么|玩什么|有哪些)/);
|
|
403
|
-
if (q?.[1])
|
|
495
|
+
if (q?.[1])
|
|
496
|
+
return q[1];
|
|
497
|
+
// 4) 短裸地名(≤12 字,无标点,无陈述动词)
|
|
404
498
|
if (trimmed.length <= 12 && !/[,,。.;;!!??::]/.test(trimmed) && !/[是有在要想得到说订吧呢]/.test(trimmed)) {
|
|
405
499
|
return trimmed.slice(0, 24);
|
|
406
500
|
}
|
|
407
501
|
return null;
|
|
408
502
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
//# sourceURL=ts/src/loop.ts
|