Files
云服务部-叶林立andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 2aa1be0b54 feat: 组合包配置以包内 config/ 为唯一来源,并新增受保护写入人工审批
配置统一:权限策略、硬防御、Tool Search、提示词封面以及新迁入的
RTK、pi-compaction、pi-worktree、pi-hermes-memory 配置全部由扩展
直接读取已安装组合包内的 config/,pi install / pi update 即完成部署;
删除 rebuild-bundle-config.mjs 与 bundle-manifest.json,my-pi/ 只保留
日志与缓存。update.sh 成功升级后将默认 git 来源的已安装副本
reset 回发布版本。AGENTS.md 新增「组合包配置原则」。

脚本整合:search_config.sh / ssh_config.sh 合并为 configure.sh
search|ssh;install.sh / update.sh 共享函数抽到 scripts/lib.sh,远程
安装器改为先下载到临时文件再执行,下载失败可被检测。

权限:hard-guards 新增 protectedWrites,写入 Pi 凭据/设置、已安装
组合包与扩展目录、pi-ssh 凭据库时一律人工确认(跳过 AutoReview、
yolo 与 Session grant,无 UI 时拒绝);放行 ls、LSP 查询、ctx 只读、
web_search、Chrome 只读与 git 只读命令,skill_manage 改为 ask。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-20 01:05:31 -07:00

118 lines
3.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it, vi } from "vitest";
import { protectedWriteDeferNote } from "#src/handlers/gates/protected-write";
import type { ProtectedWriteTarget } from "#src/handlers/gates/descriptor";
import type { PermissionCheckResult } from "#src/types";
import { DECIDED_BY_HUMAN } from "#test/helpers/decision-fixtures";
import { makeDescriptor, makeGateRunner } from "#test/helpers/gate-fixtures";
import { makeCheckResult } from "#test/helpers/handler-fixtures";
const MARKER: ProtectedWriteTarget = {
path: "/vault/auth.json",
pattern: "/vault/auth.json",
label: "Pi 凭据",
extraCount: 0,
};
function protectedDescriptor(check: PermissionCheckResult) {
return makeDescriptor({
humanOnly: MARKER,
sessionApproval: undefined,
preCheck: check,
});
}
describe("GateRunner — humanOnly protected writes", () => {
it("still escalates when the resolved check is a session grant", async () => {
const { runner, deps } = makeGateRunner();
const outcome = await runner.run(
protectedDescriptor(
makeCheckResult({
state: "ask",
source: "session",
matchedPattern: "/vault/**",
}),
),
null,
);
expect(deps.escalate).toHaveBeenCalledOnce();
expect(outcome).toEqual({ action: "allow" });
expect(deps.reporter.writeReviewLog).not.toHaveBeenCalledWith(
"permission_request.session_approved",
expect.anything(),
);
});
it("still escalates when yolo mode is enabled", async () => {
const { runner, deps } = makeGateRunner({ yolo: true });
await runner.run(
protectedDescriptor(
makeCheckResult({ state: "ask", source: "tool", origin: "builtin" }),
),
null,
);
expect(deps.escalate).toHaveBeenCalledOnce();
expect(deps.reporter.writeReviewLog).not.toHaveBeenCalledWith(
"permission_request.auto_approved",
expect.anything(),
);
});
it("escalates with humanOnly, the system note, and no session approval", async () => {
const { runner, deps } = makeGateRunner();
await runner.run(
protectedDescriptor(
makeCheckResult({ state: "ask", source: "tool", origin: "builtin" }),
),
null,
);
expect(deps.escalate).toHaveBeenCalledWith(
expect.objectContaining({
humanOnly: true,
deferNotes: [protectedWriteDeferNote(MARKER)],
}),
);
const details = vi.mocked(deps.escalate).mock.calls[0][0];
expect(details.sessionApproval).toBeUndefined();
});
it("records no session grant when the user approves", async () => {
const { runner, deps } = makeGateRunner({
escalate: vi.fn().mockResolvedValue({
approved: true,
state: "approved_for_session",
decidedBy: DECIDED_BY_HUMAN,
}),
});
const outcome = await runner.run(
protectedDescriptor(
makeCheckResult({ state: "ask", source: "tool", origin: "builtin" }),
),
null,
);
// A session-shaped approval cannot be recorded: the descriptor carries no
// sessionApproval to grant.
expect(deps.recordSessionApproval).not.toHaveBeenCalled();
expect(outcome).toEqual({ action: "allow" });
});
});
describe("protectedWriteDeferNote", () => {
it("renders the exact system note", () => {
expect(protectedWriteDeferNote(MARKER)).toEqual({
origin: "system",
text: "⚠ 该操作将写入受保护文件:/vault/auth.json(Pi 凭据,匹配 /vault/auth.json)。此类写入不经 AutoReview,必须由你人工确认。",
});
});
it("adds the extra-target count", () => {
const note = protectedWriteDeferNote({ ...MARKER, extraCount: 2 });
expect(note.text).toContain(",另有 2 个受保护目标");
});
});