Problem / 问题
需求:希望每次发送 Prompt 前,把预先设定好的一段文本插入到本次会话内容的最前/后面,并且可以指定它以 system、user 或 assistant 的身份出现。典型场景是按"预设"切换开场上下文、人设或前置约束,并希望它以对话轮次(而不是系统提示)的形式被模型看到。
现状:插件体系里没有任何可用的注入点(已在安装版 0.14.2 上逐项核对):
-
插件 API 没有写入会话的入口。 plugin-host-process.js 的 buildApi() 与会话相关的只有三项:session.getLlmContext()(只读)、agent.complete()(另起一次独立补全,不影响当前会话)、agent.registerTool()(模型在回合内主动调用)。没有 session.appendMessage / prompt.prepend / history.push 之类的接口。
-
事件是单向的。 events.on() 注册的监听器返回值被宿主忽略(handleHostEvent 只调用监听器、不消费返回值),bus.publish 只在插件之间传递消息。二者都无法改变发往模型的请求。
-
运行时没有"请求前"钩子。 resources/agent-runtime/sidecar.js 里不存在 before_agent_start / beforePrompt / transformPrompt / prependMessage 等扩展点;会话历史只在运行时构造时读一次(historyToEntries()),用户输入由 prompt(input, userMessageId, durableTurnId) 直接送入 agent。独立 CLI 的 ~/.pi/agent/extensions/*.ts 钩子不会被 sidecar 加载,因此在 PI-Desktop 里不会触发。
-
agent.prompt.inject 权限管不到这里。 它目前只用于放行 contributes.skills:把技能目录插进 baseSystemPrompt(位置在 Project instructions 之前),且技能正文需要模型主动调用 Skill 工具才加载。既不能注入 user / assistant 消息,也无法插入到会话内容最前面。
-
存储也够不到。 插件 fs 网关的 root 只能是 workspace 或 userSelected,无法访问家目录;会话数据在宿主 SQLite(pi.sqlite)与运行时内存中,即使改写数据库也不会进入正在进行的请求。
-
现有近似方案的能力边界:把文本写入 ~/.pi/agent/AGENTS.md 的受管区块,依赖宿主每回合重新解析指令链来生效。但它只能产生 system 级、且固定落在 Project instructions 段落内的追加,无法产生 user/assistant 轮次,也无法位于"会话内容最前面"。
Proposed change / 期望改动
建议提供"prompt 层"(prompt layer)机制,两种形态可以只做其一:
方案 A(首选):声明式贡献 + 运行时覆盖
{
"permissions": ["agent.prompt.inject"],
"contributes": {
"promptLayers": [
{
"id": "preamble",
"role": "user",
"position": "start",
"text": "…",
"when": { "mode": ["agent"] }
}
]
}
}
// 动态文本(例如面板里切换预设后即时生效)
await pi.prompt.setLayers("preamble", {
role: "user", // system | user | assistant
position: "start", // start | end
text: "…",
enabled: true,
});
方案 B:仅运行时 RPC,不做 manifest 声明(实现更小,但插件必须在 onLoad 里注册)。
期望的语义(请一并明确,避免歧义):
- 临时性:层内容只对本次请求有效,不写入 transcript、不渲染成消息气泡、不计入用户轮次。
system 层:按声明顺序合并进 system prompt,位置在 Project instructions 之后(可与现有 pluginSkillsPrompt() 使用同一拼接点)。
user / assistant 层:插入消息数组的头部或尾部,且每回合重新计算,同一层不跨回合累积。
- 多插件确定性:排序键为
position → 插件 id → 声明顺序。
- 上限与校验:单层与总量的字节上限;不支持图片/附件;
text 与 path 二选一。
- provider 兼容性提示:部分 provider 对首条消息角色有要求(例如首条必须是
user),position: "start" + role: "assistant" 可能被拒;希望宿主侧做校验或给出明确错误码,而不是静默失败。
- 缓存影响:注入内容若落在前缀位置,会影响 prompt cache,建议在文档中说明。
- 安全:新增独立权限或复用
agent.prompt.inject(高风险);默认关闭;请求含插件注入内容时 UI 上要有可见标记(例如 composer 的提示),并写入审计事件。
建议的实现位置(沿用现有插件内容下发链路):宿主在组装运行时配置时已经把插件内容传给 sidecar(pluginTools、pluginSkills,sidecar 侧由 params.pluginSkills 接收),promptLayers 可走同一条路;拼装点在 sidecar 的 composeSystemPrompt()(system 层)与 prompt()(user/assistant 层,请求发出前)。
Alternatives / 其他方案
不做插件级注入,改为内置功能:在设置里提供"Prompt 层"(角色 + 位置 + 文本 + 开关),或支持"每条消息自动附加前缀"。对"按预设切换前置文本"这个具体需求,内置实现风险更低、落地更快;插件化主要对生态有价值。
Additional context / 补充信息
- 环境:PI-Desktop 0.14.2(Windows 11,安装版);
Problem / 问题
需求:希望每次发送 Prompt 前,把预先设定好的一段文本插入到本次会话内容的最前/后面,并且可以指定它以
system、user或assistant的身份出现。典型场景是按"预设"切换开场上下文、人设或前置约束,并希望它以对话轮次(而不是系统提示)的形式被模型看到。现状:插件体系里没有任何可用的注入点(已在安装版 0.14.2 上逐项核对):
插件 API 没有写入会话的入口。
plugin-host-process.js的buildApi()与会话相关的只有三项:session.getLlmContext()(只读)、agent.complete()(另起一次独立补全,不影响当前会话)、agent.registerTool()(模型在回合内主动调用)。没有session.appendMessage/prompt.prepend/history.push之类的接口。事件是单向的。
events.on()注册的监听器返回值被宿主忽略(handleHostEvent只调用监听器、不消费返回值),bus.publish只在插件之间传递消息。二者都无法改变发往模型的请求。运行时没有"请求前"钩子。
resources/agent-runtime/sidecar.js里不存在before_agent_start/beforePrompt/transformPrompt/prependMessage等扩展点;会话历史只在运行时构造时读一次(historyToEntries()),用户输入由prompt(input, userMessageId, durableTurnId)直接送入 agent。独立 CLI 的~/.pi/agent/extensions/*.ts钩子不会被 sidecar 加载,因此在 PI-Desktop 里不会触发。agent.prompt.inject权限管不到这里。 它目前只用于放行contributes.skills:把技能目录插进baseSystemPrompt(位置在 Project instructions 之前),且技能正文需要模型主动调用Skill工具才加载。既不能注入user/assistant消息,也无法插入到会话内容最前面。存储也够不到。 插件
fs网关的 root 只能是workspace或userSelected,无法访问家目录;会话数据在宿主 SQLite(pi.sqlite)与运行时内存中,即使改写数据库也不会进入正在进行的请求。现有近似方案的能力边界:把文本写入
~/.pi/agent/AGENTS.md的受管区块,依赖宿主每回合重新解析指令链来生效。但它只能产生 system 级、且固定落在 Project instructions 段落内的追加,无法产生 user/assistant 轮次,也无法位于"会话内容最前面"。Proposed change / 期望改动
建议提供"prompt 层"(prompt layer)机制,两种形态可以只做其一:
方案 A(首选):声明式贡献 + 运行时覆盖
{ "permissions": ["agent.prompt.inject"], "contributes": { "promptLayers": [ { "id": "preamble", "role": "user", "position": "start", "text": "…", "when": { "mode": ["agent"] } } ] } }方案 B:仅运行时 RPC,不做 manifest 声明(实现更小,但插件必须在
onLoad里注册)。期望的语义(请一并明确,避免歧义):
system层:按声明顺序合并进 system prompt,位置在 Project instructions 之后(可与现有pluginSkillsPrompt()使用同一拼接点)。user/assistant层:插入消息数组的头部或尾部,且每回合重新计算,同一层不跨回合累积。position→ 插件 id → 声明顺序。text与path二选一。user),position: "start"+role: "assistant"可能被拒;希望宿主侧做校验或给出明确错误码,而不是静默失败。agent.prompt.inject(高风险);默认关闭;请求含插件注入内容时 UI 上要有可见标记(例如 composer 的提示),并写入审计事件。建议的实现位置(沿用现有插件内容下发链路):宿主在组装运行时配置时已经把插件内容传给 sidecar(
pluginTools、pluginSkills,sidecar 侧由params.pluginSkills接收),promptLayers可走同一条路;拼装点在 sidecar 的composeSystemPrompt()(system 层)与prompt()(user/assistant 层,请求发出前)。Alternatives / 其他方案
不做插件级注入,改为内置功能:在设置里提供"Prompt 层"(角色 + 位置 + 文本 + 开关),或支持"每条消息自动附加前缀"。对"按预设切换前置文本"这个具体需求,内置实现风险更低、落地更快;插件化主要对生态有价值。
Additional context / 补充信息