@danceiny/gotry 0.0.1-rc.7 → 0.0.1-rc.9
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 +29 -74
- package/bin/gotry-inner.js +50 -1
- package/bin/gotry-stdio-ask.js +59 -0
- package/cordis.gotry-patch.yml +63 -5
- package/data/flights_2026.json +7 -4
- package/data/hotels_2026.json +102 -15
- package/data/time-slot-eval.json +384 -0
- package/data/yunnan-pack.json +2 -1
- package/dist/capabilities/agent-reach-bridge.py +90 -0
- package/dist/capabilities/anything.js +2 -2
- package/dist/capabilities/flyai.js +131 -0
- package/dist/capabilities/hbcli.js +11 -6
- package/dist/capabilities/incident-log.js +2 -1
- 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 +56 -0
- package/dist/capabilities/session/transport.js +95 -0
- package/dist/capabilities/session-search.js +95 -0
- package/dist/scripts/action-cache-tests.js +107 -0
- package/dist/scripts/agent-reach-tests.js +1 -1
- package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
- package/dist/scripts/async-collect.js +26 -7
- package/dist/scripts/companion-tests.js +112 -0
- package/dist/scripts/hbcli-tests.js +18 -2
- package/dist/scripts/ledger-tests.js +344 -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/probe-poi-tests.js +9 -0
- package/dist/scripts/replay.js +73 -1
- package/dist/scripts/session-attach-diagnose.js +31 -0
- package/dist/scripts/session-attach-poc.js +65 -0
- package/dist/scripts/session-attach-wait.js +41 -0
- package/dist/scripts/session-extract-tests.js +81 -0
- package/dist/scripts/session-login.js +48 -0
- package/dist/scripts/session-tests.js +162 -0
- package/dist/scripts/skeleton-check.js +3 -1
- package/dist/scripts/skills-contract-tests.js +85 -0
- package/dist/scripts/smoke.js +242 -4
- package/dist/scripts/state-cli-tests.js +136 -0
- package/dist/scripts/state-cli.js +234 -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 +1 -1
- package/dist/src/companions.js +112 -0
- package/dist/src/dsh-llm.js +27 -7
- package/dist/src/index.js +600 -121
- package/dist/src/loop.js +89 -25
- 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 +6 -1
- package/dist/src/slot-spec.js +165 -0
- package/dist/src/state-ledger.js +848 -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 +3 -1
- package/dist/src/wish-pool.js +27 -0
- package/package.json +6 -2
- package/ts/capabilities/hbcli.ts +6 -6
- package/ts/capabilities/incident-log.ts +6 -3
- package/ts/package.json +3 -0
- package/ts/scripts/skeleton-check.ts +5 -1
- package/ts/src/dsh-llm.ts +27 -7
- package/ts/src/index.ts +439 -91
- package/ts/src/loop.ts +109 -34
- package/ts/src/mock-llm.ts +13 -1
- package/ts/src/unified.ts +6 -1
- package/ts/cordis.gotry-patch.yml +0 -36
package/dist/src/loop.js
CHANGED
|
@@ -228,6 +228,26 @@ export async function runTurn(state, userMsg, llm, history = [], solve = null) {
|
|
|
228
228
|
} else {
|
|
229
229
|
const invalid = spec ? validateSpec(spec) : '翻译器未产出 spec';
|
|
230
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
|
+
}
|
|
231
251
|
state.spec = spec;
|
|
232
252
|
state.solve = await solve(spec);
|
|
233
253
|
parts.push(llm.render ? await llm.render(state) : renderSolve(state));
|
|
@@ -244,50 +264,94 @@ export async function runTurn(state, userMsg, llm, history = [], solve = null) {
|
|
|
244
264
|
};
|
|
245
265
|
}
|
|
246
266
|
import { join } from 'node:path';
|
|
247
|
-
import { mkdir } from 'node:fs/promises';
|
|
248
|
-
import {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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, {
|
|
252
273
|
recursive: true
|
|
253
274
|
});
|
|
254
|
-
return
|
|
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);
|
|
255
281
|
}
|
|
256
|
-
export async function persistAsyncTicket(ticket, state) {
|
|
257
|
-
const
|
|
258
|
-
|
|
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({
|
|
259
293
|
ticket,
|
|
260
294
|
state
|
|
261
|
-
}, null, 2)
|
|
295
|
+
}, null, 2));
|
|
262
296
|
return p;
|
|
263
297
|
}
|
|
264
|
-
export async function loadAsyncTicket(ticketId) {
|
|
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
|
+
}
|
|
265
307
|
try {
|
|
266
|
-
const p = await
|
|
308
|
+
const p = join(await asyncDir(stateRoot), `${ticketId}.json`);
|
|
267
309
|
return JSON.parse(await readFile(p, 'utf-8'));
|
|
268
310
|
} catch {
|
|
269
311
|
return null;
|
|
270
312
|
}
|
|
271
313
|
}
|
|
272
|
-
export async function settleAsyncTicket(ticketId, reply) {
|
|
273
|
-
|
|
274
|
-
await
|
|
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);
|
|
275
319
|
return p;
|
|
276
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
|
+
}
|
|
277
334
|
export function probePoi(msg) {
|
|
278
335
|
const trimmed = msg.trim();
|
|
279
336
|
if (!trimmed) return null;
|
|
280
|
-
const
|
|
281
|
-
if (isShort) {
|
|
282
|
-
const stripped = trimmed.replace(/^(查一下|查|搜|找|看看|推荐|告诉我|检索)\s*/, '').trim();
|
|
283
|
-
if (stripped.length >= 2) return stripped.slice(0, 24);
|
|
284
|
-
}
|
|
337
|
+
const cutAtPunct = (s)=>s.split(/[,,。.;;!!??]/)[0]?.trim() ?? '';
|
|
285
338
|
const m = trimmed.match(/(查一下|查|搜|找|看看|推荐|告诉我|检索)\s*(.{2,24})/);
|
|
286
|
-
if (m
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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);
|
|
291
355
|
}
|
|
292
356
|
return null;
|
|
293
357
|
}
|
|
@@ -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
|
package/dist/src/mock-llm.js
CHANGED
|
@@ -50,7 +50,7 @@ const SCRIPT = [
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
];
|
|
53
|
-
export function createMockLlm(flightPackPath) {
|
|
53
|
+
export function createMockLlm(flightPackPath, slotScript = []) {
|
|
54
54
|
let stepIdx = 0;
|
|
55
55
|
return {
|
|
56
56
|
async extractFacts (history, _state) {
|
|
@@ -88,6 +88,11 @@ export function createMockLlm(flightPackPath) {
|
|
|
88
88
|
spec.budgetCny = 9000;
|
|
89
89
|
return spec;
|
|
90
90
|
},
|
|
91
|
+
async extractSlots (history) {
|
|
92
|
+
const last = history[history.length - 1]?.text ?? '';
|
|
93
|
+
const hit = slotScript.find((s)=>last.includes(s.when));
|
|
94
|
+
return hit ? structuredClone(hit.extraction) : null;
|
|
95
|
+
},
|
|
91
96
|
async polishQuestion (q) {
|
|
92
97
|
return `【${q.key}】${q.text}\n(为什么问:${q.why})`;
|
|
93
98
|
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { parseAbsoluteDate, ymd } from './time-anchor.js';
|
|
2
|
+
const WEEKDAY_ZH = {
|
|
3
|
+
日: 0,
|
|
4
|
+
一: 1,
|
|
5
|
+
二: 2,
|
|
6
|
+
三: 3,
|
|
7
|
+
四: 4,
|
|
8
|
+
五: 5,
|
|
9
|
+
六: 6
|
|
10
|
+
};
|
|
11
|
+
function addDaysYmd(today, n) {
|
|
12
|
+
const [y, m, d] = today.split('-').map(Number);
|
|
13
|
+
return ymd(new Date(y, m - 1, d + n));
|
|
14
|
+
}
|
|
15
|
+
function splitPlusSuffix(expr) {
|
|
16
|
+
const m = expr.match(/^(.*?)\+(\d+)天?$/);
|
|
17
|
+
if (m && m[1]) return {
|
|
18
|
+
base: m[1],
|
|
19
|
+
plusDays: Number(m[2])
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
base: expr,
|
|
23
|
+
plusDays: 0
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function neighborDayOffset(expr) {
|
|
27
|
+
if (expr === '今天') return 0;
|
|
28
|
+
if (expr === '明天') return 1;
|
|
29
|
+
if (expr === '后天') return 2;
|
|
30
|
+
if (expr === '大后天') return 3;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function weekdayOffsetDays(expr, today) {
|
|
34
|
+
const m = expr.match(/^(本|下|下下)周([日一二三四五六])$/);
|
|
35
|
+
if (!m || !m[2]) return null;
|
|
36
|
+
const target = WEEKDAY_ZH[m[2]];
|
|
37
|
+
if (target === undefined) return null;
|
|
38
|
+
const [y, mm, d] = today.split('-').map(Number);
|
|
39
|
+
const todayMonOffset = (new Date(y, mm - 1, d).getDay() + 6) % 7;
|
|
40
|
+
const targetMonOffset = (target + 6) % 7;
|
|
41
|
+
const weekShift = m[1] === '本' ? 0 : m[1] === '下' ? 7 : 14;
|
|
42
|
+
return weekShift + targetMonOffset - todayMonOffset;
|
|
43
|
+
}
|
|
44
|
+
function monthFragmentDate(expr, today) {
|
|
45
|
+
if (![
|
|
46
|
+
'下个月初',
|
|
47
|
+
'下个月中旬',
|
|
48
|
+
'下个月下旬'
|
|
49
|
+
].includes(expr)) return null;
|
|
50
|
+
const [y, m] = today.split('-').map(Number);
|
|
51
|
+
const day = expr === '下个月初' ? 1 : expr === '下个月中旬' ? 11 : 21;
|
|
52
|
+
return ymd(new Date(y, m, day));
|
|
53
|
+
}
|
|
54
|
+
export function resolveSlotDate(expr, anchor) {
|
|
55
|
+
const raw = expr.trim();
|
|
56
|
+
const year = Number(anchor.today.slice(0, 4));
|
|
57
|
+
const absolute = parseAbsoluteDate(raw, year);
|
|
58
|
+
if (absolute) return {
|
|
59
|
+
raw,
|
|
60
|
+
date: absolute,
|
|
61
|
+
kind: 'absolute'
|
|
62
|
+
};
|
|
63
|
+
const { base, plusDays } = splitPlusSuffix(raw);
|
|
64
|
+
const baseAbs = parseAbsoluteDate(base, year);
|
|
65
|
+
if (baseAbs) return {
|
|
66
|
+
raw,
|
|
67
|
+
date: addDaysYmd(baseAbs, plusDays),
|
|
68
|
+
kind: 'absolute'
|
|
69
|
+
};
|
|
70
|
+
const month = monthFragmentDate(base, anchor.today);
|
|
71
|
+
if (month) return {
|
|
72
|
+
raw,
|
|
73
|
+
date: addDaysYmd(month, plusDays),
|
|
74
|
+
kind: 'card'
|
|
75
|
+
};
|
|
76
|
+
const neighbor = neighborDayOffset(base);
|
|
77
|
+
if (neighbor !== null) return {
|
|
78
|
+
raw,
|
|
79
|
+
date: addDaysYmd(anchor.today, neighbor + plusDays),
|
|
80
|
+
kind: 'card'
|
|
81
|
+
};
|
|
82
|
+
const offset = weekdayOffsetDays(base, anchor.today);
|
|
83
|
+
if (offset !== null) return {
|
|
84
|
+
raw,
|
|
85
|
+
date: addDaysYmd(anchor.today, offset + plusDays),
|
|
86
|
+
kind: 'card'
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
raw,
|
|
90
|
+
date: null,
|
|
91
|
+
kind: 'unresolved'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const DATE_FIELDS = {
|
|
95
|
+
requisition: [
|
|
96
|
+
'start_date',
|
|
97
|
+
'end_date'
|
|
98
|
+
],
|
|
99
|
+
flight: [
|
|
100
|
+
'departure_date',
|
|
101
|
+
'return_date'
|
|
102
|
+
],
|
|
103
|
+
hotel: [
|
|
104
|
+
'check_in_date',
|
|
105
|
+
'check_out_date'
|
|
106
|
+
]
|
|
107
|
+
};
|
|
108
|
+
export function resolveSlots(ext, anchor) {
|
|
109
|
+
const out = {
|
|
110
|
+
unresolved: []
|
|
111
|
+
};
|
|
112
|
+
for (const domain of ext.domains){
|
|
113
|
+
const slots = ext.slots[domain];
|
|
114
|
+
if (!slots) continue;
|
|
115
|
+
for (const field of DATE_FIELDS[domain] ?? []){
|
|
116
|
+
const value = slots[field];
|
|
117
|
+
if (typeof value !== 'string') continue;
|
|
118
|
+
const res = resolveSlotDate(value, anchor);
|
|
119
|
+
const bucket = out[domain] ??= {};
|
|
120
|
+
bucket[field] = res;
|
|
121
|
+
if (res.kind === 'unresolved') out.unresolved.push({
|
|
122
|
+
field: `${domain}.${field}`,
|
|
123
|
+
raw: res.raw
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
export function specDateMismatches(spec, resolved) {
|
|
130
|
+
const mismatches = [];
|
|
131
|
+
const primary = [
|
|
132
|
+
{
|
|
133
|
+
field: 'requisition.start_date',
|
|
134
|
+
res: resolved.requisition?.start_date
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
field: 'flight.departure_date',
|
|
138
|
+
res: resolved.flight?.departure_date
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
field: 'hotel.check_in_date',
|
|
142
|
+
res: resolved.hotel?.check_in_date
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
const dated = primary.filter((p)=>p.res?.date != null);
|
|
146
|
+
if (dated.length === 0) return mismatches;
|
|
147
|
+
const datedSegs = (spec.segments ?? []).filter((s)=>typeof s.date === 'string' && s.date);
|
|
148
|
+
if (datedSegs.length !== 1) return mismatches;
|
|
149
|
+
const seg = datedSegs[0];
|
|
150
|
+
const slotDates = new Set(dated.map((s)=>s.res.date));
|
|
151
|
+
if (!slotDates.has(seg.date)) {
|
|
152
|
+
mismatches.push({
|
|
153
|
+
specAt: `segments[${(spec.segments ?? []).indexOf(seg)}].date`,
|
|
154
|
+
field: dated.map((s)=>s.field).join('|'),
|
|
155
|
+
specDate: seg.date,
|
|
156
|
+
slotDate: [
|
|
157
|
+
...slotDates
|
|
158
|
+
].join('|')
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return mismatches;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
//# sourceURL=/Users/bytedance/work/gotry/ts/src/slot-spec.ts
|