@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,318 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { buildTimeAnchor, parseAbsoluteDate } from '../src/time-anchor.js';
|
|
5
|
+
import { detectLanguage, flagExpiredSlots, scoreExtraction } from '../src/travel-slots.js';
|
|
6
|
+
import { resolveSlotDate, resolveSlots, specDateMismatches } from '../src/slot-spec.js';
|
|
7
|
+
import { createMockLlm } from '../src/mock-llm.js';
|
|
8
|
+
import { createOpenAICompatLlm } from '../src/dsh-llm.js';
|
|
9
|
+
const dataset = JSON.parse(readFileSync(join('..', 'data', 'time-slot-eval.json'), 'utf-8'));
|
|
10
|
+
const [ay, am, ad] = dataset.anchor_date.split('-').map(Number);
|
|
11
|
+
const ANCHOR_NOW = new Date(ay, am - 1, ad, 12);
|
|
12
|
+
{
|
|
13
|
+
const a = buildTimeAnchor(ANCHOR_NOW);
|
|
14
|
+
assert.equal(a.today, '2026-08-27', 'today');
|
|
15
|
+
assert.equal(a.todayWeekdayZh, '周四', '2026-08-27 是周四');
|
|
16
|
+
assert.match(a.tzLabel, /^UTC[+-]\d/, '时区标注格式');
|
|
17
|
+
const c = a.card;
|
|
18
|
+
assert.ok(c.includes('明天 2026-08-28 周五'), '明天');
|
|
19
|
+
assert.ok(c.includes('大后天 2026-08-30 周日'), '大后天');
|
|
20
|
+
assert.ok(c.includes('下周:周一 2026-08-31'), '下周一=08-31');
|
|
21
|
+
assert.ok(c.includes('周日 2026-09-06'), '下周日=09-06');
|
|
22
|
+
assert.ok(c.includes('周四 2026-09-10'), '下下周四=09-10');
|
|
23
|
+
assert.ok(c.includes('下个月 2026-09:初 09-01~09-10|中旬 09-11~09-20|下旬 09-21~09-30'), '下个月分段');
|
|
24
|
+
assert.ok(c.includes('下个季度初:2026-10-01 起'), '下个季度初');
|
|
25
|
+
assert.ok(c.includes('国庆 2026-10-01'), '国庆锚点');
|
|
26
|
+
assert.ok(c.includes('元旦 2027-01-01'), '元旦取下一次发生');
|
|
27
|
+
assert.ok(c.includes('春节 2027-02-06'), '春节锚点(2026 春节已过,取 2027)');
|
|
28
|
+
console.log('1. 锚点卡断言 OK(固定 2026-08-27:下周一/大后天/下下周四/月分段/季度/节日)');
|
|
29
|
+
const a2030 = buildTimeAnchor(new Date(2030, 5, 1, 12));
|
|
30
|
+
assert.ok(a2030.card.includes('春节 2031-01-23'), 'D-9 扩表:2030 年视视角春节取 2031-01-23');
|
|
31
|
+
console.log('1b. D-9 扩表断言 OK(2030 锚点 → 春节 2031-01-23,表覆盖至 2031)');
|
|
32
|
+
}{
|
|
33
|
+
const anchor = buildTimeAnchor(ANCHOR_NOW);
|
|
34
|
+
const base = {
|
|
35
|
+
schema_version: 'travel_slot_extraction.v1',
|
|
36
|
+
language: 'zh',
|
|
37
|
+
domains: [
|
|
38
|
+
'requisition'
|
|
39
|
+
],
|
|
40
|
+
slots: {
|
|
41
|
+
requisition: {
|
|
42
|
+
start_date: '8.1',
|
|
43
|
+
end_date: '8.10'
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
missing_slots: []
|
|
47
|
+
};
|
|
48
|
+
const flagged = flagExpiredSlots(base, anchor);
|
|
49
|
+
assert.deepEqual(flagged.missing_slots, [
|
|
50
|
+
'requisition.start_date (date is expired)'
|
|
51
|
+
], '8.1 在 08-27 锚点下过期');
|
|
52
|
+
assert.equal(flagged.slots.requisition.start_date, '8.1', '槽位原值逐字保留');
|
|
53
|
+
assert.equal(flagged.missing_slots.length, 1, 'end_date 不重复判(只判主日期字段,对齐 golden)');
|
|
54
|
+
const again = flagExpiredSlots(flagged, anchor);
|
|
55
|
+
assert.equal(again.missing_slots.length, 1, '幂等不重复加');
|
|
56
|
+
const future = flagExpiredSlots({
|
|
57
|
+
...base,
|
|
58
|
+
slots: {
|
|
59
|
+
requisition: {
|
|
60
|
+
start_date: '9.1'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}, anchor);
|
|
64
|
+
assert.equal(future.missing_slots.length, 0, '9.1 不过期');
|
|
65
|
+
const relative = flagExpiredSlots({
|
|
66
|
+
...base,
|
|
67
|
+
slots: {
|
|
68
|
+
requisition: {
|
|
69
|
+
start_date: '本周三'
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}, anchor);
|
|
73
|
+
assert.equal(relative.missing_slots.length, 0, '相对表达(本周三)永不判过期');
|
|
74
|
+
const iso = flagExpiredSlots({
|
|
75
|
+
...base,
|
|
76
|
+
slots: {
|
|
77
|
+
requisition: {
|
|
78
|
+
start_date: '2026-08-01'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}, anchor);
|
|
82
|
+
assert.equal(iso.missing_slots.length, 1, 'ISO 绝对日期判过期');
|
|
83
|
+
const zhDate = flagExpiredSlots({
|
|
84
|
+
...base,
|
|
85
|
+
slots: {
|
|
86
|
+
requisition: {
|
|
87
|
+
start_date: '8月5日'
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}, anchor);
|
|
91
|
+
assert.equal(zhDate.missing_slots.length, 1, '中文月日判过期');
|
|
92
|
+
const enDate = flagExpiredSlots({
|
|
93
|
+
...base,
|
|
94
|
+
slots: {
|
|
95
|
+
requisition: {
|
|
96
|
+
start_date: 'Aug-5'
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}, anchor);
|
|
100
|
+
assert.equal(enDate.missing_slots.length, 0, '英文月名不判过期(逐字传递,对齐 golden time_english_date_range)');
|
|
101
|
+
const flight = flagExpiredSlots({
|
|
102
|
+
...base,
|
|
103
|
+
domains: [
|
|
104
|
+
'flight'
|
|
105
|
+
],
|
|
106
|
+
slots: {
|
|
107
|
+
flight: {
|
|
108
|
+
departure_date: '8.1'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}, anchor);
|
|
112
|
+
assert.deepEqual(flight.missing_slots, [
|
|
113
|
+
'flight.departure_date (date is expired)'
|
|
114
|
+
], 'flight 主日期字段');
|
|
115
|
+
assert.equal(parseAbsoluteDate('下周一', 2026), null, 'parseAbsoluteDate 不碰相对表达');
|
|
116
|
+
for (const c of dataset.cases){
|
|
117
|
+
assert.equal(detectLanguage(c.utterance), c.expected.language, `detectLanguage ${c.id}`);
|
|
118
|
+
}
|
|
119
|
+
console.log('2. 过期校验断言 OK(8.1 过期/9.1 不/本周三不/ISO+中文月日判/英文不判/幂等)+ detectLanguage 全题对齐');
|
|
120
|
+
}{
|
|
121
|
+
const exp = dataset.cases[0].expected;
|
|
122
|
+
const identical = structuredClone(exp);
|
|
123
|
+
assert.equal(scoreExtraction(exp, identical).pass, true, '完全一致应 pass');
|
|
124
|
+
const wrongLang = {
|
|
125
|
+
...structuredClone(exp),
|
|
126
|
+
language: 'en'
|
|
127
|
+
};
|
|
128
|
+
assert.equal(scoreExtraction(exp, wrongLang).pass, false, 'language 不一致应 fail');
|
|
129
|
+
const wrongDomain = {
|
|
130
|
+
...structuredClone(exp),
|
|
131
|
+
domains: [
|
|
132
|
+
'flight'
|
|
133
|
+
]
|
|
134
|
+
};
|
|
135
|
+
assert.equal(scoreExtraction(exp, wrongDomain).pass, false, 'domains 不一致应 fail');
|
|
136
|
+
const wrongSlot = structuredClone(exp);
|
|
137
|
+
wrongSlot.slots.requisition.destination = '北京';
|
|
138
|
+
const r = scoreExtraction(exp, wrongSlot);
|
|
139
|
+
assert.equal(r.pass, false, '槽位值不一致应 fail');
|
|
140
|
+
assert.ok(r.diffs.some((d)=>d.includes('slots.requisition.destination')), 'diff 带路径');
|
|
141
|
+
const wrongMissing = {
|
|
142
|
+
...structuredClone(exp),
|
|
143
|
+
missing_slots: [
|
|
144
|
+
'requisition.start_date (date is expired)'
|
|
145
|
+
]
|
|
146
|
+
};
|
|
147
|
+
assert.equal(scoreExtraction(exp, wrongMissing).pass, false, 'missing_slots 不一致应 fail');
|
|
148
|
+
const extraKey = structuredClone(exp);
|
|
149
|
+
extraKey.slots.requisition['adults'] = 2;
|
|
150
|
+
const rk = scoreExtraction(exp, extraKey);
|
|
151
|
+
assert.equal(rk.pass, true, '多出键不 fail(非致命)');
|
|
152
|
+
assert.equal(rk.warnings.length, 1, '多出键记 warning');
|
|
153
|
+
console.log('3. 评分器自测 OK(pass/language/domains/槽位值/missing_slots/extra-key warning)');
|
|
154
|
+
}{
|
|
155
|
+
const script = dataset.cases.map((c)=>({
|
|
156
|
+
when: c.utterance,
|
|
157
|
+
extraction: c.expected
|
|
158
|
+
}));
|
|
159
|
+
const llm = createMockLlm(undefined, script);
|
|
160
|
+
let pass = 0;
|
|
161
|
+
for (const c of dataset.cases){
|
|
162
|
+
const ext = await llm.extractSlots([
|
|
163
|
+
{
|
|
164
|
+
role: 'user',
|
|
165
|
+
text: c.utterance
|
|
166
|
+
}
|
|
167
|
+
]);
|
|
168
|
+
assert.ok(ext, `mock 应命中 ${c.id}`);
|
|
169
|
+
const r = scoreExtraction(c.expected, ext);
|
|
170
|
+
assert.ok(r.pass, `mock 回放 ${c.id} 应 pass:${r.diffs.join(';')}`);
|
|
171
|
+
pass++;
|
|
172
|
+
}
|
|
173
|
+
console.log(`4. mock 回放管道 OK(${pass}/${dataset.cases.length}——评分管道自身正确)`);
|
|
174
|
+
}{
|
|
175
|
+
const anchor = buildTimeAnchor(ANCHOR_NOW);
|
|
176
|
+
const r = (expr)=>resolveSlotDate(expr, anchor);
|
|
177
|
+
assert.equal(r('2026-09-05').date, '2026-09-05', 'ISO');
|
|
178
|
+
assert.equal(r('8.20').date, '2026-08-20', '点分');
|
|
179
|
+
assert.equal(r('9月5日').date, '2026-09-05', '中文月日');
|
|
180
|
+
assert.equal(r('下周一').date, '2026-08-31', '下周一');
|
|
181
|
+
assert.equal(r('本周六').date, '2026-08-29', '本周六');
|
|
182
|
+
assert.equal(r('下下周四').date, '2026-09-10', '下下周四');
|
|
183
|
+
assert.equal(r('明天').date, '2026-08-28', '明天');
|
|
184
|
+
assert.equal(r('下个月中旬').date, '2026-09-11', '下个月中旬取首日');
|
|
185
|
+
assert.equal(r('下周五+3').date, '2026-09-07', '下周五+3');
|
|
186
|
+
assert.equal(r('8.20+2天').date, '2026-08-22', '8.20+2天');
|
|
187
|
+
assert.equal(r('近期').kind, 'unresolved', '模糊词');
|
|
188
|
+
assert.equal(r('这阵子').date, null, '模糊词不猜');
|
|
189
|
+
assert.equal(r('next Monday').kind, 'unresolved', '英文相对不解析');
|
|
190
|
+
assert.equal(r('next Monday').raw, 'next Monday', 'unresolved 保留原话');
|
|
191
|
+
const ext = {
|
|
192
|
+
schema_version: 'travel_slot_extraction.v1',
|
|
193
|
+
language: 'zh',
|
|
194
|
+
domains: [
|
|
195
|
+
'hotel',
|
|
196
|
+
'flight'
|
|
197
|
+
],
|
|
198
|
+
slots: {
|
|
199
|
+
hotel: {
|
|
200
|
+
action: 'search',
|
|
201
|
+
city: '上海',
|
|
202
|
+
check_in_date: '下周五',
|
|
203
|
+
check_out_date: '下周五+3'
|
|
204
|
+
},
|
|
205
|
+
flight: {
|
|
206
|
+
action: 'search',
|
|
207
|
+
origin: '上海',
|
|
208
|
+
destination: '杭州',
|
|
209
|
+
departure_date: '这阵子',
|
|
210
|
+
trip_type: 'one_way'
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
missing_slots: []
|
|
214
|
+
};
|
|
215
|
+
const resolved = resolveSlots(ext, anchor);
|
|
216
|
+
assert.equal(resolved.hotel?.check_in_date?.date, '2026-09-04', 'hotel 入住=下周五');
|
|
217
|
+
assert.equal(resolved.hotel?.check_out_date?.date, '2026-09-07', 'hotel 退房=下周五+3');
|
|
218
|
+
assert.deepEqual(resolved.unresolved, [
|
|
219
|
+
{
|
|
220
|
+
field: 'flight.departure_date',
|
|
221
|
+
raw: '这阵子'
|
|
222
|
+
}
|
|
223
|
+
], 'unresolved 清单');
|
|
224
|
+
assert.equal(specDateMismatches({
|
|
225
|
+
segments: [
|
|
226
|
+
{
|
|
227
|
+
date: '2026-09-04'
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}, resolved).length, 0, '一致则零 mismatch');
|
|
231
|
+
const mm = specDateMismatches({
|
|
232
|
+
segments: [
|
|
233
|
+
{
|
|
234
|
+
date: '2026-09-05'
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
}, resolved);
|
|
238
|
+
assert.equal(mm.length, 1, '单日期段分歧暴露');
|
|
239
|
+
assert.equal(mm[0]?.specDate, '2026-09-05');
|
|
240
|
+
assert.equal(mm[0]?.slotDate, '2026-09-04');
|
|
241
|
+
assert.equal(specDateMismatches({
|
|
242
|
+
segments: [
|
|
243
|
+
{
|
|
244
|
+
date: '2026-09-04'
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
date: '2026-09-05'
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}, resolved).length, 0, '多段无逐段真值不判(巡检修正)');
|
|
251
|
+
const unresolvedOnly = {
|
|
252
|
+
unresolved: [
|
|
253
|
+
{
|
|
254
|
+
field: 'flight.departure_date',
|
|
255
|
+
raw: '近期'
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
};
|
|
259
|
+
assert.equal(specDateMismatches({
|
|
260
|
+
segments: [
|
|
261
|
+
{
|
|
262
|
+
date: '2026-09-05'
|
|
263
|
+
}
|
|
264
|
+
]
|
|
265
|
+
}, unresolvedOnly).length, 0, '全 unresolved 不参与比对');
|
|
266
|
+
assert.equal(specDateMismatches({
|
|
267
|
+
segments: []
|
|
268
|
+
}, resolved).length, 0, 'spec 无日期不比对');
|
|
269
|
+
console.log('5. 槽位→日期解析 OK(绝对/词表/+N/unresolved 边界/整张解析/spec 一致性闸)');
|
|
270
|
+
}console.log('\nTIME-EVAL TESTS: 5/5 OK(确定性部分,CI 口径)');
|
|
271
|
+
if (process.argv.includes('--real')) {
|
|
272
|
+
try {
|
|
273
|
+
for (const line of readFileSync(join('..', '.env'), 'utf-8').split('\n')){
|
|
274
|
+
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
275
|
+
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].trim();
|
|
276
|
+
}
|
|
277
|
+
} catch {}
|
|
278
|
+
const hasKey = Boolean(process.env['LLM_API_KEY'] ?? process.env['DEEPSEEK_API_KEY']);
|
|
279
|
+
if (!hasKey) {
|
|
280
|
+
console.log('\n--real:无 LLM_API_KEY,跳过真模型巡检(ADR-8 回退原则)');
|
|
281
|
+
} else {
|
|
282
|
+
const clock = ()=>ANCHOR_NOW;
|
|
283
|
+
const llm = createOpenAICompatLlm(undefined, clock);
|
|
284
|
+
console.log(`\n=== 真模型巡检(${process.env['LLM_MODEL'] ?? 'MiniMax-M2'},锚点 ${dataset.anchor_date})===`);
|
|
285
|
+
let pass = 0;
|
|
286
|
+
let p0Pass = 0;
|
|
287
|
+
let p0Total = 0;
|
|
288
|
+
for (const c of dataset.cases){
|
|
289
|
+
if (c.priority === 'P0') p0Total++;
|
|
290
|
+
try {
|
|
291
|
+
const ext = await llm.extractSlots([
|
|
292
|
+
{
|
|
293
|
+
role: 'user',
|
|
294
|
+
text: c.utterance
|
|
295
|
+
}
|
|
296
|
+
]);
|
|
297
|
+
if (!ext) {
|
|
298
|
+
console.log(`FAIL ${c.id}(${c.priority}):模型未产出合法 v1 JSON`);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const r = scoreExtraction(c.expected, ext);
|
|
302
|
+
if (r.pass) {
|
|
303
|
+
pass++;
|
|
304
|
+
if (c.priority === 'P0') p0Pass++;
|
|
305
|
+
console.log(`PASS ${c.id}${r.warnings.length ? `(warning: ${r.warnings.join(';')})` : ''}`);
|
|
306
|
+
} else {
|
|
307
|
+
console.log(`FAIL ${c.id}(${c.priority}):\n ${r.diffs.join('\n ')}`);
|
|
308
|
+
}
|
|
309
|
+
} catch (e) {
|
|
310
|
+
console.log(`ERROR ${c.id}(${c.priority}):${e.message.slice(0, 120)}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
console.log(`\nREAL ACCURACY: ${pass}/${dataset.cases.length}(P0: ${p0Pass}/${p0Total})——巡检报告口径,不设红线`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/time-eval-tests.ts
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { appendTrip, makeTripId, projectTimeline, timelineGapsForVerified, resolveTimelineDate } from '../src/travel-timeline.js';
|
|
3
|
+
import { buildTimeAnchor } from '../src/time-anchor.js';
|
|
4
|
+
const anchor = buildTimeAnchor(new Date(2026, 7, 28, 12));
|
|
5
|
+
let n = 0;
|
|
6
|
+
function pass(name, body) {
|
|
7
|
+
body();
|
|
8
|
+
console.log(` ${++n}. ${name} OK`);
|
|
9
|
+
}
|
|
10
|
+
pass('守门:必填与绝对日期(不猜)', ()=>{
|
|
11
|
+
const ev = {
|
|
12
|
+
destination: '大理',
|
|
13
|
+
start: '2025-10-01',
|
|
14
|
+
source: 'user-verbatim',
|
|
15
|
+
evidence: '用户原话'
|
|
16
|
+
};
|
|
17
|
+
assert.equal(appendTrip([], {
|
|
18
|
+
...ev,
|
|
19
|
+
destination: ''
|
|
20
|
+
}).appended, false, '无目的地拒');
|
|
21
|
+
assert.equal(appendTrip([], {
|
|
22
|
+
...ev,
|
|
23
|
+
evidence: ''
|
|
24
|
+
}).appended, false, '无 evidence 拒(溯源 P0)');
|
|
25
|
+
assert.ok(appendTrip([], {
|
|
26
|
+
...ev,
|
|
27
|
+
start: '下周一'
|
|
28
|
+
}).reason?.includes('YYYY-MM-DD'), '词表外日期拒收(不猜)');
|
|
29
|
+
assert.ok(appendTrip([], {
|
|
30
|
+
...ev,
|
|
31
|
+
end: '2025-09-30'
|
|
32
|
+
}).reason?.includes('end 早于 start'), 'end<start 拒');
|
|
33
|
+
});
|
|
34
|
+
pass('幂等:同目的地+start+source = 同一行程 no-op', ()=>{
|
|
35
|
+
const ev = {
|
|
36
|
+
destination: '大理',
|
|
37
|
+
start: '2025-10-01',
|
|
38
|
+
source: 'user-verbatim',
|
|
39
|
+
evidence: '原话'
|
|
40
|
+
};
|
|
41
|
+
const r1 = appendTrip([], ev);
|
|
42
|
+
assert.equal(r1.appended, true);
|
|
43
|
+
const r2 = appendTrip(r1.events, {
|
|
44
|
+
...ev,
|
|
45
|
+
evidence: '换个说法再说一遍'
|
|
46
|
+
});
|
|
47
|
+
assert.equal(r2.appended, false, '同 trip_id 重放 no-op');
|
|
48
|
+
assert.equal(r2.tripId, r1.tripId);
|
|
49
|
+
});
|
|
50
|
+
pass('重叠冲突即停:同目的地日期区间重叠拒收', ()=>{
|
|
51
|
+
const r1 = appendTrip([], {
|
|
52
|
+
destination: '大理',
|
|
53
|
+
start: '2025-10-01',
|
|
54
|
+
end: '2025-10-05',
|
|
55
|
+
source: 'user-verbatim',
|
|
56
|
+
evidence: '原话'
|
|
57
|
+
});
|
|
58
|
+
const r2 = appendTrip(r1.events, {
|
|
59
|
+
destination: '大理',
|
|
60
|
+
start: '2025-10-04',
|
|
61
|
+
source: 'user-verbatim',
|
|
62
|
+
evidence: '原话2'
|
|
63
|
+
});
|
|
64
|
+
assert.equal(r2.appended, false);
|
|
65
|
+
assert.ok(r2.reason?.includes('重叠'), '冲突理由说明');
|
|
66
|
+
});
|
|
67
|
+
pass('日期解析:锚点卡词表进、词表外 null(经 resolveTimelineDate)', ()=>{
|
|
68
|
+
assert.equal(resolveTimelineDate('2025-10-01', anchor), '2025-10-01');
|
|
69
|
+
assert.equal(resolveTimelineDate('去年国庆', anchor), null, '开放表达不猜');
|
|
70
|
+
});
|
|
71
|
+
pass('投影:start 倒序(最近在前)', ()=>{
|
|
72
|
+
let evs = [];
|
|
73
|
+
evs = appendTrip(evs, {
|
|
74
|
+
destination: '普吉',
|
|
75
|
+
start: '2026-07-10',
|
|
76
|
+
source: 'user-verbatim',
|
|
77
|
+
evidence: 'a'
|
|
78
|
+
}).events;
|
|
79
|
+
evs = appendTrip(evs, {
|
|
80
|
+
destination: '大理',
|
|
81
|
+
start: '2025-10-01',
|
|
82
|
+
source: 'user-verbatim',
|
|
83
|
+
evidence: 'b'
|
|
84
|
+
}).events;
|
|
85
|
+
const p = projectTimeline(evs);
|
|
86
|
+
assert.equal(p[0]?.destination, '普吉');
|
|
87
|
+
assert.equal(p.length, 2);
|
|
88
|
+
});
|
|
89
|
+
pass('交叉一致:verified wish 无 timeline = 缺口暴露(巡检口径,不自动补写)', ()=>{
|
|
90
|
+
const evs = appendTrip([], {
|
|
91
|
+
destination: '大理',
|
|
92
|
+
start: '2025-10-01',
|
|
93
|
+
source: 'wish-confirmed',
|
|
94
|
+
evidence: 'wish w1 confirm'
|
|
95
|
+
}).events;
|
|
96
|
+
const gaps = timelineGapsForVerified(evs, [
|
|
97
|
+
{
|
|
98
|
+
wishId: 'w1',
|
|
99
|
+
destination: '大理'
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
wishId: 'w2',
|
|
103
|
+
destination: '清迈'
|
|
104
|
+
}
|
|
105
|
+
]);
|
|
106
|
+
assert.deepEqual(gaps, [
|
|
107
|
+
{
|
|
108
|
+
wishId: 'w2'
|
|
109
|
+
}
|
|
110
|
+
]);
|
|
111
|
+
});
|
|
112
|
+
pass('trip_id 语义派生:确定性可重放', ()=>{
|
|
113
|
+
assert.equal(makeTripId({
|
|
114
|
+
destination: '大 理',
|
|
115
|
+
start: '2025-10-01',
|
|
116
|
+
source: 'user-verbatim',
|
|
117
|
+
evidence: 'x'
|
|
118
|
+
}), '大_理|2025-10-01|user-verbatim');
|
|
119
|
+
});
|
|
120
|
+
console.log(`\nTRAVEL TIMELINE TESTS: ${n}/7 OK(memory-design P1 守门面)`);
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/travel-timeline-tests.ts
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { parseFlightPackToSpec, solveUnified } from '../src/unified.js';
|
|
5
|
+
import { hhmmToMin } from '../src/model.js';
|
|
6
|
+
const pack = JSON.parse(await readFile(join('..', 'data', 'flights_2026.json'), 'utf-8'));
|
|
7
|
+
const spec = parseFlightPackToSpec(pack);
|
|
8
|
+
spec.budgetCny = 9000;
|
|
9
|
+
const r1 = await solveUnified(spec);
|
|
10
|
+
assert.equal(r1.feasible, true);
|
|
11
|
+
assert.equal(r1.money_cny, r1.legs.reduce((a, l)=>a + l.price_cny, 0));
|
|
12
|
+
assert.ok(7280 <= r1.money_cny && r1.money_cny <= 8350, `money=${r1.money_cny}`);
|
|
13
|
+
const byLeg = Object.fromEntries(r1.legs.map((l)=>[
|
|
14
|
+
l.leg,
|
|
15
|
+
l
|
|
16
|
+
]));
|
|
17
|
+
assert.notEqual(byLeg['f4'].service, 'DZ6252');
|
|
18
|
+
assert.equal(byLeg['f5'].service, 'EK329');
|
|
19
|
+
assert.equal(byLeg['f5'].energy_pct, 79);
|
|
20
|
+
assert.ok(byLeg['f5'].wake.includes('前一日'));
|
|
21
|
+
assert.equal(byLeg['f5'].door_to_door, '11h20m');
|
|
22
|
+
if (byLeg['f3'].service === 'MU6088') {
|
|
23
|
+
assert.equal(byLeg['f3'].door_to_door, '6h15m');
|
|
24
|
+
}
|
|
25
|
+
const tight = parseFlightPackToSpec(pack);
|
|
26
|
+
tight.segments[0].anchors.arriveByMin = hhmmToMin('15:00');
|
|
27
|
+
const r2 = await solveUnified(tight);
|
|
28
|
+
assert.equal(r2.feasible, false);
|
|
29
|
+
assert.ok(r2.unsat_core.includes('f1:arrive_by'), `core=${JSON.stringify(r2.unsat_core)}`);
|
|
30
|
+
assert.ok(r2.unsat_core.every((c)=>!c.includes('|')), 'core 无竖线残留');
|
|
31
|
+
assert.ok(r2.suggestions.some((sg)=>sg.relax === 'f1:arrive_by'));
|
|
32
|
+
const poor = parseFlightPackToSpec(pack);
|
|
33
|
+
poor.budgetCny = 1000;
|
|
34
|
+
const r3 = await solveUnified(poor);
|
|
35
|
+
assert.equal(r3.feasible, false);
|
|
36
|
+
assert.ok(r3.unsat_core.includes('total:budget'));
|
|
37
|
+
const excluded = r1.work_window_exclusions ?? [];
|
|
38
|
+
assert.ok(excluded.some((e)=>e.segment === 'f2' && e.option === 'TG216'), JSON.stringify(excluded));
|
|
39
|
+
assert.ok(excluded.some((e)=>e.segment === 'f2' && e.option === 'TG218'));
|
|
40
|
+
assert.equal(byLeg['f2'].service, 'VZ303');
|
|
41
|
+
const off = parseFlightPackToSpec(pack);
|
|
42
|
+
off.budgetCny = 9000;
|
|
43
|
+
off.workWindow = undefined;
|
|
44
|
+
const r4 = await solveUnified(off);
|
|
45
|
+
assert.deepEqual(r4.work_window_exclusions, []);
|
|
46
|
+
console.log(`TS UNIFIED TESTS: 4/4 OK(工作窗口生效,f2=VZ303,¥${r1.money_cny})`);
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/unified-tests.ts
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { geocodePlace, getForecast, getClimate, wmoLabel } from '../capabilities/weather.js';
|
|
3
|
+
const geo = await geocodePlace('大理市', {
|
|
4
|
+
count: 10
|
|
5
|
+
});
|
|
6
|
+
assert.equal(geo.ok, true, `geocode ok: ${geo.error ?? ''}`);
|
|
7
|
+
assert.ok(geo.results.length > 0, '应有结果');
|
|
8
|
+
const dali = geo.results.find((r)=>(r.admin1 ?? '').includes('云南')) ?? geo.results[0];
|
|
9
|
+
assert.ok(Math.abs(dali.latitude - 25.6) < 0.5, `纬度应≈25.6,实际 ${dali.latitude}(${dali.name},${dali.admin1})`);
|
|
10
|
+
assert.match(geo.evidence, /open-meteo-geo@2/, '证据链带时间戳');
|
|
11
|
+
console.log(`1. geocode 大理市 → ${dali.latitude},${dali.longitude} (${dali.name},${dali.admin1}) OK`);
|
|
12
|
+
const fc = await getForecast({
|
|
13
|
+
latitude: dali.latitude,
|
|
14
|
+
longitude: dali.longitude
|
|
15
|
+
}, {
|
|
16
|
+
days: 7
|
|
17
|
+
});
|
|
18
|
+
assert.equal(fc.ok, true, `forecast ok: ${fc.error ?? ''}`);
|
|
19
|
+
assert.equal(fc.daily.length, 7, '7 天');
|
|
20
|
+
assert.ok(fc.daily[0].tempMaxC > -50 && fc.daily[0].tempMaxC < 60, '温度合理范围');
|
|
21
|
+
assert.match(fc.evidence, /\[实时API:open-meteo@2/, '证据链');
|
|
22
|
+
assert.ok(fc.latencyMs < 5000, `延迟应 <5s,实际 ${fc.latencyMs}ms`);
|
|
23
|
+
console.log(`2. forecast 大理 7 天:${fc.daily[0].tempMinC}–${fc.daily[0].tempMaxC}°C ${wmoLabel(fc.daily[0].weatherCode)} OK`);
|
|
24
|
+
const cl = await getClimate({
|
|
25
|
+
latitude: dali.latitude,
|
|
26
|
+
longitude: dali.longitude
|
|
27
|
+
}, 8);
|
|
28
|
+
assert.equal(cl.ok, true, `climate ok: ${cl.error ?? ''}`);
|
|
29
|
+
assert.ok(cl.daily.length >= 28, '8 月应有 ≥28 天');
|
|
30
|
+
assert.match(cl.evidence, /open-meteo-climate@/, '气候证据链');
|
|
31
|
+
console.log(`3. climate 大理去年 8 月:${cl.daily.length} 天 OK`);
|
|
32
|
+
const bad = await getForecast({
|
|
33
|
+
latitude: 999,
|
|
34
|
+
longitude: 0
|
|
35
|
+
}, {
|
|
36
|
+
timeoutMs: 10_000
|
|
37
|
+
});
|
|
38
|
+
assert.equal(bad.ok, false, '非法输入应返回 ok=false');
|
|
39
|
+
assert.ok(bad.evidence.includes('error'), '降级证据标注');
|
|
40
|
+
console.log(`4. 降级 ok=false 不抛错 OK`);
|
|
41
|
+
assert.equal(wmoLabel(0), '晴');
|
|
42
|
+
assert.equal(wmoLabel(95), '雷暴');
|
|
43
|
+
assert.match(wmoLabel(999), /天气码999/, '未知码回退');
|
|
44
|
+
console.log('5. WMO 码映射 OK');
|
|
45
|
+
console.log('\nWEATHER TESTS: 5/5 OK(Open-Meteo 真实 API,免费无 key)');
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/weather-tests.ts
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export async function ensureStateDir(root) {
|
|
4
|
+
const dir = join(root, 'gotry-state');
|
|
5
|
+
await mkdir(dir, {
|
|
6
|
+
recursive: true
|
|
7
|
+
});
|
|
8
|
+
return dir;
|
|
9
|
+
}
|
|
10
|
+
export async function readJson(path, fallback) {
|
|
11
|
+
try {
|
|
12
|
+
const text = await readFile(path, 'utf-8');
|
|
13
|
+
return JSON.parse(text);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
if (e.code === 'ENOENT') return fallback;
|
|
16
|
+
throw e;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function writeJson(path, value) {
|
|
20
|
+
const tmp = `${path}.tmp`;
|
|
21
|
+
await writeFile(tmp, JSON.stringify(value, null, 2), 'utf-8');
|
|
22
|
+
const { rename } = await import('node:fs/promises');
|
|
23
|
+
await rename(tmp, path);
|
|
24
|
+
}
|
|
25
|
+
export async function recordLatency(logPath, latencyMs, kind) {
|
|
26
|
+
await appendFile(logPath, JSON.stringify({
|
|
27
|
+
ts: new Date().toISOString(),
|
|
28
|
+
kind,
|
|
29
|
+
latencyMs
|
|
30
|
+
}) + '\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/bridge.ts
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const SENSITIVE_PATTERNS = [
|
|
2
|
+
{
|
|
3
|
+
re: /\d{15,17}[Xx]?/,
|
|
4
|
+
label: '疑似证件号/卡号'
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
re: /1[3-9]\d{9}/,
|
|
8
|
+
label: '疑似手机号'
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
re: /(护照|身份证|证件号|通行证)\s*号?\s*[::]?\s*\w+/i,
|
|
12
|
+
label: '证件信息'
|
|
13
|
+
}
|
|
14
|
+
];
|
|
15
|
+
export function sensitiveViolation(text) {
|
|
16
|
+
for (const { re, label } of SENSITIVE_PATTERNS){
|
|
17
|
+
if (re.test(text)) return `负面清单拒收:${label}不入库(需要时由后端从登录态填充)`;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
export function companionId(label) {
|
|
22
|
+
return label.replace(/\s+/g, '');
|
|
23
|
+
}
|
|
24
|
+
function mergeStrArray(oldArr, newArr) {
|
|
25
|
+
const a = oldArr ?? [];
|
|
26
|
+
let added = false;
|
|
27
|
+
for (const x of newArr ?? []){
|
|
28
|
+
if (!a.includes(x)) {
|
|
29
|
+
a.push(x);
|
|
30
|
+
added = true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
merged: a,
|
|
35
|
+
added
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function upsertCompanion(profiles, patch) {
|
|
39
|
+
if (!patch.label?.trim()) return {
|
|
40
|
+
profiles,
|
|
41
|
+
appended: false,
|
|
42
|
+
companionId: '',
|
|
43
|
+
reason: 'label 必填'
|
|
44
|
+
};
|
|
45
|
+
const violation = sensitiveViolation(JSON.stringify(patch));
|
|
46
|
+
if (violation) return {
|
|
47
|
+
profiles,
|
|
48
|
+
appended: false,
|
|
49
|
+
companionId: '',
|
|
50
|
+
reason: violation
|
|
51
|
+
};
|
|
52
|
+
const id = companionId(patch.label);
|
|
53
|
+
const existing = profiles.find((p)=>p.companion_id === id);
|
|
54
|
+
if (!existing) {
|
|
55
|
+
const full = {
|
|
56
|
+
schema: 'companion_profile.v1',
|
|
57
|
+
companion_id: id,
|
|
58
|
+
label: patch.label.trim(),
|
|
59
|
+
constraints: {
|
|
60
|
+
mobility: patch.constraints.mobility,
|
|
61
|
+
health: patch.constraints.health ? [
|
|
62
|
+
...patch.constraints.health
|
|
63
|
+
] : undefined,
|
|
64
|
+
prefs: patch.constraints.prefs ? [
|
|
65
|
+
...patch.constraints.prefs
|
|
66
|
+
] : undefined
|
|
67
|
+
},
|
|
68
|
+
evidence: [
|
|
69
|
+
patch.evidence
|
|
70
|
+
],
|
|
71
|
+
ts: new Date().toISOString()
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
profiles: [
|
|
75
|
+
...profiles,
|
|
76
|
+
full
|
|
77
|
+
],
|
|
78
|
+
appended: true,
|
|
79
|
+
companionId: id
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const mergedConstraints = {
|
|
83
|
+
mobility: patch.constraints.mobility ?? existing.constraints.mobility,
|
|
84
|
+
health: mergeStrArray(existing.constraints.health, patch.constraints.health).merged,
|
|
85
|
+
prefs: mergeStrArray(existing.constraints.prefs, patch.constraints.prefs).merged
|
|
86
|
+
};
|
|
87
|
+
const ev = mergeStrArray(existing.evidence, [
|
|
88
|
+
patch.evidence
|
|
89
|
+
]);
|
|
90
|
+
const healthLen = mergedConstraints.health?.length ?? 0;
|
|
91
|
+
const prefsLen = mergedConstraints.prefs?.length ?? 0;
|
|
92
|
+
const changed = mergedConstraints.mobility !== existing.constraints.mobility || healthLen !== (existing.constraints.health?.length ?? 0) || prefsLen !== (existing.constraints.prefs?.length ?? 0) || ev.added;
|
|
93
|
+
if (!changed) return {
|
|
94
|
+
profiles,
|
|
95
|
+
appended: false,
|
|
96
|
+
companionId: id
|
|
97
|
+
};
|
|
98
|
+
const next = profiles.map((p)=>p.companion_id === id ? {
|
|
99
|
+
...p,
|
|
100
|
+
constraints: mergedConstraints,
|
|
101
|
+
evidence: ev.merged,
|
|
102
|
+
ts: new Date().toISOString()
|
|
103
|
+
} : p);
|
|
104
|
+
return {
|
|
105
|
+
profiles: next,
|
|
106
|
+
appended: true,
|
|
107
|
+
companionId: id
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/companions.ts
|