@danceiny/gotry 0.0.1-rc.10
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/LICENSE +21 -0
- package/README.md +267 -0
- package/bin/gotry-inner.js +214 -0
- package/bin/gotry-stdio-ask.js +59 -0
- package/bin/gotry.js +28 -0
- package/cordis.gotry-patch.yml +86 -0
- package/data/flights_2026.json +218 -0
- package/data/golden_erhai.json +92 -0
- package/data/golden_trip_2026.json +75 -0
- package/data/hotels_2026.json +151 -0
- package/data/openflights-skeleton.json +895 -0
- package/data/time-slot-eval.json +384 -0
- package/data/yunnan-pack.json +202 -0
- package/dist/capabilities/agent-reach-bridge.py +90 -0
- package/dist/capabilities/agent-reach-deep.js +172 -0
- package/dist/capabilities/agent-reach.js +204 -0
- package/dist/capabilities/anything.js +139 -0
- package/dist/capabilities/flyai.js +131 -0
- package/dist/capabilities/hbcli.js +143 -0
- package/dist/capabilities/incident-log.js +100 -0
- package/dist/capabilities/opensky.js +74 -0
- package/dist/capabilities/session/action-cache.js +120 -0
- package/dist/capabilities/session/adapters/ctrip-flight.js +83 -0
- package/dist/capabilities/session/adapters/meituan-local.js +73 -0
- package/dist/capabilities/session/extract.js +40 -0
- package/dist/capabilities/session/read-guard.js +99 -0
- package/dist/capabilities/session/transport.js +101 -0
- package/dist/capabilities/session-search.js +93 -0
- package/dist/capabilities/weather.js +180 -0
- package/dist/scripts/action-cache-tests.js +107 -0
- package/dist/scripts/agent-reach-deep-tests.js +54 -0
- package/dist/scripts/agent-reach-tests.js +38 -0
- package/dist/scripts/agent-reach-wrapper-tests.js +79 -0
- package/dist/scripts/anything-tests.js +95 -0
- package/dist/scripts/async-collect.js +37 -0
- package/dist/scripts/companion-tests.js +112 -0
- package/dist/scripts/diff-test.js +39 -0
- package/dist/scripts/engine-run.js +14 -0
- package/dist/scripts/engine-tests.js +45 -0
- package/dist/scripts/hbcli-tests.js +101 -0
- package/dist/scripts/incident-tests.js +96 -0
- package/dist/scripts/journey-tests.js +45 -0
- package/dist/scripts/ledger-tests.js +365 -0
- package/dist/scripts/ledger-workflow-crash.js +42 -0
- package/dist/scripts/memory-capture-tests.js +77 -0
- package/dist/scripts/memory-decay-tests.js +83 -0
- package/dist/scripts/memory-metrics.js +24 -0
- package/dist/scripts/nudge-digest.js +83 -0
- package/dist/scripts/opensky-check.js +20 -0
- package/dist/scripts/opensky-tests.js +52 -0
- package/dist/scripts/probe-poi-tests.js +88 -0
- package/dist/scripts/publish-preverify.js +68 -0
- package/dist/scripts/replay-async.js +44 -0
- package/dist/scripts/replay-real.js +38 -0
- package/dist/scripts/replay.js +116 -0
- package/dist/scripts/session-attach-diagnose.js +27 -0
- package/dist/scripts/session-attach-poc.js +65 -0
- package/dist/scripts/session-extract-tests.js +81 -0
- package/dist/scripts/session-tests.js +162 -0
- package/dist/scripts/skeleton-check.js +37 -0
- package/dist/scripts/skeleton-integration-test.js +22 -0
- package/dist/scripts/skills-contract-tests.js +85 -0
- package/dist/scripts/smoke.js +328 -0
- package/dist/scripts/state-cli-tests.js +136 -0
- package/dist/scripts/state-cli.js +248 -0
- package/dist/scripts/time-eval-tests.js +318 -0
- package/dist/scripts/travel-timeline-tests.js +123 -0
- package/dist/scripts/unified-tests.js +49 -0
- package/dist/scripts/weather-tests.js +48 -0
- package/dist/src/bridge.js +34 -0
- package/dist/src/companions.js +112 -0
- package/dist/src/contracts.js +64 -0
- package/dist/src/dsh-llm.js +174 -0
- package/dist/src/engine.js +331 -0
- package/dist/src/index.js +1239 -0
- package/dist/src/journey.js +147 -0
- package/dist/src/loop.js +360 -0
- package/dist/src/memory-capture.js +40 -0
- package/dist/src/memory-decay.js +31 -0
- package/dist/src/memory-utility.js +55 -0
- package/dist/src/mock-llm.js +103 -0
- package/dist/src/model.js +134 -0
- package/dist/src/slot-spec.js +165 -0
- package/dist/src/state-ledger.js +901 -0
- package/dist/src/time-anchor.js +100 -0
- package/dist/src/tool-packet.js +12 -0
- package/dist/src/travel-slots.js +144 -0
- package/dist/src/travel-timeline.js +78 -0
- package/dist/src/unified.js +538 -0
- package/dist/src/wish-pool.js +27 -0
- package/package.json +84 -0
- package/ts/capabilities/agent-reach-deep.ts +114 -0
- package/ts/capabilities/agent-reach.ts +158 -0
- package/ts/capabilities/anything.ts +201 -0
- package/ts/capabilities/flyai.ts +151 -0
- package/ts/capabilities/hbcli.ts +152 -0
- package/ts/capabilities/incident-log.ts +148 -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 +107 -0
- package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
- package/ts/capabilities/session/extract.ts +54 -0
- package/ts/capabilities/session/read-guard.ts +141 -0
- package/ts/capabilities/session/transport.ts +114 -0
- package/ts/capabilities/session-search.ts +129 -0
- package/ts/capabilities/weather.ts +176 -0
- package/ts/package.json +27 -0
- package/ts/scripts/async-collect.ts +50 -0
- package/ts/scripts/skeleton-check.ts +49 -0
- package/ts/scripts/skeleton-integration-test.ts +23 -0
- package/ts/scripts/state-cli.ts +201 -0
- package/ts/src/bridge.ts +44 -0
- package/ts/src/companions.ts +113 -0
- package/ts/src/contracts.ts +165 -0
- package/ts/src/dsh-llm.ts +155 -0
- package/ts/src/index.ts +968 -0
- package/ts/src/loop.ts +411 -0
- package/ts/src/memory-capture.ts +59 -0
- package/ts/src/memory-decay.ts +55 -0
- package/ts/src/memory-utility.ts +70 -0
- package/ts/src/mock-llm.ts +100 -0
- package/ts/src/model.ts +220 -0
- package/ts/src/slot-spec.ts +172 -0
- package/ts/src/state-ledger.ts +849 -0
- package/ts/src/time-anchor.ts +134 -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/unified.ts +582 -0
- package/ts/src/wish-pool.ts +66 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { init } from 'z3-solver';
|
|
2
|
+
import { hhmmToMin, minToHhmm } from './model.js';
|
|
3
|
+
let z3Promise = null;
|
|
4
|
+
async function getZ3() {
|
|
5
|
+
if (!z3Promise) z3Promise = (async ()=>(await init()).Context('main'))();
|
|
6
|
+
return z3Promise;
|
|
7
|
+
}
|
|
8
|
+
export function evaluateLeg(leg, svc) {
|
|
9
|
+
const wake = svc.depMin - leg.bufferMin - leg.originTransferMin;
|
|
10
|
+
const wakeDisplay = wake < 0 ? `${minToHhmm(wake + 1440)}(前一日)` : minToHhmm(wake);
|
|
11
|
+
const arriveStay = svc.arrMin + leg.destTransferMin;
|
|
12
|
+
const d2d = arriveStay - wake;
|
|
13
|
+
let energy;
|
|
14
|
+
if (leg.redEye && (leg.redEyeDurationMin ?? 0) > 0) {
|
|
15
|
+
const sleepH = ((leg.redEyeDurationMin ?? 0) - 60) / 60;
|
|
16
|
+
energy = Math.max(30, Math.min(75, 30 + 8 * sleepH));
|
|
17
|
+
} else {
|
|
18
|
+
energy = 100 - 2 * 8;
|
|
19
|
+
if (wake < 5 * 60) energy -= 30;
|
|
20
|
+
else if (wake < 6 * 60) energy -= 25;
|
|
21
|
+
if (arriveStay > 21 * 60) energy -= 10;
|
|
22
|
+
if (d2d > 6 * 60) energy -= 10;
|
|
23
|
+
energy = Math.max(0, energy);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
service: svc.id,
|
|
27
|
+
dep: minToHhmm(svc.depMin),
|
|
28
|
+
wake: wakeDisplay,
|
|
29
|
+
wakeMin: wake,
|
|
30
|
+
arrive_stay: minToHhmm(arriveStay),
|
|
31
|
+
door_to_door: `${Math.floor(d2d / 60)}h${String(d2d % 60).padStart(2, '0')}m`,
|
|
32
|
+
energy_pct: Math.round(energy),
|
|
33
|
+
price_cny: svc.priceCny
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function exactlyOne(z3, sels) {
|
|
37
|
+
const { If, Sum, Int } = z3;
|
|
38
|
+
return Sum(...sels.map((s)=>If(s, Int.val(1), Int.val(0)))).eq(Int.val(1));
|
|
39
|
+
}
|
|
40
|
+
export async function solveJourney(req) {
|
|
41
|
+
const z3 = await getZ3();
|
|
42
|
+
const { Bool, If, Sum, Int, Solver } = z3;
|
|
43
|
+
const wakeFloor = req.wakeFloorMin ?? hhmmToMin('06:00');
|
|
44
|
+
const allSels = {};
|
|
45
|
+
const assertions = {};
|
|
46
|
+
const legSpecs = req.legs;
|
|
47
|
+
for (const leg of legSpecs){
|
|
48
|
+
const sels = leg.services.map((_, i)=>Bool.const(`${leg.id}_s${i}`));
|
|
49
|
+
allSels[leg.id] = sels;
|
|
50
|
+
const pick = (attr)=>Sum(...sels.map((s, i)=>If(s, Int.val(leg.services[i][attr]), Int.val(0))));
|
|
51
|
+
const dep = pick('depMin'), arr = pick('arrMin'), price = pick('priceCny');
|
|
52
|
+
const wake = dep.sub(Int.val(leg.bufferMin + leg.originTransferMin));
|
|
53
|
+
const arriveStay = arr.add(Int.val(leg.destTransferMin));
|
|
54
|
+
if (leg.arriveByMin !== undefined) assertions[`${leg.id}:arrive_by`] = arriveStay.le(Int.val(leg.arriveByMin));
|
|
55
|
+
if (leg.departAfterMin !== undefined) assertions[`${leg.id}:depart_after`] = dep.ge(Int.val(leg.departAfterMin));
|
|
56
|
+
if (!leg.redEye) assertions[`${leg.id}:wake_floor`] = wake.ge(Int.val(wakeFloor));
|
|
57
|
+
}
|
|
58
|
+
const total = Sum(...legSpecs.flatMap((leg)=>allSels[leg.id].map((s, i)=>If(s, Int.val(leg.services[i].priceCny), Int.val(0)))));
|
|
59
|
+
if (req.budgetCny !== undefined) assertions['total:budget'] = total.le(Int.val(req.budgetCny));
|
|
60
|
+
const coreOf = (s)=>{
|
|
61
|
+
const v = s.unsatCore();
|
|
62
|
+
const out = [];
|
|
63
|
+
for(let i = 0; i < v.length(); i++){
|
|
64
|
+
out.push(String(v.get(i)).replace(/^\|/, '').replace(/\|$/, ''));
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
};
|
|
68
|
+
const extract = async (model)=>{
|
|
69
|
+
const chosen = new Map();
|
|
70
|
+
for (const leg of legSpecs){
|
|
71
|
+
for(let i = 0; i < allSels[leg.id].length; i++){
|
|
72
|
+
const v = await maybeAwait(model.eval(allSels[leg.id][i], true));
|
|
73
|
+
if (String(v) === 'true') chosen.set(leg.id, leg.services[i]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return chosen;
|
|
77
|
+
};
|
|
78
|
+
const s = new Solver();
|
|
79
|
+
for (const sel of Object.values(allSels))s.add(exactlyOne(z3, sel));
|
|
80
|
+
for (const [name, expr] of Object.entries(assertions))s.addAndTrack(expr, name);
|
|
81
|
+
if (String(await s.check()) !== 'unsat') {
|
|
82
|
+
const chosen = await extract(s.model());
|
|
83
|
+
const legReports = legSpecs.map((leg)=>evaluateLeg(leg, chosen.get(leg.id)));
|
|
84
|
+
const money = legReports.reduce((a, r)=>a + r.price_cny, 0);
|
|
85
|
+
const redFlags = [
|
|
86
|
+
...legSpecs.map((leg, i)=>leg.redEye && legReports[i].energy_pct < 50 ? `${leg.id} 落地精力仅 ${legReports[i].energy_pct}%(红眼后直奔事务,当日不宜安排重要会议)` : '').filter(Boolean),
|
|
87
|
+
...legSpecs.map((leg, i)=>!leg.redEye && legReports[i].wakeMin >= 0 && legReports[i].wakeMin < 6 * 60 ? `${leg.id} 起床 ${legReports[i].wake}(早于 6:00,生物钟代价)` : '').filter(Boolean)
|
|
88
|
+
];
|
|
89
|
+
return {
|
|
90
|
+
feasible: true,
|
|
91
|
+
money_cny: money,
|
|
92
|
+
legs: legReports,
|
|
93
|
+
red_flags: redFlags
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const core = coreOf(s).sort();
|
|
97
|
+
const suggestions = [];
|
|
98
|
+
for (const name of core){
|
|
99
|
+
const s2 = new Solver();
|
|
100
|
+
for (const sel of Object.values(allSels))s2.add(exactlyOne(z3, sel));
|
|
101
|
+
for (const [n2, expr] of Object.entries(assertions)){
|
|
102
|
+
if (n2 !== name) s2.addAndTrack(expr, n2);
|
|
103
|
+
}
|
|
104
|
+
if (String(await s2.check()) !== 'unsat') {
|
|
105
|
+
const chosen = await extract(s2.model());
|
|
106
|
+
const rep = legSpecs.map((leg)=>evaluateLeg(leg, chosen.get(leg.id)));
|
|
107
|
+
suggestions.push({
|
|
108
|
+
relax: name,
|
|
109
|
+
money_cny: rep.reduce((a, r)=>a + r.price_cny, 0),
|
|
110
|
+
legs: rep.map((r, i)=>({
|
|
111
|
+
...r,
|
|
112
|
+
leg: legSpecs[i].id
|
|
113
|
+
}))
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
feasible: false,
|
|
119
|
+
unsat_core: core,
|
|
120
|
+
suggestions
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function maybeAwait(v) {
|
|
124
|
+
return v instanceof Promise ? await v : v;
|
|
125
|
+
}
|
|
126
|
+
export function parseFlightPackLegs(pack) {
|
|
127
|
+
const legs = pack['legs'].map((l)=>({
|
|
128
|
+
id: String(l['id']),
|
|
129
|
+
note: l['note'] ? String(l['note']) : undefined,
|
|
130
|
+
services: l['services'].map((sv)=>({
|
|
131
|
+
id: String(sv['id']),
|
|
132
|
+
depMin: hhmmToMin(String(sv['dep'])),
|
|
133
|
+
arrMin: hhmmToMin(String(sv['arr'])),
|
|
134
|
+
priceCny: Number(sv['price_cny'])
|
|
135
|
+
})),
|
|
136
|
+
bufferMin: Number(l['buffer_min']),
|
|
137
|
+
originTransferMin: Number(l['origin_transfer_min']),
|
|
138
|
+
destTransferMin: Number(l['dest_transfer_min']),
|
|
139
|
+
arriveByMin: l['arrive_by'] ? hhmmToMin(String(l['arrive_by'])) : undefined,
|
|
140
|
+
redEye: Boolean(l['red_eye']),
|
|
141
|
+
redEyeDurationMin: Number(l['red_eye_duration_min'] ?? 0)
|
|
142
|
+
}));
|
|
143
|
+
return legs;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/journey.ts
|
package/dist/src/loop.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { anythingSearch } from '../capabilities/anything.js';
|
|
2
|
+
export function newState(year = 2026) {
|
|
3
|
+
return {
|
|
4
|
+
calendar: {
|
|
5
|
+
year,
|
|
6
|
+
assertedWeekdays: {}
|
|
7
|
+
},
|
|
8
|
+
profile: {},
|
|
9
|
+
gates: [],
|
|
10
|
+
wishes: []
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function interviewNext(state) {
|
|
14
|
+
const p = state.profile;
|
|
15
|
+
const questions = [];
|
|
16
|
+
const ww = p.workWindow;
|
|
17
|
+
if (!p.workWindow && !ww?.vacation) {
|
|
18
|
+
questions.push({
|
|
19
|
+
key: 'workWindow',
|
|
20
|
+
text: '你这两周要远程办公——工作时间是几点到几点、按哪个时区?这决定每天的可玩时段。',
|
|
21
|
+
why: '工作窗口直接决定每日节奏(普吉 13:00-22:00 与 9:00-18:00 是两种人生)'
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (!p.bookedResources) {
|
|
25
|
+
questions.push({
|
|
26
|
+
key: 'bookedResources',
|
|
27
|
+
text: '已经有订好的航班或酒店吗?(哪怕只是意向)——已订资源是硬锚点,先告诉我再规划。',
|
|
28
|
+
why: '已订资源决定哪些段不可动,Kimi 式规划会绕开它们重排'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (!p.budgetTier) {
|
|
32
|
+
questions.push({
|
|
33
|
+
key: 'budgetTier',
|
|
34
|
+
text: '预算档位:经济(¥12.6k 级)/ 舒适(¥16.3k 级)/ 便利优先?',
|
|
35
|
+
why: '分层预算决定班次与住宿的选型口径',
|
|
36
|
+
options: [
|
|
37
|
+
{
|
|
38
|
+
label: '经济',
|
|
39
|
+
tradeOff: '省钱,接驳多走路'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: '舒适',
|
|
43
|
+
tradeOff: 'workation 办公质量优先'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: '便利优先',
|
|
47
|
+
tradeOff: '时间最省'
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
questions,
|
|
54
|
+
missing: questions.map((q)=>q.key)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function validateSpec(spec) {
|
|
58
|
+
if (!Array.isArray(spec.segments) || spec.segments.length === 0) return '没有段(segments)';
|
|
59
|
+
for (const seg of spec.segments){
|
|
60
|
+
if (!seg.id) return '存在缺少 id 的段';
|
|
61
|
+
if (!Array.isArray(seg.options) || seg.options.length === 0) return `段 ${seg.id} 没有可选方案`;
|
|
62
|
+
for (const opt of seg.options){
|
|
63
|
+
const svcs = opt.move?.services;
|
|
64
|
+
if (!Array.isArray(svcs) || svcs.length === 0) return `段 ${seg.id} 的方案 ${opt.id} 缺少班次(services)`;
|
|
65
|
+
for (const s of svcs){
|
|
66
|
+
if (typeof s.depMin !== 'number' || typeof s.arrMin !== 'number') {
|
|
67
|
+
return `段 ${seg.id}/${opt.id} 的班次 ${s.id ?? '?'} 缺少时刻(depMin/arrMin)`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
export function isComplex(state) {
|
|
75
|
+
const p = state.profile;
|
|
76
|
+
return Boolean(p.workWindow && p.bookedResources) && (state.gates.length > 0 || Boolean(state.spec));
|
|
77
|
+
}
|
|
78
|
+
export async function requestDeepPlanning(state) {
|
|
79
|
+
const ticket = {
|
|
80
|
+
id: `dp-${Date.now().toString(36)}`,
|
|
81
|
+
objective: '生成已验证的行程方案:全成本核算 + 动机/约束匹配 + 不失望四条',
|
|
82
|
+
requestedAt: new Date().toISOString(),
|
|
83
|
+
etaLabel: '约 1 小时(mock 期:秒级)'
|
|
84
|
+
};
|
|
85
|
+
state.gates.push({
|
|
86
|
+
id: `async-${ticket.id}`,
|
|
87
|
+
question: `深度规划已启动(${ticket.etaLabel})`,
|
|
88
|
+
options: []
|
|
89
|
+
});
|
|
90
|
+
const reply = '这趟行程跨度大(两周 workation + 三城 + 红眼返程),我切到**深度规划模式**:后台做多轮校验' + '(锚点/工作窗口/全成本/负例排查),做好后通知你。**先留一道选择题给你**,回来直接定:' + '\n1. 预算档位?(经济/舒适/便利优先)';
|
|
91
|
+
return {
|
|
92
|
+
reply,
|
|
93
|
+
ticket
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export async function collectDeepPlanning(state, ticket, solve) {
|
|
97
|
+
const spec = state.spec;
|
|
98
|
+
if (!spec) return {
|
|
99
|
+
reply: '(内部状态缺失 spec——深度规划未就绪,这是不应发生的路径)',
|
|
100
|
+
state
|
|
101
|
+
};
|
|
102
|
+
state.solve = await solve(spec);
|
|
103
|
+
state.gates = state.gates.filter((g)=>!g.id.startsWith('async-'));
|
|
104
|
+
const checks = {
|
|
105
|
+
'1_承诺时间后必有明确产物': Boolean(state.solve.legs?.length || state.solve.verdicts?.length),
|
|
106
|
+
'2_产物通过自检清单': state.solve.feasible ? state.solve.legs?.every((l)=>l['energy_pct'] !== undefined) ?? false : Boolean(state.solve.unsat_core?.length),
|
|
107
|
+
'3_待决问题全部是简单选择题': state.gates.every((g)=>g.id === 'budget' || g.options.length >= 2),
|
|
108
|
+
'4_做不到的诚实说': state.solve.feasible || Boolean(state.solve.suggestions?.length)
|
|
109
|
+
};
|
|
110
|
+
const head = `# 回访交付:${ticket.objective}\n(工单 ${ticket.id},不失望四条:${Object.values(checks).every(Boolean) ? '4/4 ✅' : '有未达项 ❌'})`;
|
|
111
|
+
return {
|
|
112
|
+
reply: `${head}\n\n${renderSolve(state)}`,
|
|
113
|
+
state
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function renderSolve(state) {
|
|
117
|
+
const s = state.solve;
|
|
118
|
+
if (!s) return '(无求解结果)';
|
|
119
|
+
const lines = [];
|
|
120
|
+
if (s.feasible) {
|
|
121
|
+
lines.push(`**方案可行,机票合计 ¥${s.money_cny}**`);
|
|
122
|
+
for (const lg of s.legs ?? []){
|
|
123
|
+
const l = lg;
|
|
124
|
+
lines.push(`- ${l['leg']} ${l['service']}:${l['dep']} 起飞,${l['wake']} 出发,${l['arrive_stay']} 到,` + `门到门 ${l['door_to_door']},落地精力 ${l['energy_pct']}%,¥${l['price_cny']}`);
|
|
125
|
+
}
|
|
126
|
+
for (const e of s.work_window_exclusions ?? []){
|
|
127
|
+
lines.push(`- 已排除 ${e.option}(工作窗口):${e.reason}`);
|
|
128
|
+
}
|
|
129
|
+
for (const n of s.skeleton_notes ?? [])lines.push(`- ${n}`);
|
|
130
|
+
for (const f of s.red_flags ?? [])lines.push(`- ⚠️ ${f}`);
|
|
131
|
+
} else {
|
|
132
|
+
lines.push(`**当前约束下不可行——冲突:${(s.unsat_core ?? []).join('、')}`);
|
|
133
|
+
for (const sg of s.suggestions ?? [])lines.push(`- 放宽「${sg.relax}」可解(约 ¥${sg.money_cny})`);
|
|
134
|
+
}
|
|
135
|
+
if (state.gates.length) {
|
|
136
|
+
lines.push('', '**待你决定(选择题)**');
|
|
137
|
+
for (const g of state.gates){
|
|
138
|
+
lines.push(`- ${g.question} → ${g.options.map((o)=>o.label + (o.tradeOff ? `(${o.tradeOff})` : '')).join(' / ')}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return lines.join('\n');
|
|
142
|
+
}
|
|
143
|
+
export async function runTurn(state, userMsg, llm, history = [], solve = null) {
|
|
144
|
+
const facts = await llm.extractFacts([
|
|
145
|
+
...history,
|
|
146
|
+
{
|
|
147
|
+
role: 'user',
|
|
148
|
+
text: userMsg
|
|
149
|
+
}
|
|
150
|
+
], state);
|
|
151
|
+
const conflicts = [];
|
|
152
|
+
if (facts.calendar?.assertedWeekdays) {
|
|
153
|
+
for (const [date, wd] of Object.entries(facts.calendar.assertedWeekdays)){
|
|
154
|
+
const prev = state.calendar.assertedWeekdays[date];
|
|
155
|
+
if (prev && prev !== wd) {
|
|
156
|
+
conflicts.push(`${date}:已有断言 ${prev},新说法 ${wd}——以先断言为准,如需改请明确说`);
|
|
157
|
+
} else {
|
|
158
|
+
state.calendar.assertedWeekdays[date] = wd;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
Object.assign(state.profile, facts.profile ?? {});
|
|
163
|
+
const { questions } = interviewNext(state);
|
|
164
|
+
const blocking = questions.filter((q)=>q.key !== 'budgetTier');
|
|
165
|
+
const budgetQ = questions.find((q)=>q.key === 'budgetTier');
|
|
166
|
+
if (budgetQ && !state.gates.some((g)=>g.id === 'budget')) {
|
|
167
|
+
state.gates.push({
|
|
168
|
+
id: 'budget',
|
|
169
|
+
question: budgetQ.text,
|
|
170
|
+
options: budgetQ.options ?? [
|
|
171
|
+
{
|
|
172
|
+
label: '经济'
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
label: '舒适'
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
label: '便利优先'
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const parts = [];
|
|
184
|
+
if (conflicts.length) parts.push(`⚠️ 日历冲突:\n${conflicts.map((c)=>`- ${c}`).join('\n')}`);
|
|
185
|
+
const poiProbe = probePoi(userMsg);
|
|
186
|
+
if (poiProbe) {
|
|
187
|
+
const ar = await anythingSearch({
|
|
188
|
+
keyword: poiProbe
|
|
189
|
+
});
|
|
190
|
+
if (ar.verdict === 'hit' && ar.hits && ar.hits.length > 0) {
|
|
191
|
+
const top = ar.hits.slice(0, 5);
|
|
192
|
+
const lines = top.map((h, i)=>{
|
|
193
|
+
const latlng = h.latitude !== undefined && h.longitude !== undefined ? ` @ (${h.latitude.toFixed(3)},${h.longitude.toFixed(3)})` : '';
|
|
194
|
+
return ` ${i + 1}. [${h.type}] ${h.name}${latlng}`;
|
|
195
|
+
}).join('\n');
|
|
196
|
+
parts.push(`**${poiProbe} → hit (${ar.hits.length} 候选项)**\n${lines}\n${ar.evidence}`);
|
|
197
|
+
} else if (ar.verdict === 'miss') {
|
|
198
|
+
parts.push(`**${poiProbe} → miss** (酒店-be Anything 一切正常但无候选)\n${ar.evidence}`);
|
|
199
|
+
} else {
|
|
200
|
+
parts.push(`**${poiProbe} → unavailable** (${ar.error ?? 'hbcli 不可达'})\n${ar.evidence}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const q of blocking)parts.push(await llm.polishQuestion(q));
|
|
204
|
+
if (blocking.length === 0 && solve) {
|
|
205
|
+
const spec = await llm.extractSpec([
|
|
206
|
+
...history,
|
|
207
|
+
{
|
|
208
|
+
role: 'user',
|
|
209
|
+
text: userMsg
|
|
210
|
+
}
|
|
211
|
+
], state);
|
|
212
|
+
if (spec && spec.note === 'erhai-candidates') {
|
|
213
|
+
const { readFile } = await import('node:fs/promises');
|
|
214
|
+
const { join } = await import('node:path');
|
|
215
|
+
try {
|
|
216
|
+
const raw = JSON.parse(await readFile(join(import.meta.dirname, '..', '..', 'data', 'golden_erhai.json'), 'utf-8'));
|
|
217
|
+
const { parseCandidate, parseRequest } = await import('./model.js');
|
|
218
|
+
const { segmentsFromCandidate, solveChoiceSegment } = await import('./unified.js');
|
|
219
|
+
const req = parseRequest(raw['request']);
|
|
220
|
+
const cands = raw['candidates'].map(parseCandidate);
|
|
221
|
+
const erhaiSpec = segmentsFromCandidate(req, cands);
|
|
222
|
+
const result = solveChoiceSegment(erhaiSpec, req);
|
|
223
|
+
state.solve = result;
|
|
224
|
+
parts.push(result.answer_md ?? '(候选求解完成)');
|
|
225
|
+
} catch {
|
|
226
|
+
parts.push('(洱海候选求解暂不可用——退回访谈)');
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
const invalid = spec ? validateSpec(spec) : '翻译器未产出 spec';
|
|
230
|
+
if (spec && !invalid) {
|
|
231
|
+
const ext = await llm.extractSlots([
|
|
232
|
+
...history,
|
|
233
|
+
{
|
|
234
|
+
role: 'user',
|
|
235
|
+
text: userMsg
|
|
236
|
+
}
|
|
237
|
+
]);
|
|
238
|
+
if (ext) {
|
|
239
|
+
const { resolveSlots, specDateMismatches } = await import('./slot-spec.js');
|
|
240
|
+
const { buildTimeAnchor } = await import('./time-anchor.js');
|
|
241
|
+
const mismatches = specDateMismatches(spec, resolveSlots(ext, buildTimeAnchor()));
|
|
242
|
+
if (mismatches.length > 0) {
|
|
243
|
+
const lines = mismatches.map((m)=>`- ${m.specAt}:翻译说 ${m.specDate},你的原话换算是 ${m.slotDate}(${m.field})`);
|
|
244
|
+
parts.push(`⚠️ 日期分歧,确认后我再算(以你为准):\n${lines.join('\n')}`);
|
|
245
|
+
return {
|
|
246
|
+
reply: parts.join('\n\n'),
|
|
247
|
+
state
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
state.spec = spec;
|
|
252
|
+
state.solve = await solve(spec);
|
|
253
|
+
parts.push(llm.render ? await llm.render(state) : renderSolve(state));
|
|
254
|
+
} else if (blocking.length === 0 && invalid) {
|
|
255
|
+
parts.push(`(行程骨架还不完整:${invalid}——请补充对应信息,我不猜)`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} else if (blocking.length === 0) {
|
|
259
|
+
parts.push('(约束齐备——进入规划,S3 接线后此处产出方案与选择题)');
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
reply: parts.join('\n\n'),
|
|
263
|
+
state
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
import { join } from 'node:path';
|
|
267
|
+
import { mkdir, rename, writeFile, readFile } from 'node:fs/promises';
|
|
268
|
+
import { ensureLedger, openLedgerIfExists } from './state-ledger.js';
|
|
269
|
+
async function asyncDir(stateRoot) {
|
|
270
|
+
const root = stateRoot === '.' ? process.cwd() : stateRoot;
|
|
271
|
+
const dir = join(root, 'gotry-state', 'async');
|
|
272
|
+
await mkdir(dir, {
|
|
273
|
+
recursive: true
|
|
274
|
+
});
|
|
275
|
+
return dir;
|
|
276
|
+
}
|
|
277
|
+
async function atomicWrite(path, text) {
|
|
278
|
+
const tmp = `${path}.tmp`;
|
|
279
|
+
await writeFile(tmp, text, 'utf-8');
|
|
280
|
+
await rename(tmp, path);
|
|
281
|
+
}
|
|
282
|
+
export async function persistAsyncTicket(ticket, state, stateRoot = '.') {
|
|
283
|
+
const ledger = ensureLedger(stateRoot);
|
|
284
|
+
ledger.createWorkflowRun({
|
|
285
|
+
id: ticket.id,
|
|
286
|
+
goal: ticket.objective,
|
|
287
|
+
ticket,
|
|
288
|
+
state
|
|
289
|
+
});
|
|
290
|
+
const dir = await asyncDir(stateRoot);
|
|
291
|
+
const p = join(dir, `${ticket.id}.json`);
|
|
292
|
+
await atomicWrite(p, JSON.stringify({
|
|
293
|
+
ticket,
|
|
294
|
+
state
|
|
295
|
+
}, null, 2));
|
|
296
|
+
return p;
|
|
297
|
+
}
|
|
298
|
+
export async function loadAsyncTicket(ticketId, stateRoot = '.') {
|
|
299
|
+
const ledger = openLedgerIfExists(stateRoot);
|
|
300
|
+
const run = ledger?.getWorkflowRun(ticketId);
|
|
301
|
+
if (run) {
|
|
302
|
+
return {
|
|
303
|
+
ticket: JSON.parse(run.ticket_json),
|
|
304
|
+
state: JSON.parse(run.state_json)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const p = join(await asyncDir(stateRoot), `${ticketId}.json`);
|
|
309
|
+
return JSON.parse(await readFile(p, 'utf-8'));
|
|
310
|
+
} catch {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
export async function settleAsyncTicket(ticketId, reply, stateRoot = '.') {
|
|
315
|
+
openLedgerIfExists(stateRoot)?.settleWorkflowRun(ticketId, reply);
|
|
316
|
+
const dir = await asyncDir(stateRoot);
|
|
317
|
+
const p = join(dir, `${ticketId}.deliverable.md`);
|
|
318
|
+
await atomicWrite(p, reply);
|
|
319
|
+
return p;
|
|
320
|
+
}
|
|
321
|
+
export function makeJournaledSolvePort(ledger, runId, solve, opts) {
|
|
322
|
+
return async (spec)=>{
|
|
323
|
+
const step = ledger.getWorkflowStep(runId, 'solve');
|
|
324
|
+
if (step?.status === 'done' && step.result) {
|
|
325
|
+
return JSON.parse(step.result);
|
|
326
|
+
}
|
|
327
|
+
ledger.markStepIntent(runId, 'solve');
|
|
328
|
+
opts?.onRealSolve?.();
|
|
329
|
+
const r = await solve(spec);
|
|
330
|
+
ledger.markStepDone(runId, 'solve', r);
|
|
331
|
+
return r;
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
export function probePoi(msg) {
|
|
335
|
+
const trimmed = msg.trim();
|
|
336
|
+
if (!trimmed) return null;
|
|
337
|
+
const cutAtPunct = (s)=>s.split(/[,,。.;;!!??]/)[0]?.trim() ?? '';
|
|
338
|
+
const m = trimmed.match(/(查一下|查|搜|找|看看|推荐|告诉我|检索)\s*(.{2,24})/);
|
|
339
|
+
if (m?.[2]) {
|
|
340
|
+
const obj = cutAtPunct(m[2]);
|
|
341
|
+
if (obj.length >= 2) return obj.slice(0, 24);
|
|
342
|
+
}
|
|
343
|
+
if (/(酒店|民宿|客栈|饭店)/.test(trimmed)) {
|
|
344
|
+
const parts = trimmed.split(/(酒店|民宿|客栈|饭店)/);
|
|
345
|
+
const after = cutAtPunct((parts[2] ?? '').replace(/^[的::\s]+/, ''));
|
|
346
|
+
if (after.length >= 2 && /[A-Za-z0-9]/.test(after)) return after.slice(0, 24);
|
|
347
|
+
const nounAt = trimmed.search(/(酒店|民宿|客栈|饭店)/);
|
|
348
|
+
const pre = trimmed.slice(Math.max(0, nounAt - 6), nounAt).match(/[一-龥]{2,6}$/);
|
|
349
|
+
if (pre && !/(机票|车票|和|做|订|的)$/.test(pre[0])) return pre[0];
|
|
350
|
+
}
|
|
351
|
+
const q = trimmed.match(/([一-龥a-zA-Z]{2,12})\s*(?:有什么|玩什么|有哪些)/);
|
|
352
|
+
if (q?.[1]) return q[1];
|
|
353
|
+
if (trimmed.length <= 12 && !/[,,。.;;!!??::]/.test(trimmed) && !/[是有在要想得到说订吧呢]/.test(trimmed)) {
|
|
354
|
+
return trimmed.slice(0, 24);
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/loop.ts
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function mergeProfile(current, patch) {
|
|
2
|
+
if (!patch) return null;
|
|
3
|
+
const base = {
|
|
4
|
+
weights: current?.weights ?? {},
|
|
5
|
+
evidence: current?.evidence ?? [],
|
|
6
|
+
hard: current?.hard ?? {}
|
|
7
|
+
};
|
|
8
|
+
const newEvidence = (patch.evidence ?? []).filter(Boolean).filter((e)=>!base.evidence.includes(e));
|
|
9
|
+
const patchWeights = patch.weights;
|
|
10
|
+
const weightsChanged = !!patchWeights && Object.keys(patchWeights).length > 0 && JSON.stringify(patchWeights) !== JSON.stringify(base.weights);
|
|
11
|
+
const hardChanged = !!patch.hard && Object.keys(patch.hard).length > 0 && JSON.stringify(patch.hard) !== JSON.stringify(base.hard);
|
|
12
|
+
if (newEvidence.length === 0 && !weightsChanged && !hardChanged) return null;
|
|
13
|
+
let weights = {
|
|
14
|
+
...base.weights
|
|
15
|
+
};
|
|
16
|
+
if (weightsChanged && newEvidence.length > 0 && patchWeights) {
|
|
17
|
+
weights = {
|
|
18
|
+
...weights,
|
|
19
|
+
...patchWeights
|
|
20
|
+
};
|
|
21
|
+
const sum = Object.values(weights).reduce((a, b)=>a + b, 0);
|
|
22
|
+
if (sum > 0) for (const k of Object.keys(weights))weights[k] = Math.round(weights[k] / sum * 100) / 100;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
weights,
|
|
26
|
+
evidence: [
|
|
27
|
+
...base.evidence,
|
|
28
|
+
...newEvidence
|
|
29
|
+
],
|
|
30
|
+
hard: hardChanged ? {
|
|
31
|
+
...base.hard,
|
|
32
|
+
...patch.hard ?? {}
|
|
33
|
+
} : {
|
|
34
|
+
...base.hard
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/memory-capture.ts
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function windowFactor(ageDays) {
|
|
2
|
+
if (ageDays <= 30) return 1.0;
|
|
3
|
+
if (ageDays <= 90) return 0.75;
|
|
4
|
+
if (ageDays <= 180) return 0.5;
|
|
5
|
+
if (ageDays <= 365) return 0.25;
|
|
6
|
+
return 0.1;
|
|
7
|
+
}
|
|
8
|
+
export const KIND_WEIGHT = {
|
|
9
|
+
recalled: 0.25,
|
|
10
|
+
applied: 0.5,
|
|
11
|
+
verified_outcome: 1.0
|
|
12
|
+
};
|
|
13
|
+
export function ageDaysOf(ts, now) {
|
|
14
|
+
const then = new Date(ts).getTime();
|
|
15
|
+
if (Number.isNaN(then)) return Number.POSITIVE_INFINITY;
|
|
16
|
+
return Math.max(0, (now.getTime() - then) / 86_400_000);
|
|
17
|
+
}
|
|
18
|
+
export function eventDecayScore(ev, now) {
|
|
19
|
+
const w = KIND_WEIGHT[ev.kind] ?? 0;
|
|
20
|
+
return round4(w * windowFactor(ageDaysOf(ev.ts, now)));
|
|
21
|
+
}
|
|
22
|
+
export function decayedConfidence(events, now) {
|
|
23
|
+
const sum = events.reduce((acc, ev)=>acc + eventDecayScore(ev, now), 0);
|
|
24
|
+
return round4(Math.min(1, sum));
|
|
25
|
+
}
|
|
26
|
+
function round4(x) {
|
|
27
|
+
return Math.round(x * 10_000) / 10_000;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/memory-decay.ts
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function makeEventId(ev) {
|
|
2
|
+
return [
|
|
3
|
+
ev.wish_id,
|
|
4
|
+
ev.kind,
|
|
5
|
+
ev.ctx ?? '',
|
|
6
|
+
ev.attribution ?? ''
|
|
7
|
+
].join('|');
|
|
8
|
+
}
|
|
9
|
+
export function appendEvent(events, ev) {
|
|
10
|
+
const full = {
|
|
11
|
+
schema: 'memory_utility_observation.v0',
|
|
12
|
+
event_id: makeEventId(ev),
|
|
13
|
+
...ev
|
|
14
|
+
};
|
|
15
|
+
if (events.some((e)=>e.event_id === full.event_id)) return {
|
|
16
|
+
events,
|
|
17
|
+
appended: false
|
|
18
|
+
};
|
|
19
|
+
if (ev.kind === 'verified_outcome' && !ev.attribution) {
|
|
20
|
+
return appendEvent(events, {
|
|
21
|
+
...ev,
|
|
22
|
+
kind: 'applied'
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
events: [
|
|
27
|
+
...events,
|
|
28
|
+
full
|
|
29
|
+
],
|
|
30
|
+
appended: true
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function projectUtility(events) {
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const ev of events){
|
|
36
|
+
const w = out[ev.wish_id] ??= {
|
|
37
|
+
wish_id: ev.wish_id,
|
|
38
|
+
status: 'unknown',
|
|
39
|
+
recalled: 0,
|
|
40
|
+
applied: 0,
|
|
41
|
+
verified: 0
|
|
42
|
+
};
|
|
43
|
+
if (ev.kind === 'recalled') w.recalled++;
|
|
44
|
+
if (ev.kind === 'applied') w.applied++;
|
|
45
|
+
if (ev.kind === 'verified_outcome') {
|
|
46
|
+
w.verified++;
|
|
47
|
+
if (ev.attribution) w.status = ev.attribution;
|
|
48
|
+
}
|
|
49
|
+
w.last_event_ts = ev.ts;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/memory-utility.ts
|