ygc-dsh-sync 1.2.2 → 1.3.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/README.md +107 -283
- package/client.js +425 -545
- package/index.js +311 -223
- package/lib/api.js +161 -747
- package/lib/core.js +73 -43
- package/lib/skill-learn.js +1115 -0
- package/lib/skill-optimize.js +888 -0
- package/lib/skill-scope.js +288 -0
- package/lib/skill-share-run.js +388 -0
- package/lib/skillshare.js +781 -0
- package/package.json +14 -3
package/index.js
CHANGED
|
@@ -19,22 +19,13 @@ import {
|
|
|
19
19
|
import { attachMany, findPulledUnattached } from './lib/attach.js';
|
|
20
20
|
import { scanFolders } from './lib/patrol.js';
|
|
21
21
|
import { applyFeaturePlan, onlyActive, pickRemovedIds, planFeatures, reconcileRemoved } from './lib/features.js';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
makeStamp,
|
|
30
|
-
planSync,
|
|
31
|
-
readMnemonLedger,
|
|
32
|
-
recordLineage,
|
|
33
|
-
saveLineage,
|
|
34
|
-
} from './lib/skills.js';
|
|
35
|
-
import { appendMasterChanges } from './lib/changelog.js';
|
|
36
|
-
import { runMnemonPrepass } from './lib/mnemon.js';
|
|
37
|
-
import { runOptimizationCycle } from './lib/mnemon-optimize.js';
|
|
22
|
+
// 1.3.0 母版取消:技能不再有「母版 ↔ 本机」这条链,只剩
|
|
23
|
+
// 本机特征文件夹备份 + 别人格子只读拉取 + 本机技能优化
|
|
24
|
+
import { LOCAL_SKILLS_ROOT } from './lib/skills.js';
|
|
25
|
+
import { draftsDirOf, runSkillShare } from './lib/skill-share-run.js';
|
|
26
|
+
import { inspectAll, runOptimizeRound } from './lib/skill-optimize.js';
|
|
27
|
+
import { ownNames, scopeSnapshot, SCOPE } from './lib/skill-scope.js';
|
|
28
|
+
import { approveLearning, listPendingLearnings, readDraft, rejectLearning } from './lib/skill-learn.js';
|
|
38
29
|
import { createHostDrafter } from './lib/mnemon-drafter.js';
|
|
39
30
|
import {
|
|
40
31
|
addToBundles as addPluginToBundles,
|
|
@@ -44,9 +35,6 @@ import {
|
|
|
44
35
|
|
|
45
36
|
export const inject = ['webServer', 'workspaceRegistry'];
|
|
46
37
|
|
|
47
|
-
/** 外部草稿暂存区:优化闭环把待审草稿放这里,审核通过才落地。 */
|
|
48
|
-
const STAGE_DIR = process.env.DSH_SYNC_STAGE_DIR || 'C:/DSH-Work/mnemon-drafts/stage';
|
|
49
|
-
|
|
50
38
|
const DEBOUNCE_MS = 4000;
|
|
51
39
|
/** 巡检轮询间隔:每 30 秒看一次是否到了该跑的时候。 */
|
|
52
40
|
const PATROL_TICK_MS = 30 * 1000;
|
|
@@ -212,7 +200,265 @@ export function apply(ctx, config = {}) {
|
|
|
212
200
|
}
|
|
213
201
|
};
|
|
214
202
|
|
|
215
|
-
// ----
|
|
203
|
+
// ---- 技能共享(1.3.0 新增)----
|
|
204
|
+
//
|
|
205
|
+
// 与插件清单同步同构:一人一格,只写自己的格子。触发点是「技能同步」
|
|
206
|
+
// (开机 + 定时 + 面板按钮),不单独开一个定时器——用户的心智模型是
|
|
207
|
+
// 「同步技能时顺带把技能也共享出去」,两个独立周期只会让人怀疑哪个没跑。
|
|
208
|
+
const runShare = async ({ reason = '技能同步', dryRun = false } = {}) => {
|
|
209
|
+
const state = loadState();
|
|
210
|
+
const share = state.skills.share || {};
|
|
211
|
+
if (share.enabled === false && reason !== '手动') {
|
|
212
|
+
return { ok: true, skipped: '技能共享已关闭' };
|
|
213
|
+
}
|
|
214
|
+
const feature = state.machineId;
|
|
215
|
+
if (!feature) return { ok: false, error: '本机标识还没生成' };
|
|
216
|
+
let generate = null;
|
|
217
|
+
try {
|
|
218
|
+
generate = createHostDrafter(ctx, {
|
|
219
|
+
logger: (m) => log(`[learn] ${m}`),
|
|
220
|
+
provider: config.draftProvider,
|
|
221
|
+
model: config.draftModel,
|
|
222
|
+
});
|
|
223
|
+
} catch {
|
|
224
|
+
generate = null;
|
|
225
|
+
}
|
|
226
|
+
const report = await runSkillShare({
|
|
227
|
+
config: { libraryRoot: state.plugins.libraryRoot },
|
|
228
|
+
machineId: feature,
|
|
229
|
+
machineHash: state.machineHash,
|
|
230
|
+
machineName: state.machineName,
|
|
231
|
+
localRoot: LOCAL_SKILLS_ROOT,
|
|
232
|
+
// 母版已取消:不再传 masterRoot。技能来源就是各台机器的特征文件夹备份,
|
|
233
|
+
// 归属判定只靠市场台账 + 人工指定 + 默认归自己(见 lib/skill-scope.js)。
|
|
234
|
+
modes: state.skills.modes || {},
|
|
235
|
+
scopeOverrides: (state.skills.share && state.skills.share.scope) || {},
|
|
236
|
+
learn: share.learn !== false,
|
|
237
|
+
learnPerRound: Number(share.learnPerRound) || 4,
|
|
238
|
+
autoApply: share.autoApply === true,
|
|
239
|
+
generate,
|
|
240
|
+
dryRun,
|
|
241
|
+
log,
|
|
242
|
+
reason,
|
|
243
|
+
});
|
|
244
|
+
if (dryRun) return report;
|
|
245
|
+
const next = loadState();
|
|
246
|
+
if (!next.skills.share) next.skills.share = {};
|
|
247
|
+
next.skills.share.lastRunAt = Date.now();
|
|
248
|
+
next.skills.share.lastScope = report.scope || null;
|
|
249
|
+
next.skills.share.lastResult = {
|
|
250
|
+
peers: report.peers.length,
|
|
251
|
+
own: report.scope ? report.scope.own : null,
|
|
252
|
+
thirdParty: report.scope ? report.scope.thirdParty.length : 0,
|
|
253
|
+
backup: report.backup ? report.backup.counts.total : 0,
|
|
254
|
+
missing: report.missing.found,
|
|
255
|
+
pulled: report.missing.pulled,
|
|
256
|
+
differ: report.differ.found,
|
|
257
|
+
evaluated: report.differ.evaluated,
|
|
258
|
+
byAction: report.differ.byAction,
|
|
259
|
+
pending: report.pending,
|
|
260
|
+
applied: report.applied.length,
|
|
261
|
+
redundant: report.redundant || 0,
|
|
262
|
+
reason,
|
|
263
|
+
};
|
|
264
|
+
next.skills.share.lastError = report.errors.length ? report.errors.slice(0, 3).join(';') : '';
|
|
265
|
+
if (report.missing.pulled || report.differ.evaluated || report.errors.length) {
|
|
266
|
+
pushHistory(next, {
|
|
267
|
+
kind: 'skill-share',
|
|
268
|
+
detail:
|
|
269
|
+
`技能共享(${reason}):备份 ${report.backup ? report.backup.counts.total : 0}、` +
|
|
270
|
+
`他人 ${report.peers.length} 台、缺失 ${report.missing.found}(拉 ${report.missing.pulled})、` +
|
|
271
|
+
`差异 ${report.differ.found}(评估 ${report.differ.evaluated})、待批 ${report.pending}` +
|
|
272
|
+
(report.errors.length ? `、错误 ${report.errors.length}` : ''),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
saveState(next);
|
|
276
|
+
return report;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// ---- 本机技能优化(1.3.0)----
|
|
280
|
+
//
|
|
281
|
+
// 点一下就在后台跑:体检 → 排优先级 → 调模型出草稿 → 落草稿等批。
|
|
282
|
+
// 三条纪律:
|
|
283
|
+
// ① 同一时刻只跑一轮(inFlight),重复点击不会堆出并发模型调用
|
|
284
|
+
// ② 只处理归本插件管的技能;市场装的第三方技能在 ownSet 之外,直接跳过
|
|
285
|
+
// ③ 出的都是草稿,改本机技能只在人点「批准」时发生(批准前留备份)
|
|
286
|
+
/** 优化任务的运行态:面板靠它显示进度,不靠长连接的返回值。 */
|
|
287
|
+
const optimizeRun = {
|
|
288
|
+
running: false,
|
|
289
|
+
startedAt: null,
|
|
290
|
+
finishedAt: null,
|
|
291
|
+
lastReport: null,
|
|
292
|
+
lastError: null,
|
|
293
|
+
reason: '',
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
/** 归属判定的统一入口:一处算、处处用。 */
|
|
297
|
+
const scopeOf = () => {
|
|
298
|
+
const st = loadState();
|
|
299
|
+
const overrides = (st.skills.share && st.skills.share.scope) || {};
|
|
300
|
+
return scopeSnapshot({
|
|
301
|
+
localRoot: LOCAL_SKILLS_ROOT,
|
|
302
|
+
overrides,
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/** 拿 ownSet(Set<技能名>),失败时退化成 null(调用方按「全都管」处理)。 */
|
|
307
|
+
const ownSetOf = () => {
|
|
308
|
+
try {
|
|
309
|
+
const st = loadState();
|
|
310
|
+
const overrides = (st.skills.share && st.skills.share.scope) || {};
|
|
311
|
+
return ownNames({ localRoot: LOCAL_SKILLS_ROOT, overrides }).names;
|
|
312
|
+
} catch (err) {
|
|
313
|
+
log(`技能归属判定失败,按「全都管」处理:${String((err && err.message) || err)}`);
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/** 整库体检:纯机械、不调模型、不写盘,随时可点。 */
|
|
319
|
+
const inspectSkills = () => {
|
|
320
|
+
const out = inspectAll({ localRoot: LOCAL_SKILLS_ROOT, ownSet: ownSetOf() });
|
|
321
|
+
return {
|
|
322
|
+
...out,
|
|
323
|
+
thirdPartyNote: '技能市场装的第三方技能不进体检(不管理、不同步、不优化)',
|
|
324
|
+
scope: scopeOf().counts,
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/** 起一轮优化(后台)。已经有一轮在跑就直接返回当前运行态。 */
|
|
329
|
+
const startOptimize = ({ maxPerRound, includeMerge = true } = {}) => {
|
|
330
|
+
if (optimizeRun.running) {
|
|
331
|
+
return { running: true, startedAt: optimizeRun.startedAt, note: '上一轮还在跑,本次未启动' };
|
|
332
|
+
}
|
|
333
|
+
optimizeRun.running = true;
|
|
334
|
+
optimizeRun.startedAt = Date.now();
|
|
335
|
+
optimizeRun.finishedAt = null;
|
|
336
|
+
optimizeRun.lastError = null;
|
|
337
|
+
optimizeRun.reason = '面板';
|
|
338
|
+
const st = loadState();
|
|
339
|
+
const share = st.skills.share || {};
|
|
340
|
+
const feature = st.machineId;
|
|
341
|
+
const ownSet = ownSetOf();
|
|
342
|
+
let generate = null;
|
|
343
|
+
try {
|
|
344
|
+
generate = createHostDrafter(ctx, {
|
|
345
|
+
logger: (m) => log(`[opt] ${m}`),
|
|
346
|
+
provider: config.draftProvider,
|
|
347
|
+
model: config.draftModel,
|
|
348
|
+
});
|
|
349
|
+
} catch {
|
|
350
|
+
generate = null;
|
|
351
|
+
}
|
|
352
|
+
Promise.resolve()
|
|
353
|
+
.then(() =>
|
|
354
|
+
runOptimizeRound({
|
|
355
|
+
config: { libraryRoot: st.plugins.libraryRoot },
|
|
356
|
+
machineId: feature,
|
|
357
|
+
machineName: st.machineName,
|
|
358
|
+
localRoot: LOCAL_SKILLS_ROOT,
|
|
359
|
+
ownSet,
|
|
360
|
+
generate,
|
|
361
|
+
maxPerRound: Number(maxPerRound) || Number(share.optimizePerRound) || 3,
|
|
362
|
+
includeMerge,
|
|
363
|
+
log,
|
|
364
|
+
}),
|
|
365
|
+
)
|
|
366
|
+
.then((report) => {
|
|
367
|
+
optimizeRun.lastReport = {
|
|
368
|
+
scanned: report.scanned,
|
|
369
|
+
healthy: report.healthy,
|
|
370
|
+
skippedThirdParty: report.skippedThirdParty,
|
|
371
|
+
queued: report.queued.length,
|
|
372
|
+
drafted: report.drafted.length,
|
|
373
|
+
draftedNames: report.drafted.map((x) => `${x.name}(${x.label})`).slice(0, 12),
|
|
374
|
+
rejected: report.rejected.length,
|
|
375
|
+
merged: report.merged.length,
|
|
376
|
+
errors: report.errors.slice(0, 5),
|
|
377
|
+
dryRun: report.dryRun,
|
|
378
|
+
ms: report.ms,
|
|
379
|
+
};
|
|
380
|
+
const next = loadState();
|
|
381
|
+
if (!next.skills.share) next.skills.share = {};
|
|
382
|
+
next.skills.share.lastOptimizeAt = Date.now();
|
|
383
|
+
next.skills.share.lastOptimize = optimizeRun.lastReport;
|
|
384
|
+
if (report.drafted.length || report.errors.length) {
|
|
385
|
+
pushHistory(next, {
|
|
386
|
+
kind: 'skill-optimize',
|
|
387
|
+
detail:
|
|
388
|
+
`本机技能优化:体检 ${report.scanned} 个(健康 ${report.healthy},第三方跳过 ${report.skippedThirdParty})、` +
|
|
389
|
+
`出草稿 ${report.drafted.length}、审核未过 ${report.rejected.length}` +
|
|
390
|
+
(report.errors.length ? `、错误 ${report.errors.length}` : ''),
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
saveState(next);
|
|
394
|
+
})
|
|
395
|
+
.catch((err) => {
|
|
396
|
+
optimizeRun.lastError = String((err && err.message) || err);
|
|
397
|
+
log(`本机技能优化失败:${optimizeRun.lastError}`);
|
|
398
|
+
})
|
|
399
|
+
.finally(() => {
|
|
400
|
+
optimizeRun.running = false;
|
|
401
|
+
optimizeRun.finishedAt = Date.now();
|
|
402
|
+
});
|
|
403
|
+
return { running: true, startedAt: optimizeRun.startedAt };
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/** 待批草稿(学习 + 优化同一份台账)。 */
|
|
407
|
+
const listLearning = () => {
|
|
408
|
+
const st = loadState();
|
|
409
|
+
if (!st.machineId) return [];
|
|
410
|
+
try {
|
|
411
|
+
return listPendingLearnings({ config: { libraryRoot: st.plugins.libraryRoot }, machineId: st.machineId });
|
|
412
|
+
} catch (err) {
|
|
413
|
+
log(`读待批草稿失败:${String((err && err.message) || err)}`);
|
|
414
|
+
return [];
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const draftOf = (name) => {
|
|
419
|
+
const st = loadState();
|
|
420
|
+
if (!st.machineId) return { ok: false, text: '', message: '本机标识还没生成' };
|
|
421
|
+
return readDraft({ config: { libraryRoot: st.plugins.libraryRoot }, machineId: st.machineId, name });
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const approveOf = ({ name, force = false }) => {
|
|
425
|
+
const st = loadState();
|
|
426
|
+
if (!st.machineId) return { ok: false, message: '本机标识还没生成' };
|
|
427
|
+
const r = approveLearning({
|
|
428
|
+
config: { libraryRoot: st.plugins.libraryRoot },
|
|
429
|
+
machineId: st.machineId,
|
|
430
|
+
localRoot: LOCAL_SKILLS_ROOT,
|
|
431
|
+
name,
|
|
432
|
+
force,
|
|
433
|
+
log,
|
|
434
|
+
});
|
|
435
|
+
if (r.ok) {
|
|
436
|
+
const next = loadState();
|
|
437
|
+
pushHistory(next, { kind: 'skill-apply', detail: `技能草稿落地:${name}` });
|
|
438
|
+
saveState(next);
|
|
439
|
+
}
|
|
440
|
+
return r;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const rejectOf = ({ name, reason }) => {
|
|
444
|
+
const st = loadState();
|
|
445
|
+
if (!st.machineId) return { ok: false, message: '本机标识还没生成' };
|
|
446
|
+
return rejectLearning({ config: { libraryRoot: st.plugins.libraryRoot }, machineId: st.machineId, name, reason });
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
/** 人工改归属:写进 state(本机持久化),下次判定直接生效。 */
|
|
450
|
+
const setScopeOf = ({ name, scope }) => {
|
|
451
|
+
const allowed = new Set([SCOPE.OWN, SCOPE.THIRD_PARTY]);
|
|
452
|
+
if (!allowed.has(scope)) return { ok: false, message: `未知归属 ${scope}` };
|
|
453
|
+
const next = loadState();
|
|
454
|
+
if (!next.skills.share) next.skills.share = {};
|
|
455
|
+
if (!next.skills.share.scope || typeof next.skills.share.scope !== 'object') next.skills.share.scope = {};
|
|
456
|
+
next.skills.share.scope[name] = scope;
|
|
457
|
+
saveState(next);
|
|
458
|
+
log(`技能归属改为 ${scope}:${name}(人工指定优先于市场台账判定)`);
|
|
459
|
+
return { ok: true, name, scope, scopeSnapshot: scopeOf() };
|
|
460
|
+
};
|
|
461
|
+
|
|
216
462
|
const api = createApi({
|
|
217
463
|
workspaces,
|
|
218
464
|
machineId,
|
|
@@ -222,6 +468,26 @@ export function apply(ctx, config = {}) {
|
|
|
222
468
|
entities,
|
|
223
469
|
// 起草器:宿主的默认模型。没有 llm 服务时返回 null,流水线自动跳过起草。
|
|
224
470
|
log,
|
|
471
|
+
// 技能共享(1.3.0):同步一次 / 技能同步触发时顺带跑
|
|
472
|
+
runShare,
|
|
473
|
+
draftsRoot: () => {
|
|
474
|
+
const st = loadState();
|
|
475
|
+
return draftsDirOf({ libraryRoot: st.plugins.libraryRoot }, st.machineId);
|
|
476
|
+
},
|
|
477
|
+
skillShareConfig: () => {
|
|
478
|
+
const st = loadState();
|
|
479
|
+
return { ...(st.skills.share || {}), libraryRoot: st.plugins.libraryRoot, machineId: st.machineId };
|
|
480
|
+
},
|
|
481
|
+
// 本机优化:体检 / 出草稿(后台跑)/ 待批草稿的批准与驳回
|
|
482
|
+
inspectSkills: async () => inspectSkills(),
|
|
483
|
+
runOptimize: (opts) => startOptimize(opts),
|
|
484
|
+
optimizeState: () => ({ ...optimizeRun }),
|
|
485
|
+
learningList: async () => listLearning(),
|
|
486
|
+
learningDraft: async (name) => draftOf(name),
|
|
487
|
+
learningApprove: async (args) => approveOf(args),
|
|
488
|
+
learningReject: async (args) => rejectOf(args),
|
|
489
|
+
skillScope: async () => scopeOf(),
|
|
490
|
+
setSkillScope: async (args) => setScopeOf(args),
|
|
225
491
|
// 插件清单同步(1.0.0 迁入):同步动作、安装通道、bundles 登记
|
|
226
492
|
syncPlugins,
|
|
227
493
|
pluginInstall,
|
|
@@ -607,209 +873,28 @@ export function apply(ctx, config = {}) {
|
|
|
607
873
|
};
|
|
608
874
|
});
|
|
609
875
|
|
|
610
|
-
// ----
|
|
611
|
-
// 语义:开机拉取一次,之后每 intervalMinutes 分钟一次(默认 120)。
|
|
876
|
+
// ---- 技能共享 + 本机优化 ----
|
|
612
877
|
//
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
878
|
+
// 语义(1.3.0 起,母版取消后):开机跑一次,之后每 intervalMinutes 分钟一次。
|
|
879
|
+
// 一轮里做三件事,顺序固定:
|
|
880
|
+
// 1. 把本机自研技能备份进 `插件库/<本机标识>/skills/`(整棵目录 + 名片)
|
|
881
|
+
// 2. 扫别人格子:本机没有的拉过来;同名不同内容进学习评估出草稿
|
|
882
|
+
// 3. (可选)顺带跑一轮本机优化:精炼 / 精简 / 合并重复 / 补工况 / 补踩坑
|
|
883
|
+
//
|
|
884
|
+
// 三条纪律:
|
|
885
|
+
// ① 只碰「归本插件管」的技能:市场装的第三方技能不同步、不优化(skill-scope.js 判定)
|
|
886
|
+
// ② 优化一律出草稿,不自动改本机技能;批准才落地(除非显式开 share.autoApply)
|
|
887
|
+
// ③ 别人的特征文件夹全程只读:本模块的写操作全在自己那一格里
|
|
619
888
|
const runSkillsPull = async (reason) => {
|
|
620
889
|
try {
|
|
621
890
|
const state = loadState();
|
|
622
|
-
|
|
623
|
-
if (!master || !fs.existsSync(master)) {
|
|
624
|
-
log(`技能拉取跳过:母版不可用(${master || '未设置'})`);
|
|
891
|
+
if (state.skills.enabled === false && reason !== '手动') {
|
|
625
892
|
return null;
|
|
626
893
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
//
|
|
630
|
-
// 两条纪律:
|
|
631
|
-
// ① **起草不能阻塞同步**。一轮最多 6 条、每条一次模型调用几十秒,串进同步里
|
|
632
|
-
// 会让 pull/push 卡两分钟以上,看起来像同步挂了。所以起草放在后台跑,
|
|
633
|
-
// 同步立刻继续;后台那轮自己写账本、自己落地。
|
|
634
|
-
// ② 后台同一时刻只允许跑一轮,用 inFlight 挡住重入。
|
|
635
|
-
if (state.skills.mnemonOptimize !== false) {
|
|
636
|
-
if (optimizeInFlight) {
|
|
637
|
-
log('优化闭环:上一轮还在跑,本轮跳过起草');
|
|
638
|
-
} else {
|
|
639
|
-
optimizeInFlight = true;
|
|
640
|
-
// 先把「不需要模型」的部分做完(对账结案、已有草稿审核落地),这一步很快
|
|
641
|
-
runOptimizationCycle({
|
|
642
|
-
localRoot: LOCAL_SKILLS_ROOT,
|
|
643
|
-
masterRoot: master,
|
|
644
|
-
stageDir: STAGE_DIR,
|
|
645
|
-
minConfidence: Number(state.skills.mnemonMinConfidence) || 0.7,
|
|
646
|
-
enabled: true,
|
|
647
|
-
draft: null,
|
|
648
|
-
onEvent: (kind, payload) => {
|
|
649
|
-
if (kind === 'landed') log(`优化闭环落地: ${payload.skillName}`);
|
|
650
|
-
},
|
|
651
|
-
})
|
|
652
|
-
.then((r) => {
|
|
653
|
-
if (r.closed.length || r.accepted.length) {
|
|
654
|
-
log(
|
|
655
|
-
`优化闭环(无模型段):结案 ${r.closed.length}、落地 ${r.accepted.length}、剩余 ${r.remaining}`,
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
// 再把这批「需要模型」的丢到后台
|
|
659
|
-
return runOptimizationCycle({
|
|
660
|
-
localRoot: LOCAL_SKILLS_ROOT,
|
|
661
|
-
masterRoot: master,
|
|
662
|
-
stageDir: STAGE_DIR,
|
|
663
|
-
minConfidence: Number(state.skills.mnemonMinConfidence) || 0.7,
|
|
664
|
-
enabled: true,
|
|
665
|
-
draft: createHostDrafter(ctx, { logger: (m) => log(`[draft] ${m}`) }),
|
|
666
|
-
maxDrafts: Number(state.skills.mnemonMaxDrafts) || 6,
|
|
667
|
-
onEvent: (kind, payload) => {
|
|
668
|
-
if (kind === 'landed' || kind === 'drafting' || kind === 'audit-fail' || kind === 'parse-fail') {
|
|
669
|
-
log(`优化闭环 ${kind}: ${payload.skillName}`);
|
|
670
|
-
}
|
|
671
|
-
},
|
|
672
|
-
});
|
|
673
|
-
})
|
|
674
|
-
.then((r) => {
|
|
675
|
-
log(
|
|
676
|
-
`优化闭环(起草段):起草 ${(r.pipeline?.stage?.drafted || []).length}、` +
|
|
677
|
-
`落地 ${r.accepted.length}、失败 ${(r.pipeline?.stage?.failed || []).length}、剩余 ${r.remaining}`,
|
|
678
|
-
);
|
|
679
|
-
for (const e of r.errors.slice(0, 6)) log(` ! ${e}`);
|
|
680
|
-
})
|
|
681
|
-
.catch((err) => log(`优化闭环失败:${err && err.message ? err.message : String(err)}`))
|
|
682
|
-
.finally(() => {
|
|
683
|
-
optimizeInFlight = false;
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
// 记忆插件草稿预处理(可关:state.skills.mnemonAuto === false)
|
|
689
|
-
const prepass = runMnemonPrepass({
|
|
690
|
-
masterRoot: master,
|
|
691
|
-
localRoot: LOCAL_SKILLS_ROOT,
|
|
692
|
-
modes: state.skills.modes || {},
|
|
693
|
-
machineId: state.machineId,
|
|
694
|
-
machineName: state.machineName,
|
|
695
|
-
enabled: state.skills.mnemonAuto !== false,
|
|
696
|
-
});
|
|
697
|
-
if (prepass.applied.length || prepass.skipped.length) {
|
|
698
|
-
log(
|
|
699
|
-
`记忆插件预处理:草稿 ${prepass.drafts}、落地 ${prepass.applied.length}、` +
|
|
700
|
-
`跳过 ${prepass.skipped.length}、推回母版 ${prepass.pushed.length}`,
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// 判新旧要的三份只读证据:谱系观测 + 记忆插件账本 + 历史快照
|
|
705
|
-
const lineageFile = path.join(master, LINEAGE_FILE);
|
|
706
|
-
const lineage = loadLineage(lineageFile);
|
|
707
|
-
const plan = planSync({
|
|
708
|
-
masterRoot: master,
|
|
709
|
-
localRoot: LOCAL_SKILLS_ROOT,
|
|
710
|
-
modes: (state.skills.modes || {}),
|
|
711
|
-
lineage,
|
|
712
|
-
mnemonLedger: readMnemonLedger(),
|
|
713
|
-
history: buildHistoryIndex({ masterRoot: master, localRoot: LOCAL_SKILLS_ROOT }),
|
|
714
|
-
});
|
|
715
|
-
const stamp = makeStamp();
|
|
716
|
-
|
|
717
|
-
// 母版更新 → 拉下来;本机更新(同步属性)→ 推上去。判不出来的一律不动。
|
|
718
|
-
const pulled = plan.pull.length
|
|
719
|
-
? applyPull({ items: plan.pull, localRoot: LOCAL_SKILLS_ROOT, stamp })
|
|
720
|
-
: { pulled: [], errors: [] };
|
|
721
|
-
|
|
722
|
-
let pushed = { pushed: [], errors: [] };
|
|
723
|
-
if (plan.autoPush.length) {
|
|
724
|
-
pushed = applyPush({
|
|
725
|
-
items: plan.autoPush,
|
|
726
|
-
masterRoot: master,
|
|
727
|
-
backRoot: path.join(master, '_backup'),
|
|
728
|
-
stamp,
|
|
729
|
-
});
|
|
730
|
-
const audit = pushed.pushed.map((p) => ({
|
|
731
|
-
skill: p.name,
|
|
732
|
-
operation: p.operation,
|
|
733
|
-
detail: `${p.operation === '覆盖' ? `旧版备份于 _backup/${p.name}.${stamp}` : '母版此前无此技能'}(自动:内容更新)`,
|
|
734
|
-
}));
|
|
735
|
-
if (audit.length) {
|
|
736
|
-
try {
|
|
737
|
-
appendMasterChanges(audit, {
|
|
738
|
-
masterRoot: master,
|
|
739
|
-
machineId: state.machineId,
|
|
740
|
-
machineName: state.machineName,
|
|
741
|
-
trigger: `自动同步(${reason})`,
|
|
742
|
-
});
|
|
743
|
-
} catch {
|
|
744
|
-
/* 记录写不进去不影响同步 */
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// 记观测 + 把已经对齐的登记成基线
|
|
750
|
-
let stamped = 0;
|
|
751
|
-
try {
|
|
752
|
-
let map = lineage;
|
|
753
|
-
const now = Date.now();
|
|
754
|
-
for (const [name, m] of plan.master) {
|
|
755
|
-
const l = plan.local.get(name);
|
|
756
|
-
if (!l) continue;
|
|
757
|
-
map = recordLineage(map, {
|
|
758
|
-
name,
|
|
759
|
-
...(m.treeHash === l.treeHash ? { baseline: m.treeHash } : {}),
|
|
760
|
-
master: m,
|
|
761
|
-
local: l,
|
|
762
|
-
by: `timer:${reason}`,
|
|
763
|
-
at: now,
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
const aligned = [
|
|
767
|
-
...plan.pull.map((x) => x.master),
|
|
768
|
-
...plan.autoPush.map((x) => x.local),
|
|
769
|
-
...plan.same.map((x) => x.master),
|
|
770
|
-
].filter(Boolean);
|
|
771
|
-
for (const x of aligned) {
|
|
772
|
-
map = recordLineage(map, { name: x.name, baseline: x.treeHash, master: x, local: x, by: `timer:${reason}`, at: now });
|
|
773
|
-
}
|
|
774
|
-
stamped = saveLineage(lineageFile, map) ? aligned.length : 0;
|
|
775
|
-
} catch {
|
|
776
|
-
/* 谱系写失败只影响判定精度,不影响主流程 */
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
const next = loadState();
|
|
780
|
-
next.skills.lastRunAt = Date.now();
|
|
781
|
-
next.skills.lastResult = {
|
|
782
|
-
pulled: pulled.pulled.length,
|
|
783
|
-
pushed: pushed.pushed.length,
|
|
784
|
-
pendingPush: plan.push.length,
|
|
785
|
-
errors: pulled.errors.length + pushed.errors.length + prepass.errors.length,
|
|
786
|
-
lineage: stamped,
|
|
787
|
-
reason,
|
|
788
|
-
// 起草已移到后台(见阶段 0),同步这一轮没有可报告的优化数据;
|
|
789
|
-
// 实况用 POST /skills/optimize/state 查。
|
|
790
|
-
optimize: null,
|
|
791
|
-
mnemon: {
|
|
792
|
-
drafts: prepass.drafts,
|
|
793
|
-
applied: prepass.applied.length,
|
|
794
|
-
pushed: prepass.pushed.length,
|
|
795
|
-
skipped: prepass.skipped.length,
|
|
796
|
-
},
|
|
797
|
-
};
|
|
798
|
-
const summary = `拉取 ${pulled.pulled.length}、推送 ${pushed.pushed.length}、待裁决 ${plan.push.length}`;
|
|
799
|
-
const mnemonNote = prepass.applied.length
|
|
800
|
-
? `;记忆插件草稿落地 ${prepass.applied.length}(推回母版 ${prepass.pushed.length})`
|
|
801
|
-
: '';
|
|
802
|
-
if (pulled.pulled.length || pushed.pushed.length || plan.push.length || prepass.applied.length) {
|
|
803
|
-
pushHistory(next, 'skills', `技能自动同步(${reason}):${summary}${mnemonNote}`);
|
|
804
|
-
}
|
|
805
|
-
saveState(next);
|
|
806
|
-
|
|
807
|
-
if (pulled.pulled.length || pushed.pushed.length || plan.push.length || prepass.applied.length) {
|
|
808
|
-
log(`技能自动同步:${summary}${mnemonNote}`);
|
|
809
|
-
}
|
|
810
|
-
return next.skills.lastResult;
|
|
894
|
+
const report = await runShare({ reason });
|
|
895
|
+
return report;
|
|
811
896
|
} catch (err) {
|
|
812
|
-
log(
|
|
897
|
+
log(`技能共享失败:${err && err.message ? err.message : String(err)}`);
|
|
813
898
|
return null;
|
|
814
899
|
}
|
|
815
900
|
};
|
|
@@ -823,7 +908,9 @@ export function apply(ctx, config = {}) {
|
|
|
823
908
|
try {
|
|
824
909
|
const state = loadState();
|
|
825
910
|
const cfg = state.skills || {};
|
|
826
|
-
|
|
911
|
+
const share = cfg.share || {};
|
|
912
|
+
// 没有母版了:开关只看 enabled,不再要求任何外部路径存在
|
|
913
|
+
if (cfg.enabled === false || share.enabled === false) {
|
|
827
914
|
booted = false;
|
|
828
915
|
nextDueAt = 0;
|
|
829
916
|
lastIntervalMs = 0;
|
|
@@ -834,31 +921,31 @@ export function apply(ctx, config = {}) {
|
|
|
834
921
|
|
|
835
922
|
// 间隔被改过:按新间隔从上次运行时间重算
|
|
836
923
|
if (lastIntervalMs && intervalMs !== lastIntervalMs) {
|
|
837
|
-
const lastRun = Number(
|
|
924
|
+
const lastRun = Number(share.lastRunAt) || 0;
|
|
838
925
|
nextDueAt = lastRun ? lastRun + intervalMs : Date.now();
|
|
839
926
|
}
|
|
840
927
|
lastIntervalMs = intervalMs;
|
|
841
928
|
|
|
842
|
-
//
|
|
929
|
+
// 开机首跑
|
|
843
930
|
if (!booted) {
|
|
844
931
|
booted = true;
|
|
845
|
-
runSkillsPull('开机').catch((err) => log('
|
|
932
|
+
runSkillsPull('开机').catch((err) => log('开机共享失败:' + (err && err.message ? err.message : String(err))));
|
|
846
933
|
nextDueAt = Date.now() + intervalMs;
|
|
847
934
|
return;
|
|
848
935
|
}
|
|
849
936
|
|
|
850
937
|
if (Date.now() < nextDueAt) return;
|
|
851
938
|
|
|
852
|
-
runSkillsPull('定时').catch((err) => log('
|
|
939
|
+
runSkillsPull('定时').catch((err) => log('定时共享失败:' + (err && err.message ? err.message : String(err))));
|
|
853
940
|
nextDueAt = Date.now() + intervalMs;
|
|
854
941
|
} catch (err) {
|
|
855
|
-
log(
|
|
942
|
+
log(`技能共享调度失败:${err && err.message ? err.message : err}`);
|
|
856
943
|
}
|
|
857
944
|
};
|
|
858
945
|
|
|
859
946
|
const timer = setInterval(tick, PATROL_TICK_MS);
|
|
860
947
|
if (typeof timer?.unref === 'function') timer.unref();
|
|
861
|
-
//
|
|
948
|
+
// 开机首跑稍晚一点,避开启动高峰
|
|
862
949
|
const boot = setTimeout(tick, 12 * 1000);
|
|
863
950
|
if (typeof boot?.unref === 'function') boot.unref();
|
|
864
951
|
|
|
@@ -868,6 +955,7 @@ export function apply(ctx, config = {}) {
|
|
|
868
955
|
};
|
|
869
956
|
});
|
|
870
957
|
|
|
958
|
+
|
|
871
959
|
log('已加载');
|
|
872
960
|
}
|
|
873
961
|
|