@dsh-workbench/shared 0.1.15 → 0.1.17

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.
@@ -71,6 +71,7 @@ export declare const dictionaries: {
71
71
  readonly 'panels.market.sectionPending': "未安装";
72
72
  readonly 'panels.market.sectionShared': "共用依赖";
73
73
  readonly 'panels.market.sharedUsedBy': "供 {modules} 使用";
74
+ readonly 'panels.market.sharedFoot': "这一段不给卸载:它们跟着模块来,卸掉也会被装回来。要停用一份,就去关掉正在用它的那些模块。";
74
75
  readonly 'panels.market.listSeparator': "、";
75
76
  readonly 'panels.market.sharedMissing': "未装";
76
77
  readonly 'panels.market.sharedOutdated': "有新版";
@@ -98,8 +99,18 @@ export declare const dictionaries: {
98
99
  readonly 'panels.market.installedSection': "已安装";
99
100
  readonly 'panels.market.sectionLocal': "本地装的";
100
101
  readonly 'panels.market.sectionUnlisted': "不是从市场装的";
102
+ readonly 'panels.market.sectionPlugins': "已安装 opencli 插件";
101
103
  readonly 'panels.market.moduleOutdated': "有新版";
104
+ readonly 'panels.market.moduleRefused': "这个模块是给旧版本的工作台写的,现在用不了。更新它,或者卸载。";
105
+ readonly 'panels.market.moduleRefusedDetail': "装不上的原因:{reason}";
102
106
  readonly 'panels.market.moduleRestartHint': "装好了但还没生效 —— 重启一次 dsh 就会出来";
107
+ readonly 'panels.market.pluginLocalNoUpdate': "本地装的,没地方查新版,所以只给卸载";
108
+ readonly 'panels.market.pluginKeptInUse': "文件先留着:{names} 还要用它。";
109
+ readonly 'panels.market.pluginKeptUnsure': "文件先留着:这台机器上还有模块看不出用了什么,说不准谁在用它。";
110
+ readonly 'panels.market.pluginKeptHeld': "文件留着:你自己装的那一份,模块放手不动它。";
111
+ readonly 'panels.market.pluginKeptUnnamed': "文件先留着:看不出这是哪个插件,说不准谁在用它。";
112
+ readonly 'panels.market.pluginAlsoKnownAs': "包名是 {pkg}(更新与卸载按它来)";
113
+ readonly 'panels.market.pluginBroken': "这份装坏了:看不出它是个插件。可以卸载,或者去上面按包名再装一次。";
103
114
  readonly 'panels.market.restartNeeded': "新版装好了,重启一次才生效。";
104
115
  readonly 'panels.market.foot': "装好后到那个项目的面板页勾选它,模块才会出现。";
105
116
  readonly 'panels.market.recheck': "重新检查更新";
@@ -147,7 +158,11 @@ export declare const dictionaries: {
147
158
  readonly 'panels.market.add.problemInstalled': "该模块已安装";
148
159
  readonly 'panels.market.add.problemNotFound': "未找到相关模块";
149
160
  readonly 'panels.market.add.problemNotPackage': "该路径不存在或不是有效的模块包";
150
- readonly 'panels.market.add.problemNotBundle': "这个包没有声明组合包,无法作为模块安装:{reason}";
161
+ readonly 'panels.market.add.problemNotBundle': "这个包装不进这里:{reason}。要是它其实是个 opencli 插件,请改用包名(例如 opencli-plugin-cmdb)或者本地目录 —— 从 git 地址和 tarball 判断不出它是什么。";
162
+ readonly 'panels.market.add.problemNotWorkbenchModule': "这个包跟现在的工作台对不上,装上也用不了。换一个版本,或者找这个模块的作者。";
163
+ readonly 'panels.market.add.problemNotModuleOrPlugin': "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上。换一个版本,或者找这个包的作者。";
164
+ readonly 'panels.market.add.failureNotModuleOrPlugin': "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上";
165
+ readonly 'panels.market.add.installedPlugin': "装上了:opencli 插件 {name}";
151
166
  readonly 'panels.market.add.problemNetwork': "无法连接模块源,请检查网络后重试";
152
167
  readonly 'panels.market.add.problemUnknown': "无法获取模块信息:{reason}";
153
168
  readonly 'panels.market.add.failureNetwork': "网络连接失败";
@@ -178,6 +193,7 @@ export declare const dictionaries: {
178
193
  readonly 'panels.market.add.registryPnpm': "pnpm 自己配的源";
179
194
  readonly 'panels.market.add.check': "预检";
180
195
  readonly 'panels.market.add.checking': "正在预检…";
196
+ readonly 'panels.market.add.classifying': "判定中…";
181
197
  readonly 'panels.market.add.change': "改一下";
182
198
  readonly 'panels.market.add.previewTitle': "将要安装";
183
199
  readonly 'panels.market.add.fieldName': "包名";
@@ -313,7 +329,7 @@ export declare const dictionaries: {
313
329
  readonly 'panel.conversation': "对话";
314
330
  readonly 'panels.label': "全局面板";
315
331
  };
316
- en: Record<"time.now" | "time.minutes" | "time.hours" | "time.days" | "blocker.retry" | "blocker.copy" | "blocker.unknown.title" | "blocker.login.go" | "blocker.login.titleGeneric" | "blocker.login.title" | "blocker.login.manual" | "blocker.login.hint" | "blocker.forbidden.open" | "blocker.forbidden.titleGeneric" | "blocker.forbidden.title" | "blocker.forbidden.hint" | "blocker.timeout.hint" | "blocker.exec.hint" | "blocker.fix.install" | "blocker.fix.update" | "blocker.market.open" | "env.unit.failed" | "env.unit.failedHint" | "env.unit.missingPlugin" | "env.unit.hint" | "env.unit.missingCommands" | "env.unit.missingCommandsHint" | "env.unit.unchecked" | "env.unit.uncheckedHint" | "brand.name" | "toggle.open" | "toggle.collapse" | "project.label" | "workspace.section" | "project.none" | "project.empty" | "project.add" | "project.search.label" | "project.search.placeholder" | "project.search.empty" | "project.rename.row" | "project.rename.current" | "project.rename.title" | "project.rename.description" | "project.rename.field" | "project.rename.placeholder" | "project.rename.hint" | "project.rename.save" | "project.rename.saving" | "project.rename.cancel" | "project.rename.failed" | "session.new" | "session.new.label" | "session.empty" | "session.noProject" | "session.pinned" | "session.actions" | "status.idle" | "status.running" | "status.waitingApproval" | "status.planReview" | "status.waitingAnswer" | "status.completed" | "status.subagentsRunning.one" | "status.subagentsRunning.other" | "panels.title" | "panels.reset" | "panels.enabled" | "panels.disabled" | "panels.choose" | "panels.buildModule" | "panels.failed" | "panels.hintEffect" | "panels.dragHint" | "panels.unknownStep" | "panels.labelWithHint" | "panels.dep.failed" | "panels.dep.missingPlugin" | "panels.dep.missingCommands" | "panels.dep.unchecked" | "panels.tab.modules" | "panels.tab.market" | "panels.browseMarket" | "panels.noneInstalled" | "panels.noneInstalledHint" | "panels.market.installed" | "panels.market.outdated" | "panels.market.hasNew" | "panels.market.missing" | "panels.market.unchecked" | "panels.market.checking" | "panels.market.sectionInstalled" | "panels.market.sectionPending" | "panels.market.sectionShared" | "panels.market.sharedUsedBy" | "panels.market.listSeparator" | "panels.market.sharedMissing" | "panels.market.sharedOutdated" | "panels.market.sharedUnsatisfied" | "panels.market.sharedOk" | "panels.market.versionArrow" | "panels.market.lead" | "panels.market.install" | "panels.market.update" | "panels.market.uninstall" | "panels.market.uninstallLabel" | "panels.market.installing" | "panels.market.updating" | "panels.market.uninstalling" | "panels.market.confirmUninstallTitle" | "panels.market.confirmUninstallDetail" | "panels.market.confirmUninstallAck" | "panels.market.confirmYes" | "panels.market.confirmNo" | "panels.market.failed" | "panels.market.needEnv" | "panels.market.noSource" | "panels.market.badSource" | "panels.market.empty" | "panels.market.installedSection" | "panels.market.sectionLocal" | "panels.market.sectionUnlisted" | "panels.market.moduleOutdated" | "panels.market.moduleRestartHint" | "panels.market.restartNeeded" | "panels.market.foot" | "panels.market.recheck" | "panels.market.checkUpdates" | "panels.market.checkedAt" | "panels.market.justNow" | "panels.market.minutesAgo" | "panels.market.hoursAgo" | "panels.market.add.description" | "panels.market.add.specPlaceholder" | "panels.market.add.guideToggle" | "panels.market.add.guideHide" | "panels.market.add.guideIdTitle" | "panels.market.add.guideIdExample" | "panels.market.add.guideIdHint" | "panels.market.add.guideGitTitle" | "panels.market.add.guideGitExample" | "panels.market.add.guideGitHint" | "panels.market.add.guidePathTitle" | "panels.market.add.guidePathExample" | "panels.market.add.guidePathHint" | "panels.market.add.guideExampleLabel" | "panels.market.add.guideFill" | "panels.market.add.guideSafety" | "panels.market.add.registryLegend" | "panels.market.add.registryOfficial" | "panels.market.add.registryMirror" | "panels.market.add.registryWithHost" | "panels.market.add.registryToggle" | "panels.market.add.installVersion" | "panels.market.add.registryDefault" | "panels.market.add.registryCustom" | "panels.market.add.registryCustomPlaceholder" | "panels.market.add.registryCustomHint" | "panels.market.add.registryCustomInvalid" | "panels.market.add.run" | "panels.market.add.edit" | "panels.market.add.changeRegistry" | "panels.market.add.installingTitle" | "panels.market.add.installedTitle" | "panels.market.add.failedTitle" | "panels.market.add.cancelledTitle" | "panels.market.add.failureNetworkHost" | "panels.market.add.problemInvalid" | "panels.market.add.problemInstalled" | "panels.market.add.problemNotFound" | "panels.market.add.problemNotPackage" | "panels.market.add.problemNotBundle" | "panels.market.add.problemNetwork" | "panels.market.add.problemUnknown" | "panels.market.add.failureNetwork" | "panels.market.add.failureNotFound" | "panels.market.add.failureNoMatchingVersion" | "panels.market.add.failureDiskFull" | "panels.market.add.failurePermission" | "panels.market.add.failureBuildBlocked" | "panels.market.add.failureIntegrity" | "panels.market.add.failureTimeout" | "panels.market.add.failurePnpmMissing" | "panels.market.add.failureGeneric" | "panels.market.add.approveTitle" | "panels.market.add.approveCaution" | "panels.market.add.approveRun" | "panels.market.add.restart" | "panels.market.add.tookEffect" | "panels.market.add.details" | "panels.market.add.open" | "panels.market.add.title" | "panels.market.add.intro" | "panels.market.add.specLabel" | "panels.market.add.specHint" | "panels.market.add.exampleTitle" | "panels.market.add.exampleFill" | "panels.market.add.registryLabel" | "panels.market.add.registryProfile" | "panels.market.add.registryPnpm" | "panels.market.add.check" | "panels.market.add.checking" | "panels.market.add.change" | "panels.market.add.previewTitle" | "panels.market.add.fieldName" | "panels.market.add.fieldVersion" | "panels.market.add.fieldDescription" | "panels.market.add.fieldSource" | "panels.market.add.install" | "panels.market.add.installing" | "panels.market.add.cancel" | "panels.market.add.close" | "panels.market.add.retry" | "panels.market.add.changeSource" | "panels.market.add.approveBuilds" | "panels.market.add.approveHint" | "panels.market.add.doneApplied" | "panels.market.add.doneRestart" | "panels.market.add.cancelled" | "panels.market.add.failed" | "panels.market.add.removed" | "panels.market.add.supplyPending" | "panels.market.add.supplyFailed" | "panels.market.add.notModule" | "panels.market.add.removedRestart" | "panels.market.add.remove" | "panels.market.add.problem.invalidSpec" | "panels.market.add.problem.alreadyInstalled" | "panels.market.add.problem.notFound" | "panels.market.add.problem.notAPackage" | "panels.market.add.problem.notABundle" | "panels.market.add.problem.network" | "panels.market.add.problem.unknown" | "panels.market.add.reason.invalidSpec" | "panels.market.add.reason.incompatible" | "panels.market.add.reason.notBundle" | "panels.market.add.reason.notRemovable" | "panels.market.add.reason.bundleInUse" | "panels.market.add.reason.stopProfile" | "panels.market.add.reason.staleApproval" | "panels.market.add.reason.operationError" | "panels.market.add.reason.buildBlocked" | "panels.market.add.reason.pnpmMissing" | "panels.market.add.reason.noMatchingVersion" | "panels.market.add.reason.diskFull" | "panels.market.add.reason.permission" | "panels.market.add.reason.integrity" | "panels.market.add.reason.timeout" | "panels.market.add.reason.network" | "panels.market.add.reason.notFound" | "panels.market.add.reason.unknown" | "blocker.fix.running" | "blocker.fix.failed" | "blocker.retry.running" | "panels.reading" | "panels.cardBroken" | "panels.cardRetry" | "panels.empty" | "panels.emptyHint" | "env.title" | "env.checking" | "env.installing" | "env.failed" | "env.elapsed" | "env.stepFailed.install" | "env.stepFailed.path" | "env.stepFailed.shell" | "env.bridge.ok" | "env.bridge.short" | "env.bridge.why" | "env.bridge.daemon" | "env.bridge.extension" | "env.bridge.extensionLink" | "env.bridge.profile" | "env.bridge.stale" | "env.bridge.unknown" | "env.fix.title" | "env.fix.why" | "env.fix.whyPath" | "env.fix.whyShell" | "env.fix.step" | "env.fix.stepPath" | "env.fix.stepShell" | "env.fix.permission" | "env.fix.network" | "env.fix.details" | "env.retry" | "env.copyCommand" | "env.copied" | "env.step.opencli" | "env.step.codingPlugin" | "env.step.bridge" | "env.step.warm" | "env.warm.done" | "env.warm.short" | "rightbar.label" | "rightbar.expand" | "rightbar.collapse" | "rightbar.column.label" | "rightbar.column.workbench" | "rightbar.column.dsh" | "rightbar.refresh" | "rightbar.refreshing" | "rightbar.updatedAt" | "rightbar.noProject" | "rightbar.openPlatform" | "coding.notARepo.title" | "error.unknown" | "board.title" | "panel.conversation" | "panels.label", string>;
332
+ en: Record<"time.now" | "time.minutes" | "time.hours" | "time.days" | "blocker.retry" | "blocker.copy" | "blocker.unknown.title" | "blocker.login.go" | "blocker.login.titleGeneric" | "blocker.login.title" | "blocker.login.manual" | "blocker.login.hint" | "blocker.forbidden.open" | "blocker.forbidden.titleGeneric" | "blocker.forbidden.title" | "blocker.forbidden.hint" | "blocker.timeout.hint" | "blocker.exec.hint" | "blocker.fix.install" | "blocker.fix.update" | "blocker.market.open" | "env.unit.failed" | "env.unit.failedHint" | "env.unit.missingPlugin" | "env.unit.hint" | "env.unit.missingCommands" | "env.unit.missingCommandsHint" | "env.unit.unchecked" | "env.unit.uncheckedHint" | "brand.name" | "toggle.open" | "toggle.collapse" | "project.label" | "workspace.section" | "project.none" | "project.empty" | "project.add" | "project.search.label" | "project.search.placeholder" | "project.search.empty" | "project.rename.row" | "project.rename.current" | "project.rename.title" | "project.rename.description" | "project.rename.field" | "project.rename.placeholder" | "project.rename.hint" | "project.rename.save" | "project.rename.saving" | "project.rename.cancel" | "project.rename.failed" | "session.new" | "session.new.label" | "session.empty" | "session.noProject" | "session.pinned" | "session.actions" | "status.idle" | "status.running" | "status.waitingApproval" | "status.planReview" | "status.waitingAnswer" | "status.completed" | "status.subagentsRunning.one" | "status.subagentsRunning.other" | "panels.title" | "panels.reset" | "panels.enabled" | "panels.disabled" | "panels.choose" | "panels.buildModule" | "panels.failed" | "panels.hintEffect" | "panels.dragHint" | "panels.unknownStep" | "panels.labelWithHint" | "panels.dep.failed" | "panels.dep.missingPlugin" | "panels.dep.missingCommands" | "panels.dep.unchecked" | "panels.tab.modules" | "panels.tab.market" | "panels.browseMarket" | "panels.noneInstalled" | "panels.noneInstalledHint" | "panels.market.installed" | "panels.market.outdated" | "panels.market.hasNew" | "panels.market.missing" | "panels.market.unchecked" | "panels.market.checking" | "panels.market.sectionInstalled" | "panels.market.sectionPending" | "panels.market.sectionShared" | "panels.market.sharedUsedBy" | "panels.market.sharedFoot" | "panels.market.listSeparator" | "panels.market.sharedMissing" | "panels.market.sharedOutdated" | "panels.market.sharedUnsatisfied" | "panels.market.sharedOk" | "panels.market.versionArrow" | "panels.market.lead" | "panels.market.install" | "panels.market.update" | "panels.market.uninstall" | "panels.market.uninstallLabel" | "panels.market.installing" | "panels.market.updating" | "panels.market.uninstalling" | "panels.market.confirmUninstallTitle" | "panels.market.confirmUninstallDetail" | "panels.market.confirmUninstallAck" | "panels.market.confirmYes" | "panels.market.confirmNo" | "panels.market.failed" | "panels.market.needEnv" | "panels.market.noSource" | "panels.market.badSource" | "panels.market.empty" | "panels.market.installedSection" | "panels.market.sectionLocal" | "panels.market.sectionUnlisted" | "panels.market.sectionPlugins" | "panels.market.moduleOutdated" | "panels.market.moduleRefused" | "panels.market.moduleRefusedDetail" | "panels.market.moduleRestartHint" | "panels.market.pluginLocalNoUpdate" | "panels.market.pluginKeptInUse" | "panels.market.pluginKeptUnsure" | "panels.market.pluginKeptHeld" | "panels.market.pluginKeptUnnamed" | "panels.market.pluginAlsoKnownAs" | "panels.market.pluginBroken" | "panels.market.restartNeeded" | "panels.market.foot" | "panels.market.recheck" | "panels.market.checkUpdates" | "panels.market.checkedAt" | "panels.market.justNow" | "panels.market.minutesAgo" | "panels.market.hoursAgo" | "panels.market.add.description" | "panels.market.add.specPlaceholder" | "panels.market.add.guideToggle" | "panels.market.add.guideHide" | "panels.market.add.guideIdTitle" | "panels.market.add.guideIdExample" | "panels.market.add.guideIdHint" | "panels.market.add.guideGitTitle" | "panels.market.add.guideGitExample" | "panels.market.add.guideGitHint" | "panels.market.add.guidePathTitle" | "panels.market.add.guidePathExample" | "panels.market.add.guidePathHint" | "panels.market.add.guideExampleLabel" | "panels.market.add.guideFill" | "panels.market.add.guideSafety" | "panels.market.add.registryLegend" | "panels.market.add.registryOfficial" | "panels.market.add.registryMirror" | "panels.market.add.registryWithHost" | "panels.market.add.registryToggle" | "panels.market.add.installVersion" | "panels.market.add.registryDefault" | "panels.market.add.registryCustom" | "panels.market.add.registryCustomPlaceholder" | "panels.market.add.registryCustomHint" | "panels.market.add.registryCustomInvalid" | "panels.market.add.run" | "panels.market.add.edit" | "panels.market.add.changeRegistry" | "panels.market.add.installingTitle" | "panels.market.add.installedTitle" | "panels.market.add.failedTitle" | "panels.market.add.cancelledTitle" | "panels.market.add.failureNetworkHost" | "panels.market.add.problemInvalid" | "panels.market.add.problemInstalled" | "panels.market.add.problemNotFound" | "panels.market.add.problemNotPackage" | "panels.market.add.problemNotBundle" | "panels.market.add.problemNotWorkbenchModule" | "panels.market.add.problemNotModuleOrPlugin" | "panels.market.add.failureNotModuleOrPlugin" | "panels.market.add.installedPlugin" | "panels.market.add.problemNetwork" | "panels.market.add.problemUnknown" | "panels.market.add.failureNetwork" | "panels.market.add.failureNotFound" | "panels.market.add.failureNoMatchingVersion" | "panels.market.add.failureDiskFull" | "panels.market.add.failurePermission" | "panels.market.add.failureBuildBlocked" | "panels.market.add.failureIntegrity" | "panels.market.add.failureTimeout" | "panels.market.add.failurePnpmMissing" | "panels.market.add.failureGeneric" | "panels.market.add.approveTitle" | "panels.market.add.approveCaution" | "panels.market.add.approveRun" | "panels.market.add.restart" | "panels.market.add.tookEffect" | "panels.market.add.details" | "panels.market.add.open" | "panels.market.add.title" | "panels.market.add.intro" | "panels.market.add.specLabel" | "panels.market.add.specHint" | "panels.market.add.exampleTitle" | "panels.market.add.exampleFill" | "panels.market.add.registryLabel" | "panels.market.add.registryProfile" | "panels.market.add.registryPnpm" | "panels.market.add.check" | "panels.market.add.checking" | "panels.market.add.classifying" | "panels.market.add.change" | "panels.market.add.previewTitle" | "panels.market.add.fieldName" | "panels.market.add.fieldVersion" | "panels.market.add.fieldDescription" | "panels.market.add.fieldSource" | "panels.market.add.install" | "panels.market.add.installing" | "panels.market.add.cancel" | "panels.market.add.close" | "panels.market.add.retry" | "panels.market.add.changeSource" | "panels.market.add.approveBuilds" | "panels.market.add.approveHint" | "panels.market.add.doneApplied" | "panels.market.add.doneRestart" | "panels.market.add.cancelled" | "panels.market.add.failed" | "panels.market.add.removed" | "panels.market.add.supplyPending" | "panels.market.add.supplyFailed" | "panels.market.add.notModule" | "panels.market.add.removedRestart" | "panels.market.add.remove" | "panels.market.add.problem.invalidSpec" | "panels.market.add.problem.alreadyInstalled" | "panels.market.add.problem.notFound" | "panels.market.add.problem.notAPackage" | "panels.market.add.problem.notABundle" | "panels.market.add.problem.network" | "panels.market.add.problem.unknown" | "panels.market.add.reason.invalidSpec" | "panels.market.add.reason.incompatible" | "panels.market.add.reason.notBundle" | "panels.market.add.reason.notRemovable" | "panels.market.add.reason.bundleInUse" | "panels.market.add.reason.stopProfile" | "panels.market.add.reason.staleApproval" | "panels.market.add.reason.operationError" | "panels.market.add.reason.buildBlocked" | "panels.market.add.reason.pnpmMissing" | "panels.market.add.reason.noMatchingVersion" | "panels.market.add.reason.diskFull" | "panels.market.add.reason.permission" | "panels.market.add.reason.integrity" | "panels.market.add.reason.timeout" | "panels.market.add.reason.network" | "panels.market.add.reason.notFound" | "panels.market.add.reason.unknown" | "blocker.fix.running" | "blocker.fix.failed" | "blocker.retry.running" | "panels.reading" | "panels.cardBroken" | "panels.cardRetry" | "panels.empty" | "panels.emptyHint" | "env.title" | "env.checking" | "env.installing" | "env.failed" | "env.elapsed" | "env.stepFailed.install" | "env.stepFailed.path" | "env.stepFailed.shell" | "env.bridge.ok" | "env.bridge.short" | "env.bridge.why" | "env.bridge.daemon" | "env.bridge.extension" | "env.bridge.extensionLink" | "env.bridge.profile" | "env.bridge.stale" | "env.bridge.unknown" | "env.fix.title" | "env.fix.why" | "env.fix.whyPath" | "env.fix.whyShell" | "env.fix.step" | "env.fix.stepPath" | "env.fix.stepShell" | "env.fix.permission" | "env.fix.network" | "env.fix.details" | "env.retry" | "env.copyCommand" | "env.copied" | "env.step.opencli" | "env.step.codingPlugin" | "env.step.bridge" | "env.step.warm" | "env.warm.done" | "env.warm.short" | "rightbar.label" | "rightbar.expand" | "rightbar.collapse" | "rightbar.column.label" | "rightbar.column.workbench" | "rightbar.column.dsh" | "rightbar.refresh" | "rightbar.refreshing" | "rightbar.updatedAt" | "rightbar.noProject" | "rightbar.openPlatform" | "coding.notARepo.title" | "error.unknown" | "board.title" | "panel.conversation" | "panels.label", string>;
317
333
  };
318
334
  export type { WorkbenchMessages };
319
335
  export interface Dictionaries {
package/dist/i18n/zh.d.ts CHANGED
@@ -69,6 +69,7 @@ export declare const zh: {
69
69
  readonly 'panels.market.sectionPending': "未安装";
70
70
  readonly 'panels.market.sectionShared': "共用依赖";
71
71
  readonly 'panels.market.sharedUsedBy': "供 {modules} 使用";
72
+ readonly 'panels.market.sharedFoot': "这一段不给卸载:它们跟着模块来,卸掉也会被装回来。要停用一份,就去关掉正在用它的那些模块。";
72
73
  readonly 'panels.market.listSeparator': "、";
73
74
  readonly 'panels.market.sharedMissing': "未装";
74
75
  readonly 'panels.market.sharedOutdated': "有新版";
@@ -96,8 +97,18 @@ export declare const zh: {
96
97
  readonly 'panels.market.installedSection': "已安装";
97
98
  readonly 'panels.market.sectionLocal': "本地装的";
98
99
  readonly 'panels.market.sectionUnlisted': "不是从市场装的";
100
+ readonly 'panels.market.sectionPlugins': "已安装 opencli 插件";
99
101
  readonly 'panels.market.moduleOutdated': "有新版";
102
+ readonly 'panels.market.moduleRefused': "这个模块是给旧版本的工作台写的,现在用不了。更新它,或者卸载。";
103
+ readonly 'panels.market.moduleRefusedDetail': "装不上的原因:{reason}";
100
104
  readonly 'panels.market.moduleRestartHint': "装好了但还没生效 —— 重启一次 dsh 就会出来";
105
+ readonly 'panels.market.pluginLocalNoUpdate': "本地装的,没地方查新版,所以只给卸载";
106
+ readonly 'panels.market.pluginKeptInUse': "文件先留着:{names} 还要用它。";
107
+ readonly 'panels.market.pluginKeptUnsure': "文件先留着:这台机器上还有模块看不出用了什么,说不准谁在用它。";
108
+ readonly 'panels.market.pluginKeptHeld': "文件留着:你自己装的那一份,模块放手不动它。";
109
+ readonly 'panels.market.pluginKeptUnnamed': "文件先留着:看不出这是哪个插件,说不准谁在用它。";
110
+ readonly 'panels.market.pluginAlsoKnownAs': "包名是 {pkg}(更新与卸载按它来)";
111
+ readonly 'panels.market.pluginBroken': "这份装坏了:看不出它是个插件。可以卸载,或者去上面按包名再装一次。";
101
112
  readonly 'panels.market.restartNeeded': "新版装好了,重启一次才生效。";
102
113
  readonly 'panels.market.foot': "装好后到那个项目的面板页勾选它,模块才会出现。";
103
114
  readonly 'panels.market.recheck': "重新检查更新";
@@ -145,7 +156,11 @@ export declare const zh: {
145
156
  readonly 'panels.market.add.problemInstalled': "该模块已安装";
146
157
  readonly 'panels.market.add.problemNotFound': "未找到相关模块";
147
158
  readonly 'panels.market.add.problemNotPackage': "该路径不存在或不是有效的模块包";
148
- readonly 'panels.market.add.problemNotBundle': "这个包没有声明组合包,无法作为模块安装:{reason}";
159
+ readonly 'panels.market.add.problemNotBundle': "这个包装不进这里:{reason}。要是它其实是个 opencli 插件,请改用包名(例如 opencli-plugin-cmdb)或者本地目录 —— 从 git 地址和 tarball 判断不出它是什么。";
160
+ readonly 'panels.market.add.problemNotWorkbenchModule': "这个包跟现在的工作台对不上,装上也用不了。换一个版本,或者找这个模块的作者。";
161
+ readonly 'panels.market.add.problemNotModuleOrPlugin': "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上。换一个版本,或者找这个包的作者。";
162
+ readonly 'panels.market.add.failureNotModuleOrPlugin': "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上";
163
+ readonly 'panels.market.add.installedPlugin': "装上了:opencli 插件 {name}";
149
164
  readonly 'panels.market.add.problemNetwork': "无法连接模块源,请检查网络后重试";
150
165
  readonly 'panels.market.add.problemUnknown': "无法获取模块信息:{reason}";
151
166
  readonly 'panels.market.add.failureNetwork': "网络连接失败";
@@ -176,6 +191,7 @@ export declare const zh: {
176
191
  readonly 'panels.market.add.registryPnpm': "pnpm 自己配的源";
177
192
  readonly 'panels.market.add.check': "预检";
178
193
  readonly 'panels.market.add.checking': "正在预检…";
194
+ readonly 'panels.market.add.classifying': "判定中…";
179
195
  readonly 'panels.market.add.change': "改一下";
180
196
  readonly 'panels.market.add.previewTitle': "将要安装";
181
197
  readonly 'panels.market.add.fieldName': "包名";
package/dist/i18n/zh.js CHANGED
@@ -69,6 +69,7 @@ export const zh = {
69
69
  'panels.market.sectionPending': '未安装',
70
70
  'panels.market.sectionShared': '共用依赖',
71
71
  'panels.market.sharedUsedBy': '供 {modules} 使用',
72
+ 'panels.market.sharedFoot': '这一段不给卸载:它们跟着模块来,卸掉也会被装回来。要停用一份,就去关掉正在用它的那些模块。',
72
73
  'panels.market.listSeparator': '、',
73
74
  'panels.market.sharedMissing': '未装',
74
75
  'panels.market.sharedOutdated': '有新版',
@@ -96,8 +97,18 @@ export const zh = {
96
97
  'panels.market.installedSection': '已安装',
97
98
  'panels.market.sectionLocal': '本地装的',
98
99
  'panels.market.sectionUnlisted': '不是从市场装的',
100
+ 'panels.market.sectionPlugins': '已安装 opencli 插件',
99
101
  'panels.market.moduleOutdated': '有新版',
102
+ 'panels.market.moduleRefused': '这个模块是给旧版本的工作台写的,现在用不了。更新它,或者卸载。',
103
+ 'panels.market.moduleRefusedDetail': '装不上的原因:{reason}',
100
104
  'panels.market.moduleRestartHint': '装好了但还没生效 —— 重启一次 dsh 就会出来',
105
+ 'panels.market.pluginLocalNoUpdate': '本地装的,没地方查新版,所以只给卸载',
106
+ 'panels.market.pluginKeptInUse': '文件先留着:{names} 还要用它。',
107
+ 'panels.market.pluginKeptUnsure': '文件先留着:这台机器上还有模块看不出用了什么,说不准谁在用它。',
108
+ 'panels.market.pluginKeptHeld': '文件留着:你自己装的那一份,模块放手不动它。',
109
+ 'panels.market.pluginKeptUnnamed': '文件先留着:看不出这是哪个插件,说不准谁在用它。',
110
+ 'panels.market.pluginAlsoKnownAs': '包名是 {pkg}(更新与卸载按它来)',
111
+ 'panels.market.pluginBroken': '这份装坏了:看不出它是个插件。可以卸载,或者去上面按包名再装一次。',
101
112
  'panels.market.restartNeeded': '新版装好了,重启一次才生效。',
102
113
  'panels.market.foot': '装好后到那个项目的面板页勾选它,模块才会出现。',
103
114
  'panels.market.recheck': '重新检查更新',
@@ -145,7 +156,11 @@ export const zh = {
145
156
  'panels.market.add.problemInstalled': '该模块已安装',
146
157
  'panels.market.add.problemNotFound': '未找到相关模块',
147
158
  'panels.market.add.problemNotPackage': '该路径不存在或不是有效的模块包',
148
- 'panels.market.add.problemNotBundle': '这个包没有声明组合包,无法作为模块安装:{reason}',
159
+ 'panels.market.add.problemNotBundle': '这个包装不进这里:{reason}。要是它其实是个 opencli 插件,请改用包名(例如 opencli-plugin-cmdb)或者本地目录 —— 从 git 地址和 tarball 判断不出它是什么。',
160
+ 'panels.market.add.problemNotWorkbenchModule': '这个包跟现在的工作台对不上,装上也用不了。换一个版本,或者找这个模块的作者。',
161
+ 'panels.market.add.problemNotModuleOrPlugin': '这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上。换一个版本,或者找这个包的作者。',
162
+ 'panels.market.add.failureNotModuleOrPlugin': '这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上',
163
+ 'panels.market.add.installedPlugin': '装上了:opencli 插件 {name}',
149
164
  'panels.market.add.problemNetwork': '无法连接模块源,请检查网络后重试',
150
165
  'panels.market.add.problemUnknown': '无法获取模块信息:{reason}',
151
166
  'panels.market.add.failureNetwork': '网络连接失败',
@@ -176,6 +191,7 @@ export const zh = {
176
191
  'panels.market.add.registryPnpm': 'pnpm 自己配的源',
177
192
  'panels.market.add.check': '预检',
178
193
  'panels.market.add.checking': '正在预检…',
194
+ 'panels.market.add.classifying': '判定中…',
179
195
  'panels.market.add.change': '改一下',
180
196
  'panels.market.add.previewTitle': '将要安装',
181
197
  'panels.market.add.fieldName': '包名',
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from './api/host-write.js';
8
8
  export * from './api/long-task.js';
9
9
  export * from './api/unit-board.js';
10
10
  export * from './api/unit.js';
11
+ export * from './api/unit-declaration.js';
11
12
  export * from './api/workbench-client.js';
12
13
  export * from './api/workbench-contract.js';
13
14
  export * from './components/Activity.js';
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export * from './api/host-write.js';
8
8
  export * from './api/long-task.js';
9
9
  export * from './api/unit-board.js';
10
10
  export * from './api/unit.js';
11
+ export * from './api/unit-declaration.js';
11
12
  export * from './api/workbench-client.js';
12
13
  export * from './api/workbench-contract.js';
13
14
  export * from './components/Activity.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/shared",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "工作台与模块共享的纯代码与契约类型:类型、纯逻辑、哑组件、平台解析。宿主侧的东西从 /host 进(不影响浏览器产物)。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,227 @@
1
+ import type { UnitEnvNeeds, UnitHostNeeds, UnitManifest } from './unit.js';
2
+
3
+ export const WORKBENCH_API_VERSION = 1;
4
+
5
+ export const SUPPORTED_API_VERSIONS: readonly number[] = [WORKBENCH_API_VERSION];
6
+
7
+ export interface UnitCardDeclaration {
8
+ readonly titleKey: string;
9
+ readonly order: number;
10
+ }
11
+
12
+ export interface UnitToolDeclaration {
13
+ readonly name: string;
14
+ readonly writes: boolean;
15
+ }
16
+
17
+ export interface UnitDeclaration {
18
+ readonly apiVersion: number;
19
+ readonly unit: string;
20
+ readonly card: UnitCardDeclaration | null;
21
+ readonly tools: readonly UnitToolDeclaration[];
22
+ readonly host: UnitHostNeeds;
23
+ readonly env?: UnitEnvNeeds;
24
+ }
25
+
26
+ export const DECLARATION_KEYS: readonly string[] = [
27
+ 'apiVersion',
28
+ 'unit',
29
+ 'card',
30
+ 'tools',
31
+ 'host',
32
+ 'env',
33
+ ];
34
+
35
+ export const DECLARATION_REQUIRED_KEYS: readonly string[] = [
36
+ 'apiVersion',
37
+ 'unit',
38
+ 'card',
39
+ 'tools',
40
+ 'host',
41
+ ];
42
+
43
+ const CARD_KEYS = ['titleKey', 'order'];
44
+ const TOOL_KEYS = ['name', 'writes'];
45
+ const HOST_KEYS = ['inject', 'optional'];
46
+
47
+ function text(value: unknown): string {
48
+ return typeof value === 'string' ? value.trim() : '';
49
+ }
50
+
51
+ function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
52
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
53
+ }
54
+
55
+ function outsideKeys(value: Readonly<Record<string, unknown>>, allowed: readonly string[]): string[] {
56
+ return Object.keys(value).filter((key) => !allowed.includes(key));
57
+ }
58
+
59
+ function listOf(keys: readonly string[]): string {
60
+ return keys.map((key) => JSON.stringify(key)).join(' / ');
61
+ }
62
+
63
+ function stringArrayProblems(at: string, value: unknown): string[] {
64
+ if (!Array.isArray(value)) return [`${at} 要是一串字符串,现在是 ${JSON.stringify(value)}`];
65
+ return value.flatMap((item, index) =>
66
+ text(item) === '' ? [`${at}[${index}] 不是非空字符串:${JSON.stringify(item)}`] : [],
67
+ );
68
+ }
69
+
70
+ export function declarationProblemsOf(subject: string, raw: unknown): readonly string[] {
71
+ if (!isRecord(raw)) return [`${subject}: dsh.workbench 不是一个对象 —— ${JSON.stringify(raw)}`];
72
+
73
+ const version = raw.apiVersion;
74
+ if (version === undefined) {
75
+ return [
76
+ `${subject}: dsh.workbench 没有 apiVersion —— 协议从 v${WORKBENCH_API_VERSION} 起,` +
77
+ `写 \`"apiVersion": ${WORKBENCH_API_VERSION}\`。缺它 = 外壳不认这个模块(ADR-0038 §2.3)`,
78
+ ];
79
+ }
80
+ if (!Number.isInteger(version) || !SUPPORTED_API_VERSIONS.includes(version as number)) {
81
+ return [
82
+ `${subject}: dsh.workbench.apiVersion 是 ${JSON.stringify(version)},这里只认 ` +
83
+ `${SUPPORTED_API_VERSIONS.join(' / ')}(要整数)`,
84
+ ];
85
+ }
86
+
87
+ const problems: string[] = [];
88
+
89
+ const unknown = outsideKeys(raw, DECLARATION_KEYS);
90
+ if (unknown.length > 0) {
91
+ problems.push(
92
+ `${subject}: dsh.workbench 里有协议 v${WORKBENCH_API_VERSION} 不认识的键 ${listOf(unknown)} —— ` +
93
+ `只有 ${listOf(DECLARATION_KEYS)}。(拼错的键会被外壳**静默忽略**:那句声明等于没写)`,
94
+ );
95
+ }
96
+
97
+ for (const key of DECLARATION_REQUIRED_KEYS) {
98
+ if (raw[key] === undefined) {
99
+ problems.push(
100
+ `${subject}: dsh.workbench 缺 ${key}` +
101
+ (key === 'card' ? '(这个模块没有卡片就写 `"card": null`)' : ''),
102
+ );
103
+ }
104
+ }
105
+
106
+ const card = raw.card;
107
+ if (card !== undefined && card !== null) {
108
+ if (!isRecord(card)) {
109
+ problems.push(`${subject}: dsh.workbench.card 要么是对象、要么是 null,现在是 ${JSON.stringify(card)}`);
110
+ } else {
111
+ const extra = outsideKeys(card, CARD_KEYS);
112
+ if (extra.length > 0) {
113
+ problems.push(
114
+ `${subject}: dsh.workbench.card 里有不认的键 ${listOf(extra)} —— card 只有 ${listOf(CARD_KEYS)}` +
115
+ `(卡片默认显不显示由运行期的面板配置决定,清单里没有这一位)`,
116
+ );
117
+ }
118
+ if (text(card.titleKey) === '') {
119
+ problems.push(`${subject}: dsh.workbench.card.titleKey 要是非空字符串(面板那一格的文案键)`);
120
+ }
121
+ if (typeof card.order !== 'number' || !Number.isFinite(card.order)) {
122
+ problems.push(`${subject}: dsh.workbench.card.order 要是数字,现在是 ${JSON.stringify(card.order)}`);
123
+ }
124
+ }
125
+ }
126
+
127
+ const tools = raw.tools;
128
+ if (tools !== undefined) {
129
+ if (!Array.isArray(tools)) {
130
+ problems.push(`${subject}: dsh.workbench.tools 要是一串声明,现在是 ${JSON.stringify(tools)}`);
131
+ } else {
132
+ const seen = new Set<string>();
133
+ tools.forEach((tool, index) => {
134
+ const at = `dsh.workbench.tools[${index}]`;
135
+ if (!isRecord(tool)) {
136
+ problems.push(`${subject}: ${at} 要是对象,现在是 ${JSON.stringify(tool)}`);
137
+ return;
138
+ }
139
+ const extra = outsideKeys(tool, TOOL_KEYS);
140
+ if (extra.length > 0) {
141
+ problems.push(`${subject}: ${at} 里有不认的键 ${listOf(extra)} —— tools 只有 ${listOf(TOOL_KEYS)}`);
142
+ }
143
+ const name = text(tool.name);
144
+ if (name === '') {
145
+ problems.push(`${subject}: ${at}.name 要是非空字符串`);
146
+ } else if (seen.has(name)) {
147
+ problems.push(`${subject}: ${at}.name「${name}」与前面那条撞了 —— 工具名会进 dsh 的工具表,不许重`);
148
+ } else {
149
+ seen.add(name);
150
+ }
151
+ if (typeof tool.writes !== 'boolean') {
152
+ problems.push(
153
+ `${subject}: ${at}.writes 要是 true / false(它是"这会写"的唯一声明,缺了审批无从认领)`,
154
+ );
155
+ }
156
+ });
157
+ }
158
+ }
159
+
160
+ const host = raw.host;
161
+ if (host !== undefined) {
162
+ if (!isRecord(host)) {
163
+ problems.push(`${subject}: dsh.workbench.host 要是对象,现在是 ${JSON.stringify(host)}`);
164
+ } else {
165
+ const extra = outsideKeys(host, HOST_KEYS);
166
+ if (extra.length > 0) {
167
+ problems.push(`${subject}: dsh.workbench.host 里有不认的键 ${listOf(extra)} —— host 只有 ${listOf(HOST_KEYS)}`);
168
+ }
169
+ if (host.inject === undefined) {
170
+ problems.push(
171
+ `${subject}: dsh.workbench.host.inject 没写 —— 它声明这个模块要外壳的哪几个服务,` +
172
+ `外壳靠它判"能不能挂"。一个都不要也要写 \`"inject": []\``,
173
+ );
174
+ } else {
175
+ problems.push(...stringArrayProblems(`${subject}: dsh.workbench.host.inject`, host.inject));
176
+ }
177
+ if (host.optional !== undefined) {
178
+ problems.push(...stringArrayProblems(`${subject}: dsh.workbench.host.optional`, host.optional));
179
+ }
180
+ }
181
+ }
182
+
183
+ if (raw.env !== undefined && !isRecord(raw.env)) {
184
+ problems.push(
185
+ `${subject}: dsh.workbench.env 要是对象(command / plugins / libs)—— ` +
186
+ `语义由门禁的 env 判据查,这里只查形状`,
187
+ );
188
+ }
189
+
190
+ return problems;
191
+ }
192
+
193
+ export interface DeclarationRead {
194
+ readonly declaration?: UnitDeclaration;
195
+ readonly manifest?: UnitManifest;
196
+ readonly problems: readonly string[];
197
+ }
198
+
199
+ export function readDeclaration(subject: string, raw: unknown): DeclarationRead {
200
+ const problems = declarationProblemsOf(subject, raw);
201
+ if (problems.length > 0) return { problems };
202
+ const declaration = raw as UnitDeclaration;
203
+ const card = declaration.card;
204
+ return {
205
+ declaration,
206
+ problems: [],
207
+ manifest: {
208
+ id: text(declaration.unit),
209
+ card:
210
+ card === null
211
+ ? null
212
+ : {
213
+ titleKey: text(card.titleKey),
214
+ order: card.order,
215
+ },
216
+ tools: declaration.tools.map((tool) => ({
217
+ name: text(tool.name),
218
+ writes: tool.writes,
219
+ })),
220
+ host: {
221
+ inject: [...(declaration.host.inject ?? [])],
222
+ ...(declaration.host.optional === undefined ? {} : { optional: [...declaration.host.optional] }),
223
+ },
224
+ ...(declaration.env === undefined ? {} : { env: declaration.env }),
225
+ },
226
+ };
227
+ }
package/src/api/unit.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export interface UnitCard {
2
2
  readonly titleKey: string;
3
3
  readonly order: number;
4
- readonly enabledByDefault: boolean;
5
4
  }
6
5
 
7
6
  export interface UnitTool {
@@ -51,7 +50,6 @@ export interface PluginRelease {
51
50
 
52
51
  export interface UnitManifest {
53
52
  readonly id: string;
54
- readonly kind: 'board' | 'ui';
55
53
  readonly card: UnitCard | null;
56
54
  readonly tools: readonly UnitTool[];
57
55
  readonly host: UnitHostNeeds;
@@ -8,7 +8,9 @@ export type ProvisionAction =
8
8
  | 'install-bundle'
9
9
  | 'update-bundle'
10
10
  | 'remove-bundle'
11
- | 'cancel-install';
11
+ | 'cancel-install'
12
+ | 'update-plugin'
13
+ | 'uninstall-plugin';
12
14
 
13
15
  export interface PluginStatePayload {
14
16
  readonly name: string;
@@ -57,7 +57,7 @@ export function ApprovalDetail(props: ApprovalDetailProps) {
57
57
  if (owner?.approval !== undefined) {
58
58
  const Render = owner.approval.render;
59
59
  return (
60
- <CardBoundary unit={owner.manifest.id} resetKey={props.callId ?? ''} t={t}>
60
+ <CardBoundary unit={owner.unit} resetKey={props.callId ?? ''} t={t}>
61
61
  <Render input={input} t={t} />
62
62
  </CardBoundary>
63
63
  );
@@ -44,13 +44,12 @@ export interface BoardModule {
44
44
  readonly id: string;
45
45
  readonly titleKey: string;
46
46
  readonly order: number;
47
- readonly enabledByDefault: boolean;
48
47
  readonly View: (props: BoardModuleProps) => ReactNode;
49
48
  readonly steps?: Readonly<Record<string, UnitStep>>;
50
49
  }
51
50
 
52
51
  export interface UnitClientFace {
53
- readonly manifest: UnitManifest;
52
+ readonly unit: string;
54
53
  readonly card: (props: BoardModuleProps) => ReactNode;
55
54
  readonly steps?: Readonly<Record<string, UnitStep>>;
56
55
  readonly messages?: Dictionaries;
@@ -62,6 +61,10 @@ export interface UnitClientFace {
62
61
  | undefined;
63
62
  }
64
63
 
64
+ export interface UnitOwnedClientFace extends UnitClientFace {
65
+ readonly manifest: UnitManifest;
66
+ }
67
+
65
68
  export interface ApprovalInput {
66
69
  readonly tool: string;
67
70
  readonly args: Readonly<Record<string, unknown>>;
@@ -0,0 +1,94 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ heldEntriesOfHome,
4
+ heldEntryOfPlugin,
5
+ ledgerKeyOf,
6
+ parsePluginLedger,
7
+ pluginRetentionOf,
8
+ withReleasedEntry,
9
+ type PluginLedgerEntry,
10
+ } from '../plugin-ledger.js';
11
+
12
+ const HOME = '/h/workbench/opencli/home';
13
+
14
+ function entry(over: Partial<PluginLedgerEntry> = {}): PluginLedgerEntry {
15
+ return {
16
+ opencliHome: HOME,
17
+ package: 'opencli-plugin-cmdb',
18
+ installedAt: '2026-10-14T00:00:00.000Z',
19
+ ...over,
20
+ };
21
+ }
22
+
23
+ describe('台账条目的「放手」那一笔', () => {
24
+ it('老文件(没有 releasedAt)读出来还是"用户持着"(读旧数据,不是垫片)', () => {
25
+ const parsed = parsePluginLedger({ entries: [entry()] });
26
+ expect(parsed).toHaveLength(1);
27
+ expect(parsed[0]?.releasedAt).toBeUndefined();
28
+ expect(heldEntriesOfHome(parsed, HOME)).toHaveLength(1);
29
+ });
30
+
31
+ it('★ 放手了的那一笔还在文件里(自动清得靠它认得"这是我们装的"),但不再是"持着"', () => {
32
+ const released = withReleasedEntry([entry()], HOME, 'opencli-plugin-cmdb', '2026-10-15T00:00:00.000Z');
33
+
34
+ expect(released).toHaveLength(1);
35
+ expect(released[0]?.releasedAt).toBe('2026-10-15T00:00:00.000Z');
36
+ expect(heldEntriesOfHome(released, HOME)).toEqual([]);
37
+ });
38
+
39
+ it('记两次放手不覆盖第一次的时间(第二次是重放,不是新事实)', () => {
40
+ const once = withReleasedEntry([entry()], HOME, 'opencli-plugin-cmdb', '2026-10-15T00:00:00.000Z');
41
+ const twice = withReleasedEntry(once, HOME, 'opencli-plugin-cmdb', '2026-10-16T00:00:00.000Z');
42
+
43
+ expect(twice[0]?.releasedAt).toBe('2026-10-15T00:00:00.000Z');
44
+ });
45
+
46
+ it('另一个 home 的那一笔不受影响(隔离 wrapper 会换 home)', () => {
47
+ const other = entry({ opencliHome: '/h/global/opencli/home' });
48
+ const released = withReleasedEntry([entry(), other], HOME, 'opencli-plugin-cmdb', '2026-10-15T00:00:00.000Z');
49
+
50
+ expect(released.find((item) => item.opencliHome !== HOME)?.releasedAt).toBeUndefined();
51
+ expect(heldEntriesOfHome(released, other.opencliHome)).toHaveLength(1);
52
+ });
53
+
54
+ it('★ 按插件名找"用户持着的"那一份:插件名与包名两边都认(§3.4 那条映射)', () => {
55
+ const entries = [entry({ name: 'cmdb' })];
56
+
57
+ expect(heldEntryOfPlugin(entries, HOME, 'cmdb')?.package).toBe('opencli-plugin-cmdb');
58
+ expect(heldEntryOfPlugin(entries, HOME, 'opencli-plugin-cmdb')?.name).toBe('cmdb');
59
+ expect(heldEntryOfPlugin(entries, HOME, '别的')).toBeUndefined();
60
+ expect(heldEntryOfPlugin(entries, HOME, ' ')).toBeUndefined();
61
+ });
62
+
63
+ it('放手了的那一笔不再算"用户持着"(模块放手时就可以删它了)', () => {
64
+ const released = withReleasedEntry([entry({ name: 'cmdb' })], HOME, 'opencli-plugin-cmdb', '2026-10-15T00:00:00.000Z');
65
+
66
+ expect(heldEntryOfPlugin(released, HOME, 'cmdb')).toBeUndefined();
67
+ });
68
+
69
+ it('键还是「home + 包名」(同一份文件里两个 home 不互相撒谎)', () => {
70
+ expect(ledgerKeyOf({ opencliHome: '/a', package: 'x' })).not.toBe(
71
+ ledgerKeyOf({ opencliHome: '/b', package: 'x' }),
72
+ );
73
+ });
74
+ });
75
+
76
+ describe('物理去留:由谁主张说话(动作与结果分层那条)', () => {
77
+ it('用户还持着 → 留着(模块放手不动它)', () => {
78
+ expect(pluginRetentionOf({ held: true, claimedBy: [], trustworthy: true })).toBe('keep-held');
79
+ expect(pluginRetentionOf({ held: true, claimedBy: ['x'], trustworthy: false })).toBe('keep-held');
80
+ });
81
+
82
+ it('★ 不是用户持的、引用又不可信 → 一律不删(宁可留一个孤儿,也不删别人的文件)', () => {
83
+ expect(pluginRetentionOf({ held: false, claimedBy: [], trustworthy: false })).toBe('keep-unsure');
84
+ expect(pluginRetentionOf({ held: false, claimedBy: ['x'], trustworthy: false })).toBe('keep-unsure');
85
+ });
86
+
87
+ it('还有模块在用 → 留着(点了卸载,文件也不动)', () => {
88
+ expect(pluginRetentionOf({ held: false, claimedBy: ['issues'], trustworthy: true })).toBe('keep-claimed');
89
+ });
90
+
91
+ it('★ 引用可信 + 没人主张 → 这才是唯一敢删的那一格', () => {
92
+ expect(pluginRetentionOf({ held: false, claimedBy: [], trustworthy: true })).toBe('remove');
93
+ });
94
+ });