dsh-data-domain-knowledge 0.1.0-alpha.13 → 0.1.0-alpha.15
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 +89 -56
- package/cordis.patch.yml +36 -129
- package/eval/benchmark-extraction-format.mjs +20 -0
- package/lib/client.js +3262 -4142
- package/lib/doc-page.js +1 -1
- package/lib/pages.js +1147 -364
- package/lib/types/axis-registry.d.ts +8 -1
- package/lib/types/axis-registry.d.ts.map +1 -1
- package/lib/types/axis-registry.js +62 -7
- package/lib/types/axis-registry.js.map +1 -1
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +3 -0
- package/lib/types/convert/host.js.map +1 -1
- package/lib/types/convert/ingest.d.ts +9 -0
- package/lib/types/convert/ingest.d.ts.map +1 -1
- package/lib/types/convert/ingest.js +68 -9
- package/lib/types/convert/ingest.js.map +1 -1
- package/lib/types/diagnosis/cards.d.ts +7 -3
- package/lib/types/diagnosis/cards.d.ts.map +1 -1
- package/lib/types/diagnosis/cards.js +13 -8
- package/lib/types/diagnosis/cards.js.map +1 -1
- package/lib/types/diagnosis/run.d.ts +4 -13
- package/lib/types/diagnosis/run.d.ts.map +1 -1
- package/lib/types/diagnosis/run.js +26 -16
- package/lib/types/diagnosis/run.js.map +1 -1
- package/lib/types/diagnosis/schema.js +2 -2
- package/lib/types/diagnosis/schema.js.map +1 -1
- package/lib/types/diagnosis/seed-source.d.ts +1 -1
- package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
- package/lib/types/diagnosis/seed-source.js +5 -4
- package/lib/types/diagnosis/seed-source.js.map +1 -1
- package/lib/types/diagnosis/seed.d.ts +4 -4
- package/lib/types/diagnosis/seed.d.ts.map +1 -1
- package/lib/types/diagnosis/seed.js +6 -8
- package/lib/types/diagnosis/seed.js.map +1 -1
- package/lib/types/diagnosis/store.d.ts +18 -17
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +94 -66
- package/lib/types/diagnosis/store.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +10 -16
- package/lib/types/diagnosis/types.d.ts.map +1 -1
- package/lib/types/diagnosis/types.js.map +1 -1
- package/lib/types/environment/dsh-python.d.ts +77 -0
- package/lib/types/environment/dsh-python.d.ts.map +1 -0
- package/lib/types/environment/dsh-python.js +139 -0
- package/lib/types/environment/dsh-python.js.map +1 -0
- package/lib/types/environment/install.d.ts +9 -1
- package/lib/types/environment/install.d.ts.map +1 -1
- package/lib/types/environment/install.js +149 -24
- package/lib/types/environment/install.js.map +1 -1
- package/lib/types/environment/model-catalog.d.ts +34 -2
- package/lib/types/environment/model-catalog.d.ts.map +1 -1
- package/lib/types/environment/model-catalog.js +40 -2
- package/lib/types/environment/model-catalog.js.map +1 -1
- package/lib/types/environment/service.d.ts +13 -3
- package/lib/types/environment/service.d.ts.map +1 -1
- package/lib/types/environment/service.js +127 -23
- package/lib/types/environment/service.js.map +1 -1
- package/lib/types/environment/types.d.ts +9 -0
- package/lib/types/environment/types.d.ts.map +1 -1
- package/lib/types/events.d.ts +2 -0
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/events.js +13 -0
- package/lib/types/events.js.map +1 -1
- package/lib/types/extract-contract.d.ts +1 -1
- package/lib/types/extract-contract.d.ts.map +1 -1
- package/lib/types/extract-contract.js +34 -31
- package/lib/types/extract-contract.js.map +1 -1
- package/lib/types/extract-model.d.ts +2 -0
- package/lib/types/extract-model.d.ts.map +1 -1
- package/lib/types/extract-model.js.map +1 -1
- package/lib/types/extract-table-contract.d.ts +3 -0
- package/lib/types/extract-table-contract.d.ts.map +1 -0
- package/lib/types/extract-table-contract.js +30 -0
- package/lib/types/extract-table-contract.js.map +1 -0
- package/lib/types/extract-table.d.ts +8 -0
- package/lib/types/extract-table.d.ts.map +1 -0
- package/lib/types/extract-table.js +151 -0
- package/lib/types/extract-table.js.map +1 -0
- package/lib/types/extraction-runs.d.ts +30 -6
- package/lib/types/extraction-runs.d.ts.map +1 -1
- package/lib/types/extraction-runs.js +69 -8
- package/lib/types/extraction-runs.js.map +1 -1
- package/lib/types/fs-atomic.d.ts +0 -7
- package/lib/types/fs-atomic.d.ts.map +1 -1
- package/lib/types/fs-atomic.js +17 -2
- package/lib/types/fs-atomic.js.map +1 -1
- package/lib/types/images.d.ts +1 -1
- package/lib/types/images.d.ts.map +1 -1
- package/lib/types/images.js +2 -2
- package/lib/types/images.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +232 -50
- package/lib/types/index.js.map +1 -1
- package/lib/types/intent/scope.d.ts.map +1 -1
- package/lib/types/intent/scope.js +1 -0
- package/lib/types/intent/scope.js.map +1 -1
- package/lib/types/intent/tools.d.ts +19 -0
- package/lib/types/intent/tools.d.ts.map +1 -1
- package/lib/types/intent/tools.js +37 -1
- package/lib/types/intent/tools.js.map +1 -1
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +5 -16
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/knowledge.d.ts +3 -0
- package/lib/types/knowledge.d.ts.map +1 -1
- package/lib/types/knowledge.js +21 -5
- package/lib/types/knowledge.js.map +1 -1
- package/lib/types/materialize-snapshot.d.ts +49 -0
- package/lib/types/materialize-snapshot.d.ts.map +1 -0
- package/lib/types/materialize-snapshot.js +2 -0
- package/lib/types/materialize-snapshot.js.map +1 -0
- package/lib/types/model-text.d.ts +1 -1
- package/lib/types/model-text.d.ts.map +1 -1
- package/lib/types/model-text.js +5 -4
- package/lib/types/model-text.js.map +1 -1
- package/lib/types/oplog.d.ts.map +1 -1
- package/lib/types/oplog.js +7 -0
- package/lib/types/oplog.js.map +1 -1
- package/lib/types/pipeline/engine.d.ts +60 -0
- package/lib/types/pipeline/engine.d.ts.map +1 -0
- package/lib/types/pipeline/engine.js +338 -0
- package/lib/types/pipeline/engine.js.map +1 -0
- package/lib/types/pipeline/store.d.ts +73 -0
- package/lib/types/pipeline/store.d.ts.map +1 -0
- package/lib/types/pipeline/store.js +143 -0
- package/lib/types/pipeline/store.js.map +1 -0
- package/lib/types/pipeline/types.d.ts +101 -0
- package/lib/types/pipeline/types.d.ts.map +1 -0
- package/lib/types/pipeline/types.js +5 -0
- package/lib/types/pipeline/types.js.map +1 -0
- package/lib/types/present.d.ts +2 -0
- package/lib/types/present.d.ts.map +1 -1
- package/lib/types/present.js +9 -4
- package/lib/types/present.js.map +1 -1
- package/lib/types/retention.d.ts +39 -0
- package/lib/types/retention.d.ts.map +1 -0
- package/lib/types/retention.js +42 -0
- package/lib/types/retention.js.map +1 -0
- package/lib/types/runtime.d.ts +115 -5
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +862 -132
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/source-evidence.d.ts +9 -1
- package/lib/types/source-evidence.d.ts.map +1 -1
- package/lib/types/source-evidence.js +13 -5
- package/lib/types/source-evidence.js.map +1 -1
- package/lib/types/store/axis-references.js +3 -3
- package/lib/types/store/axis-references.js.map +1 -1
- package/lib/types/store/derived.d.ts +1 -1
- package/lib/types/store/derived.d.ts.map +1 -1
- package/lib/types/store/derived.js +41 -7
- package/lib/types/store/derived.js.map +1 -1
- package/lib/types/store/knowledge-db.d.ts +18 -0
- package/lib/types/store/knowledge-db.d.ts.map +1 -1
- package/lib/types/store/knowledge-db.js +111 -6
- package/lib/types/store/knowledge-db.js.map +1 -1
- package/lib/types/store/migrate.js +1 -1
- package/lib/types/store/migrate.js.map +1 -1
- package/lib/types/trajectory-types.d.ts +172 -0
- package/lib/types/trajectory-types.d.ts.map +1 -0
- package/lib/types/trajectory-types.js +36 -0
- package/lib/types/trajectory-types.js.map +1 -0
- package/lib/types/types.d.ts +3 -4
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-finalize.d.ts +16 -0
- package/lib/types/wiki-finalize.d.ts.map +1 -1
- package/lib/types/wiki-finalize.js +26 -0
- package/lib/types/wiki-finalize.js.map +1 -1
- package/lib/types/wiki-materialize.d.ts.map +1 -1
- package/lib/types/wiki-materialize.js +3 -15
- package/lib/types/wiki-materialize.js.map +1 -1
- package/lib/types/wiki-trial-types.d.ts +3 -0
- package/lib/types/wiki-trial-types.d.ts.map +1 -1
- package/lib/types/wiki-trial.d.ts +2 -0
- package/lib/types/wiki-trial.d.ts.map +1 -1
- package/lib/types/wiki-trial.js +31 -7
- package/lib/types/wiki-trial.js.map +1 -1
- package/package.json +6 -3
- package/preset/tita-data-agent/agent.cordis.yml +36 -129
- package/src/axis-registry.ts +57 -9
- package/src/client/AttributionEventGuide.tsx +10 -9
- package/src/client/CompileDialogs.tsx +10 -2
- package/src/client/EnvironmentSetup.tsx +76 -16
- package/src/client/ExpansionGraph.tsx +13 -7
- package/src/client/ExtractionTasksPage.tsx +543 -156
- package/src/client/ImportFilesDialog.tsx +2 -13
- package/src/client/IntentTab.tsx +3 -1
- package/src/client/KnowledgeBaseTab.tsx +139 -122
- package/src/client/KnowledgeLayerSection.tsx +19 -2
- package/src/client/KnowledgePanel.tsx +17 -3
- package/src/client/ListPager.tsx +15 -5
- package/src/client/MaterializeTrajectoryView.tsx +124 -0
- package/src/client/NodeSettings.tsx +90 -25
- package/src/client/NormalizeTrajectoryView.tsx +136 -0
- package/src/client/PipelineNotice.tsx +26 -0
- package/src/client/RecallPanel.tsx +55 -25
- package/src/client/TabGuide.tsx +4 -4
- package/src/client/TrialReportView.tsx +61 -16
- package/src/client/TypeDefinitionDrawer.tsx +33 -9
- package/src/client/WikiCardPreview.tsx +43 -10
- package/src/client/WikiFlowPanel.tsx +4 -4
- package/src/client/architecture.tsx +1 -1
- package/src/client/attribution/AttributionTab.tsx +37 -36
- package/src/client/attribution/CaseView.tsx +3 -1
- package/src/client/attribution/EventView.tsx +14 -4
- package/src/client/attribution/RunView.tsx +2 -2
- package/src/client/attribution/ViewSwitch.tsx +20 -5
- package/src/client/attribution/graph-options.ts +38 -31
- package/src/client/attribution/model.ts +16 -9
- package/src/client/graph/palette.ts +165 -0
- package/src/client/pages.css +224 -28
- package/src/client/pages.tsx +33 -11
- package/src/client/styles.css +61 -6
- package/src/client/wiki-trial.ts +30 -1
- package/src/convert/host.ts +3 -0
- package/src/convert/ingest.ts +73 -9
- package/src/diagnosis/cards.ts +13 -8
- package/src/diagnosis/run.ts +27 -17
- package/src/diagnosis/schema.ts +2 -2
- package/src/diagnosis/seed-source.ts +5 -4
- package/src/diagnosis/seed.ts +6 -8
- package/src/diagnosis/store.ts +99 -69
- package/src/diagnosis/types.ts +10 -16
- package/src/environment/dsh-python.ts +156 -0
- package/src/environment/install.ts +134 -19
- package/src/environment/model-catalog.ts +45 -3
- package/src/environment/service.ts +111 -23
- package/src/environment/types.ts +6 -0
- package/src/events.ts +12 -0
- package/src/extract-contract.ts +6 -3
- package/src/extract-model.ts +2 -0
- package/src/extract-table-contract.ts +29 -0
- package/src/extract-table.ts +126 -0
- package/src/extraction-runs.ts +70 -11
- package/src/fs-atomic.ts +19 -2
- package/src/images.ts +2 -2
- package/src/index.ts +207 -72
- package/src/intent/scope.ts +1 -0
- package/src/intent/tools.ts +38 -1
- package/src/knowledge-bases.ts +4 -12
- package/src/knowledge.ts +24 -4
- package/src/materialize-snapshot.ts +46 -0
- package/src/model-text.ts +5 -3
- package/src/oplog.ts +7 -0
- package/src/pipeline/engine.ts +312 -0
- package/src/pipeline/store.ts +147 -0
- package/src/pipeline/types.ts +92 -0
- package/src/present.ts +10 -4
- package/src/retention.ts +76 -0
- package/src/runtime.ts +853 -131
- package/src/source-evidence.ts +12 -5
- package/src/store/axis-references.ts +3 -3
- package/src/store/derived.ts +40 -7
- package/src/store/knowledge-db.ts +111 -5
- package/src/store/migrate.ts +1 -1
- package/src/trajectory-types.ts +142 -0
- package/src/types.ts +3 -4
- package/src/wiki-finalize.ts +28 -0
- package/src/wiki-materialize.ts +3 -14
- package/src/wiki-trial-types.ts +3 -0
- package/src/wiki-trial.ts +26 -7
- package/src/client/convert/ConvertPanel.tsx +0 -583
- package/src/client/convert/styles.css +0 -138
- package/src/client/convert/styles.ts +0 -49
package/src/diagnosis/store.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
cardFromRecord, cardsToGraph, CARD_EDGE_KINDS, graphToCards, parseCard, serializeCard,
|
|
8
8
|
RUN_EDGE_KINDS, type DiagnosisCard,
|
|
9
9
|
} from './cards.js'
|
|
10
|
-
import {
|
|
10
|
+
import { caseViewOfRun, parseRunSource, renderRunSource, runReferenceProblems, runToGraph } from './run.js'
|
|
11
11
|
import type {
|
|
12
12
|
AttributionCase, AttributionEdge, AttributionEvent, AttributionGraphData, AttributionNode, AttributionNodeKind, DiagnosisRunRecord,
|
|
13
13
|
} from './types.js'
|
|
@@ -22,7 +22,7 @@ import type { SchemaType } from '../types.js'
|
|
|
22
22
|
* | 目录 | 内容 | 谁是真相 |
|
|
23
23
|
* | --- | --- | --- |
|
|
24
24
|
* | `01_source_raw/` | 手册、复盘与排查原文 | SourceRaw 的唯一真相 |
|
|
25
|
-
* | `02_wiki/diagnosis/` |
|
|
25
|
+
* | `02_wiki/diagnosis/` | 历史诊断卡片 | 仅为旧数据兼容读取;新排查不再写 diagnosis/case 卡片 |
|
|
26
26
|
*
|
|
27
27
|
* `05_diagnosis/` 在 v1.1 撤销了(0021 §0.3)。它同时装着两类东西 —— 「一次排查的产物」
|
|
28
28
|
* (源头)与「五类模板卡」(配置)—— 一个编号段混装两种性质,`rebuild --only` 的语义
|
|
@@ -58,8 +58,8 @@ const LEGACY_RUN_SHARDS: Record<string, AttributionNodeKind> = {
|
|
|
58
58
|
'causes.jsonl': 'cause',
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
export type DiagnosisSource = 'cards' | 'jsonl' | 'empty'
|
|
61
|
+
/** 读取来源,界面据此说明当前展示的是卡片、记录、旧快照还是空库。 */
|
|
62
|
+
export type DiagnosisSource = 'cards' | 'records' | 'jsonl' | 'empty'
|
|
63
63
|
|
|
64
64
|
export interface DiagnosisSnapshot {
|
|
65
65
|
graph: AttributionGraphData
|
|
@@ -87,7 +87,7 @@ export interface DiagnosisSnapshot {
|
|
|
87
87
|
* `loadSources` 是 v1.1 新增的:一次排查的产物现在是一条 `agent_diagnosis_case`
|
|
88
88
|
* 的 SourceRaw,归因图读的是它,而不是自造的 jsonl。
|
|
89
89
|
*/
|
|
90
|
-
type DiagnosisHost = Pick<KnowledgeRuntime, 'rootDir' | 'addSource' | 'writeCard' | 'loadSources' | 'loadCards' | 'loadEvents' | 'readCard'>
|
|
90
|
+
type DiagnosisHost = Pick<KnowledgeRuntime, 'rootDir' | 'addSource' | 'writeCard' | 'loadSources' | 'loadCards' | 'loadEvents' | 'readCard' | 'knowledgeStoreAvailable'>
|
|
91
91
|
|
|
92
92
|
export class DiagnosisStore {
|
|
93
93
|
constructor(private readonly host: DiagnosisHost) {}
|
|
@@ -113,26 +113,53 @@ export class DiagnosisStore {
|
|
|
113
113
|
* 造出「设置里改了、磁盘上那份没改」的第二真源。
|
|
114
114
|
*/
|
|
115
115
|
async ensure(): Promise<{ seeded: boolean; migrated: boolean; problems: string[] }> {
|
|
116
|
+
/**
|
|
117
|
+
* **「读不到」不等于「空」**(0070)。
|
|
118
|
+
*
|
|
119
|
+
* `readCards()` 在存储不可用时返回空数组,于是「node:sqlite 连不上 / 属主标记不符 /
|
|
120
|
+
* 权限不对」会被读成「这个库什么都还没有」,接着走下面的旧格式迁移 —— 那是一次
|
|
121
|
+
* **写**(会写入内置种子来源与几十张卡)。一次可恢复的读故障就这样变成不可逆的污染。
|
|
122
|
+
* 所以先问一句存储到底在不在,不在就什么都不做,并如实报出来。
|
|
123
|
+
*/
|
|
124
|
+
if (!await this.host.knowledgeStoreAvailable()) {
|
|
125
|
+
return { seeded: false, migrated: false, problems: ['知识存储当前不可用,已跳过内置示例与旧格式迁移;请先解决存储问题再打开本页。'] }
|
|
126
|
+
}
|
|
116
127
|
if ((await this.readCards()).length > 0) return { seeded: false, migrated: false, problems: [] }
|
|
117
128
|
if ((await this.legacyShardFiles()).length > 0) return await this.migrate()
|
|
118
129
|
return { seeded: false, migrated: false, problems: [] }
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
/**
|
|
122
|
-
* 载入**内置示例**:一份排查手册 + 三篇复盘 + 三次排查日志 → SourceRaw
|
|
133
|
+
* 载入**内置示例**:一份排查手册 + 三篇复盘 + 三次排查日志 → SourceRaw、可复用诊断卡与只读排查视图。
|
|
123
134
|
*
|
|
124
135
|
* 只有用户明确点了「载入内置示例」才走这里(`ensure()` 不再自动调用)。幂等:
|
|
125
136
|
* `addSource` 按 `externalId`、`writeCard` 按 entityId,点两次不会写出第二份。
|
|
126
137
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* `confirmedBy = user:load-sample`,然后照常走 `writeCard`。这样做的两个好处:
|
|
130
|
-
* 待办区里能看到这批卡片是怎么来的;而「未确认不落 `02_wiki/`」这条规则没有被开一个
|
|
131
|
-
* 后门 —— 例外是**确认者的身份**,不是门的缺失。
|
|
138
|
+
* 示例里的可复用诊断 Knowledge 是预先整理好的演示数据,直接写入当前知识库。
|
|
139
|
+
* 三条排查记录作为 SourceRaw 写入,并由读取层派生只读 Case 视图;不创建 `diagnosis/case` WikiCard。
|
|
132
140
|
*/
|
|
133
141
|
async loadSample(): Promise<{ seeded: boolean; migrated: boolean; problems: string[] }> {
|
|
142
|
+
/**
|
|
143
|
+
* 载入示例是一次**写**,所以门要在服务端,不能只在界面上。
|
|
144
|
+
*
|
|
145
|
+
* `canLoadSample` 原来只出现在 `snapshot()` 的返回值里,用来决定那枚按钮显不显示 ——
|
|
146
|
+
* 而 `POST /diagnosis/sample` 直接调这里。任何非界面的调用方(脚本、旧标签页、
|
|
147
|
+
* 以后的 runbook)打一次,就能在一个已经有真实知识的库上注入一整套演示数据,
|
|
148
|
+
* 库里于是有两份互相矛盾的知识,而这正是本文件开头要防的事。
|
|
149
|
+
*
|
|
150
|
+
* **不给覆盖开关**:曾经加过一个 `force`,但它没有任何调用方(路由按无参调用),
|
|
151
|
+
* 而且「往有真实知识的库里再塞一套演示」本来就不该是一个可选项 —— 那正是这条门要防的。
|
|
152
|
+
*/
|
|
153
|
+
const allowed = await this.sampleAllowed()
|
|
154
|
+
if (!allowed.ok) {
|
|
155
|
+
return { seeded: false, migrated: false, problems: [`这个知识库里已经有内容(${allowed.reason}),不再载入内置示例。`] }
|
|
156
|
+
}
|
|
134
157
|
const { problems: sourceProblems, refs } = await this.writeSources(seedSources())
|
|
135
|
-
const cards = withSourceRefs(
|
|
158
|
+
const cards = withSourceRefs(
|
|
159
|
+
graphToCards(SEED_GRAPH),
|
|
160
|
+
refs,
|
|
161
|
+
SEED_CASES,
|
|
162
|
+
)
|
|
136
163
|
const problems = [...sourceProblems, ...await this.writeCards(cards, 'user:load-sample')]
|
|
137
164
|
return { seeded: true, migrated: false, problems }
|
|
138
165
|
}
|
|
@@ -142,44 +169,55 @@ export class DiagnosisStore {
|
|
|
142
169
|
return this.readEvents()
|
|
143
170
|
}
|
|
144
171
|
|
|
172
|
+
/**
|
|
173
|
+
* 「能不能载入内置示例」的**唯一**判据 —— 只看四个计数。
|
|
174
|
+
*
|
|
175
|
+
* 抽出来是因为它有两个消费者(`snapshot()` 的 `canLoadSample` 与 `loadSample()` 的门),
|
|
176
|
+
* 各写一份的话,界面说「可以」而服务端说「不行」只是时间问题。判据与**读取**分开:
|
|
177
|
+
* 两边各自的读取是必要的(一个已经拿到了卡片/Event,一个还没读),规则不必要各写一遍。
|
|
178
|
+
*/
|
|
179
|
+
private static sampleGate(state: { cards: number; events: number; runs: number; shards: number }): { ok: boolean; reason?: string } {
|
|
180
|
+
if (state.cards > 0) return { ok: false, reason: '已经有卡片' }
|
|
181
|
+
if (state.events > 0) return { ok: false, reason: '已经有 Event' }
|
|
182
|
+
if (state.runs > 0) return { ok: false, reason: '已经有排查记录' }
|
|
183
|
+
if (state.shards > 0) return { ok: false, reason: '还有旧格式的 05_diagnosis 分片等待迁移' }
|
|
184
|
+
return { ok: true }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private async sampleAllowed(): Promise<{ ok: boolean; reason?: string }> {
|
|
188
|
+
return DiagnosisStore.sampleGate({
|
|
189
|
+
cards: (await this.readCards()).length,
|
|
190
|
+
events: (await this.readEvents()).length,
|
|
191
|
+
runs: (await this.readRuns()).records.length,
|
|
192
|
+
shards: (await this.legacyShardFiles()).length,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
145
196
|
async snapshot(): Promise<DiagnosisSnapshot> {
|
|
146
197
|
const { seeded, migrated, problems } = await this.ensure()
|
|
147
198
|
const cards = await this.readCards()
|
|
148
199
|
const events = await this.readEvents()
|
|
200
|
+
const baseGraph = cards.length > 0 ? { nodes: [], edges: [] } : await this.readLegacyGraph()
|
|
201
|
+
const baseCases = cards.length > 0 ? [] : await this.readLegacyCases()
|
|
202
|
+
const built = await this.buildGraph(cards, baseGraph, baseCases)
|
|
203
|
+
const runRecords = (await this.readRuns()).records
|
|
149
204
|
/**
|
|
150
|
-
* 「什么都没有」=
|
|
205
|
+
* 「什么都没有」= 没有卡片、Event、排查日志和旧 jsonl。它是**可以载入内置示例**的唯一条件:
|
|
151
206
|
* 已经有知识(不管哪一代形态)时不该再往里塞示例 —— 那会变成第二份互相矛盾的知识。
|
|
152
207
|
*/
|
|
153
|
-
const canLoadSample =
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const built = await this.buildGraph(cards)
|
|
157
|
-
return {
|
|
158
|
-
graph: built.graph,
|
|
159
|
-
cases: built.cases,
|
|
160
|
-
events,
|
|
161
|
-
issues: validateDiagnosis(built.graph),
|
|
162
|
-
seeded,
|
|
163
|
-
migrated,
|
|
164
|
-
source: 'cards',
|
|
165
|
-
canLoadSample: false,
|
|
166
|
-
problems: [...problems, ...built.problems],
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 还没有卡片:旧 jsonl 就是当前可读的来源;两份都没有就是一个空库。
|
|
171
|
-
const graph = await this.readLegacyGraph()
|
|
172
|
-
const cases = await this.readLegacyCases()
|
|
208
|
+
const canLoadSample = DiagnosisStore.sampleGate({
|
|
209
|
+
cards: cards.length, events: events.length, runs: runRecords.length, shards: (await this.legacyShardFiles()).length,
|
|
210
|
+
}).ok
|
|
173
211
|
return {
|
|
174
|
-
graph,
|
|
175
|
-
cases,
|
|
212
|
+
graph: built.graph,
|
|
213
|
+
cases: built.cases,
|
|
176
214
|
events,
|
|
177
|
-
issues: validateDiagnosis(graph),
|
|
215
|
+
issues: validateDiagnosis(built.graph),
|
|
178
216
|
seeded,
|
|
179
217
|
migrated,
|
|
180
|
-
source: canLoadSample ? 'empty' :
|
|
218
|
+
source: canLoadSample ? 'empty' : cards.length > 0 ? 'cards' : events.length > 0 || runRecords.length > 0 ? 'records' : 'jsonl',
|
|
181
219
|
canLoadSample,
|
|
182
|
-
problems,
|
|
220
|
+
problems: [...problems, ...built.problems],
|
|
183
221
|
}
|
|
184
222
|
}
|
|
185
223
|
|
|
@@ -190,21 +228,27 @@ export class DiagnosisStore {
|
|
|
190
228
|
* 与四条执行边全部来自 `agent_diagnosis_case` 的 SourceRaw。合成一张图是**读**的时候
|
|
191
229
|
* 做的,不是写的时候 —— 写的时候合成,等于让一次执行的结果变成可复用的知识。
|
|
192
230
|
*/
|
|
193
|
-
private async buildGraph(cards: DiagnosisCard[]): Promise<{ graph: AttributionGraphData; cases: AttributionCase[]; problems: string[] }> {
|
|
231
|
+
private async buildGraph(cards: DiagnosisCard[], baseGraph: AttributionGraphData = { nodes: [], edges: [] }, baseCases: AttributionCase[] = []): Promise<{ graph: AttributionGraphData; cases: AttributionCase[]; problems: string[] }> {
|
|
194
232
|
const compiled = cardsToGraph(cards)
|
|
195
233
|
const problems = [...compiled.problems]
|
|
196
|
-
const nodes: AttributionNode[] = [...compiled.graph.nodes]
|
|
197
|
-
const edges: AttributionEdge[] = [...compiled.graph.edges]
|
|
234
|
+
const nodes: AttributionNode[] = [...baseGraph.nodes, ...compiled.graph.nodes]
|
|
235
|
+
const edges: AttributionEdge[] = [...baseGraph.edges, ...compiled.graph.edges]
|
|
236
|
+
const knownCardNodes = [...baseGraph.nodes, ...compiled.graph.nodes]
|
|
237
|
+
const cases = new Map<string, AttributionCase>()
|
|
238
|
+
for (const item of [...baseCases, ...compiled.cases]) cases.set(item.id, item)
|
|
198
239
|
|
|
199
240
|
const runs = await this.readRuns()
|
|
200
241
|
problems.push(...runs.problems)
|
|
201
242
|
for (const record of runs.records) {
|
|
202
243
|
const run = runToGraph(record)
|
|
203
244
|
// 引用完整性要**连这次排查自己产出的 id 一起算**:路径里的 `E1` / `C1` 正是它写下的。
|
|
204
|
-
const known = new Set([...
|
|
245
|
+
const known = new Set([...knownCardNodes.map(node => node.id), ...run.nodes.map(node => node.id)])
|
|
205
246
|
problems.push(...run.problems, ...runReferenceProblems(record, known))
|
|
206
247
|
nodes.push(...run.nodes)
|
|
207
248
|
edges.push(...run.edges)
|
|
249
|
+
const symptomTitle = knownCardNodes.find(node => node.id === record.symptomId)?.title ?? record.symptomId
|
|
250
|
+
const view = caseViewOfRun(record, symptomTitle)
|
|
251
|
+
cases.set(view.id, view)
|
|
208
252
|
}
|
|
209
253
|
|
|
210
254
|
/**
|
|
@@ -219,7 +263,7 @@ export class DiagnosisStore {
|
|
|
219
263
|
nodes.push(...legacyRun.nodes)
|
|
220
264
|
edges.push(...legacyRun.edges)
|
|
221
265
|
|
|
222
|
-
return { graph: dedupe({ nodes, edges }, problems), cases:
|
|
266
|
+
return { graph: dedupe({ nodes, edges }, problems), cases: [...cases.values()], problems }
|
|
223
267
|
}
|
|
224
268
|
|
|
225
269
|
/**
|
|
@@ -247,14 +291,15 @@ export class DiagnosisStore {
|
|
|
247
291
|
|
|
248
292
|
async readGraph(): Promise<AttributionGraphData> {
|
|
249
293
|
const cards = await this.readCards()
|
|
250
|
-
|
|
251
|
-
return await this.
|
|
294
|
+
const baseGraph = cards.length > 0 ? { nodes: [], edges: [] } : await this.readLegacyGraph()
|
|
295
|
+
return (await this.buildGraph(cards, baseGraph)).graph
|
|
252
296
|
}
|
|
253
297
|
|
|
254
298
|
async readCases(): Promise<AttributionCase[]> {
|
|
255
299
|
const cards = await this.readCards()
|
|
256
|
-
|
|
257
|
-
|
|
300
|
+
const baseGraph = cards.length > 0 ? { nodes: [], edges: [] } : await this.readLegacyGraph()
|
|
301
|
+
const baseCases = cards.length > 0 ? [] : await this.readLegacyCases()
|
|
302
|
+
return (await this.buildGraph(cards, baseGraph, baseCases)).cases
|
|
258
303
|
}
|
|
259
304
|
|
|
260
305
|
/* ────────────────────────────────────────────── 卡片:读写 ── */
|
|
@@ -326,15 +371,11 @@ export class DiagnosisStore {
|
|
|
326
371
|
}
|
|
327
372
|
|
|
328
373
|
/**
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
* 一次排查的价值就在于「当时是怎么走的」,让它停在待办队列里等于把经验丢掉。
|
|
333
|
-
* 卡片会被标成 `unreviewed`,人在「知识库 → Wiki」里 review 它。
|
|
334
|
-
*
|
|
335
|
-
* 幂等:`externalId` = runId,同一次排查重跑不会写出第二条来源;卡片按 entityId 写入。
|
|
374
|
+
* 一次排查结束只写一条 SourceRaw。案例视图从这条记录派生;Knowledge / Event 的抽取与归一
|
|
375
|
+
* 仍由知识处理阶段执行,避免把同一份排查过程再写成另一张 WikiCard。
|
|
376
|
+
* 幂等:`externalId` = runId,同一次排查重跑不会写出第二条来源。
|
|
336
377
|
*/
|
|
337
|
-
async recordRun(record: DiagnosisRunRecord): Promise<{ sourceId?: string;
|
|
378
|
+
async recordRun(record: DiagnosisRunRecord): Promise<{ sourceId?: string; problems: string[] }> {
|
|
338
379
|
const problems: string[] = []
|
|
339
380
|
const added = await this.host.addSource({
|
|
340
381
|
title: `AI 诊断日志 · ${record.symptomId} · ${record.runId}`,
|
|
@@ -345,18 +386,7 @@ export class DiagnosisStore {
|
|
|
345
386
|
if (added.ok === false) return { problems: [`诊断日志 ${record.runId} 写入失败:${added.error.code} ${added.error.message}`] }
|
|
346
387
|
const sourceId = added.data.sourceId
|
|
347
388
|
|
|
348
|
-
|
|
349
|
-
const draft = caseCardOf(record, symptomTitle)
|
|
350
|
-
const written = await this.host.writeCard({
|
|
351
|
-
entityType: draft.entityType as SchemaType,
|
|
352
|
-
entityId: draft.entityId,
|
|
353
|
-
title: draft.title,
|
|
354
|
-
content: renderCardBody(draft),
|
|
355
|
-
sourceRefs: [sourceId],
|
|
356
|
-
status: 'unreviewed',
|
|
357
|
-
})
|
|
358
|
-
if (written.ok === false) problems.push(`案例卡片 ${draft.entityId} 写入失败:${written.error.code} ${written.error.message}`)
|
|
359
|
-
return { sourceId, ...(written.ok === false ? {} : { entityId: draft.entityId }), problems }
|
|
389
|
+
return { sourceId, problems }
|
|
360
390
|
}
|
|
361
391
|
|
|
362
392
|
/* ────────────────────────────────────────────── 旧知识根迁移 ── */
|
|
@@ -499,8 +529,8 @@ export function renderCardBody(card: DiagnosisCard): string {
|
|
|
499
529
|
/**
|
|
500
530
|
* 给卡片挂出处。
|
|
501
531
|
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
532
|
+
* 规则是**数据驱动的**,不是凭印象分的:可复用诊断卡优先引用第一篇走过该节点的复盘;
|
|
533
|
+
* 没有经过复盘的节点引用手册。历史 Case 卡仍由旧数据读取,不在新示例里生成。
|
|
504
534
|
*/
|
|
505
535
|
function withSourceRefs(
|
|
506
536
|
cards: DiagnosisCard[],
|
package/src/diagnosis/types.ts
CHANGED
|
@@ -15,7 +15,8 @@ import type { ProblemClassId } from './problem-classes.js'
|
|
|
15
15
|
export type NodeLayer = 'type' | 'reference' | 'instance'
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* 九类归因图节点。当前 Knowledge 节点可对应 WikiCard;`case` 是排查记录视图,
|
|
19
|
+
* `asset` 与执行产物也不对应 WikiCard。历史 Case WikiCard 仅为兼容数据。
|
|
19
20
|
*
|
|
20
21
|
* **两个名字用的是下划线**(`check_point` / `failure_reason`),卡片类型用连字符
|
|
21
22
|
* (`diagnosis/check-point` / `diagnosis/failure-reason`):kind 是代码里的标识符,
|
|
@@ -187,18 +188,9 @@ export interface AttributionGraphData {
|
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* (`cardsToGraph()` 从卡片把它算出来,`/diagnosis/graph` 把它发给界面),
|
|
194
|
-
* 而示例只是**一份**案例的取值来源。视图要渲染「服务端给的案例」,
|
|
195
|
-
* 那它 import 的形状就不该从示例模块里拿。
|
|
196
|
-
*
|
|
197
|
-
* 两段各有明确消费者,不能合并:
|
|
198
|
-
* - `sections` 是给人读、给向量召回的散文(可 diff、可评审、可追溯);
|
|
199
|
-
* - `path` 是给图和剪枝用的结构(有序、可判真假、带被否定的分支)。
|
|
200
|
-
*
|
|
201
|
-
* 序和枚举一旦写进散文,归并算法就再也读不到它们了 —— 所以路径单独成段。
|
|
191
|
+
* Case 是一次排查记录的只读展示投影,不是持久化实体。
|
|
192
|
+
* `sections` 供人阅读,`path` 保留检查顺序与被否定的分支;来源是 SourceRaw 运行记录。
|
|
193
|
+
* 历史 `diagnosis/case` WikiCard 仍可读取,但新的运行与示例载入不再生成它。
|
|
202
194
|
*/
|
|
203
195
|
export interface CaseSection {
|
|
204
196
|
heading: string
|
|
@@ -223,9 +215,11 @@ export interface AttributionCase {
|
|
|
223
215
|
/** 误导段:最贵的那条知识。没有它,下次还会有人在同一个假设上烧掉四十分钟。 */
|
|
224
216
|
misleading?: { nodeId: string; label: string; wastedMinutes: number; why: string }
|
|
225
217
|
rootCause: string
|
|
218
|
+
/** 排查记录里的根因确认状态;旧案例卡片缺省时按已有内容展示。 */
|
|
219
|
+
rootCauseStatus?: 'confirmed' | 'unconfirmed'
|
|
226
220
|
}
|
|
227
221
|
|
|
228
|
-
/**
|
|
222
|
+
/** 从独立 Event 实体读取的一次具体发生,供智能归因的事件视图展示。 */
|
|
229
223
|
export interface AttributionEvent {
|
|
230
224
|
id: string
|
|
231
225
|
title: string
|
|
@@ -323,8 +317,8 @@ export interface DiagnosisRunCause {
|
|
|
323
317
|
/**
|
|
324
318
|
* 一次排查的完整记录。落到 SourceRaw 正文里的 ```json diagnosis-run 块。
|
|
325
319
|
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
320
|
+
* 它是 Agent 跑完一圈留下的原始记录。Case 视图从这条记录派生;可复用 Knowledge 与 Event
|
|
321
|
+
* 由独立抽取和归一阶段生成,不在这条记录写成第二张卡片。
|
|
328
322
|
*/
|
|
329
323
|
export interface DiagnosisRunRecord {
|
|
330
324
|
/** 本次排查的 runId。同时用作 SourceRaw 的 `externalId`,重跑不产生第二条记录。 */
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH 自带 Python 的落点(2026-10-18)。
|
|
3
|
+
*
|
|
4
|
+
* 桌面端把 Python 3.12 **打在自己的运行时里**:
|
|
5
|
+
* `<App>/Contents/Resources/runtime/primary-runtime/dependencies/python`(清单见同目录
|
|
6
|
+
* `runtime.json` 的 `python` 字段)。但这一层没把位置告诉插件 —— 宿主不导出 `DSH_PYTHON`,
|
|
7
|
+
* 于是「一键准备」的默认值 `python3` 落到了系统那个 3.9(macOS 命令行工具自带的正是 3.9),
|
|
8
|
+
* 第一步就报「Python 版本不受支持」,而机器上明明有 3.12。
|
|
9
|
+
*
|
|
10
|
+
* 所以这里自己找,顺序是:
|
|
11
|
+
*
|
|
12
|
+
* 1. **`DSH_PYTHON`** —— 显式覆盖永远是第一位的(文档里承诺的自定义入口)。
|
|
13
|
+
* 2. **DSH 自带的那一个** —— 从宿主告诉我们的两个锚点推:Electron 的 `resourcesPath`,
|
|
14
|
+
* 以及 `process.execPath`(桌面端是 `Contents/MacOS/<App>`,`ELECTRON_RUN_AS_NODE` 下
|
|
15
|
+
* 它就是运行时用的那个可执行文件)。两者都会向上找几级,因为包装脚本与真实二进制的
|
|
16
|
+
* 层级不一定一样。
|
|
17
|
+
* 3. **PATH 上的 `python3`** —— 都没有才退回。
|
|
18
|
+
*
|
|
19
|
+
* ## 这里只回答「哪个路径最可能是它」,不回答「它是哪个版本」
|
|
20
|
+
*
|
|
21
|
+
* 版本判断必须由准备阶段做(它要跑一次 `sys.version_info`,并把结论写进安装日志)。
|
|
22
|
+
* 在这里再探测一遍等于把同步可判的事情做成两次异步调用,而且拿到版本也不知道该报给谁。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { existsSync } from 'node:fs'
|
|
26
|
+
import { posix, win32 } from 'node:path'
|
|
27
|
+
import { runProcess } from './process.js'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 按**目标平台**拼路径,不按跑这段代码的机器。
|
|
31
|
+
*
|
|
32
|
+
* 生产里两者永远一致(Windows 上 `process.platform` 就是 `win32`),但这里允许注入平台 ——
|
|
33
|
+
* 而 POSIX 上的 `dirname('C:\\x\\node.exe')` 会给出 `'.'`,于是那条 Windows 断言在 mac 上
|
|
34
|
+
* 跑不通。用平台自己的路径工具,注入才是真的能测。
|
|
35
|
+
*/
|
|
36
|
+
const pathsFor = (platform: NodeJS.Platform): typeof posix => platform === 'win32' ? win32 : posix
|
|
37
|
+
|
|
38
|
+
/** 相对 App 资源根的落点。`runtime.json` 就在它的上一级。 */
|
|
39
|
+
const BUNDLE_RELATIVE = ['runtime/primary-runtime/dependencies/python']
|
|
40
|
+
|
|
41
|
+
/** 一个解释器可能叫的几个名字。Windows 的独立发行版把 `python.exe` 放在根上。 */
|
|
42
|
+
function interpreterNames(root: string, platform: NodeJS.Platform): string[] {
|
|
43
|
+
const { join } = pathsFor(platform)
|
|
44
|
+
return platform === 'win32'
|
|
45
|
+
? [join(root, 'python.exe'), join(root, 'python3.exe')]
|
|
46
|
+
: [join(root, 'bin', 'python3'), join(root, 'bin', 'python')]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 从锚点向上走几级,每一级都试「本体」和「本体/Resources」两种排法。 */
|
|
50
|
+
function bundleCandidates(anchor: string, platform: NodeJS.Platform): string[] {
|
|
51
|
+
const { dirname, join } = pathsFor(platform)
|
|
52
|
+
const found: string[] = []
|
|
53
|
+
let dir = anchor
|
|
54
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
55
|
+
for (const relative of BUNDLE_RELATIVE) {
|
|
56
|
+
for (const name of interpreterNames(join(dir, relative), platform)) found.push(name)
|
|
57
|
+
// macOS / Windows 的 `.app`:真实资源在 `Contents/Resources`,而锚点是 `Contents/MacOS`。
|
|
58
|
+
for (const name of interpreterNames(join(dir, 'Resources', relative), platform)) found.push(name)
|
|
59
|
+
}
|
|
60
|
+
const parent = dirname(dir)
|
|
61
|
+
if (parent === dir) break
|
|
62
|
+
dir = parent
|
|
63
|
+
}
|
|
64
|
+
return found
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface PythonPick {
|
|
68
|
+
/** 要拿去建虚拟环境的解释器。 */
|
|
69
|
+
path: string
|
|
70
|
+
/** 它从哪来 —— 报错时要能说清「我没找到自带的那个」。 */
|
|
71
|
+
source: 'override' | 'bundled' | 'path'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface PythonProbe {
|
|
75
|
+
env?: Record<string, string | undefined>
|
|
76
|
+
/** Electron 的 `process.resourcesPath`;不在 Electron 里时省略。 */
|
|
77
|
+
resourcesPath?: string
|
|
78
|
+
/** 宿主的可执行文件路径;默认取 `process.execPath`。 */
|
|
79
|
+
execPath?: string
|
|
80
|
+
platform?: NodeJS.Platform
|
|
81
|
+
/** 存在性判断,测试里换成假的。 */
|
|
82
|
+
exists?: (path: string) => boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 挑一个基础解释器。**纯函数**(存在性判断可注入),所以「自带优先于 PATH」这条能被单独断言。
|
|
87
|
+
*/
|
|
88
|
+
export function pickBasePython(probe: PythonProbe = {}): PythonPick {
|
|
89
|
+
const env = probe.env ?? process.env
|
|
90
|
+
const override = env.DSH_PYTHON?.trim()
|
|
91
|
+
if (override) return { path: override, source: 'override' }
|
|
92
|
+
const platform = probe.platform ?? process.platform
|
|
93
|
+
const exists = probe.exists ?? existsSync
|
|
94
|
+
const anchors: string[] = []
|
|
95
|
+
if (probe.resourcesPath) anchors.push(probe.resourcesPath)
|
|
96
|
+
const { dirname } = pathsFor(platform)
|
|
97
|
+
const executables = [env.DSH_DESKTOP_NODE_EXECUTABLE, probe.execPath ?? process.execPath]
|
|
98
|
+
for (const executable of executables) {
|
|
99
|
+
const value = executable?.trim()
|
|
100
|
+
if (value) anchors.push(dirname(value))
|
|
101
|
+
}
|
|
102
|
+
for (const anchor of anchors) {
|
|
103
|
+
for (const candidate of bundleCandidates(anchor, platform)) {
|
|
104
|
+
if (exists(candidate)) return { path: candidate, source: 'bundled' }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { path: 'python3', source: 'path' }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 报错里那句「没找到自带的那一个」用得上。 */
|
|
111
|
+
export function pythonSourceLabel(source: PythonPick['source']): string {
|
|
112
|
+
return source === 'override' ? 'DSH_PYTHON' : source === 'bundled' ? 'DSH 自带运行时' : 'PATH 上的 python3'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 这个解释器能不能加载**第三方原生扩展**(torch 这类 pip 装的 dylib)。
|
|
117
|
+
*
|
|
118
|
+
* macOS 上不行的一种情况是真实存在过的(2026-10-18 实测):DSH 桌面端把 Python 3.12
|
|
119
|
+
* 打在自己的运行时里,并给它签了 Developer ID + **hardened runtime**
|
|
120
|
+
* (`flags=0x10000(runtime)`),又没有 `com.apple.security.cs.disable-library-validation`。
|
|
121
|
+
* 库校验因此生效 —— 它只肯加载**同一 Team ID** 签的 dylib。pip 上的 torch 是 ad-hoc 签名
|
|
122
|
+
* (Team ID 为空),于是:
|
|
123
|
+
*
|
|
124
|
+
* ```
|
|
125
|
+
* OSError: dlopen(.../libtorch_global_deps.dylib, 0x000A):
|
|
126
|
+
* code signature ... not valid for use in process:
|
|
127
|
+
* mapping process and mapped file (non-platform) have different Team IDs
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* 同一个 dylib 换成 uv 管的独立 CPython(`adhoc, linker-signed`,没有 runtime 标记)
|
|
131
|
+
* 就能加载 —— 这一条在同一台机器上验过。
|
|
132
|
+
*
|
|
133
|
+
* 判据读的是 `codesign` 的 flags,不看路径:运行时目录在某些安装形态下不在 `.app` 里,
|
|
134
|
+
* 而「带不带 hardened runtime」才是真正决定库校验的那件事。没有 `codesign`(Linux/Windows)
|
|
135
|
+
* 或读不到(没签名)时一律当「能加载」—— 不因为探测失败就改道去下载解释器。
|
|
136
|
+
*/
|
|
137
|
+
export async function hasHardenedRuntime(path: string, run: typeof runProcess = runProcess): Promise<boolean> {
|
|
138
|
+
if (process.platform !== 'darwin') return false
|
|
139
|
+
try {
|
|
140
|
+
const output = await run('/usr/bin/codesign', ['-dv', '--verbose=2', path], { timeoutMs: 10_000 })
|
|
141
|
+
const flags = /flags=0x[0-9a-f]+\(([^)]*)\)/i.exec(output)?.[1] ?? ''
|
|
142
|
+
return flags.split(',').some(flag => flag.trim() === 'runtime')
|
|
143
|
+
} catch {
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 这个解释器要不要换成 uv 管的那一个。
|
|
150
|
+
*
|
|
151
|
+
* 判据只有一条:**带 hardened runtime 就用不了第三方原生扩展**(见 `hasHardenedRuntime`)。
|
|
152
|
+
* `DSH_PYTHON` 显式给的不改道 —— 那是用户的选择;插件只把风险写进日志。
|
|
153
|
+
*/
|
|
154
|
+
export function needsManagedInterpreter(pick: PythonPick, hardened: boolean): boolean {
|
|
155
|
+
return hardened && pick.source !== 'override'
|
|
156
|
+
}
|