@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,4 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 产物面(issue #25):agent 生成的文件不再只是「落在本地文件系统里的一个文件名」。
|
|
3
|
+
*
|
|
4
|
+
* 只读能力层,两个纯函数入口(dsh 工具 gotry_artifacts_list / gotry_artifacts_read 的实现):
|
|
5
|
+
* - listArtifacts: 产物发现。权威源 = 账本 workflow_runs(ADR-15;无账本的旧 root 回退
|
|
6
|
+
* 扫描 gotry-state/async/*.deliverable.md 文件视图),外加 dsh 工作目录顶层 *.md
|
|
7
|
+
* (agent 写出的行程/规划文件正落在这里——issue 截图里的 trip-2027-*.md 即此类)。
|
|
8
|
+
* - readArtifact: 产物阅读。行窗口(offset/limit)+ 原始行号,输出 dsh read 卡所需的
|
|
9
|
+
* 全部字段({number,text}[] / totalLines / lang),UI 侧渲染为行号文件视图。
|
|
10
|
+
*
|
|
11
|
+
* 纪律:本层只读(不写任何文件;WriteGate 红线不涉及);读取范围白名单 =
|
|
12
|
+
* stateRoot 根 + dsh 工作目录(排除 node_modules/.git),扩展名白名单 =
|
|
13
|
+
* 文本类(md/txt/json/jsonl/csv/log/yaml/yml)——本工具是「产物查看」,不是通用文件浏览器。
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
import { readdir, readFile, realpath, stat } from 'node:fs/promises';
|
|
2
17
|
import { existsSync } from 'node:fs';
|
|
3
18
|
import { isAbsolute, join, resolve, sep } from 'node:path';
|
|
4
19
|
import { openLedgerIfExists } from '../src/state-ledger.js';
|
|
@@ -6,19 +21,9 @@ const MAX_LIST = 50;
|
|
|
6
21
|
const MAX_WINDOW = 400;
|
|
7
22
|
const MAX_BYTES = 2 * 1024 * 1024;
|
|
8
23
|
const TEXT_EXT_LANG = {
|
|
9
|
-
md: 'markdown',
|
|
10
|
-
txt: 'text',
|
|
11
|
-
json: 'json',
|
|
12
|
-
jsonl: 'json',
|
|
13
|
-
csv: 'csv',
|
|
14
|
-
log: 'text',
|
|
15
|
-
yaml: 'yaml',
|
|
16
|
-
yml: 'yaml'
|
|
24
|
+
md: 'markdown', txt: 'text', json: 'json', jsonl: 'json', csv: 'csv', log: 'text', yaml: 'yaml', yml: 'yaml',
|
|
17
25
|
};
|
|
18
|
-
const DIR_DENY = [
|
|
19
|
-
'node_modules',
|
|
20
|
-
'.git'
|
|
21
|
-
];
|
|
26
|
+
const DIR_DENY = ['node_modules', '.git'];
|
|
22
27
|
function rootOf(stateRoot) {
|
|
23
28
|
return stateRoot === '.' ? process.cwd() : resolve(stateRoot);
|
|
24
29
|
}
|
|
@@ -27,16 +32,20 @@ function underRoot(p, root) {
|
|
|
27
32
|
return p === r || p.startsWith(r + sep);
|
|
28
33
|
}
|
|
29
34
|
function hasDeniedSegment(p) {
|
|
30
|
-
return p.split(sep).some(
|
|
35
|
+
return p.split(sep).some(seg => DIR_DENY.includes(seg));
|
|
31
36
|
}
|
|
32
37
|
function asyncDeliverablePath(root, id) {
|
|
33
38
|
return join(root, 'gotry-state', 'async', `${id}.deliverable.md`);
|
|
34
39
|
}
|
|
40
|
+
/** 账本 workflow_runs 是权威(listWorkflowRuns 只读 SELECT 直查,不为一个视图改 ledger 类)。 */
|
|
35
41
|
function listRunsFromLedger(root, tenant, limit) {
|
|
36
42
|
const ledger = openLedgerIfExists(root, tenant);
|
|
37
|
-
if (!ledger)
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
if (!ledger)
|
|
44
|
+
return [];
|
|
45
|
+
const rows = ledger.db
|
|
46
|
+
.prepare('SELECT id, goal, status, deliverable, updated FROM workflow_runs WHERE tenant_id = ? ORDER BY updated DESC LIMIT ?')
|
|
47
|
+
.all(ledger.tenant, limit);
|
|
48
|
+
return rows.map(r => {
|
|
40
49
|
const file = asyncDeliverablePath(root, r.id);
|
|
41
50
|
return {
|
|
42
51
|
source: 'async-run',
|
|
@@ -45,23 +54,29 @@ function listRunsFromLedger(root, tenant, limit) {
|
|
|
45
54
|
path: file,
|
|
46
55
|
status: r.status,
|
|
47
56
|
updated: r.updated,
|
|
48
|
-
bytes: r.deliverable?.length
|
|
57
|
+
bytes: r.deliverable?.length,
|
|
49
58
|
};
|
|
50
59
|
});
|
|
51
60
|
}
|
|
61
|
+
/** 无账本旧 root 的兼容视图:直接扫 async 目录的 deliverable 文件(只读,与清扫合同同一目录)。 */
|
|
52
62
|
async function listDeliverableFiles(root, limit) {
|
|
53
63
|
const dir = join(root, 'gotry-state', 'async');
|
|
54
64
|
let names = [];
|
|
55
65
|
try {
|
|
56
|
-
names = (await readdir(dir)).filter(
|
|
57
|
-
}
|
|
66
|
+
names = (await readdir(dir)).filter(n => n.endsWith('.deliverable.md'));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
58
69
|
return [];
|
|
59
70
|
}
|
|
60
71
|
const entries = [];
|
|
61
|
-
for (const n of names.slice(0, limit * 2)){
|
|
72
|
+
for (const n of names.slice(0, limit * 2)) {
|
|
62
73
|
const p = join(dir, n);
|
|
63
|
-
const st = await stat(p).catch(()=>null);
|
|
64
|
-
if (!st?.isFile())
|
|
74
|
+
const st = await stat(p).catch(() => null);
|
|
75
|
+
if (!st?.isFile())
|
|
76
|
+
continue;
|
|
77
|
+
const canonical = await realpath(p).catch(() => null);
|
|
78
|
+
if (!canonical || !underRoot(canonical, root) || hasDeniedSegment(canonical))
|
|
79
|
+
continue;
|
|
65
80
|
entries.push({
|
|
66
81
|
source: 'async-run',
|
|
67
82
|
id: n.replace(/\.deliverable\.md$/, ''),
|
|
@@ -69,129 +84,137 @@ async function listDeliverableFiles(root, limit) {
|
|
|
69
84
|
path: p,
|
|
70
85
|
status: existsSync(p.replace(/\.deliverable\.md$/, '.json')) ? 'pending-view' : 'legacy',
|
|
71
86
|
updated: new Date(st.mtimeMs).toISOString(),
|
|
72
|
-
bytes: st.size
|
|
87
|
+
bytes: st.size,
|
|
73
88
|
});
|
|
74
89
|
}
|
|
75
|
-
return entries.sort((a, b)=>String(b.updated).localeCompare(String(a.updated)));
|
|
90
|
+
return entries.sort((a, b) => String(b.updated).localeCompare(String(a.updated)));
|
|
76
91
|
}
|
|
92
|
+
/** dsh 工作目录顶层 *.md(agent 写出的行程规划等);非递归,排除 dotfiles。 */
|
|
77
93
|
async function listCwdMarkdown(cwd, limit) {
|
|
78
94
|
let dirents;
|
|
79
95
|
try {
|
|
80
|
-
dirents = await readdir(cwd, {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
} catch {
|
|
96
|
+
dirents = await readdir(cwd, { withFileTypes: true });
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
84
99
|
return [];
|
|
85
100
|
}
|
|
86
101
|
const entries = [];
|
|
87
|
-
for (const d of dirents){
|
|
88
|
-
if (!d.isFile() || !/\.md$/i.test(d.name) || d.name.startsWith('.'))
|
|
102
|
+
for (const d of dirents) {
|
|
103
|
+
if (!d.isFile() || !/\.md$/i.test(d.name) || d.name.startsWith('.'))
|
|
104
|
+
continue;
|
|
89
105
|
const p = join(cwd, d.name);
|
|
90
|
-
const st = await stat(p).catch(()=>null);
|
|
91
|
-
if (!st)
|
|
106
|
+
const st = await stat(p).catch(() => null);
|
|
107
|
+
if (!st)
|
|
108
|
+
continue;
|
|
109
|
+
const canonical = await realpath(p).catch(() => null);
|
|
110
|
+
if (!canonical || !underRoot(canonical, cwd) || hasDeniedSegment(canonical))
|
|
111
|
+
continue;
|
|
92
112
|
entries.push({
|
|
93
113
|
source: 'cwd-file',
|
|
94
114
|
id: d.name,
|
|
95
115
|
title: d.name.replace(/\.md$/i, ''),
|
|
96
116
|
path: p,
|
|
97
117
|
updated: new Date(st.mtimeMs).toISOString(),
|
|
98
|
-
bytes: st.size
|
|
118
|
+
bytes: st.size,
|
|
99
119
|
});
|
|
100
120
|
}
|
|
101
|
-
return entries.sort((a, b)=>String(b.updated).localeCompare(String(a.updated))).slice(0, limit);
|
|
121
|
+
return entries.sort((a, b) => String(b.updated).localeCompare(String(a.updated))).slice(0, limit);
|
|
102
122
|
}
|
|
103
123
|
export async function listArtifacts(opts) {
|
|
104
124
|
const limit = Math.max(1, Math.min(opts.limit ?? 20, MAX_LIST));
|
|
105
125
|
const root = rootOf(opts.stateRoot);
|
|
106
126
|
const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
|
|
127
|
+
const canonicalRoot = await realpath(root).catch(() => root);
|
|
128
|
+
const canonicalCwd = await realpath(cwd).catch(() => cwd);
|
|
107
129
|
const seenPath = new Set();
|
|
108
130
|
const merged = [];
|
|
109
|
-
for (const e of [
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
...await listCwdMarkdown(cwd, limit)
|
|
113
|
-
]){
|
|
114
|
-
if (seenPath.has(e.path)) continue;
|
|
131
|
+
for (const e of [...listRunsFromLedger(canonicalRoot, 'local', limit), ...(await listDeliverableFiles(canonicalRoot, limit)), ...(await listCwdMarkdown(canonicalCwd, limit))]) {
|
|
132
|
+
if (seenPath.has(e.path))
|
|
133
|
+
continue;
|
|
115
134
|
seenPath.add(e.path);
|
|
116
135
|
merged.push(e);
|
|
117
136
|
}
|
|
118
|
-
merged.sort((a, b)=>String(b.updated ?? '').localeCompare(String(a.updated ?? '')));
|
|
137
|
+
merged.sort((a, b) => String(b.updated ?? '').localeCompare(String(a.updated ?? '')));
|
|
119
138
|
const total = merged.length;
|
|
120
|
-
return {
|
|
121
|
-
artifacts: merged.slice(0, limit),
|
|
122
|
-
total,
|
|
123
|
-
truncated: total > limit,
|
|
124
|
-
roots: [
|
|
125
|
-
root,
|
|
126
|
-
cwd
|
|
127
|
-
]
|
|
128
|
-
};
|
|
139
|
+
return { artifacts: merged.slice(0, limit), total, truncated: total > limit, roots: [canonicalRoot, canonicalCwd] };
|
|
129
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* 读一个产物。path 三形态:
|
|
143
|
+
* 1. 裸工单 id(无 / 无 .)→ 账本 workflow_runs.deliverable(权威),文件视图缺失也能读;
|
|
144
|
+
* 2. list 返回的绝对/相对路径 → 限定在 stateRoot 根或 dsh 工作目录内(排除 node_modules/.git);
|
|
145
|
+
* 3. 相对文件名 → 先按 dsh 工作目录顶层,再按 gotry-state/async/ 找。
|
|
146
|
+
* 窗口:offset(1 起)/limit(≤400 行);超窗返回 windowed:true,UI 用 read 卡渲染行号视图。
|
|
147
|
+
*/
|
|
130
148
|
export async function readArtifact(opts) {
|
|
131
149
|
const root = rootOf(opts.stateRoot);
|
|
132
150
|
const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
|
|
151
|
+
const canonicalRoot = await realpath(root).catch(() => root);
|
|
152
|
+
const canonicalCwd = await realpath(cwd).catch(() => cwd);
|
|
133
153
|
const raw = String(opts.path ?? '').trim();
|
|
134
|
-
if (!raw)
|
|
135
|
-
ok: false,
|
|
136
|
-
error: 'path 必填(来自 gotry_artifacts_list 的 path,或异步工单 id)'
|
|
137
|
-
};
|
|
154
|
+
if (!raw)
|
|
155
|
+
return { ok: false, error: 'path 必填(来自 gotry_artifacts_list 的 path,或异步工单 id)' };
|
|
138
156
|
let text = null;
|
|
139
157
|
let filePath = '';
|
|
158
|
+
// 1) 裸工单 id:账本权威读(文件视图缺失不挡阅读)
|
|
140
159
|
if (!raw.includes('/') && !raw.includes('\\') && !raw.includes('.')) {
|
|
141
160
|
const ledger = openLedgerIfExists(root, 'local');
|
|
142
|
-
const run = ledger?.db
|
|
161
|
+
const run = ledger?.db
|
|
162
|
+
.prepare('SELECT id, goal, status, deliverable FROM workflow_runs WHERE id = ? AND tenant_id = ?')
|
|
163
|
+
.get(raw, ledger.tenant);
|
|
143
164
|
if (run?.deliverable) {
|
|
144
165
|
text = run.deliverable;
|
|
145
166
|
filePath = asyncDeliverablePath(root, raw);
|
|
146
|
-
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
147
169
|
const p = asyncDeliverablePath(root, raw);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
170
|
+
const canonical = await realpath(p).catch(() => null);
|
|
171
|
+
if (canonical) {
|
|
172
|
+
if (!underRoot(canonical, canonicalRoot) || hasDeniedSegment(canonical)) {
|
|
173
|
+
return { ok: false, error: `路径越界:${raw}`, hint: `只读 ${root} 与 dsh 工作目录内的文本产物` };
|
|
174
|
+
}
|
|
175
|
+
const ext = canonical.slice(canonical.lastIndexOf('.') + 1).toLowerCase();
|
|
176
|
+
if (!TEXT_EXT_LANG[ext]) {
|
|
177
|
+
return { ok: false, error: `不支持的文件类型 .${ext}`, hint: `白名单:${Object.keys(TEXT_EXT_LANG).join('/')}` };
|
|
178
|
+
}
|
|
179
|
+
const st = await stat(canonical).catch(() => null);
|
|
180
|
+
if (!st?.isFile())
|
|
181
|
+
return { ok: false, error: `文件不存在:${raw}`, hint: '先 gotry_artifacts_list 看在册产物' };
|
|
182
|
+
if (st.size > MAX_BYTES)
|
|
183
|
+
return { ok: false, error: `文件过大(${st.size} bytes > ${MAX_BYTES})` };
|
|
184
|
+
filePath = canonical;
|
|
185
|
+
text = await readFile(canonical, 'utf-8');
|
|
151
186
|
}
|
|
152
187
|
}
|
|
153
|
-
if (text === null)
|
|
154
|
-
ok: false,
|
|
155
|
-
error: `工单 ${raw} 无 deliverable(未交付或不存在)`,
|
|
156
|
-
hint: '先 gotry_artifacts_list 看在册产物'
|
|
157
|
-
};
|
|
188
|
+
if (text === null)
|
|
189
|
+
return { ok: false, error: `工单 ${raw} 无 deliverable(未交付或不存在)`, hint: '先 gotry_artifacts_list 看在册产物' };
|
|
158
190
|
}
|
|
191
|
+
// 2) 路径形态:目录白名单 + 扩展名白名单
|
|
159
192
|
if (text === null) {
|
|
160
|
-
const candidates = isAbsolute(raw) ? [
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
resolve(root, raw),
|
|
165
|
-
resolve(root, 'gotry-state', 'async', raw)
|
|
166
|
-
];
|
|
167
|
-
const allowed = candidates.find((p)=>(underRoot(p, cwd) || underRoot(p, root)) && !hasDeniedSegment(p));
|
|
193
|
+
const candidates = isAbsolute(raw) ? [resolve(raw)] : [resolve(cwd, raw), resolve(root, raw), resolve(root, 'gotry-state', 'async', raw)];
|
|
194
|
+
const inScope = (p) => (underRoot(p, cwd) || underRoot(p, root)) && !hasDeniedSegment(p);
|
|
195
|
+
// Prefer an existing candidate so a cwd miss does not mask a valid state-root file.
|
|
196
|
+
const allowed = candidates.find(p => inScope(p) && existsSync(p)) ?? candidates.find(inScope);
|
|
168
197
|
if (!allowed) {
|
|
169
|
-
return {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
};
|
|
198
|
+
return { ok: false, error: `路径越界:${raw}`, hint: `只读 ${root} 与 dsh 工作目录内的文本产物` };
|
|
199
|
+
}
|
|
200
|
+
const canonical = await realpath(allowed).catch(() => null);
|
|
201
|
+
if (!canonical) {
|
|
202
|
+
return { ok: false, error: `文件不存在:${raw}`, hint: '先 gotry_artifacts_list 看在册产物' };
|
|
174
203
|
}
|
|
175
|
-
|
|
204
|
+
if (!(underRoot(canonical, canonicalCwd) || underRoot(canonical, canonicalRoot)) || hasDeniedSegment(canonical)) {
|
|
205
|
+
return { ok: false, error: `路径越界:${raw}`, hint: `只读 ${root} 与 dsh 工作目录内的文本产物` };
|
|
206
|
+
}
|
|
207
|
+
const ext = canonical.slice(canonical.lastIndexOf('.') + 1).toLowerCase();
|
|
176
208
|
if (!TEXT_EXT_LANG[ext]) {
|
|
177
|
-
return {
|
|
178
|
-
ok: false,
|
|
179
|
-
error: `不支持的文件类型 .${ext}`,
|
|
180
|
-
hint: `白名单:${Object.keys(TEXT_EXT_LANG).join('/')}`
|
|
181
|
-
};
|
|
209
|
+
return { ok: false, error: `不支持的文件类型 .${ext}`, hint: `白名单:${Object.keys(TEXT_EXT_LANG).join('/')}` };
|
|
182
210
|
}
|
|
183
|
-
const st = await stat(
|
|
184
|
-
if (!st?.isFile())
|
|
185
|
-
ok: false,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
ok: false,
|
|
191
|
-
error: `文件过大(${st.size} bytes > ${MAX_BYTES})`
|
|
192
|
-
};
|
|
193
|
-
filePath = allowed;
|
|
194
|
-
text = await readFile(allowed, 'utf-8');
|
|
211
|
+
const st = await stat(canonical).catch(() => null);
|
|
212
|
+
if (!st?.isFile())
|
|
213
|
+
return { ok: false, error: `文件不存在:${raw}`, hint: '先 gotry_artifacts_list 看在册产物' };
|
|
214
|
+
if (st.size > MAX_BYTES)
|
|
215
|
+
return { ok: false, error: `文件过大(${st.size} bytes > ${MAX_BYTES})` };
|
|
216
|
+
filePath = canonical;
|
|
217
|
+
text = await readFile(canonical, 'utf-8');
|
|
195
218
|
}
|
|
196
219
|
const allLines = text.split('\n');
|
|
197
220
|
const offset = Math.max(1, Math.min(opts.offset ?? 1, allLines.length));
|
|
@@ -200,18 +223,14 @@ export async function readArtifact(opts) {
|
|
|
200
223
|
const ext = filePath.slice(filePath.lastIndexOf('.') + 1).toLowerCase();
|
|
201
224
|
return {
|
|
202
225
|
ok: true,
|
|
226
|
+
source: underRoot(filePath, canonicalRoot) ? 'state-root' : 'cwd',
|
|
203
227
|
path: filePath,
|
|
204
228
|
offset,
|
|
205
|
-
lines: slice.map((t, i)=>({
|
|
206
|
-
number: offset + i,
|
|
207
|
-
text: t
|
|
208
|
-
})),
|
|
229
|
+
lines: slice.map((t, i) => ({ number: offset + i, text: t })),
|
|
209
230
|
totalLines: allLines.length,
|
|
210
231
|
lang: TEXT_EXT_LANG[ext] ?? 'text',
|
|
232
|
+
version: createHash('sha256').update(text).digest('hex').slice(0, 12),
|
|
211
233
|
content: slice.join('\n'),
|
|
212
|
-
windowed: allLines.length > offset - 1 + slice.length
|
|
234
|
+
windowed: allLines.length > offset - 1 + slice.length,
|
|
213
235
|
};
|
|
214
236
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
//# sourceURL=ts/capabilities/artifacts.ts
|
|
@@ -1,51 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通道健康面(channel health)——issue #106/#107/#108 编排设计
|
|
3
|
+
* (docs/design/tool-orchestration-design.md §2.2)的会话瞬态态 + 持久事件面。
|
|
4
|
+
*
|
|
5
|
+
* 两个粒度,均从既有 verdict 流派生,不新增运行时:
|
|
6
|
+
* - 会话瞬态面:进程内 Map。检索通道 verdict≠hit 时记状态
|
|
7
|
+
* (down=本会话别再试 / cooldown=节律闸冷却中),后续同意图工具结果的
|
|
8
|
+
* routing 建议(channel-registry.routingAdvice)据此排除不可用通道;
|
|
9
|
+
* 成功调用(hit)自动清除——补装 key/装完扩展后不被陈旧状态锁死。
|
|
10
|
+
* 与断路器同先例(ADR-18):进程内瞬态,不是持久资产。
|
|
11
|
+
* - 持久事件面:down/cooldown 事件 append-only 落
|
|
12
|
+
* <stateRoot>/gotry-state/channel-health.jsonl,供 doctor(CLI 独立进程,
|
|
13
|
+
* 看不到进程内 Map)显示「最近一次达限/不可用时间」。只记状态变化,
|
|
14
|
+
* 不记成功(零写放大);落盘失败不阻塞检索主路径(事实侧车同纪律)。
|
|
15
|
+
*
|
|
16
|
+
* verdict → 状态映射的唯一入口是 noteChannelVerdict:
|
|
17
|
+
* needs-setup → down(quota-or-setup) challenged → down(challenged)
|
|
18
|
+
* needs-login / needs-extension → down(同因,会话内改道;hit 即清除)
|
|
19
|
+
* cooldown → cooldown(默认 30s,会话节律闸同款)
|
|
20
|
+
* hit → 清除该通道全部状态
|
|
21
|
+
* 其余 verdict(miss/error 等)不改变通道状态——那是业务结果,不是通道健康。
|
|
22
|
+
*
|
|
23
|
+
* @module capabilities/channel-health
|
|
24
|
+
*/
|
|
25
|
+
/** 会话瞬态面(进程内;与断路器状态同生命周期先例) */
|
|
1
26
|
const session = new Map();
|
|
27
|
+
/** 默认 cooldown = 会话节律闸同款 30s */
|
|
2
28
|
export const DEFAULT_COOLDOWN_MS = 30_000;
|
|
3
29
|
export function markChannelDown(channel, reason, now = Date.now()) {
|
|
4
|
-
session.set(channel, {
|
|
5
|
-
|
|
6
|
-
reason,
|
|
7
|
-
since: now
|
|
8
|
-
});
|
|
9
|
-
return {
|
|
10
|
-
channel,
|
|
11
|
-
state: 'down',
|
|
12
|
-
reason,
|
|
13
|
-
at: new Date(now).toISOString()
|
|
14
|
-
};
|
|
30
|
+
session.set(channel, { state: 'down', reason, since: now });
|
|
31
|
+
return { channel, state: 'down', reason, at: new Date(now).toISOString() };
|
|
15
32
|
}
|
|
16
33
|
export function markChannelCooldown(channel, cooldownMs = DEFAULT_COOLDOWN_MS, now = Date.now()) {
|
|
17
|
-
session.set(channel, {
|
|
18
|
-
|
|
19
|
-
until: now + cooldownMs,
|
|
20
|
-
since: now
|
|
21
|
-
});
|
|
22
|
-
return {
|
|
23
|
-
channel,
|
|
24
|
-
state: 'cooldown',
|
|
25
|
-
at: new Date(now).toISOString()
|
|
26
|
-
};
|
|
34
|
+
session.set(channel, { state: 'cooldown', until: now + cooldownMs, since: now });
|
|
35
|
+
return { channel, state: 'cooldown', at: new Date(now).toISOString() };
|
|
27
36
|
}
|
|
37
|
+
/** 成功即清除(恢复信号:下一次成功调用自动解锁,不要求显式复位) */
|
|
28
38
|
export function clearChannel(channel) {
|
|
29
39
|
session.delete(channel);
|
|
30
40
|
}
|
|
41
|
+
/** 读通道状态;cooldown 过期惰性清除。无状态返回 undefined。 */
|
|
31
42
|
export function channelState(channel, now = Date.now()) {
|
|
32
43
|
const e = session.get(channel);
|
|
33
|
-
if (!e)
|
|
44
|
+
if (!e)
|
|
45
|
+
return undefined;
|
|
34
46
|
if (e.state === 'cooldown' && (e.until ?? 0) <= now) {
|
|
35
47
|
session.delete(channel);
|
|
36
48
|
return undefined;
|
|
37
49
|
}
|
|
38
|
-
return {
|
|
39
|
-
state: e.state,
|
|
40
|
-
reason: e.reason
|
|
41
|
-
};
|
|
50
|
+
return { state: e.state, reason: e.reason };
|
|
42
51
|
}
|
|
52
|
+
/** 测试隔离:清空全部会话态(生产路径不调) */
|
|
43
53
|
export function resetChannelHealth() {
|
|
44
54
|
session.clear();
|
|
45
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* verdict → 通道状态变化的唯一映射(工具层在拿到渠道结果后调用)。
|
|
58
|
+
* 返回落盘事件(down/cooldown 才有;hit 清除与其余 verdict 返回 undefined)。
|
|
59
|
+
*/
|
|
46
60
|
export function noteChannelVerdict(channel, verdict, opts = {}) {
|
|
47
61
|
const now = opts.now ?? Date.now();
|
|
48
|
-
switch(verdict){
|
|
62
|
+
switch (verdict) {
|
|
49
63
|
case 'hit':
|
|
50
64
|
case 'found':
|
|
51
65
|
clearChannel(channel);
|
|
@@ -62,16 +76,23 @@ export function noteChannelVerdict(channel, verdict, opts = {}) {
|
|
|
62
76
|
return undefined;
|
|
63
77
|
}
|
|
64
78
|
}
|
|
79
|
+
/** 持久事件面:append-only JSONL(<stateRoot>/gotry-state/channel-health.jsonl)。永不抛错。 */
|
|
65
80
|
export async function recordChannelEvent(stateRoot, event) {
|
|
66
81
|
try {
|
|
67
82
|
const { mkdir, appendFile } = await import('node:fs/promises');
|
|
68
83
|
const dir = `${stateRoot.replace(/\/+$/, '')}/gotry-state`;
|
|
69
|
-
await mkdir(dir, {
|
|
70
|
-
recursive: true
|
|
71
|
-
});
|
|
84
|
+
await mkdir(dir, { recursive: true });
|
|
72
85
|
await appendFile(`${dir}/channel-health.jsonl`, `${JSON.stringify(event)}\n`, 'utf-8');
|
|
73
|
-
}
|
|
86
|
+
}
|
|
87
|
+
catch { /* 证据侧车永不阻塞主路径 */ }
|
|
74
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* 读最近事件(每通道取最新一条)——doctor(CLI 独立进程)的持久面输入。
|
|
91
|
+
* 容忍坏行/空文件/缺文件;limitDays 过滤过旧事件(默认 30 天)。
|
|
92
|
+
* latest-wins:'ok' 恢复事件可超越同通道更早的 down/cooldown(外部事件接缝
|
|
93
|
+
* 第 1 段);消费方以 `state !== 'down'` 判当前健康,旧判定(`=== 'down'`)
|
|
94
|
+
* 不受影响。
|
|
95
|
+
*/
|
|
75
96
|
export async function readLatestChannelEvents(stateRoot, opts = {}) {
|
|
76
97
|
const latest = new Map();
|
|
77
98
|
try {
|
|
@@ -79,25 +100,24 @@ export async function readLatestChannelEvents(stateRoot, opts = {}) {
|
|
|
79
100
|
const raw = await readFile(`${stateRoot.replace(/\/+$/, '')}/gotry-state/channel-health.jsonl`, 'utf-8');
|
|
80
101
|
const nowMs = opts.now ?? Date.now();
|
|
81
102
|
const cutoff = nowMs - (opts.limitDays ?? 30) * 86_400_000;
|
|
82
|
-
for (const line of raw.split('\n')){
|
|
103
|
+
for (const line of raw.split('\n')) {
|
|
83
104
|
const t = line.trim();
|
|
84
|
-
if (!t)
|
|
105
|
+
if (!t)
|
|
106
|
+
continue;
|
|
85
107
|
try {
|
|
86
108
|
const ev = JSON.parse(t);
|
|
87
|
-
if (!ev || typeof ev.channel !== 'string' || typeof ev.state !== 'string')
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
'ok'
|
|
92
|
-
].includes(ev.state)) continue;
|
|
109
|
+
if (!ev || typeof ev.channel !== 'string' || typeof ev.state !== 'string')
|
|
110
|
+
continue;
|
|
111
|
+
if (!['down', 'cooldown', 'ok'].includes(ev.state))
|
|
112
|
+
continue;
|
|
93
113
|
const atMs = Date.parse(ev.at ?? '');
|
|
94
|
-
if (Number.isFinite(atMs) && atMs < cutoff)
|
|
114
|
+
if (Number.isFinite(atMs) && atMs < cutoff)
|
|
115
|
+
continue;
|
|
95
116
|
latest.set(ev.channel, ev);
|
|
96
|
-
}
|
|
117
|
+
}
|
|
118
|
+
catch { /* 坏行跳过 */ }
|
|
97
119
|
}
|
|
98
|
-
}
|
|
120
|
+
}
|
|
121
|
+
catch { /* 无文件=无事件 */ }
|
|
99
122
|
return latest;
|
|
100
123
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
//# sourceURL=ts/capabilities/channel-health.ts
|