mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
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>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
a0ee9389c7
commit
2aa1be0b54
@@ -28,7 +28,6 @@ import {
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SUBAGENT_CHILD_SESSION_CREATED } from "#src/authority/subagent-lifecycle-events";
|
||||
import { getSubagentSessionRegistry } from "#src/authority/subagent-registry";
|
||||
import { getGlobalConfigPath } from "#src/config-paths";
|
||||
import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
|
||||
import piPermissionSystemExtension from "#src/index";
|
||||
import { PERMISSIONS_READY_CHANNEL } from "#src/permission-events";
|
||||
@@ -55,9 +54,30 @@ const EXPECTED_HANDLERS = [
|
||||
|
||||
let agentDir: string;
|
||||
|
||||
/**
|
||||
* Policy and hard-guard files live under the per-test temp dir and are passed
|
||||
* to the factory explicitly — the bundle `config/` files are never touched.
|
||||
*/
|
||||
function testPolicyPath(): string {
|
||||
return join(agentDir, "pi-permission-policy.jsonc");
|
||||
}
|
||||
function testHardGuardsPath(): string {
|
||||
return join(agentDir, "pi-permission-hard-guards.jsonc");
|
||||
}
|
||||
function testConfigPaths(): { configPath: string; hardGuardConfigPath: string } {
|
||||
return {
|
||||
configPath: testPolicyPath(),
|
||||
hardGuardConfigPath: testHardGuardsPath(),
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
agentDir = mkdtempSync(join(tmpdir(), "pi-perm-comp-root-"));
|
||||
vi.stubEnv("PI_CODING_AGENT_DIR", agentDir);
|
||||
writeFileSync(
|
||||
testHardGuardsPath(),
|
||||
JSON.stringify({ version: 1, permission: { "*": "allow" } }),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -75,9 +95,9 @@ afterEach(() => {
|
||||
|
||||
// ── Shared helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
/** Write the global config file under the stubbed agent dir. */
|
||||
/** Write the global policy file under the stubbed temp dir. */
|
||||
function writeGlobalConfig(config: Record<string, unknown>): void {
|
||||
const globalConfigPath = getGlobalConfigPath(agentDir);
|
||||
const globalConfigPath = testPolicyPath();
|
||||
mkdirSync(dirname(globalConfigPath), { recursive: true });
|
||||
writeFileSync(
|
||||
globalConfigPath,
|
||||
@@ -174,7 +194,7 @@ function fireSessionStart(
|
||||
describe("event-handler registration completeness", () => {
|
||||
it("registers a handler for every required event exactly once", () => {
|
||||
const pi = makeFakePi();
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
expect([...pi.handlers.keys()].sort()).toEqual(EXPECTED_HANDLERS);
|
||||
});
|
||||
@@ -186,7 +206,7 @@ describe("shutdown teardown chain", () => {
|
||||
it("unpublishes the service and unsubscribes the lifecycle on shutdown", async () => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-perm-teardown-cwd-"));
|
||||
const pi = makeFakePi();
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
// The service is published at session_start, not at factory init.
|
||||
await fireSessionStart(pi, makeChildCtx(cwd, "top-session"));
|
||||
@@ -219,7 +239,7 @@ describe("service and gate share one formatter registry", () => {
|
||||
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-perm-ui-cwd-"));
|
||||
const pi = makeFakePi({ toolNames: ["demo"] });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
const capturedTitles: string[] = [];
|
||||
const { ctx } = makeUiCtx(cwd, capturedTitles);
|
||||
@@ -257,7 +277,7 @@ describe("service and gate share one access extractor registry", () => {
|
||||
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-perm-ext-cwd-"));
|
||||
const pi = makeFakePi({ toolNames: ["ffgrep"] });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
const { ctx } = makeUiCtx(cwd, []);
|
||||
await fireSessionStart(pi, ctx);
|
||||
@@ -295,7 +315,7 @@ describe("service and chain share one authorizer registry", () => {
|
||||
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-perm-auth-cwd-"));
|
||||
const pi = makeFakePi({ toolNames: ["demo"] });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
const capturedTitles: string[] = [];
|
||||
const { ctx } = makeUiCtx(cwd, capturedTitles);
|
||||
@@ -327,7 +347,7 @@ describe("service and chain share one authorizer registry", () => {
|
||||
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-perm-auth-optin-"));
|
||||
const pi = makeFakePi({ toolNames: ["demo"] });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
const capturedTitles: string[] = [];
|
||||
const { ctx } = makeUiCtx(cwd, capturedTitles);
|
||||
@@ -364,7 +384,7 @@ describe("ready emitted after service publication", () => {
|
||||
seen.push(getPermissionsService() ? "present" : "missing");
|
||||
});
|
||||
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
// ready is not emitted at load; only after session_start publishes.
|
||||
expect(seen).toEqual([]);
|
||||
@@ -389,7 +409,7 @@ describe("single source of truth for session state", () => {
|
||||
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-perm-sot-cwd-"));
|
||||
const pi = makeFakePi({ toolNames: ["demo"] });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
// UI ctx that approves the gate prompt for this session (options[1]).
|
||||
// Return the second option label-agnostically — always the "for this
|
||||
@@ -436,7 +456,7 @@ describe("service path queries evaluate the supplied path (#503)", () => {
|
||||
writeGlobalConfig({ permission: { path: { [target]: "deny" } } });
|
||||
|
||||
const pi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
await fireSessionStart(pi, makeChildCtx(cwd, "svc-path-session"));
|
||||
|
||||
const result = getPermissionsService()!.checkPermission("path", target);
|
||||
@@ -453,7 +473,7 @@ describe("project trust gates project-scoped config (#644)", () => {
|
||||
writeProjectConfig(cwd, { permission: { bash: "allow" } });
|
||||
|
||||
const pi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
await fireSessionStart(
|
||||
pi,
|
||||
makeBaseCtx(cwd, "untrusted-session", { isProjectTrusted: false }),
|
||||
@@ -473,7 +493,7 @@ describe("project trust gates project-scoped config (#644)", () => {
|
||||
writeProjectConfig(cwd, { permission: { bash: "allow" } });
|
||||
|
||||
const pi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
await fireSessionStart(
|
||||
pi,
|
||||
makeBaseCtx(cwd, "trusted-session", { isProjectTrusted: true }),
|
||||
@@ -516,7 +536,7 @@ describe("bash bare-token path gating (#509, #645)", () => {
|
||||
});
|
||||
|
||||
const pi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
const ctx = makeChildCtx(cwd, "bare-token-session-deny");
|
||||
await fireSessionStart(pi, ctx);
|
||||
|
||||
@@ -534,7 +554,7 @@ describe("bash bare-token path gating (#509, #645)", () => {
|
||||
});
|
||||
|
||||
const pi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
const ctx = makeChildCtx(cwd, "bare-token-session-wildcard");
|
||||
await fireSessionStart(pi, ctx);
|
||||
|
||||
@@ -556,7 +576,7 @@ describe("bash bare-token path gating (#509, #645)", () => {
|
||||
});
|
||||
|
||||
const pi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
const ctx = makeChildCtx(cwd, "bare-symlink-session-deny");
|
||||
await fireSessionStart(pi, ctx);
|
||||
|
||||
@@ -573,7 +593,7 @@ describe("bash bare-token path gating (#509, #645)", () => {
|
||||
});
|
||||
|
||||
const pi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
const ctx = makeChildCtx(cwd, "bare-token-session-unaffected");
|
||||
await fireSessionStart(pi, ctx);
|
||||
|
||||
@@ -592,7 +612,7 @@ describe("bash bare-token path gating (#509, #645)", () => {
|
||||
});
|
||||
|
||||
const pi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
const ctx = makeChildCtx(cwd, "bare-token-session-absent");
|
||||
await fireSessionStart(pi, ctx);
|
||||
|
||||
@@ -613,7 +633,7 @@ describe("bash bare-token path gating (#509, #645)", () => {
|
||||
});
|
||||
|
||||
const pi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
const ctx = makeChildCtx(cwd, "bare-token-session-norule");
|
||||
await fireSessionStart(pi, ctx);
|
||||
|
||||
@@ -637,9 +657,9 @@ describe("multi-instance global service interplay", () => {
|
||||
const childSessionId = "child-session-mi";
|
||||
|
||||
const parentPi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(parentPi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(parentPi as unknown as ExtensionAPI, testConfigPaths());
|
||||
const childPi = makeFakePi({ events: createEventBus() });
|
||||
piPermissionSystemExtension(childPi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(childPi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
// The parent is not a registered child, so it publishes its service.
|
||||
await fireSessionStart(
|
||||
@@ -703,7 +723,7 @@ describe("session approvals do not leak across same-cwd session switches", () =>
|
||||
|
||||
// ── Session #1: approve `demo` for the session, then shut down ──────────
|
||||
const firstPi = makeFakePi({ toolNames: ["demo"] });
|
||||
piPermissionSystemExtension(firstPi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(firstPi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
const firstCtx = makeSessionApprovingCtx(cwd, "switch-session-1");
|
||||
await fireSessionStart(firstPi, firstCtx);
|
||||
@@ -724,7 +744,7 @@ describe("session approvals do not leak across same-cwd session switches", () =>
|
||||
|
||||
// ── Session #2: the re-invoked cached factory, same cwd ────────────────
|
||||
const secondPi = makeFakePi({ toolNames: ["demo"] });
|
||||
piPermissionSystemExtension(secondPi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(secondPi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
await fireSessionStart(secondPi, makeChildCtx(cwd, "switch-session-2"));
|
||||
|
||||
@@ -776,12 +796,12 @@ describe("forwarded grant-scope selection round-trip", () => {
|
||||
|
||||
const parentBus = createEventBus();
|
||||
const parentPi = makeFakePi({ events: parentBus, toolNames: ["demo"] });
|
||||
piPermissionSystemExtension(parentPi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(parentPi as unknown as ExtensionAPI, testConfigPaths());
|
||||
const childPi = makeFakePi({
|
||||
events: createEventBus(),
|
||||
toolNames: ["demo"],
|
||||
});
|
||||
piPermissionSystemExtension(childPi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(childPi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
// The parent starts serving (its poll drains the inbox using the UI ctx).
|
||||
const parentCtx = makeServingCtx(
|
||||
@@ -841,12 +861,12 @@ describe("forwarded grant-scope selection round-trip", () => {
|
||||
|
||||
const parentBus = createEventBus();
|
||||
const parentPi = makeFakePi({ events: parentBus, toolNames: ["demo"] });
|
||||
piPermissionSystemExtension(parentPi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(parentPi as unknown as ExtensionAPI, testConfigPaths());
|
||||
const childPi = makeFakePi({
|
||||
events: createEventBus(),
|
||||
toolNames: ["demo"],
|
||||
});
|
||||
piPermissionSystemExtension(childPi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(childPi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
const parentCtx = makeServingCtx(
|
||||
parentCwd,
|
||||
@@ -905,7 +925,7 @@ describe("yolo grants asks synthesized after resolution", () => {
|
||||
writeGlobalConfig(config);
|
||||
const cwd = mkdtempSync(join(tmpdir(), "pi-perm-yolo-cwd-"));
|
||||
const pi = makeFakePi({ toolNames: ["bash"] });
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI);
|
||||
piPermissionSystemExtension(pi as unknown as ExtensionAPI, testConfigPaths());
|
||||
|
||||
const prompts: string[] = [];
|
||||
const { ctx } = makeUiCtx(cwd, prompts);
|
||||
|
||||
Reference in New Issue
Block a user