@lelouchhe/webagent 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 LelouchHe
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,244 @@
1
+ # WebAgent
2
+
3
+ A terminal-style web UI for ACP-compatible agents.
4
+
5
+ Tech stack: Node.js + TypeScript (`--experimental-strip-types`), real-time WebSocket communication (`ws`), SQLite persistence (`better-sqlite3`), Zod validation.
6
+
7
+ ## Screenshots
8
+
9
+ <table>
10
+ <tr>
11
+ <td width="50%">
12
+ <img src="docs/images/overview-chat.png" alt="Desktop chat overview" />
13
+ <br />
14
+ <sub>Streaming chat in the terminal-style desktop layout.</sub>
15
+ </td>
16
+ <td width="50%">
17
+ <img src="docs/images/plan-busy.png" alt="Plan mode while busy" />
18
+ <br />
19
+ <sub>Plan mode highlighted while a turn is still running.</sub>
20
+ </td>
21
+ </tr>
22
+ <tr>
23
+ <td width="50%">
24
+ <img src="docs/images/permission-dialog.png" alt="Permission request dialog" />
25
+ <br />
26
+ <sub>Permission prompts stay inline in the conversation flow.</sub>
27
+ </td>
28
+ <td width="50%">
29
+ <img src="docs/images/bash-output.png" alt="Inline bash command output" />
30
+ <br />
31
+ <sub><code>!&lt;command&gt;</code> output streams directly into the session.</sub>
32
+ </td>
33
+ </tr>
34
+ </table>
35
+
36
+ <p align="center">
37
+ <img src="docs/images/mobile-autopilot.png" alt="Mobile autopilot mode" width="320" />
38
+ <br />
39
+ <sub>Compact mobile layout with mode highlighting and terminal-style action keys.</sub>
40
+ </p>
41
+
42
+ ## Prerequisites
43
+
44
+ - Node.js 22.6+ (requires `--experimental-strip-types`)
45
+ - An ACP-compatible agent (e.g. [Copilot CLI](https://github.com/github/copilot-cli)) installed and authenticated
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ npm install -g @lelouchhe/webagent
51
+ ```
52
+
53
+ Or run directly with npx:
54
+
55
+ ```bash
56
+ npx @lelouchhe/webagent
57
+ ```
58
+
59
+ ## Run
60
+
61
+ ```bash
62
+ webagent # start with defaults (port 6800)
63
+ webagent --config /path/to/config.toml # start with custom config
64
+ ```
65
+
66
+ Data (SQLite database, uploaded images) is stored in `./data/` relative to your current working directory by default.
67
+
68
+ ### From source
69
+
70
+ ```bash
71
+ git clone https://github.com/LelouchHe/webagent.git
72
+ cd webagent
73
+ npm install
74
+ npm run build # build static assets (public/ → dist/)
75
+ npm start # start on port 6800
76
+ ```
77
+
78
+ ### Development
79
+
80
+ ```bash
81
+ npm run dev # port 6801, uses data-dev/, auto-restarts on file changes
82
+ ```
83
+
84
+ How you keep the process running in the background is intentionally left to your own environment and preferred process manager.
85
+
86
+ ### Configuration
87
+
88
+ Configuration is via TOML files, passed with `--config`:
89
+
90
+ ```bash
91
+ webagent --config config.toml
92
+ ```
93
+
94
+ If no `--config` is provided, all settings use built-in defaults. See `config.toml` for the checked-in default settings and `config.dev.toml` for development.
95
+
96
+ | Key | Default | Description |
97
+ |---|---|---|
98
+ | `port` | `6800` | HTTP/WebSocket server port |
99
+ | `data_dir` | `data` | SQLite + uploads directory |
100
+ | `default_cwd` | `process.cwd()` | Working directory for new sessions |
101
+ | `public_dir` | `dist` | Static assets directory |
102
+ | `agent_cmd` | `copilot --acp` | ACP agent command (binary + args, space-separated) |
103
+ | `limits.bash_output` | `1048576` (1 MB) | Max bash output stored in DB per command |
104
+ | `limits.image_upload` | `10485760` (10 MB) | Max image upload size |
105
+ | `limits.cancel_timeout` | `10000` (10s) | Cancel timeout in ms; 0 disables |
106
+
107
+ To use a different ACP-compatible agent backend:
108
+
109
+ ```toml
110
+ agent_cmd = "my-agent --acp"
111
+ ```
112
+
113
+ ## Features
114
+
115
+ ### Chat
116
+
117
+ - Real-time streaming responses with Markdown rendering + syntax highlighting
118
+ - Collapsible thinking process display
119
+ - Tool call display (status animation, expandable details, diff rendering)
120
+ - Agent execution plan display (pending ○ / in-progress ◉ / done ●)
121
+ - Permission confirmation dialog for sensitive operations (Allow / Deny), synced across devices; auto-approved in autopilot mode
122
+ - Smart scroll: force-scrolls on load/switch/send, soft auto-scroll during streaming
123
+
124
+ ### Images
125
+
126
+ - Upload images (button or `^U` shortcut)
127
+ - Paste images (Ctrl+V / Cmd+V)
128
+ - Preview before sending + removable, supports multiple images
129
+ - Server-side storage, displayed inline in chat
130
+
131
+ ### Bash Execution
132
+
133
+ - `!<command>` to run shell commands directly
134
+ - Real-time output streaming (stderr in red)
135
+ - Collapsible output with exit code display
136
+ - Cancel running processes
137
+ - Cancel is session-scoped inside WebAgent: it stops the current ACP turn plus WebAgent-owned session work (like local `!` bash), but it cannot stop host-level tasks started outside the WebAgent server/runtime
138
+
139
+ ### Session Management
140
+
141
+ - Auto-resumes last session on page open, no manual switching needed
142
+ - After server restart, restores session context via ACP `loadSession` so conversations can continue
143
+ - Auto-generated titles (async, using a fast model)
144
+ - Session history persisted in SQLite, survives restarts
145
+ - `/sessions` lists all sessions (git-branch style, `*` marks current in green)
146
+ - Switching sessions replays full message history
147
+
148
+ ### Slash Commands
149
+
150
+ Type `/` to trigger an autocomplete menu (arrow keys to navigate, Tab to select, Esc to close).
151
+
152
+ | Command | Description |
153
+ |---|---|
154
+ | `/new [cwd]` | Create new session (optionally specify working directory) |
155
+ | `/pwd` | Show current working directory |
156
+ | `/model [name]` | View or switch model (fuzzy match, e.g. `/model opus`) |
157
+ | `/mode [name]` | View or switch mode (Agent / Plan / Autopilot) |
158
+ | `/think [level]` | View or switch reasoning effort (low / medium / high) |
159
+ | `/cancel` | Cancel current response |
160
+ | `/switch <title\|id>` | Switch to a session (match by title or ID prefix) |
161
+ | `/delete <title\|id>` | Delete a session |
162
+ | `/prune` | Delete all sessions except current |
163
+ | `/help` | Show help |
164
+
165
+ ### Keyboard Shortcuts
166
+
167
+ | Shortcut | Action |
168
+ |---|---|
169
+ | `Enter` | Send message |
170
+ | `Shift+Enter` | New line |
171
+ | `Ctrl+X` | Cancel current response |
172
+ | `Ctrl+M` | Cycle mode (Agent → Plan → Autopilot) |
173
+ | `Ctrl+U` | Upload image |
174
+
175
+ Tap the `❯` prompt indicator to cycle mode. Tap `new` to create a new session (hidden when input has content).
176
+
177
+ ### Theme
178
+
179
+ - Dark / light / system, toggle with `◑`
180
+ - Terminal-style UI (monospace font, `>_` logo)
181
+ - Preference saved to localStorage
182
+
183
+ ### Other
184
+
185
+ - PWA support (installable to home screen)
186
+ - WebSocket auto-reconnect (3s retry on disconnect)
187
+ - 30s heartbeat keepalive
188
+ - Auto-expanding input box
189
+ - Mobile-friendly layout
190
+ - Multi-client broadcast (events synced across devices)
191
+
192
+ ## Testing
193
+
194
+ ```bash
195
+ npm test # unit + integration
196
+ npm run test:e2e # Playwright browser E2E
197
+ ```
198
+
199
+ - `TEST_SCENARIOS.md` is the scenario-level coverage map for the current suite.
200
+ - Use it when reviewing what is already protected before adding new tests or auditing gaps.
201
+ - The E2E suite now covers session lifecycle, reconnect/restart recovery, permissions, cancel flows, bash lifecycle, media persistence, slash-menu UX, config persistence/inheritance, and multi-client config behavior.
202
+
203
+ ## Architecture
204
+
205
+ ```
206
+ Browser ←WebSocket→ server.ts ←ACP→ copilot CLI
207
+ ├── routes.ts (HTTP handlers)
208
+ ├── ws-handler.ts (WS dispatch)
209
+ ├── session-manager.ts (state)
210
+ ├── title-service.ts (auto-title)
211
+ └── store.ts (SQLite)
212
+ ```
213
+
214
+ - **server.ts** — HTTP/WebSocket server bootstrap
215
+ - **routes.ts** — HTTP request handlers (static files, REST API, image upload)
216
+ - **ws-handler.ts** — WebSocket message dispatch + broadcast
217
+ - **session-manager.ts** — Session state management (live sessions, buffers, bash procs, model cache)
218
+ - **bridge.ts** — ACP bridge, manages agent subprocess, handles permissions and file I/O
219
+ - **store.ts** — SQLite persistence (sessions + events tables, WAL mode)
220
+ - **title-service.ts** — Async session title generation (dedicated Haiku session)
221
+ - **types.ts** — Shared types + Zod schemas for WS messages
222
+
223
+ ## ACP Scope and Current Limits
224
+
225
+ WebAgent uses ACP for the core agent loop: session creation / restore, prompt turns, permission requests, streaming updates, model selection, and text file read/write.
226
+
227
+ Current scope in this repo:
228
+
229
+ - Session lifecycle goes through ACP (`newSession`, `loadSession`, `prompt`, `cancel`)
230
+ - The UI renders a subset of ACP session updates: assistant text, thinking text, tool calls, tool call updates, and plans
231
+ - Session history is persisted locally and restored after server restart
232
+
233
+ Current limits:
234
+
235
+ - MCP servers are not forwarded to the agent; sessions are created with an empty `mcpServers` list
236
+ - ACP terminal APIs are not used; `!<command>` runs through the app's own local `bash` bridge instead of an ACP-managed terminal session
237
+ - The web UI does not expose native CLI command surfaces such as `/plan`, `/fleet`, `/mcp`, `/agent`, or `/skills`
238
+ - Autopilot mode is supported: permissions are auto-approved server-side using `allow_once`
239
+ - Event handling is intentionally narrower than a native CLI client; only selected ACP updates are rendered/persisted, and the silent title-generation session suppresses normal UI events
240
+ - Model switching depends on the agent's ACP implementation and currently uses the SDK's unstable session-model API
241
+ - ACP does not expose context window usage, token counts, or remaining capacity
242
+ - No method to compact or clear session context; only option is to create a new session
243
+
244
+ In practice, this means WebAgent provides a browser UI for the core ACP chat/session workflow, but not the full product surface of direct Copilot CLI or Claude Code in a terminal.
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { dirname, join } from "node:path";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const server = join(__dirname, "..", "src", "server.ts");
9
+
10
+ const child = spawn(
11
+ process.execPath,
12
+ ["--experimental-strip-types", server, ...process.argv.slice(2)],
13
+ { stdio: "inherit" },
14
+ );
15
+
16
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
17
+ process.on(sig, () => child.kill(sig));
18
+ }
19
+
20
+ child.on("exit", (code, signal) => {
21
+ if (signal) process.kill(process.pid, signal);
22
+ else process.exit(code ?? 1);
23
+ });
package/config.toml ADDED
@@ -0,0 +1,28 @@
1
+ # WebAgent production configuration
2
+
3
+ # HTTP/WebSocket server port (default: 6800)
4
+ port = 6800
5
+
6
+ # SQLite + uploads directory (default: "data")
7
+ data_dir = "data"
8
+
9
+ # Default working directory for new sessions (default: process.cwd()).
10
+ # Leave this commented out to use the process working directory.
11
+ # default_cwd = "/path/to/your/workspace"
12
+
13
+ # Static assets directory (default: "dist")
14
+ public_dir = "dist"
15
+
16
+ # ACP agent command (binary + args, space-separated) (default: "copilot --acp")
17
+ agent_cmd = "copilot --acp"
18
+
19
+ [limits]
20
+ # Max bash output stored in DB per command (bytes, default 1 MB)
21
+ bash_output = 1_048_576
22
+
23
+ # Max image upload size (bytes, default 10 MB)
24
+ image_upload = 10_485_760
25
+
26
+ # Cancel timeout (ms, default 10s). After sending cancel, if the agent
27
+ # does not respond within this time the UI resets to idle. Set to 0 to disable.
28
+ cancel_timeout = 10_000
Binary file
Binary file
Binary file
@@ -0,0 +1,4 @@
1
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>
2
+ <rect width='100' height='100' rx='16' fill='#0d1117'/>
3
+ <text x='12' y='68' font-family='monospace' font-size='56' font-weight='bold' fill='#58a6ff'>>_</text>
4
+ </svg>
@@ -0,0 +1,46 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>>_</title>
7
+ <link rel="icon" href="/icons/icon.svg" type="image/svg+xml">
8
+ <link rel="apple-touch-icon" href="/icons/icon-180.png">
9
+ <link rel="manifest" href="/manifest.json">
10
+ <meta name="theme-color" content="#0d1117">
11
+ <meta name="apple-mobile-web-app-capable" content="yes">
12
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
13
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/dompurify@3.3.2/dist/purify.min.js"></script>
15
+ <script>document.documentElement.setAttribute('data-theme', localStorage.getItem('theme') || 'auto');</script>
16
+ <link rel="stylesheet" href="/styles.mmjqzu9r.css">
17
+ </head>
18
+ <body>
19
+
20
+ <div id="header">
21
+ <div class="header-side header-left">
22
+ <span class="logo">>_</span>
23
+ </div>
24
+ <span id="session-info" class="status"></span>
25
+ <div class="header-side header-right">
26
+ <span id="status" class="status-dot is-disconnected" data-state="disconnected" role="status" aria-live="polite" aria-label="disconnected" title="disconnected"></span>
27
+ <button id="theme-btn" title="Toggle theme">◑</button>
28
+ </div>
29
+ </div>
30
+
31
+ <div id="messages"></div>
32
+
33
+ <div id="attach-preview"></div>
34
+ <div id="input-area">
35
+ <div id="slash-menu"></div>
36
+ <span id="input-prompt" title="Cycle mode (Ctrl+M)">❯ </span>
37
+ <textarea id="input" rows="1" placeholder="Message or ?" autofocus></textarea>
38
+ <button id="new-btn" class="input-btn" title="New session">new</button>
39
+ <button id="attach-btn" class="input-btn" title="Attach image (Ctrl+U)">^U</button>
40
+ <button id="send-btn" class="input-btn" title="Send (Enter)">↵</button>
41
+ <input type="file" id="file-input" accept="image/*" multiple hidden>
42
+ </div>
43
+
44
+ <script type="module" src="/js/app.mmjqzu9r.js"></script>
45
+ </body>
46
+ </html>
@@ -0,0 +1,10 @@
1
+ // Boot entry point — imports all modules and starts the app
2
+
3
+ import './render.mmjqzu9r.js'; // theme, click-to-collapse listeners
4
+ import './commands.mmjqzu9r.js'; // slash menu listeners
5
+ import './images.mmjqzu9r.js'; // attach/paste listeners
6
+ import './input.mmjqzu9r.js'; // keyboard/send listeners
7
+ import { connect } from './connection.mmjqzu9r.js';
8
+
9
+ connect();
10
+ if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js');