01. IPC 协议
翻译说明: 本页是与 英文源规格 一一对应的机器辅助翻译。代码、协议字段和标识符保持原文;如翻译与英文源事实有歧义,以英文版本为准。
1. 目标
定义渲染器和主程序之间的稳定契约。
原则:
- 所有功能均经过 preload 许可名单
- 输入 Requests/responses
- 长时间运行的任务使用事件流,而不是单个超大响应
- 错误必须有代码+消息
2. API 组
| 域名 | 描述 |
|---|---|
app | 应用程序信息、健康检查 |
agent | 对话、中止、状态和交互式 Asktool 解决方案 |
plan | Plan 提案列出、决议和变更事件 |
session | 会话 CRUD/历史记录 |
settings | 配置 read/write |
secrets | 秘密 write/delete/exists(绝不将明文返回到 UI 日志) |
project | 工作空间选择与查询 |
tool | 权限确认回调 |
shell | 主机 shell 目录和持久默认 shell |
log | 前端可以显示的诊断信息 |
plugin | 插件 install/enable-disable/query/permissions |
commandPalette | 命令面板搜索和执行 |
workspace | 工作区选择和遗留工作树诊断 |
terminal | 工作面板 PTY create/write/resize/dispose + data/exit 事件 |
browser | 工作面板嵌入预览 navigation/bounds/visibility + 状态事件 |
fs | 工作面板工作区文件 listing/reading/reveal(只读) |
window | 无框窗口状态、控件和有界工作面板宽度预留 |
menu | 列入许可名单的应用程序菜单命令和本机 editing/window 操作 |
notification | 持久收件箱 list/read/clear 和 new/activated 事件 |
3. 渠道约定
invoke: pi-desktop/<domain>/<action>
event: pi-desktop/<domain>/event/<name>示例:
pi-desktop/agent/promptpi-desktop/agent/abortpi-desktop/agent/event/messagepi-desktop/agent/askTool/resolvepi-desktop/session/listpi-desktop/project/openpi-desktop/project/openFolder
4. 通用响应包络
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: AppError };
type AppError = {
code: string;
message: string;
details?: unknown;
retriable?: boolean;
};5. Agent API
5. 1提示
type AgentPromptRequest = {
sessionId: string;
content: string;
/** Truncate durable transcript to N leading messages before append (regenerate). */
truncateBefore?: number;
};
type AgentPromptResponse = {
accepted: boolean;
turnId: string;
};斜线模板扩展 (D123):当 content 以 /name 开头且 名称与加载的 pi 提示模板匹配,主进程处理程序展开 持久化之前调用 (parseCommandArgs + substituteArgs)。 持久化的用户消息存储 content = expanded text 以及一个可选的 command: string 字段携带转录的键入调用 显示。重新设定种子会重播 content,因此代理上下文在整个过程中是相同的 重新启动。 Builtin/plugin 斜杠别名永远不会到达此通道 — 渲染器在本地执行它们。未知的 /foo 作为文字传递 内容。 @path 令牌不会在管道 (D124) 中的任何位置进行转换。
提示执行解析 mode、providerId、modelId 和 thinkingLevel 从持久会话记录和快照中获取有效的命令 shell ID 和 Bash 的方言。 渲染器通过以下方式更改这些值 会话空闲时的 pi-desktop/session/configure:
type ThinkingLevel =
| "off" | "minimal" | "low" | "medium"
| "high" | "xhigh" | "max";
type SessionConfigureRequest = {
id: string;
mode: "plan" | "goal" | "agent";
providerId?: string;
modelId?: string;
thinkingLevel: ThinkingLevel;
};仅当会话空闲时才接受 session/configure。模式、提供商、 模型、权限和 shell 默认更改在回合或回合时被拒绝 Plan/Goal content = expanded text/command: string/content 记录存在。渲染器可能会保留这些 控制在回合期间可编辑,但它会将最新的完整配置排队 本地并仅在终止事件后调用此通道;跑步的 回合永远不会观察到乐观的下一个回合选择。
只有更改后的有效全局 defaultCommandShell 在所有范围内仅处于空闲状态 受影响的会话:任何活动轮次或 pending/queued/running Plan/Goal 工作块 该 shell 会发生变化,而省略的或幂等的 shell 字段则不会。
图像和文件有效负载不是当前提示合同的一部分。
重新生成历史记录 (D109) 也使用会话通道:
pi-desktop/session/saveRevisionpi-desktop/session/listRevisionspi-desktop/session/activateRevision
Root 用户轮次可能包括 revisionRootId、revisionCount 和 activeRevision。激活修订版将实时尾部替换为 prefix + archived branch 并处置会话代理。 输入框 附件可供性保持隐藏,直到 main、sidecar、pi 模型 功能和持久性都会消耗有效负载。
5. 2 中止
type AgentAbortRequest = {
sessionId: string;
turnId?: string;
};中止请求和响应不携带 Composer 草稿或文件参考数据。 如果渲染器智能停止撤消未应答的用户回合,则恢复来自 渲染器的 session/turn-scoped 预序列化快照;现有的 转录重写会删除发送的行而不更改协议版本。
5. 3 紧凑型(协议 v9)
type AgentCompactRequest = { sessionId: string };
type AgentCompactResponse = { accepted: boolean };pi-desktop/agent/compact 为空闲创建模型上下文检查点 会议。即使自动上下文保护被禁用,它也可用。 缺少 provider/session 配置无法通过正常的 AppError 信封;主动转向或压实返回 AGENT_BUSY。
5. 4 Plan 和 Goal 检查点批准
合同批准与工具许可是分开的。 Plan 和 Goal 分享此内容 整个表面; kind 是唯一的鉴别器 (D198)。渲染器接收 的 来自同一 Agent 的主机写入的工件元数据并通过以下方式解析它 输入 preload IPC;它永远不会乐观地改变会话模式。合同 条目 并且提交仍然是 Agent/host 操作,而不是渲染器 preload 方法。
type PlanningState = "inactive" | "planning" | "awaiting_approval";
type ProposalKind = "plan" | "goal";
type GlobalPermissionMode = "ask" | "accept-edits" | "auto";
type PlanApprovalAction = "approve" | "reject";
type PlanProposalStatus =
| "pending" | "approved" | "rejected"
| "expired" | "interrupted";
type PlanExecutionState =
| "queued" | "running" | "completed" | "interrupted";
// Same shape for SubmitPlan and SubmitGoal; the tool name selects the kind.
type SubmitPlanInput = {
title: string;
markdown: string;
question: string;
};
type PlanArtifact = {
relativePath: string; // `.pi/plan/<unique-name>.md` or `.pi/goal/<unique-name>.md`
sha256: string;
sizeBytes: number;
};
type PlanProposal = {
id: string;
sessionId: string;
turnId: string;
toolCallId: string;
// Legacy rows written before the discriminator existed read back as `plan`.
kind: ProposalKind;
plan: string;
markdown: string;
title: string;
question: string;
status: PlanProposalStatus;
createdAt: string;
updatedAt: string;
expiresAt?: string;
resolvedAt?: string;
action?: PlanApprovalAction;
targetPermissionMode?: GlobalPermissionMode;
errorCode?: string;
artifact?: PlanArtifact;
version: number;
executionId?: string;
executionState?: PlanExecutionState;
};
type PlanExecution = {
id: string;
proposalId: string;
sessionId: string;
kind: ProposalKind;
plan: string;
title: string;
question: string;
artifact: PlanArtifact;
targetPermissionMode: GlobalPermissionMode;
state: PlanExecutionState;
};
type PlanningStateEvent = {
sessionId: string;
state: PlanningState;
// Absent only for `inactive` transitions that carry no proposal.
kind?: ProposalKind;
proposalId?: string;
title?: string;
markdown?: string;
question?: string;
artifact?: PlanArtifact;
version?: number;
plan?: string;
action?: PlanApprovalAction;
targetPermissionMode?: GlobalPermissionMode;
executionId?: string;
executionState?: PlanExecutionState;
proposal?: PlanProposal;
};
type PlansPendingResult = {
plans: PlanProposal[];
state?: PlanningState;
// The contract being negotiated, for mode chip and approval copy.
kind?: ProposalKind;
};
type PlanResolveIdentity = {
proposalId: string;
sessionId: string;
turnId: string;
toolCallId: string;
version?: number;
};
type PlanResolveRequest =
| (PlanResolveIdentity & {
action: "approve";
targetPermissionMode: GlobalPermissionMode;
})
| (PlanResolveIdentity & { action: "reject" });
type PlanResolutionResult = {
ok: boolean;
proposal: PlanProposal;
state: PlanningState;
action?: PlanApprovalAction;
targetPermissionMode?: GlobalPermissionMode;
execution?: PlanExecution;
};预加载方法:
pi-desktop/plans/pending({ sessionId? }) -> PlansPendingResultpi-desktop/plans/resolve(PlanResolveRequest) -> PlanResolutionResult
Electron 将每个主机 plans.changed 通知原封不动地转发到 通过稳定的共享 IPC.event.plansChanged 通道渲染器 (pi-desktop/plans/event/changed)。这是 Plan/Goal 更改事件表面; 的 渲染器不会接收作为 AgentEvent 变体的合同批准转换。 plans.pending 仅返回当前待批准的行。终端 plan_approvals 行保留持久主机记录,但不是渲染器 水合数据;渲染器仅保留其最新的合同快照 当实时 plans.changed 事件到达时当前渲染器的生命周期。
对于 approve、host-core 和 Electron 需要显式 targetPermissionMode; Electron 永远不会从存储的设置中填充它。的 渲染器将每个批准初始化为“询问”,这仍然是产品默认值, 并且主持人不会将选择保留为下一次批准默认值。 reject 携带无权限模式。 对错误提案、会话、回合、工具调用、版本或过期的响应 主机拥有的截止日期失败,并出现稳定的 Plan/Goal 批准错误。没有 请求更改操作。
5. 5 获取状态
type AgentStatus = {
sessionId: string;
isRunning: boolean;
currentTurnId?: string;
modelId?: string;
pendingToolConfirmations: number;
};6. Agent 活动
从主→渲染器推送:
type AgentEventEnvelope = {
sessionId: string;
turnId?: string;
ts: number;
event: AgentEvent;
/** Set on events emitted inside a subagent (D201, ADR 0062): the `Task` call
* that spawned it, and the definition name. */
parentToolCallId?: string;
agentName?: string;
};
type AgentEvent =
| { type: "agent_start" }
| { type: "agent_end"; messageIds: string[] }
| { type: "turn_start" }
| { type: "turn_end" }
| { type: "message_start"; message: UiMessage }
| { type: "message_update"; message: UiMessage;
deltaText?: string; deltaThinking?: string }
| { type: "message_end"; message: UiMessage }
| { type: "tool_start"; toolCallId: string; toolName: string; args: unknown }
| { type: "tool_update"; toolCallId: string; partialResult?: unknown }
| { type: "tool_end"; toolCallId: string; result: unknown; isError?: boolean;
toolUsage?: ToolTokenUsage }
| ({ type: "planning_state" } & Omit<PlanningStateEvent, "sessionId">)
| { type: "tool_permission_request"; request: ToolPermissionRequest }
| { type: "compaction_start";
reason: "manual" | "threshold" | "overflow" }
| { type: "compaction_end";
reason: "manual" | "threshold" | "overflow";
ok: boolean; tokensBefore?: number; firstKeptMessageId?: string;
willRetry: boolean; fallback?: "retained_tail";
mark?: { id: string; throughMessageId: string;
generation: number; summaryTokens: number;
summarized: boolean };
error?: { code: string; message: string } }
| { type: "error"; error: AppError }
| { type: "status"; status: AgentStatus };这些是 UI 标准化事件,而不是原始 pi 事件的传递。
packages/agent-runtime负责将 pi 事件映射到此模型。
planning_state 是代理运行时的本地规划投影。其可选的 提案和执行字段镜像共享 PlanningStateEvent 形状 (proposal、executionId 和 executionState)。完全批准执行 描述符使用PlanExecution并由主机result/notification携带。 权威主机 approval/queue 转换是单独的 plans.changed 通过 IPC.event.plansChanged 转发的通知。 tools.output 是 packages/agent-runtime 使用的主机通知 当 Bash 工具运行时;它不是代理事件。
turn_end 关闭 model/tool 一轮但不是终端桌面运行事件: 可能会立即提出另一个提供商的请求。 Renderer 繁忙状态和 因此,持久回合完成仅在 agent_end 或 error 上确定。 压缩始终是内联的:compaction_start 使运行保持忙碌,手册 操作取决于其匹配的 compaction_end 和 threshold/overflow 压实保留在活性剂运行内。没有预先计算的阶段 区分(D203)。
只要安装了检查点,compaction_end.mark 就会出现。它是 渲染器对该压缩的整体视图:id,throughMessageId 锚定 成绩单行位于 generation 之后(此会话有多少个检查点 已安装)、summaryTokens(摘要的估计上下文成本)以及 summarized(当窗口滚动且未向模型询问时,false 总结)。记录本身不被携带——它的摘要和保留尾部被携带 远远大于事件应有的大小——而是从 SessionDetail.compactions 会话打开或分叉。
自动摘要失败仍可能产生成功的生命周期事件 fallback: "retained_tail";这意味着有一个耐用的、有边界的尾巴 检查点已安装,运行可能会继续,但历史记录会减少 上下文。手动压实永远不会悄无声息地倒退。
提供程序 error 事件可能包括以下中的有限诊断字段: AppError.details:phase(request 或 stream)、providerStatus、 providerCode、providerWaitMs、streamMs 和 retryAttempt。这些领域 是添加和编辑的;他们从不携带凭证或不受限制的 提供商响应。瞬时流故障可能会在内部重播 同一回合,没有终端 error 事件或重复的辅助消息。 第二次失败会发出终端标准化 STREAM_FAILED 错误。
6a。通知 API(D117,协议 v4)
持久收件箱请求已列入允许名单 preload 调用 Electron 转发 到单一主机 RPC 域,无需渲染器访问 SQLite:
pi-desktop/notification/list({ unreadOnly?, limit? })pi-desktop/notification/markRead({ id })pi-desktop/notification/markAllRead()pi-desktop/notification/clear()
渲染器调用 pi-desktop/notification/setViewingSession({ sessionId }) 每当聊天时 页面的活动会话发生变化; sessionId: null 清除查看上下文 非聊天页面。 Electron 将此提示与 Main 拥有的窗口结合起来 visibility/focus 位于最终事件边界。它还调用 pi-desktop/notification/showNative({ id, sessionId, title, body }) 之后 本地化新记录。这个仅限电子的请求永远不会进入主机 RPC 域。
type AppNotification = {
id: string;
kind: "task.completed" | "task.failed";
sessionId: string;
sessionTitle: string;
turnId: string;
errorCode?: string;
createdAt: string;
readAt?: string | null;
};
type NotificationListResult = {
notifications: AppNotification[];
unreadCount: number;
};
type NotificationChangedEvent = {
notification: AppNotification;
};
type NotificationActivatedEvent = {
id: string;
sessionId: string;
};Main 发送两个事件:
session.endTurn返回后的pi-desktop/notification/event/changed新插入的记录。 Renderer 将记录合并到其有界本地列表中 并重新计算确切的未读计数。最终结果已经可见 聚焦的当前聊天、重复的终端更新和中止的回合会发出 什么也没有。- 用户点击 Electron 后的
pi-desktop/notification/event/activated本机系统通知。 Renderer 遵循其现有的会话选择 路径,包括项目绑定会话的项目激活。
Electron 拥有本机表面,而渲染器则派生本地化表面 结构化记录中的 title/body 文本。 Electron 仅接受 showNative 对于有效的 notification/session 对,仅在以下情况下显示本机通知 主窗口未聚焦且支持平台 API,则 restores/shows 并在发出 activated 之前聚焦窗口。没有 集中注意力且没有权限时的本机通知、计划提醒或 本合同中的插件源。本地交付是尽力而为;耐用的 当操作系统抑制横幅时,收件箱仍然具有权威性。在 Windows 上, Electron 主将 com.pi-desktop.app 注册为进程 AppUserModelID 在准备就绪之前和创建任何窗口之前。 ID 与 NSIS 匹配 包标识所以通知属性、通知设置、任务栏 分组,安装的快捷方式解析为 PI-Desktop,而不是库存 Electron 主机。
查看会话提示是建议性的和自动防故障的:丢失、陈旧、隐藏或 未聚焦的渲染器状态会创建持久通知。发生抑制 仅当主窗口可见且聚焦且报告的聊天会话时 与收尾阶段相匹配。窗口创建、渲染器重新加载和渲染器 进程丢失在评估任何后续终端事件之前清除提示。
7. 会话 API
type SessionSummary = {
id: string;
title: string;
projectPath?: string;
modelId?: string;
providerId?: string;
mode: "plan" | "goal" | "agent";
thinkingLevel: ThinkingLevel;
supportsReasoning?: boolean;
supportedThinkingLevels?: ThinkingLevel[];
updatedAt: string;
createdAt: string;
};
type UiMessage = {
id: string;
role: "user" | "assistant" | "system" | "tool";
content: string;
thinking?: string; // assistant reasoning, never folded into content
usage?: MessageUsage; // provider-reported assistant usage
responseDurationMs?: number; // model stream duration for throughput
responseOutputTokens?: number; // estimated partial output when stop has no final usage
toolName?: string;
toolCallId?: string;
toolArgs?: unknown;
toolResult?: unknown;
toolUsage?: ToolTokenUsage; // estimated tool call/result footprint
error?: AppError; // structured failure owned by this assistant turn
createdAt: string;
// Rows produced inside a subagent (D201, ADR 0062); absent on the session's own
parentToolCallId?: string; // `Task` call that spawned the delegate
agentName?: string; // delegate definition name
// status/tool fields omitted here
};
type ToolTokenUsage = {
argumentTokens: number;
resultTokens: number;
totalTokens: number;
estimated: true;
};
type SessionDetail = SessionSummary & {
messages: UiMessage[];
};Electron 主要丰富了会话 list/get/create/fork/configure 结果 来自 pi-ai 模型记录的有效推理能力 精确的 (providerId, modelId)。缺少 pi 模型元数据产量 supportsReasoning: false 和 off;缓存发现和遗留提供程序 覆盖不会取代 pi 语义。 Rust 主机仅具有权威性 打造耐用的 thinkingLevel。
全局插件启动器使用仅 Electron 允许的通道:
pi-desktop/pluginLauncher/toggle显示或隐藏居中的实用程序窗口pi-desktop/pluginLauncher/dismiss仅在被该窗口调用时才隐藏它pi-desktop/pluginLauncher/event/shown重置其查询,重新加载安装 插件,并在每次调用后恢复输入焦点
启动器重用 plugin/list 和 plugin/openPanel;它不添加 host-core 插件 RPC。 Electron主进程还调用了附加宿主方法 keyboard.setGlobalShortcut({ binding }) 用于启用仅限 Windows 的回退 用于保留的 Alt+Space 绑定。主机核心发出通知 keyboard.shortcut({ binding: "Alt+Space" }) 当其低电平时 Windows 键盘钩子检测和弦;钩子消耗了那个和弦,所以活动的 窗口系统菜单打不开。非 Windows 主机将该方法视为 无操作。 responseDurationMs 和 responseOutputTokens 是可选的转录本 元数据保留在消息元数据中,因此协议 v9 和存储架构 v11 保持不变。
最小接口:
session/listsession/createsession/fork({ sessionId, title?, throughMessageId? }) -> { session: SessionDetail }session/getsession/deletesession/renamesession/importScansession/importRun(candidates) -> { imported, skipped, failed }
导入候选者携带 projectPath: string | null。导入成功 刷新会话和持久项目索引。
session/fork 是一个协议 v5 通道,可创建独立的 来自源会话当前活动记录的会话。当可选时 throughMessageId 存在,复制的快照以该消息结束;一个 未知 ID 返回 NOT_FOUND。 Electron 拒绝 当该源会话处于活动状态时,使用 AGENT_BUSY 发出请求。 Electron拥有本地化并提供面向用户的分支名称;主机 后备标题是为非 UI 调用者保留的。 主机分配新的会话 ID、消息 ID 和工具调用 ID;它复制 耐用的 project/provider/model/mode/thinking/permission 配置,但是 不复制回合、通知、工件、暂存数据、权限 授予,或重新生成修订。源会话保持不变。 消息范围的助手 Fork/Edit 使用此选项,以便子进程收到 新的会话 ID,因此无法重用或改变源 pi 运行时或 它的提供商缓存。
协议版本9添加检查点Plan合约:SubmitPlan,唯一 .pi/plan/*.md 工件元数据、approve/reject-only 响应、绝对 到期、plan_approvals 执行字段、shell catalog/identity 字段以及 直播 stdout/stderr 事件。 v7 或更旧的主机,以及任何不兼容的 v8 对等方,握手必须失败,以便桌面无法静默显示 Plan 丢失工件、队列、shell 或策略边界。 pi-desktop/agent/compact 和 session.appendCompaction 仍然是 v9 的一部分 合同。 Goal 合约在 v9 (D198) 中是附加的:kind 是可选的 在线且缺席意味着 plan,因此早于 Goal 的对等点继续工作 并且根本不进行谈判。
协议版本 2 添加了 thinkingLevel、UiMessage.thinking 和 message_update.deltaThinking。 v1 对等方必须通过版本检查 默默地丢弃这些字段。
UiMessage.error 是可选的附加字段。提供商失败附加 生命周期 error 事件携带的相同标准化 AppErrormessage_end 之前的助理消息。错误消息仍然存在 转录本,但被排除在恢复的模型上下文之外。
上下文检查器消耗两个附加使用信号。 MessageUsage 是 提供商报告的助理使用情况,responseDurationMs 是已用时间 sidecar 用于显示每秒输出令牌的流时间。 ToolTokenUsage 是根据工具调用参数和结果估计的运行时间;提供商不 报告每个工具的分配,因此渲染器将这些行标记为估计值并 永远不会将它们合并到确切的提供商总数中。年长的同行可能会忽略所有 这些可选字段不会破坏 v6 握手。
8. 设置/秘密 API
设置
可以返回到UI的非敏感配置:
- 提供商列表(无秘密明文)
- 默认模型
- 从主机 shell 目录中保留
defaultCommandShell - 权限策略切换
- UI 首选项,包括可选的
AppSettings.keybindings覆盖键控 通过共享快捷操作 ID;值使用便携式Mod+Shift+Key表示法并且不包含特定于平台的本机加速器字符串 - 可选的
AppSettings.developerMode;缺席和false均保留开发人员 工具已禁用
settings.set 接受部分设置对象。提供主机核心合并 字段写入存储的应用程序设置,因此省略字段,包括 defaultCommandShell,均保留。只有传入的shell字段才是shell 已验证;空闲的 Plan/configuration 门仅在其有效 shell 时运行 会改变的。当前有效的无关写入和幂等写入 当工作正在进行时,shell 仍然被接受。遗产 planApprovalPermissionMode 被忽略并从当前读取中剥离, 写道;它不会被暴露或重新创建。
外壳
type CommandShellId = "windows-powershell" | "cmd" | "git-bash" | "bash";
type CommandShellOption = {
id: CommandShellId;
label: string;
dialect: "powershell" | "cmd" | "posix";
available: boolean;
isDefault: boolean;
};
type CommandShellCatalog = {
configuredId: CommandShellId | null;
effective: CommandShellOption | null;
fallback: boolean;
choices: CommandShellOption[];
};预加载方法:
pi-desktop/commandShell/list() -> CommandShellCatalogpi-desktop/settings/set({ defaultCommandShell }) -> { ok: true }
设置 shell 写入仅接受当前平台的可用 ID,并且 拒绝未知、不可用或错误的平台 ID。真正有效的外壳 仅当所有会话和 Plan/Goal 工作空闲时才接受更改。如果一个 持久化 ID 稍后变得不可用,目录选择第一个可用的 平台外壳并设置 fallback: true;如果没有可用的选择,则 Bash 返回 SHELL_NOT_FOUND。 每回合固定有效 ID 和方言。运行时传输这两个值; 主机在权限评估之前和生成之前拒绝更改的引脚 COMMAND_SHELL_CHANGED。
秘密
secrets/set(providerId, apiKey)secrets/delete(providerId)secrets/has(providerId) -> boolean
禁止:
- 将完整的 API 密钥写入普通日志
- 在渲染器中长期保留 API 密钥明文
9. API 项目
project/open():系统目录选择器project/openFolder(path):打开系统文件中已知的项目目录 经理project/get():当前工作空间project/list():持久的项目记录,包括导入创建的条目project/set(path):设置工作空间project/clear()
返回:
type ProjectWorkspace = {
path: string;
name: string;
};
type ProjectRecord = {
id: number;
path: string;
name: string;
pinned: boolean;
createdAt: number;
lastOpenedAt: number;
};10. 工具权限API
当工具需要确认时:
1.主发送tool_permission_request 2. UI显示确认卡 3.UI调用tool/resolvePermission
type ToolPermissionRequest = {
requestId: string;
sessionId: string;
toolCallId: string;
toolName: string;
argsPreview: unknown;
risk: "low" | "medium" | "high";
reason: string;
/** Definition name when a subagent asked (D201, ADR 0062); absent for the
* session's own calls, together with the `Task` call that spawned it. */
agentName?: string;
parentToolCallId?: string;
};
type ToolPermissionResolution = {
requestId: string;
decision: "allow-once" | "allow-session" | "deny";
};一旦运行并行子代理,一个会话就可以容纳多个打开的请求。 渲染器按会话对它们进行排队,并首先回答最旧的;决议 合约未更改,因为它已由 requestId 键入 (04-ux/03-permission-ux.md §6a)。
Plan 不会取代此通用许可合同。 Plan Bash 调用 使用正常的会话范围权限流:ask 和 accept-edits 发出 工具权限请求,而 auto 执行时无需确认。 Plan 批准是一个单独的状态转换,并且始终使用 plan 方法 上面。
11. 版本兼容性
- IPC/host 合约版本字段:
protocolVersion: 9 - 重大更改必须提升版本并记录 ADR
- 渲染器和主程序在启动时验证版本;不匹配时,提示 upgrade/reinstall
- 协议 v4 增加了通知记录、通道和 带有通知的
session.endTurn结果。 v3 对等点被拒绝 而不是默默地丢失持久的 completion/failure 事件。 - 可选的查看会话调用和
createNotification结束回合字段 是附加的 v4 行为。年长的调用者省略该字段并保留 创建通知的故障安全默认值。 - 协议 v5 添加了所需的
session/fork快照操作。 v4 对等点是 在聊天变得交互之前被拒绝而不是公开分支 只能在调用时失败的命令 (ADR 0023)。 - 协议 v6 添加了持久上下文检查点以及 manual/lifecycle 渠道。 v5 对等点被拒绝,因为默默地忽略检查点可能会导致 使下一个提供商请求不安全(ADR 0030)。
- 协议 v9 取代了早期的 v7 Plan 合约。它添加了
SubmitPlan, 精确独特的工件元数据,approve/reject-only 分辨率,30 分钟 绝对到期、plan_approvals执行状态、shell 选择和 固定 ID/dialect,并流式传输命令输出。 v7/v8 对等点被拒绝 在 UI 变得交互式之前,因为它无法强制或表示这一点 边界(ADR 0053/0054)。SubmitGoal和可选的kind鉴别器 在 v9 中运行,不需要版本冲突,因为缺少kind是 正是目标前的行为。
12. 插件 API(主机 UI 端)
最小接口:
plugin/listplugin/loadDev(path)plugin/reload(id)— 从其存储中重新加载已注册的开发插件 路径并刷新其权限上限plugin/installFromPath(path)plugin/enable(id)plugin/disable(id)plugin/uninstall(id)plugin/getPermissions(id)plugin/setPermission(id, permission, allowed)(可选细粒度)plugin/setScope(id, scope)(D192)
返回摘要:
type PluginSummary = {
id: string
name: string
version: string
enabled: boolean
source: "installed" | "dev"
status: "ready" | "error" | "disabled"
errorMessage?: string
permissions: string[]
scope?: ActivationScope
}12a。用户 MCP 服务器 API (D193)
用户配置的服务器周围没有插件。 host-core 拥有 记录(<data>/mcp/servers.json); Electron 主要拥有连接。
mcp/list→{ servers: McpServerRecord[]; statuses: McpServerStatus[] }mcp/upsert(server)— id 决定创建还是替换mcp/remove(id)mcp/setEnabled(id, enabled)mcp/setScope(id, scope)mcp/test(id)→{ status }— 强制一次握手并保留mcp/import({ text })→{ imported, failed: [{ id, reason }] }
import 接受粘贴的 mcpServers 块;报告了格式错误的条目 failed,对于粘贴的其余部分来说永远不会致命。
type McpServerStatus = {
serverId: string
state: "idle" | "connecting" | "ready" | "failed"
toolCount: number
toolNames?: string[]
message?: string
updatedAt: number
}工具以 mcp_<serverId>_<toolName> 的形式到达代理,与插件分离 桥的 plugin_ 命名空间 (D015)。
12b。用户技能 API (D194)
每个 Markdown 文档位于 <data>/skills/<id>/SKILL.md 下。
skill/list→{ skills: UserSkillRecord[] }skill/create(skill)skill/import()— 原生选择器;退出时的{ canceled: true }skill/update(id, skill)skill/read(id)→{ skill, body }— 返回文档的唯一调用skill/remove(id)skill/setEnabled(id, enabled)skill/setScope(id, scope)skill/reveal(id)
正文不在 list 中:只有描述进入提示,并且 当模型调用 Skill (D174) 时,会获取文档。
12c。子代理 API (D202)
每个 Markdown 文档位于 <data>/agents/<id>.md 下,其中 id 是 模型的名称传递给 Task。注册表记录镜像 UserSkillRecord 和 添加编辑器拥有的 frontmatter:tools、model、thinkingLevel, maxTurns。
subagent/list→{ subagents: UserSubagentRecord[] }subagent/create(subagent)— 重复名称失败并显示SUBAGENT_INVALIDsubagent/update(id, subagent)—model: ""清除引脚,maxTurns: 0清除覆盖subagent/read(id)→{ subagent, body }— 唯一返回 文件subagent/remove(id)subagent/setEnabled(id, enabled)subagent/setScope(id, scope)subagent/reveal({ id })/subagent/reveal({ path })subagent/catalog()→{ 子代理:SubagentDefinition[],诊断, 项目路径 }
catalog 是一个不是注册表调用的通道: Electron main 运行 活动项目的真实加载程序,因此结果是合并后的有效结果 目录——项目,然后是用户注册表,然后是内置的——这就是呈现的内容 只读 builtin/project 行并提供“复制为我的”的正文 定义”。合并和优先级永远不会在渲染器中重新实现。
每个突变都会发出 pluginChanged 和 reason: "subagent",这是 扩展页面已经监听的刷新信号。
12 天。激活范围(D192)
上面的每个 setScope 调用都采用相同的形状,并且 PluginSummary, McpServerRecord、UserSkillRecord 和 UserSubagentRecord 都带有它 enabled 旁边:
type ActivationScope = {
mode: "global" | "projects"
/** Absolute paths; read only in "projects" mode, kept across mode changes. */
projects: string[]
}匹配不区分大小写,不区分尾随分隔符,并且包括 作用域路径的子目录。缺少范围意味着全局。 projects 模式 扩展在没有项目的会话中处于非活动状态。
13. 命令面板 API
commandPalette/search(query)commandPalette/execute(commandId)
命令来源:
- 内置命令
- 插件贡献.命令
13a。工作面板 API
工作面板通道是 Electron 主要的实现。用户驱动的工作区 操作从 workspace.get 解析可见根并失败关闭 没有一个。代理驱动的 BrowserPreview 路由解析原始 通过 session.get 进行对话,因此后台预览永远不会继承 可见会话的工作区。
工作区
workspace/diff()→WorkspaceDiff { repo, clean, files: DiffFile[], truncated? }。 此遗留诊断通道可以检查当前工作树,但它 不是评论的真实来源。评论 UI 读取消息拥有的评论 相反,来自转录工具结果的记录,因此提交无法删除 记录的变化。workspace/review/rollback({sessionId, snapshotId})→ReviewRollbackResult。主机在之前验证当前的后工具哈希 恢复快照;它返回rolledBack、alreadyRolledBack、conflict或unavailable并且永远不会覆盖冲突的后续编辑。
终端 (D099)
terminal/create({cwd, cols?, rows?})→{termId, replay}— 每一个 PTY 工作空间路径,活着时重用;replay恢复最近的回滚。terminal/write({termId, data})、terminal/resize({termId, cols, rows})、terminal/dispose({termId})- 事件:
terminal/event/data {termId, data}、terminal/event/exit {termId, exitCode}
浏览器 (D100)
browser/navigate({url, sessionId?})(方案标准化;http/https 工作 没有工作空间,而本地路径需要提供的会话的 持久的项目根目录或遗留调用的可见工作区),browser/action({action: back|forward|reload|stop}),browser/setBounds({x,y,width,height})(渲染器测量的内容矩形),browser/setVisible({visible})、browser/openExternal()、browser/getState()- 事件:
browser/event/state {url, title, isLoading, canGoBack, canGoForward} - 代理预览活动:
browser/event/preview {sessionId, path}。 Electron 主要 在发出之前验证该会话项目内的path;渲染器 将其记录在匹配的运行时面板上下文中,并仅在该情况下进行导航 对话可见。
fs(只读)
fs/list({path})→ 条目首先按目录排序;忽略.git,node_modules,默认忽略子集 15-工作区-忽略-规则fs/read({path})→ 文本 (≤512KB) / 图像数据 URL (≤5MB) / 二进制 / 太大fs/reveal({path})→ 在 Finder 中显示- 每个路径都在工作空间根目录内解析;外面的遍历是 被拒绝(
INVALID_ARGUMENT)。
13b。桌面菜单和窗口 API
preload 公开同步、只读 platform: NodeJS.Platform 值,以便渲染器选择本机 macOS chrome 或无菜单 Windows/Linux 第一次喷漆前无框镀铬。
主渲染器应用程序命令使用一个列入白名单的事件:
type AppMenuCommand =
| "newTask" | "openProject" | "openSettings"
| "openCommandPalette" | "toggleSidebar"
| "openHelp" | "openLogs" | "checkForUpdates";
event: menu/event/command { command: AppMenuCommand }
menu/rendererReady() -> { ready: true }渲染器在调用之前订阅 menu/event/commandmenu/rendererReady。当本机菜单出现时,Main 会等待该确认 命令创建或重新加载窗口,因此启动计时不能删除第一个 命令。
渲染器拥有的 Windows/Linux 键盘快捷键执行缩放和全屏 通过 menu/nativeAction 进行操作。保留的兼容面 还支持编辑和窗口操作。其请求仅限于 导出的 NATIVE_MENU_ACTIONS 元组;未知的价值观会失败而不是成为 通用主进程命令界面:
type NativeMenuAction =
| "undo" | "redo" | "cut" | "copy" | "paste" | "selectAll"
| "reload" | "zoomIn" | "zoomOut" | "resetZoom"
| "toggleFullScreen" | "minimize" | "toggleMaximize" | "close";
menu/nativeAction({ action: NativeMenuAction })
-> { maximized: boolean; fullScreen: boolean }开发人员工具使用专用的 Main 拥有的门,而不是通用的本机 菜单操作:
devtools/toggle({ open?: boolean }) -> { open: boolean }当 AppSettings.developerMode 不是 true 或 no 时,Main 拒绝请求 实时窗口存在。所有平台上存储的旗门F12相同, 在 Windows/Linux 和 macOS 视图菜单角色上按 Ctrl+Shift+I。禁用标志 关闭已经打开的开发人员工具窗口。
window/control 接受导出的 WINDOW_CONTROL_ACTIONS 元组:
type WindowControlAction =
| "getState" | "minimize" | "toggleMaximize" | "close";
window/control({ action: WindowControlAction })
-> { maximized: boolean }Maximize/unmaximize 变化也会发出 window/event/maximized。未知的操作失败。这些仅限电子的通道 不要跨入 host-core,也不要更改主机 RPC 协议版本。 preload 故意不公开任意的 BrowserWindow 调整大小通道。 一种特定于几何形状的功能是目标状态工作面板保留 (D163,ADR 0032):
window/setWorkPanelReservation({ width: 0 | number })
-> { requested: number; reserved: number }width 必须是等于 0 或在 JSON 内的有限整数 包括 244..720 范围。字符串、布尔值、null、小数值和 其他格式错误的有效负载会因 INVALID_ARGUMENT 而失败,而不是 被胁迫。零是 closed/collapsed 目标,正值是 可见面板的承诺固定宽度。 requested 是接受的当前目标。 reserved 是当前添加的原生宽度 到该目标的正常基本窗口,并且可以小于 requested 仅当显示工作区域不足时。调用是幂等目标 更新:重复相同的宽度不会添加另一个增量。
正常状态下,Main 向右扩展基边界并向左移动 仅根据需要将扩展边界保留在当前显示工作范围内 区。零目标对称地消除了增加的宽度并反转了这一点 保留引起的转变。 Main 仍然保留基界,并且移除了这两种效果。 本机边缘手势仅更新那些基边界,留下 requested 和 渲染器拥有的固定面板宽度不变。最大化和全屏窗口 记住最新的目标但推迟几何;恢复正常协调 它一次针对恢复的基础边界和当前工作区域。如果窗户 管理器首先在显示期间压缩或重新定位外部窗口,或者 工作区转换,协调保留最后确认的基界; 返回到更宽敞的工作区域会恢复原始的聊天宽度。 Renderer 代码 仅针对当前可见的会话设置此目标:背景工件 无法更改可见的保留几何形状。
13c。 Composer 输入 API(D123/D124/D197、ADR 0024/0059)
仅电子通道支持输入框自动完成和剪贴板文件 参考。 composer/commands 和 fs/index 是只读且软故障; composer/pasteFiles 仅写入原始会话的 Electron 拥有的 暂存目录。 None 添加主机 RPC 方法或更改主机协议 版本。
composer/commands
composer/commands() -> { commands: ComposerCommand[] }
type ComposerCommand = {
/** Slash name typed after "/", unique across the merged list. */
name: string;
kind: "template" | "builtin" | "plugin";
title: string; // display title (templates: name)
description?: string; // template frontmatter / palette title
argumentHint?: string; // template frontmatter `argument-hint`
source?: "project" | "user"; // template provenance
id?: string; // builtin/plugin palette id for execution
};模板从 <workspace>/.pi/prompts/*.md 加载并 ~/.pi/agent/prompts/*.md(项目赢得名称冲突;短 TTL 缓存)。 没有工作区,只有用户全局模板、内置函数和插件 命令返回。
fs/index
fs/index() -> { entries: FsIndexEntry[]; truncated: boolean }
type FsIndexEntry = { path: string; kind: "file" | "dir" };@ 菜单的工作空间相对路径:git ls-files -co --exclude-standard快速路径,忽略设置递归行走回退, 从文件路径派生的目录,8000 个条目上限,truncated: true, 每个根的短 TTL 缓存。无法关闭到空列表而没有 工作区。模糊过滤发生在渲染器端。
composer/pasteFiles
composer/pasteFiles({ sessionId, files }) -> {
files: ComposerPastedFile[];
}
type ComposerPasteFile = {
name?: string;
mimeType?: string;
data: ArrayBuffer;
};
type ComposerPastedFile = {
path: string; // UUID-backed absolute storage path
name: string; // sanitized original leaf display name
mimeType: string;
size: number;
};Electron main 验证 sessionId 是否解析为持久主机会话, 将请求限制为 20 个文件,每个文件 64 MiB,总共 128 MiB,条带 渲染器提供的目录组件,并在下面写上唯一的名称 具有独占创建语义的 <data_dir>/scratch/<sessionId>/pasted/。的 渲染器将返回的路径保存在瞬态参考状态,显示 name, 并将每个精确路径序列化为文本提示作为 @ 参考 派遣。剪贴板字节永远不会进入持久提示或主机代理 消息。 无效会话和 malformed/oversized 负载失败并出现 IPC 错误,并且 该操作无法写入工作区。
14. 错误代码 — 初始注册表(可扩展)
| 代码 | 含义 |
|---|---|
AGENT_BUSY | 当前会话已经有一个正在运行的轮次 |
AGENT_NOT_FOUND | 会话不存在 |
MODEL_NOT_CONFIGURED | 无可用模型 |
PROVIDER_SECRET_MISSING | 缺少 API 密钥 |
TOOL_DENIED | 权限被拒绝 |
TOOL_TIMEOUT | 工具超时 |
WORKSPACE_REQUIRED | 需要项目目录 |
PATH_OUTSIDE_WORKSPACE | 在明确的外部路径权限决策之前路径超出范围 |
INTERNAL | 未分类的内部错误 |