Enhance permission gate with auto-allow for safe operations
Permission gate now automatically allows read operations on tracked files
and safe jj commands (diff, log, show, status) without prompting.
Also updates default model to claude-sonnet-4-0.
Assisted-by: Claude Sonnet 4.0 via Claude Code
diff --git a/.pi/agent/extensions/permission-gate.ts b/.pi/agent/extensions/permission-gate.ts
index bceaa6d4..79e55cb4 100644
--- a/.pi/agent/extensions/permission-gate.ts
+++ b/.pi/agent/extensions/permission-gate.ts
@@ -2,14 +2,59 @@
* Permission Gate Extension
*
* Requires explicit user confirmation before every tool call.
+ * Auto-allows read-only operations (read tool, non-destructive jj commands).
* Shows tool name and a summary of the arguments, then asks to allow or block.
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
+import { execSync } from "node:child_process";
+import * as path from "node:path";
+
+const ALLOWED_JJ_SUBCOMMANDS = ["diff", "log", "show", "status"];
+
+let trackedFiles: Set<string> | null = null;
+
+function getTrackedFiles(cwd: string): Set<string> {
+ if (trackedFiles) return trackedFiles;
+ try {
+ const output = execSync("jj file list", { cwd, encoding: "utf-8" });
+ trackedFiles = new Set(output.trim().split("\n").map((f) => path.resolve(cwd, f)));
+ } catch {
+ trackedFiles = new Set();
+ }
+ return trackedFiles;
+}
+
+function isAllowed(toolName: string, input: Record<string, unknown>, cwd: string): boolean {
+ if (toolName === "read") {
+ const filePath = path.resolve(cwd, String(input.path ?? ""));
+ return getTrackedFiles(cwd).has(filePath);
+ }
+
+ if (toolName === "bash") {
+ const cmd = String(input.command ?? "").trimStart();
+ if (cmd.startsWith("jj ")) {
+ const subcommand = cmd.slice(3).trimStart().split(/\s/)[0];
+ return ALLOWED_JJ_SUBCOMMANDS.includes(subcommand);
+ }
+ }
+
+ return false;
+}
export default function (pi: ExtensionAPI) {
+ pi.on("tool_result", async (event) => {
+ if (event.toolName === "bash") {
+ const cmd = String(event.input.command ?? "").trimStart();
+ if (cmd.startsWith("jj ")) {
+ trackedFiles = null;
+ }
+ }
+ });
+
pi.on("tool_call", async (event, ctx) => {
if (!ctx.hasUI) return;
+ if (isAllowed(event.toolName, event.input, ctx.cwd)) return;
const summary = formatArgs(event.toolName, event.input);
const allowed = await ctx.ui.confirm(event.toolName, summary);
diff --git a/.pi/agent/settings.json b/.pi/agent/settings.json
index 36e43037..234ce17a 100644
--- a/.pi/agent/settings.json
+++ b/.pi/agent/settings.json
@@ -1,7 +1,7 @@
{
"lastChangelogVersion": "0.52.6",
"defaultProvider": "anthropic",
- "defaultModel": "claude-opus-4-6",
+ "defaultModel": "claude-sonnet-4-0",
"skills": [
"~/.claude/skills"
],