pi-desktop-ui 1.0.1
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/README.md +129 -0
- package/assets/Screenshot_1.png +0 -0
- package/assets/Screenshot_2.png +0 -0
- package/assets/Screenshot_3.png +0 -0
- package/assets/Screenshot_4.png +0 -0
- package/diagrams.md +291 -0
- package/index.ts +1224 -0
- package/package.json +27 -0
- package/pi-desktop.cmd +3 -0
- package/pi-desktop.sh +28 -0
- package/web/app.js +2671 -0
- package/web/index.html +589 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Pi Desktop UI
|
|
2
|
+
|
|
3
|
+
A native desktop GUI for [pi](https://github.com/mariozechner/pi-coding-agent) — a fully functional chat window that mirrors your terminal session with real-time streaming, markdown rendering, and workspace management.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Full bidirectional chat** — send messages from the window or the terminal, both stay in sync
|
|
12
|
+
- **Real-time streaming** — assistant responses stream token-by-token with thinking indicators
|
|
13
|
+
- **Markdown rendering** with syntax-highlighted code blocks
|
|
14
|
+
- **Mermaid diagrams** — flowcharts, sequence diagrams, state machines, and more rendered as SVG (neutral theme, dark mode adapted via CSS)
|
|
15
|
+
- **LaTeX math** — inline `$...$` and block `$$...$$` formulas rendered via KaTeX
|
|
16
|
+
- **Cancel streaming** — stop button replaces send during streaming, Escape / Ctrl+C keyboard shortcuts
|
|
17
|
+
- **Tool execution display** — see tool calls (bash, read, edit, write) with inline diffs for edits
|
|
18
|
+
- **Sidebar navigation:**
|
|
19
|
+
- **Threads** — browse and switch between session threads
|
|
20
|
+
- **Skills & Extensions** — view installed skills and loaded extensions
|
|
21
|
+
- **Settings** — model info, token stats, cost tracking
|
|
22
|
+
- **Explorer** — browse project files
|
|
23
|
+
- **Workspaces** — navigate across all pi workspaces and their sessions
|
|
24
|
+
- **Slash command palette** — access all pi commands from the window
|
|
25
|
+
- **File attachments** — drag & drop or attach files to messages
|
|
26
|
+
- **Custom footer & widget** in the terminal showing project, branch, model, and token stats
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
### From Git (recommended)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pi install git:github.com/pvjagtap/pi-desktop-ui
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Manual
|
|
37
|
+
|
|
38
|
+
Clone the repo into your pi extensions directory:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/pvjagtap/pi-desktop-ui ~/.pi/agent/extensions/pi-desktop-ui
|
|
42
|
+
cd ~/.pi/agent/extensions/pi-desktop-ui
|
|
43
|
+
npm install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then reload pi:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
/reload
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
### Auto-open on startup
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pi --desktop
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This launches pi and automatically opens the desktop window. You can also create a shortcut using the included `pi-desktop.cmd` (Windows) or `pi-desktop.sh` (macOS/Linux) scripts.
|
|
61
|
+
|
|
62
|
+
### Open manually during a session
|
|
63
|
+
|
|
64
|
+
| Method | Action |
|
|
65
|
+
|--------|--------|
|
|
66
|
+
| `Ctrl+Alt+N` | Keyboard shortcut |
|
|
67
|
+
| `/desktop` | Slash command |
|
|
68
|
+
| `/nav` | Slash command (alias) |
|
|
69
|
+
|
|
70
|
+
The window opens alongside your terminal — type in either place. Messages, tool calls, and responses are synced in real-time.
|
|
71
|
+
|
|
72
|
+
## Security
|
|
73
|
+
|
|
74
|
+
The extension implements multiple layers of defense:
|
|
75
|
+
|
|
76
|
+
- **XSS prevention** — all markdown output is sanitized through [DOMPurify](https://github.com/cure53/DOMPurify) with a strict tag/attribute allowlist
|
|
77
|
+
- **Content Security Policy** — restrictive CSP blocks inline scripts from external sources, disables `connect-src`, `object-src`, and `form-action`
|
|
78
|
+
- **Subresource Integrity** — CDN dependencies (marked, highlight.js, DOMPurify) are loaded with `integrity` hashes to prevent tampering
|
|
79
|
+
- **Pinned dependencies** — all CDN scripts are pinned to exact versions
|
|
80
|
+
- **Path traversal protection** — file explorer, thread viewer, and workspace handlers validate that paths stay within allowed directories (cwd and `~/.pi/agent/sessions/`)
|
|
81
|
+
- **Attachment limits** — file uploads are capped at 25 MB with sanitized filenames
|
|
82
|
+
- **Input validation** — message length caps, strict shape validation on persisted config, and traversal rejection on all user-supplied paths
|
|
83
|
+
|
|
84
|
+
## Dependencies
|
|
85
|
+
|
|
86
|
+
- [glimpseui](https://github.com/nickarrow/glimpseui) — native webview windows from Node.js
|
|
87
|
+
- [marked](https://github.com/markedjs/marked) — markdown parsing
|
|
88
|
+
- [mermaid](https://github.com/mermaid-js/mermaid) — diagram rendering (loaded via CDN)
|
|
89
|
+
- [KaTeX](https://github.com/KaTeX/KaTeX) — LaTeX math rendering (loaded via CDN)
|
|
90
|
+
- [DOMPurify](https://github.com/cure53/DOMPurify) — HTML sanitization (loaded via CDN)
|
|
91
|
+
- [highlight.js](https://github.com/highlightjs/highlight.js) — syntax highlighting (loaded via CDN)
|
|
92
|
+
- [ws](https://github.com/websockets/ws) — WebSocket support
|
|
93
|
+
|
|
94
|
+
## Requirements
|
|
95
|
+
|
|
96
|
+
- [pi](https://github.com/mariozechner/pi-coding-agent) coding agent
|
|
97
|
+
- The [glimpse](https://github.com/nickarrow/glimpseui) skill/package must be available (provides the `glimpseui` module)
|
|
98
|
+
|
|
99
|
+
## How It Works
|
|
100
|
+
|
|
101
|
+
The extension hooks into pi's event system to capture the full agent lifecycle:
|
|
102
|
+
|
|
103
|
+
1. **`session_start`** — initializes the custom footer and context widget
|
|
104
|
+
2. **`message_start` / `message_update` / `message_end`** — streams assistant responses to the window
|
|
105
|
+
3. **`tool_execution_start` / `tool_execution_end`** — displays tool calls with arguments and results
|
|
106
|
+
4. **`agent_start` / `agent_end`** — shows loading states and updates token stats
|
|
107
|
+
|
|
108
|
+
User messages typed in the desktop window are sent to pi via `pi.sendUserMessage()`, and the response streams back through the same event hooks.
|
|
109
|
+
|
|
110
|
+
## Package Structure
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
pi-desktop-ui/
|
|
114
|
+
├── assets/
|
|
115
|
+
│ ├── Screenshot_1.png
|
|
116
|
+
│ └── Screenshot_2.png
|
|
117
|
+
├── web/
|
|
118
|
+
│ ├── index.html # Desktop window HTML shell
|
|
119
|
+
│ └── app.js # Frontend app (sidebar, chat, explorer, themes)
|
|
120
|
+
├── index.ts # Extension entry point (events, commands, window management)
|
|
121
|
+
├── package.json # Pi package manifest + dependencies
|
|
122
|
+
├── pi-desktop.cmd # Windows launcher shortcut
|
|
123
|
+
├── pi-desktop.sh # macOS/Linux launcher shortcut
|
|
124
|
+
└── README.md
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/diagrams.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# Pi Desktop UI — Architecture Diagrams
|
|
2
|
+
|
|
3
|
+
## 1. Extension Lifecycle
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
stateDiagram-v2
|
|
7
|
+
[*] --> Registered: pi loads extension
|
|
8
|
+
Registered --> SessionStart: session_start event
|
|
9
|
+
SessionStart --> FooterEnabled: enableFooter()
|
|
10
|
+
FooterEnabled --> WidgetEnabled: enableWidget()
|
|
11
|
+
WidgetEnabled --> Ready: setStatus("desktop")
|
|
12
|
+
|
|
13
|
+
state Ready {
|
|
14
|
+
[*] --> Idle
|
|
15
|
+
Idle --> WindowOpen: Ctrl+Alt+N / /desktop / /nav / --desktop flag
|
|
16
|
+
WindowOpen --> Idle: window closed
|
|
17
|
+
WindowOpen --> WindowOpen: session_start (reload/new/resume/fork)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Ready --> Shutdown: session_shutdown
|
|
21
|
+
Shutdown --> [*]: closeActiveWindow()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 2. High-Level Architecture
|
|
25
|
+
|
|
26
|
+
```mermaid
|
|
27
|
+
graph TB
|
|
28
|
+
subgraph Terminal["Pi Terminal - TUI"]
|
|
29
|
+
PiAgent["Pi Agent Core"]
|
|
30
|
+
Footer["Custom Footer"]
|
|
31
|
+
Widget["Context Widget"]
|
|
32
|
+
Status["Status Bar"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
subgraph Extension["index.ts Extension"]
|
|
36
|
+
EventHandlers["Event Handlers"]
|
|
37
|
+
WindowManager["Window Manager"]
|
|
38
|
+
DataCollectors["Data Collectors"]
|
|
39
|
+
Security["Security Layer"]
|
|
40
|
+
CommandReg["Command Registration"]
|
|
41
|
+
HTMLBuilder["HTML Builder"]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
subgraph DesktopWindow["Glimpse Native Window"]
|
|
45
|
+
IndexHTML["web/index.html"]
|
|
46
|
+
Sidebar["Sidebar Navigation"]
|
|
47
|
+
ChatView["Chat View"]
|
|
48
|
+
ExplorerView["File Explorer"]
|
|
49
|
+
SkillsView["Skills View"]
|
|
50
|
+
SettingsView["Settings View"]
|
|
51
|
+
WorkspaceView["Workspace View"]
|
|
52
|
+
DiffOverlay["Diff Overlay"]
|
|
53
|
+
CommandPalette["Command Palette"]
|
|
54
|
+
StreamingUI["Streaming UI"]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
PiAgent -->|"pi.on() events"| EventHandlers
|
|
58
|
+
EventHandlers -->|"sendToWindow()"| WindowManager
|
|
59
|
+
WindowManager -->|"glimpse.send(js)"| DesktopWindow
|
|
60
|
+
DesktopWindow -->|"glimpse.on(message)"| WindowManager
|
|
61
|
+
WindowManager -->|"handleWindowMessage()"| DataCollectors
|
|
62
|
+
DataCollectors -->|"sendToWindow()"| WindowManager
|
|
63
|
+
CommandReg -->|"registerCommand"| PiAgent
|
|
64
|
+
HTMLBuilder -->|"buildDesktopHtml()"| WindowManager
|
|
65
|
+
Extension --> Footer
|
|
66
|
+
Extension --> Widget
|
|
67
|
+
Extension --> Status
|
|
68
|
+
Security -.->|validates| DataCollectors
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 3. Message Streaming Flow
|
|
72
|
+
|
|
73
|
+
```mermaid
|
|
74
|
+
sequenceDiagram
|
|
75
|
+
participant User as User (Terminal or Window)
|
|
76
|
+
participant Pi as Pi Agent
|
|
77
|
+
participant Ext as Extension (index.ts)
|
|
78
|
+
participant Win as Desktop Window (app.js)
|
|
79
|
+
|
|
80
|
+
User->>Win: Types message + Enter
|
|
81
|
+
Win->>Ext: { type: "send-message", text }
|
|
82
|
+
Ext->>Pi: pi.sendUserMessage(text)
|
|
83
|
+
|
|
84
|
+
Pi->>Ext: agent_start event
|
|
85
|
+
Ext->>Win: { type: "agent-start" }
|
|
86
|
+
Note over Win: Show waiting indicator
|
|
87
|
+
|
|
88
|
+
Pi->>Ext: message_start (assistant)
|
|
89
|
+
Ext->>Win: { type: "message-start" }
|
|
90
|
+
|
|
91
|
+
loop Thinking (optional)
|
|
92
|
+
Pi->>Ext: message_update (thinking_start)
|
|
93
|
+
Ext->>Win: { type: "thinking-start" }
|
|
94
|
+
Pi->>Ext: message_update (thinking_delta)
|
|
95
|
+
Ext->>Win: { type: "thinking-chunk", text }
|
|
96
|
+
Pi->>Ext: message_update (thinking_end)
|
|
97
|
+
Ext->>Win: { type: "thinking-end" }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
loop Text streaming
|
|
101
|
+
Pi->>Ext: message_update (text_start)
|
|
102
|
+
Ext->>Win: { type: "message-chunk-start" }
|
|
103
|
+
Pi->>Ext: message_update (text_delta)
|
|
104
|
+
Ext->>Win: { type: "message-chunk", text }
|
|
105
|
+
Pi->>Ext: message_update (text_end)
|
|
106
|
+
Ext->>Win: { type: "message-chunk-end" }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
Pi->>Ext: message_end (assistant)
|
|
110
|
+
Ext->>Win: { type: "message-end", content }
|
|
111
|
+
Note over Win: Append final message
|
|
112
|
+
|
|
113
|
+
Pi->>Ext: agent_end event
|
|
114
|
+
Ext->>Win: { type: "agent-end" }
|
|
115
|
+
Ext->>Win: { type: "stats-update", stats }
|
|
116
|
+
Note over Win: Update token stats
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 4. Tool Execution Flow
|
|
120
|
+
|
|
121
|
+
```mermaid
|
|
122
|
+
sequenceDiagram
|
|
123
|
+
participant Pi as Pi Agent
|
|
124
|
+
participant Ext as Extension
|
|
125
|
+
participant Win as Desktop Window
|
|
126
|
+
|
|
127
|
+
Pi->>Ext: tool_execution_start
|
|
128
|
+
Note over Ext: Format args display<br/>(bash→command, read→path,<br/>edit→diffs as base64)
|
|
129
|
+
|
|
130
|
+
alt Plan Mode Active & Write Tool
|
|
131
|
+
Ext->>Win: { type: "plan-mode-violation" }
|
|
132
|
+
Note over Win: Show warning toast
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
Ext->>Win: { type: "tool-start", toolName,<br/>toolCallId, argsDisplay,<br/>editDiffsB64, editPath }
|
|
136
|
+
Note over Win: Show spinner + tool card
|
|
137
|
+
|
|
138
|
+
Pi->>Ext: tool_execution_end
|
|
139
|
+
Ext->>Win: { type: "tool-end", toolCallId,<br/>isError, resultText }
|
|
140
|
+
Note over Win: Update card in-place<br/>(preserves details open state)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 5. Window ↔ Extension Communication Protocol
|
|
144
|
+
|
|
145
|
+
```mermaid
|
|
146
|
+
graph LR
|
|
147
|
+
subgraph "Window → Extension Messages"
|
|
148
|
+
SM["send-message"] --> |text| A1[pi.sendUserMessage]
|
|
149
|
+
OT["open-thread"] --> |file| A2[extractThreadMessages]
|
|
150
|
+
NAV["nav"] --> |action| A3[getDirEntries]
|
|
151
|
+
ETE["explorer-tree-expand"] --> |path| A4[getDirEntries + validate]
|
|
152
|
+
EO["explorer-open"] --> |path| A5[readFile / openApp]
|
|
153
|
+
GC["get-commands"] --> A6[getAllCommands]
|
|
154
|
+
GS["get-stats"] --> A7[getTokenStats]
|
|
155
|
+
RT["refresh-threads"] --> A8[getSessionThreads]
|
|
156
|
+
RS["refresh-skills"] --> A9[getSkills + getExtensions]
|
|
157
|
+
GW["get-workspaces"] --> A10[getWorkspaces]
|
|
158
|
+
GWS["get-workspace-sessions"] --> A11[getWorkspaceSessions]
|
|
159
|
+
ST["search-threads"] --> A12[searchSessionThreads]
|
|
160
|
+
OFP["open-folder-path"] --> A13[resolve + validate path]
|
|
161
|
+
SPM["set-plan-mode"] --> A14[toggle planMode]
|
|
162
|
+
AF["attach-file"] --> A15[write to tmp]
|
|
163
|
+
CS["cancel-streaming"] --> A16[ctx.abort]
|
|
164
|
+
SHW["set-hidden-workspaces"] --> A17[saveHiddenWorkspaces]
|
|
165
|
+
CL["close"] --> A18[closeActiveWindow]
|
|
166
|
+
end
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```mermaid
|
|
170
|
+
graph LR
|
|
171
|
+
subgraph "Extension → Window Messages"
|
|
172
|
+
B1["agent-start / agent-end"]
|
|
173
|
+
B2["message-start / chunk / end"]
|
|
174
|
+
B3["thinking-start / chunk / end"]
|
|
175
|
+
B4["tool-start / tool-end"]
|
|
176
|
+
B5["stats-update"]
|
|
177
|
+
B6["thread-messages"]
|
|
178
|
+
B7["explorer-data / tree-children"]
|
|
179
|
+
B8["file-content"]
|
|
180
|
+
B9["commands-list"]
|
|
181
|
+
B10["update-threads / update-skills"]
|
|
182
|
+
B11["workspaces-list / workspace-sessions"]
|
|
183
|
+
B12["search-results"]
|
|
184
|
+
B13["workspace-opened"]
|
|
185
|
+
B14["file-attached-ack"]
|
|
186
|
+
B15["plan-mode-violation"]
|
|
187
|
+
B16["session-changed"]
|
|
188
|
+
B17["provider-request"]
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## 6. Sidebar Navigation State Machine
|
|
193
|
+
|
|
194
|
+
```mermaid
|
|
195
|
+
stateDiagram-v2
|
|
196
|
+
[*] --> Threads: default view
|
|
197
|
+
|
|
198
|
+
Threads --> Skills: click Skills nav
|
|
199
|
+
Threads --> Settings: click Settings nav
|
|
200
|
+
Threads --> Explorer: click Explorer nav
|
|
201
|
+
Threads --> WorkspaceModal: click Open Workspace nav
|
|
202
|
+
|
|
203
|
+
Skills --> Threads: click Threads nav
|
|
204
|
+
Settings --> Threads: click Threads nav
|
|
205
|
+
Explorer --> Threads: click Explorer tree back
|
|
206
|
+
WorkspaceModal --> Threads: close modal
|
|
207
|
+
|
|
208
|
+
state Threads {
|
|
209
|
+
[*] --> CurrentSession
|
|
210
|
+
CurrentSession --> OldThread: click thread in sidebar
|
|
211
|
+
OldThread --> CurrentSession: Escape key
|
|
212
|
+
CurrentSession --> SearchResults: type in search box
|
|
213
|
+
SearchResults --> OldThread: click search result
|
|
214
|
+
SearchResults --> CurrentSession: clear search
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
state Explorer {
|
|
218
|
+
[*] --> FileTree
|
|
219
|
+
FileTree --> FileViewer: click file
|
|
220
|
+
FileViewer --> FileTree: Back button
|
|
221
|
+
FileTree --> FileTree: expand/collapse dirs
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
state WorkspaceModal {
|
|
225
|
+
[*] --> BrowseWorkspaces
|
|
226
|
+
BrowseWorkspaces --> OpenFolder: enter path
|
|
227
|
+
BrowseWorkspaces --> ExpandWorkspace: click workspace
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## 7. Data Flow on Window Open
|
|
232
|
+
|
|
233
|
+
```mermaid
|
|
234
|
+
flowchart TD
|
|
235
|
+
A[openDesktopWindow called] --> B{window already open?}
|
|
236
|
+
B -->|Yes| C[Notify warning & return]
|
|
237
|
+
B -->|No| D[collectWindowData]
|
|
238
|
+
|
|
239
|
+
D --> D1[getTokenStats]
|
|
240
|
+
D --> D2[getSessionThreads]
|
|
241
|
+
D --> D3[getSkills]
|
|
242
|
+
D --> D4[getExtensions]
|
|
243
|
+
D --> D5[getWorkspaces]
|
|
244
|
+
D --> D6[extractSessionMessages]
|
|
245
|
+
D --> D7[getDirEntries cwd]
|
|
246
|
+
D --> D8[getAllCommands]
|
|
247
|
+
D --> D9[loadHiddenWorkspaces]
|
|
248
|
+
|
|
249
|
+
D1 & D2 & D3 & D4 & D5 & D6 & D7 & D8 & D9 --> E[buildDesktopHtml]
|
|
250
|
+
|
|
251
|
+
E --> E1["Read index.html template"]
|
|
252
|
+
E --> E2["Read app.js"]
|
|
253
|
+
E --> E3["JSON.stringify data → Base64"]
|
|
254
|
+
E1 & E2 & E3 --> E4["Replace __INLINE_DATA__ and __INLINE_JS__"]
|
|
255
|
+
|
|
256
|
+
E4 --> F["glimpseui.open(html, opts)"]
|
|
257
|
+
F --> G["Register event listeners:<br/>on('message') → handleWindowMessage<br/>on('closed') → cleanup<br/>on('error') → cleanup"]
|
|
258
|
+
G --> H["Set hidden thinking label<br/>setHiddenThinkingLabel()"]
|
|
259
|
+
H --> I[Window Ready — bidirectional sync active]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 8. Security Model
|
|
263
|
+
|
|
264
|
+
```mermaid
|
|
265
|
+
flowchart TD
|
|
266
|
+
subgraph Input["User Input Validation"]
|
|
267
|
+
V1["Message length: max 100K chars"]
|
|
268
|
+
V2["Search query: 2-200 chars"]
|
|
269
|
+
V3["Attachment: max 25MB"]
|
|
270
|
+
V4["Path: no '..' traversal"]
|
|
271
|
+
V5["Filename: sanitized chars only"]
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
subgraph PathSec["Path Security"]
|
|
275
|
+
P1["isPathAllowed():<br/>must be under cwd or<br/>~/.pi/agent/sessions/"]
|
|
276
|
+
P2["isValidSessionFile():<br/>must end .jsonl +<br/>under sessions dir +<br/>no '..' in path"]
|
|
277
|
+
P3["Workspace dirName:<br/>reject / \\ chars"]
|
|
278
|
+
P4["open-folder-path:<br/>resolve + normalize +<br/>verify under sessions root"]
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
subgraph Output["Output Security"]
|
|
282
|
+
O1["DOMPurify: strict tag allowlist"]
|
|
283
|
+
O2["CSP: no connect-src,<br/>no object-src, no form-action"]
|
|
284
|
+
O3["SRI: integrity hashes on CDN"]
|
|
285
|
+
O4["escapeNonAscii():<br/>\\uXXXX encoding for<br/>Windows CP1252 safety"]
|
|
286
|
+
O5["Base64 data transfer:<br/>survives webview bridge"]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
Input --> PathSec
|
|
290
|
+
PathSec --> Output
|
|
291
|
+
```
|