feat: 权限档位体系(三档 plan/workspace/full + 四桥 from_session_id)
L2 核心改动:sessions 表补 permission_mode / permission_enforcement 两列 (sqlite + pg 同步),三桥 lib/permission-mode.js 翻译档位到平台原生配置, homeagent advisory 模式提示词告知模型实际强制力。四桥全部携带 from_session_id 供 relay 去重与会话回溯。 FromHuman / ToHuman 判据已加入心跳 payload 与 notify/mail.go。
This commit is contained in:
@ -36,11 +36,13 @@
|
||||
import { mkdirSync, openSync, closeSync, unlinkSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { ModelRuntime } from '@earendil-works/pi-coding-agent';
|
||||
import { ModelRuntime, getAgentDir } from '@earendil-works/pi-coding-agent';
|
||||
|
||||
import { GatewayClient, readLocalKey, generateLocalKey, saveConfig } from './gateway.mjs';
|
||||
import { createWorkerPool } from './pool.mjs';
|
||||
import { createSessionScanner } from './session-scan.mjs';
|
||||
import { describeError } from './turn.mjs';
|
||||
import { BoundedSet, MAX_TRACKED_MAILS } from '../lib/bounded.js';
|
||||
import { snapshotPiModels } from '../lib/model-scope.js';
|
||||
import { snapshotPiSessions } from '../lib/session-snapshot.js';
|
||||
import { selectCatchup } from '../lib/catchup.js';
|
||||
@ -91,12 +93,18 @@ const log = (...args) => console.error('[pi-mail-bridge]', ...args);
|
||||
// 会话映射、权限授权、命名指纹都下沉到 pool 里按邮件会话存 —— 主进程不再持有
|
||||
// AgentSession 对象(那东西跨不了进程边界)。
|
||||
|
||||
const deliveredMails = new Set(); // 已投过的 mail_id(SSE 与补拉共用,B-7.3)
|
||||
// 已投过的 mail_id(SSE 与补拉共用,B-7.3)。
|
||||
//
|
||||
// 有界:桥是守护进程,跑几十天下来这里会攒下每一封处理过的邮件 id 而永远
|
||||
// 没有出口。淘汰是安全的 —— 它防的两种重复(心跳与 SSE 建连之间的窗口、
|
||||
// SSE 断线重放)都发生在秒到分钟级,几千封之前的 id 不可能再来。
|
||||
const deliveredMails = new BoundedSet(MAX_TRACKED_MAILS);
|
||||
|
||||
let allowedModels = [];
|
||||
let modelRuntime = null;
|
||||
let client = null;
|
||||
let pool = null;
|
||||
let sessionScanner = null;
|
||||
let heartbeatTimer = null;
|
||||
let shuttingDown = false;
|
||||
|
||||
@ -172,14 +180,15 @@ function handlePermissionDecision(data) {
|
||||
|
||||
async function reportSessions() {
|
||||
try {
|
||||
const { SessionManager } = await import('@earendil-works/pi-coding-agent');
|
||||
// 不传参数:`listAll(dir)` 把字符串当**自定义会话目录**,传 getAgentDir()
|
||||
// 会去 ~/.pi/agent 下直接找 .jsonl(那里没有),得到空列表。
|
||||
// 不传时它用默认的 ~/.pi/agent/sessions,逐个 cwd 子目录扫。
|
||||
// **不用 `SessionManager.listAll()`**:它为了拿 id/cwd/name/modified 四个
|
||||
// 字段,把 ~/.pi/agent/sessions 下每个 .jsonl 的每一行都读进来并 JSON.parse,
|
||||
// 还把所有消息正文拼成一个 allMessagesText 大字符串。本机实测(115 个文件 /
|
||||
// 145MB)单次 1431ms、堆里瞬时 240MB —— 而这 282MB 每 30 秒分配一次随即
|
||||
// 变成垃圾,且那 1.4 秒是同步解析,跑在事件循环上(SSE 读循环那期间停着)。
|
||||
//
|
||||
// 用 listAll 而不是 list(cwd):桥的进程 cwd 与会话 cwd 无关,
|
||||
// 按前者过滤会漏掉所有真正在干活的会话。
|
||||
const all = await SessionManager.listAll();
|
||||
// sessionScanner 只读 header 的首行 + 增量扫尾部找 session_info:
|
||||
// 稳态下未变化的文件一个字节都不读(实测 3ms / 0 字节)。
|
||||
const all = await sessionScanner.scan();
|
||||
const driven = pool.mailDrivenIDs();
|
||||
return snapshotPiSessions(all, (id) => driven.has(id));
|
||||
} catch (e) {
|
||||
@ -255,6 +264,16 @@ async function main() {
|
||||
const runtimeErr = modelRuntime.getError?.();
|
||||
if (runtimeErr) log(`模型运行时告警: ${runtimeErr}`);
|
||||
|
||||
// 会话目录扫描器。**必须建一次并复用** —— 它的省内存全靠跨拍存活的
|
||||
// size 缓存(稳态下未变化的文件一个字节都不读)。每拍新建一个等于
|
||||
// 每拍都冷启动,退回 listAll 那种全量读的开销。
|
||||
//
|
||||
// 路径自己拼而不是 import getSessionsDir:SDK 只导出 getAgentDir,
|
||||
// getSessionsDir 是内部函数(dist/config.js 里 `join(getAgentDir(), "sessions")`)。
|
||||
sessionScanner = createSessionScanner({
|
||||
sessionsDir: join(getAgentDir(), 'sessions'),
|
||||
});
|
||||
|
||||
// 工作进程池。config() 每次派活时取一次 —— allowedModels 随心跳变,
|
||||
// 取快照会让 worker 用上一轮的模型范围。
|
||||
pool = createWorkerPool({
|
||||
@ -339,6 +358,14 @@ function handleSSEEvent(type, data) {
|
||||
handlePermissionDecision(data);
|
||||
return;
|
||||
}
|
||||
if (type === 'session_archived') {
|
||||
// 会话归档 = 那条会话再也不会收信(别名 404),pool 里的 sessionState
|
||||
// 可以确定性地清掉,不必等上限淘汰去猜。
|
||||
if (pool?.forget(data?.session_id || '')) {
|
||||
log(`会话 ${data.session_id} 已归档,清除本地状态`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (type !== 'new_mail') return;
|
||||
if (data?.role && data.role !== 'to' && data.role !== 'cc') return;
|
||||
const id = data?.mail_id;
|
||||
|
||||
Reference in New Issue
Block a user