@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24
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 +102 -194
- package/README.zh-CN.md +115 -210
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-bootstrap.js +466 -35
- package/bin/gotry-inner.js +255 -36
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +2 -2
- package/client/client.js +174 -0
- package/cordis.gotry-patch.yml +70 -93
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +63 -148
- package/dist/capabilities/anything.js +89 -75
- package/dist/capabilities/artifacts.js +123 -104
- package/dist/capabilities/channel-health.js +65 -45
- package/dist/capabilities/channel-registry.js +108 -87
- package/dist/capabilities/doctor.js +357 -235
- package/dist/capabilities/effect.js +223 -309
- package/dist/capabilities/fact-log.js +28 -14
- package/dist/capabilities/flyai.js +245 -156
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +144 -148
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/model-override.js +30 -10
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +50 -32
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
- package/dist/capabilities/session/adapters/dida-portal.js +206 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +169 -112
- package/dist/capabilities/session/benchmark.js +85 -149
- package/dist/capabilities/session/extension-bridge.js +320 -259
- package/dist/capabilities/session/extension-channel.js +57 -76
- package/dist/capabilities/session/extension-distribution.js +115 -119
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +55 -33
- package/dist/capabilities/session/health-watch.js +146 -154
- package/dist/capabilities/session/read-guard.js +55 -47
- package/dist/capabilities/session/static-flight-golden.js +49 -36
- package/dist/capabilities/session/transport.js +54 -61
- package/dist/capabilities/session/wizard.js +63 -93
- package/dist/capabilities/session-consent.js +143 -47
- package/dist/capabilities/session-login.js +95 -126
- package/dist/capabilities/session-search.js +391 -253
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +87 -73
- package/dist/capabilities/weather.js +163 -235
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
- package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
- package/dist/scripts/anything-tests.js +58 -62
- package/dist/scripts/artifact-client-contract-tests.js +69 -0
- package/dist/scripts/artifacts-capability-tests.js +235 -0
- package/dist/scripts/async-collect.js +26 -15
- package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
- package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
- package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
- package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
- package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
- package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +61 -123
- package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
- package/dist/scripts/bootstrap-tests.js +1383 -128
- package/dist/scripts/build-changelog.js +89 -130
- package/dist/scripts/build-metrics-report.js +135 -143
- package/dist/scripts/changelog-tests.js +35 -82
- package/dist/scripts/channel-probe-tests.js +44 -116
- package/dist/scripts/channel-probe.js +84 -167
- package/dist/scripts/channel-registry-tests.js +97 -185
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +428 -120
- package/dist/scripts/dsh-artifact-e2e.js +150 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
- package/dist/scripts/dsh-runtime-closure.js +75 -63
- package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +165 -552
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +69 -242
- package/dist/scripts/evaluation-contract-tests.js +193 -499
- package/dist/scripts/extension-distribution-cli.js +19 -15
- package/dist/scripts/extension-distribution-tests.js +98 -211
- package/dist/scripts/extension-tests.js +366 -389
- package/dist/scripts/fact-gate-tests.js +725 -263
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-tests.js +325 -68
- package/dist/scripts/g5-guard-tests.js +131 -0
- package/dist/scripts/g5-guard.js +189 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +71 -90
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +41 -115
- package/dist/scripts/health-watch-cli.js +42 -28
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +16 -9
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +547 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +325 -340
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +474 -222
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +62 -199
- package/dist/scripts/model-override-e2e.js +66 -105
- package/dist/scripts/nightly-evidence-tests.js +64 -89
- package/dist/scripts/nightly-evidence.js +108 -111
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/onboarding-tests.js +63 -88
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +37 -12
- package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +126 -227
- package/dist/scripts/price-drift-watch.js +209 -253
- package/dist/scripts/probe-poi-tests.js +52 -45
- package/dist/scripts/product-metrics.js +187 -273
- package/dist/scripts/publish-preverify.js +47 -41
- package/dist/scripts/realtime-pricing-tests.js +37 -76
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +16 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +16 -12
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +66 -147
- package/dist/scripts/session-dida-live-e2e.js +151 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-login.js +14 -13
- package/dist/scripts/session-tests.js +545 -583
- package/dist/scripts/sf-live-benchmark.js +92 -110
- package/dist/scripts/sf-live-cli-tests.js +2 -9
- package/dist/scripts/sf-soft-score-tests.js +19 -60
- package/dist/scripts/sf-summary-tests.js +149 -0
- package/dist/scripts/sf-summary.js +466 -84
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +11 -10
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +12 -20
- package/dist/scripts/smoke.js +541 -422
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +549 -212
- package/dist/scripts/static-golden-tests.js +46 -180
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +46 -93
- package/dist/scripts/turn-handoff-collect.js +71 -99
- package/dist/scripts/turn-policy-tests.js +28 -29
- package/dist/scripts/typed-contract-canary.js +77 -162
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +23 -19
- package/dist/scripts/visa-policy-tests.js +31 -44
- package/dist/scripts/weather-tests.js +121 -543
- package/dist/scripts/wish-channel-gate-tests.js +29 -86
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +18 -12
- package/dist/src/artifact-gate.js +652 -221
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +55 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +194 -0
- package/dist/src/benchmark-agent-conformance.js +296 -262
- package/dist/src/benchmark-environment-bridge.js +298 -413
- package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
- package/dist/src/benchmark-tool-isolation.js +94 -67
- package/dist/src/bookable-facts.js +299 -174
- package/dist/src/booking-saga.js +64 -120
- package/dist/src/booking-surface/availability-policy.js +443 -576
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/contracts.js +24 -80
- package/dist/src/booking-surface/dsh-planner.js +510 -281
- package/dist/src/booking-surface/dsh-plugin.js +17 -1
- package/dist/src/booking-surface/error-codes.js +82 -10
- package/dist/src/booking-surface/index.js +0 -3
- package/dist/src/booking-surface/profile.js +10 -54
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +765 -891
- package/dist/src/booking-surface/server.js +200 -251
- package/dist/src/booking-surface/startup.js +58 -54
- package/dist/src/booking-surface/validation.js +150 -218
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +82 -120
- package/dist/src/engine.js +253 -239
- package/dist/src/evaluation-cadence.js +72 -209
- package/dist/src/evaluation-contracts.js +220 -843
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/gotry-backend.js +70 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +18 -7
- package/dist/src/index.js +1425 -1629
- package/dist/src/journey.js +132 -81
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +257 -166
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/realtime-pricing.js +63 -72
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +310 -471
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +121 -127
- package/dist/src/turn-policy.js +61 -89
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +504 -381
- package/dist/src/wish-pool.js +39 -14
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +273 -7
- package/extension/README.md +4 -3
- package/extension/background.js +125 -14
- package/extension/content-bridge.js +23 -1
- package/extension/content-main.js +15 -4
- package/extension/manifest.json +16 -8
- package/package.json +251 -220
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +1 -1
- package/ts/capabilities/artifacts.ts +46 -9
- package/ts/capabilities/flyai.ts +156 -37
- package/ts/capabilities/hbcli.ts +1 -1
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
- package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
- package/ts/capabilities/session/extension-bridge.ts +226 -101
- package/ts/capabilities/session/extension-channel.ts +22 -9
- package/ts/capabilities/session/wizard.ts +2 -2
- package/ts/capabilities/session-consent.ts +98 -14
- package/ts/capabilities/session-login.ts +1 -0
- package/ts/capabilities/session-search.ts +315 -31
- package/ts/dsh-runtime/vendor/README.md +8 -4
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
- package/ts/package.json +236 -16
- package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
- package/ts/scripts/state-cli.ts +418 -51
- package/ts/src/artifact-gate.ts +561 -15
- package/ts/src/benchmark-environment-bridge.ts +37 -20
- package/ts/src/bookable-facts.ts +183 -7
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/dsh-planner.ts +240 -32
- package/ts/src/booking-surface/dsh-plugin.js +17 -1
- package/ts/src/booking-surface/error-codes.ts +75 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/server.ts +84 -51
- package/ts/src/booking-surface/validation.ts +2 -0
- package/ts/src/contracts.ts +12 -2
- package/ts/src/dsh-llm.ts +4 -8
- package/ts/src/index.ts +609 -90
- package/ts/src/loop.ts +116 -7
- package/ts/src/memory-capture.ts +227 -3
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/state-ledger.ts +47 -29
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +217 -13
- package/ts/src/unified.ts +316 -54
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session V3 确定性迁移契约(issue #268):在隔离临时目录下用真实发布的 V2 编解码器
|
|
3
|
+
* 生成 V2 header/events,序列化为 JSONL 字节写入磁盘,保存确切源字节快照;通过
|
|
4
|
+
* sessionFormatCatalog 从磁盘读取并解析这些字节,分类为 migration-required,迁移为
|
|
5
|
+
* V3,编码独立 V3 继任者写入磁盘;然后用 catalog validation:current 完全解码 V3
|
|
6
|
+
* 继任者;迁移后重新读取 V2 源文件做字节比较,证明迁移不修改源工件字节。
|
|
7
|
+
*
|
|
8
|
+
* 这是确定性格式兼容性证据(隔离文件字节,非内存 structuredClone),不是创始人状态
|
|
9
|
+
* 迁移,也不是 M5/M6 准入。
|
|
10
|
+
*
|
|
11
|
+
* 运行:cd ts && npx tsx scripts/dsh-session-v3-migration-proof.ts
|
|
12
|
+
*/
|
|
13
|
+
import assert from 'node:assert/strict';
|
|
14
|
+
import { writeFileSync, readFileSync, mkdtempSync, rmSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { tmpdir } from 'node:os';
|
|
17
|
+
import { sessionFormatCatalog } from '@deepseek-ai/dsh-session-format-catalog';
|
|
18
|
+
import { releasedV2SessionFormatCodec } from '@deepseek-ai/dsh-session-format-v1-to-v2';
|
|
19
|
+
// 最小有效 V2 工件:一个 turn 包含一个 step,turn 正常完成。
|
|
20
|
+
// V2 事件处置要求 turn/start 需 ["turn"],step/start|step/end 需 ["turn","step"],
|
|
21
|
+
// turn/end 需 ["turn","reason"],reason.kind="completed" 是最简有效种类。
|
|
22
|
+
const v2Header = {
|
|
23
|
+
version: 2,
|
|
24
|
+
id: 's-min-v2',
|
|
25
|
+
createdAt: 1700000000000,
|
|
26
|
+
isSeeded: false,
|
|
27
|
+
delegationDepth: 0,
|
|
28
|
+
};
|
|
29
|
+
const v2Events = [
|
|
30
|
+
{ type: 'turn/start', seq: 0, time: 1700000000000, data: { turn: 1 } },
|
|
31
|
+
{ type: 'step/start', seq: 1, time: 1700000000001, data: { turn: 1, step: 1 } },
|
|
32
|
+
{ type: 'step/end', seq: 2, time: 1700000000002, data: { turn: 1, step: 1 } },
|
|
33
|
+
{ type: 'turn/end', seq: 3, time: 1700000000003, data: { turn: 1, reason: { kind: 'completed' } } },
|
|
34
|
+
];
|
|
35
|
+
// 1. 用真实发布的 V2 编解码器编码 header + 事件行为 JSON 对象,序列化为 JSONL 字节。
|
|
36
|
+
const encodedHeader = releasedV2SessionFormatCodec.encodeHeader(v2Header, 0);
|
|
37
|
+
const encodedRows = v2Events.map((e) => releasedV2SessionFormatCodec.encodeEvent(e));
|
|
38
|
+
function toLine(obj) {
|
|
39
|
+
return JSON.stringify(obj);
|
|
40
|
+
}
|
|
41
|
+
const sourceLines = [toLine(encodedHeader), ...encodedRows.map(toLine)];
|
|
42
|
+
const sourceJsonl = sourceLines.join('\n') + '\n';
|
|
43
|
+
const tmp = mkdtempSync(join(tmpdir(), 'session-v3-migration-proof-'));
|
|
44
|
+
const sourcePath = join(tmp, 'source.v2.jsonl');
|
|
45
|
+
const successorPath = join(tmp, 'successor.v3.jsonl');
|
|
46
|
+
try {
|
|
47
|
+
// 2. 写 V2 源字节到磁盘,保存确切源字节快照。
|
|
48
|
+
writeFileSync(sourcePath, sourceJsonl, 'utf8');
|
|
49
|
+
const sourceBytes = readFileSync(sourcePath);
|
|
50
|
+
// 3. 从磁盘读取并解析 V2 源字节:首行 = header,其余 = 事件行。
|
|
51
|
+
const parsedLines = readFileSync(sourcePath, 'utf8').split('\n').filter((l) => l.length > 0);
|
|
52
|
+
assert.ok(parsedLines.length === sourceLines.length, `磁盘行数必须等于源行数(${parsedLines.length} vs ${sourceLines.length})`);
|
|
53
|
+
const headerValue = JSON.parse(parsedLines[0]);
|
|
54
|
+
const rowValues = parsedLines.slice(1).map((l) => JSON.parse(l));
|
|
55
|
+
// 4. catalog 当前版本 = 3;V2 header 分类为 migration-required。
|
|
56
|
+
assert.equal(sessionFormatCatalog.currentVersion, 3, 'catalog 当前版本必须为 3');
|
|
57
|
+
const headerRead = sessionFormatCatalog.readHeader(headerValue);
|
|
58
|
+
assert.equal(headerRead.status, 'migration-required', `V2 header 必须分类为 migration-required(当前 ${headerRead.status})`);
|
|
59
|
+
// 5. 创建恢复器,逐行解码,完成恢复为 V3 工件(validation:transformed,迁移后校验)。
|
|
60
|
+
const restore = sessionFormatCatalog.createRestore(headerValue, {
|
|
61
|
+
recovery: 'recoverable',
|
|
62
|
+
validation: 'transformed',
|
|
63
|
+
});
|
|
64
|
+
for (const row of rowValues) {
|
|
65
|
+
restore.decodeRow(row);
|
|
66
|
+
}
|
|
67
|
+
const artifact = restore.finish();
|
|
68
|
+
// 6. V3 工件版本 = 3,事件数 = V2 + 1(在 step/start 后插入 system head)。
|
|
69
|
+
assert.equal(artifact.header.version, 3, '恢复后工件版本必须为 3');
|
|
70
|
+
assert.equal(artifact.events.length, v2Events.length + 1, `V3 事件数必须 = V2 + 1(插入 system head),当前 ${artifact.events.length}`);
|
|
71
|
+
// 插入的 system head(原 step/start 后,索引 2)必须是 system/message。
|
|
72
|
+
const inserted = artifact.events[2];
|
|
73
|
+
assert.equal(inserted.type, 'system/message', `插入的事件必须是 system/message(当前 ${inserted.type})`);
|
|
74
|
+
// 7. 编码 V3 继任者为 JSONL 字节,写入独立磁盘文件。
|
|
75
|
+
const v3HeaderRecord = sessionFormatCatalog.encodeCurrentHeader(artifact.header, artifact.inheritedEventCount);
|
|
76
|
+
const v3RowRecords = artifact.events.map((e) => sessionFormatCatalog.encodeCurrentEvent(e));
|
|
77
|
+
const successorLines = [toLine(v3HeaderRecord), ...v3RowRecords.map(toLine)];
|
|
78
|
+
const successorJsonl = successorLines.join('\n') + '\n';
|
|
79
|
+
writeFileSync(successorPath, successorJsonl, 'utf8');
|
|
80
|
+
// 8. 从磁盘读取 V3 继任者,用 catalog validation:current 完全解码。
|
|
81
|
+
const successorParsed = readFileSync(successorPath, 'utf8').split('\n').filter((l) => l.length > 0);
|
|
82
|
+
assert.ok(successorParsed.length === successorLines.length, `V3 磁盘行数必须等于继任者行数`);
|
|
83
|
+
const successorHeaderValue = JSON.parse(successorParsed[0]);
|
|
84
|
+
const successorRowValues = successorParsed.slice(1).map((l) => JSON.parse(l));
|
|
85
|
+
const successorRead = sessionFormatCatalog.readHeader(successorHeaderValue);
|
|
86
|
+
assert.equal(successorRead.status, 'current', `V3 继任者必须重新打开为 current(当前 ${successorRead.status})`);
|
|
87
|
+
const successorRestore = sessionFormatCatalog.createRestore(successorHeaderValue, {
|
|
88
|
+
recovery: 'recoverable',
|
|
89
|
+
validation: 'current',
|
|
90
|
+
});
|
|
91
|
+
for (const row of successorRowValues) {
|
|
92
|
+
successorRestore.decodeRow(row);
|
|
93
|
+
}
|
|
94
|
+
const successorArtifact = successorRestore.finish();
|
|
95
|
+
assert.equal(successorArtifact.header.version, 3, 'V3 继任者完全解码后版本必须为 3');
|
|
96
|
+
assert.equal(successorArtifact.events.length, artifact.events.length, 'V3 继任者完全解码后事件数必须与迁移工件一致');
|
|
97
|
+
// 9. 迁移后重新读取 V2 源文件字节,与快照字节比较——证明迁移不修改源工件字节。
|
|
98
|
+
const sourceBytesAfter = readFileSync(sourcePath);
|
|
99
|
+
assert.ok(Buffer.compare(sourceBytes, sourceBytesAfter) === 0, '迁移后 V2 源文件字节必须与快照完全一致——迁移不修改源工件字节');
|
|
100
|
+
console.log(`SESSION V3 MIGRATION PROOF: V2 source ${sourceBytes.length}B (${v2Events.length} events) on disk → ` +
|
|
101
|
+
`V3(${artifact.events.length} events, +system/message), successor ${successorJsonl.length}B reopens as current ` +
|
|
102
|
+
`and fully decodes (${successorArtifact.events.length} events), source bytes unchanged after migration`);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
106
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-subprocess-local 确定性证明(issue #268):仅通过公共 API 挂载 provider,
|
|
3
|
+
* 执行真实的本地托管子进程与 PTY,观察真实输出,终止它,await 公共 waitForExit,
|
|
4
|
+
* 并证明派生后代已消失。平台中立(Ubuntu CI + macOS)——不调用私有
|
|
5
|
+
* selectContainmentMode()/disposeManagedProcesses(),不断言主机是 macOS fallback,
|
|
6
|
+
* 不用已退出的顶层进程作为后代清理替代。
|
|
7
|
+
*
|
|
8
|
+
* 公共挂载模式:await ctx.plugin(LocalSubprocessRuntime) → ctx.subprocess 可用。
|
|
9
|
+
* spawn 一个活跃的 node 父进程(被 provider 以 detached:true 启动,自成进程组领袖),
|
|
10
|
+
* 该父进程用 child_process.spawn 创建一个非分离的真实长运行子进程(继承父进程组),
|
|
11
|
+
* 并将子进程 PID 输出到 stdout。证明通过收集 stdout 读取子进程 PID,调用公共
|
|
12
|
+
* terminate() + await waitForExit(有界 abort 信号),断言父进程结果和子进程 PID
|
|
13
|
+
* 都已消失(进程组信号终止整个组)。
|
|
14
|
+
*
|
|
15
|
+
* 真实 PTY 验证在 CI 支持平台(Linux/macOS)都执行:spawnTerminal 启动 node 子进程,
|
|
16
|
+
* 子进程向 stdout 写入 'pty-line\n' 并正常退出;断言 PTY 输出包含 'pty-line' 且 exitCode=0。
|
|
17
|
+
* 目标 node-pty 包的 Darwin 预构建同时带 `pty.node` 与 `spawn-helper`,Linux x64/arm64
|
|
18
|
+
* 预构建只带 `pty.node`。本证明在两个平台都跑真实 PTY 行为;filesystem 上
|
|
19
|
+
* spawn-helper 存在性与 0755 模式断言仅在 Darwin 下生效。Linux 报告
|
|
20
|
+
* "helper not-applicable",同时仍验证真实 PTY 输出与成功退出,不 stat 不存在的文件。
|
|
21
|
+
*
|
|
22
|
+
* finally 中 await ctx.fiber.dispose() 并移除临时文件。不向子进程传递整个环境变量。
|
|
23
|
+
*
|
|
24
|
+
* 运行:cd ts && npx tsx scripts/dsh-subprocess-local-proof.ts
|
|
25
|
+
*/
|
|
26
|
+
import assert from 'node:assert/strict';
|
|
27
|
+
import { writeFileSync, mkdtempSync, rmSync, existsSync, statSync } from 'node:fs';
|
|
28
|
+
import { join, dirname } from 'node:path';
|
|
29
|
+
import { tmpdir } from 'node:os';
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
32
|
+
import { LocalSubprocessRuntime } from '@deepseek-ai/dsh-subprocess-local';
|
|
33
|
+
const ctx = new Context();
|
|
34
|
+
const fiber = ctx.plugin(LocalSubprocessRuntime);
|
|
35
|
+
await fiber;
|
|
36
|
+
const tmp = mkdtempSync(join(tmpdir(), 'subprocess-local-proof-'));
|
|
37
|
+
let childPid;
|
|
38
|
+
let childGoneAfter = false;
|
|
39
|
+
let parentOutcome;
|
|
40
|
+
let rangeEmpty;
|
|
41
|
+
let ptyExitCode = null;
|
|
42
|
+
let ptyOutput = '';
|
|
43
|
+
let helperFact;
|
|
44
|
+
let helperMode;
|
|
45
|
+
try {
|
|
46
|
+
// 1. 父进程脚本:spawn 一个非分离的长运行子进程(继承父进程组),输出子进程 PID。
|
|
47
|
+
// 父进程自身保持活跃(setInterval),直到被 provider 终止。
|
|
48
|
+
const parentScript = join(tmp, 'parent.mjs');
|
|
49
|
+
writeFileSync(parentScript, [
|
|
50
|
+
"import { spawn } from 'node:child_process'",
|
|
51
|
+
"const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 60000)'], { stdio: 'inherit' })",
|
|
52
|
+
"process.stdout.write('CHILD_PID=' + child.pid + '\\n')",
|
|
53
|
+
'setInterval(() => {}, 60000)',
|
|
54
|
+
'',
|
|
55
|
+
].join('\n'));
|
|
56
|
+
// 2. 通过公共 ctx.subprocess.spawn 启动父进程。不传 env——让 provider 用
|
|
57
|
+
// scrubbedParentEnv() 清洗环境(保留 PATH/HOME,剥离凭据/DSH_*),不传递整个环境变量。
|
|
58
|
+
const handle = ctx.subprocess.spawn({
|
|
59
|
+
argv: [process.execPath, parentScript],
|
|
60
|
+
cwd: tmp,
|
|
61
|
+
stdio: { stdin: 'ignore', stdout: { maxBytes: 1 << 20 }, stderr: { maxBytes: 1 << 20 } },
|
|
62
|
+
graceMs: 8000,
|
|
63
|
+
});
|
|
64
|
+
const stdoutReader = handle.collected.stdout;
|
|
65
|
+
assert.ok(stdoutReader, 'collect 模式下 collected.stdout reader 必须存在');
|
|
66
|
+
// 3. 通过观察父进程输出获取子进程 PID(有界轮询,不假设即时可用)。
|
|
67
|
+
let observedChildPid;
|
|
68
|
+
const pollDeadline = Date.now() + 5000;
|
|
69
|
+
while (Date.now() < pollDeadline && observedChildPid === undefined) {
|
|
70
|
+
const read = stdoutReader.readFrom(0);
|
|
71
|
+
const m = /CHILD_PID=(\d+)/.exec(read.text);
|
|
72
|
+
if (m)
|
|
73
|
+
observedChildPid = Number(m[1]);
|
|
74
|
+
else
|
|
75
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
76
|
+
}
|
|
77
|
+
assert.ok(observedChildPid !== undefined && observedChildPid > 0, `必须从父进程 stdout 观察到有效 CHILD_PID(当前 ${observedChildPid})`);
|
|
78
|
+
childPid = observedChildPid;
|
|
79
|
+
// 4. 终止前:子进程必须存活(父进程已 spawn 它)。
|
|
80
|
+
assert.ok(isPidAlive(childPid), `终止前子进程 PID ${childPid} 必须存活`);
|
|
81
|
+
// 5. 调用公共 terminate() + await 公共 waitForExit(有界 abort 信号)。
|
|
82
|
+
// 进程组信号(process.kill(-parentPid, SIGTERM))终止整个组,包括非分离子进程。
|
|
83
|
+
handle.terminate();
|
|
84
|
+
rangeEmpty = await handle.waitForExit(AbortSignal.timeout(8000));
|
|
85
|
+
assert.equal(rangeEmpty, true, 'waitForExit 必须返回 true——托管范围(含后代)已空');
|
|
86
|
+
// 6. 断言直接进程结果:父进程被信号终止(非正常退出)。
|
|
87
|
+
parentOutcome = await handle.done;
|
|
88
|
+
assert.ok(parentOutcome.exitCode === null && parentOutcome.signal !== null, `父进程必须被信号终止(exitCode=${parentOutcome.exitCode}, signal=${parentOutcome.signal})`);
|
|
89
|
+
// 7. 断言子进程 PID 不再存在——后代清理的真实证据(非已退出顶层进程替代)。
|
|
90
|
+
childGoneAfter = !isPidAlive(childPid);
|
|
91
|
+
assert.ok(childGoneAfter, `provider 终止后子进程 PID ${childPid} 必须已消失——进程组包含的真实后代清理证据`);
|
|
92
|
+
// 8. 真实 PTY 验证(每个支持平台都执行):spawnTerminal 用 native PTY 后端 fork/exec,
|
|
93
|
+
// 子进程写 'pty-line\n' 并正常退出。原生 PTY 输出经 PTY 终端层到达 PTY 主面,
|
|
94
|
+
// 再被 node-pty 回调成 output 'data' 事件——这是跨平台的 PTY 行为证明。
|
|
95
|
+
const ptyScript = join(tmp, 'pty.mjs');
|
|
96
|
+
writeFileSync(ptyScript, "process.stdout.write('pty-line\\n'); process.exit(0);\n");
|
|
97
|
+
const term = await ctx.subprocess.spawnTerminal({
|
|
98
|
+
argv: [process.execPath, ptyScript],
|
|
99
|
+
cwd: tmp,
|
|
100
|
+
rows: 24,
|
|
101
|
+
cols: 80,
|
|
102
|
+
graceMs: 8000,
|
|
103
|
+
});
|
|
104
|
+
assert.equal(typeof term.pid, 'number', `PTY pid 必须是数字(当前 ${typeof term.pid})`);
|
|
105
|
+
assert.ok(term.pid > 0, `PTY pid 必须为正(当前 ${term.pid})`);
|
|
106
|
+
term.output.on('data', (d) => {
|
|
107
|
+
ptyOutput += d.toString();
|
|
108
|
+
});
|
|
109
|
+
const termOutcome = await term.done;
|
|
110
|
+
ptyExitCode = termOutcome.exitCode;
|
|
111
|
+
assert.equal(ptyExitCode, 0, `PTY 必须正常退出(exitCode=${ptyExitCode})`);
|
|
112
|
+
assert.equal(termOutcome.signal, null, `PTY 正常退出不得携带 signal(当前 ${termOutcome.signal})`);
|
|
113
|
+
assert.ok(ptyOutput.includes('pty-line'), `PTY 输出必须包含 pty-line(当前 ${JSON.stringify(ptyOutput)})`);
|
|
114
|
+
await term.terminate();
|
|
115
|
+
// 9. node-pty spawn-helper 文件系统断言仅在 Darwin 下生效。
|
|
116
|
+
// 已检查的目标包事实:darwin-{x64,arm64}/prebuilds 同时带 pty.node 与
|
|
117
|
+
// spawn-helper,linux-{x64,arm64}/prebuilds 仅带 pty.node。本证明不构造、
|
|
118
|
+
// 不复制、不 stat 实际不存在的 helper。
|
|
119
|
+
helperMode = null;
|
|
120
|
+
if (process.platform === 'darwin') {
|
|
121
|
+
const ptyEntry = fileURLToPath(import.meta.resolve('node-pty'));
|
|
122
|
+
const ptyRoot = dirname(dirname(ptyEntry));
|
|
123
|
+
const helperPath = join(ptyRoot, 'prebuilds', `${process.platform}-${process.arch}`, 'spawn-helper');
|
|
124
|
+
assert.ok(existsSync(helperPath), `Darwin prebuilds 必须携带 spawn-helper(实际不存在:${helperPath})——ensure-spawn-helper.mjs 的终局面契约`);
|
|
125
|
+
helperMode = statSync(helperPath).mode & 0o777;
|
|
126
|
+
assert.equal(helperMode, 0o755, `Darwin spawn-helper 权限模式必须是 0755(当前 0o${helperMode.toString(8)})——ensure-spawn-helper.mjs 的终局面契约`);
|
|
127
|
+
helperFact = `helper=darwin 0o${helperMode.toString(8)}`;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
helperFact = `helper=not-applicable:${process.platform}-${process.arch}; real PTY output='pty-line' present, exitCode=0`;
|
|
131
|
+
assert.ok(ptyExitCode === 0 && ptyOutput.includes('pty-line'), '非 Darwin 平台必须仍证明真实 PTY 行为:PTY 输出包含 pty-line 且 exitCode=0');
|
|
132
|
+
}
|
|
133
|
+
console.log(`SUBPROCESS LOCAL PROOF: parent killed by signal ${parentOutcome.signal}, child PID ${childPid} gone after terminate=${childGoneAfter}, ` +
|
|
134
|
+
`waitForExit rangeEmpty=${rangeEmpty}, PTY pid=${term.pid} exitCode=${ptyExitCode} output-has-pty-line=${ptyOutput.includes('pty-line')}, ` +
|
|
135
|
+
`${helperFact}`);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
await ctx.fiber.dispose();
|
|
139
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
140
|
+
}
|
|
141
|
+
function isPidAlive(pid) {
|
|
142
|
+
try {
|
|
143
|
+
process.kill(pid, 0);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH 目标闭包契约(issue #268):读取仓库当前状态,断言已精确迁移到
|
|
3
|
+
* @deepseek-ai/dsh 0.1.5-rc.1 的 232 包闭包。这是 issue #268 的 failing-before
|
|
4
|
+
* 契约——在起始 0.1.2-alpha.3/216 闭包上必须失败,迁移完成后必须通过。
|
|
5
|
+
*
|
|
6
|
+
* 断言面(任一不符即 FAIL):
|
|
7
|
+
* - 运行时 spawn guard 版本 = 目标;
|
|
8
|
+
* - 闭包常量 = 目标包数;
|
|
9
|
+
* - 根 manifest 全部 dsh* 依赖精确锁定目标版本,包数 = 目标;
|
|
10
|
+
* - 目标闭包 sentinel 在根 manifest 精确存在(含 alpha.1→rc.1 新增三项与 documentpreview 更名),移除项不存在;
|
|
11
|
+
* - 根 npm lock 闭包与 manifest 集合/版本/包数一致;
|
|
12
|
+
* - 根 pnpm lock 闭包 + root importer 与 manifest 集合/版本/包数一致;
|
|
13
|
+
* - ts/package.json 显式声明 @deepseek-ai/dsh-sdk-client@目标(GoTry 直接 import,
|
|
14
|
+
* 不再由上游 main 图传递到达),且其所有显式 dsh* 依赖精确锁定目标版本;
|
|
15
|
+
* - ts/package.json overrides 的名称集合与根 232 包集合完全一致,值全部精确目标版本;
|
|
16
|
+
* - ts/package-lock.json 每个真实 DSH 条目(按最后包名段过滤,排除嵌套非 dsh 依赖)
|
|
17
|
+
* 精确锁定目标版本,包数 = 目标,名称集合与根 232 完全一致,拒绝嵌套/混合版本;
|
|
18
|
+
* - ts/ 实际安装树:用 ts 包的 createRequire 上下文解析每个 dsh 包 package.json,
|
|
19
|
+
* 全精确目标版本,解析路径直接位于 ts/node_modules/@deepseek-ai/ 下(非嵌套)。
|
|
20
|
+
*
|
|
21
|
+
* 仅根目录 232 证据不足——ts/ 是 GoTry 直接 import 的消费方,必须独立验证其三层闭包。
|
|
22
|
+
*
|
|
23
|
+
* 运行:cd ts && npx tsx scripts/dsh-target-closure-proof.ts
|
|
24
|
+
*/
|
|
25
|
+
import assert from 'node:assert/strict';
|
|
26
|
+
import { readFileSync } from 'node:fs';
|
|
27
|
+
import { join } from 'node:path';
|
|
28
|
+
import { createRequire } from 'node:module';
|
|
29
|
+
import { REQUIRED_BENCHMARK_DSH_VERSION } from '../../bin/gotry-runtime-resolution.js';
|
|
30
|
+
import { parsePnpmDshLock, parsePnpmRootDshImporter, REQUIRED_DSH_RUNTIME_PACKAGE_COUNT, validateDshRuntimeClosure, validatePnpmRootDshImporter, } from './dsh-runtime-closure.js';
|
|
31
|
+
const TARGET_VERSION = '0.1.5-rc.1';
|
|
32
|
+
const TARGET_PACKAGE_COUNT = 232;
|
|
33
|
+
const ADDED_SENTINELS = [
|
|
34
|
+
'@deepseek-ai/dsh-api-workspace-files',
|
|
35
|
+
'@deepseek-ai/dsh-chunked-list',
|
|
36
|
+
'@deepseek-ai/dsh-client-file-upload',
|
|
37
|
+
'@deepseek-ai/dsh-client-resources',
|
|
38
|
+
'@deepseek-ai/dsh-client-ui-open-in-app',
|
|
39
|
+
'@deepseek-ai/dsh-client-ui-sidebar-documentpreview',
|
|
40
|
+
'@deepseek-ai/dsh-client-ui-sidebar-files',
|
|
41
|
+
'@deepseek-ai/dsh-client-ui-sidebar-right',
|
|
42
|
+
'@deepseek-ai/dsh-host-open-in-app',
|
|
43
|
+
'@deepseek-ai/dsh-http-proxy',
|
|
44
|
+
'@deepseek-ai/dsh-package-manifest',
|
|
45
|
+
'@deepseek-ai/dsh-session-format',
|
|
46
|
+
'@deepseek-ai/dsh-session-format-catalog',
|
|
47
|
+
'@deepseek-ai/dsh-session-format-v0-to-v1',
|
|
48
|
+
'@deepseek-ai/dsh-session-format-v1-to-v2',
|
|
49
|
+
'@deepseek-ai/dsh-session-format-v2-to-v3',
|
|
50
|
+
'@deepseek-ai/dsh-tool-present',
|
|
51
|
+
];
|
|
52
|
+
const REMOVED_SENTINELS = [
|
|
53
|
+
'@deepseek-ai/dsh-client-ui-sidebar-textpreview',
|
|
54
|
+
'@deepseek-ai/dsh-tool-subagent-report',
|
|
55
|
+
];
|
|
56
|
+
const repoRoot = join(import.meta.dirname, '..', '..');
|
|
57
|
+
function readJson(filePath) {
|
|
58
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
59
|
+
}
|
|
60
|
+
function dshDependencyNames(deps) {
|
|
61
|
+
return Object.keys(deps)
|
|
62
|
+
.filter((name) => name === '@deepseek-ai/dsh' || name.startsWith('@deepseek-ai/dsh-'))
|
|
63
|
+
.sort();
|
|
64
|
+
}
|
|
65
|
+
// 1. 运行时 spawn guard 与闭包常量必须已对齐目标。
|
|
66
|
+
assert.equal(REQUIRED_BENCHMARK_DSH_VERSION, TARGET_VERSION, `runtime spawn guard 必须锁定 ${TARGET_VERSION}(当前 ${REQUIRED_BENCHMARK_DSH_VERSION})`);
|
|
67
|
+
assert.equal(REQUIRED_DSH_RUNTIME_PACKAGE_COUNT, TARGET_PACKAGE_COUNT, `闭包常量必须为 ${TARGET_PACKAGE_COUNT}(当前 ${REQUIRED_DSH_RUNTIME_PACKAGE_COUNT})`);
|
|
68
|
+
// 2. 根 manifest:全部 dsh* 依赖精确锁定目标版本,包数 = 目标。
|
|
69
|
+
const rootPkg = readJson(join(repoRoot, 'package.json'));
|
|
70
|
+
const rootDeps = rootPkg.dependencies ?? {};
|
|
71
|
+
assert.equal(rootDeps['@deepseek-ai/dsh'], TARGET_VERSION, '根 manifest @deepseek-ai/dsh 未锁定目标版本');
|
|
72
|
+
const rootDshNames = dshDependencyNames(rootDeps);
|
|
73
|
+
for (const name of rootDshNames) {
|
|
74
|
+
assert.equal(rootDeps[name], TARGET_VERSION, `根 manifest 版本漂移:${name}@${rootDeps[name]}(expected ${TARGET_VERSION})`);
|
|
75
|
+
}
|
|
76
|
+
for (const name of ADDED_SENTINELS) {
|
|
77
|
+
assert.equal(rootDeps[name], TARGET_VERSION, `根 manifest 缺失目标新增 sentinel:${name}`);
|
|
78
|
+
}
|
|
79
|
+
for (const name of REMOVED_SENTINELS) {
|
|
80
|
+
assert.ok(!(name in rootDeps), `根 manifest 必须移除:${name}`);
|
|
81
|
+
}
|
|
82
|
+
assert.equal(rootDshNames.length, TARGET_PACKAGE_COUNT, `根 manifest dsh 包数不符:${rootDshNames.length}(expected ${TARGET_PACKAGE_COUNT})`);
|
|
83
|
+
// 3. 根 npm lock 闭包与 manifest 集合/版本/包数一致。
|
|
84
|
+
const npmLock = JSON.parse(readFileSync(join(repoRoot, 'package-lock.json'), 'utf8'));
|
|
85
|
+
const npmClosure = validateDshRuntimeClosure({
|
|
86
|
+
dependencies: rootDeps,
|
|
87
|
+
lockPackages: npmLock.packages ?? {},
|
|
88
|
+
runtimeVersion: TARGET_VERSION,
|
|
89
|
+
expectedPackageCount: TARGET_PACKAGE_COUNT,
|
|
90
|
+
});
|
|
91
|
+
// 4. 根 pnpm lock 闭包 + root importer 与 manifest 集合/版本/包数一致。
|
|
92
|
+
const pnpmLock = readFileSync(join(repoRoot, 'pnpm-lock.yaml'), 'utf8');
|
|
93
|
+
const pnpmClosure = validateDshRuntimeClosure({
|
|
94
|
+
dependencies: rootDeps,
|
|
95
|
+
lockPackages: parsePnpmDshLock(pnpmLock),
|
|
96
|
+
runtimeVersion: TARGET_VERSION,
|
|
97
|
+
expectedPackageCount: TARGET_PACKAGE_COUNT,
|
|
98
|
+
});
|
|
99
|
+
const pnpmImporter = validatePnpmRootDshImporter({
|
|
100
|
+
dependencies: rootDeps,
|
|
101
|
+
importerEntries: parsePnpmRootDshImporter(pnpmLock),
|
|
102
|
+
runtimeVersion: TARGET_VERSION,
|
|
103
|
+
expectedPackageCount: TARGET_PACKAGE_COUNT,
|
|
104
|
+
});
|
|
105
|
+
// 5. ts/package.json 必须显式声明 dsh-sdk-client@目标(GoTry 直接 import)。
|
|
106
|
+
const tsDir = join(repoRoot, 'ts');
|
|
107
|
+
const tsPkg = readJson(join(tsDir, 'package.json'));
|
|
108
|
+
assert.equal(tsPkg.dependencies?.['@deepseek-ai/dsh-sdk-client'], TARGET_VERSION, 'ts/package.json 必须显式声明 @deepseek-ai/dsh-sdk-client@' + TARGET_VERSION);
|
|
109
|
+
// 6. ts/package.json:所有显式 dsh* 依赖必须精确锁定目标版本(不要求 232,ts/ 闭包是
|
|
110
|
+
// 传递性的;但显式声明的每个 dsh 依赖都必须精确,不允许 ^/~ 可变范围)。
|
|
111
|
+
const tsDeps = tsPkg.dependencies ?? {};
|
|
112
|
+
const tsExplicitDsh = dshDependencyNames(tsDeps);
|
|
113
|
+
for (const name of tsExplicitDsh) {
|
|
114
|
+
assert.equal(tsDeps[name], TARGET_VERSION, `ts/package.json 显式 dsh 依赖版本漂移:${name}@${tsDeps[name]}(expected ${TARGET_VERSION})`);
|
|
115
|
+
}
|
|
116
|
+
// 7. ts/package.json overrides 必须精确覆盖根 manifest 的完整 232 包集合。
|
|
117
|
+
// 这组 override 是修复 sdk-client peer range 漂移到 alpha.2 的关键约束;只验证
|
|
118
|
+
// 三个显式依赖或最终 lock 会遗漏 manifest 层的防漂移策略。
|
|
119
|
+
const tsOverrides = tsPkg.overrides ?? {};
|
|
120
|
+
const tsOverrideDshNames = dshDependencyNames(tsOverrides);
|
|
121
|
+
assert.deepEqual(tsOverrideDshNames, rootDshNames, 'ts/package.json overrides 的 DSH 名称集合必须与根 232 包集合完全一致');
|
|
122
|
+
for (const name of tsOverrideDshNames) {
|
|
123
|
+
assert.equal(tsOverrides[name], TARGET_VERSION, `ts/package.json override 版本漂移:${name}@${tsOverrides[name]}(expected ${TARGET_VERSION})`);
|
|
124
|
+
}
|
|
125
|
+
// 8. ts/package-lock.json:每个真实 DSH 条目(按包名过滤,排除嵌套非 dsh 依赖)必须
|
|
126
|
+
// 精确锁定目标版本,包数 = 目标,名称集合与根 232 完全一致。
|
|
127
|
+
// 关键:lock 路径前缀 `node_modules/@deepseek-ai/dsh-skill-filesystem/node_modules/chokidar`
|
|
128
|
+
// 的最后包名段是 `chokidar`(非 dsh),不能按路径前缀过滤,必须按最后包名段过滤。
|
|
129
|
+
const tsNpmLock = JSON.parse(readFileSync(join(tsDir, 'package-lock.json'), 'utf8'));
|
|
130
|
+
const tsLockPackages = tsNpmLock.packages ?? {};
|
|
131
|
+
function lastPackageName(lockKey) {
|
|
132
|
+
const idx = lockKey.lastIndexOf('node_modules/');
|
|
133
|
+
return idx >= 0 ? lockKey.slice(idx + 'node_modules/'.length) : lockKey;
|
|
134
|
+
}
|
|
135
|
+
function isDshPackageName(name) {
|
|
136
|
+
return name === '@deepseek-ai/dsh' || name.startsWith('@deepseek-ai/dsh-');
|
|
137
|
+
}
|
|
138
|
+
const tsLockDshNames = new Set();
|
|
139
|
+
for (const [key, entry] of Object.entries(tsLockPackages)) {
|
|
140
|
+
if (key === '')
|
|
141
|
+
continue; // 根包自身
|
|
142
|
+
const pkgName = lastPackageName(key);
|
|
143
|
+
if (!isDshPackageName(pkgName))
|
|
144
|
+
continue;
|
|
145
|
+
// 拒绝嵌套 dsh 条目:路径包含多次 node_modules/ 且最后段是 dsh 包 → 嵌套副本。
|
|
146
|
+
const nodeModulesCount = key.split('node_modules/').length - 1;
|
|
147
|
+
assert.equal(nodeModulesCount, 1, `ts lock 拒绝嵌套/混合版本 dsh 条目:${key}(node_modules 深度 ${nodeModulesCount})`);
|
|
148
|
+
assert.equal(entry.version, TARGET_VERSION, `ts lock dsh 条目版本漂移:${pkgName}@${entry.version}(expected ${TARGET_VERSION})`);
|
|
149
|
+
assert.ok(!tsLockDshNames.has(pkgName), `ts lock 出现重复 dsh 条目:${pkgName}`);
|
|
150
|
+
tsLockDshNames.add(pkgName);
|
|
151
|
+
}
|
|
152
|
+
assert.equal(tsLockDshNames.size, TARGET_PACKAGE_COUNT, `ts lock dsh 包数不符:${tsLockDshNames.size}(expected ${TARGET_PACKAGE_COUNT})`);
|
|
153
|
+
const rootDshNameSet = new Set(rootDshNames);
|
|
154
|
+
for (const name of tsLockDshNames) {
|
|
155
|
+
assert.ok(rootDshNameSet.has(name), `ts lock dsh 包不在根 232 集合中:${name}`);
|
|
156
|
+
}
|
|
157
|
+
for (const name of rootDshNameSet) {
|
|
158
|
+
assert.ok(tsLockDshNames.has(name), `根 232 dsh 包不在 ts lock 中:${name}`);
|
|
159
|
+
}
|
|
160
|
+
// 9. ts/ 实际安装树:用 ts 包的 createRequire 上下文解析每个 dsh 包的 package.json,
|
|
161
|
+
// 读取版本,断言全精确目标版本;解析路径必须直接位于 ts/node_modules/@deepseek-ai/
|
|
162
|
+
// 下(非嵌套),拒绝混合版本。
|
|
163
|
+
const tsRequire = createRequire(join(tsDir, 'package.json'));
|
|
164
|
+
const tsNodeModulesDshDir = join(tsDir, 'node_modules', '@deepseek-ai');
|
|
165
|
+
const tsInstalledNames = new Set();
|
|
166
|
+
for (const name of rootDshNames) {
|
|
167
|
+
const pkgJsonPath = tsRequire.resolve(join(name, 'package.json'));
|
|
168
|
+
const entry = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
|
|
169
|
+
assert.equal(entry.version, TARGET_VERSION, `ts 安装树版本漂移:${name}@${entry.version}(expected ${TARGET_VERSION})`);
|
|
170
|
+
// 解析路径必须直接位于 ts/node_modules/@deepseek-ai/<pkg>/package.json——非嵌套。
|
|
171
|
+
assert.ok(pkgJsonPath.startsWith(tsNodeModulesDshDir + '/'), `ts 安装树解析路径不在直接 @deepseek-ai 目录下(嵌套/混合版本):${name} → ${pkgJsonPath}`);
|
|
172
|
+
const relative = pkgJsonPath.slice(tsNodeModulesDshDir.length + 1);
|
|
173
|
+
assert.ok(!relative.includes('/node_modules/'), `ts 安装树出现嵌套 dsh 副本:${name} → ${relative}`);
|
|
174
|
+
tsInstalledNames.add(name);
|
|
175
|
+
}
|
|
176
|
+
assert.equal(tsInstalledNames.size, TARGET_PACKAGE_COUNT, `ts 安装树 dsh 包数不符:${tsInstalledNames.size}(expected ${TARGET_PACKAGE_COUNT})`);
|
|
177
|
+
console.log(`DSH TARGET CLOSURE PROOF: root ${npmClosure.names.length} npm / ${pnpmClosure.names.length} pnpm / ${pnpmImporter.names.length} importer, ` +
|
|
178
|
+
`ts ${tsExplicitDsh.length} explicit / ${tsOverrideDshNames.length} overrides / ${tsLockDshNames.size} lock / ${tsInstalledNames.size} installed at ${TARGET_VERSION}`);
|