@huseynsnmz/pi-suite 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.2]
4
+
5
+ ### Fixes
6
+
7
+ - Pi's `codemode` tool now works in plan and read-only modes. Each tool a script calls is still checked, so a script can't write there. Another package's tool named `codemode` stays blocked.
8
+ - A script or other tool can no longer call `edit`, `write`, `powershell`, or bash that isn't read-only, in any mode. The model makes those as their own tool calls, so you see each one and `router/auto` sees that editing has started. With `codemode.mode` set to `only` this is off in normal, auto and yolo, because scripts are then the only way to reach tools.
9
+
3
10
  ## [0.2.1]
4
11
 
5
12
  ### Fixes
package/README.md CHANGED
@@ -17,7 +17,7 @@ One package for [Pi](https://pi.dev): modes and permissions, a framed screen, a
17
17
  Requires Pi 1.5 or newer.
18
18
 
19
19
  ```bash
20
- pi install npm:@huseynsnmz/pi-suite@0.2.1
20
+ pi install npm:@huseynsnmz/pi-suite@0.2.2
21
21
  ```
22
22
 
23
23
  If you installed any of these separately, remove them first:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huseynsnmz/pi-suite",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "One Pi package for all of it: modes and permissions, a framed screen, an agent board, memory, web tools with a separate page reader, and a model router. Turn off what you don't want with /suite.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,7 +26,7 @@ Requires Pi 1.5 or newer.
26
26
  1. Install it:
27
27
 
28
28
  ```bash
29
- pi install npm:@huseynsnmz/pi-suite@0.2.1
29
+ pi install npm:@huseynsnmz/pi-suite@0.2.2
30
30
  ```
31
31
 
32
32
  2. Restart Pi, and press `Left` twice on an empty prompt, or run `/agent-board`.
@@ -19,7 +19,7 @@ Requires Pi 1.5 or newer.
19
19
  2. Install it:
20
20
 
21
21
  ```bash
22
- pi install npm:@huseynsnmz/pi-suite@0.2.1
22
+ pi install npm:@huseynsnmz/pi-suite@0.2.2
23
23
  ```
24
24
 
25
25
  3. Restart Pi. The start card says `π-frame`.
@@ -9,7 +9,7 @@ Long-term memory for [Pi](https://pi.dev). It reviews your past conversations, k
9
9
  Requires Pi 1.5 or newer, on Node 22.18 or newer.
10
10
 
11
11
  ```bash
12
- pi install npm:@huseynsnmz/pi-suite@0.2.1
12
+ pi install npm:@huseynsnmz/pi-suite@0.2.2
13
13
  ```
14
14
 
15
15
  ## Use
@@ -23,7 +23,7 @@ Requires Pi 1.5 or newer.
23
23
  1. Install it:
24
24
 
25
25
  ```bash
26
- pi install npm:@huseynsnmz/pi-suite@0.2.1
26
+ pi install npm:@huseynsnmz/pi-suite@0.2.2
27
27
  ```
28
28
 
29
29
  2. Move pi-suite above `@gotgenes/pi-permission-system`, because `pi install` adds packages last. Otherwise, in `plan` and `read-only` you may be asked to approve an action that is then blocked anyway. It stays blocked either way, and the extension warns you at startup. Skip this step if you don't use that package.
@@ -123,6 +123,8 @@ Without a classifier, `auto` behaves like `normal`, and the extension warns you
123
123
  **`web`:** `web_fetch` and `web_search` are allowed in `plan` and `read-only`, as they only read. The same names from another package are blocked there.
124
124
 
125
125
 
126
+ **Codemode with `pi-permission-system`:** it asks about `codemode` like any tool, once for each script. To skip that, add `"codemode": "allow"` to `permission` in its config. Each tool a script calls is still checked against your rules.
127
+
126
128
  **`pi-subagents`:** blocked in `plan` and `read-only`, because a child agent could write files. In `normal` and `auto` it is unchanged.
127
129
 
128
130
  **`pi-router`:** `plan` mode uses its plan model, and approving a plan moves to its build model.
@@ -135,6 +137,7 @@ Without a classifier, `auto` behaves like `normal`, and the extension warns you
135
137
 
136
138
  - **Allowed tools:** `read`, `grep`, `find`, `ls`, read-only `bash`, and `submit_plan` in plan mode.
137
139
  - **Blocked tools:** `write`, `edit`, subagents, MCP tools and every other tool, including ones added later.
140
+ - **Codemode:** Pi's `codemode` tool runs, and each tool a script calls is checked like any other. Its `models` calls aren't tools, so they aren't checked: a script can still send data to a classifier or image model you have a login for, at your cost.
138
141
  - **Read-only bash:** a fixed list of commands, such as `ls`, `cat`, `rg` and `git status`. A command is refused if it uses `$`, backticks, redirects, subshells, background jobs, unquoted globs, control characters or a path to a binary. Options that write or run code are refused too, including bundled and abbreviated forms such as `sort -uo` and `sort --outp`. Quote a glob you mean literally, as in `find . -name '*.ts'`.
139
142
 
140
143
  Any other command asks you in `plan` and is blocked in `read-only`.
@@ -145,6 +148,7 @@ These modes keep an honest agent on track. They are not a sandbox.
145
148
 
146
149
  - **Reads are not restricted by this extension.** A read-only command can still read secrets, so keep `deny` rules for them in your permission system.
147
150
  - **A repo can already be malicious.** Git can run code from a repository's config and hooks, even for `git status`, and a plain clone can bring them. In `plan` and `read-only`, git commands stop counting as read-only where a repository sets anything unusual or has a hook that reading can fire, which covers what's known today. Other tools can have their own ways in, so use a container for repos you don't trust.
151
+ - **Scripts can't change things, in any mode.** A script or other tool can't call `edit`, `write`, `powershell`, or bash that isn't read-only, so each change is its own visible step. The exception is `codemode.mode` set to `only` in `normal`, `auto` and `yolo`, where scripts are the only way to reach tools. There a script's edits aren't in the transcript, so `router/auto` can stay on its plan model. The same goes for any setup with `modes` turned off in `/suite`.
148
152
  - **Child agents started earlier keep running.** Switching mode doesn't stop a subagent that is already running.
149
153
  - **The classifier hook relies on an internal detail.** There is no public way to switch a registered judge on and off, so this extension wraps it when it registers. It uses the global key that `pi-permission-system` documents for its service map. If that breaks, you get a warning on your first prompt that the judge may auto-approve outside `auto`.
150
154
 
@@ -10,7 +10,7 @@ import { registerInterview } from "./interview.ts";
10
10
  import { registerReviewTools } from "./review/tools.ts";
11
11
  import { aliasDefinitions, DENY_OFF_PROMPT, withAliases, withoutAliases } from "./deny-off.ts";
12
12
  import { clearGate, hasPermissionSystem, installJudgeGate, JUDGE_NAMES, judgeGated, registerYoloLink, setGateMode, YOLO_LINK } from "./judge-gate.ts";
13
- import { decideToolCall, displayCommand, fromExpectedPackage, notReadOnly, isMode, loadsAfterPermissionSystem, type Mode, MODES, modePrompt, nextMode, resumedMode, startingMode, SUBMIT_PLAN_TOOL } from "./modes.ts";
13
+ import { decideToolCall, displayCommand, fromExpectedPackage, notReadOnly, isMode, scriptedChange, loadsAfterPermissionSystem, type Mode, MODES, modePrompt, nextMode, resumedMode, startingMode, SUBMIT_PLAN_TOOL } from "./modes.ts";
14
14
 
15
15
  const STATUS_KEY = "pi-modes";
16
16
  const DENY_STATUS_KEY = "pi-modes-deny";
@@ -239,6 +239,11 @@ export default function piModes(pi: ExtensionAPI): void {
239
239
  if (ctx.hasUI) ctx.ui.notify("The sandbox went off, so yolo ended.", "warning");
240
240
  return { block: true, reason: "Yolo ended because the sandbox is off." };
241
241
  }
242
+ // With codemode.mode set to "only", scripts are the one way to reach any tool, so they have to be able to write.
243
+ if (event.parentToolCallId !== undefined && pi.getSettings().codemode?.mode !== "only") {
244
+ const reason = scriptedChange(event.toolName, event.input);
245
+ if (reason !== undefined) return { block: true, reason };
246
+ }
242
247
  let decision = decideToolCall(mode, event.toolName, event.input);
243
248
  if (decision.action === "allow" && (mode === "plan" || mode === "read-only") && event.toolName === "bash") {
244
249
  const risk = runsGit((event.input as { command: string }).command) ? await gitRisk(ctx.cwd) : undefined;
@@ -10,9 +10,11 @@ const CYCLE: readonly Mode[] = ["normal", "auto", "plan", "read-only"];
10
10
 
11
11
  export const SUBMIT_PLAN_TOOL = "submit_plan";
12
12
  export const ASK_TOOL = "ask_questions";
13
+ const CODEMODE_TOOL = "codemode";
13
14
 
14
15
  // pi-web's tools only read, and a separate model reads the page for them.
15
- const READ_TOOLS = new Set(["read", "grep", "find", "ls", "web_fetch", "web_search", "todo", ASK_TOOL]);
16
+ // Each tool a codemode script calls comes through this check itself, so the script needs none of its own.
17
+ const READ_TOOLS = new Set(["read", "grep", "find", "ls", "web_fetch", "web_search", "todo", ASK_TOOL, CODEMODE_TOOL]);
16
18
 
17
19
  /** Allowed for what pi-suite's own do. Another package's tool with the name could do anything. */
18
20
  const SUITE_TOOLS: ReadonlySet<string> = new Set(["todo", "web_fetch", "web_search", ASK_TOOL]);
@@ -28,6 +30,7 @@ function realPath(path: string): string | undefined {
28
30
 
29
31
  /** The whole suite loads from one file, so that file is what these tools must come from. */
30
32
  export function fromExpectedPackage(toolName: string, path: string | undefined, entry = SUITE_ENTRY): boolean {
33
+ if (toolName === CODEMODE_TOOL) return path === "builtin:codemode";
31
34
  if (!SUITE_TOOLS.has(toolName)) return true;
32
35
  const expected = realPath(entry);
33
36
  return path !== undefined && expected !== undefined && realPath(path) === expected;
@@ -84,6 +87,20 @@ export function decideToolCall(mode: Mode, toolName: string, input: unknown): To
84
87
  return { action: "block", reason: `${mode} mode blocks ${toolName}. ${leave}` };
85
88
  }
86
89
 
90
+ /**
91
+ * Why a tool can't make this call from inside a script, if it can't. The transcript only shows the script,
92
+ * so a change must be its own step: you can see it, and the router can see that editing has started.
93
+ */
94
+ export function scriptedChange(toolName: string, input: unknown): string | undefined {
95
+ const asOwnStep = "Call it directly, not from a script.";
96
+ if (/(^|_)(edit|write)$/.test(toolName) || toolName === "powershell") return `${toolName} changes files. ${asOwnStep}`;
97
+ if (!/(^|_)bash$/.test(toolName)) return undefined;
98
+ const command = (input as { command?: unknown } | null)?.command;
99
+ if (typeof command !== "string") return `${toolName} needs a command. ${asOwnStep}`;
100
+ const verdict = classifyBash(command);
101
+ return verdict.readOnly ? undefined : `${verdict.reason}. ${asOwnStep}`;
102
+ }
103
+
87
104
  /**
88
105
  * Makes a command safe to show in a dialog. Control and bidirectional characters
89
106
  * become visible escapes, so a command can't hide or reorder what the user sees.
@@ -9,7 +9,7 @@ Adds `router/auto` to [Pi](https://pi.dev): one choice in `/model` that uses a s
9
9
  Requires Pi 1.5 or newer.
10
10
 
11
11
  ```bash
12
- pi install npm:@huseynsnmz/pi-suite@0.2.1
12
+ pi install npm:@huseynsnmz/pi-suite@0.2.2
13
13
  ```
14
14
 
15
15
  Log in to the providers you want with `/login`, run `/router` to choose a model for each role, then pick `router/auto` in `/model`.
@@ -9,7 +9,7 @@ A web page can hold instructions aimed at the AI reading it. So a separate model
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- pi install npm:@huseynsnmz/pi-suite@0.2.1
12
+ pi install npm:@huseynsnmz/pi-suite@0.2.2
13
13
  ```
14
14
 
15
15
  `web_fetch` works straight away. It reads pages with the session's model, unless you set `readerModel` or use [pi-router](https://github.com/huseynsnmz/pi-suite/tree/main/packages/router), which can choose the reader. `web_search` stays off until you choose where searches go.