@danceiny/gotry 0.0.1-rc.18 → 0.0.1-rc.19

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.
Files changed (137) hide show
  1. package/README.md +42 -16
  2. package/README.zh-CN.md +37 -15
  3. package/bin/gotry-bootstrap.js +236 -3
  4. package/bin/gotry-inner.js +66 -20
  5. package/bin/gotry-runtime-resolution.d.ts +1 -1
  6. package/bin/gotry-runtime-resolution.js +7 -8
  7. package/cordis.gotry-patch.yml +15 -5
  8. package/data/stations-12306-verify.json +524 -0
  9. package/dist/capabilities/agent-reach.js +4 -4
  10. package/dist/capabilities/channel-health.js +103 -0
  11. package/dist/capabilities/channel-registry.js +213 -0
  12. package/dist/capabilities/doctor.js +285 -0
  13. package/dist/capabilities/effect.js +178 -5
  14. package/dist/capabilities/flyai.js +13 -0
  15. package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
  16. package/dist/capabilities/session/adapters/rail-12306.js +285 -0
  17. package/dist/capabilities/session/extension-bridge.js +14 -1
  18. package/dist/capabilities/session/extension-channel.js +4 -2
  19. package/dist/capabilities/session/extension-distribution.js +1 -1
  20. package/dist/capabilities/session-search.js +311 -0
  21. package/dist/capabilities/visa-policy.js +126 -0
  22. package/dist/scripts/agent-planning-turn-deadline-e2e.js +304 -0
  23. package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
  24. package/dist/scripts/benchmark-environment-bridge-e2e.js +5 -10
  25. package/dist/scripts/benchmark-environment-bridge-tests.js +148 -142
  26. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
  27. package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
  28. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
  29. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
  30. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
  31. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
  32. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
  33. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
  34. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
  35. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
  36. package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
  37. package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
  38. package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
  39. package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
  40. package/dist/scripts/bootstrap-tests.js +87 -2
  41. package/dist/scripts/build-metrics-report.js +339 -0
  42. package/dist/scripts/channel-probe-tests.js +148 -0
  43. package/dist/scripts/channel-probe.js +252 -0
  44. package/dist/scripts/channel-registry-tests.js +262 -0
  45. package/dist/scripts/doctor-tests.js +121 -0
  46. package/dist/scripts/effect-tests.js +274 -1
  47. package/dist/scripts/extension-tests.js +129 -11
  48. package/dist/scripts/fact-gate-tests.js +91 -2
  49. package/dist/scripts/flyai-tests.js +17 -1
  50. package/dist/scripts/hbcli-e2e-tests.js +1 -3
  51. package/dist/scripts/metrics-report-tests.js +288 -0
  52. package/dist/scripts/persona-surface-guard-tests.js +19 -0
  53. package/dist/scripts/session-tests.js +401 -1
  54. package/dist/scripts/smoke.js +100 -83
  55. package/dist/scripts/turn-handoff-collect-tests.js +216 -0
  56. package/dist/scripts/turn-handoff-collect.js +189 -0
  57. package/dist/scripts/turn-policy-tests.js +56 -0
  58. package/dist/scripts/typed-contract-canary.js +261 -0
  59. package/dist/scripts/visa-policy-tests.js +75 -0
  60. package/dist/scripts/wish-channel-gate-tests.js +111 -0
  61. package/dist/src/artifact-gate.js +91 -3
  62. package/dist/src/benchmark-agent-conformance.js +7 -4
  63. package/dist/src/benchmark-environment-bridge.js +68 -7
  64. package/dist/src/bookable-facts.js +62 -3
  65. package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
  66. package/dist/src/booking-surface/canonical-schema.js +8 -22
  67. package/dist/src/booking-surface/contracts.js +70 -19
  68. package/dist/src/booking-surface/dsh-planner.js +310 -177
  69. package/dist/src/booking-surface/dsh-plugin.js +3 -3
  70. package/dist/src/booking-surface/index.js +1 -5
  71. package/dist/src/booking-surface/profile.js +1 -1
  72. package/dist/src/booking-surface/runtime.js +1682 -247
  73. package/dist/src/booking-surface/server.js +371 -185
  74. package/dist/src/booking-surface/startup.js +17 -27
  75. package/dist/src/booking-surface/validation.js +270 -760
  76. package/dist/src/index.js +735 -149
  77. package/dist/src/turn-deadline.js +292 -0
  78. package/dist/src/turn-policy.js +146 -0
  79. package/dist/src/wish-pool.js +5 -0
  80. package/extension/background.js +23 -4
  81. package/extension/content-main.js +47 -9
  82. package/extension/manifest.json +27 -9
  83. package/package.json +6 -26
  84. package/schemas/booking.surface.schema.json +2593 -0
  85. package/ts/capabilities/agent-reach.ts +4 -4
  86. package/ts/capabilities/flyai.ts +21 -3
  87. package/ts/capabilities/session/action-cache.ts +1 -1
  88. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  89. package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
  90. package/ts/capabilities/session/extension-bridge.ts +32 -2
  91. package/ts/capabilities/session/extension-channel.ts +9 -8
  92. package/ts/capabilities/session/extension-distribution.ts +3 -1
  93. package/ts/capabilities/session/health-watch.ts +1 -1
  94. package/ts/capabilities/session/read-guard.ts +1 -1
  95. package/ts/capabilities/session/wizard.ts +1 -1
  96. package/ts/capabilities/session-search.ts +323 -1
  97. package/ts/package.json +1 -0
  98. package/ts/scripts/turn-handoff-collect.ts +178 -0
  99. package/ts/src/artifact-gate.ts +82 -2
  100. package/ts/src/benchmark-agent-conformance.ts +14 -6
  101. package/ts/src/benchmark-environment-bridge.ts +29 -10
  102. package/ts/src/bookable-facts.ts +109 -5
  103. package/ts/src/booking-saga.ts +1 -1
  104. package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
  105. package/ts/src/booking-surface/canonical-schema.js +8 -22
  106. package/ts/src/booking-surface/contracts.ts +278 -239
  107. package/ts/src/booking-surface/dsh-planner.ts +333 -194
  108. package/ts/src/booking-surface/dsh-plugin.js +3 -3
  109. package/ts/src/booking-surface/index.ts +1 -5
  110. package/ts/src/booking-surface/profile.ts +11 -11
  111. package/ts/src/booking-surface/runtime.ts +1351 -351
  112. package/ts/src/booking-surface/server.ts +282 -175
  113. package/ts/src/booking-surface/startup.ts +36 -48
  114. package/ts/src/booking-surface/validation.ts +194 -442
  115. package/ts/src/contracts.ts +1 -1
  116. package/ts/src/index.ts +489 -171
  117. package/ts/src/loop.ts +1 -1
  118. package/ts/src/state-ledger.ts +1 -1
  119. package/ts/src/turn-deadline.ts +361 -0
  120. package/ts/src/turn-policy.ts +154 -0
  121. package/ts/src/wish-pool.ts +17 -4
  122. package/dist/scripts/agent-planning-budget-e2e.js +0 -227
  123. package/dist/scripts/agent-planning-budget-tests.js +0 -173
  124. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
  125. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
  126. package/dist/src/booking-surface/contracts-v2.js +0 -89
  127. package/dist/src/booking-surface/runtime-v2.js +0 -1771
  128. package/dist/src/booking-surface/server-v2.js +0 -334
  129. package/dist/src/booking-surface/validation-v2.js +0 -319
  130. package/dist/src/tool-budget.js +0 -136
  131. package/schemas/booking.surface.v1.schema.json +0 -927
  132. package/schemas/booking.surface.v2.schema.json +0 -61
  133. package/ts/src/booking-surface/contracts-v2.ts +0 -118
  134. package/ts/src/booking-surface/runtime-v2.ts +0 -1466
  135. package/ts/src/booking-surface/server-v2.ts +0 -247
  136. package/ts/src/booking-surface/validation-v2.ts +0 -205
  137. package/ts/src/tool-budget.ts +0 -165
@@ -0,0 +1,126 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { BOOKABLE_FACT_SCHEMA, makeFactId } from '../src/bookable-facts.js';
3
+ const SOURCE_HOST = 'https://cs.mfa.gov.cn';
4
+ const SOURCE_ID = 'cs-mfa';
5
+ const USER_AGENT = 'GoTry/0.0.1 (+https://github.com/Danceiny/gotry; polite policy reader)';
6
+ const CONTINENTS = [
7
+ 'yz_645708',
8
+ 'feizhou_645714',
9
+ 'ouzhou_645711',
10
+ 'bz_645718',
11
+ 'dyz_645717'
12
+ ];
13
+ export function countryPageUrl(continent, country) {
14
+ if (!/^[a-z0-9_]+$/.test(continent) || !/^[a-z0-9_]+$/.test(country)) return null;
15
+ return `${SOURCE_HOST}/zggmcg/ljmdd/${continent}/${country}/`;
16
+ }
17
+ function stripTags(html) {
18
+ return html.replace(/<(script|style)[\s\S]*?<\/\1>/gi, ' ').replace(/<[^>]+>/g, ' ').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/\s+/g, ' ');
19
+ }
20
+ export function extractVisaSection(pageText) {
21
+ const text = stripTags(pageText);
22
+ const start = text.indexOf('签证入境');
23
+ if (start < 0) return null;
24
+ const rest = text.slice(start + '签证入境'.length);
25
+ const sectionEnds = [
26
+ '海关检查',
27
+ '海关申报',
28
+ '医疗条件',
29
+ '交通出行',
30
+ '实用信息',
31
+ '领事保护',
32
+ '国家安全',
33
+ '特别提醒'
34
+ ].map((kw)=>rest.indexOf(kw)).filter((i)=>i > 0);
35
+ const numHeaders = Array.from(rest.matchAll(/[一二三四五六七八九十]+、(?!免签|落地签|签证|入境规定)/g)).map((m)=>m.index).filter((i)=>i > 0);
36
+ const allEnds = [
37
+ ...sectionEnds,
38
+ ...numHeaders
39
+ ].sort((a, b)=>a - b);
40
+ const end = allEnds.length > 0 ? allEnds[0] : Math.min(rest.length, 4_000);
41
+ const section = rest.slice(0, end).trim();
42
+ return section.length > 20 ? section : null;
43
+ }
44
+ export function splitPolicyEntries(section) {
45
+ const parts = section.split(/(?:[一二三四五六七八九十]+、|[((]\s*[一二三四五六七八九十]+\s*[))])\s*/).map((p)=>p.trim());
46
+ return parts.filter((p)=>p.length > 30);
47
+ }
48
+ export function policyFactsFromMfa(input) {
49
+ const entries = splitPolicyEntries(input.section);
50
+ const asOf = input.fetchedAt.slice(0, 10);
51
+ const queryId = `visa-policy:${SOURCE_ID}:${input.countryPath}:${asOf}`;
52
+ return entries.map((entry, i)=>({
53
+ schema: BOOKABLE_FACT_SCHEMA,
54
+ fact_id: makeFactId([
55
+ input.countryPath,
56
+ String(i),
57
+ entry.slice(0, 64),
58
+ queryId
59
+ ]),
60
+ kind: 'policy',
61
+ subject: `${input.countryLabel} 签证入境(条款 ${i + 1})`,
62
+ statement: entry,
63
+ source: `${SOURCE_ID}@${input.fetchedAt}`,
64
+ query_id: queryId,
65
+ fetched_at: input.fetchedAt,
66
+ as_of: asOf,
67
+ review_by: defaultReviewBy(input.fetchedAt)
68
+ }));
69
+ }
70
+ export function defaultReviewBy(fetchedAt) {
71
+ const d = new Date(fetchedAt);
72
+ d.setDate(d.getDate() + 30);
73
+ return d.toISOString().slice(0, 10);
74
+ }
75
+ export function listCountryPaths(continentListHtml, continent) {
76
+ const hits = continentListHtml.matchAll(new RegExp(`href="(\\./([a-z0-9_]+)/)"[^>]*>([^<]{2,20})<`, 'g'));
77
+ const out = [];
78
+ for (const m of hits){
79
+ if (!m[2] || !m[3]) continue;
80
+ out.push({
81
+ path: m[2],
82
+ label: m[3].trim()
83
+ });
84
+ }
85
+ return out.map((x)=>x.path).filter((p)=>/^[a-z0-9_]+$/.test(p));
86
+ }
87
+ export function continentListUrl(continent) {
88
+ if (!/^[a-z0-9_]+$/.test(continent)) return null;
89
+ return `${SOURCE_HOST}/zggmcg/ljmdd/${continent}/`;
90
+ }
91
+ export function readPageFixture(path) {
92
+ return readFileSync(path, 'utf-8');
93
+ }
94
+ export async function fetchVisaPolicyPage(url, timeoutMs = 20_000) {
95
+ const controller = new AbortController();
96
+ const timer = setTimeout(()=>controller.abort(), timeoutMs);
97
+ try {
98
+ const res = await fetch(url, {
99
+ headers: {
100
+ 'user-agent': USER_AGENT,
101
+ 'accept-language': 'zh-CN,zh'
102
+ },
103
+ signal: controller.signal,
104
+ redirect: 'follow'
105
+ });
106
+ if (!res.ok) return {
107
+ ok: false,
108
+ error: `HTTP ${res.status}`
109
+ };
110
+ return {
111
+ ok: true,
112
+ html: await res.text()
113
+ };
114
+ } catch (e) {
115
+ const msg = controller.signal.aborted ? `timeout after ${timeoutMs}ms` : String(e.message ?? e);
116
+ return {
117
+ ok: false,
118
+ error: msg
119
+ };
120
+ } finally{
121
+ clearTimeout(timer);
122
+ }
123
+ }
124
+
125
+
126
+ //# sourceURL=ts/capabilities/visa-policy.ts
@@ -0,0 +1,304 @@
1
+ import assert from 'node:assert/strict';
2
+ import { createServer } from 'node:http';
3
+ import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { spawn } from 'node:child_process';
7
+ const ROOT = join(import.meta.dirname, '..', '..');
8
+ const TS_DIR = join(import.meta.dirname, '..');
9
+ const TIMEOUT_MS = 90_000;
10
+ const TOOL = 'gotry_artifacts_list';
11
+ const SOFT_MS = 5_000;
12
+ const HARD_MS = 10_000;
13
+ const RELAY_STALL_MS = HARD_MS + 1_000;
14
+ const HANDOFF = 'TURN_DEADLINE_HANDOFF';
15
+ const HANDOFF_SCHEMA = 'gotry_turn_handoff.v1';
16
+ const DEEP_MESSAGE = '2026年我还有6天IRW额度,我准备再请几天假,在国内待个十几天。' + '其中10.3要去湖南衡阳参加同学婚礼。请根据我的实际情况和偏好,给我安排行程';
17
+ function sse(data) {
18
+ return `data: ${JSON.stringify(data)}\n\n`;
19
+ }
20
+ function toolResponse(idSuffix) {
21
+ const call = {
22
+ index: 0,
23
+ id: `deadline-call-${idSuffix}`,
24
+ type: 'function',
25
+ function: {
26
+ name: TOOL,
27
+ arguments: JSON.stringify({
28
+ query: {
29
+ limit: 1
30
+ }
31
+ })
32
+ }
33
+ };
34
+ return sse({
35
+ id: `deadline-${idSuffix}`,
36
+ object: 'chat.completion.chunk',
37
+ created: 1,
38
+ model: 'synthetic-deadline-model',
39
+ choices: [
40
+ {
41
+ index: 0,
42
+ delta: {
43
+ role: 'assistant',
44
+ tool_calls: [
45
+ call
46
+ ]
47
+ },
48
+ finish_reason: null
49
+ }
50
+ ]
51
+ }) + sse({
52
+ id: `deadline-${idSuffix}-finish`,
53
+ object: 'chat.completion.chunk',
54
+ created: 1,
55
+ model: 'synthetic-deadline-model',
56
+ choices: [
57
+ {
58
+ index: 0,
59
+ delta: {},
60
+ finish_reason: 'tool_calls'
61
+ }
62
+ ]
63
+ }) + 'data: [DONE]\n\n';
64
+ }
65
+ function textResponse(text, id = 'deadline-final') {
66
+ return sse({
67
+ id,
68
+ object: 'chat.completion.chunk',
69
+ created: 1,
70
+ model: 'synthetic-deadline-model',
71
+ choices: [
72
+ {
73
+ index: 0,
74
+ delta: {
75
+ role: 'assistant',
76
+ content: text
77
+ },
78
+ finish_reason: null
79
+ }
80
+ ]
81
+ }) + sse({
82
+ id: `${id}-finish`,
83
+ object: 'chat.completion.chunk',
84
+ created: 1,
85
+ model: 'synthetic-deadline-model',
86
+ choices: [
87
+ {
88
+ index: 0,
89
+ delta: {},
90
+ finish_reason: 'stop'
91
+ }
92
+ ]
93
+ }) + 'data: [DONE]\n\n';
94
+ }
95
+ async function startRelay() {
96
+ const bodies = [];
97
+ const plannerBodies = [];
98
+ const server = createServer((req, res)=>{
99
+ if (req.method !== 'POST' || !req.url?.endsWith('/chat/completions')) {
100
+ res.writeHead(404).end('not found');
101
+ return;
102
+ }
103
+ const chunks = [];
104
+ req.on('data', (chunk)=>chunks.push(Buffer.from(chunk)));
105
+ req.on('end', ()=>{
106
+ let body;
107
+ try {
108
+ body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
109
+ } catch {
110
+ body = {};
111
+ }
112
+ bodies.push(body);
113
+ res.writeHead(200, {
114
+ 'content-type': 'text/event-stream',
115
+ 'cache-control': 'no-cache'
116
+ });
117
+ if (toolNames(body).includes(TOOL)) {
118
+ plannerBodies.push(body);
119
+ const plannerIndex = plannerBodies.length;
120
+ const alreadyHandedOff = JSON.stringify(body).includes(HANDOFF);
121
+ const stall = !alreadyHandedOff && plannerIndex === 1 ? RELAY_STALL_MS : 0;
122
+ setTimeout(()=>{
123
+ if (!alreadyHandedOff && plannerIndex === 1) {
124
+ res.end(toolResponse(String(plannerIndex)));
125
+ } else {
126
+ res.end(textResponse('deadline E2E final answer'));
127
+ }
128
+ }, stall);
129
+ } else {
130
+ res.end(textResponse('deadline E2E final answer', 'deadline-auxiliary'));
131
+ }
132
+ });
133
+ });
134
+ await new Promise((resolve)=>server.listen(0, '127.0.0.1', resolve));
135
+ return {
136
+ port: server.address().port,
137
+ bodies,
138
+ plannerBodies,
139
+ close: ()=>new Promise((resolve)=>server.close(()=>resolve()))
140
+ };
141
+ }
142
+ function allToolMessages(bodies) {
143
+ return bodies.flatMap((body)=>(body.messages ?? []).filter((message)=>message.role === 'tool'));
144
+ }
145
+ function toolNames(body) {
146
+ return (body.tools ?? []).map((tool)=>{
147
+ const fn = tool.function;
148
+ return String(fn?.name ?? tool.name ?? '');
149
+ }).filter(Boolean);
150
+ }
151
+ async function main() {
152
+ for (const file of [
153
+ 'dist/src/index.js',
154
+ 'dist/capabilities/artifacts.js'
155
+ ]){
156
+ assert.ok(existsSync(join(ROOT, file)), `missing ${file}; run node scripts/build-dist.mjs first`);
157
+ }
158
+ const relay = await startRelay();
159
+ const dshHome = mkdtempSync(join(tmpdir(), 'gotry-deadline-dsh-home-'));
160
+ const childCwd = mkdtempSync(join(tmpdir(), 'gotry-deadline-cwd-'));
161
+ const handoffRoot = mkdtempSync(join(tmpdir(), 'gotry-deadline-handoff-'));
162
+ let exitCode = null;
163
+ let output = '';
164
+ const configuredBin = process.env.GOTRY_DEADLINE_E2E_BIN;
165
+ assert.ok(configuredBin && existsSync(configuredBin), 'GOTRY_DEADLINE_E2E_BIN must point to the clean installed-package binary');
166
+ try {
167
+ const executableArgs = [
168
+ DEEP_MESSAGE
169
+ ];
170
+ const childEnv = {
171
+ ...process.env
172
+ };
173
+ childEnv.DSH_HOME = dshHome;
174
+ childEnv.HOME = dshHome;
175
+ childEnv.LLM_API_KEY = 'synthetic-e2e-key';
176
+ childEnv.LLM_BASE_URL = 'http://127.0.0.1:' + relay.port + '/v1';
177
+ childEnv.LLM_MODEL = 'synthetic-deadline-model';
178
+ childEnv.GOTRY_LOCALE = 'zh-CN';
179
+ childEnv.GOTRY_TURN_DEADLINE_SOFT_MS = String(SOFT_MS);
180
+ childEnv.GOTRY_TURN_DEADLINE_HARD_MS = String(HARD_MS);
181
+ childEnv.GOTRY_TURN_HANDOFF_ROOT = handoffRoot;
182
+ delete childEnv.GOTRY_LLM_MODEL;
183
+ delete childEnv.GOTRY_FORCE_TURN_DEADLINE;
184
+ const child = spawn(configuredBin, executableArgs, {
185
+ cwd: childCwd,
186
+ env: childEnv,
187
+ stdio: [
188
+ 'ignore',
189
+ 'pipe',
190
+ 'pipe'
191
+ ]
192
+ });
193
+ child.stdout.on('data', (chunk)=>{
194
+ output += chunk.toString();
195
+ });
196
+ child.stderr.on('data', (chunk)=>{
197
+ output += chunk.toString();
198
+ });
199
+ exitCode = await new Promise((resolve)=>{
200
+ const timer = setTimeout(()=>{
201
+ child.kill('SIGKILL');
202
+ resolve(null);
203
+ }, TIMEOUT_MS);
204
+ child.once('exit', (code)=>{
205
+ clearTimeout(timer);
206
+ resolve(code);
207
+ });
208
+ });
209
+ } finally{
210
+ await new Promise((resolve)=>setTimeout(resolve, 200));
211
+ }
212
+ assert.equal(exitCode, 0, `headless exit=${exitCode}; output tail=${output.slice(-1200)}`);
213
+ assert.ok(relay.plannerBodies.length >= 1, `expected at least one tool-enabled planner request, got ${relay.plannerBodies.length}; output tail=${output.slice(-12_000)}`);
214
+ const toolMessages = allToolMessages(relay.bodies);
215
+ const toolMessageDiagnostic = JSON.stringify(toolMessages).slice(-12_000);
216
+ const handoffResults = toolMessages.filter((message)=>JSON.stringify(message).includes(HANDOFF));
217
+ const successfulResults = toolMessages.filter((message)=>!JSON.stringify(message).includes(HANDOFF));
218
+ assert.ok(handoffResults.length >= 1, `expected at least one handoff result; messages=${toolMessageDiagnostic}`);
219
+ assert.equal(successfulResults.length, 0, `no tool body should run past the hard deadline; messages=${toolMessageDiagnostic}`);
220
+ assert.match(JSON.stringify(handoffResults[0]), /th-/, 'handoff result carries the ticket id');
221
+ const ticketDir = join(handoffRoot, 'gotry-state', 'turn-handoffs');
222
+ assert.ok(existsSync(ticketDir), `ticket dir missing: ${ticketDir}`);
223
+ const ticketFiles = readdirSync(ticketDir).filter((name)=>name.endsWith('.json'));
224
+ assert.equal(ticketFiles.length, 1, `expected exactly one handoff ticket, got ${ticketFiles.join(', ')}`);
225
+ const ticket = JSON.parse(readFileSync(join(ticketDir, ticketFiles[0]), 'utf8'));
226
+ assert.equal(ticket['schema'], HANDOFF_SCHEMA);
227
+ assert.equal(ticket['status'], 'open');
228
+ assert.ok(String(ticket['userMessage']).includes('婚礼'), 'ticket carries the verbatim deep-planning ask');
229
+ assert.ok(String(ticket['etaLabel']).includes('约 1 小时'));
230
+ const finalRequest = relay.bodies.at(-1);
231
+ const finalNames = toolNames(finalRequest);
232
+ assert.ok(!finalNames.includes(TOOL), `final request inherited GoTry tool: ${finalNames.join(', ')}`);
233
+ assert.match(output, /deadline E2E final answer/);
234
+ {
235
+ const ticketId = String(ticket['id']);
236
+ const collector = spawn(process.execPath, [
237
+ join(TS_DIR, 'node_modules', 'tsx', 'dist', 'cli.mjs'),
238
+ join(TS_DIR, 'scripts', 'turn-handoff-collect.ts'),
239
+ ticketId,
240
+ handoffRoot
241
+ ], {
242
+ cwd: TS_DIR,
243
+ env: {
244
+ ...process.env,
245
+ GOTRY_HANDOFF_PLANNER_BIN: configuredBin,
246
+ LLM_API_KEY: 'synthetic-e2e-key',
247
+ LLM_BASE_URL: 'http://127.0.0.1:' + relay.port + '/v1',
248
+ LLM_MODEL: 'synthetic-deadline-model',
249
+ GOTRY_LOCALE: 'zh-CN'
250
+ },
251
+ stdio: [
252
+ 'ignore',
253
+ 'pipe',
254
+ 'pipe'
255
+ ]
256
+ });
257
+ let collectorOut = '';
258
+ let collectorErr = '';
259
+ collector.stdout.on('data', (chunk)=>{
260
+ collectorOut += chunk.toString();
261
+ });
262
+ collector.stderr.on('data', (chunk)=>{
263
+ collectorErr += chunk.toString();
264
+ });
265
+ const collectorCode = await new Promise((resolve)=>{
266
+ const timer = setTimeout(()=>{
267
+ collector.kill('SIGKILL');
268
+ resolve(null);
269
+ }, 90_000);
270
+ collector.once('exit', (code)=>{
271
+ clearTimeout(timer);
272
+ resolve(code);
273
+ });
274
+ });
275
+ assert.equal(collectorCode, 0, `collector exit=${collectorCode}; stderr tail=${collectorErr.slice(-800)}; stdout tail=${collectorOut.slice(-400)}`);
276
+ const terminal = JSON.parse(collectorOut.trim().split('\n').at(-1));
277
+ assert.equal(terminal['schema'], 'gotry_turn_handoff_terminal.v1');
278
+ assert.equal(terminal['status'], 'succeeded');
279
+ assert.ok(String(terminal['deliverable_path']).includes(`${ticketId}.deliverable.md`));
280
+ const settled = JSON.parse(readFileSync(join(ticketDir, `${ticketId}.json`), 'utf8'));
281
+ assert.equal(settled['status'], 'settled');
282
+ assert.ok(settled['settledAt'], 'settledAt recorded');
283
+ const deliverable = readFileSync(join(ticketDir, `${ticketId}.deliverable.md`), 'utf8');
284
+ assert.ok(deliverable.includes('deadline E2E final answer'), `deliverable carries planner final; got: ${deliverable.slice(-400)}`);
285
+ }
286
+ console.log(`agent turn deadline E2E: OK (exit=${exitCode}, plannerRequests=${relay.plannerBodies.length}, handoffResults=${handoffResults.length}, ticket=${ticketFiles[0]}, collected=settled)`);
287
+ await relay.close();
288
+ rmSync(dshHome, {
289
+ recursive: true,
290
+ force: true
291
+ });
292
+ rmSync(childCwd, {
293
+ recursive: true,
294
+ force: true
295
+ });
296
+ rmSync(handoffRoot, {
297
+ recursive: true,
298
+ force: true
299
+ });
300
+ }
301
+ await main();
302
+
303
+
304
+ //# sourceURL=ts/scripts/agent-planning-turn-deadline-e2e.ts