refactor(prompt): 提示词去工具化 + WebUI 插件状态按运行期真相展示

## 1. 系统提示词/人格瘦身(工具用法搬去 tooldef 与 skill)

用户指出:默认系统提示词与 personal 里有大量本该属于 skill / 工具描述的内容。
核对属实,且已造成**真实故障**:提示词教模型用 cmd_run,而 cmd 外置按需后
工具根本不在清单里 ⇒ 模型第一轮调用不存在的工具,拿到「工具 cmd_run 不存在」。

改动:
- kernelmod/agent.defaultSystemPrompt:删掉 output_* 用法、工具分类清单、
  cmd_run/terminal/files 策略、并行策略、媒体工具清单、emoji 风格要求。
  只留:身份 + 「工具描述与技能索引会注入,先读再用」+「只依据工具返回值作答」。
- config.DefaultPersonaPrompt:只留身份/称呼/语气 + 版本以运行时快照为准
  (后者由 TestDefaultPersonaPromptHasNoVersionLiterals 钉住)。
- 命令与文件策略下沉到工具描述:cmd_run 补「完整 bash,支持管道/heredoc/重定向」,
  files_write 补「原子替换 + 覆盖受保护路径前留档」。
- 对话时序块自带语义说明(原先靠提示词解释,提示词一精简就丢语义)。
- 文档新增「提示词分层」章节(中英),写明归属判据与这次的反面教材。

## 2. WebUI:插件「启动状态」按运行期真相展示

旧实现只看内核 status 的 loaded 一个布尔,三种形态被混成同一个「未加载」:
auto_start=false 的按需待命(正常)、该起没起(异常)、plugin_stop 后的运行态停止。
用户看到一屏「未加载」以为插件全坏了。

- sdk.PluginRuntimeInfo 新增 AutoStart / RuntimeStopped;registry 从清单填充。
- pluginmgr /plugins 列表暴露 auto_start / runtime_stopped / builtin / tool_count。
- webui 新增 POST /plugins/<name>/start|stop(运行态,不动持久禁用清单)。
- dashboard 状态列改为五态:已禁用 / 进程丢失 / 已运行 / 按需待命 / 未运行,
  并额外标注「自启 / 按需」;按钮改为 启动/停止(运行态)与 禁用/启用(持久态)分列。
- 新增两条回归判据(引用 auto_start/runtime_stopped/alive 字段;startPlugin 走 /start)。

## 验收(本机实例实测)

- files 保持 auto_start=false(按需);问 agent 写文件 → 它先 plugin_start cmd+files,
  再 files_write/files_read,内容校验一致;未用 shell 代替文件操作。
- WebUI 插件页截图确认:a2a/acp/agentcli 显示「已运行 自启」,
  ai_image 显示「按需待命 按需」+「启动」按钮。
- go test(config/agent core/plugin/pluginmgr/webui/sdk)全绿。
This commit is contained in:
root
2026-10-20 08:57:36 +08:00
parent 2aab90c508
commit b5f024b054
13 changed files with 332 additions and 71 deletions

View File

@ -5700,6 +5700,13 @@ function renderPlugins() {
disabledNames[d.name] = d;
});
var installedNames = (state.installedPlugins || []).map((p) => p.name);
// 安装清单(plugin.json + 运行期)按名字索引:它带着 auto_start /
// runtime_stopped / alive,是区分「按需待命」「运行态已停」「该起没起」
// 的唯一数据源。内核 status 只给 loaded,光看它三者会被混成「未加载」。
var installedByName = {};
(state.installedPlugins || []).forEach((p) => {
installedByName[p.name] = p;
});
var allPluginNames = {};
plugins.forEach((p) => {
allPluginNames[p.name] = true;
@ -5707,6 +5714,9 @@ function renderPlugins() {
state.disabledPlugins.forEach((d) => {
allPluginNames[d.name] = true;
});
installedNames.forEach((n) => {
allPluginNames[n] = true;
});
html +=
'<div class="card"><h2>' +
__("所有插件", "All Plugins") +
@ -5731,38 +5741,90 @@ function renderPlugins() {
names.forEach((name) => {
var isExternal = installedNames.indexOf(name) >= 0;
var isDisabled = disabledNames[name];
var meta = installedByName[name] || {};
var loaded = plugins.some((p) => p.name === name);
var statusHtml =
loaded && !isDisabled
? '<span class="badge badge-green">' +
__("已加载", "Loaded") +
"</span>"
: loaded && isDisabled
? '<span class="badge badge-yellow">' +
__("运行中(禁用待生效)", "Running (disable pending)") +
"</span>"
: isDisabled
? '<span class="badge badge-red">' +
__("已禁用", "Disabled") +
"</span>"
: '<span class="badge badge-gray">' +
__("未加载", "Not Loaded") +
"</span>";
// auto_start 缺省 true(内置插件与老清单都按自启算)。
var autoStart = meta.auto_start !== false;
var runtimeStopped = meta.runtime_stopped === true;
var crashed = loaded && meta.alive === false;
// 状态判定必须覆盖五种真实形态,缺一种就会把正常状态显示成故障:
// 已禁用 / 进程丢失 / 已运行 / 按需待命 / 该起没起
var statusHtml;
if (isDisabled && loaded) {
statusHtml =
'<span class="badge badge-yellow">' +
__("运行中(禁用待生效)", "Running (disable pending)") +
"</span>";
} else if (isDisabled) {
statusHtml =
'<span class="badge badge-red">' +
__("已禁用", "Disabled") +
"</span>";
} else if (crashed) {
statusHtml =
'<span class="badge badge-red">' +
__("进程丢失", "Process lost") +
"</span>";
} else if (loaded) {
statusHtml =
'<span class="badge badge-green">' +
(runtimeStopped
? __("已运行(停止待生效)", "Running (stop pending)")
: __("已运行", "Running")) +
"</span>";
} else if (!autoStart) {
// 按需插件不运行是**正常待命**,不是故障。旧版一律显示
// 「未加载」,用户以为插件坏了,实际它在等 plugin_start。
statusHtml =
'<span class="badge badge-blue">' +
__("按需待命", "On-demand") +
"</span>";
} else if (runtimeStopped) {
statusHtml =
'<span class="badge badge-yellow">' +
__("已停止(运行态)", "Stopped (runtime)") +
"</span>";
} else {
statusHtml =
'<span class="badge badge-gray">' +
__("未运行", "Not running") +
"</span>";
}
statusHtml +=
' <span class="badge badge-' +
(autoStart ? "green" : "gray") +
'">' +
(autoStart ? __("自启", "auto") : __("按需", "on-demand")) +
"</span>";
var actionsHtml = "";
if (loaded) {
actionsHtml +=
'<button class="btn btn-sm btn-warning" onclick="disablePlugin(\'' +
'<button class="btn btn-sm btn-warning" onclick="stopPlugin(\'' +
escHtml(name) +
'\')" style="margin-right:4px">' +
__("停止", "Stop") +
"</button>";
actionsHtml +=
'<button class="btn btn-sm btn-ghost" onclick="disablePlugin(\'' +
escHtml(name) +
'\')" style="margin-right:4px">' +
__("禁用", "Disable") +
"</button>";
} else {
} else if (isDisabled) {
actionsHtml +=
'<button class="btn btn-sm btn-success" onclick="enablePlugin(\'' +
escHtml(name) +
'\')" style="margin-right:4px">' +
__("启用", "Enable") +
"</button>";
} else {
// 未运行但未禁用:一律走**运行态启动**,不碰持久禁用清单。
actionsHtml +=
'<button class="btn btn-sm btn-success" onclick="startPlugin(\'' +
escHtml(name) +
'\')" style="margin-right:4px">' +
__("启动", "Start") +
"</button>";
}
if (isExternal) {
actionsHtml +=
@ -6043,6 +6105,46 @@ async function disablePlugin(name) {
}
}
// startPlugin / stopPlugin 是**运行态**启停(对应 plugin_start / plugin_stop):
// 只改现在跑不跑,不写持久禁用清单。与 enable/disable(持久态)分开,
// 避免「点了一下启动,重启后行为却变了」。
async function startPlugin(name) {
try {
await api("/plugins/" + encodeURIComponent(name) + "/start", {
method: "POST",
});
toast(__("已启动: ", "Started: ") + name);
state.kernel = await api("/kernel");
await loadInstalledPlugins();
renderPlugins();
} catch (e) {
toast(__("启动失败: ", "Start failed: ") + e.message, true);
}
}
async function stopPlugin(name) {
if (name === "webui") {
var r = confirm(
__(
"停止 WebUI 插件后此管理面板会立即断开,只能通过 CLI 命令 /plugin start webui 恢复。\n\n确定要停止吗?",
"Stopping the WebUI plugin disconnects this panel immediately. Recover with CLI /plugin start webui.\n\nStop it?",
),
);
if (!r) return;
}
try {
await api("/plugins/" + encodeURIComponent(name) + "/stop", {
method: "POST",
});
toast(__("已停止: ", "Stopped: ") + name);
state.kernel = await api("/kernel");
await loadInstalledPlugins();
renderPlugins();
} catch (e) {
toast(__("停止失败: ", "Stop failed: ") + e.message, true);
}
}
async function enablePlugin(name) {
try {
var r = await api("/plugins/" + encodeURIComponent(name) + "/enable", {