@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
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anything 能力层测试(模拟 hbcli):
|
|
3
|
+
* 1. 真实候选:fake hbcli 按 hotel-be SearchItem wire 形状返 JSON — verdict hit + hits
|
|
4
|
+
* (i18n name zh 优先 / region.centerLat / hotel.latlngCoordinator / star / id 逐字段对齐)
|
|
5
|
+
* 2. 旗标回归:--json 在子命令前(cli.ts 全局旗标预扫描只认子命令前位置)+
|
|
6
|
+
* --content-type / parentDestinationId → --destination-id 映射
|
|
7
|
+
* 3. 空候选:fake hbcli 返 empty JSON — verdict miss
|
|
8
|
+
* 4. exit≠0:fake hbcli 返 stderr+退出 — verdict error + 不抛错
|
|
9
|
+
* 5. 旧 CLI「unknown command」:error 带升级指引(issue #195:裸 unknown command 读起来像工具坏了)
|
|
10
|
+
* 6. 超时:fake hbcli hang — AbortError → verdict error + 不抛错
|
|
11
|
+
* 7. 关键词为空:不调 hbcli — verdict error
|
|
12
|
+
*
|
|
13
|
+
* 运行: cd ts && npx tsx scripts/anything-tests.ts
|
|
14
|
+
*/
|
|
1
15
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { mkdtemp, rm, writeFile
|
|
16
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
17
|
import { tmpdir } from 'node:os';
|
|
4
18
|
import { join } from 'node:path';
|
|
5
19
|
import { anythingSearch } from '../capabilities/anything.js';
|
|
@@ -8,6 +22,8 @@ try {
|
|
|
8
22
|
async function fakeBin(name, behaviour) {
|
|
9
23
|
const p = join(tmp, name);
|
|
10
24
|
if (behaviour === 'ok') {
|
|
25
|
+
// hotel-be SearchItem 真实 wire 形状(2026-09-07 对齐源码):i18n name 对象、
|
|
26
|
+
// region.coordinates.centerLat/centerLng、hotel.latlngCoordinator.google、star、matchScore
|
|
11
27
|
await writeFile(p, `#!/bin/sh
|
|
12
28
|
cat <<'JSON'
|
|
13
29
|
{"candidates":[
|
|
@@ -16,52 +32,49 @@ cat <<'JSON'
|
|
|
16
32
|
{"type":"place","matchScore":90,"region":{"id":"r1","name":{"zh":"洱海"}},"place":{"latlngCoordinator":{"google":{"lat":25.74,"lng":100.25}}}}
|
|
17
33
|
]}
|
|
18
34
|
JSON
|
|
19
|
-
`, {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
35
|
+
`, { mode: 0o755 });
|
|
36
|
+
}
|
|
37
|
+
else if (behaviour === 'echo-args') {
|
|
38
|
+
// 旗标回归探针:把 "$@" 回显进候选 region.id(anythingSearch 原样透传为 destinationId)
|
|
23
39
|
await writeFile(p, `#!/bin/sh
|
|
24
40
|
printf '{"candidates":[{"type":"city","region":{"id":"%s"}}]}' "$*"
|
|
25
|
-
`, {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} else if (behaviour === 'empty') {
|
|
41
|
+
`, { mode: 0o755 });
|
|
42
|
+
}
|
|
43
|
+
else if (behaviour === 'empty') {
|
|
29
44
|
await writeFile(p, `#!/bin/sh
|
|
30
45
|
cat <<'JSON'
|
|
31
46
|
{"candidates":[]}
|
|
32
47
|
JSON
|
|
33
|
-
`, {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} else if (behaviour === 'fail') {
|
|
48
|
+
`, { mode: 0o755 });
|
|
49
|
+
}
|
|
50
|
+
else if (behaviour === 'fail') {
|
|
37
51
|
await writeFile(p, `#!/bin/sh
|
|
38
52
|
echo 'hotelbe down' >&2
|
|
39
53
|
exit 1
|
|
40
|
-
`, {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
`, { mode: 0o755 });
|
|
55
|
+
}
|
|
56
|
+
else if (behaviour === 'unknown-command') {
|
|
57
|
+
// 旧版 CLI(< 2026-09 的 staicli)没有 search anything 子命令时的 commander 原话
|
|
44
58
|
await writeFile(p, `#!/bin/sh
|
|
45
59
|
echo "error: unknown command 'anything'" >&2
|
|
46
60
|
exit 1
|
|
47
|
-
`, {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
`, { mode: 0o755 });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// hang forever — test will timeout. Spawn on POSIX reacts to AbortSignal
|
|
65
|
+
// via SIGTERM; SIGKILL is the fallback at +500ms. Some shells (e.g. bash
|
|
66
|
+
// builtins shelled-out via Bun) cache the child pid briefly, so we give
|
|
67
|
+
// a generous upper bound in the assertion (note in code for future).
|
|
51
68
|
await writeFile(p, `#!/bin/sh
|
|
52
69
|
echo 'starting' >&2
|
|
53
70
|
while :; do sleep 5; done
|
|
54
|
-
`, {
|
|
55
|
-
mode: 0o755
|
|
56
|
-
});
|
|
71
|
+
`, { mode: 0o755 });
|
|
57
72
|
}
|
|
58
73
|
return p;
|
|
59
74
|
}
|
|
75
|
+
// 1. 真实候选(wire 形状逐字段)
|
|
60
76
|
const ok = await fakeBin('hbcli-ok', 'ok');
|
|
61
|
-
const r1 = await anythingSearch({
|
|
62
|
-
keyword: '大理',
|
|
63
|
-
hbcliBin: ok
|
|
64
|
-
});
|
|
77
|
+
const r1 = await anythingSearch({ keyword: '大理', hbcliBin: ok });
|
|
65
78
|
assert.equal(r1.verdict, 'hit');
|
|
66
79
|
assert.equal(r1.hits.length, 3);
|
|
67
80
|
assert.ok(r1.evidence.includes('hbcli-anything@'));
|
|
@@ -83,62 +96,45 @@ while :; do sleep 5; done
|
|
|
83
96
|
assert.equal(placeHit.name, '洱海');
|
|
84
97
|
assert.equal(placeHit.latitude, 25.74, 'place 坐标回退 place.latlngCoordinator');
|
|
85
98
|
console.log('1. 真实候选 wire 形状(hotel/city/place 逐字段)OK');
|
|
99
|
+
// 2. 旗标回归:--json 必须在子命令前(cli.ts 全局旗标预扫描只认子命令前位置);
|
|
100
|
+
// contentType → --content-type;parentDestinationId → --destination-id(上游 CLI 旗标名)
|
|
86
101
|
const echo = await fakeBin('hbcli-echo-args', 'echo-args');
|
|
87
|
-
const r2 = await anythingSearch({
|
|
88
|
-
keyword: '大理',
|
|
89
|
-
contentType: 'city',
|
|
90
|
-
parentDestinationId: 'd1',
|
|
91
|
-
hbcliBin: echo
|
|
92
|
-
});
|
|
102
|
+
const r2 = await anythingSearch({ keyword: '大理', contentType: 'city', parentDestinationId: 'd1', hbcliBin: echo });
|
|
93
103
|
assert.equal(r2.verdict, 'hit');
|
|
94
104
|
assert.equal(r2.hits[0].destinationId, '--json search anything 大理 --content-type city --destination-id d1', 'spawn 旗标形态(--json 前置/--destination-id 映射)应稳定,实际 ' + r2.hits[0].destinationId);
|
|
95
105
|
console.log('2. 旗标回归(--json 前置 + --destination-id 映射)OK');
|
|
106
|
+
// 3. 空候选
|
|
96
107
|
const empty = await fakeBin('hbcli-empty', 'empty');
|
|
97
|
-
const r3 = await anythingSearch({
|
|
98
|
-
keyword: 'nothing',
|
|
99
|
-
hbcliBin: empty
|
|
100
|
-
});
|
|
108
|
+
const r3 = await anythingSearch({ keyword: 'nothing', hbcliBin: empty });
|
|
101
109
|
assert.equal(r3.verdict, 'miss');
|
|
102
110
|
assert.equal(r3.hits.length, 0);
|
|
103
111
|
console.log('3. nothing → miss OK');
|
|
112
|
+
// 4. exit≠0
|
|
104
113
|
const fail = await fakeBin('hbcli-fail', 'fail');
|
|
105
|
-
const r4 = await anythingSearch({
|
|
106
|
-
keyword: 'x',
|
|
107
|
-
hbcliBin: fail
|
|
108
|
-
});
|
|
114
|
+
const r4 = await anythingSearch({ keyword: 'x', hbcliBin: fail });
|
|
109
115
|
assert.equal(r4.verdict, 'error');
|
|
110
116
|
assert.equal(r4.ok, false);
|
|
111
117
|
assert.match(r4.evidence, /error/);
|
|
112
118
|
console.log('4. fail → error (降级) OK');
|
|
119
|
+
// 5. 旧 CLI unknown command → error 带升级指引(issue #195)
|
|
113
120
|
const old = await fakeBin('hbcli-old', 'unknown-command');
|
|
114
|
-
const r5 = await anythingSearch({
|
|
115
|
-
keyword: 'x',
|
|
116
|
-
hbcliBin: old
|
|
117
|
-
});
|
|
121
|
+
const r5 = await anythingSearch({ keyword: 'x', hbcliBin: old });
|
|
118
122
|
assert.equal(r5.verdict, 'error');
|
|
119
123
|
assert.match(r5.error ?? '', /hbcli update|gotry setup/, 'error 应带升级指引,实际 ' + r5.error);
|
|
120
124
|
assert.match(r5.evidence, /unknown command/, 'evidence 保留上游原话(溯源)');
|
|
121
125
|
console.log(`5. 旧 CLI unknown command → 升级指引 OK(${r5.error})`);
|
|
126
|
+
// 6. 超时:fake hang 60s,我们给 800ms 超时,期望 verdict=error
|
|
127
|
+
// (latency 断言范围放宽,因为 sandbox node spawn 的 abort 行为不稳定)
|
|
122
128
|
const hang = await fakeBin('hbcli-hang', 'hang');
|
|
123
|
-
const r6 = await anythingSearch({
|
|
124
|
-
keyword: 'x',
|
|
125
|
-
hbcliBin: hang,
|
|
126
|
-
timeoutMs: 800
|
|
127
|
-
});
|
|
129
|
+
const r6 = await anythingSearch({ keyword: 'x', hbcliBin: hang, timeoutMs: 800 });
|
|
128
130
|
assert.equal(r6.verdict, 'error', 'timeout 应判 error');
|
|
129
131
|
console.log(`6. hang/timeout → error OK (latency=${r6.latencyMs}ms)`);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
});
|
|
132
|
+
// 7. 空 keyword
|
|
133
|
+
const r7 = await anythingSearch({ keyword: ' ' });
|
|
133
134
|
assert.equal(r7.verdict, 'error');
|
|
134
135
|
console.log('7. empty keyword → error OK');
|
|
135
136
|
console.log('\nANYTHING TESTS: 7/7 OK(hbcli fake + wire 对齐 + 降级诚实)');
|
|
136
|
-
} finally{
|
|
137
|
-
await rm(tmp, {
|
|
138
|
-
recursive: true,
|
|
139
|
-
force: true
|
|
140
|
-
});
|
|
141
137
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
finally {
|
|
139
|
+
await rm(tmp, { recursive: true, force: true });
|
|
140
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Client contract: package export, lazy-CJS loader shape, keyed DSH
|
|
3
|
+
* registrations, and defensive rendering from the runtime `block` payload.
|
|
4
|
+
* Browser execution is covered separately by dsh-artifact-web-e2e.ts.
|
|
5
|
+
*/
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import vm from 'node:vm';
|
|
11
|
+
const ROOT = join(import.meta.dirname, '..', '..');
|
|
12
|
+
const source = readFileSync(join(ROOT, 'client/client.js'), 'utf8');
|
|
13
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
|
|
14
|
+
assert.equal(pkg.exports?.['./client'], './client/client.js');
|
|
15
|
+
assert.equal(pkg.dsh?.client?.platform, 'web');
|
|
16
|
+
assert.ok(pkg.dsh?.client?.inject?.includes('@deepseek-ai/dsh-client-ui-tool'));
|
|
17
|
+
assert.ok(pkg.files?.includes('client/'));
|
|
18
|
+
assert.ok(source.includes('window.__ModuleLoader__.load'));
|
|
19
|
+
assert.ok(source.includes("tool.call.toolview"));
|
|
20
|
+
assert.ok(source.includes("gotry_artifacts_list"));
|
|
21
|
+
assert.ok(source.includes("gotry_artifacts_read"));
|
|
22
|
+
assert.ok(!source.includes('@deepseek-ai/dsh-client-ui-tool/lib/'));
|
|
23
|
+
const registrations = [];
|
|
24
|
+
const context = vm.createContext({
|
|
25
|
+
window: { __ModuleLoader__: { load: (registration) => registrations.push(registration) } },
|
|
26
|
+
console,
|
|
27
|
+
});
|
|
28
|
+
new vm.Script(source, { filename: 'client/client.js' }).runInContext(context);
|
|
29
|
+
assert.equal(registrations.length, 1);
|
|
30
|
+
assert.equal(registrations[0].id, '@danceiny/gotry');
|
|
31
|
+
function createElement(type, props, ...children) {
|
|
32
|
+
return { type, props: props ?? {}, children };
|
|
33
|
+
}
|
|
34
|
+
const fakeReact = {
|
|
35
|
+
createElement,
|
|
36
|
+
useState(initial) { return [initial, () => { }]; },
|
|
37
|
+
};
|
|
38
|
+
const clientExports = registrations[0].factory(name => {
|
|
39
|
+
assert.equal(name, 'react');
|
|
40
|
+
return fakeReact;
|
|
41
|
+
});
|
|
42
|
+
assert.deepEqual(Object.keys(clientExports), ['apply', 'inject']);
|
|
43
|
+
const slotRegistrations = [];
|
|
44
|
+
const registrationContext = {
|
|
45
|
+
inject(_deps, callback) { callback(registrationContext); },
|
|
46
|
+
slots: {
|
|
47
|
+
inject(_slot, callback) { callback(); },
|
|
48
|
+
register(definition, view) {
|
|
49
|
+
slotRegistrations.push({ key: definition.key, view });
|
|
50
|
+
return () => { };
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
clientExports.apply(registrationContext);
|
|
55
|
+
assert.deepEqual(slotRegistrations.map(item => item.key), ['gotry_artifacts_list', 'gotry_artifacts_read']);
|
|
56
|
+
assert.equal(slotRegistrations[0].view, slotRegistrations[1].view);
|
|
57
|
+
const view = slotRegistrations[0].view;
|
|
58
|
+
const listTree = view({
|
|
59
|
+
toolName: 'gotry_artifacts_list',
|
|
60
|
+
block: { kind: 'tool-result', isError: false, meta: { shape: 'paths', paths: ['/tmp/trip-2027.md'], total: 1, truncated: false }, content: [] },
|
|
61
|
+
});
|
|
62
|
+
assert.equal(listTree.props['data-gotry-artifact-card'], 'list');
|
|
63
|
+
assert.equal(listTree.props['data-gotry-artifact-state'], 'ok');
|
|
64
|
+
const malformedTree = view({ toolName: 'gotry_artifacts_read', block: { kind: 'tool-result', isError: false, meta: { shape: 'bad' }, content: [{ type: 'text', text: 'raw fallback' }] } });
|
|
65
|
+
assert.equal(malformedTree.props['data-gotry-artifact-card'], 'read');
|
|
66
|
+
assert.equal(malformedTree.props['data-gotry-artifact-state'], 'ok');
|
|
67
|
+
const requireRoot = createRequire(join(ROOT, 'package.json'));
|
|
68
|
+
assert.ok(requireRoot.resolve('./client/client.js'));
|
|
69
|
+
console.log('ARTIFACT CLIENT CONTRACT: package export + web manifest + lazy loader + 2 keyed views + malformed fallback OK');
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 产物面(issue #285)能力层离线 proof:
|
|
3
|
+
* list/read 工具链的纯函数 + 隔离 fixture 路径护栏。
|
|
4
|
+
* 与 smoke.ts §15 互相补——smoke 验注册 + dsh 卡片契约(在 plugin context),
|
|
5
|
+
* 本文件验 capabilities/artifacts.ts 纯函数 + 隔离 stateRoot(无 dsh 上下文)。
|
|
6
|
+
*
|
|
7
|
+
* 1) 账本权威 list:种工单 + 交付 → list 找到 async-run
|
|
8
|
+
* 2) cwd md list:工作目录顶层 md → list 找到 cwd-file
|
|
9
|
+
* 3) 合并去重:账本 + cwd 同 path 不重复
|
|
10
|
+
* 4) 裸工单 id read:从账本 deliverable 读出
|
|
11
|
+
* 5) 行号窗口:offset/limit 行号正确
|
|
12
|
+
* 6) 越界路径:ok:false + error 含「越界」
|
|
13
|
+
* 7) symlink 越界:ok:false
|
|
14
|
+
* 7b) 裸工单 id symlink/超大 deliverable:仍经过 canonical/root/size 护栏
|
|
15
|
+
* 8) 不支持扩展名:.db → ok:false + error 含「不支持的文件类型」
|
|
16
|
+
* 9) 不存在文件:ok:false
|
|
17
|
+
* 10) 超大文件:>2MB → ok:false
|
|
18
|
+
* 11) read 在 cwd/根边界外的相对路径:rejects
|
|
19
|
+
* 12) list limit 截断:truncated=true, paths.length === limit
|
|
20
|
+
*/
|
|
21
|
+
import assert from 'node:assert/strict';
|
|
22
|
+
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { tmpdir } from 'node:os';
|
|
24
|
+
import { join, resolve } from 'node:path';
|
|
25
|
+
import { listArtifacts, readArtifact } from '../capabilities/artifacts.js';
|
|
26
|
+
import { ensureLedger } from '../src/state-ledger.js';
|
|
27
|
+
const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-artifacts-cap-tests-'));
|
|
28
|
+
const cwd = mkdtempSync(join(tmpdir(), 'gotry-artifacts-cap-cwd-'));
|
|
29
|
+
// 1) 账本权威 list
|
|
30
|
+
{
|
|
31
|
+
const ledger = ensureLedger(stateRoot);
|
|
32
|
+
ledger.createWorkflowRun({ id: 'cap-probe-1', goal: '产物探针·账本', ticket: { objective: 'probe' }, state: {} });
|
|
33
|
+
ledger.settleWorkflowRun('cap-probe-1', '# 交付·账本权威\nD1 大理\nD2 洱海');
|
|
34
|
+
const r = await listArtifacts({ stateRoot, limit: 20 });
|
|
35
|
+
const run = r.artifacts.find(a => a.id === 'cap-probe-1');
|
|
36
|
+
assert.ok(run, '账本工单应被 list 找到');
|
|
37
|
+
assert.equal(run?.source, 'async-run');
|
|
38
|
+
assert.equal(run?.status, 'settled');
|
|
39
|
+
assert.ok(run?.path.endsWith('cap-probe-1.deliverable.md'), `path 形态应为 deliverable.md,实际 ${run?.path}`);
|
|
40
|
+
console.log('1) 账本权威 list OK');
|
|
41
|
+
}
|
|
42
|
+
// 2) cwd md list
|
|
43
|
+
{
|
|
44
|
+
const lines = Array.from({ length: 8 }, (_, i) => `第 ${i + 1} 行`);
|
|
45
|
+
writeFileSync(join(cwd, 'trip-2027-cap.md'), `# 行程探针\n${lines.join('\n')}\n`);
|
|
46
|
+
const r = await listArtifacts({ stateRoot, cwd, limit: 20 });
|
|
47
|
+
const md = r.artifacts.find(a => a.id === 'trip-2027-cap.md');
|
|
48
|
+
assert.ok(md, 'cwd 顶层 md 应被 list 找到');
|
|
49
|
+
assert.equal(md?.source, 'cwd-file');
|
|
50
|
+
console.log('2) cwd md list OK');
|
|
51
|
+
}
|
|
52
|
+
// 3) 合并去重:账本工单 cap-probe-1 的 path 与 cwd 任何文件 path 不冲突——验证 seenPath 去重逻辑
|
|
53
|
+
{
|
|
54
|
+
// 制造一个 cwd 文件与账本 deliverable 同 basename 看似不同
|
|
55
|
+
writeFileSync(join(cwd, 'cap-probe-1.deliverable.md'), 'unrelated');
|
|
56
|
+
const r = await listArtifacts({ stateRoot, cwd, limit: 20 });
|
|
57
|
+
const seen = new Map();
|
|
58
|
+
for (const a of r.artifacts) {
|
|
59
|
+
if (a.id === 'cap-probe-1.deliverable.md' || a.id === 'cap-probe-1') {
|
|
60
|
+
const k = resolve(a.path);
|
|
61
|
+
seen.set(k, (seen.get(k) ?? 0) + 1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// 同一 path 出现 ≥2 次即去重失败
|
|
65
|
+
for (const [k, n] of seen) {
|
|
66
|
+
assert.ok(n <= 1, `path ${k} 出现 ${n} 次,应去重为 1`);
|
|
67
|
+
}
|
|
68
|
+
console.log('3) 合并去重 OK');
|
|
69
|
+
}
|
|
70
|
+
// 4) 裸工单 id read
|
|
71
|
+
{
|
|
72
|
+
const r = await readArtifact({ stateRoot, cwd, path: 'cap-probe-1' });
|
|
73
|
+
assert.equal(r.ok, true, '裸工单 id 应可读');
|
|
74
|
+
if (r.ok) {
|
|
75
|
+
assert.equal(r.lang, 'markdown');
|
|
76
|
+
assert.equal(r.offset, 1);
|
|
77
|
+
assert.equal(r.lines[0]?.number, 1);
|
|
78
|
+
assert.ok(r.lines[0]?.text.includes('交付·账本权威'), '首行应含账本 deliverable 标题');
|
|
79
|
+
assert.equal(r.totalLines, 3, `交付 3 行,实际 ${r.totalLines}`);
|
|
80
|
+
}
|
|
81
|
+
console.log('4) 裸工单 id read OK');
|
|
82
|
+
}
|
|
83
|
+
// 5) 行号窗口
|
|
84
|
+
{
|
|
85
|
+
const r = await readArtifact({ stateRoot, cwd, path: 'trip-2027-cap.md', offset: 3, limit: 3 });
|
|
86
|
+
assert.equal(r.ok, true, 'cwd md 应可读');
|
|
87
|
+
if (r.ok) {
|
|
88
|
+
assert.equal(r.lines.length, 3, `offset=3 limit=3 应 3 行,实际 ${r.lines.length}`);
|
|
89
|
+
assert.equal(r.lines[0]?.number, 3, `首行号应 3,实际 ${r.lines[0]?.number}`);
|
|
90
|
+
assert.equal(r.lines[2]?.number, 5);
|
|
91
|
+
assert.equal(r.totalLines, 10, `8 行 + 标题 1 + 末尾空行 1 = 10,实际 ${r.totalLines}`);
|
|
92
|
+
assert.equal(r.windowed, true, `offset=3 limit=3 总 10 行 → windowed=true(可继续翻页)`);
|
|
93
|
+
}
|
|
94
|
+
const full = await readArtifact({ stateRoot, cwd, path: 'trip-2027-cap.md' });
|
|
95
|
+
assert.equal(full.ok, true, 'cwd md 默认 limit 应读完');
|
|
96
|
+
if (full.ok) {
|
|
97
|
+
assert.equal(full.windowed, false, `读完应 windowed=false`);
|
|
98
|
+
assert.equal(full.lines.length, full.totalLines, `lines.length === totalLines,实际 ${full.lines.length}/${full.totalLines}`);
|
|
99
|
+
}
|
|
100
|
+
console.log('5) 行号窗口 OK');
|
|
101
|
+
}
|
|
102
|
+
// 6) 越界路径
|
|
103
|
+
{
|
|
104
|
+
const r = await readArtifact({ stateRoot, cwd, path: '../../../../../etc/passwd' });
|
|
105
|
+
assert.equal(r.ok, false, '越界路径必须被拒');
|
|
106
|
+
if (!r.ok) {
|
|
107
|
+
assert.ok(r.error.includes('越界'), `error 应含「越界」,实际 ${r.error}`);
|
|
108
|
+
}
|
|
109
|
+
console.log('6) 越界路径 OK');
|
|
110
|
+
}
|
|
111
|
+
// 7) symlink 越界
|
|
112
|
+
{
|
|
113
|
+
const linkPath = join(cwd, 'symlink-out.md');
|
|
114
|
+
try {
|
|
115
|
+
symlinkSync('/etc/passwd', linkPath);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// macOS 上 /etc/passwd 是符号链接到 /private/etc/passwd;symlink 自身仍 out-of-root
|
|
119
|
+
}
|
|
120
|
+
const r = await readArtifact({ stateRoot, cwd, path: 'symlink-out.md' });
|
|
121
|
+
assert.equal(r.ok, false, '相对路径 symlink 解析后越出 cwd 必须被拒');
|
|
122
|
+
if (!r.ok)
|
|
123
|
+
assert.ok(r.error.includes('越界'), `symlink 越界错误应含「越界」,实际 ${r.error}`);
|
|
124
|
+
// 绝对 symlink 指向目录也必须按 canonical target 做边界检查。
|
|
125
|
+
const absLink = join(stateRoot, 'abs-link.md');
|
|
126
|
+
try {
|
|
127
|
+
symlinkSync('/etc', absLink);
|
|
128
|
+
const r2 = await readArtifact({ stateRoot, cwd, path: absLink });
|
|
129
|
+
assert.equal(r2.ok, false, '绝对 symlink 越界必须被拒');
|
|
130
|
+
if (!r2.ok) {
|
|
131
|
+
assert.ok(r2.error.includes('越界'), `error 应含「越界」,实际 ${r2.error}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch { /* 非 POSIX fs 跳过 */ }
|
|
135
|
+
console.log('7) symlink 越界 OK');
|
|
136
|
+
}
|
|
137
|
+
// 7b) 裸工单 id 的兼容文件视图也必须经过同一组 canonical/root/size 护栏。
|
|
138
|
+
{
|
|
139
|
+
mkdirSync(join(stateRoot, 'gotry-state', 'async'), { recursive: true });
|
|
140
|
+
const bareLink = join(stateRoot, 'gotry-state', 'async', 'evil.deliverable.md');
|
|
141
|
+
let bareLinkCreated = false;
|
|
142
|
+
try {
|
|
143
|
+
symlinkSync('/etc/hosts', bareLink);
|
|
144
|
+
bareLinkCreated = true;
|
|
145
|
+
}
|
|
146
|
+
catch { /* 非 POSIX fs 跳过 */ }
|
|
147
|
+
if (bareLinkCreated) {
|
|
148
|
+
const r = await readArtifact({ stateRoot, cwd, path: 'evil' });
|
|
149
|
+
assert.equal(r.ok, false, '裸工单 id symlink 指向 root 外时必须被拒');
|
|
150
|
+
if (!r.ok)
|
|
151
|
+
assert.ok(r.error.includes('越界'), `裸工单 id symlink 越界错误应含「越界」,实际 ${r.error}`);
|
|
152
|
+
}
|
|
153
|
+
const bareBig = join(stateRoot, 'gotry-state', 'async', 'oversized.deliverable.md');
|
|
154
|
+
writeFileSync(bareBig, 'x'.repeat(2 * 1024 * 1024 + 1));
|
|
155
|
+
const oversized = await readArtifact({ stateRoot, cwd, path: 'oversized' });
|
|
156
|
+
assert.equal(oversized.ok, false, '裸工单 id 超大文件必须被拒');
|
|
157
|
+
if (!oversized.ok)
|
|
158
|
+
assert.ok(oversized.error.includes('过大'), `裸工单 id 超大错误应含「过大」,实际 ${oversized.error}`);
|
|
159
|
+
rmSync(bareLink, { force: true });
|
|
160
|
+
rmSync(bareBig, { force: true });
|
|
161
|
+
console.log('7b) 裸工单 id symlink/超大 deliverable 护栏 OK');
|
|
162
|
+
}
|
|
163
|
+
// 8) 不支持扩展名
|
|
164
|
+
{
|
|
165
|
+
mkdirSync(join(stateRoot, 'gotry-state'), { recursive: true });
|
|
166
|
+
writeFileSync(join(stateRoot, 'gotry-state', 'gotry-state.db'), 'fake db');
|
|
167
|
+
const r = await readArtifact({ stateRoot, cwd, path: 'gotry-state/gotry-state.db' });
|
|
168
|
+
assert.equal(r.ok, false, '.db 必须被拒');
|
|
169
|
+
if (!r.ok) {
|
|
170
|
+
assert.ok(r.error.includes('不支持的文件类型'), `error 应含「不支持的文件类型」,实际 ${r.error}`);
|
|
171
|
+
}
|
|
172
|
+
console.log('8) 不支持扩展名 OK');
|
|
173
|
+
}
|
|
174
|
+
// 9) 不存在文件
|
|
175
|
+
{
|
|
176
|
+
const r = await readArtifact({ stateRoot, cwd, path: 'not-found.md' });
|
|
177
|
+
assert.equal(r.ok, false, '不存在文件必须被拒');
|
|
178
|
+
if (!r.ok) {
|
|
179
|
+
assert.ok(r.error.includes('不存在'), `error 应含「不存在」,实际 ${r.error}`);
|
|
180
|
+
}
|
|
181
|
+
console.log('9) 不存在文件 OK');
|
|
182
|
+
}
|
|
183
|
+
// 10) 超大文件
|
|
184
|
+
{
|
|
185
|
+
const big = join(cwd, 'big.md');
|
|
186
|
+
// 2MB+ 一点点;read 默认上限 2MB
|
|
187
|
+
const chunk = 'x'.repeat(64 * 1024);
|
|
188
|
+
const handle = await import('node:fs/promises').then(m => m.open(big, 'w'));
|
|
189
|
+
try {
|
|
190
|
+
for (let i = 0; i < 33; i++)
|
|
191
|
+
await handle.write(chunk); // 33 * 64KB = 2.11MB
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
await handle.close();
|
|
195
|
+
}
|
|
196
|
+
const r = await readArtifact({ stateRoot, cwd, path: 'big.md' });
|
|
197
|
+
assert.equal(r.ok, false, '>2MB 必须被拒');
|
|
198
|
+
if (!r.ok) {
|
|
199
|
+
assert.ok(r.error.includes('过大'), `error 应含「过大」,实际 ${r.error}`);
|
|
200
|
+
}
|
|
201
|
+
console.log('10) 超大文件 OK');
|
|
202
|
+
}
|
|
203
|
+
// 11) read 接受 path 三形态:id / cwd 文件名 / 绝对路径
|
|
204
|
+
{
|
|
205
|
+
// 11a) 相对文件名仅在 cwd 顶层
|
|
206
|
+
const r = await readArtifact({ stateRoot, cwd, path: 'trip-2027-cap.md' });
|
|
207
|
+
assert.equal(r.ok, true, '相对文件名应解析为 cwd 顶层');
|
|
208
|
+
// 11b) path 为裸字符串无后缀 + 不在 cwd → 尝试 stateRoot 下的 gotry-state/async/
|
|
209
|
+
const r2 = await readArtifact({ stateRoot, cwd, path: 'cap-probe-1' });
|
|
210
|
+
assert.equal(r2.ok, true, '裸工单 id 应解析为账本');
|
|
211
|
+
// 11c) 空 path
|
|
212
|
+
const r3 = await readArtifact({ stateRoot, cwd, path: '' });
|
|
213
|
+
assert.equal(r3.ok, false, '空 path 应被拒');
|
|
214
|
+
console.log('11) path 三形态 OK');
|
|
215
|
+
}
|
|
216
|
+
// 12) list limit 截断
|
|
217
|
+
{
|
|
218
|
+
for (let i = 0; i < 5; i++)
|
|
219
|
+
writeFileSync(join(cwd, `extra-${i}.md`), `# 副产物 ${i}`);
|
|
220
|
+
const r = await listArtifacts({ stateRoot, cwd, limit: 3 });
|
|
221
|
+
assert.equal(r.artifacts.length, 3, `limit=3 应 3 条,实际 ${r.artifacts.length}`);
|
|
222
|
+
assert.equal(r.truncated, true, `应有更多,truncated 应为 true`);
|
|
223
|
+
// total = sum of each source's retained count = 1 (账本) + 0 (async dir) + 3 (cwd top 3 by mtime) = 4
|
|
224
|
+
// (listCwdMarkdown 内部就 slice(0, limit);要更多需调用方放大 limit)
|
|
225
|
+
assert.equal(r.total, 4, `total 应 4(账本 1 + cwd top 3),实际 ${r.total}`);
|
|
226
|
+
assert.ok(r.total > r.artifacts.length, `total 应 > artifacts.length 才能 truncated,实际 ${r.total} vs ${r.artifacts.length}`);
|
|
227
|
+
// 放大 limit 后能看到全部
|
|
228
|
+
const r2 = await listArtifacts({ stateRoot, cwd, limit: 20 });
|
|
229
|
+
assert.equal(r2.truncated, false, `limit=20 应能装下,truncated=false`);
|
|
230
|
+
assert.ok(r2.total >= 7, `放大 limit 后 total 应 ≥ 7(账本 1 + cwd 多个 md),实际 ${r2.total}`);
|
|
231
|
+
console.log('12) list limit 截断 OK');
|
|
232
|
+
}
|
|
233
|
+
rmSync(stateRoot, { recursive: true, force: true });
|
|
234
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
235
|
+
console.log('all artifacts capability tests OK');
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 异步工单回收(S5 编排半段 → ADR-15 durable 恢复语义):加载持久化工单,
|
|
3
|
+
* 经可日志化 solve 端口执行深度规划(done 步骤复用账本结果,不重执行),
|
|
4
|
+
* 交付物落账本 + .deliverable.md 视图。可由任何后续进程执行(loopx 驱动的
|
|
5
|
+
* tick、state-cli tick、人工、未来的通知系统)——「一小时后回来」跨进程成立,
|
|
6
|
+
* 且崩溃恢复 exactly-once:重放不重复花钱。最后一行固定输出
|
|
7
|
+
* gotry_async_terminal.v1 JSON;4/4 exit 0,非 4/4 exit 2。
|
|
8
|
+
* 运行(在 ts/ 下):npx tsx scripts/async-collect.ts <ticketId> [stateRoot]
|
|
9
|
+
* 环境变量 GOTRY_SOLVE_COUNT_FILE:每次真实求解追加一行(回归用计数探针)。
|
|
10
|
+
*/
|
|
1
11
|
import { appendFileSync } from 'node:fs';
|
|
2
|
-
import { ASYNC_TERMINAL_SCHEMA, collectDeepPlanning, loadAsyncTicket, makeJournaledSolvePort, settleAsyncTicket } from '../src/loop.js';
|
|
12
|
+
import { ASYNC_TERMINAL_SCHEMA, collectDeepPlanning, loadAsyncTicket, makeJournaledSolvePort, settleAsyncTicket, } from '../src/loop.js';
|
|
3
13
|
import { solveUnified } from '../src/unified.js';
|
|
4
14
|
import { openLedgerIfExists } from '../src/state-ledger.js';
|
|
5
15
|
function isTerminalOutcome(value) {
|
|
6
|
-
return value?.['schema'] === ASYNC_TERMINAL_SCHEMA
|
|
16
|
+
return value?.['schema'] === ASYNC_TERMINAL_SCHEMA
|
|
17
|
+
&& (value['status'] === 'succeeded' || value['status'] === 'failed')
|
|
18
|
+
&& typeof value['passed'] === 'number'
|
|
19
|
+
&& value['total'] === 4
|
|
20
|
+
&& Array.isArray(value['failed_checks']);
|
|
7
21
|
}
|
|
8
22
|
function legacyTerminalOutcome(ticket, status, deliverable) {
|
|
9
23
|
const succeeded = status === 'settled' && deliverable.includes('不失望四条:4/4 ✅');
|
|
@@ -13,12 +27,8 @@ function legacyTerminalOutcome(ticket, status, deliverable) {
|
|
|
13
27
|
status: succeeded ? 'succeeded' : 'failed',
|
|
14
28
|
passed: succeeded ? 4 : 0,
|
|
15
29
|
total: 4,
|
|
16
|
-
checks: {
|
|
17
|
-
|
|
18
|
-
},
|
|
19
|
-
failed_checks: succeeded ? [] : [
|
|
20
|
-
'legacy_unstructured_terminal'
|
|
21
|
-
]
|
|
30
|
+
checks: { legacy_human_marker_only: succeeded },
|
|
31
|
+
failed_checks: succeeded ? [] : ['legacy_unstructured_terminal'],
|
|
22
32
|
};
|
|
23
33
|
}
|
|
24
34
|
function emitTerminal(outcome) {
|
|
@@ -36,11 +46,14 @@ if (!loaded) {
|
|
|
36
46
|
console.error(`工单 ${ticketId} 不存在(账本 workflow_runs / gotry-state/async/${ticketId}.json)`);
|
|
37
47
|
process.exit(1);
|
|
38
48
|
}
|
|
49
|
+
// 终态幂等:已 settled/failed 的工单复诵账本交付物与机器终态,零重算
|
|
39
50
|
const ledger = openLedgerIfExists(stateRoot);
|
|
40
51
|
const run = ledger?.getWorkflowRun(ticketId);
|
|
41
52
|
if ((run?.status === 'settled' || run?.status === 'failed') && run.deliverable) {
|
|
42
53
|
const storedOutcome = ledger?.getWorkflowTerminalOutcome(ticketId) ?? null;
|
|
43
|
-
const outcome = isTerminalOutcome(storedOutcome)
|
|
54
|
+
const outcome = isTerminalOutcome(storedOutcome)
|
|
55
|
+
? storedOutcome
|
|
56
|
+
: legacyTerminalOutcome(ticketId, run.status, run.deliverable);
|
|
44
57
|
await settleAsyncTicket(ticketId, run.deliverable, stateRoot, outcome);
|
|
45
58
|
console.log(`工单 ${ticketId} 已交付(账本终态,复诵不重算):\n\n${run.deliverable}`);
|
|
46
59
|
emitTerminal(outcome);
|
|
@@ -50,16 +63,14 @@ if (!ledger) {
|
|
|
50
63
|
process.exit(1);
|
|
51
64
|
}
|
|
52
65
|
const solve = makeJournaledSolvePort(ledger, ticketId, solveUnified, {
|
|
53
|
-
onRealSolve: ()=>{
|
|
66
|
+
onRealSolve: () => {
|
|
54
67
|
const f = process.env['GOTRY_SOLVE_COUNT_FILE'];
|
|
55
|
-
if (f)
|
|
56
|
-
|
|
68
|
+
if (f)
|
|
69
|
+
appendFileSync(f, 'solve\n');
|
|
70
|
+
},
|
|
57
71
|
});
|
|
58
72
|
const { reply, outcome } = await collectDeepPlanning(loaded.state, loaded.ticket, solve);
|
|
59
73
|
const out = await settleAsyncTicket(ticketId, reply, stateRoot, outcome);
|
|
60
74
|
console.log(`交付物已落盘:${out}\n\n${reply}`);
|
|
61
75
|
console.log(JSON.stringify(outcome));
|
|
62
76
|
process.exitCode = outcome.status === 'succeeded' ? 0 : 2;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//# sourceURL=ts/scripts/async-collect.ts
|