@stardustlc/dsh-dream 0.5.1 → 0.7.0
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/CHANGELOG.md +22 -0
- package/README.en.md +39 -14
- package/README.md +43 -18
- package/lib/bridge.d.ts +205 -2
- package/lib/bridge.js +1053 -6
- package/lib/client.js +634 -27
- package/lib/evidence.d.ts +15 -0
- package/lib/evidence.js +123 -0
- package/lib/index.js +3 -1
- package/lib/knowledge-store.d.ts +23 -5
- package/lib/knowledge-store.js +311 -41
- package/lib/knowledge.d.ts +40 -0
- package/lib/knowledge.js +54 -0
- package/lib/mask.d.ts +3 -1
- package/lib/mask.js +8 -2
- package/lib/retrieval.d.ts +12 -0
- package/lib/retrieval.js +68 -3
- package/lib/tools.d.ts +2 -0
- package/lib/tools.js +436 -128
- package/lib/web-actions.d.ts +11 -0
- package/lib/web-actions.js +175 -0
- package/lib/web.d.ts +50 -1
- package/lib/web.js +153 -13
- package/package.json +2 -2
- package/skills/dream-protocol/SKILL.md +9 -4
package/lib/knowledge.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* @module dsh-dream/knowledge
|
|
8
8
|
*/
|
|
9
9
|
import { createHash } from 'node:crypto';
|
|
10
|
+
/** 审阅备注最大长度;超出截断而不是报错。 */
|
|
11
|
+
export const REVIEW_NOTE_MAX_LENGTH = 500;
|
|
10
12
|
/** 知识层错误码:invalid=数据不合法;revision=乐观锁不符;duplicate=重复;io=读写/锁失败。 */
|
|
11
13
|
export class KnowledgeError extends Error {
|
|
12
14
|
code;
|
|
@@ -118,6 +120,21 @@ function readApplicability(value) {
|
|
|
118
120
|
}
|
|
119
121
|
return out;
|
|
120
122
|
}
|
|
123
|
+
function readResolution(value) {
|
|
124
|
+
if (value === undefined || value === null)
|
|
125
|
+
return undefined;
|
|
126
|
+
if (!isRecord(value))
|
|
127
|
+
fail('review.resolution', '必须是对象');
|
|
128
|
+
const kind = readString(value.kind, 'review.resolution.kind', { required: true, nonEmpty: true });
|
|
129
|
+
const targetId = readString(value.targetId, 'review.resolution.targetId', { nonEmpty: true });
|
|
130
|
+
const affectedIds = readStringArray(value.affectedIds, 'review.resolution.affectedIds');
|
|
131
|
+
const resolution = { kind };
|
|
132
|
+
if (targetId !== undefined)
|
|
133
|
+
resolution.targetId = targetId;
|
|
134
|
+
if (affectedIds !== undefined)
|
|
135
|
+
resolution.affectedIds = affectedIds;
|
|
136
|
+
return resolution;
|
|
137
|
+
}
|
|
121
138
|
function readReview(value) {
|
|
122
139
|
if (value === undefined || value === null)
|
|
123
140
|
return { decision: 'unreviewed' };
|
|
@@ -133,8 +150,39 @@ function readReview(value) {
|
|
|
133
150
|
review.actor = actor;
|
|
134
151
|
if (at !== undefined)
|
|
135
152
|
review.at = at;
|
|
153
|
+
const noteRaw = readString(value.note, 'review.note');
|
|
154
|
+
if (noteRaw !== undefined) {
|
|
155
|
+
const trimmed = noteRaw.trim();
|
|
156
|
+
if (trimmed !== '')
|
|
157
|
+
review.note = trimmed.length > REVIEW_NOTE_MAX_LENGTH ? trimmed.slice(0, REVIEW_NOTE_MAX_LENGTH) : trimmed;
|
|
158
|
+
}
|
|
159
|
+
const resolution = readResolution(value.resolution);
|
|
160
|
+
if (resolution !== undefined)
|
|
161
|
+
review.resolution = resolution;
|
|
136
162
|
return review;
|
|
137
163
|
}
|
|
164
|
+
function readFeedback(value) {
|
|
165
|
+
if (value === undefined)
|
|
166
|
+
return undefined;
|
|
167
|
+
if (!isRecord(value))
|
|
168
|
+
fail('feedback', '必须是对象');
|
|
169
|
+
for (const key of ['useful', 'notApplicable']) {
|
|
170
|
+
if (!Number.isSafeInteger(value[key]) || value[key] < 0)
|
|
171
|
+
fail('feedback.' + key, '必须是非负安全整数');
|
|
172
|
+
}
|
|
173
|
+
if (!Array.isArray(value.recent) || value.recent.length > 20)
|
|
174
|
+
fail('feedback.recent', '最多 20 条');
|
|
175
|
+
const recent = value.recent.map((item) => {
|
|
176
|
+
if (!isRecord(item) || (item.vote !== 'useful' && item.vote !== 'not-applicable'))
|
|
177
|
+
fail('feedback.recent.vote', '取值不合法');
|
|
178
|
+
const at = readIsoDate(item.at, 'feedback.recent.at', true);
|
|
179
|
+
const note = readString(item.note, 'feedback.recent.note');
|
|
180
|
+
if (note !== undefined && note.length > REVIEW_NOTE_MAX_LENGTH)
|
|
181
|
+
fail('feedback.recent.note', '超过长度上限');
|
|
182
|
+
return { vote: item.vote, at, ...(note === undefined ? {} : { note }) };
|
|
183
|
+
});
|
|
184
|
+
return { useful: value.useful, notApplicable: value.notApplicable, recent };
|
|
185
|
+
}
|
|
138
186
|
/** 当前内存模型 / 写盘的 lesson schemaVersion。 */
|
|
139
187
|
export const LESSON_SCHEMA_VERSION = 1;
|
|
140
188
|
/** 已知迁移链占位:v2 落地时在此登记;存储层默认只识别不自动迁移。 */
|
|
@@ -221,6 +269,9 @@ export function validateLesson(value, options = {}) {
|
|
|
221
269
|
lesson.reviewAfter = reviewAfter;
|
|
222
270
|
if (supersedes !== undefined)
|
|
223
271
|
lesson.supersedes = supersedes;
|
|
272
|
+
const feedback = readFeedback(value.feedback);
|
|
273
|
+
if (feedback !== undefined)
|
|
274
|
+
lesson.feedback = feedback;
|
|
224
275
|
return lesson;
|
|
225
276
|
}
|
|
226
277
|
/** 校验并归一化一条证据;失败抛 KnowledgeError('invalid')。未知字段会被丢弃。 */
|
|
@@ -241,6 +292,7 @@ export function validateEvidence(value) {
|
|
|
241
292
|
const sessionId = readString(value.sessionId, 'evidence.sessionId', { nonEmpty: true });
|
|
242
293
|
const projectId = readString(value.projectId, 'evidence.projectId', { nonEmpty: true });
|
|
243
294
|
const sourceHash = readString(value.sourceHash, 'evidence.sourceHash', { nonEmpty: true });
|
|
295
|
+
const verificationReason = readString(value.verificationReason, 'evidence.verificationReason', { nonEmpty: true });
|
|
244
296
|
const recordSeqRaw = value.recordSeq;
|
|
245
297
|
let recordSeq;
|
|
246
298
|
if (recordSeqRaw !== undefined && recordSeqRaw !== null) {
|
|
@@ -267,6 +319,8 @@ export function validateEvidence(value) {
|
|
|
267
319
|
evidence.projectId = projectId;
|
|
268
320
|
if (sourceHash !== undefined)
|
|
269
321
|
evidence.sourceHash = sourceHash;
|
|
322
|
+
if (verificationReason !== undefined)
|
|
323
|
+
evidence.verificationReason = verificationReason;
|
|
270
324
|
return evidence;
|
|
271
325
|
}
|
|
272
326
|
/**
|
package/lib/mask.d.ts
CHANGED
|
@@ -10,4 +10,6 @@ export declare const SECRET_PATTERNS: Array<{
|
|
|
10
10
|
label: string;
|
|
11
11
|
}>;
|
|
12
12
|
/** 对文本做脱敏;无命中时原样返回。 */
|
|
13
|
-
export declare function maskSecrets(text: string): string;
|
|
13
|
+
export declare function maskSecrets(text: string, publicReferences?: readonly string[]): string;
|
|
14
|
+
/** 只认可插件生成的引用形状;不得把任意 *Id / *Hash 字段当作脱敏豁免。 */
|
|
15
|
+
export declare function isDreamReference(value: string): boolean;
|
package/lib/mask.js
CHANGED
|
@@ -22,12 +22,18 @@ function looksHighEntropy(text) {
|
|
|
22
22
|
return /[a-z]/.test(text) && (/[A-Z]/.test(text) || /[0-9]/.test(text));
|
|
23
23
|
}
|
|
24
24
|
/** 对文本做脱敏;无命中时原样返回。 */
|
|
25
|
-
export function maskSecrets(text) {
|
|
25
|
+
export function maskSecrets(text, publicReferences = []) {
|
|
26
26
|
let out = text;
|
|
27
27
|
for (const { re, label } of SECRET_PATTERNS) {
|
|
28
28
|
out = out.replace(re, '[已脱敏·' + label + ']');
|
|
29
29
|
}
|
|
30
30
|
out = out.replace(CRED_ASSIGN, (_m, key, sep) => key + sep + '[已脱敏·凭据]');
|
|
31
|
-
|
|
31
|
+
const references = new Set(publicReferences.filter(isDreamReference));
|
|
32
|
+
out = out.replace(LONG_TOKEN, (m) => (references.has(m) || !looksHighEntropy(m) ? m : '[已脱敏·长令牌]'));
|
|
32
33
|
return out;
|
|
33
34
|
}
|
|
35
|
+
/** 只认可插件生成的引用形状;不得把任意 *Id / *Hash 字段当作脱敏豁免。 */
|
|
36
|
+
export function isDreamReference(value) {
|
|
37
|
+
return /^(?:lsn|evd|evt|brg)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)
|
|
38
|
+
|| /^pvw_[0-9a-f]{24}$/i.test(value);
|
|
39
|
+
}
|
package/lib/retrieval.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface RetrievalQuery {
|
|
|
22
22
|
packageVersion?: string;
|
|
23
23
|
/** 显式包名上下文;与 query 文本一起用于识别 applicability[].package(R3)。 */
|
|
24
24
|
packageName?: string;
|
|
25
|
+
/** 任务目标平台;宿主工具缺省使用当前系统,跨平台任务可显式指定。 */
|
|
26
|
+
platform?: string;
|
|
25
27
|
/** 是否返回 candidate;默认 true(面板/审计需要)。dream_context 默认传 false(R1)。 */
|
|
26
28
|
includeCandidates?: boolean;
|
|
27
29
|
/** 是否保留 independentSupportCount===0 且非 usable 的条目;默认 true。dream_context 传 false(R1)。 */
|
|
@@ -73,6 +75,13 @@ export declare function rankInversionCount(rankedLessonIds: string[], result: {
|
|
|
73
75
|
reason: string;
|
|
74
76
|
}>;
|
|
75
77
|
}): number;
|
|
78
|
+
/** skipped.reason 的中文标签(面板与工具共用;未知 reason 原样返回)。 */
|
|
79
|
+
export declare function skippedReasonLabel(reason: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* 浏览态(无 query 上下文)下一条经验的确定性扣留原因;usable 返回 undefined。
|
|
82
|
+
* 与 retrieveLessons 的优先级一致:no-evidence 先于 candidate-hold;version-mismatch 需 query/包名,不适用。
|
|
83
|
+
*/
|
|
84
|
+
export declare function deterministicHoldReason(lesson: Pick<Lesson, 'state' | 'independentSupportCount'>): string | undefined;
|
|
76
85
|
/** 解析 limit/maxChars,套用默认值与硬上限。 */
|
|
77
86
|
export declare function resolveRetrievalBudget(q: Pick<RetrievalQuery, 'limit' | 'maxChars'>): {
|
|
78
87
|
limit: number;
|
|
@@ -80,6 +89,9 @@ export declare function resolveRetrievalBudget(q: Pick<RetrievalQuery, 'limit' |
|
|
|
80
89
|
};
|
|
81
90
|
/** 简单 semver 条件匹配:支持 = / > / >= / < / <= / ^ / ~、逗号或空格 AND、|| OR、x/* 通配。 */
|
|
82
91
|
export declare function versionSatisfies(versionText: string, rangeText: string): boolean;
|
|
92
|
+
export declare function normalizePlatform(value: string): string;
|
|
93
|
+
/** 显式任务平台优先,其次无歧义的任务文本,最后宿主平台。 */
|
|
94
|
+
export declare function taskPlatform(query: string, explicit?: string, fallback?: string): string | undefined;
|
|
83
95
|
/**
|
|
84
96
|
* 任务相关经验检索。只返回 global 或与查询项目/工作区精确匹配的记录;
|
|
85
97
|
* 未知项目(未给 projectId/workspaceRoot)时只返回 global,绝不跨项目扫描。
|
package/lib/retrieval.js
CHANGED
|
@@ -142,6 +142,37 @@ export function rankInversionCount(rankedLessonIds, result) {
|
|
|
142
142
|
}
|
|
143
143
|
return inversions;
|
|
144
144
|
}
|
|
145
|
+
const SKIPPED_REASON_LABELS = {
|
|
146
|
+
'state:rejected': '已驳回',
|
|
147
|
+
'state:stale': '已过期(待复核)',
|
|
148
|
+
'state:disputed': '冲突未解决',
|
|
149
|
+
'candidate-hold': '候选未审阅(默认不注入)',
|
|
150
|
+
'no-evidence': '无独立证据',
|
|
151
|
+
'version-mismatch': '版本条件不匹配',
|
|
152
|
+
'platform-mismatch': '平台条件不匹配',
|
|
153
|
+
limit: '超出条数上限',
|
|
154
|
+
'budget-stop': '预算放不下',
|
|
155
|
+
budget: '预算放不下(旧值)',
|
|
156
|
+
};
|
|
157
|
+
/** skipped.reason 的中文标签(面板与工具共用;未知 reason 原样返回)。 */
|
|
158
|
+
export function skippedReasonLabel(reason) {
|
|
159
|
+
return SKIPPED_REASON_LABELS[reason] ?? reason;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 浏览态(无 query 上下文)下一条经验的确定性扣留原因;usable 返回 undefined。
|
|
163
|
+
* 与 retrieveLessons 的优先级一致:no-evidence 先于 candidate-hold;version-mismatch 需 query/包名,不适用。
|
|
164
|
+
*/
|
|
165
|
+
export function deterministicHoldReason(lesson) {
|
|
166
|
+
if (lesson.state === 'rejected')
|
|
167
|
+
return 'state:rejected';
|
|
168
|
+
if (lesson.state === 'stale')
|
|
169
|
+
return 'state:stale';
|
|
170
|
+
if (lesson.state === 'disputed')
|
|
171
|
+
return 'state:disputed';
|
|
172
|
+
if (lesson.state !== 'usable')
|
|
173
|
+
return lesson.independentSupportCount === 0 ? 'no-evidence' : 'candidate-hold';
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
145
176
|
/** 解析 limit/maxChars,套用默认值与硬上限。 */
|
|
146
177
|
export function resolveRetrievalBudget(q) {
|
|
147
178
|
const rawLimit = q.limit;
|
|
@@ -257,9 +288,34 @@ function packageIdentified(pkg, q, queryText) {
|
|
|
257
288
|
return true;
|
|
258
289
|
return queryText.includes(normalizedPkg);
|
|
259
290
|
}
|
|
291
|
+
export function normalizePlatform(value) {
|
|
292
|
+
const key = value.trim().toLowerCase();
|
|
293
|
+
if (['win32', 'win64', 'windows', 'win', '微软windows'].includes(key))
|
|
294
|
+
return 'windows';
|
|
295
|
+
if (['darwin', 'macos', 'mac', 'osx', 'os x'].includes(key))
|
|
296
|
+
return 'macos';
|
|
297
|
+
if (['linux', 'gnu/linux'].includes(key))
|
|
298
|
+
return 'linux';
|
|
299
|
+
return key;
|
|
300
|
+
}
|
|
301
|
+
/** 显式任务平台优先,其次无歧义的任务文本,最后宿主平台。 */
|
|
302
|
+
export function taskPlatform(query, explicit, fallback) {
|
|
303
|
+
if (trimmed(explicit))
|
|
304
|
+
return normalizePlatform(explicit);
|
|
305
|
+
const platforms = [];
|
|
306
|
+
if (/\b(?:windows|win32|win64)\b/i.test(query))
|
|
307
|
+
platforms.push('windows');
|
|
308
|
+
if (/\b(?:linux|ubuntu|debian|fedora)\b/i.test(query))
|
|
309
|
+
platforms.push('linux');
|
|
310
|
+
if (/\b(?:macos|darwin|osx)\b/i.test(query))
|
|
311
|
+
platforms.push('macos');
|
|
312
|
+
return platforms.length === 1 ? platforms[0] : fallback ? normalizePlatform(fallback) : undefined;
|
|
313
|
+
}
|
|
260
314
|
function matchApplicability(lesson, q, queryText) {
|
|
261
|
-
const out = { packages: [], versions: [], platforms: [], versionMismatch: false };
|
|
315
|
+
const out = { packages: [], versions: [], platforms: [], versionMismatch: false, platformMismatch: false };
|
|
262
316
|
const packageVersion = trimmed(q.packageVersion);
|
|
317
|
+
const currentPlatform = taskPlatform(q.query, q.platform);
|
|
318
|
+
const platformChecks = [];
|
|
263
319
|
const identifiedVersionChecks = [];
|
|
264
320
|
for (const entry of lesson.applicability) {
|
|
265
321
|
const pkg = trimmed(entry.package);
|
|
@@ -274,11 +330,16 @@ function matchApplicability(lesson, q, queryText) {
|
|
|
274
330
|
out.versions.push(versions);
|
|
275
331
|
}
|
|
276
332
|
const platform = trimmed(entry.platform);
|
|
277
|
-
if (
|
|
278
|
-
|
|
333
|
+
if (currentPlatform !== undefined) {
|
|
334
|
+
const ok = platform === undefined || ['*', 'any', 'all'].includes(normalizePlatform(platform))
|
|
335
|
+
|| platform.split(/[,|/]/).some(item => normalizePlatform(item) === currentPlatform);
|
|
336
|
+
platformChecks.push(ok);
|
|
337
|
+
if (ok && platform !== undefined && !out.platforms.includes(platform))
|
|
338
|
+
out.platforms.push(platform);
|
|
279
339
|
}
|
|
280
340
|
}
|
|
281
341
|
out.versionMismatch = identifiedVersionChecks.length > 0 && identifiedVersionChecks.every((ok) => !ok);
|
|
342
|
+
out.platformMismatch = platformChecks.length > 0 && platformChecks.every((ok) => !ok);
|
|
282
343
|
return out;
|
|
283
344
|
}
|
|
284
345
|
function queryTerms(query) {
|
|
@@ -424,6 +485,10 @@ export function retrieveLessons(lessons, evidence, q) {
|
|
|
424
485
|
}
|
|
425
486
|
// R3 优先于 R1 的候选扣留:包名已识别但版本不满足时,reason 必须是 version-mismatch。
|
|
426
487
|
const applicability = matchApplicability(lesson, q, queryText);
|
|
488
|
+
if (applicability.platformMismatch) {
|
|
489
|
+
skipped.push({ lessonId: lesson.id, reason: 'platform-mismatch' });
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
427
492
|
if (applicability.versionMismatch) {
|
|
428
493
|
skipped.push({ lessonId: lesson.id, reason: 'version-mismatch' });
|
|
429
494
|
continue;
|
package/lib/tools.d.ts
CHANGED
|
@@ -25,3 +25,5 @@ export interface DreamToolDefinition {
|
|
|
25
25
|
export declare function buildDreamTools(config: ResolvedDreamConfig): DreamToolDefinition[];
|
|
26
26
|
/** 把会话摘要拼成一段可读文本(供模型一次性阅读)。 */
|
|
27
27
|
export declare function buildDigestText(digest: SessionDigest): string;
|
|
28
|
+
/** actor comes from the trusted caller, never from tool or browser arguments. */
|
|
29
|
+
export declare function executeDreamReview(cfg: ResolvedDreamConfig, rawArgs: unknown, exec: unknown, actor?: 'model' | 'human'): Promise<unknown>;
|