@danceiny/gotry 0.0.1-rc.14 → 0.0.1-rc.16

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 (142) hide show
  1. package/README.md +26 -20
  2. package/README.zh-CN.md +27 -21
  3. package/bin/gotry-bootstrap.js +331 -3
  4. package/cordis.gotry-patch.yml +10 -0
  5. package/data/airline-airports.json +39 -0
  6. package/dist/capabilities/agent-reach-deep.js +1 -1
  7. package/dist/capabilities/agent-reach.js +1 -1
  8. package/dist/capabilities/anything.js +1 -1
  9. package/dist/capabilities/artifacts.js +217 -0
  10. package/dist/capabilities/effect.js +293 -0
  11. package/dist/capabilities/fact-log.js +41 -0
  12. package/dist/capabilities/flyai.js +1 -1
  13. package/dist/capabilities/hbcli.js +43 -1
  14. package/dist/capabilities/incident-log.js +1 -1
  15. package/dist/capabilities/opensky.js +1 -1
  16. package/dist/capabilities/resilience.js +90 -0
  17. package/dist/capabilities/session/action-cache.js +1 -1
  18. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  19. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  20. package/dist/capabilities/session/benchmark.js +2 -1
  21. package/dist/capabilities/session/extension-bridge.js +345 -0
  22. package/dist/capabilities/session/extension-channel.js +93 -0
  23. package/dist/capabilities/session/extract.js +1 -1
  24. package/dist/capabilities/session/health-watch.js +216 -0
  25. package/dist/capabilities/session/read-guard.js +1 -1
  26. package/dist/capabilities/session/transport.js +1 -1
  27. package/dist/capabilities/session/wizard.js +377 -0
  28. package/dist/capabilities/session-consent.js +1 -1
  29. package/dist/capabilities/session-login.js +77 -1
  30. package/dist/capabilities/session-search.js +69 -2
  31. package/dist/capabilities/weather.js +1 -1
  32. package/dist/scripts/action-cache-tests.js +1 -1
  33. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  34. package/dist/scripts/agent-reach-tests.js +1 -1
  35. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  36. package/dist/scripts/anything-tests.js +1 -1
  37. package/dist/scripts/async-collect.js +1 -1
  38. package/dist/scripts/booking-saga-tests.js +187 -0
  39. package/dist/scripts/bootstrap-tests.js +49 -3
  40. package/dist/scripts/build-changelog.js +279 -0
  41. package/dist/scripts/changelog-tests.js +165 -0
  42. package/dist/scripts/companion-tests.js +1 -1
  43. package/dist/scripts/diff-test.js +1 -1
  44. package/dist/scripts/effect-tests.js +474 -0
  45. package/dist/scripts/engine-run.js +1 -1
  46. package/dist/scripts/engine-tests.js +1 -1
  47. package/dist/scripts/extension-tests.js +468 -0
  48. package/dist/scripts/fact-gate-tests.js +318 -0
  49. package/dist/scripts/flyai-tests.js +1 -1
  50. package/dist/scripts/hbcli-e2e-tests.js +180 -0
  51. package/dist/scripts/hbcli-tests.js +1 -1
  52. package/dist/scripts/health-watch-cli.js +51 -0
  53. package/dist/scripts/i18n-tests.js +1 -1
  54. package/dist/scripts/incident-tests.js +1 -1
  55. package/dist/scripts/journey-tests.js +1 -1
  56. package/dist/scripts/ledger-tests.js +1 -1
  57. package/dist/scripts/ledger-workflow-crash.js +1 -1
  58. package/dist/scripts/memory-capture-tests.js +1 -1
  59. package/dist/scripts/memory-decay-tests.js +1 -1
  60. package/dist/scripts/memory-metrics.js +1 -1
  61. package/dist/scripts/memory-value-report.js +1 -1
  62. package/dist/scripts/nightly-evidence-tests.js +186 -0
  63. package/dist/scripts/nightly-evidence.js +289 -0
  64. package/dist/scripts/nudge-digest.js +1 -1
  65. package/dist/scripts/onboarding-tests.js +218 -0
  66. package/dist/scripts/opensky-check.js +1 -1
  67. package/dist/scripts/opensky-tests.js +1 -1
  68. package/dist/scripts/price-drift-tests.js +441 -0
  69. package/dist/scripts/price-drift-watch.js +491 -0
  70. package/dist/scripts/probe-poi-tests.js +1 -1
  71. package/dist/scripts/product-metrics.js +1 -1
  72. package/dist/scripts/publish-preverify.js +1 -1
  73. package/dist/scripts/realtime-pricing-tests.js +1 -1
  74. package/dist/scripts/replay-async.js +1 -1
  75. package/dist/scripts/replay-real.js +1 -1
  76. package/dist/scripts/replay.js +1 -1
  77. package/dist/scripts/session-attach-diagnose.js +1 -1
  78. package/dist/scripts/session-attach-poc.js +1 -1
  79. package/dist/scripts/session-benchmark.js +1 -1
  80. package/dist/scripts/session-extract-tests.js +1 -1
  81. package/dist/scripts/session-login.js +1 -1
  82. package/dist/scripts/session-tests.js +1 -1
  83. package/dist/scripts/skeleton-check.js +1 -1
  84. package/dist/scripts/skeleton-integration-test.js +1 -1
  85. package/dist/scripts/skills-contract-tests.js +1 -1
  86. package/dist/scripts/smoke.js +159 -1
  87. package/dist/scripts/state-cli-tests.js +1 -1
  88. package/dist/scripts/state-cli.js +1 -1
  89. package/dist/scripts/time-eval-tests.js +1 -1
  90. package/dist/scripts/travel-timeline-tests.js +1 -1
  91. package/dist/scripts/unified-tests.js +1 -1
  92. package/dist/scripts/weather-tests.js +1 -1
  93. package/dist/scripts/wizard-bootstrap.js +32 -0
  94. package/dist/scripts/z3-race-tests.js +1 -1
  95. package/dist/src/artifact-gate.js +310 -0
  96. package/dist/src/bookable-facts.js +349 -0
  97. package/dist/src/booking-saga.js +153 -0
  98. package/dist/src/bridge.js +1 -1
  99. package/dist/src/companions.js +1 -1
  100. package/dist/src/contracts.js +1 -1
  101. package/dist/src/dsh-llm.js +31 -7
  102. package/dist/src/engine.js +1 -1
  103. package/dist/src/i18n.js +1 -1
  104. package/dist/src/index.js +322 -46
  105. package/dist/src/journey.js +1 -1
  106. package/dist/src/loop.js +1 -1
  107. package/dist/src/memory-capture.js +1 -1
  108. package/dist/src/memory-decay.js +1 -1
  109. package/dist/src/memory-utility.js +1 -1
  110. package/dist/src/mock-llm.js +1 -1
  111. package/dist/src/model.js +1 -1
  112. package/dist/src/realtime-pricing.js +14 -7
  113. package/dist/src/slot-spec.js +1 -1
  114. package/dist/src/state-ledger.js +1 -1
  115. package/dist/src/time-anchor.js +1 -1
  116. package/dist/src/tool-packet.js +1 -1
  117. package/dist/src/travel-slots.js +1 -1
  118. package/dist/src/travel-timeline.js +1 -1
  119. package/dist/src/unified.js +1 -1
  120. package/dist/src/wish-pool.js +1 -1
  121. package/dist/src/z3-shared.js +1 -1
  122. package/extension/README.md +26 -0
  123. package/extension/background.js +160 -0
  124. package/extension/content-bridge.js +41 -0
  125. package/extension/content-main.js +62 -0
  126. package/extension/manifest.json +32 -0
  127. package/package.json +8 -2
  128. package/ts/capabilities/artifacts.ts +235 -0
  129. package/ts/capabilities/fact-log.ts +54 -0
  130. package/ts/capabilities/hbcli.ts +46 -0
  131. package/ts/capabilities/session/benchmark.ts +4 -1
  132. package/ts/capabilities/session/extension-bridge.ts +354 -0
  133. package/ts/capabilities/session/extension-channel.ts +92 -0
  134. package/ts/capabilities/session/health-watch.ts +257 -0
  135. package/ts/capabilities/session/wizard.ts +282 -0
  136. package/ts/capabilities/session-login.ts +53 -2
  137. package/ts/capabilities/session-search.ts +68 -3
  138. package/ts/src/artifact-gate.ts +365 -0
  139. package/ts/src/bookable-facts.ts +557 -0
  140. package/ts/src/booking-saga.ts +133 -0
  141. package/ts/src/dsh-llm.ts +40 -3
  142. package/ts/src/index.ts +230 -28
@@ -1,4 +1,6 @@
1
1
  import { openSession } from './session/transport.js';
2
+ import { extensionCookieNames, extensionOpenLogin, classifyBridgeFailure, NEEDS_EXTENSION_HINT } from './session/extension-channel.js';
3
+ import { resolveTransportMode } from './session-search.js';
2
4
  export const LOGIN_TARGETS = {
3
5
  'ctrip-flight': {
4
6
  domain: 'ctrip.com',
@@ -47,6 +49,9 @@ export async function sessionLogin(q = {}) {
47
49
  if (!target) {
48
50
  return err(site, 'error', `未知站点 ${site}(可选 ${Object.keys(LOGIN_TARGETS).join('/')})`, started, ts);
49
51
  }
52
+ if (resolveTransportMode() === 'extension') {
53
+ return sessionLoginViaExtension(site, target, q, started, ts);
54
+ }
50
55
  const t = await openSession({
51
56
  mode: 'cdp',
52
57
  guard: false
@@ -114,6 +119,77 @@ export async function sessionLogin(q = {}) {
114
119
  return err(site, 'error', e instanceof Error ? e.message : String(e), started, ts);
115
120
  }
116
121
  }
122
+ async function sessionLoginViaExtension(site, target, q, started, ts) {
123
+ const evidenceTag = `[会话:${site}-login@${ts}]`;
124
+ const quick = (verdict, error)=>({
125
+ ok: false,
126
+ via: 'session-login-error',
127
+ evidence: `[会话:login-error@${ts}] ${error.slice(0, 200)}`,
128
+ latencyMs: Date.now() - started,
129
+ verdict,
130
+ site,
131
+ error: verdict === 'needs-extension' ? `${error};${NEEDS_EXTENSION_HINT}`.slice(0, 400) : error.slice(0, 400)
132
+ });
133
+ const pre = await extensionCookieNames({
134
+ site,
135
+ domain: target.domain,
136
+ ticketNames: target.names,
137
+ timeoutMs: 8_000
138
+ });
139
+ if (!pre.ok) return quick(classifyBridgeFailure(pre.kind) === 'needs-extension' ? 'needs-extension' : 'error', pre.summary);
140
+ if (pre.tickets.length > 0) {
141
+ return {
142
+ ok: true,
143
+ via: 'session-login',
144
+ latencyMs: Date.now() - started,
145
+ verdict: 'logged-in',
146
+ site,
147
+ tickets: pre.tickets,
148
+ evidence: `${evidenceTag} 自动检测:票据 cookie 已在(先前登录已生效)——[${pre.tickets.join(', ')}](只读名字,0 网页交互,零弹窗)`
149
+ };
150
+ }
151
+ const opened = await extensionOpenLogin({
152
+ site,
153
+ url: target.entryUrl,
154
+ timeoutMs: 15_000
155
+ });
156
+ if (!opened.ok) return quick(classifyBridgeFailure(opened.kind) === 'needs-extension' ? 'needs-extension' : 'error', opened.summary);
157
+ const waitMs = Math.min(Math.max(q.waitMs ?? 90_000, 0), 300_000);
158
+ const pollMs = Math.min(Math.max(q.pollMs ?? 3_000, 500), 10_000);
159
+ const deadline = Date.now() + waitMs;
160
+ let tickets = [];
161
+ while(Date.now() < deadline){
162
+ await new Promise((resolve)=>setTimeout(resolve, pollMs));
163
+ const poll = await extensionCookieNames({
164
+ site,
165
+ domain: target.domain,
166
+ ticketNames: target.names,
167
+ timeoutMs: 8_000
168
+ });
169
+ if (poll.ok) tickets = poll.tickets;
170
+ if (tickets.length > 0) break;
171
+ }
172
+ if (tickets.length > 0) {
173
+ return {
174
+ ok: true,
175
+ via: 'session-login',
176
+ latencyMs: Date.now() - started,
177
+ verdict: 'logged-in',
178
+ site,
179
+ tickets,
180
+ evidence: `${evidenceTag} 票据 cookie 已检出 [${tickets.join(', ')}](只读名字;登录在你自己的浏览器里完成,gotry 全程未接触任何密码/验证码/cookie 值)`
181
+ };
182
+ }
183
+ return {
184
+ ok: true,
185
+ via: 'session-login',
186
+ latencyMs: Date.now() - started,
187
+ verdict: 'pending',
188
+ site,
189
+ tickets: [],
190
+ evidence: `${evidenceTag} 登录入口已在你的 Chrome 置前打开(${target.label});在标签页里正常登录完成后再说一声「继续查」即可——gotry 只检查"是否已登录",永不收集你的账号信息`
191
+ };
192
+ }
117
193
 
118
194
 
119
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session-login.ts
195
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session-login.ts
@@ -1,4 +1,7 @@
1
1
  import { openSession } from './session/transport.js';
2
+ import { extensionCookieNames, extensionSearchJob, classifyBridgeFailure, NEEDS_EXTENSION_HINT } from './session/extension-channel.js';
3
+ import { appendFileSync, mkdirSync } from 'node:fs';
4
+ import { dirname } from 'node:path';
2
5
  import { buildEntryUrl, NETWORK_HINTS, parseBatchSearch, LOGIN_COOKIE_NAMES, SITE_DOMAIN } from './session/adapters/ctrip-flight.js';
3
6
  const MIN_INTERVAL_MS = 30_000;
4
7
  const lastCallAt = new Map();
@@ -9,6 +12,24 @@ const CHALLENGE_RE = /验证|滑块|captcha|verify/i;
9
12
  export function classifyTransportFailure(summary, cdpMode) {
10
13
  return cdpMode && /日常 Chrome 未开调试端口|cdp attach 失败/.test(summary) ? 'needs-attach' : 'error';
11
14
  }
15
+ export function resolveTransportMode(profileDir) {
16
+ if (profileDir !== undefined) return 'persistent';
17
+ return (process.env.GOTRY_SESSION_TRANSPORT ?? '').trim().toLowerCase() === 'cdp' ? 'cdp' : 'extension';
18
+ }
19
+ export function appendExtensionAudit(auditPath, entry) {
20
+ if (!auditPath) return;
21
+ try {
22
+ const record = {
23
+ ts: new Date().toISOString(),
24
+ ...entry,
25
+ url: entry.url.slice(0, 400)
26
+ };
27
+ mkdirSync(dirname(auditPath), {
28
+ recursive: true
29
+ });
30
+ appendFileSync(auditPath, JSON.stringify(record) + '\n');
31
+ } catch {}
32
+ }
12
33
  export async function sessionFlightSearch(q) {
13
34
  const started = Date.now();
14
35
  const ts = new Date().toISOString();
@@ -30,11 +51,57 @@ export async function sessionFlightSearch(q) {
30
51
  if (!entry.ok || !entry.url) {
31
52
  return err('error', `unresolved entry: ${(entry.unresolved ?? []).join('/')} 不在城市码表`);
32
53
  }
54
+ const mode = resolveTransportMode(q.profileDir);
55
+ if (mode === 'extension') {
56
+ const login = await extensionCookieNames({
57
+ site,
58
+ domain: SITE_DOMAIN.replace(/^\./, ''),
59
+ ticketNames: LOGIN_COOKIE_NAMES
60
+ });
61
+ if (!login.ok) {
62
+ const verdict = classifyBridgeFailure(login.kind);
63
+ return err(verdict, verdict === 'needs-extension' ? `${login.summary};${NEEDS_EXTENSION_HINT}` : login.summary);
64
+ }
65
+ if (login.tickets.length === 0 && !q.allowAnonymous) {
66
+ return err('needs-login', '未检出你本人登录态——调用 gotry_session_login 为用户打开携程登录入口(登录在携程官网完成;gotry 永不经手密码/验证码/cookie 值)');
67
+ }
68
+ const r = await extensionSearchJob({
69
+ site,
70
+ url: entry.url,
71
+ timeoutMs: q.timeoutMs
72
+ });
73
+ appendExtensionAudit(q.auditPath, {
74
+ kind: 'extension-session-job',
75
+ site,
76
+ url: entry.url,
77
+ jobId: 'search',
78
+ result: r.ok ? r.timedOut ? 'timeout' : `body ${r.body.length}B title="${r.title.slice(0, 60)}"` : `${r.kind}:${r.summary.slice(0, 120)}`
79
+ });
80
+ if (!r.ok) {
81
+ const verdict = classifyBridgeFailure(r.kind);
82
+ return err(verdict, verdict === 'needs-extension' ? `${r.summary};${NEEDS_EXTENSION_HINT}` : r.summary);
83
+ }
84
+ const title = r.title;
85
+ const head = r.body.slice(0, 5000);
86
+ if (CHALLENGE_RE.test(title + head)) {
87
+ return err('challenged', `风控/验证码命中(title=${title.slice(0, 60)});按红线不重试不绕过,交还用户`);
88
+ }
89
+ const options = parseBatchSearch(r.body);
90
+ const verdict = options.length > 0 ? 'hit' : 'miss';
91
+ return {
92
+ ok: true,
93
+ via: 'session-ctrip-flight',
94
+ evidence: `[会话:${site}@${ts}] ${options.length} options;transport=extension(被动嗅探,零系统弹窗;扩展零写行为=物理只读)${q.allowAnonymous ? ';anonymous=自检态' : ''}`,
95
+ latencyMs: Date.now() - started,
96
+ verdict,
97
+ options
98
+ };
99
+ }
33
100
  const t = await openSession({
34
101
  profileDir: q.profileDir,
35
102
  headless: q.headless,
36
103
  auditPath: q.auditPath,
37
- mode: q.profileDir ? 'persistent' : 'cdp',
104
+ mode: mode === 'persistent' ? 'persistent' : 'cdp',
38
105
  newPage: true
39
106
  });
40
107
  if (!t.ok) {
@@ -93,4 +160,4 @@ export async function sessionFlightSearch(q) {
93
160
  }
94
161
 
95
162
 
96
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session-search.ts
163
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session-search.ts
@@ -232,4 +232,4 @@ export async function getClimate(point, month, opts = {}) {
232
232
  }
233
233
 
234
234
 
235
- //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/weather.ts
235
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/weather.ts
@@ -104,4 +104,4 @@ console.log(`\nACTION-CACHE: ${pass} pass, ${fail} fail`);
104
104
  process.exit(fail > 0 ? 1 : 0);
105
105
 
106
106
 
107
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/action-cache-tests.ts
107
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/action-cache-tests.ts
@@ -51,4 +51,4 @@ import { videoSubtitle, githubSearch } from '../capabilities/agent-reach-deep.js
51
51
  }console.log('\nAGENT-REACH DEEP TESTS: 4/4 OK(yt-dlp/gh 可选工具,不装则降级)');
52
52
 
53
53
 
54
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/agent-reach-deep-tests.ts
54
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/agent-reach-deep-tests.ts
@@ -35,4 +35,4 @@ import { readUrl } from '../capabilities/agent-reach.js';
35
35
  }console.log('\nAGENT-REACH TESTS: 3/3 OK(readUrl 薄壳;上游真读见 wrapper 套)');
36
36
 
37
37
 
38
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/agent-reach-tests.ts
38
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/agent-reach-tests.ts
@@ -80,4 +80,4 @@ import { reach, reachStatus } from '../capabilities/agent-reach.js';
80
80
  }console.log('\nAGENT-REACH WRAPPER TESTS: 7/7 OK(反射桥 + doctor 透传 + 自描述错误)');
81
81
 
82
82
 
83
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/agent-reach-wrapper-tests.ts
83
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/agent-reach-wrapper-tests.ts
@@ -92,4 +92,4 @@ while :; do sleep 5; done
92
92
  }
93
93
 
94
94
 
95
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/anything-tests.ts
95
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/anything-tests.ts
@@ -62,4 +62,4 @@ console.log(JSON.stringify(outcome));
62
62
  process.exitCode = outcome.status === 'succeeded' ? 0 : 2;
63
63
 
64
64
 
65
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/async-collect.ts
65
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/async-collect.ts
@@ -0,0 +1,187 @@
1
+ import { mkdtempSync, rmSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { BOOKING_SAGA_SCHEMA, SAGA_EDGES, SAGA_REJECTIONS, SAGA_STATUSES, SAGA_TRIGGER_EVENT, resolveSagaTrigger, sagaLegalPaths, sagaTraceViolations } from '../src/booking-saga.js';
5
+ import { ensureLedger } from '../src/state-ledger.js';
6
+ let pass = 0;
7
+ let fail = 0;
8
+ function assert(cond, msg) {
9
+ if (cond) {
10
+ pass++;
11
+ console.log(` ok - ${msg}`);
12
+ } else {
13
+ fail++;
14
+ console.error(` FAIL - ${msg}`);
15
+ }
16
+ }
17
+ function parseReceipt(payload) {
18
+ try {
19
+ const doc = JSON.parse(payload);
20
+ return typeof doc.receipt === 'string' && doc.receipt !== '' ? doc.receipt : undefined;
21
+ } catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ const TRIGGERS = [
26
+ 'propose',
27
+ 'confirm',
28
+ 'compensate'
29
+ ];
30
+ const ORIGINS = [
31
+ 'none',
32
+ ...SAGA_STATUSES
33
+ ];
34
+ const COMBOS = ORIGINS.flatMap((from)=>TRIGGERS.map((trigger)=>`${from}:${trigger}`));
35
+ const edgeKeys = new Set(SAGA_EDGES.map((e)=>`${e.from}:${e.trigger}`));
36
+ const rejectKeys = new Set(SAGA_REJECTIONS.map((e)=>`${e.from}:${e.trigger}`));
37
+ assert(BOOKING_SAGA_SCHEMA === 'booking_saga_fsm.v1' && SAGA_STATUSES.join(',') === 'pending,confirmed,compensated', 'schema 词唯一;状态字母表与 pending_writes CHECK 逐字一致');
38
+ assert(COMBOS.length === 12 && new Set(COMBOS).size === 12 && edgeKeys.size === SAGA_EDGES.length && rejectKeys.size === SAGA_REJECTIONS.length, '12 个 (origin,trigger) 组合;边表与拒绝表内部无重复');
39
+ assert(COMBOS.every((k)=>edgeKeys.has(k) !== rejectKeys.has(k)), '每格恰为一条合法边或一条结构化拒绝(全函数,无空洞无第三态)');
40
+ assert(SAGA_EDGES.every((e)=>SAGA_STATUSES.includes(e.to) && e.event === SAGA_TRIGGER_EVENT[e.trigger] && e.guard.length > 0), '合法边:终点在字母表内、事件 kind 与触发器一一对应、守卫语义齐全');
41
+ const allConsistent = ORIGINS.every((from)=>TRIGGERS.every((trigger)=>{
42
+ const v = resolveSagaTrigger(from, trigger);
43
+ const edge = SAGA_EDGES.find((e)=>e.from === from && e.trigger === trigger);
44
+ if (edge) return v.ok && v.to === edge.to && v.event === edge.event;
45
+ return !v.ok && v.reason === SAGA_REJECTIONS.find((x)=>x.from === from && x.trigger === trigger).reason;
46
+ }));
47
+ assert(allConsistent, 'resolveSagaTrigger 十二格与边表/拒绝表全一致(解析器无平行第二逻辑)');
48
+ {
49
+ const vProp = resolveSagaTrigger('none', 'propose');
50
+ const vConf = resolveSagaTrigger('pending', 'confirm');
51
+ const vComp = resolveSagaTrigger('confirmed', 'compensate');
52
+ assert(vProp.ok && vProp.to === 'pending' && vConf.ok && vConf.to === 'confirmed' && vComp.ok && vComp.to === 'compensated', '主路径 ∅→pending→confirmed→compensated 连通(L2 登记只不执行 / L3 确认携 receipt / saga 补偿)');
53
+ const vDouble = resolveSagaTrigger('confirmed', 'confirm');
54
+ assert(!vDouble.ok && vDouble.reason === 'already-confirmed', '二次确认结构化拒绝(already-confirmed)');
55
+ const vIdem = resolveSagaTrigger('compensated', 'propose');
56
+ const vRevive = resolveSagaTrigger('compensated', 'confirm');
57
+ const vReComp = resolveSagaTrigger('compensated', 'compensate');
58
+ assert(!vIdem.ok && vIdem.reason === 'idem-exists' && !vRevive.ok && vRevive.reason === 'absorbed-compensated' && !vReComp.ok && vReComp.reason === 'already-compensated', 'compensated 吸收态:propose=幂等存在,confirm/compensate 拒绝(无复活路径)');
59
+ assert(sagaLegalPaths().length === 4, '合法路径恰四条(主路径及其真前缀)');
60
+ const legalClean = sagaLegalPaths().every((path)=>{
61
+ const events = path.map((trigger, i)=>({
62
+ seq: i + 1,
63
+ kind: SAGA_TRIGGER_EVENT[trigger],
64
+ receipt: trigger === 'confirm' ? 'PNR-X' : undefined
65
+ }));
66
+ return sagaTraceViolations(events).length === 0;
67
+ });
68
+ assert(legalClean, '四条合法路径的审计链均零违例');
69
+ const flagOf = (events)=>sagaTraceViolations(events)[0];
70
+ assert(flagOf([
71
+ {
72
+ kind: 'write.confirmed',
73
+ receipt: 'PNR-A'
74
+ }
75
+ ]) !== undefined, '缺 pending 起点的 confirm 被审计链抓出');
76
+ assert(flagOf([
77
+ {
78
+ kind: 'write.pending'
79
+ },
80
+ {
81
+ kind: 'write.confirmed',
82
+ receipt: 'PNR-A'
83
+ },
84
+ {
85
+ kind: 'write.confirmed',
86
+ receipt: 'PNR-B'
87
+ }
88
+ ]) !== undefined, '双确认被审计链抓出(每键至多确认一次)');
89
+ assert(flagOf([
90
+ {
91
+ kind: 'write.pending'
92
+ },
93
+ {
94
+ kind: 'write.compensated'
95
+ },
96
+ {
97
+ kind: 'write.confirmed',
98
+ receipt: 'X'
99
+ }
100
+ ]) !== undefined, 'compensated 之后不得再有 saga 事件(吸收态)');
101
+ assert((flagOf([
102
+ {
103
+ kind: 'write.pending'
104
+ },
105
+ {
106
+ kind: 'write.confirmed'
107
+ }
108
+ ]) ?? '').includes('receipt'), 'write.confirmed 空 receipt 被抓(L3 必携外部回执)');
109
+ }const root = mkdtempSync(join(tmpdir(), 'gotry-booking-saga-'));
110
+ try {
111
+ const ledger = ensureLedger(root);
112
+ const sagaEventsOf = (l, key)=>{
113
+ const rows = l.db.prepare(`SELECT seq, kind, payload FROM events WHERE tenant_id = ? AND subject_id = ? AND kind IN ('write.pending','write.confirmed','write.compensated') ORDER BY seq`).all(l.tenant, key);
114
+ return rows.map((r)=>({
115
+ seq: r.seq,
116
+ kind: r.kind,
117
+ receipt: parseReceipt(r.payload)
118
+ }));
119
+ };
120
+ const K1 = 'booking:K1';
121
+ const proposed = ledger.requestPendingWrite({
122
+ idemKey: K1,
123
+ seam: 'flight-order-confirm',
124
+ payload: {
125
+ flight: 'MU123'
126
+ }
127
+ });
128
+ assert(proposed.created === true && proposed.status === 'pending' && resolveSagaTrigger('none', 'propose').to === 'pending', '物理提议 ∅→pending 与状态机同归宿(L2 只登记不执行)');
129
+ const dup = ledger.requestPendingWrite({
130
+ idemKey: K1,
131
+ seam: 'flight-order-confirm',
132
+ payload: {
133
+ flight: 'MU123'
134
+ }
135
+ });
136
+ assert(dup.created === false && dup.status === 'pending' && resolveSagaTrigger('pending', 'propose').reason === 'idem-exists', '同幂等键重复提议:账本 no-op = 状态机 idem-exists');
137
+ const confirmed = ledger.confirmPendingWrite(K1, 'PNR-A');
138
+ assert(confirmed.ok === true && confirmed.status === 'confirmed' && resolveSagaTrigger('pending', 'confirm').to === 'confirmed', 'L3 具名确认 pending→confirmed(携 receipt)');
139
+ const reConfirmed = ledger.confirmPendingWrite(K1, 'PNR-SHOULD-IGNORE');
140
+ assert(reConfirmed.ok === false && (ledger.listPendingWrites().find((w)=>w.idem_key === K1)?.receipt ?? null) === 'PNR-A' && resolveSagaTrigger('confirmed', 'confirm').reason === 'already-confirmed', '二次确认被账本守卫拒绝且 receipt 不可变(仍为 PNR-A)');
141
+ const compensated = ledger.compensatePendingWrite(K1, '用户改签退款');
142
+ assert(compensated.ok === true && ledger.listPendingWrites().find((w)=>w.idem_key === K1)?.status === 'compensated', '已确认副作用可 saga 补偿(pending|confirmed → compensated)');
143
+ const afterConfirm = ledger.confirmPendingWrite(K1, 'PNR-POST');
144
+ const afterCompensate = ledger.compensatePendingWrite(K1, '重复补偿');
145
+ const afterPropose = ledger.requestPendingWrite({
146
+ idemKey: K1,
147
+ seam: 'flight-order-confirm',
148
+ payload: {}
149
+ });
150
+ assert(afterConfirm.ok === false && afterCompensate.ok === false && afterPropose.created === false, '吸收态:compensated 后 confirm/compensate/propose 全拒,零复活');
151
+ const audit = sagaEventsOf(ledger, K1);
152
+ assert(audit.map((e)=>e.kind).join(',') === 'write.pending,write.confirmed,write.compensated', '审计事件链与边表路径同构(被拒调用零事件)');
153
+ assert(sagaTraceViolations(audit).length === 0, '真实账本审计链过词汇层校验零违例');
154
+ const K3 = 'booking:K3';
155
+ ledger.requestPendingWrite({
156
+ idemKey: K3,
157
+ seam: 'hotel-order-confirm',
158
+ payload: {}
159
+ });
160
+ const emptyReceipt = ledger.confirmPendingWrite(K3, '');
161
+ assert(emptyReceipt.ok === true && sagaTraceViolations(sagaEventsOf(ledger, K3)).some((v)=>v.includes('receipt')), '已知边界:账本物理层暂不挡空 receipt,词汇层审计链抓出(物理 CHECK 随 M5 拍板入 schema)');
162
+ const other = ensureLedger(root, 'tenant-b');
163
+ const crossProp = other.requestPendingWrite({
164
+ idemKey: K1,
165
+ seam: 'flight-order-confirm',
166
+ payload: {}
167
+ });
168
+ assert(crossProp.created === true, '同 idem_key 跨租户互不可见:tenant-b 可建自己的 K1(tenant_id 一等字段,ADR-16)');
169
+ const LOCAL_ONLY = 'booking:local-only';
170
+ ledger.requestPendingWrite({
171
+ idemKey: LOCAL_ONLY,
172
+ seam: 'flight-order-confirm',
173
+ payload: {}
174
+ });
175
+ const crossMissing = other.confirmPendingWrite(LOCAL_ONLY, 'PNR-CROSS');
176
+ assert(crossMissing.ok === false && crossMissing.status === 'missing', 'tenant-b 看不到 local 的 saga 键(拒绝 missing)');
177
+ } finally{
178
+ rmSync(root, {
179
+ recursive: true,
180
+ force: true
181
+ });
182
+ }
183
+ console.log(`\nBOOKING SAGA TESTS: ${pass} ok, ${fail} fail`);
184
+ if (fail > 0) process.exit(1);
185
+
186
+
187
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/booking-saga-tests.ts
@@ -35,7 +35,9 @@ assert.equal(c1.code, 0, `--check-only 应 exit 0,实际 ${c1.code}\n${c1.out}`)
35
35
  assert.ok(c1.out.includes('hbcli'), '报告应含 hbcli 节');
36
36
  assert.ok(c1.out.includes('agent-reach'), '报告应含 agent-reach 节');
37
37
  assert.ok(c1.out.includes('flyai'), '报告应含 flyai(无需安装)节');
38
- console.log('1. --check-only 探测报告 exit 0(hbcli/agent-reach/flyai 三节齐)OK');
38
+ assert.ok(c1.out.includes('dsh-better-sidebar'), '报告应含 dsh-better-sidebar 节(issue #25 产物查看面)');
39
+ assert.ok(c1.out.includes('Session Bridge'), '报告应含会话扩展节(issue #21 传输层方案 C)');
40
+ console.log('1. --check-only 探测报告 exit 0(hbcli/agent-reach/flyai/dsh-better-sidebar/会话扩展 五节齐)OK');
39
41
  const c2 = runBootstrap([
40
42
  '--auto'
41
43
  ], {
@@ -50,7 +52,51 @@ const c3 = runBootstrap([], {
50
52
  assert.equal(c3.code, 0, '显式模式跳过态应 exit 0');
51
53
  assert.ok(c3.out.includes('跳过'), '应输出跳过说明');
52
54
  console.log('3. 显式模式 + GOTRY_SETUP_SKIP=1 → exit 0 OK');
53
- console.log('BOOTSTRAP TESTS: 3/3 OK(探测报告/跳过开关/postinstall 非致命)');
55
+ const c4 = runBootstrap([
56
+ '--check-only'
57
+ ], {
58
+ GOTRY_SETUP_SIDEBAR: '0'
59
+ });
60
+ assert.equal(c4.code, 0, '单项跳过态应 exit 0');
61
+ assert.ok(c4.out.includes('GOTRY_SETUP_SIDEBAR=0 跳过'), '应输出侧栏单项跳过说明');
62
+ assert.ok(c4.out.includes('hbcli'), '其余节不受单项开关影响');
63
+ console.log('4. GOTRY_SETUP_SIDEBAR=0 单项跳过(其余节照常)OK');
64
+ const c5 = runBootstrap([
65
+ '--check-only'
66
+ ], {});
67
+ assert.ok(c5.out.includes('.gotry/extension'), '扩展报告应含落位路径 ~/.gotry/extension(绝对路径形态)');
68
+ const c5b = runBootstrap([
69
+ '--check-only'
70
+ ], {
71
+ GOTRY_SETUP_EXTENSION: '0'
72
+ });
73
+ assert.equal(c5b.code, 0, '扩展单项跳过态应 exit 0');
74
+ assert.ok(c5b.out.includes('GOTRY_SETUP_EXTENSION=0 跳过'), '应输出扩展单项跳过说明');
75
+ console.log('5. 会话扩展节(check-only 报告 + GOTRY_SETUP_EXTENSION=0 单项跳过)OK');
76
+ const c6 = runBootstrap([
77
+ 'wizard',
78
+ '--dry-run'
79
+ ], {});
80
+ assert.equal(c6.code, 0, `wizard --dry-run 应 exit 0,实际 ${c6.code}\n${c6.out}`);
81
+ assert.ok(c6.out.includes('dry-run'), '应输出 dry-run 字样');
82
+ assert.ok(c6.out.includes('Gotry Wizard') || c6.out.includes('gotry-wizard') || c6.out.includes('3 步'), '应输出引导步骤');
83
+ assert.ok(c6.out.includes('ensure-extension-files'), 'dry-run 应列 5 步');
84
+ assert.ok(c6.out.includes('open-chrome-extensions'), 'dry-run 应列开 chrome://extensions');
85
+ assert.ok(c6.out.includes('clipboard-extension-path'), 'dry-run 应列剪贴板');
86
+ assert.ok(c6.out.includes('panel-guide'), 'dry-run 应列面板');
87
+ assert.ok(c6.out.includes('watch-extension-ready'), 'dry-run 应列 watch precheck');
88
+ console.log('6. wizard 子命令(--dry-run 零网络,5 步齐全 + 引导文案)OK');
89
+ const c7 = runBootstrap([
90
+ 'wizard'
91
+ ], {
92
+ GOTRY_SETUP_EXTENSION: '0',
93
+ GOTRY_ONBOARDING_TIMEOUT_MS: '600',
94
+ GOTRY_ONBOARDING_INTERVAL_MS: '200'
95
+ });
96
+ assert.equal(c7.code, 1, `wizard(超时)应 exit 1,实际 ${c7.code}\n${c7.out}`);
97
+ assert.ok(c7.out.includes('3 步') || c7.out.includes('gotry-wizard'), '应输出引导标题');
98
+ console.log('7. wizard 真实路径(扩展未就绪,exit 1 + 引导标题 + 心跳)OK');
99
+ console.log('BOOTSTRAP TESTS: 7/7 OK(探测报告/跳过开关/单项开关/postinstall 非致命/会话扩展/wizard --dry-run/wizard 真实)');
54
100
 
55
101
 
56
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/bootstrap-tests.ts
102
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/bootstrap-tests.ts