@noob-stupid/dsh-plugin-console 0.5.29 → 0.5.31
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 +7 -7
- package/README.zh.md +8 -8
- package/SECURITY.md +2 -2
- package/lib/client.js +91 -5
- package/lib/index.js +141 -141
- package/lib/server/domain/ai-run.js +1 -1
- package/lib/server/domain/ai.js +39 -11
- package/lib/server/domain/compat.js +69 -3
- package/lib/server/domain/components.js +1 -1
- package/lib/server/domain/dep-source.js +1 -1
- package/lib/server/domain/framework-cleanup.js +225 -0
- package/lib/server/domain/framework-residuals.js +59 -0
- package/lib/server/domain/framework.js +65 -6
- package/lib/server/domain/install-job.js +1 -1
- package/lib/server/domain/install.js +1 -1
- package/lib/server/domain/jobs.js +1 -1
- package/lib/server/domain/market.js +1 -1
- package/lib/server/domain/patch-composition-audit.js +218 -0
- package/lib/server/domain/patch.js +69 -16
- package/lib/server/domain/preset-audit.js +144 -0
- package/lib/server/domain/presets.js +1 -1
- package/lib/server/domain/release-source.js +3 -3
- package/lib/server/domain/repoland.js +1 -1
- package/lib/server/domain/runtime.js +1 -1
- package/lib/server/domain/skills.js +1 -1
- package/lib/server/domain/sources.js +1 -1
- package/lib/server/domain/suite.js +1 -1
- package/lib/server/infra/exec.js +24 -3
- package/lib/server/infra/framework-root.js +99 -0
- package/lib/server/infra/fsx.js +3 -3
- package/lib/server/infra/http.js +1 -1
- package/lib/server/infra/httpd.js +1 -1
- package/lib/server/infra/mask.js +1 -1
- package/lib/server/infra/package-resolve.js +73 -0
- package/lib/server/infra/paths.js +1 -1
- package/lib/server/infra/registry-versions.js +64 -0
- package/lib/server/infra/semver.js +1 -1
- package/lib/server/routes/compat.js +41 -1
- package/lib/server/routes/components.js +1 -1
- package/lib/server/routes/framework-preflight.js +43 -2
- package/lib/server/routes/framework-upgrade.js +16 -17
- package/lib/server/routes/framework.js +18 -2
- package/lib/server/routes/github-login.js +2 -2
- package/lib/server/routes/index.js +17 -2
- package/lib/server/routes/patch-audit.js +59 -0
- package/lib/server/routes/plugins.js +20 -2
- package/lib/server/routes/preset-audit.js +24 -0
- package/lib/server/routes/state.js +9 -3
- package/package.json +1 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// L2 · routes —— 补丁体检(GET /plugin-console/patch-audit;0.5.30 加法)
|
|
2
|
+
//
|
|
3
|
+
// 真机事故 2026-09-28:框架 0.2.0-rc.1 移除了 `@deepseek-ai/dsh-workflow-worker-thread`(引擎改为 ptc),
|
|
4
|
+
// 而 profile 补丁三处行仍指向旧包 → 该行解析失败 → 隔离组里 `workflowEngine` 没有提供方 →
|
|
5
|
+
// `dsh-tool-workflow` / `dsh-tool-ralph` 永久 waiting → 老会话 resume 失败
|
|
6
|
+
// (RemoteError: tool-workflow … waiting for workflowEngine)。
|
|
7
|
+
//
|
|
8
|
+
// 本路由把 domain/patch-composition-audit.js 的**纯判据**接到真实环境(真补丁文件 + 真 Node 解析器),
|
|
9
|
+
// 并且**只报告、不代改**:改名建议来自「真机确证表」(KNOWN_RENAMES),猜不到的只列候选 ——
|
|
10
|
+
// 绝不自动改写用户补丁(同 preset-rows.js 的判据文化:宁缺勿滥、不猜)。
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
13
|
+
import { dirname, join } from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
import { auditPatchText } from '../domain/patch-composition-audit.js'
|
|
16
|
+
import { frameworkBases } from '../infra/framework-root.js'
|
|
17
|
+
import { sendError, sendJson } from '../infra/httpd.js'
|
|
18
|
+
import { listAvailablePackages, makePackageResolver } from '../infra/package-resolve.js'
|
|
19
|
+
import { baseDirOf, findPatchPath } from '../infra/paths.js'
|
|
20
|
+
|
|
21
|
+
// 框架运行时根的取法已收敛到 infra/framework-root.js(0.5.30 改错:**同一份判据**必须同时供
|
|
22
|
+
// 只读体检与本文件、以及 domain/patch.js 的自愈使用 —— 各写一份的话,框架下次再挪目录只会有一处被修好;
|
|
23
|
+
// 2026-09-29 真机事故正是"自愈那处没跟上")。
|
|
24
|
+
|
|
25
|
+
/** profile 补丁 → 体检报告(只读;不写任何文件)。 */
|
|
26
|
+
async function routePatchAuditGet(req, res, rc) {
|
|
27
|
+
const ctx = rc.ctx
|
|
28
|
+
let patchPath = null
|
|
29
|
+
try { patchPath = findPatchPath(ctx) } catch {}
|
|
30
|
+
if (patchPath === null || !existsSync(patchPath)) {
|
|
31
|
+
sendError(res, 404, '找不到 profile 补丁文件(cordis.patch.yml)—— 无法体检')
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
const profileDir = dirname(patchPath)
|
|
35
|
+
let baseDir = null
|
|
36
|
+
try { baseDir = baseDirOf(rc?.deps?.baseUrl ?? rc?.ctx?.baseUrl ?? 'file:///') } catch { baseDir = null }
|
|
37
|
+
const fwBases = frameworkBases(baseDir, profileDir)
|
|
38
|
+
const bases = [profileDir, ...fwBases]
|
|
39
|
+
const resolve = makePackageResolver(bases)
|
|
40
|
+
const candidates = listAvailablePackages([join(profileDir, 'node_modules'), ...fwBases])
|
|
41
|
+
const text = readFileSync(patchPath, 'utf8')
|
|
42
|
+
const report = auditPatchText(text, {
|
|
43
|
+
resolve,
|
|
44
|
+
existsFile: (url) => { try { return existsSync(fileURLToPath(url)) } catch { return false } },
|
|
45
|
+
candidates,
|
|
46
|
+
})
|
|
47
|
+
sendJson(res, 200, {
|
|
48
|
+
ok: true,
|
|
49
|
+
patchPath,
|
|
50
|
+
bases,
|
|
51
|
+
rows: report.rows,
|
|
52
|
+
blockerCount: report.blockers.length,
|
|
53
|
+
warningCount: report.warnings.length,
|
|
54
|
+
blockers: report.blockers,
|
|
55
|
+
warnings: report.warnings,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { routePatchAuditGet }
|
|
@@ -5,6 +5,8 @@ import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs'
|
|
|
5
5
|
import { dirname, join } from 'node:path'
|
|
6
6
|
import { createRequire } from 'node:module'
|
|
7
7
|
import { checkPluginFrameworkCompat, probePluginImport, readCompatPending, rowIdModuleMap, writeCompatPending } from '../domain/compat.js'
|
|
8
|
+
import { cleanFrameworkBackups, cleanFrameworkTopLevel, readRollbackReferenced, reportFrameworkEntities, resolveFrameworkLayout } from '../domain/framework-cleanup.js'
|
|
9
|
+
import { currentFrameworkVersion } from '../domain/framework.js'
|
|
8
10
|
import { pnpmRemove } from '../domain/install-job.js'
|
|
9
11
|
import { curlManualInstall, readExtraBundleOwners } from '../domain/install.js'
|
|
10
12
|
import { disableEntry, enableEntry, readPatchState, removeDisableBlock, removeInsertRow } from '../domain/patch.js'
|
|
@@ -17,7 +19,7 @@ import { orderedRegistries, readSources } from '../domain/sources.js'
|
|
|
17
19
|
import { fetchJsonUrl } from '../infra/http.js'
|
|
18
20
|
import { sendError, sendJson } from '../infra/httpd.js'
|
|
19
21
|
import { removeDirVerifiedAsync } from '../infra/fsx.js'
|
|
20
|
-
import { dshHome, findPatchPath, pluginRoot, resolvePackageJson, rowIdOf } from '../infra/paths.js'
|
|
22
|
+
import { baseDirOf, dshHome, findPatchPath, pluginRoot, resolvePackageJson, rowIdOf } from '../infra/paths.js'
|
|
21
23
|
import { installJobs } from '../state.js'
|
|
22
24
|
|
|
23
25
|
/** 插件控制台自身的包名(撤销分支禁止删自己;@deepseek-ai/* 由通用护栏拒绝)。 */
|
|
@@ -504,7 +506,23 @@ async function routeCleanResiduals(req, res, rc) {
|
|
|
504
506
|
}
|
|
505
507
|
}
|
|
506
508
|
} catch {}
|
|
507
|
-
|
|
509
|
+
// ── scope 扩展(2026-09-28 加法):framework / frameworkBackups / pnpmEntities ──
|
|
510
|
+
// 默认(不传 scope)与以前**完全一致**:只清插件侧残留。framework 两档必须显式 confirm:true,
|
|
511
|
+
// 否则只出报告(dryRun)—— 高风险动作不许"点一下全干"。
|
|
512
|
+
const scope = Array.isArray(body?.scope) ? body.scope.filter((s) => typeof s === 'string' && s !== '') : ['plugin']
|
|
513
|
+
const extra = {}
|
|
514
|
+
if (scope.some((s) => s !== 'plugin')) {
|
|
515
|
+
const dryRun = body?.confirm !== true
|
|
516
|
+
let runningVersion = null
|
|
517
|
+
try { runningVersion = currentFrameworkVersion(ctx) } catch {}
|
|
518
|
+
const layout = resolveFrameworkLayout({ baseDir: baseDirOf(ctx?.baseUrl ?? 'file:///'), profileDir })
|
|
519
|
+
if (scope.includes('framework')) extra.framework = await cleanFrameworkTopLevel({ topScopeDir: layout.topScopeDir, dshLinkPath: layout.dshLinkPath, runningVersion, relink: body?.relink !== false, dryRun, stamp: Date.now() })
|
|
520
|
+
if (scope.includes('frameworkBackups')) extra.frameworkBackups = await cleanFrameworkBackups({ backupsRoot: join(dshHome(), 'plugin-console', 'framework-backups'), referenced: readRollbackReferenced(), keep: Number.isFinite(body?.keep) ? body.keep : 1, dryRun })
|
|
521
|
+
if (scope.includes('pnpmEntities')) extra.pnpmEntities = reportFrameworkEntities({ pnpmDir: layout.pnpmDir, runningVersion })
|
|
522
|
+
extra.scope = scope
|
|
523
|
+
extra.dryRun = dryRun
|
|
524
|
+
}
|
|
525
|
+
const payload = { ok: failed.length === 0, removed, kept, failed, count: removed.length, ...extra }
|
|
508
526
|
if (failed.length > 0) {
|
|
509
527
|
const detail = failed.slice(0, 3).map((f) => `${f.name}(${f.error ?? '未知原因'})`).join(';')
|
|
510
528
|
const keptNote = kept.length > 0 ? `;已在用插件 ${kept.length} 个(保留不删)` : ''
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// L2 · routes —— 预设声明行体检(GET /plugin-console/preset-audit;0.5.30 加法)
|
|
2
|
+
//
|
|
3
|
+
// 真机背景:框架 0.1.7-rc.1 起预设靠 profile 补丁里的**声明行**发现;预设目录自身后来被改成 v10,
|
|
4
|
+
// 而补丁里的行还指向 v1 → 老会话 resume 到错的模块(而且没有任何地方会报)。本路由**只读**出报告:
|
|
5
|
+
// 陈旧(stale)/ 缺失(missing)/ 悬空(orphan)/ 目标不存在(targetMissing)。
|
|
6
|
+
import { existsSync } from 'node:fs'
|
|
7
|
+
import { join } from 'node:path'
|
|
8
|
+
import { collectPresetAudit } from '../domain/preset-audit.js'
|
|
9
|
+
import { sendError, sendJson } from '../infra/httpd.js'
|
|
10
|
+
import { dshHome, findPatchPath } from '../infra/paths.js'
|
|
11
|
+
|
|
12
|
+
async function routePresetAuditGet(req, res, rc) {
|
|
13
|
+
let patchPath = null
|
|
14
|
+
try { patchPath = findPatchPath(rc.ctx) } catch {}
|
|
15
|
+
if (patchPath === null || !existsSync(patchPath)) {
|
|
16
|
+
sendError(res, 404, '找不到 profile 补丁文件(cordis.patch.yml)—— 无法体检')
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
const presetsRoot = join(dshHome(), '.agent-presets')
|
|
20
|
+
const report = collectPresetAudit({ presetsRoot, patchPath })
|
|
21
|
+
sendJson(res, 200, { ok: true, patchPath, presetsRoot, ...report })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { routePresetAuditGet }
|
|
@@ -28,7 +28,9 @@ async function routeStateGet(req, res, rc) {
|
|
|
28
28
|
const readExtraBundleRows = rc.deps.readExtraBundleRows
|
|
29
29
|
const patchPath = findPatchPath(ctx)
|
|
30
30
|
const patch = await readPatchState(patchPath)
|
|
31
|
-
// 补丁安全自愈(核心行误禁用恢复 /
|
|
31
|
+
// 补丁安全自愈(核心行误禁用恢复 / **确证**缺失模块行自动禁用)——每 2 分钟最多跑一次。
|
|
32
|
+
// 2026-09-30 改错:① 无真正需要修复的内容时零写盘(避免每 2 分钟无谓重写用户补丁);
|
|
33
|
+
// ② 写盘失败 / 解析不确定 / 框架自带包 → 如实回报(skipped / uncertain / writeError),不静默。
|
|
32
34
|
let patchHeal = null
|
|
33
35
|
try {
|
|
34
36
|
if (patchHealAt === null || Date.now() - patchHealAt > 120000) {
|
|
@@ -38,7 +40,11 @@ async function routeStateGet(req, res, rc) {
|
|
|
38
40
|
patchHeal = patchHealReport
|
|
39
41
|
}
|
|
40
42
|
if (patchHeal !== null) setPatchHealReport(patchHeal)
|
|
41
|
-
} catch {
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// 自愈本身抛异常也要如实进 patchHeal(过去这里 catch {} 静默吞掉,面板看不出任何异常)
|
|
45
|
+
patchHeal = { healed: [], autoDisabled: [], skipped: [], uncertain: [], healedAt: 0, written: false, writeError: String(error?.message ?? error) }
|
|
46
|
+
setPatchHealReport(patchHeal)
|
|
47
|
+
}
|
|
42
48
|
const extraRows = await readExtraBundleRows(dirname(patchPath))
|
|
43
49
|
const compatPending = readCompatPending()
|
|
44
50
|
const compatPendingRows = new Set((compatPending?.pending ?? []).filter((p) => (p.status ?? 'pending') === 'pending').map((p) => p.rowId))
|
|
@@ -91,7 +97,7 @@ async function routeStateGet(req, res, rc) {
|
|
|
91
97
|
const selfPkg = JSON.parse(readFileSync(join(pluginRoot(), 'package.json'), 'utf8'))
|
|
92
98
|
selfVersion = typeof selfPkg.version === 'string' ? selfPkg.version : null
|
|
93
99
|
} catch {}
|
|
94
|
-
sendJson(res, 200, { ok: true, entries, patchPath, compat, installJobs: jobs, recentFailures, pendingRestart, github: { loggedIn: auth.loggedIn, login: auth.login }, patch: { disables: patch.disables, forced: patch.forced, inserts: patch.inserts }, framework, patchHeal: patchHeal === null ? null : { healed: patchHeal.healed ?? [], autoDisabled: patchHeal.autoDisabled ?? [], healedAt: patchHeal.healedAt ?? 0 }, gating: gatingSummary(ctx, compatPending, entries), compatPending: compatPending === null ? null : { frameworkVersion: compatPending.frameworkVersion ?? null, upgradeFrom: compatPending.upgradeFrom ?? null, pending: (compatPending.pending ?? []).filter((p) => (p.status ?? 'pending') === 'pending').map((p) => ({ rowId: p.rowId, moduleName: p.moduleName, version: p.version ?? null, checkNote: p.checkNote ?? null, check: p.check ?? null, riskyApprovedAt: p.riskyApprovedAt ?? null, adoptable: adoptable.get(p.rowId) ?? null })) }, compatGate, rollback: rollbackRec, selfVersion, components: findComponents().map((c) => ({ id: c.id, name: c.name, kind: c.kind ?? 'server', pid: c.pid ?? null, port: c.port ?? null, healthUrl: c.healthUrl ?? null, uiUrl: compUiUrl(c), autoStart: c.autoStart === true })) })
|
|
100
|
+
sendJson(res, 200, { ok: true, entries, patchPath, compat, installJobs: jobs, recentFailures, pendingRestart, github: { loggedIn: auth.loggedIn, login: auth.login }, patch: { disables: patch.disables, forced: patch.forced, inserts: patch.inserts }, framework, patchHeal: patchHeal === null ? null : { healed: patchHeal.healed ?? [], autoDisabled: patchHeal.autoDisabled ?? [], healedAt: patchHeal.healedAt ?? 0, written: patchHeal.written === true, writeError: patchHeal.writeError ?? null, skippedFrameworkOwned: (patchHeal.skipped ?? []).map((s) => s.id ?? null), uncertain: (patchHeal.uncertain ?? []).map((u) => u.id ?? null) }, gating: gatingSummary(ctx, compatPending, entries), compatPending: compatPending === null ? null : { frameworkVersion: compatPending.frameworkVersion ?? null, upgradeFrom: compatPending.upgradeFrom ?? null, pending: (compatPending.pending ?? []).filter((p) => (p.status ?? 'pending') === 'pending').map((p) => ({ rowId: p.rowId, moduleName: p.moduleName, version: p.version ?? null, checkNote: p.checkNote ?? null, check: p.check ?? null, riskyApprovedAt: p.riskyApprovedAt ?? null, adoptable: adoptable.get(p.rowId) ?? null })) }, compatGate, rollback: rollbackRec, selfVersion, components: findComponents().map((c) => ({ id: c.id, name: c.name, kind: c.kind ?? 'server', pid: c.pid ?? null, port: c.port ?? null, healthUrl: c.healthUrl ?? null, uiUrl: compUiUrl(c), autoStart: c.autoStart === true })) })
|
|
95
101
|
return
|
|
96
102
|
}
|
|
97
103
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noob-stupid/dsh-plugin-console",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.31",
|
|
4
4
|
"description": "DSH 框架升级安全与插件升级门控:一键升级、失败自动回滚、升级后回滚上版、旧插件不适配自动禁用;内置多源插件市场为发现层,插件源全部可自定义,可指向公司内网私有源 / 私有索引 / 本地 Git 仓库,纯内网离线可用 | Framework upgrade safety & plugin version gating for DSH, with a customizable multi-source plugin market: point every source at internal mirrors or a local file:// repo for intranet-only, offline installs.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|