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

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 (229) hide show
  1. package/README.md +47 -13
  2. package/README.zh-CN.md +20 -10
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +111 -51
  5. package/bin/gotry-inner.js +442 -60
  6. package/bin/gotry-runtime-resolution.d.ts +27 -0
  7. package/bin/gotry-runtime-resolution.js +50 -0
  8. package/bin/gotry.js +1 -1
  9. package/cordis.gotry-patch.yml +5 -1
  10. package/dist/capabilities/agent-reach-deep.js +1 -1
  11. package/dist/capabilities/agent-reach.js +1 -1
  12. package/dist/capabilities/anything.js +1 -1
  13. package/dist/capabilities/artifacts.js +1 -1
  14. package/dist/capabilities/effect.js +1 -1
  15. package/dist/capabilities/fact-log.js +1 -1
  16. package/dist/capabilities/flyai.js +20 -6
  17. package/dist/capabilities/hbcli.js +1 -1
  18. package/dist/capabilities/incident-log.js +1 -1
  19. package/dist/capabilities/model-override.js +18 -0
  20. package/dist/capabilities/opensky.js +1 -1
  21. package/dist/capabilities/resilience.js +1 -1
  22. package/dist/capabilities/session/action-cache.js +1 -1
  23. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  24. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  25. package/dist/capabilities/session/benchmark.js +1 -1
  26. package/dist/capabilities/session/extension-bridge.js +19 -6
  27. package/dist/capabilities/session/extension-channel.js +3 -3
  28. package/dist/capabilities/session/extension-distribution.js +234 -0
  29. package/dist/capabilities/session/extract.js +1 -1
  30. package/dist/capabilities/session/golden-score.js +92 -0
  31. package/dist/capabilities/session/health-watch.js +1 -1
  32. package/dist/capabilities/session/read-guard.js +1 -1
  33. package/dist/capabilities/session/static-flight-golden.js +137 -0
  34. package/dist/capabilities/session/transport.js +1 -1
  35. package/dist/capabilities/session/wizard.js +21 -251
  36. package/dist/capabilities/session-consent.js +1 -1
  37. package/dist/capabilities/session-login.js +11 -5
  38. package/dist/capabilities/session-search.js +46 -4
  39. package/dist/capabilities/weather.js +168 -46
  40. package/dist/data/session-golden-20.json +25 -0
  41. package/dist/data/sf-golden-manifest.json +102 -0
  42. package/dist/data/sf-static-routes.json +91 -0
  43. package/dist/scripts/action-cache-tests.js +1 -1
  44. package/dist/scripts/agent-planning-budget-e2e.js +227 -0
  45. package/dist/scripts/agent-planning-budget-tests.js +173 -0
  46. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  47. package/dist/scripts/agent-reach-tests.js +1 -1
  48. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  49. package/dist/scripts/anything-tests.js +1 -1
  50. package/dist/scripts/async-collect.js +1 -1
  51. package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
  52. package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
  53. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
  54. package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
  55. package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
  56. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
  57. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
  58. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
  59. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
  60. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
  61. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
  62. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
  63. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
  64. package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
  65. package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
  66. package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
  67. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
  68. package/dist/scripts/booking-saga-tests.js +1 -1
  69. package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
  70. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
  71. package/dist/scripts/bootstrap-tests.js +30 -10
  72. package/dist/scripts/build-changelog.js +1 -1
  73. package/dist/scripts/changelog-tests.js +1 -1
  74. package/dist/scripts/companion-tests.js +1 -1
  75. package/dist/scripts/diff-test.js +1 -1
  76. package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
  77. package/dist/scripts/dsh-runtime-closure.js +150 -0
  78. package/dist/scripts/effect-tests.js +1 -1
  79. package/dist/scripts/engine-run.js +1 -1
  80. package/dist/scripts/engine-tests.js +1 -1
  81. package/dist/scripts/evaluation-cadence-tests.js +347 -0
  82. package/dist/scripts/evaluation-contract-tests.js +574 -0
  83. package/dist/scripts/extension-distribution-cli.js +35 -0
  84. package/dist/scripts/extension-distribution-tests.js +341 -0
  85. package/dist/scripts/extension-tests.js +128 -23
  86. package/dist/scripts/fact-gate-tests.js +1 -1
  87. package/dist/scripts/flyai-tests.js +1 -1
  88. package/dist/scripts/hbcli-e2e-tests.js +1 -1
  89. package/dist/scripts/hbcli-tests.js +1 -1
  90. package/dist/scripts/health-watch-cli.js +1 -1
  91. package/dist/scripts/i18n-tests.js +1 -1
  92. package/dist/scripts/incident-tests.js +1 -1
  93. package/dist/scripts/journey-tests.js +1 -1
  94. package/dist/scripts/ledger-tests.js +1 -1
  95. package/dist/scripts/ledger-workflow-crash.js +1 -1
  96. package/dist/scripts/memory-capture-tests.js +1 -1
  97. package/dist/scripts/memory-decay-tests.js +1 -1
  98. package/dist/scripts/memory-metrics.js +1 -1
  99. package/dist/scripts/memory-value-report.js +1 -1
  100. package/dist/scripts/model-override-e2e.js +176 -0
  101. package/dist/scripts/nightly-evidence-tests.js +1 -1
  102. package/dist/scripts/nightly-evidence.js +1 -1
  103. package/dist/scripts/nudge-digest.js +1 -1
  104. package/dist/scripts/onboarding-tests.js +21 -53
  105. package/dist/scripts/opensky-check.js +1 -1
  106. package/dist/scripts/opensky-tests.js +1 -1
  107. package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
  108. package/dist/scripts/price-drift-tests.js +1 -1
  109. package/dist/scripts/price-drift-watch.js +1 -1
  110. package/dist/scripts/probe-poi-tests.js +1 -1
  111. package/dist/scripts/product-metrics.js +1 -1
  112. package/dist/scripts/publish-preverify.js +40 -4
  113. package/dist/scripts/realtime-pricing-tests.js +1 -1
  114. package/dist/scripts/replay-async.js +1 -1
  115. package/dist/scripts/replay-real.js +1 -1
  116. package/dist/scripts/replay.js +1 -1
  117. package/dist/scripts/session-attach-diagnose.js +1 -1
  118. package/dist/scripts/session-attach-poc.js +1 -1
  119. package/dist/scripts/session-benchmark.js +1 -1
  120. package/dist/scripts/session-extract-tests.js +1 -1
  121. package/dist/scripts/session-login.js +1 -1
  122. package/dist/scripts/session-tests.js +70 -20
  123. package/dist/scripts/sf-live-benchmark.js +338 -0
  124. package/dist/scripts/sf-live-cli-tests.js +21 -0
  125. package/dist/scripts/sf-soft-score-tests.js +108 -0
  126. package/dist/scripts/sf-summary.js +93 -0
  127. package/dist/scripts/skeleton-check.js +1 -1
  128. package/dist/scripts/skeleton-integration-test.js +1 -1
  129. package/dist/scripts/skills-contract-tests.js +1 -1
  130. package/dist/scripts/smoke-session-gate-tests.js +29 -0
  131. package/dist/scripts/smoke.js +79 -33
  132. package/dist/scripts/state-cli-tests.js +1 -1
  133. package/dist/scripts/state-cli.js +1 -1
  134. package/dist/scripts/static-golden-tests.js +299 -0
  135. package/dist/scripts/time-eval-tests.js +1 -1
  136. package/dist/scripts/travel-timeline-tests.js +1 -1
  137. package/dist/scripts/unified-tests.js +1 -1
  138. package/dist/scripts/weather-tests.js +694 -44
  139. package/dist/scripts/z3-race-tests.js +1 -1
  140. package/dist/src/artifact-gate.js +1 -1
  141. package/dist/src/benchmark-agent-conformance.js +370 -0
  142. package/dist/src/benchmark-environment-bridge.js +384 -0
  143. package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
  144. package/dist/src/benchmark-tool-isolation.js +124 -0
  145. package/dist/src/bookable-facts.js +1 -1
  146. package/dist/src/booking-saga.js +1 -1
  147. package/dist/src/booking-surface/availability-policy-v2.js +830 -0
  148. package/dist/src/booking-surface/canonical-schema.js +113 -0
  149. package/dist/src/booking-surface/contracts-v2.js +89 -0
  150. package/dist/src/booking-surface/contracts.js +46 -0
  151. package/dist/src/booking-surface/dsh-planner.js +453 -0
  152. package/dist/src/booking-surface/dsh-plugin.js +93 -0
  153. package/dist/src/booking-surface/error-codes.js +94 -0
  154. package/dist/src/booking-surface/index.js +15 -0
  155. package/dist/src/booking-surface/profile.js +68 -0
  156. package/dist/src/booking-surface/runtime-v2.js +1771 -0
  157. package/dist/src/booking-surface/runtime.js +351 -0
  158. package/dist/src/booking-surface/server-v2.js +334 -0
  159. package/dist/src/booking-surface/server.js +302 -0
  160. package/dist/src/booking-surface/startup.js +159 -0
  161. package/dist/src/booking-surface/validation-v2.js +319 -0
  162. package/dist/src/booking-surface/validation.js +809 -0
  163. package/dist/src/bridge.js +1 -1
  164. package/dist/src/companions.js +1 -1
  165. package/dist/src/contracts.js +1 -1
  166. package/dist/src/dsh-llm.js +1 -1
  167. package/dist/src/engine.js +1 -1
  168. package/dist/src/evaluation-cadence.js +234 -0
  169. package/dist/src/evaluation-contracts.js +906 -0
  170. package/dist/src/i18n.js +1 -1
  171. package/dist/src/index.js +50 -19
  172. package/dist/src/journey.js +1 -1
  173. package/dist/src/loop.js +1 -1
  174. package/dist/src/memory-capture.js +1 -1
  175. package/dist/src/memory-decay.js +1 -1
  176. package/dist/src/memory-utility.js +1 -1
  177. package/dist/src/mock-llm.js +1 -1
  178. package/dist/src/model.js +1 -1
  179. package/dist/src/realtime-pricing.js +1 -1
  180. package/dist/src/slot-spec.js +1 -1
  181. package/dist/src/state-ledger.js +2 -1
  182. package/dist/src/time-anchor.js +1 -1
  183. package/dist/src/tool-budget.js +136 -0
  184. package/dist/src/tool-packet.js +1 -1
  185. package/dist/src/travel-slots.js +1 -1
  186. package/dist/src/travel-timeline.js +1 -1
  187. package/dist/src/unified.js +1 -1
  188. package/dist/src/wish-pool.js +1 -1
  189. package/dist/src/z3-shared.js +1 -1
  190. package/extension/README.md +31 -7
  191. package/package.json +286 -11
  192. package/schemas/booking.surface.v1.schema.json +927 -0
  193. package/schemas/booking.surface.v2.schema.json +61 -0
  194. package/ts/capabilities/flyai.ts +16 -3
  195. package/ts/capabilities/session/extension-bridge.ts +37 -14
  196. package/ts/capabilities/session/extension-channel.ts +6 -3
  197. package/ts/capabilities/session/extension-distribution.ts +264 -0
  198. package/ts/capabilities/session/golden-score.ts +139 -0
  199. package/ts/capabilities/session/health-watch.ts +1 -1
  200. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  201. package/ts/capabilities/session/wizard.ts +34 -176
  202. package/ts/capabilities/session-login.ts +15 -5
  203. package/ts/capabilities/session-search.ts +40 -3
  204. package/ts/capabilities/weather.ts +141 -52
  205. package/ts/package.json +3 -3
  206. package/ts/src/benchmark-agent-conformance.ts +448 -0
  207. package/ts/src/benchmark-environment-bridge.ts +348 -0
  208. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  209. package/ts/src/benchmark-tool-isolation.ts +166 -0
  210. package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
  211. package/ts/src/booking-surface/canonical-schema.js +113 -0
  212. package/ts/src/booking-surface/contracts-v2.ts +118 -0
  213. package/ts/src/booking-surface/contracts.ts +380 -0
  214. package/ts/src/booking-surface/dsh-planner.ts +452 -0
  215. package/ts/src/booking-surface/dsh-plugin.js +93 -0
  216. package/ts/src/booking-surface/error-codes.ts +101 -0
  217. package/ts/src/booking-surface/index.ts +12 -0
  218. package/ts/src/booking-surface/profile.ts +42 -0
  219. package/ts/src/booking-surface/runtime-v2.ts +1466 -0
  220. package/ts/src/booking-surface/runtime.ts +483 -0
  221. package/ts/src/booking-surface/server-v2.ts +247 -0
  222. package/ts/src/booking-surface/server.ts +324 -0
  223. package/ts/src/booking-surface/startup.ts +196 -0
  224. package/ts/src/booking-surface/validation-v2.ts +205 -0
  225. package/ts/src/booking-surface/validation.ts +453 -0
  226. package/ts/src/index.ts +64 -11
  227. package/ts/src/state-ledger.ts +1 -0
  228. package/ts/src/tool-budget.ts +165 -0
  229. package/dist/scripts/wizard-bootstrap.js +0 -32
@@ -0,0 +1,341 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFileSync, spawn } from 'node:child_process';
3
+ import { createServer } from 'node:http';
4
+ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join, dirname } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { DEFAULT_RELEASE_BASE, DIST_ASSET_MANIFEST, DIST_ASSET_STORE_ZIP, DIST_ASSET_TARBALL, compareVersions, distAssetUrls, installExtensionFromGithub, parseDistManifest, readExtensionKey, sha256Hex } from '../capabilities/session/extension-distribution.js';
9
+ const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
10
+ const EXT_SRC = join(repoRoot, 'extension');
11
+ const EXT_FILES = [
12
+ 'manifest.json',
13
+ 'background.js',
14
+ 'content-main.js',
15
+ 'content-bridge.js',
16
+ 'README.md'
17
+ ];
18
+ let passed = 0;
19
+ function ok(cond, label) {
20
+ if (!cond) {
21
+ console.error(` ✗ ${label}`);
22
+ process.exit(1);
23
+ }
24
+ passed += 1;
25
+ console.log(` ok - ${label}`);
26
+ }
27
+ {
28
+ const urls = distAssetUrls();
29
+ ok(urls.tarball === `${DEFAULT_RELEASE_BASE}/latest/download/${DIST_ASSET_TARBALL}`, `tarball URL 形态(${urls.tarball})`);
30
+ ok(urls.manifest.endsWith(`/latest/download/${DIST_ASSET_MANIFEST}`), 'manifest URL 形态(latest/download 永久链)');
31
+ ok(distAssetUrls('https://x.example/releases/').tarball === 'https://x.example/releases/latest/download/gotry-session-bridge.tar.gz', 'releaseBase 尾斜杠归一');
32
+ const pkgSrc = readFileSync(join(repoRoot, 'scripts', 'package-extension.mjs'), 'utf8');
33
+ ok(pkgSrc.includes(`const ASSET_TARBALL = '${DIST_ASSET_TARBALL}'`), 'package-extension.mjs tarball 资产名一致');
34
+ ok(pkgSrc.includes(`const ASSET_STORE_ZIP = '${DIST_ASSET_STORE_ZIP}'`), 'package-extension.mjs store zip 资产名一致');
35
+ ok(pkgSrc.includes(`const ASSET_MANIFEST = '${DIST_ASSET_MANIFEST}'`), 'package-extension.mjs dist-manifest 资产名一致');
36
+ ok(pkgSrc.includes("const STAGE = join(OUT, 'gotry-session-bridge')"), '打包根目录名固定 gotry-session-bridge/(下载端 strip-components 依赖)');
37
+ ok(pkgSrc.includes('delete m.key'), 'store zip 变体剥离 manifest key(2026-08-30 商店首传实测拒绝 key;tar.gz 保留 key 保通道 ID 不变量)');
38
+ }{
39
+ const good = JSON.stringify({
40
+ version: '0.1.0',
41
+ tarball: DIST_ASSET_TARBALL,
42
+ tarballSha256: 'a'.repeat(64),
43
+ zip: DIST_ASSET_STORE_ZIP,
44
+ zipSha256: 'b'.repeat(64)
45
+ });
46
+ ok(parseDistManifest(good).version === '0.1.0', '合法 dist-manifest 解析');
47
+ const rejects = [
48
+ [
49
+ '{bad json',
50
+ '坏 JSON'
51
+ ],
52
+ [
53
+ JSON.stringify({
54
+ version: 'x',
55
+ tarball: DIST_ASSET_TARBALL,
56
+ tarballSha256: 'a'.repeat(64),
57
+ zip: DIST_ASSET_STORE_ZIP,
58
+ zipSha256: 'b'.repeat(64)
59
+ }),
60
+ 'version 非 x.y.z'
61
+ ],
62
+ [
63
+ JSON.stringify({
64
+ tarball: DIST_ASSET_TARBALL,
65
+ tarballSha256: 'a'.repeat(64),
66
+ zip: DIST_ASSET_STORE_ZIP,
67
+ zipSha256: 'b'.repeat(64)
68
+ }),
69
+ '缺 version'
70
+ ],
71
+ [
72
+ JSON.stringify({
73
+ version: '0.1.0',
74
+ tarball: 'other-name.tar.gz',
75
+ tarballSha256: 'a'.repeat(64),
76
+ zip: DIST_ASSET_STORE_ZIP,
77
+ zipSha256: 'b'.repeat(64)
78
+ }),
79
+ 'tarball 资产名漂移'
80
+ ],
81
+ [
82
+ JSON.stringify({
83
+ version: '0.1.0',
84
+ tarball: DIST_ASSET_TARBALL,
85
+ tarballSha256: 'zz',
86
+ zip: DIST_ASSET_STORE_ZIP,
87
+ zipSha256: 'b'.repeat(64)
88
+ }),
89
+ 'tarballSha256 非 64-hex'
90
+ ]
91
+ ];
92
+ for (const [raw, label] of rejects){
93
+ let threw = false;
94
+ try {
95
+ parseDistManifest(raw);
96
+ } catch {
97
+ threw = true;
98
+ }
99
+ ok(threw, `fail-closed:${label}`);
100
+ }
101
+ }{
102
+ ok(compareVersions('0.1.0', '0.1.1') < 0, '0.1.0 < 0.1.1');
103
+ ok(compareVersions('0.2.0', '0.1.9') > 0, '0.2.0 > 0.1.9');
104
+ ok(compareVersions('1.0.0', '0.9.9') > 0, '1.0.0 > 0.9.9');
105
+ ok(compareVersions('0.1.0', '0.1.0') === 0, '相等为 0');
106
+ ok(compareVersions('0.1', '0.1.0') === 0, '缺段按 0 补齐');
107
+ }async function startLoopback(manifestJson, tarball) {
108
+ let hits = 0;
109
+ const server = createServer((req, res)=>{
110
+ if (req.url?.endsWith(DIST_ASSET_MANIFEST)) {
111
+ res.writeHead(200, {
112
+ 'content-type': 'application/json'
113
+ });
114
+ res.end(manifestJson);
115
+ } else if (req.url?.endsWith(DIST_ASSET_TARBALL)) {
116
+ hits += 1;
117
+ res.writeHead(200, {
118
+ 'content-type': 'application/gzip'
119
+ });
120
+ res.end(tarball);
121
+ } else {
122
+ res.writeHead(404);
123
+ res.end('not found');
124
+ }
125
+ });
126
+ await new Promise((r)=>server.listen(0, '127.0.0.1', r));
127
+ const addr = server.address();
128
+ const base = `http://127.0.0.1:${typeof addr === 'object' && addr ? addr.port : 0}/releases`;
129
+ return {
130
+ base,
131
+ manifestJson,
132
+ tarball,
133
+ tarballHits: ()=>hits,
134
+ close: ()=>new Promise((r)=>server.close(()=>r()))
135
+ };
136
+ }
137
+ function buildTarball(sourceDir, versionOverride) {
138
+ const stage = mkdtempSync(join(tmpdir(), 'extdist-stage-'));
139
+ const root = join(stage, 'gotry-session-bridge');
140
+ mkdirSync(root);
141
+ for (const f of EXT_FILES)copyFileSync(join(sourceDir, f), join(root, f));
142
+ if (versionOverride != null) {
143
+ const m = JSON.parse(readFileSync(join(root, 'manifest.json'), 'utf8'));
144
+ writeFileSync(join(root, 'manifest.json'), JSON.stringify({
145
+ ...m,
146
+ version: versionOverride
147
+ }));
148
+ }
149
+ const tarPath = join(stage, DIST_ASSET_TARBALL);
150
+ execFileSync('tar', [
151
+ '-czf',
152
+ tarPath,
153
+ '-C',
154
+ stage,
155
+ 'gotry-session-bridge'
156
+ ]);
157
+ const tarball = readFileSync(tarPath);
158
+ rmSync(stage, {
159
+ recursive: true,
160
+ force: true
161
+ });
162
+ return {
163
+ tarball,
164
+ sha: sha256Hex(tarball)
165
+ };
166
+ }
167
+ function distManifestJson(version, sha) {
168
+ return JSON.stringify({
169
+ version,
170
+ tarball: DIST_ASSET_TARBALL,
171
+ tarballSha256: sha,
172
+ zip: DIST_ASSET_STORE_ZIP,
173
+ zipSha256: 'c'.repeat(64),
174
+ builtFromCommit: 'test'
175
+ });
176
+ }
177
+ {
178
+ const work = mkdtempSync(join(tmpdir(), 'extdist-e2e-'));
179
+ const { tarball, sha } = buildTarball(EXT_SRC, '9.9.9');
180
+ const srcVersion = JSON.parse(readFileSync(join(EXT_SRC, 'manifest.json'), 'utf8'));
181
+ const remoteVersion = '9.9.9';
182
+ {
183
+ const dest = join(work, 'dest1');
184
+ const lb = await startLoopback(distManifestJson(remoteVersion, sha), tarball);
185
+ const r = await installExtensionFromGithub({
186
+ destDir: dest,
187
+ pinnedSourceDir: EXT_SRC,
188
+ releaseBase: lb.base
189
+ });
190
+ lb.close();
191
+ ok(r.ok && r.action === 'installed' && r.version === remoteVersion, `installed 全链(action=${r.action},v=${r.version},err=${r.error ?? '无'})`);
192
+ ok(readExtensionKey(dest) === srcVersion.key, '落位产物 key 与 bundled 逐字一致(同扩展 ID)');
193
+ ok(JSON.parse(readFileSync(join(dest, 'manifest.json'), 'utf8')).version === remoteVersion, '落位产物为远端版本');
194
+ for (const f of EXT_FILES)ok(existsSync(join(dest, f)), `文件就位:${f}`);
195
+ }
196
+ {
197
+ const dest = join(work, 'dest2');
198
+ const lb = await startLoopback(distManifestJson(remoteVersion, '0'.repeat(64)), tarball);
199
+ const r = await installExtensionFromGithub({
200
+ destDir: dest,
201
+ pinnedSourceDir: EXT_SRC,
202
+ releaseBase: lb.base
203
+ });
204
+ lb.close();
205
+ ok(!r.ok && r.action === 'fallback-bundled' && /SHA256/.test(r.error ?? ''), `SHA256 不符拒绝(${r.error})`);
206
+ ok(!existsSync(dest), '失败路径零落盘');
207
+ }
208
+ {
209
+ const dest = join(work, 'dest3');
210
+ const lb = await startLoopback(distManifestJson(remoteVersion, sha), tarball);
211
+ lb.close();
212
+ const r = await installExtensionFromGithub({
213
+ destDir: dest,
214
+ releaseBase: lb.base
215
+ });
216
+ ok(!r.ok && r.action === 'fallback-bundled', `无网降级 fallback-bundled(${(r.error ?? '').slice(0, 40)})`);
217
+ }
218
+ {
219
+ const rogue = mkdtempSync(join(work, 'rogue-'));
220
+ const rogueManifest = {
221
+ ...srcVersion,
222
+ version: remoteVersion,
223
+ key: `ROGUE-${srcVersion.key}`
224
+ };
225
+ writeFileSync(join(rogue, 'manifest.json'), JSON.stringify(rogueManifest));
226
+ for (const f of EXT_FILES.filter((x)=>x !== 'manifest.json'))copyFileSync(join(EXT_SRC, f), join(rogue, f));
227
+ const { tarball: rTar, sha: rSha } = buildTarball(rogue);
228
+ const dest = join(work, 'dest4');
229
+ const lb = await startLoopback(distManifestJson(remoteVersion, rSha), rTar);
230
+ const r = await installExtensionFromGithub({
231
+ destDir: dest,
232
+ pinnedSourceDir: EXT_SRC,
233
+ releaseBase: lb.base
234
+ });
235
+ lb.close();
236
+ ok(!r.ok && /key 漂移/.test(r.error ?? ''), `key 漂移拒绝(${r.error})`);
237
+ ok(!existsSync(dest), '漂移路径零落盘');
238
+ rmSync(rogue, {
239
+ recursive: true,
240
+ force: true
241
+ });
242
+ }
243
+ {
244
+ const dest = join(work, 'dest5');
245
+ mkdirSync(dest, {
246
+ recursive: true
247
+ });
248
+ writeFileSync(join(dest, 'manifest.json'), JSON.stringify({
249
+ ...srcVersion,
250
+ version: remoteVersion
251
+ }));
252
+ const lb = await startLoopback(distManifestJson(remoteVersion, sha), tarball);
253
+ const r = await installExtensionFromGithub({
254
+ destDir: dest,
255
+ pinnedSourceDir: EXT_SRC,
256
+ releaseBase: lb.base
257
+ });
258
+ const hits = lb.tarballHits();
259
+ lb.close();
260
+ ok(r.ok && r.action === 'up-to-date' && hits === 0, `up-to-date 跳过(tarball 下载 ${hits} 次)`);
261
+ }
262
+ {
263
+ const dest = join(work, 'dest6');
264
+ const lb = await startLoopback(distManifestJson(remoteVersion, sha), tarball);
265
+ const r = await installExtensionFromGithub({
266
+ destDir: dest,
267
+ releaseBase: lb.base,
268
+ checkOnly: true
269
+ });
270
+ lb.close();
271
+ ok(r.ok && r.version === remoteVersion && !existsSync(dest), 'check-only 报告远端版本且零落盘');
272
+ }
273
+ rmSync(work, {
274
+ recursive: true,
275
+ force: true
276
+ });
277
+ }{
278
+ const { tarball, sha } = buildTarball(EXT_SRC, '9.9.9');
279
+ const srcVersion = JSON.parse(readFileSync(join(EXT_SRC, 'manifest.json'), 'utf8'));
280
+ const lb = await startLoopback(distManifestJson('9.9.9', sha), tarball);
281
+ const dest = mkdtempSync(join(tmpdir(), 'extdist-cli-'));
282
+ const cli = join(repoRoot, 'ts', 'scripts', 'extension-distribution-cli.ts');
283
+ const run = (args)=>new Promise((resolve)=>{
284
+ const child = spawn('npx', [
285
+ '--yes',
286
+ 'tsx',
287
+ cli,
288
+ ...args
289
+ ], {
290
+ cwd: join(repoRoot, 'ts'),
291
+ stdio: [
292
+ 'ignore',
293
+ 'pipe',
294
+ 'inherit'
295
+ ]
296
+ });
297
+ let buf = '';
298
+ child.stdout.on('data', (c)=>{
299
+ buf += c.toString('utf8');
300
+ });
301
+ child.on('close', (code)=>{
302
+ const line = buf.trim().split('\n').pop() ?? '';
303
+ let json = null;
304
+ if (line.startsWith('{')) {
305
+ try {
306
+ json = JSON.parse(line);
307
+ } catch {}
308
+ }
309
+ resolve({
310
+ code: code ?? -1,
311
+ json
312
+ });
313
+ });
314
+ });
315
+ const r1 = await run([
316
+ '--dest',
317
+ dest,
318
+ '--source-dir',
319
+ EXT_SRC,
320
+ '--release-base',
321
+ lb.base
322
+ ]);
323
+ ok(r1.code === 0 && r1.json?.ok === true && r1.json?.action === 'installed', `CLI installed 单行 JSON + exit 0(action=${String(r1.json?.action)})`);
324
+ const r2 = await run([
325
+ '--dest',
326
+ dest,
327
+ '--source-dir',
328
+ EXT_SRC,
329
+ '--release-base',
330
+ 'http://127.0.0.1:1/releases'
331
+ ]);
332
+ ok(r2.code === 2 && r2.json?.ok === false && r2.json?.action === 'fallback-bundled', `CLI 失败 exit 2 + fallback-bundled JSON`);
333
+ lb.close();
334
+ rmSync(dest, {
335
+ recursive: true,
336
+ force: true
337
+ });
338
+ }console.log(`EXTENSION DISTRIBUTION: ${passed} pass, 0 fail`);
339
+
340
+
341
+ //# sourceURL=ts/scripts/extension-distribution-tests.ts
@@ -1,16 +1,18 @@
1
1
  import assert from 'node:assert/strict';
2
+ import { fork } from 'node:child_process';
2
3
  import { createHash } from 'node:crypto';
3
4
  import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
4
5
  import { tmpdir } from 'node:os';
5
6
  import { join } from 'node:path';
6
7
  import { fileURLToPath } from 'node:url';
7
- import { BRIDGE_PORTS, EXTENSION_ID, EXTENSION_ORIGIN, createSessionBridge, __resetSessionBridgeForTest, __setSessionBridgeForTest } from '../capabilities/session/extension-bridge.js';
8
+ import { BRIDGE_PORTS, EXTENSION_ID, EXTENSION_ID_STORE, EXTENSION_ORIGIN, EXTENSION_ORIGIN_STORE, EXTENSION_ORIGINS, EXTENSION_STORE_URL, createSessionBridge, __resetSessionBridgeForTest, __setSessionBridgeForTest } from '../capabilities/session/extension-bridge.js';
8
9
  import { classifyBridgeFailure, extensionCookieNames, extensionSearchJob } from '../capabilities/session/extension-channel.js';
9
10
  import { appendExtensionAudit, resolveTransportMode, sessionFlightSearch } from '../capabilities/session-search.js';
10
11
  import { sessionLogin } from '../capabilities/session-login.js';
11
12
  import { LOGIN_COOKIE_NAMES, NETWORK_HINTS, SITE_DOMAIN } from '../capabilities/session/adapters/ctrip-flight.js';
12
13
  import { evaluateDoubleSource } from '../capabilities/session/benchmark.js';
13
14
  const EXT_DIR = fileURLToPath(new URL('../../extension/', import.meta.url));
15
+ const UNREF_CHILD = fileURLToPath(new URL('./fixtures/extension-bridge-unref-child.mjs', import.meta.url));
14
16
  const read = (f)=>readFileSync(join(EXT_DIR, f), 'utf8');
15
17
  let passed = 0;
16
18
  async function check(label, assertion) {
@@ -20,8 +22,7 @@ async function check(label, assertion) {
20
22
  }
21
23
  const mustBridge = async (ports)=>{
22
24
  const created = await createSessionBridge({
23
- ports,
24
- extensionOrigin: EXTENSION_ORIGIN
25
+ ports
25
26
  });
26
27
  if (!created.ok) throw new Error(created.summary);
27
28
  return created.bridge;
@@ -61,15 +62,89 @@ async function heartbeat(port) {
61
62
  }
62
63
  });
63
64
  }
65
+ async function waitForParkedCount(port, expected, timeoutMs = 1_000) {
66
+ const deadline = Date.now() + timeoutMs;
67
+ while(Date.now() < deadline){
68
+ const r = await fetch(`http://127.0.0.1:${port}/status`);
69
+ const body = await r.json();
70
+ if (body.parked === expected) return;
71
+ await new Promise((resolve)=>setTimeout(resolve, 10));
72
+ }
73
+ throw new Error(`/status 未在 ${timeoutMs}ms 内报告 parked=${expected}`);
74
+ }
75
+ async function assertParkedClientDoesNotPinHost() {
76
+ const child = fork(UNREF_CHILD, [], {
77
+ execArgv: process.execArgv,
78
+ stdio: [
79
+ 'ignore',
80
+ 'ignore',
81
+ 'pipe',
82
+ 'ipc'
83
+ ]
84
+ });
85
+ let stderr = '';
86
+ child.stderr?.on('data', (chunk)=>{
87
+ stderr += chunk.toString('utf8');
88
+ });
89
+ let parkedRequest = null;
90
+ try {
91
+ const port = await new Promise((resolve, reject)=>{
92
+ const timer = setTimeout(()=>reject(new Error('子进程扩展桥未在 2s 内监听')), 2_000);
93
+ child.once('message', (message)=>{
94
+ clearTimeout(timer);
95
+ const data = message;
96
+ if (data.kind === 'listening' && typeof data.port === 'number') resolve(data.port);
97
+ else reject(new Error(data.summary ?? `子进程消息异常:${JSON.stringify(data)}`));
98
+ });
99
+ child.once('exit', (code, signal)=>{
100
+ clearTimeout(timer);
101
+ reject(new Error(`子进程过早退出 code=${code} signal=${signal} ${stderr}`));
102
+ });
103
+ });
104
+ parkedRequest = claimOnce(port, null).catch(()=>({
105
+ job: null
106
+ }));
107
+ await waitForParkedCount(port, 1);
108
+ const exitP = new Promise((resolve, reject)=>{
109
+ const timer = setTimeout(()=>{
110
+ child.kill();
111
+ reject(new Error('默认桥被 parked /jobs 长轮询钉住超过 1500ms'));
112
+ }, 1_500);
113
+ child.once('exit', (code, signal)=>{
114
+ clearTimeout(timer);
115
+ resolve({
116
+ code,
117
+ signal
118
+ });
119
+ });
120
+ });
121
+ child.send({
122
+ kind: 'release'
123
+ });
124
+ const exited = await exitP;
125
+ assert.equal(exited.code, 0, `子进程应自然退出:${stderr}`);
126
+ assert.equal(exited.signal, null);
127
+ } finally{
128
+ if (child.exitCode === null && child.signalCode === null) child.kill();
129
+ if (parkedRequest) await parkedRequest;
130
+ }
131
+ }
64
132
  const stripRe = (s)=>s.replace(/\\/g, '');
65
133
  async function main() {
66
134
  console.log('§38 扩展传输车道合同(extension bridge,全离线)');
67
135
  const manifest = JSON.parse(read('manifest.json'));
68
136
  const derivedId = createHash('sha256').update(Buffer.from(manifest.key, 'base64')).digest().subarray(0, 16).toString('hex').replace(/[0-9a-f]/g, (c)=>'abcdefghijklmnop'[Number.parseInt(c, 16)]);
69
- await check('manifest key 派生扩展 ID = Node 侧固定 EXTENSION_ID(origin 白名单锚点)', ()=>{
137
+ await check('manifest key 派生扩展 ID = Node 侧固定 EXTENSION_ID(unpacked 通道 origin 白名单锚点)', ()=>{
70
138
  assert.equal(derivedId, EXTENSION_ID);
71
139
  assert.equal(EXTENSION_ORIGIN, `chrome-extension://${EXTENSION_ID}`);
72
140
  });
141
+ await check('双通道白名单:商店版 ID(CWS 重签,不认 manifest key)与 unpacked 固定 ID 同信,商店 URL 锚定 item ID', ()=>{
142
+ assert.equal(EXTENSION_ORIGIN_STORE, `chrome-extension://${EXTENSION_ID_STORE}`);
143
+ assert.ok(EXTENSION_ORIGINS.includes(EXTENSION_ORIGIN));
144
+ assert.ok(EXTENSION_ORIGINS.includes(EXTENSION_ORIGIN_STORE));
145
+ assert.equal(EXTENSION_ORIGINS.length, 2);
146
+ assert.ok(EXTENSION_STORE_URL.includes(EXTENSION_ID_STORE));
147
+ });
73
148
  await check('manifest 合同:MV3 + 最小权限(cookies/alarms;无 debugger/tabs)', ()=>{
74
149
  assert.equal(manifest.manifest_version, 3);
75
150
  assert.deepEqual([
@@ -157,6 +232,7 @@ async function main() {
157
232
  assert.equal(body.ok, true);
158
233
  assert.equal(body.service, 'gotry-session-bridge');
159
234
  });
235
+ await check('默认桥:parked 扩展长轮询不得钉住宿主 CLI 进程', assertParkedClientDoesNotPinHost);
160
236
  await check('origin 白名单:邪恶源(网页跨域必带)POST /jobs 与 /results 一律 403', async ()=>{
161
237
  for (const path of [
162
238
  '/jobs',
@@ -173,6 +249,26 @@ async function main() {
173
249
  assert.equal(r.status, 403, path);
174
250
  }
175
251
  });
252
+ await check('origin 白名单:商店版扩展源(CWS 重签 ID,2026-09-02 上架)POST 放行且计入心跳', async ()=>{
253
+ const storeLane = await mustBridge([
254
+ 0
255
+ ]);
256
+ try {
257
+ assert.equal(storeLane.extensionConnected(), false);
258
+ const r = await fetch(`http://127.0.0.1:${storeLane.port}/results/whatever`, {
259
+ method: 'POST',
260
+ headers: {
261
+ 'content-type': 'application/json',
262
+ origin: EXTENSION_ORIGIN_STORE
263
+ },
264
+ body: '{}'
265
+ });
266
+ assert.equal(r.status, 200, '商店版扩展源不得吃 403(否则商店通道全断)');
267
+ assert.equal(storeLane.extensionConnected(), true, '白名单源的任何请求都刷新心跳');
268
+ } finally{
269
+ await storeLane.close();
270
+ }
271
+ });
176
272
  await check('心跳判定:初始未连接;合法请求(health/长轮询)后视为在线', async ()=>{
177
273
  assert.equal(b.extensionConnected(), false);
178
274
  await heartbeat(port);
@@ -211,31 +307,40 @@ async function main() {
211
307
  assert.ok(!/\.value/.test(backgroundJs), 'background.js 不得触碰 cookie.value');
212
308
  });
213
309
  await check('长轮询幂等:并发取活只有一个客户端领到 job;后到者空手(快速中止,不挂进程)', async ()=>{
214
- const submitP = b.submit({
215
- kind: 'cookie-names',
216
- site: 'ctrip-flight',
217
- timeoutMs: 5_000
218
- }, {
219
- timeoutMs: 5_000
220
- });
221
- const firstP = claimOnce(port, (job)=>({
310
+ const firstAc = new AbortController();
311
+ const secondAc = new AbortController();
312
+ const firstP = claimOnce(port, ()=>({
222
313
  ok: true,
223
314
  kind: 'cookie-names',
224
315
  names: [
225
316
  'cticket'
226
317
  ]
227
- }));
228
- const ac = new AbortController();
229
- setTimeout(()=>ac.abort(), 300);
230
- const secondP = claimOnce(port, null, ac.signal).catch(()=>({
318
+ }), firstAc.signal).catch(()=>({
231
319
  job: null
232
320
  }));
233
- const first = await firstP;
234
- assert.ok(first.job, '先到的取活者必须领到 job');
235
- const outcome = await submitP;
236
- assert.ok(outcome.ok);
237
- ac.abort();
238
- await secondP;
321
+ let secondP = null;
322
+ try {
323
+ await waitForParkedCount(port, 1);
324
+ secondP = claimOnce(port, null, secondAc.signal).catch(()=>({
325
+ job: null
326
+ }));
327
+ await waitForParkedCount(port, 2);
328
+ const submitP = b.submit({
329
+ kind: 'cookie-names',
330
+ site: 'ctrip-flight',
331
+ timeoutMs: 5_000
332
+ }, {
333
+ timeoutMs: 5_000
334
+ });
335
+ const first = await firstP;
336
+ assert.ok(first.job, '先进入 parked 队列的取活者必须领到 job');
337
+ const outcome = await submitP;
338
+ assert.ok(outcome.ok);
339
+ } finally{
340
+ firstAc.abort();
341
+ secondAc.abort();
342
+ if (secondP) await secondP;
343
+ }
239
344
  });
240
345
  await check('超时语义:扩展在线但不回包 → timeout(降级 error,不伪装成功)', async ()=>{
241
346
  const outcome = await b.submit({
@@ -465,4 +570,4 @@ main().catch((e)=>{
465
570
  });
466
571
 
467
572
 
468
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/extension-tests.ts
573
+ //# sourceURL=ts/scripts/extension-tests.ts
@@ -315,4 +315,4 @@ console.log(`\nFACT GATE TESTS: ${pass} pass, ${fail} fail`);
315
315
  if (fail > 0) process.exit(1);
316
316
 
317
317
 
318
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/fact-gate-tests.ts
318
+ //# sourceURL=ts/scripts/fact-gate-tests.ts
@@ -92,4 +92,4 @@ await rm(tmp, {
92
92
  console.log('FLYAI TESTS: 4/4 OK(离线假 CLI:Sentinel→error / 空 itemList→miss / 命中→hit / exit≠0→error)');
93
93
 
94
94
 
95
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/flyai-tests.ts
95
+ //# sourceURL=ts/scripts/flyai-tests.ts
@@ -177,4 +177,4 @@ main().catch((e)=>{
177
177
  });
178
178
 
179
179
 
180
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/hbcli-e2e-tests.ts
180
+ //# sourceURL=ts/scripts/hbcli-e2e-tests.ts
@@ -144,4 +144,4 @@ console.log(`6. ENOENT 人话化 OK:${r6.summary}`);
144
144
  console.log('HBCLI TESTS: 8/8 OK (happy / error / no-binary / fallback-filter / fallback-no-match / v0.3.0 旗标回归 / ENOENT 人话化 / 候选路径)');
145
145
 
146
146
 
147
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/hbcli-tests.ts
147
+ //# sourceURL=ts/scripts/hbcli-tests.ts
@@ -48,4 +48,4 @@ main().catch((e)=>{
48
48
  });
49
49
 
50
50
 
51
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/health-watch-cli.ts
51
+ //# sourceURL=ts/scripts/health-watch-cli.ts
@@ -31,4 +31,4 @@ assert.equal(t('md.wish_budget', {
31
31
  console.log('\nI18N TESTS: 4/4 OK(en 零缺键/默认 zh 金标准一致/en 切换数据不动/插值+回退)');
32
32
 
33
33
 
34
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/i18n-tests.ts
34
+ //# sourceURL=ts/scripts/i18n-tests.ts
@@ -93,4 +93,4 @@ console.log('INTEG 1 OK: 子进程 uncaughtException → fsync 写盘,handler
93
93
  console.log('INCIDENT TESTS: 3/3 OK');
94
94
 
95
95
 
96
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/incident-tests.ts
96
+ //# sourceURL=ts/scripts/incident-tests.ts
@@ -42,4 +42,4 @@ assert.ok(r3.suggestions.some((sg)=>sg.relax === 'total:budget'));
42
42
  console.log(`TS JOURNEY TESTS: 5/5 OK(五段全链可行,¥${r1.money_cny},与 Python oracle 结构一致)`);
43
43
 
44
44
 
45
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/journey-tests.ts
45
+ //# sourceURL=ts/scripts/journey-tests.ts
@@ -492,4 +492,4 @@ console.log(`\nLEDGER TESTS: ${pass} ok, ${fail} fail`);
492
492
  if (fail > 0) process.exit(1);
493
493
 
494
494
 
495
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/ledger-tests.ts
495
+ //# sourceURL=ts/scripts/ledger-tests.ts
@@ -39,4 +39,4 @@ await collectDeepPlanning(state, ticket, solve);
39
39
  process.exit(9);
40
40
 
41
41
 
42
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/ledger-workflow-crash.ts
42
+ //# sourceURL=ts/scripts/ledger-workflow-crash.ts
@@ -74,4 +74,4 @@ const cur = {
74
74
  }console.log('\nMEMORY-MERGE TESTS: 3/3 OK(T1 合并守门层)');
75
75
 
76
76
 
77
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/memory-capture-tests.ts
77
+ //# sourceURL=ts/scripts/memory-capture-tests.ts
@@ -80,4 +80,4 @@ pass('动机层零衰减(构造性):本模块无作用于 motivation-profile 的
80
80
  console.log(`\nMEMORY DECAY TESTS: ${n}/5 OK(memory-design P3 分级窗口/单调/地板/上界/动机零衰减)`);
81
81
 
82
82
 
83
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/memory-decay-tests.ts
83
+ //# sourceURL=ts/scripts/memory-decay-tests.ts
@@ -21,4 +21,4 @@ console.log(`经验回流率基线 = ${verifiedWishes}/${recalledWishes} = ${ref
21
21
  if (pool.length === 0) console.log('(wish pool 为空——首访用户,指标从首条憧憬入池开始积累)');
22
22
 
23
23
 
24
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/memory-metrics.ts
24
+ //# sourceURL=ts/scripts/memory-metrics.ts
@@ -376,4 +376,4 @@ const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).hr
376
376
  if (import.meta.url === invokedPath) main();
377
377
 
378
378
 
379
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/memory-value-report.ts
379
+ //# sourceURL=ts/scripts/memory-value-report.ts