chore(format): 撤销误入提交的整体重排,并关闭本仓库的格式化器

# 发生了什么

pi-lens 内置「安全格式化」:它会自动安装 biome 并对**编辑过的文件**跑
`biome format --write`。本机原先没有任何 biome 配置,于是 biome 用它自己的
默认值 —— tab 缩进 + 双引号 —— 把文件整体重写。

我在 19a3161 那次提交里用了 `git add -A`,把这批与功能无关的重排一起扫了进去:
约 7000 行改动散落在 20 个文件上,使那次提交无法审查,还掩盖了
server/internal/handler/permission.go 的一处删行(实为文件末尾空行,无代码丢失)。

# 为什么是「关掉」而不是「配置成我们的风格」

试过把缩进/引号/lineWidth 全部对齐本仓库习惯(biome.json + space/2/single/
lineWidth 120):`biome format --write` 仍然改动 17 个文件。原因是本仓库从未按
biome 的规则排版过 —— 注释按语义换行、数组与调用按可读性手工折行,
这些无法由格式化器还原。也就是说只要格式化器开着,每次编辑都会产生与内容无关的
大面积 diff,把真正的改动埋掉。

因此 biome.jsonc 里 formatter 与 linter 都关闭:本仓库的静态检查由
tsc / go vet / tree-sitter / ast-grep 与各自测试套件承担,不引入会改动无关行的
自动修复。

(pi-lens 这一版把 format 服务的 enabled 硬编码为 true,没有配置开关,
所以只能在仓库侧用 biome 配置让它不动文件;已验证 `biome format --write`
对这些文件零改动。)

# 本提交内容

把 19a3161 里除「有意改动」外的 20 个文件还原到重排前的样子。
19a3161 中真正有意的改动是 deploy/install.sh 的扩展注册与
plugins/pi-mail-bridge/extension/index.ts 新文件,两者原样保留。

验证:Go 全量、三桥插件(320/362/409)、前端 196 全绿;
`biome format --write` 对还原后的文件零改动。
This commit is contained in:
2026-09-11 12:03:51 +08:00
parent 19a3161ee4
commit 429149e118
21 changed files with 5603 additions and 6668 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,139 +1,139 @@
export interface User {
user_id: string;
username: string;
display_name: string;
role: "admin" | "user";
status: "active" | "disabled";
allowed_agents: string[];
allowed_paths: string[];
last_login?: string;
created_at?: string;
user_id: string;
username: string;
display_name: string;
role: 'admin' | 'user';
status: 'active' | 'disabled';
allowed_agents: string[];
allowed_paths: string[];
last_login?: string;
created_at?: string;
}
export interface Workspace {
name: string;
path: string;
name: string;
path: string;
}
export interface Agent {
agent_id?: string;
agent_name: string;
workspaces: Workspace[];
platform: string;
status: string;
/** 派给该 Agent 的新任务默认多少个来回0 = 不限) */
default_rounds?: number;
agent_id?: string;
agent_name: string;
workspaces: Workspace[];
platform: string;
status: string;
/** 派给该 Agent 的新任务默认多少个来回0 = 不限) */
default_rounds?: number;
}
export interface Address {
name: string;
path: string;
session: string;
raw: string;
name: string;
path: string;
session: string;
raw: string;
}
export interface Session {
session_id: string;
session_alias: string | null;
from_agent: string;
subject: string;
status: string;
created_at: string;
updated_at: string;
mail_count?: number;
/**
* 别名是谁定的:
* platform = Agent 平台自动同步来的,后续同步可以覆盖
* manual = 人显式指定(手工改名或接受了 Agent 的提议),平台同步不得覆盖
*/
alias_source?: "platform" | "manual";
/** 用户驳回过的改名提议 */
rename_dismissed?: string;
/**
* 本任务的往返预算0 = 本会话不限,仅受 Agent 全局配额约束)。
*
* 配额的语义是「这件事值得多少个来回」—— 那是任务的属性而非 Agent 的属性,
* 所以在写信时给、在对话页里随时调,而不是去管理员页面改某个 Agent 的全局配额。
*/
max_rounds?: number;
used_rounds?: number;
/** 权限档位plan / workspace / full */
permission_mode?: string;
/** 档位实际强制力native / advisory */
permission_enforcement?: string;
session_id: string;
session_alias: string | null;
from_agent: string;
subject: string;
status: string;
created_at: string;
updated_at: string;
mail_count?: number;
/**
* 别名是谁定的:
* platform = Agent 平台自动同步来的,后续同步可以覆盖
* manual = 人显式指定(手工改名或接受了 Agent 的提议),平台同步不得覆盖
*/
alias_source?: 'platform' | 'manual';
/** 用户驳回过的改名提议 */
rename_dismissed?: string;
/**
* 本任务的往返预算0 = 本会话不限,仅受 Agent 全局配额约束)。
*
* 配额的语义是「这件事值得多少个来回」—— 那是任务的属性而非 Agent 的属性,
* 所以在写信时给、在对话页里随时调,而不是去管理员页面改某个 Agent 的全局配额。
*/
max_rounds?: number;
used_rounds?: number;
/** 权限档位plan / workspace / full */
permission_mode?: string;
/** 档位实际强制力native / advisory */
permission_enforcement?: string;
}
/** 会话往返预算快照 */
export interface SessionBudget {
session_id: string;
max_rounds: number;
used_rounds: number;
/** 不限时为 -1 */
remaining: number;
unlimited: boolean;
session_id: string;
max_rounds: number;
used_rounds: number;
/** 不限时为 -1 */
remaining: number;
unlimited: boolean;
}
/** 附件元数据。内容存盘,按 sha256 内容寻址;同内容重复上传不占额外空间。 */
export interface Attachment {
attachment_id: string;
/** 为 null 表示已上传但尚未随邮件发出 */
mail_id: string | null;
uploader: string;
filename: string;
content_type: string;
size_bytes: number;
sha256: string;
created_at: string;
attachment_id: string;
/** 为 null 表示已上传但尚未随邮件发出 */
mail_id: string | null;
uploader: string;
filename: string;
content_type: string;
size_bytes: number;
sha256: string;
created_at: string;
}
export interface Mail {
mail_id: string;
session_id: string;
parent_mail_id: string | null;
from_name: string;
from_workspace: string;
to_name: string;
to_workspace: string;
cc_list: Address[];
subject: string;
body: string;
mail_type: "normal" | "permission_request";
permission_options: string[] | null;
permission_result: string | null;
/**
* 待办类型:`permission`(危险操作审批)/ `question`(模型主动提问)。
*
* 两者共用 permission_request 这个 mail_type但**该渲染什么完全不同**
* 前者是「批准 / 拒绝」,后者是「回答问题」(勾选 + 自由文本)。
* 混用一套 UI 会让人把「回答问题」当成「批准执行」。
*/
permission_kind?: string;
/** 仅 question 使用:是否允许多选(对应 DSH 的 multi_select。 */
permission_multi_select?: boolean;
status: "unread" | "read" | "archived";
created_at: string;
hop_limit?: number;
session_alias?: string;
/**
* **这条会话**的工作目录sessions.workspace
*
* 不能用 from_workspace / to_workspace 代替:
* - 人 → Agentto_workspace 是真路径from_workspace 为空(人没有工作目录)
* - Agent → 人to_workspace 为空,而 **from_workspace 存的是 Agent 名**
* 而不是路径(历史遗留)
*
* 于是「Agent 发来的这封信,那个 Agent 在哪个目录干活」只能从会话上取。
* 界面上曾显示成 `dsh@dsh`,就是拿 from_workspace 当路径拼的。
*/
session_workspace?: string;
body_preview?: string;
attachments?: Attachment[];
/** 发件方是人类用户而不是 Agent服务端 EXISTS users 判的) */
from_human: boolean;
/** 收件方是人类用户而不是 Agent */
to_human: boolean;
permission_mode?: string;
permission_enforcement?: string;
mail_id: string;
session_id: string;
parent_mail_id: string | null;
from_name: string;
from_workspace: string;
to_name: string;
to_workspace: string;
cc_list: Address[];
subject: string;
body: string;
mail_type: 'normal' | 'permission_request';
permission_options: string[] | null;
permission_result: string | null;
/**
* 待办类型:`permission`(危险操作审批)/ `question`(模型主动提问)。
*
* 两者共用 permission_request 这个 mail_type但**该渲染什么完全不同**
* 前者是「批准 / 拒绝」,后者是「回答问题」(勾选 + 自由文本)。
* 混用一套 UI 会让人把「回答问题」当成「批准执行」。
*/
permission_kind?: string;
/** 仅 question 使用:是否允许多选(对应 DSH 的 multi_select。 */
permission_multi_select?: boolean;
status: 'unread' | 'read' | 'archived';
created_at: string;
hop_limit?: number;
session_alias?: string;
/**
* **这条会话**的工作目录sessions.workspace
*
* 不能用 from_workspace / to_workspace 代替:
* - 人 → Agentto_workspace 是真路径from_workspace 为空(人没有工作目录)
* - Agent → 人to_workspace 为空,而 **from_workspace 存的是 Agent 名**
* 而不是路径(历史遗留)
*
* 于是「Agent 发来的这封信,那个 Agent 在哪个目录干活」只能从会话上取。
* 界面上曾显示成 `dsh@dsh`,就是拿 from_workspace 当路径拼的。
*/
session_workspace?: string;
body_preview?: string;
attachments?: Attachment[];
/** 发件方是人类用户而不是 Agent服务端 EXISTS users 判的) */
from_human: boolean;
/** 收件方是人类用户而不是 Agent */
to_human: boolean;
permission_mode?: string;
permission_enforcement?: string;
}
/**
@ -145,30 +145,30 @@ export interface Mail {
* depth 是**相对锚点**的层级0 = 锚点,负数 = 祖先,正数 = 子孙。
* 分块加载时根可能还没取到,所以不用「距根深度」。
*/
export interface ThreadNode extends Omit<Mail, "body"> {
/** 距**线索根**的层级0 = 根1 = 它的直接回复 */
depth: number;
attachment_count: number;
/** 父邮件不在当前已加载集合里(无权查看,或还没滑到) */
detached?: boolean;
/** 父邮件确实存在但无权查看(区别于「尚未加载」) */
parent_hidden?: boolean;
body?: string;
export interface ThreadNode extends Omit<Mail, 'body'> {
/** 距**线索根**的层级0 = 根1 = 它的直接回复 */
depth: number;
attachment_count: number;
/** 父邮件不在当前已加载集合里(无权查看,或还没滑到) */
detached?: boolean;
/** 父邮件确实存在但无权查看(区别于「尚未加载」) */
parent_hidden?: boolean;
body?: string;
}
export interface ThreadPage {
anchor_mail_id: string;
/** 线索根的 mail_id整棵树从它展开 */
root_mail_id: string;
/** 锚点距根的层数,用于高亮定位 */
anchor_depth: number;
nodes: ThreadNode[];
total: number;
/** 因权限被过滤掉的节点数 */
hidden: number;
has_more: boolean;
/** 下一页 offset原样回传即可 */
next_offset: number;
anchor_mail_id: string;
/** 线索根的 mail_id整棵树从它展开 */
root_mail_id: string;
/** 锚点距根的层数,用于高亮定位 */
anchor_depth: number;
nodes: ThreadNode[];
total: number;
/** 因权限被过滤掉的节点数 */
hidden: number;
has_more: boolean;
/** 下一页 offset原样回传即可 */
next_offset: number;
}
/**
@ -179,69 +179,69 @@ export interface ThreadPage {
* 提议 + 人点头,既让 Agent 表达意图,又保证寻址稳定性由人掌握。
*/
export interface RenameProposal {
/** 已由服务端规范化,可直接提交给 PUT /sessions/:id/alias */
alias: string;
reason?: string;
/** 已由服务端规范化,可直接提交给 PUT /sessions/:id/alias */
alias: string;
reason?: string;
}
export interface Contact {
session_id: string;
agent_name: string;
path: string;
session_alias: string;
address: string;
status: string;
mail_count: number;
unread_count: number;
last_activity: string;
/** 会话主题(多由 Agent 平台的模型生成的摘要) */
subject: string;
/** 本任务的往返预算0 = 不限) */
max_rounds: number;
used_rounds: number;
permission_mode?: string;
permission_enforcement?: string;
/** 最后一封邮件的发件人与正文摘要(服务端已按字符截断) */
last_from: string;
last_preview: string;
session_id: string;
agent_name: string;
path: string;
session_alias: string;
address: string;
status: string;
mail_count: number;
unread_count: number;
last_activity: string;
/** 会话主题(多由 Agent 平台的模型生成的摘要) */
subject: string;
/** 本任务的往返预算0 = 不限) */
max_rounds: number;
used_rounds: number;
permission_mode?: string;
permission_enforcement?: string;
/** 最后一封邮件的发件人与正文摘要(服务端已按字符截断) */
last_from: string;
last_preview: string;
}
export interface PermissionRequest {
request_id: string;
mail_id: string;
session_id: string;
agent_name: string;
question: string;
options: string[];
context: string;
result: string | null;
decided_at: string | null;
created_at: string;
request_id: string;
mail_id: string;
session_id: string;
agent_name: string;
question: string;
options: string[];
context: string;
result: string | null;
decided_at: string | null;
created_at: string;
}
export interface SessionDetail {
session: Session;
mails: Mail[];
session: Session;
mails: Mail[];
}
export interface HumanSession {
session_id: string;
session_alias: string | null;
from_agent: string;
subject: string;
status: string;
created_at: string;
updated_at: string;
mail_count: number;
unread_count: number;
/** 本任务的往返预算0 = 不限) */
max_rounds?: number;
used_rounds?: number;
permission_mode?: string;
permission_enforcement?: string;
session_id: string;
session_alias: string | null;
from_agent: string;
subject: string;
status: string;
created_at: string;
updated_at: string;
mail_count: number;
unread_count: number;
/** 本任务的往返预算0 = 不限) */
max_rounds?: number;
used_rounds?: number;
permission_mode?: string;
permission_enforcement?: string;
}
export type SuggestKind = "name" | "path" | "session";
export type SuggestKind = 'name' | 'path' | 'session';
/**
* 会话候选项的来源。
@ -249,38 +249,38 @@ export type SuggestKind = "name" | "path" | "session";
* platform 平台侧会话镜像(人直接在 opencode/DSH 界面上开的)
* new 新建会话的哨兵项
*/
export type SessionCandidateSource = "mail" | "platform" | "new";
export type SessionCandidateSource = 'mail' | 'platform' | 'new';
export interface SessionCandidate {
/** 填进 session 位的值 */
alias: string;
/** 给人看,用来分辨两条别名相似的会话在谈什么 */
title?: string;
source: SessionCandidateSource;
/** 仅 mail 来源有意义 */
unread?: number;
/** 填进 session 位的值 */
alias: string;
/** 给人看,用来分辨两条别名相似的会话在谈什么 */
title?: string;
source: SessionCandidateSource;
/** 仅 mail 来源有意义 */
unread?: number;
}
export interface SuggestResult {
kind: SuggestKind;
suggestions: string[];
/**
* 带标题与来源的完整候选,与 suggestions 同序。
* 仅 kind === 'session' 时返回suggestions 保留纯字符串形式是为了
* 不打破已部署的前端与第三方客户端。
*/
candidates?: SessionCandidate[];
kind: SuggestKind;
suggestions: string[];
/**
* 带标题与来源的完整候选,与 suggestions 同序。
* 仅 kind === 'session' 时返回suggestions 保留纯字符串形式是为了
* 不打破已部署的前端与第三方客户端。
*/
candidates?: SessionCandidate[];
}
/** 系统初始化状态 */
export interface SetupStatus {
needs_setup: boolean;
needs_setup: boolean;
}
/** 管理员可授权范围候选 */
export interface AdminScopes {
agents: string[];
paths: string[];
agents: string[];
paths: string[];
}
/**
@ -291,58 +291,58 @@ export interface AdminScopes {
* 对 Agent 来说就是一封普通邮件,它不知道也不需要知道信来自日历。
*/
export interface CalendarEvent {
event_id: string;
title: string;
description: string;
/**
* 提醒邮件的正文。留空时后端按 title/time/description 生成默认模板。
* 支持 {title} {time} {description} 三个变量,触发时替换。
*/
reminder_text: string;
/**
* 收件 Agent 名 / 完整地址 —— **单收件人时代的字段**。
* 保留作兼容与兜底recipients 为空时才用它们。
* 新代码一律用 effectiveRecipients()。
*/
agent_name: string;
to_address: string;
/**
* 收件人列表,每项是完整三维地址串。
*
* 存原始串而不是结构化地址session 位的 new/别名三态该在**触发那一刻**
* 解析。存结构化的话「.new」这种一次性语义在建事件时就被固化
* 而重复事件每次触发都该重新决定落到哪条会话。
*/
recipients: string[];
/**
* 多收件人的投递方式。
* separate = 各发一封、落各自会话、互相看不到
* together = 首个为主收件人,其余进抄送、共享同一条线索
*
* 两种都要而不是二选一:「三个 Agent 各自独立汇报」与「pi 主办、dsh 知情」
* 是完全不同的任务形态。用错 together 会让本该独立判断的 Agent 互相
* 看到回复而趋同,那种污染事后无法分离。
*/
delivery_mode: DeliveryMode;
/** ISO 8601 */
event_time: string;
/** 提前多少分钟提醒0 = 到点才提醒 */
remind_before: number;
recurrence: Recurrence;
/** 重复终止时间;越过它事件自动置为 cancelled */
recurrence_end?: string;
status: "active" | "paused" | "cancelled";
/** 上次触发的墙上时钟 */
last_fired_at?: string;
/**
* 已触发的那个 occurrence值 = 当时的 event_time
* 去重靠它与 event_time 相等判断,不是拿 last_fired_at 比大小 ——
* 后端 DueEvents 有 60 秒 lookahead后者在窗口内恒为真会导致每 tick 重发。
*/
fired_for?: string;
created_at: string;
updated_at: string;
created_by: string;
event_id: string;
title: string;
description: string;
/**
* 提醒邮件的正文。留空时后端按 title/time/description 生成默认模板。
* 支持 {title} {time} {description} 三个变量,触发时替换。
*/
reminder_text: string;
/**
* 收件 Agent 名 / 完整地址 —— **单收件人时代的字段**。
* 保留作兼容与兜底recipients 为空时才用它们。
* 新代码一律用 effectiveRecipients()。
*/
agent_name: string;
to_address: string;
/**
* 收件人列表,每项是完整三维地址串。
*
* 存原始串而不是结构化地址session 位的 new/别名三态该在**触发那一刻**
* 解析。存结构化的话「.new」这种一次性语义在建事件时就被固化
* 而重复事件每次触发都该重新决定落到哪条会话。
*/
recipients: string[];
/**
* 多收件人的投递方式。
* separate = 各发一封、落各自会话、互相看不到
* together = 首个为主收件人,其余进抄送、共享同一条线索
*
* 两种都要而不是二选一:「三个 Agent 各自独立汇报」与「pi 主办、dsh 知情」
* 是完全不同的任务形态。用错 together 会让本该独立判断的 Agent 互相
* 看到回复而趋同,那种污染事后无法分离。
*/
delivery_mode: DeliveryMode;
/** ISO 8601 */
event_time: string;
/** 提前多少分钟提醒0 = 到点才提醒 */
remind_before: number;
recurrence: Recurrence;
/** 重复终止时间;越过它事件自动置为 cancelled */
recurrence_end?: string;
status: 'active' | 'paused' | 'cancelled';
/** 上次触发的墙上时钟 */
last_fired_at?: string;
/**
* 已触发的那个 occurrence值 = 当时的 event_time
* 去重靠它与 event_time 相等判断,不是拿 last_fired_at 比大小 ——
* 后端 DueEvents 有 60 秒 lookahead后者在窗口内恒为真会导致每 tick 重发。
*/
fired_for?: string;
created_at: string;
updated_at: string;
created_by: string;
}
/**
@ -353,38 +353,38 @@ export interface CalendarEvent {
* 也没有 lunar_weekly农历没有「周」这个单位
*/
export type Recurrence =
| "none"
| "daily"
| "weekly"
| "monthly"
| "yearly"
| "lunar_monthly"
| "lunar_yearly";
| 'none'
| 'daily'
| 'weekly'
| 'monthly'
| 'yearly'
| 'lunar_monthly'
| 'lunar_yearly';
export type DeliveryMode = "separate" | "together";
export type DeliveryMode = 'separate' | 'together';
/** 事件附件(随提醒邮件一起发出) */
export interface CalendarAttachment {
attachment_id: string;
event_id: string;
filename: string;
sha256: string;
size_bytes: number;
created_at: string;
attachment_id: string;
event_id: string;
filename: string;
sha256: string;
size_bytes: number;
created_at: string;
}
/** 新建/编辑事件的请求体。event_time 必填,其余可省。 */
export interface CalendarEventInput {
title: string;
description?: string;
reminder_text?: string;
agent_name?: string;
to_address?: string;
recipients?: string[];
delivery_mode?: DeliveryMode;
event_time: string;
remind_before?: number;
recurrence?: Recurrence;
recurrence_end?: string | null;
status?: "active" | "paused" | "cancelled";
title: string;
description?: string;
reminder_text?: string;
agent_name?: string;
to_address?: string;
recipients?: string[];
delivery_mode?: DeliveryMode;
event_time: string;
remind_before?: number;
recurrence?: Recurrence;
recurrence_end?: string | null;
status?: 'active' | 'paused' | 'cancelled';
}