Skip to content

01. IPC 协议

翻译说明: 本页是与 英文源规格 一一对应的机器辅助翻译。代码、协议字段和标识符保持原文;如翻译与英文源事实有歧义,以英文版本为准。

1. 目标

定义渲染器和主程序之间的稳定契约。

原则:

  1. 所有功能均经过 preload 许可名单
  2. 输入 Requests/responses
  3. 长时间运行的任务使用事件流,而不是单个超大响应
  4. 错误必须有代码+消息

2. API 组

域名描述
app应用程序信息、健康检查
agent对话、中止、状态和交互式 Asktool 解决方案
planPlan 提案列出、决议和变更事件
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. 渠道约定

text
invoke: pi-desktop/<domain>/<action>
event: pi-desktop/<domain>/event/<name>

示例:

  • pi-desktop/agent/prompt
  • pi-desktop/agent/abort
  • pi-desktop/agent/event/message
  • pi-desktop/agent/askTool/resolve
  • pi-desktop/session/list
  • pi-desktop/project/open
  • pi-desktop/project/openFolder

4. 通用响应包络

ts
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提示

ts
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) 中的任何位置进行转换。

提示执行解析 modeproviderIdmodelIdthinkingLevel 从持久会话记录和快照中获取有效的命令 shell ID 和 Bash 的方言。 渲染器通过以下方式更改这些值 会话空闲时的 pi-desktop/session/configure

ts
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/saveRevision
  • pi-desktop/session/listRevisions
  • pi-desktop/session/activateRevision

Root 用户轮次可能包括 revisionRootIdrevisionCountactiveRevision。激活修订版将实时尾部替换为 prefix + archived branch 并处置会话代理。 输入框 附件可供性保持隐藏,直到 main、sidecar、pi 模型 功能和持久性都会消耗有效负载。

5. 2 中止

ts
type AgentAbortRequest = {
 sessionId: string;
 turnId?: string;
};

中止请求和响应不携带 Composer 草稿或文件参考数据。 如果渲染器智能停止撤消未应答的用户回合,则恢复来自 渲染器的 session/turn-scoped 预序列化快照;现有的 转录重写会删除发送的行而不更改协议版本。

5. 3 紧凑型(协议 v9)

ts
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 方法。

ts
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? }) -> PlansPendingResult
  • pi-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 获取状态

ts
type AgentStatus = {
 sessionId: string;
 isRunning: boolean;
 currentTurnId?: string;
 modelId?: string;
 pendingToolConfirmations: number;
};

6. Agent 活动

从主→渲染器推送:

ts
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 形状 (proposalexecutionIdexecutionState)。完全批准执行 描述符使用PlanExecution并由主机result/notification携带。 权威主机 approval/queue 转换是单独的 plans.changed 通过 IPC.event.plansChanged 转发的通知。 tools.outputpackages/agent-runtime 使用的主机通知 当 Bash 工具运行时;它不是代理事件。

turn_end 关闭 model/tool 一轮但不是终端桌面运行事件: 可能会立即提出另一个提供商的请求。 Renderer 繁忙状态和 因此,持久回合完成仅在 agent_enderror 上确定。 压缩始终是内联的:compaction_start 使运行保持忙碌,手册 操作取决于其匹配的 compaction_end 和 threshold/overflow 压实保留在活性剂运行内。没有预先计算的阶段 区分(D203)。

只要安装了检查点,compaction_end.mark 就会出现。它是 渲染器对该压缩的整体视图:idthroughMessageId 锚定 成绩单行位于 generation 之后(此会话有多少个检查点 已安装)、summaryTokens(摘要的估计上下文成本)以及 summarized(当窗口滚动且未向模型询问时,false 总结)。记录本身不被携带——它的摘要和保留尾部被携带 远远大于事件应有的大小——而是从 SessionDetail.compactions 会话打开或分叉。

自动摘要失败仍可能产生成功的生命周期事件 fallback: "retained_tail";这意味着有一个耐用的、有边界的尾巴 检查点已安装,运行可能会继续,但历史记录会减少 上下文。手动压实永远不会悄无声息地倒退。

提供程序 error 事件可能包括以下中的有限诊断字段: AppError.detailsphaserequeststream)、providerStatusproviderCodeproviderWaitMsstreamMsretryAttempt。这些领域 是添加和编辑的;他们从不携带凭证或不受限制的 提供商响应。瞬时流故障可能会在内部重播 同一回合,没有终端 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 域。

ts
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

ts
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: falseoff;缓存发现和遗留提供程序 覆盖不会取代 pi 语义。 Rust 主机仅具有权威性 打造耐用的 thinkingLevel

全局插件启动器使用仅 Electron 允许的通道:

  • pi-desktop/pluginLauncher/toggle 显示或隐藏居中的实用程序窗口
  • pi-desktop/pluginLauncher/dismiss 仅在被该窗口调用时才隐藏它
  • pi-desktop/pluginLauncher/event/shown 重置其查询,重新加载安装 插件,并在每次调用后恢复输入焦点

启动器重用 plugin/listplugin/openPanel;它不添加 host-core 插件 RPC。 Electron主进程还调用了附加宿主方法 keyboard.setGlobalShortcut({ binding }) 用于启用仅限 Windows 的回退 用于保留的 Alt+Space 绑定。主机核心发出通知 keyboard.shortcut({ binding: "Alt+Space" }) 当其低电平时 Windows 键盘钩子检测和弦;钩子消耗了那个和弦,所以活动的 窗口系统菜单打不开。非 Windows 主机将该方法视为 无操作。 responseDurationMsresponseOutputTokens 是可选的转录本 元数据保留在消息元数据中,因此协议 v9 和存储架构 v11 保持不变。

最小接口:

  • session/list
  • session/create
  • session/fork({ sessionId, title?, throughMessageId? }) -> { session: SessionDetail }
  • session/get
  • session/delete
  • session/rename
  • session/importScan
  • session/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/compactsession.appendCompaction 仍然是 v9 的一部分 合同。 Goal 合约在 v9 (D198) 中是附加的:kind 是可选的 在线且缺席意味着 plan,因此早于 Goal 的对等点继续工作 并且根本不进行谈判。

协议版本 2 添加了 thinkingLevelUiMessage.thinkingmessage_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 被忽略并从当前读取中剥离, 写道;它不会被暴露或重新创建。

外壳

ts
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() -> CommandShellCatalog
  • pi-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()

返回:

ts
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

ts
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 调用 使用正常的会话范围权限流:askaccept-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/list
  • plugin/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)

返回摘要:

ts
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,对于粘贴的其余部分来说永远不会致命。

ts
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:toolsmodelthinkingLevelmaxTurns

  • subagent/list{ subagents: UserSubagentRecord[] }
  • subagent/create(subagent) — 重复名称失败并显示 SUBAGENT_INVALID
  • subagent/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 行并提供“复制为我的”的正文 定义”。合并和优先级永远不会在渲染器中重新实现。

每个突变都会发出 pluginChangedreason: "subagent",这是 扩展页面已经监听的刷新信号。

12 天。激活范围(D192)

上面的每个 setScope 调用都采用相同的形状,并且 PluginSummaryMcpServerRecordUserSkillRecordUserSubagentRecord 都带有它 enabled 旁边:

ts
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。主机在之前验证当前的后工具哈希 恢复快照;它返回 rolledBackalreadyRolledBackconflictunavailable 并且永远不会覆盖冲突的后续编辑。

终端 (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}) → 条目首先按目录排序;忽略 .gitnode_modules,默认忽略子集 15-工作区-忽略-规则
  • fs/read({path}) → 文本 (≤512KB) / 图像数据 URL (≤5MB) / 二进制 / 太大
  • fs/reveal({path}) → 在 Finder 中显示
  • 每个路径都在工作空间根目录内解析;外面的遍历是 被拒绝(INVALID_ARGUMENT)。

13b。桌面菜单和窗口 API

preload 公开同步、只读 platform: NodeJS.Platform 值,以便渲染器选择本机 macOS chrome 或无菜单 Windows/Linux 第一次喷漆前无框镀铬。

主渲染器应用程序命令使用一个列入白名单的事件:

ts
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 元组;未知的价值观会失败而不是成为 通用主进程命令界面:

ts
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 拥有的门,而不是通用的本机 菜单操作:

ts
devtools/toggle({ open?: boolean }) -> { open: boolean }

AppSettings.developerMode 不是 true 或 no 时,Main 拒绝请求 实时窗口存在。所有平台上存储的旗门F12相同, 在 Windows/Linux 和 macOS 视图菜单角色上按 Ctrl+Shift+I。禁用标志 关闭已经打开的开发人员工具窗口。

window/control 接受导出的 WINDOW_CONTROL_ACTIONS 元组:

ts
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):

ts
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/commandsfs/index 是只读且软故障; composer/pasteFiles 仅写入原始会话的 Electron 拥有的 暂存目录。 None 添加主机 RPC 方法或更改主机协议 版本。

composer/commands

ts
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

ts
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

ts
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未分类的内部错误

为本地优先开发而构建。