@ian-pascoe/pi-codemode 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ian Pascoe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # @ian-pascoe/pi-codemode
2
+
3
+ Run persistent TypeScript notebook Cells that compose Pi's registered tools.
4
+ CodeMode calls the exact handlers Pi registered; it does not contain substitute
5
+ implementations of built-in tools.
6
+
7
+ Tested with Pi `0.84.2` and Node `22.19.0`. The package installs its pinned
8
+ `deno@2.9.5` runtime, which itself transpiles and executes Cells. Its official
9
+ npm binaries cover macOS, glibc Linux, and Windows on x64 and arm64.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pi install npm:@ian-pascoe/pi-codemode
15
+ ```
16
+
17
+ From this repository:
18
+
19
+ ```bash
20
+ pi install git:github.com/ian-pascoe/pi-extensions
21
+ ```
22
+
23
+ ## Tools
24
+
25
+ ### `codemode_execute`
26
+
27
+ ```ts
28
+ codemode_execute({
29
+ script: string;
30
+ timeoutMs?: number;
31
+ wait?: boolean;
32
+ sessionId?: string;
33
+ });
34
+ ```
35
+
36
+ `wait` defaults to `true`. `timeoutMs` has no default. Omitting `sessionId`
37
+ creates a new CodeMode Session; supplying an unknown ID fails.
38
+
39
+ ```ts
40
+ const result = await tools.read({ path: "README.md" });
41
+ return result.content[0];
42
+ ```
43
+
44
+ Set `wait: false` to return immediately with `pending`, then poll the returned
45
+ ID. An accepted asynchronous execution always returns `pending`, even if its
46
+ Cell finishes before the outer call returns.
47
+
48
+ ### `codemode_result`
49
+
50
+ ```ts
51
+ codemode_result({ sessionId: string });
52
+ ```
53
+
54
+ Returns the active or latest terminal result without consuming it.
55
+
56
+ ### `codemode_cancel`
57
+
58
+ ```ts
59
+ codemode_cancel({ sessionId: string });
60
+ ```
61
+
62
+ Stops the session process and frees its capacity. The cancel call succeeds;
63
+ subsequent polling returns the retained `cancellation` failure.
64
+
65
+ All three tools return:
66
+
67
+ ```ts
68
+ type CodeModeResult =
69
+ | { result: "success"; sessionId: string; data?: JsonValue }
70
+ | { result: "pending"; sessionId: string }
71
+ | {
72
+ result: "failed";
73
+ sessionId: string;
74
+ error: {
75
+ code:
76
+ | "unknown"
77
+ | "busy"
78
+ | "capacity"
79
+ | "script"
80
+ | "serialization"
81
+ | "timeout"
82
+ | "cancellation"
83
+ | "termination"
84
+ | "runtime";
85
+ message: string;
86
+ };
87
+ };
88
+ ```
89
+
90
+ `AgentToolResult.content` remains exactly this JSON and is the only CodeMode
91
+ result text returned to the model. Pi retains additional bounded Presentation
92
+ Snapshots in tool-result details for Transcript replay and the TUI.
93
+
94
+ ## Transcript and Observer UI
95
+
96
+ The CodeMode Transcript gives all three tools semantic collapsed and expanded
97
+ rendering. Collapsed rows prioritize Cell lifecycle, a short Session ID, Cell
98
+ Ordinal, returned-value shape, nested-tool count, and elapsed time. Expanded
99
+ rows show the full Session ID, explicit call arguments, TypeScript source,
100
+ structured returned data or error, and bounded nested-tool names, outcomes, and
101
+ durations. Nested arguments and raw nested outputs are never copied into the
102
+ presentation.
103
+
104
+ Status always uses a symbol and text together:
105
+
106
+ ```text
107
+ ◉ running ○ idle ✓ completed
108
+ × failed ■ cancelled ! timed out
109
+ ```
110
+
111
+ Awaited Cells publish a presentation update immediately and once per second.
112
+ Source display is limited to 200 lines or 50 KB. Returned-data display uses
113
+ Pi's 2,000-line/50-KB limit; complete oversized data is written to a private
114
+ Result Spill while the model-facing result remains unchanged. Result Spill
115
+ files last for the live Pi session. Replayed history falls back to its retained
116
+ bounded data when a prior spill is no longer available.
117
+
118
+ In TUI mode, the read-only CodeMode Observer UI appears above the editor during
119
+ Cell activity. It shows up to eight running, idle, or recently terminal
120
+ Sessions, uses the shortest unique Session prefix of at least eight characters,
121
+ and adds `… +N more` when bounded. A footer shows `◉ N running · N live` only
122
+ while Cells run. The widget disappears ten seconds after every Session becomes
123
+ idle or terminal and remounts on later activity. It has no controls and issues
124
+ no hidden CodeMode or Pi tool calls.
125
+
126
+ ## Notebook Bindings
127
+
128
+ Top-level `let`, `const`, `var`, function, class, and destructuring declarations
129
+ become Notebook Bindings. Later Cells in the same session use them without
130
+ `globalThis`:
131
+
132
+ ```ts
133
+ // Cell 1
134
+ let count = 1;
135
+ function current() {
136
+ return count;
137
+ }
138
+
139
+ // Cell 2
140
+ count += 1;
141
+ return current(); // 2
142
+ ```
143
+
144
+ A later declaration may replace an existing binding, including a `const`.
145
+ Ordinary assignment to the current `const` still fails. Existing functions see
146
+ later assignments and successful redefinitions. A failed declaration
147
+ initializer preserves the previous value; earlier completed mutations and
148
+ declarations in the same failing Cell remain committed.
149
+
150
+ Cells accept TypeScript syntax, which Deno transpiles without type checking.
151
+ Type annotations therefore do not validate tool inputs or results. Cells support
152
+ top-level `await`, explicit `return`, and automatic return of the final
153
+ expression. Static and dynamic imports, `eval`, and dynamic function
154
+ constructors are unavailable. Annex-B block functions, nested lexical scopes,
155
+ and declarations beneath a source `with` statement remain Cell-local.
156
+
157
+ Notebook Bindings use protected non-configurable Deno global properties
158
+ internally. Normal unqualified and `globalThis` assignment both observe
159
+ `const` protection. Protected runtime names are rejected.
160
+
161
+ One Cell may run at a time in each session. Ordinary script and catchable Pi
162
+ tool failures leave the session reusable. Timeout, cancellation, Pi
163
+ termination, or process failure destroys that session's heap.
164
+
165
+ ## Registered tools
166
+
167
+ The `codemode_execute` description contains generated TypeScript declarations
168
+ for the currently exposed registered tools. Guest calls resolve to:
169
+
170
+ ```ts
171
+ type PiToolResult = {
172
+ content: Array<
173
+ { type: "text"; text: string } | { type: "image"; data: string; mimeType: string }
174
+ >;
175
+ details?: unknown;
176
+ };
177
+ ```
178
+
179
+ Ordinary tool failures reject with a catchable `CodeModeToolError`. A Pi result
180
+ that requests termination stops the complete CodeMode Session and cannot be
181
+ caught by guest code.
182
+
183
+ ## Exposure settings
184
+
185
+ Configure Exposure Modes under `codemode` in `~/.pi/agent/settings.json` or a
186
+ trusted project's `.pi/settings.json`:
187
+
188
+ ```json
189
+ {
190
+ "codemode": {
191
+ "maxSessions": 8,
192
+ "tools": [
193
+ { "pattern": "*", "exposure": "codemode-only" },
194
+ { "pattern": "bash", "exposure": "direct-and-codemode" },
195
+ { "pattern": "browser_*", "exposure": "direct-only" }
196
+ ]
197
+ }
198
+ }
199
+ ```
200
+
201
+ Patterns are case-sensitive minimatch globs over exact registered names; the
202
+ last match wins. Project `tools` replaces the global array, while project
203
+ `maxSessions` overrides only that field. `/reload` rereads settings.
204
+
205
+ An unmatched active tool defaults to `direct-and-codemode`; an unmatched
206
+ inactive tool remains unavailable. An explicit rule may expose an inactive tool
207
+ or activate direct access. The three `codemode_*` tools are always direct-only.
208
+ Pi's global allowed/excluded registry remains authoritative. Invalid fields or
209
+ patterns disable CodeMode for that session without changing Pi's active tools.
210
+
211
+ `maxSessions` defaults to 8 and counts only live Deno processes. Up to 64 recent
212
+ worker-free terminal or failed-admission records remain pollable.
213
+
214
+ ## Isolation and limits
215
+
216
+ Each live CodeMode Session owns a pinned Deno subprocess. Deno itself executes
217
+ unique `Blob` modules with the `application/typescript` media type, keeping
218
+ generated helper source out of ordinary source locations. Every operating-system
219
+ permission class is denied: filesystem read/write, network, environment, system
220
+ information, subprocesses, FFI, and remote imports.
221
+
222
+ Guest code receives ECMAScript built-ins, a read-only `tools` object, and only a
223
+ frozen `Deno.version` identity. Raw process and standard-stream access,
224
+ `console`, `Worker`, timers, filesystem/network APIs, and module loading are
225
+ withheld. The parent watchdog terminates the subprocess for timeout or an
226
+ infinite loop. Deno/V8 bounds each Session to a 128 MiB old-space heap and a
227
+ 1 MiB stack. Protocol inputs, tool results, and Cell results remain JSON-only
228
+ and limited to 8 MiB of UTF-8.
229
+
230
+ Registered Pi tools still execute in Pi's parent process with their normal
231
+ permissions and lifecycle hooks. Cancellation aborts them through Pi's
232
+ `AbortSignal`; a handler that ignores that signal cannot be forcibly killed, so
233
+ its late result is discarded after the CodeMode process stops.
234
+
235
+ CodeMode uses a capability-gated private Pi `AgentSession` seam, tested against
236
+ Pi `0.84.2`, to reach wrapped registered handlers and enforce direct exposure.
237
+ An incompatible Pi version fails closed and leaves active tools unchanged.
238
+
239
+ Sessions are memory-only in this release and end on Pi reload, session switch,
240
+ fork, resume, or shutdown. A future persistence format may checkpoint complete
241
+ JSON-safe Notebook Bindings; V1 neither serializes heaps nor replays Cells.
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@ian-pascoe/pi-codemode",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Process-isolated persistent TypeScript tool composition for Pi",
6
+ "keywords": [
7
+ "codemode",
8
+ "deno",
9
+ "pi",
10
+ "pi-extension",
11
+ "pi-package",
12
+ "typescript"
13
+ ],
14
+ "homepage": "https://github.com/ian-pascoe/pi-extensions#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/ian-pascoe/pi-extensions/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Ian Pascoe <ian.g.pascoe@gmail.com>",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/ian-pascoe/pi-extensions.git",
23
+ "directory": "packages/pi-codemode"
24
+ },
25
+ "files": [
26
+ "src",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "type": "module",
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "provenance": true
34
+ },
35
+ "scripts": {
36
+ "test": "vitest run --config ../../vitest.config.ts --root .",
37
+ "typecheck": "tsc --noEmit -p tsconfig.json"
38
+ },
39
+ "dependencies": {
40
+ "deno": "2.9.5",
41
+ "minimatch": "^10.2.5",
42
+ "typescript": "6.0.3"
43
+ },
44
+ "peerDependencies": {
45
+ "@earendil-works/pi-agent-core": "*",
46
+ "@earendil-works/pi-ai": "*",
47
+ "@earendil-works/pi-coding-agent": "*",
48
+ "@earendil-works/pi-tui": "*",
49
+ "typebox": "*"
50
+ },
51
+ "engines": {
52
+ "node": ">=22.19.0"
53
+ },
54
+ "pi": {
55
+ "extensions": [
56
+ "./src/index.ts"
57
+ ]
58
+ }
59
+ }