aicodeman 1.14.2 → 1.15.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.
Files changed (81) hide show
  1. package/README.zh-CN.md +91 -21
  2. package/dist/cli.d.ts +32 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +19 -8
  5. package/dist/cli.js.map +1 -1
  6. package/dist/hooks-config.d.ts +20 -0
  7. package/dist/hooks-config.d.ts.map +1 -1
  8. package/dist/hooks-config.js +100 -49
  9. package/dist/hooks-config.js.map +1 -1
  10. package/dist/web/public/admin-ui.js.gz +0 -0
  11. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  12. package/dist/web/public/{app.94825d85.js → app.d4c3d0f9.js} +3 -3
  13. package/dist/web/public/app.d4c3d0f9.js.br +0 -0
  14. package/dist/web/public/app.d4c3d0f9.js.gz +0 -0
  15. package/dist/web/public/constants.e7175b95.js.gz +0 -0
  16. package/dist/web/public/cron-ui.js.gz +0 -0
  17. package/dist/web/public/entrance-animations.js.gz +0 -0
  18. package/dist/web/public/i18n.da34cfaf.js.gz +0 -0
  19. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  20. package/dist/web/public/index.html +6 -5
  21. package/dist/web/public/index.html.br +0 -0
  22. package/dist/web/public/index.html.gz +0 -0
  23. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  24. package/dist/web/public/{keyboard-accessory.9936227d.js → keyboard-accessory.2ea414af.js} +3 -0
  25. package/dist/web/public/keyboard-accessory.2ea414af.js.br +0 -0
  26. package/dist/web/public/keyboard-accessory.2ea414af.js.gz +0 -0
  27. package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
  28. package/dist/web/public/mobile-overview.js.gz +0 -0
  29. package/dist/web/public/mobile.32cacde5.css.gz +0 -0
  30. package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
  31. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  32. package/dist/web/public/panels-ui.1d4203e0.js.gz +0 -0
  33. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  34. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  35. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  36. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  37. package/dist/web/public/session-ui.1356a4f4.js.gz +0 -0
  38. package/dist/web/public/settings-ui.b41d33a5.js.gz +0 -0
  39. package/dist/web/public/styles.c4e58e13.css.gz +0 -0
  40. package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
  41. package/dist/web/public/sw.js +1 -0
  42. package/dist/web/public/sw.js.br +0 -0
  43. package/dist/web/public/sw.js.gz +0 -0
  44. package/dist/web/public/terminal-ui.430395be.js +4 -0
  45. package/dist/web/public/terminal-ui.430395be.js.br +0 -0
  46. package/dist/web/public/terminal-ui.430395be.js.gz +0 -0
  47. package/dist/web/public/ultracode-panel.js.gz +0 -0
  48. package/dist/web/public/ultracode-windows.js.gz +0 -0
  49. package/dist/web/public/upload.html.gz +0 -0
  50. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  51. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  52. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  53. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  54. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  55. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  56. package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js +4 -0
  57. package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.br +0 -0
  58. package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
  59. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  60. package/dist/web/public/vendor/xterm.css.gz +0 -0
  61. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  62. package/dist/web/public/{voice-input.085e9e73.js → voice-input.e5461859.js} +3 -0
  63. package/dist/web/public/voice-input.e5461859.js.br +0 -0
  64. package/dist/web/public/{voice-input.085e9e73.js.gz → voice-input.e5461859.js.gz} +0 -0
  65. package/dist/web/public/webview-tabs.js.gz +0 -0
  66. package/dist/web/routes/session-routes.d.ts.map +1 -1
  67. package/dist/web/routes/session-routes.js +30 -2
  68. package/dist/web/routes/session-routes.js.map +1 -1
  69. package/package.json +3 -1
  70. package/scripts/postinstall.js +29 -0
  71. package/skills/codeman/SKILL.md +62 -10
  72. package/skills/codeman/reference/endpoints.md +63 -8
  73. package/skills/codeman/reference/recipes.md +28 -5
  74. package/dist/web/public/app.94825d85.js.br +0 -0
  75. package/dist/web/public/app.94825d85.js.gz +0 -0
  76. package/dist/web/public/keyboard-accessory.9936227d.js.br +0 -0
  77. package/dist/web/public/keyboard-accessory.9936227d.js.gz +0 -0
  78. package/dist/web/public/terminal-ui.bc6819a9.js +0 -4
  79. package/dist/web/public/terminal-ui.bc6819a9.js.br +0 -0
  80. package/dist/web/public/terminal-ui.bc6819a9.js.gz +0 -0
  81. package/dist/web/public/voice-input.085e9e73.js.br +0 -0
@@ -158,7 +158,16 @@ You are yourself a session on this server, and the API has **no undo**.
158
158
  `{"success":false,"error","errorCode"}`. Read `.data`. Use `/api/v1/*` paths.
159
159
  - **A wait timeout is HTTP 200**, `{wait:{timedOut:true,signal:null}}` — not an error.
160
160
  Loop over short waits (60 s); proxies cut long-idle connections. Timeouts are
161
- **clamped** (ceiling 600 s): read back `wait.timeoutMs` for what was applied.
161
+ **clamped** (ceiling 600 s): read back `wait.timeoutMs` for what was applied. The
162
+ clamp covers positive integers only: `0`, a negative, a fraction or `30s` is a 400,
163
+ so round any computed remainder and drop it entirely rather than sending zero.
164
+ - **Never branch on `.data.status`.** It is a heuristic and is often wrong in both
165
+ directions: measured on a live claude worker reading `idle` while it was mid-turn
166
+ and actively producing output (`lastActivityAt` equal to the moment of the call),
167
+ and a worker that died inside its pane also reads `idle`. Synchronize on `stop` via
168
+ send-and-wait, or on an output marker. To judge from outside, sample
169
+ `terminal?tail=` twice a few seconds apart: a changing buffer is the only cheap
170
+ positive proof a worker is still working. `wait?until=exit` is the death check.
162
171
  - **`stop` and `blocked` fire for `claude` sessions only** (Claude Code hooks). On
163
172
  `shell`/`opencode`/`codex`/`gemini`/`antigravity`, requesting them explicitly is a
164
173
  400 — and lifecycle transitions there are coarse (a short shell command may emit
@@ -191,10 +200,35 @@ contains `❯` too — observed live). Codeman *can* auto-accept that dialog its
191
200
  the accept rides a stream match that misses on some runs (both outcomes seen live),
192
201
  so wait for the composer first and handle the dialog only as the bounded fallback —
193
202
  never send a blind Enter up front (if auto-accept already fired, it lands in the
194
- composer). Stage 1 is short on purpose: an already-trusted case matches `bypass` in
203
+ composer). Stage 1 is short on purpose: an already-trusted case matches `shift+tab` in
195
204
  under a second, while a **virgin case can never pass stage 1** (the dialog is up, so
196
205
  the composer is not) and always pays it in full before the fallback runs — the long
197
- budget belongs to stage 3, after the dialog is answered:
206
+ budget belongs to stage 3, after the dialog is answered.
207
+
208
+ ⚠️ **Match `shift+tab`, never `bypass`.** The permission mode is a server-side setting
209
+ (`claudeMode`) that is **not** exposed on `GET /api/v1/sessions/:id`, so you cannot read
210
+ which mode a worker runs. `bypass permissions on` is only the DEFAULT mode's statusline.
211
+ Measured against claude-cli 2.1.226, one pane per mode:
212
+
213
+ | how Codeman spawned it | statusline reads | `shift+tab` | `bypass` |
214
+ |------------------------|------------------|-------------|----------|
215
+ | `--dangerously-skip-permissions` (default) | `bypass permissions on` | yes | yes |
216
+ | `--permission-mode auto` | `auto mode on` | yes | no |
217
+ | `--allowedTools …` | `don't ask on` | yes | no |
218
+ | neither (`normal`) | `don't ask on` | yes | no |
219
+
220
+ Every mode ends its status bar with `(shift+tab to cycle)`, so `shift+tab` is the one
221
+ token that means "the composer is up" regardless of mode, and it is space-free, which is
222
+ what makes it survive the TUI stream. Matching `bypass` instead reports a perfectly
223
+ healthy non-default worker as broken after burning the full ladder.
224
+
225
+ ⚠️ **`shift+tab` contains a `+`, so it MUST go through `--data-urlencode`.** In a
226
+ hand-built query the `+` decodes to a space and the server searches for `shift tab`,
227
+ which never appears (measured: `matched:false`, and the response echoes back
228
+ `match: "shift tab"`, which is how you spot it).
229
+
230
+ Stage 4 stays as the last resort for the case where even that misses: a worker that
231
+ answers a trivial prompt **is** ready, whatever its statusline reads.
198
232
 
199
233
  ```bash
200
234
  # ALWAYS check .success: on failure `.data.sessionId` is null, jq -r prints the string
@@ -216,10 +250,11 @@ done
216
250
  # its pane keeps status "idle" and a pid (the local tmux attach client, not the
217
251
  # worker). The death check is wait?until=exit, below.
218
252
  SEQ=1 # $CID came from the §0 preamble; do NOT rebuild it from $$
219
- # the composer's status bar ("bypass permissions on") is the ready marker — Codeman
220
- # spawns claude in bypass mode. Single-token matches only: TUI text is space-less.
253
+ # stage 1-3: `shift+tab` is the composer's status bar in EVERY permission mode (see the
254
+ # table above), so this works whatever `claudeMode` the server runs. Single-token
255
+ # matches only: TUI text is space-less. The `+` needs --data-urlencode.
221
256
  R=$("${CURL[@]}" -G "$API/api/v1/sessions/$SID/wait-output" \
222
- --data-urlencode 'match=bypass' --data-urlencode 'from=buffer' --data-urlencode 'timeout=5000')
257
+ --data-urlencode 'match=shift+tab' --data-urlencode 'from=buffer' --data-urlencode 'timeout=5000')
223
258
  if ! jq -e '.data.wait.matched' <<<"$R" >/dev/null; then
224
259
  # composer never appeared → the trust dialog is probably still up; accept it once
225
260
  T=$("${CURL[@]}" -G "$API/api/v1/sessions/$SID/wait-output" \
@@ -230,9 +265,23 @@ if ! jq -e '.data.wait.matched' <<<"$R" >/dev/null; then
230
265
  SEQ=$((SEQ+1))
231
266
  fi
232
267
  R=$("${CURL[@]}" -G "$API/api/v1/sessions/$SID/wait-output" \
233
- --data-urlencode 'match=bypass' --data-urlencode 'from=buffer' --data-urlencode 'timeout=45000')
234
- jq -e '.data.wait.matched' <<<"$R" >/dev/null || \
235
- { echo "worker $SID never became ready; inspect terminal?tail="; }
268
+ --data-urlencode 'match=shift+tab' --data-urlencode 'from=buffer' --data-urlencode 'timeout=45000')
269
+ fi
270
+ if ! jq -e '.data.wait.matched' <<<"$R" >/dev/null; then
271
+ # stage 4, last resort: the composer never appeared at all. A miss is still not proof
272
+ # of a broken worker, and answering is proof that it works. Split the token (your keystrokes echo
273
+ # into the stream) and keep it unique per call. This costs the worker one turn, so
274
+ # it runs only after the fast path missed. It must stay AFTER stage 2, which is the
275
+ # only thing that clears the trust dialog: free text plus \r into a dialog still up
276
+ # answers it blind, which is the same footgun as the up-front Enter.
277
+ TOK="${RANDOM}_$$"
278
+ "${CURL[@]}" -X POST "$API/api/v1/sessions/$SID/input" -H 'Content-Type: application/json' \
279
+ -d '{"input":"reply with the word READY immediately followed by _'"$TOK"' and nothing else\r","useMux":true,"clientId":"'"$CID"'","seq":'$SEQ'}' >/dev/null
280
+ SEQ=$((SEQ+1))
281
+ "${CURL[@]}" -G "$API/api/v1/sessions/$SID/wait-output" \
282
+ --data-urlencode "match=READY_$TOK" --data-urlencode 'from=buffer' --data-urlencode 'timeout=60000' \
283
+ | jq -e '.data.wait.matched' >/dev/null \
284
+ || echo "worker $SID never became ready; inspect terminal?tail="
236
285
  fi
237
286
  ```
238
287
 
@@ -313,8 +362,11 @@ why the loop above is bounded rather than open-ended. Fall back to the terminal
313
362
  (`textOutput` in `GET .../output` stays empty for interactive sessions; don't use it):
314
363
 
315
364
  ```bash
365
+ # \x1b is a GNU-sed extension: BSD sed (macOS) matches it as a literal "x1b", so the
366
+ # same one-liner strips NOTHING there and hands you raw ANSI. Feed sed a real ESC.
367
+ ESC=$(printf '\033')
316
368
  "${CURL[@]}" "$API/api/v1/sessions/$SID/terminal?tail=3000" | jq -r '.data.terminalBuffer' \
317
- | sed -e 's/\x1b\[[0-9;?]*[a-zA-Z]//g' -e 's/\x1b([B0]//g' | grep -v '^[[:space:]]*$' | tail -30
369
+ | sed -e "s/${ESC}\[[0-9;?]*[a-zA-Z]//g" -e "s/${ESC}([B0]//g" | grep -v '^[[:space:]]*$' | tail -30
318
370
  ```
319
371
 
320
372
  ⚠️ Do not use that pipeline to read a **claude/codex** answer. A full-screen TUI draws
@@ -13,6 +13,7 @@ Every JSON response: `{"success":true,"data":…}` or
13
13
  |-------------|------|---------|
14
14
  | `INVALID_INPUT` | 400 | malformed request; the message names the bad field |
15
15
  | `UNAUTHORIZED` | 401 | auth required or failed (send `-u user:password`). ⚠️ The 401 body is plain text, NOT this envelope — `jq` dies with a parse error, see the guard in SKILL.md |
16
+ | `FORBIDDEN` | 403 | authenticated but not permitted: an admin-only route in multi-user mode, a `workingDir`/case path outside your own workspace, or a shell session without the can-bypass-permissions grant. ⚠️ **Not** what an ownership miss on a session returns: a session you do not own answers 404 `NOT_FOUND`, identically to one that does not exist (deliberate, it leaks no existence) |
16
17
  | `NOT_FOUND` | 404 | no such session, or one this caller does not own |
17
18
  | `SESSION_BUSY` | 409 | on a **wait**: this session's waiter cap (16, combined signal+output) is full. On **quick-start**: the 50-session cap is full, so clean up before starting more |
18
19
  | `CONFLICT` / `ALREADY_EXISTS` | 409 | conflicts with current state |
@@ -23,12 +24,21 @@ Every JSON response: `{"success":true,"data":…}` or
23
24
  `SESSION_BUSY` vs `RATE_LIMITED` on the wait endpoints is deliberate: the first means
24
25
  "too many waiters on *this* session", the second means the *pool* is full.
25
26
 
27
+ ⚠️ **The guards that run before any handler answer in PLAIN TEXT, not this envelope**,
28
+ so `jq` reports a parse error and `.errorCode` is simply absent. All of them:
29
+ `401 Unauthorized` (Basic auth, carries `WWW-Authenticate`), `401 Unauthorized: hook
30
+ secret required`, `403 Forbidden: host not allowed` (Host allowlist), `403 Forbidden:
31
+ cross-site request blocked` (Origin/CSRF guard), and the auth rate limiter's
32
+ `429 Too Many Requests` (with `Retry-After`; distinct from the JSON `RATE_LIMITED`
33
+ above, which is the waiter pool). When a call returns something `jq` cannot parse,
34
+ read the status with `-w '%{http_code}'` and the raw body before assuming a bug.
35
+
26
36
  ## Sessions
27
37
 
28
38
  | Task | Call |
29
39
  |------|------|
30
40
  | list sessions (metadata only, ~1.5 KB each, safe to poll) | `GET /api/v1/sessions` |
31
- | one session (has `.data.pid`, `null` until the PTY spawns) | `GET /api/v1/sessions/:id` — ⚠️ **not a liveness check**: a worker that dies inside its pane keeps `status:"idle"` and a pid (the tmux attach client); `wait?until=exit` is the death check |
41
+ | one session (has `.data.pid`, `null` until the PTY spawns) | `GET /api/v1/sessions/:id` — ⚠️ **neither a liveness nor a busy check**, see below |
32
42
  | unified list incl. history | `GET /api/v1/sessions/unified` → `.data.sessions[]` (NOT `.data[]`), and it folds in transcript history from the whole machine — never use it to verify cleanup; `GET /api/v1/sessions` is the cleanup check |
33
43
  | start case + session in one call | `POST /api/v1/quick-start` |
34
44
  | send input | `POST /api/v1/sessions/:id/input` |
@@ -38,7 +48,33 @@ Every JSON response: `{"success":true,"data":…}` or
38
48
  | background agents, one session | `GET /api/v1/sessions/:id/subagents` |
39
49
  | background agents, global list | `GET /api/v1/subagents` (admin-only in multi-user mode) |
40
50
  | server status / version | `GET /api/v1/status` → `.data.version` |
41
- | delete one session (yours only, via `delete_session`) | `DELETE /api/v1/sessions/:id` — never call it bare; the fail-closed helper in SKILL.md §0 is the only self-protection that exists |
51
+ | delete one session (yours only, via `delete_session`) | `DELETE /api/v1/sessions/:id` — never call it bare; the fail-closed helper in SKILL.md §0 is the only self-protection that exists. Answers `{"success":true,"data":{}}`: an **empty** body is the success signal, there is nothing to read back |
52
+
53
+ `DELETE /api/v1/sessions/:id` takes one undocumented query parameter, `killMux`, and
54
+ it defaults to `true` (anything other than the exact string `false` means kill). With
55
+ `?killMux=false` the call **detaches instead of killing**: the tmux session and the
56
+ agent inside it keep running, the session drops out of `GET /api/v1/sessions` so it
57
+ looks deleted, and it is deliberately left in persisted state for recovery (the
58
+ lifecycle log records `detached`, not `deleted`). That is the wrong tool for agent
59
+ cleanup: your worker keeps burning tokens where neither you nor the user can see it,
60
+ and the list you would check to confirm cleanup shows it gone. Delete plainly, and let
61
+ `killMux` default.
62
+
63
+ ⚠️ **`.data.status` is a heuristic and is often simply wrong. Never branch on it.**
64
+ Measured on a live claude worker: `status` read `idle` while the worker was mid-turn
65
+ and actively producing output, with `lastActivityAt` equal to the moment of the call.
66
+ It is wrong in both directions, so neither value tells you anything you can act on:
67
+
68
+ - **`idle` does not mean finished.** Use `stop` (the definitive end-of-turn hook) via
69
+ send-and-wait, or an output marker. If you must judge from outside, sample
70
+ `terminal?tail=` twice a few seconds apart and compare: a changing buffer is the
71
+ only cheap positive proof that a worker is still working.
72
+ - **`idle` does not mean alive.** A worker that dies inside its pane keeps
73
+ `status:"idle"` and a pid (that pid is the local tmux attach client, not the
74
+ worker). `wait?until=exit` is the death check.
75
+
76
+ Treat `status` as a UI hint. Every synchronization decision in these recipes is built
77
+ on signals and markers for exactly this reason.
42
78
 
43
79
  ⚠️ `GET /api/v1/sessions/:id/output` → `.data.textOutput` looks like the obvious read
44
80
  but stays **empty for interactive tmux-backed sessions** (it is fed only by the legacy
@@ -47,7 +83,11 @@ JSON-stream path). Verified empty on live claude and shell sessions. Use
47
83
  hook-less modes, or to diagnose a prompt that was never submitted, and strip ANSI:
48
84
 
49
85
  ```bash
50
- … | jq -r '.data.terminalBuffer' | sed -e 's/\x1b\[[0-9;?]*[a-zA-Z]//g' -e 's/\x1b([B0]//g'
86
+ # `\x1b` is a GNU-sed extension. BSD sed (macOS, the default there) reads it as a
87
+ # literal "x1b", matches nothing, and hands back raw ANSI, silently. Feed sed a real
88
+ # ESC byte instead; that form works on GNU and BSD alike.
89
+ ESC=$(printf '\033')
90
+ … | jq -r '.data.terminalBuffer' | sed -e "s/${ESC}\[[0-9;?]*[a-zA-Z]//g" -e "s/${ESC}([B0]//g"
51
91
  ```
52
92
 
53
93
  `POST /api/v1/quick-start` body (all optional):
@@ -83,6 +123,12 @@ do want a worker in an existing checkout.
83
123
  confirmation at all.
84
124
  - `input` must be single-line (newlines are stripped). To send a bare Enter (confirm
85
125
  a dialog), send `{"input":"\r"}`.
126
+ - `input` is capped at **100 000 characters**; one character over is a 400
127
+ `INVALID_INPUT` and **nothing is typed** (the schema rejects the whole body, so it
128
+ is not a truncation). Since the value is one line anyway, a prompt that big means
129
+ you are pasting a file into the composer: write it to disk in the worker's case
130
+ directory and send a path instead. `clientId` is capped at 128 characters on the
131
+ same terms.
86
132
  - `clientId`+`seq` give exactly-once delivery: the server applies each pair at most
87
133
  once. Increment `seq` per new input.
88
134
 
@@ -94,6 +140,13 @@ Three bounded long-polls. Shared semantics:
94
140
  `tailscale serve` / cloudflared cut idle connections.
95
141
  - Timeouts are **clamped** to `[1000, 600000]` ms (operator-tunable); the applied
96
142
  value is echoed as `wait.timeoutMs` — read it back, never assume.
143
+ - ⚠️ Clamping only covers **positive integers**. `timeout=0`, a negative value, a
144
+ fraction (`timeout=1500.5`) and anything non-numeric (`timeout=30s`) are rejected by
145
+ the schema as a 400 `INVALID_INPUT` naming the field, not silently clamped up to
146
+ the floor. Omit the parameter to take the 60 000 ms default; never send a computed
147
+ remainder without rounding it and checking it is still above zero. Same rule for
148
+ `waitTimeout` in the input body, where the value must additionally be a JSON number
149
+ (a quoted `"60000"` is a 400).
97
150
  - All three nest the result under `.data.wait`, same shape, so one helper parses all.
98
151
  - `.data.status` (post-wait `SessionStatus`) and `.data.limitPaused` ride along.
99
152
  `limitPaused:true` means the session is paused on a usage limit and will emit
@@ -136,7 +189,7 @@ worker that finishes before its gather is unobservable (see recipes.md Flow 3b).
136
189
  | Param | Default | Notes |
137
190
  |-------|---------|-------|
138
191
  | `until` | `stop,idle,exit` | comma list; unknown token → 400 naming it |
139
- | `timeout` | 60000 | ms, clamped; applied value echoed as `wait.timeoutMs` |
192
+ | `timeout` | 60000 | ms, positive integer only (0/negative/fractional = 400); clamped, applied value echoed as `wait.timeoutMs` |
140
193
  | `fresh` | `0` | `1` requires an actual *transition*, ignoring the state at call time |
141
194
 
142
195
  ⚠️ A session whose PTY has not spawned (`pid:null`) or has exited counts as `exit`
@@ -152,7 +205,7 @@ SKILL.md, not this endpoint.
152
205
  | `match` | required | literal substring, 1–200 chars, ANSI-stripped; chunk-straddling matches found; **no regex** — a `regex=` param is a 400 |
153
206
  | `nocase` | `0` | case-insensitive compare; snippet keeps original casing |
154
207
  | `from` | `now` | `buffer` scans the tail (~256 KB) of existing output first |
155
- | `timeout` | 60000 | same clamp |
208
+ | `timeout` | 60000 | same clamp, same positive-integer rule |
156
209
 
157
210
  Four traps, all observed live:
158
211
 
@@ -173,7 +226,7 @@ Four traps, all observed live:
173
226
  spaced phrase. Whether a given phrase keeps its spaces depends on how the TUI
174
227
  drew it (observed live: some multi-word matches fire, some never do), so treat
175
228
  multi-word matches against TUI screens as unreliable and match a **single
176
- space-free token** (`trust`, `bypass`). Plain command output (shell workers,
229
+ space-free token** (`trust`, `shift+tab`). Plain command output (shell workers,
177
230
  `echo` lines) keeps real spaces and multi-word matches work there.
178
231
 
179
232
  Build the query with `-G --data-urlencode` (a `+` in a hand-built query decodes to a
@@ -185,7 +238,7 @@ around the match, blank runs collapsed — the snippet is often all you need to
185
238
  | Field | Notes |
186
239
  |-------|-------|
187
240
  | `wait` | `true` (default signal set) or the same comma grammar as `until`; absent = historical fire-and-forget |
188
- | `waitTimeout` | ms, same clamp |
241
+ | `waitTimeout` | ms, same clamp; a JSON number, positive integer (`"60000"` is a 400) |
189
242
 
190
243
  Registers the waiter **before** typing, which closes the race where send-then-wait
191
244
  sees the previous turn's idle state and returns instantly. Response adds `delivered`
@@ -222,7 +275,9 @@ whose prompt was never submitted (missing `\r`) produces the same
222
275
  | connection refused from inside a container | a loopback-bound server is unreachable from a container, and `CODEMAN_DOCKER_BRIDGE_HOOKS=1` does **not** fix that: it opens a hooks-only listener, so hook events start flowing but `/api/v1/*` stays refused. Driving the API from inside a Docker case needs a reachable bind (an operator decision); report it, don't retry |
223
276
  | wait routes 404 on a valid session id | read the `.error` text: a `Route ...` prefix means the server predates the wait endpoints (< 1.13.0; a dev build can serve them while reporting an older version, so probe, never version-compare) — poll `terminal?tail=` and say so. `Session ... not found` means your id is wrong, not the server |
224
277
  | wait on `stop` never resolves | non-claude mode, or hooks not reaching the server (Docker/remote), or a case created by Codeman < 1.13.0 against an `--https` install (its hook curls lacked `-k` and TLS-failed silently; a 1.13.0+ server rewrites them the next time a session starts in that case). Use markers or `idle,exit` |
225
- | new claude worker ignores its first prompt | it was showing the first-run trust dialog and Codeman's auto-accept missed; use the readiness recipe in SKILL.md (wait for `bypass` first, accept the dialog only as the bounded fallback) |
278
+ | new claude worker ignores its first prompt | it was showing the first-run trust dialog and Codeman's auto-accept missed; use the readiness recipe in SKILL.md (wait for `shift+tab` first, accept the dialog only as the bounded fallback) |
279
+ | readiness burns its whole budget, then the worker answers fine anyway | you matched `bypass`, which is the statusline of ONE permission mode. Codeman spawns `--dangerously-skip-permissions` by default, but the server's `claudeMode` setting also has `auto` (`auto mode on`), `allowedTools` and `normal` (both `don't ask on`), and the mode is not exposed on `GET /api/v1/sessions/:id`. Match **`shift+tab`** instead: every mode's status bar ends `(shift+tab to cycle)` (measured per mode against claude-cli 2.1.226). ⚠️ It must go through `--data-urlencode`, or the `+` decodes to a space and you silently search for `shift tab`. Expect `blocked` signals mid-turn on the non-default modes |
280
+ | ANSI escapes survive the strip pipeline | `sed -e 's/\x1b…'` on macOS: `\x1b` is GNU-only, BSD sed matches nothing and strips nothing. Use the `ESC=$(printf '\033')` form above |
226
281
  | `wait-output` times out although the pane shows the text | multi-word match against a TUI screen; the stream has no spaces there — match one token |
227
282
  | `wait-output` matched instantly with stale text | generic marker + tmux repaint; use `DONE_$RANDOM` |
228
283
  | 409 `SESSION_BUSY` on a wait | too many concurrent waiters on that session (cap 16 combined); reuse one wait per worker |
@@ -39,13 +39,20 @@ SEQ=1 # $CID is the fixed literal from §0; never rebuild it from
39
39
  # virgin case can never pass it (the dialog is up) and always pays it in full —
40
40
  # the long budget belongs to stage 3, after the dialog is answered.
41
41
  # Single-token matches only: TUI text is space-less in the stream.
42
+ # ⚠️ `bypass` is the statusline of ONE permission mode (the default one Codeman
43
+ # spawns). The server's `claudeMode` setting also has auto/allowedTools/normal
44
+ # spawns whose statusline differs, and the mode is not exposed on GET
45
+ # /api/v1/sessions/:id. `shift+tab` is the one token EVERY mode's status bar ends
46
+ # with ('(shift+tab to cycle)'), measured per mode, so match that and not `bypass`.
47
+ # The `+` needs --data-urlencode or it decodes to a space. Stage 4 remains the last
48
+ # resort: proving readiness by making the worker answer rather than by chrome.
42
49
  for _ in $(seq 1 30); do
43
50
  [ "$("${CURL[@]}" "$API/api/v1/sessions/$SID" | jq '.data.pid')" != null ] && break; sleep 1
44
51
  done
45
52
  # (pid != null proves startup only — a worker that later dies inside its pane keeps
46
53
  # status "idle" and a pid. The death check is wait?until=exit.)
47
54
  R=$("${CURL[@]}" -G "$API/api/v1/sessions/$SID/wait-output" \
48
- --data-urlencode 'match=bypass' --data-urlencode 'from=buffer' --data-urlencode 'timeout=5000')
55
+ --data-urlencode 'match=shift+tab' --data-urlencode 'from=buffer' --data-urlencode 'timeout=5000')
49
56
  if ! jq -e '.data.wait.matched' <<<"$R" >/dev/null; then
50
57
  T=$("${CURL[@]}" -G "$API/api/v1/sessions/$SID/wait-output" \
51
58
  --data-urlencode 'match=trust' --data-urlencode 'from=buffer' --data-urlencode 'timeout=2000')
@@ -55,8 +62,21 @@ if ! jq -e '.data.wait.matched' <<<"$R" >/dev/null; then
55
62
  SEQ=$((SEQ+1))
56
63
  fi
57
64
  R=$("${CURL[@]}" -G "$API/api/v1/sessions/$SID/wait-output" \
58
- --data-urlencode 'match=bypass' --data-urlencode 'from=buffer' --data-urlencode 'timeout=45000')
59
- jq -e '.data.wait.matched' <<<"$R" >/dev/null || echo "worker $SID not ready; inspect terminal?tail="
65
+ --data-urlencode 'match=shift+tab' --data-urlencode 'from=buffer' --data-urlencode 'timeout=45000')
66
+ fi
67
+ if ! jq -e '.data.wait.matched' <<<"$R" >/dev/null; then
68
+ # stage 4, mode-agnostic and bounded: answering a trivial prompt IS readiness.
69
+ # Costs the worker one turn, so it only runs when the fast marker missed. Split
70
+ # token (the typed line echoes into the stream) and unique per call. Must stay AFTER
71
+ # the dialog fallback: free text plus \r into a trust dialog still up answers it
72
+ # blind, the same footgun as an up-front Enter.
73
+ TOK="${RANDOM}_$$"
74
+ "${CURL[@]}" -X POST "$API/api/v1/sessions/$SID/input" -H 'Content-Type: application/json' \
75
+ -d '{"input":"reply with the word READY immediately followed by _'"$TOK"' and nothing else\r","useMux":true,"clientId":"'"$CID"'","seq":'$SEQ'}' >/dev/null
76
+ SEQ=$((SEQ+1))
77
+ "${CURL[@]}" -G "$API/api/v1/sessions/$SID/wait-output" \
78
+ --data-urlencode "match=READY_$TOK" --data-urlencode 'from=buffer' --data-urlencode 'timeout=60000' \
79
+ | jq -e '.data.wait.matched' >/dev/null || echo "worker $SID not ready; inspect terminal?tail="
60
80
  fi
61
81
 
62
82
  # 3. send-and-wait, looping on the IDENTICAL request (tagged duplicate: no retype).
@@ -244,13 +264,16 @@ the worker remembering to print a token.
244
264
 
245
265
  Claude workers can block on a permission dialog. `blocked` is a wait signal
246
266
  (claude-mode only), so watch for it and surface the question to the user instead of
247
- guessing an answer:
267
+ guessing an answer. Expect it routinely on a server whose `claudeMode` is not the
268
+ default bypass one (the same setting that decides whether the readiness marker in
269
+ Flow 1 ever appears):
248
270
 
249
271
  ```bash
272
+ ESC=$(printf '\033') # \x1b is GNU-sed only; BSD sed (macOS) would strip nothing
250
273
  R=$("${CURL[@]}" "$API/api/v1/sessions/$SID/wait?until=stop,blocked,exit&timeout=60000")
251
274
  if [ "$(jq -r '.data.wait.signal' <<<"$R")" = blocked ]; then
252
275
  "${CURL[@]}" "$API/api/v1/sessions/$SID/terminal?tail=2000" | jq -r '.data.terminalBuffer' \
253
- | sed -e 's/\x1b\[[0-9;?]*[a-zA-Z]//g' | grep -v '^[[:space:]]*$' | tail -15
276
+ | sed -e "s/${ESC}\[[0-9;?]*[a-zA-Z]//g" | grep -v '^[[:space:]]*$' | tail -15
254
277
  # show this to the user and ask how to answer; do NOT auto-confirm another
255
278
  # session's permission prompt
256
279
  fi
Binary file
Binary file
@@ -1,4 +0,0 @@
1
- "use strict";(function(e){const t=/^\x1b\[[\?>=]?[\d;]*[cnR]$/,s=/^\x1b\][\d;]*[^\x07\x1b]*(?:\x07|\x1b\\)$/,a=/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;?<>=!]*[ -/]*[@-~]|\x1b[()#][0-9A-Za-z]|\x1b[=>78M]/g,n="\x1B[5~",l="\x1B[6~",c=/^\x1b(?:\[(?:[ABCDHF]|1;[2-8][ABCDHF]|[1-8](?:;[2-8])?~)|O[ABCDHF])$/,p="\x1B[200~";function E(d){return c.test(d)}function C(d){return t.test(d)||s.test(d)}function I(d){return C(d)}const f={og:{background:"#0d0d0d",foreground:"#e0e0e0",cursor:"#e0e0e0",cursorAccent:"#0d0d0d",selection:"rgba(255,255,255,0.3)",black:"#0d0d0d",red:"#ff6b6b",green:"#51cf66",yellow:"#ffd43b",blue:"#339af0",magenta:"#cc5de8",cyan:"#22b8cf",white:"#e0e0e0",brightBlack:"#495057",brightRed:"#ff8787",brightGreen:"#69db7c",brightYellow:"#ffe066",brightBlue:"#5c7cfa",brightMagenta:"#da77f2",brightCyan:"#66d9e8",brightWhite:"#ffffff"},"daylight-green":{background:"#161b23",foreground:"#dfe6ef",cursor:"#2fd3aa",cursorAccent:"#161b23",selection:"rgba(47,211,170,0.22)",black:"#161b23",red:"#ff8585",green:"#34d8a0",yellow:"#f0c25a",blue:"#5cc6e8",magenta:"#c79af2",cyan:"#2bcbbb",white:"#dfe6ef",brightBlack:"#5b6675",brightRed:"#ffa0a0",brightGreen:"#5fe6b8",brightYellow:"#ffd884",brightBlue:"#82d4ee",brightMagenta:"#d6b3f7",brightCyan:"#5ee0d4",brightWhite:"#f3f6fa"},"daylight-blue":{background:"#161b23",foreground:"#dfe6ef",cursor:"#38b6f0",cursorAccent:"#161b23",selection:"rgba(56,182,240,0.22)",black:"#161b23",red:"#ff8585",green:"#34d8a0",yellow:"#f0c25a",blue:"#5cc6e8",magenta:"#c79af2",cyan:"#2bcbbb",white:"#dfe6ef",brightBlack:"#5b6675",brightRed:"#ffa0a0",brightGreen:"#5fe6b8",brightYellow:"#ffd884",brightBlue:"#82d4ee",brightMagenta:"#d6b3f7",brightCyan:"#5ee0d4",brightWhite:"#f3f6fa"},"paper-gray":{background:"#f6f8fa",foreground:"#1f2328",cursor:"#0969da",cursorAccent:"#ffffff",selection:"rgba(9,105,218,0.2)",black:"#24292f",red:"#cf222e",green:"#1a7f37",yellow:"#9a6700",blue:"#0969da",magenta:"#8250df",cyan:"#1b7c83",white:"#59636e",brightBlack:"#6e7781",brightRed:"#a40e26",brightGreen:"#116329",brightYellow:"#7d4e00",brightBlue:"#0550ae",brightMagenta:"#6639ba",brightCyan:"#116b75",brightWhite:"#1f2328"},"solarized-light":{background:"#fdf6e3",foreground:"#586e75",cursor:"#147ba3",cursorAccent:"#fdf6e3",selection:"rgba(38,139,210,0.2)",black:"#eee8d5",red:"#dc322f",green:"#758600",yellow:"#9b7800",blue:"#147ba3",magenta:"#d33682",cyan:"#2a9189",white:"#073642",brightBlack:"#93a1a1",brightRed:"#cb4b16",brightGreen:"#657b83",brightYellow:"#586e75",brightBlue:"#268bd2",brightMagenta:"#6c71c4",brightCyan:"#2aa198",brightWhite:"#002b36"},"catppuccin-latte":{background:"#eff1f5",foreground:"#4c4f69",cursor:"#1e66f5",cursorAccent:"#ffffff",selection:"rgba(30,102,245,0.18)",black:"#5c5f77",red:"#d20f39",green:"#3b8f2b",yellow:"#a86605",blue:"#1e66f5",magenta:"#8839ef",cyan:"#177f86",white:"#6c6f85",brightBlack:"#7c7f93",brightRed:"#b50930",brightGreen:"#2f7622",brightYellow:"#8b5604",brightBlue:"#174fbf",brightMagenta:"#6f2bc5",brightCyan:"#116b71",brightWhite:"#4c4f69"},"rose-pine-dawn":{background:"#faf4ed",foreground:"#575279",cursor:"#286983",cursorAccent:"#fffaf3",selection:"rgba(40,105,131,0.2)",black:"#575279",red:"#b4637a",green:"#286983",yellow:"#96681f",blue:"#477f91",magenta:"#907aa9",cyan:"#3f7f8b",white:"#6e6a86",brightBlack:"#797593",brightRed:"#984d66",brightGreen:"#1f5266",brightYellow:"#7d5417",brightBlue:"#386b7c",brightMagenta:"#765f90",brightCyan:"#326b76",brightWhite:"#575279"}},r=new Set(["paper-gray","solarized-light","catppuccin-latte","rose-pine-dawn"]);function _(){return typeof document<"u"&&document.documentElement.dataset.skin||"daylight-blue"}function h(){const d=_();return f[d]||f["daylight-blue"]}function S(d=_()){return r.has(d)}e.CodemanTerminalInput={isTerminalQueryResponse:C,shouldSuppressTerminalQueryResponse:I,isComposerNavKey:E,BRACKETED_PASTE_START:p,USER_SCROLL_STICKY_SUPPRESS_MS:1500,TOUCH_COMPAT_MOUSE_SUPPRESS_MS:450,REPLAY_ESCAPE_RE:a,KEY_PAGE_UP:n,KEY_PAGE_DOWN:l,PAGE_KEY_SCREEN_FRACTION:.5,PAGE_KEY_MAX_PER_BATCH:3},e.CODEMAN_XTERM_THEMES=f,e.codemanCurrentXtermTheme=h,e.codemanCurrentSkinIsLight=S})(window),Object.assign(CodemanApp.prototype,{initTerminal(){const e=parseInt(localStorage.getItem("codeman-scrollback")),t=Number.isFinite(e)&&e>0?Math.max(e,DEFAULT_SCROLLBACK):DEFAULT_SCROLLBACK;if(this.terminal=new Terminal({theme:{...window.codemanCurrentXtermTheme()},fontFamily:'"Fira Code", "Cascadia Code", "JetBrains Mono", "SF Mono", Monaco, monospace',fontSize:MobileDetection.getDeviceType()==="mobile"?10:14,lineHeight:1.2,cursorBlink:!1,cursorStyle:"block",minimumContrastRatio:window.codemanCurrentSkinIsLight()?4.5:1,scrollback:t,allowTransparency:!0,allowProposedApi:!0}),this.fitAddon=new FitAddon.FitAddon,this.terminal.loadAddon(this.fitAddon),this._xtermSnapshots=new Map,typeof SerializeAddon<"u")try{this._serializeAddon=new SerializeAddon.SerializeAddon,this.terminal.loadAddon(this._serializeAddon)}catch{this._serializeAddon=null}if(typeof Unicode11Addon<"u")try{const r=new Unicode11Addon.Unicode11Addon;this.terminal.loadAddon(r),this.terminal.unicode.activeVersion="11"}catch{}const s=document.getElementById("terminalContainer");this.terminal.open(s),this._installMobileTapMouseGuard(),this.terminal.attachCustomKeyEventHandler(r=>{if(r.isComposing||r.keyCode===229||r.altKey&&!r.ctrlKey&&!r.shiftKey&&/^(Digit[1-9]|BracketLeft|BracketRight|KeyK)$/.test(r.code||"")||r.type==="keydown"&&this.shouldOpenCommandPaletteFromShortcut?.(r))return!1;if(this.shouldCopyTerminalSelectionFromShortcut?.(r)){const _=this.terminal.hasSelection?.()?this.terminal.getSelection():"";return _?(r.preventDefault(),this.copyTerminalSelection(_),!1):r.shiftKey?(r.preventDefault(),!1):!0}if((r.ctrlKey||r.metaKey)&&r.key==="v"&&r.type==="keydown")return this.activeSessionId&&this._handleImagePaste&&this._handleImagePaste(),!1;if(r.key==="Enter"&&(r.shiftKey||r.ctrlKey)&&r.type==="keydown"){if(this.activeSessionId)if(this._localEchoEnabled){const _=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._localEchoOverlay?.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),_&&(this._pendingInput+=_,f()),setTimeout(()=>{fetch(`/api/sessions/${this.activeSessionId}/send-key`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:r.ctrlKey?"C-Enter":"S-Enter"})})},_?80:0)}else fetch(`/api/sessions/${this.activeSessionId}/send-key`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:r.ctrlKey?"C-Enter":"S-Enter"})});return!1}return!0});{const r=s.querySelector(".xterm-helper-textarea");if(r&&MobileDetection.isTouchDevice()){let _=!1,h=0;r.addEventListener("compositionstart",()=>{_=!0}),r.addEventListener("compositionend",()=>{_=!1}),r.addEventListener("keydown",S=>{!S.isComposing&&S.keyCode!==229&&(h=Date.now())}),r.addEventListener("input",S=>{if(_||S.isComposing||S.inputType!=="insertText"||!S.data||Date.now()-h<50)return;const d=S.data,w=this._localEchoOverlay?.pendingText||"";Promise.resolve().then(()=>{if(this._lastTerminalData?.data===d&&performance.now()-this._lastTerminalData.time<100){r.value="";return}const v=this._localEchoOverlay?.pendingText||"";if(this._localEchoEnabled&&v.length>w.length&&v.endsWith(d)){r.value="";return}const y=r.value;!y||y.trim()===""&&d!==" "||(this.terminal._core.coreService.triggerDataEvent(d,!0),r.value="")})})}}this._webglAddon=null;const i=new URLSearchParams(location.search),o=(()=>{try{const r=localStorage.getItem("codeman-webgl-disabled");if(!r)return!1;const{at:_}=JSON.parse(r);return Date.now()-_>WEBGL_FALLBACK.STICKY_EXPIRY_MS?(localStorage.removeItem("codeman-webgl-disabled"),!1):!0}catch{return!1}})(),a=this.loadAppSettingsFromStorage(),n=this.getDefaultSettings(),l=a.webglRendererEnabled??n.webglRendererEnabled,{skip:m,clearSticky:g}=shouldSkipWebGL({deviceType:MobileDetection.getDeviceType(),noWebglParam:i.has("nowebgl"),forceParam:i.get("webgl")==="force",stickyDisabled:o,userPrefEnabled:l});if(g)try{localStorage.removeItem("codeman-webgl-disabled")}catch{}if(m&&o&&console.log("[CRASH-DIAG] WebGL sticky-disabled from prior stalls \u2014 DOM renderer in use. Re-enable: ?webgl=force"),!m)if(typeof WebglAddon<"u")this._initWebGL();else{const r=document.createElement("script");r.src="vendor/xterm-addon-webgl.min.js",r.onload=()=>this._initWebGL(),r.onerror=()=>console.warn("[CRASH-DIAG] Failed to load WebGL addon \u2014 using canvas renderer"),document.head.appendChild(r)}this._localEchoOverlay=new LocalEchoOverlay(this.terminal),MobileDetection.isTouchDevice()&&(this.terminal.onCursorMove(()=>this._syncMobileHelperTextareaToCursor()),this.terminal.onRender(()=>this._syncMobileHelperTextareaToCursor())),this._cjkInput=null,typeof CjkInput<"u"&&(this._cjkInput=CjkInput.init({send:r=>{this._handleCjkInput(r)}}));const c=this.terminal.focus.bind(this.terminal);this.terminal.focus=()=>{const r=document.getElementById("cjkInput");r?.classList.contains("cjk-input-visible")?r.focus():c()},MobileDetection.getDeviceType()==="mobile"&&document.body.classList.contains("safari-browser")?requestAnimationFrame(()=>{this.fitAddon.fit(),requestAnimationFrame(()=>this.fitAddon.fit())}):this.fitAddon.fit(),this.registerFilePathLinkProvider(),s.addEventListener("wheel",r=>{const _=this.terminal?.modes?.mouseTrackingMode;if(_&&_!=="none"||this.terminal?.buffer?.active?.type==="alternate")return;if(r.preventDefault(),r.stopPropagation(),this._shouldForwardWheelToApp(r)){this._logScrollRouting("forward-sgr"),this._forwardScrollToApp(r.clientX,r.clientY,this._wheelScrollLines(r));return}const h=this._wheelScrollLinesFloat(r);this._maybePageCliTranscript(r,h)||(this._logScrollRouting("local-scrollback"),this._noteTerminalUserScroll(h),this._smoothScrollBy(h))},{passive:!1,capture:!0});{const r=()=>this.terminal._core?._renderService?.dimensions?.css?.cell?.height||13;let _=0,h=0,S=0,d=0,w=null,v=!1;const y=x=>{const L=d?(x-d)/16.67:1;if(d=x,!v&&Math.abs(S)>.3){const u=Math.round(S/r());u!==0&&(this._shouldForwardWheelToApp({shiftKey:!1})?this._forwardScrollToApp(_,h,u):this._maybePageCliTranscript({shiftKey:!1},u)||(this.terminal.scrollLines(u),this._maybeLoadMoreHistoryOnScroll(u))),S*=.92,w=requestAnimationFrame(y)}else v?w=requestAnimationFrame(y):(w=null,S=0)};let b=0,T=!1,k=0;const A=8;s.addEventListener("touchstart",x=>{x.touches.length===1&&(_=x.touches[0].clientX,h=x.touches[0].clientY,k=h,S=0,b=0,v=!0,T=!1,d=0,w&&(cancelAnimationFrame(w),w=null))},{passive:!0}),s.addEventListener("touchmove",x=>{if(x.touches.length===1&&v){const L=x.touches[0].clientY;if(!T&&Math.abs(L-k)>=A&&(T=!0),!T)return;x.preventDefault();const u=h-L;b+=u,S=u*1.2,_=x.touches[0].clientX,h=L;const M=r(),R=Math.trunc(b/M);R!==0&&(this._shouldForwardWheelToApp({shiftKey:!1})?(this._logScrollRouting("forward-sgr"),this._forwardScrollToApp(_,h,R)):this._maybePageCliTranscript({shiftKey:!1},R)||(this._logScrollRouting("local-scrollback"),this._noteTerminalUserScroll(R),this.terminal.scrollLines(R),this._maybeLoadMoreHistoryOnScroll(R)),b-=R*M)}},{passive:!1}),s.addEventListener("touchend",x=>{if(v=!1,!w&&Math.abs(S)>.3&&(w=requestAnimationFrame(y)),!T&&this.terminal){const L=x.changedTouches&&x.changedTouches[0],u=this.terminal.modes?.mouseTrackingMode,M=!!u&&u!=="none";L&&this._suppressTrustedTapMouseEvents(),L&&M?this._dispatchSyntheticTerminalClick(L.clientX,L.clientY):L&&this._sessionUsesServerMouseStrip()&&this._sendSyntheticSgrTap(L.clientX,L.clientY),this._syncMobileHelperTextareaToCursor();const R=document.getElementById("cjkInput");R?.classList.contains("cjk-input-visible")?R.focus():this.terminal.focus()}},{passive:!0}),s.addEventListener("touchcancel",()=>{v=!1,S=0,b=0},{passive:!0})}s.addEventListener("click",r=>this._handleDesktopTerminalClick(r)),this.showWelcome(),this.initImageInput(),this._chunkedWriteGen=0,this._bufferLoadSeq=0,this._bufferLoadOwner=null,this._lastUserScrollUpAt=null,this._resizeTimeout=null,this._lastResizeDims=null;const E=40,C=10,I=()=>{this._resizeTimeout&&clearTimeout(this._resizeTimeout),this._resizeTimeout=setTimeout(()=>{this._resizeTimeout=null,this.fitAddon&&this.fitAddon.fit(),this.flickerFilterBuffer&&(this.flickerFilterTimeout&&(clearTimeout(this.flickerFilterTimeout),this.flickerFilterTimeout=null),this.flushFlickerBuffer());const r=typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible;if(this.activeSessionId&&!r){const _=this.fitAddon.proposeDimensions(),h=_?Math.max(_.cols,E):E,S=_?Math.max(_.rows,C):C;if(!this._lastResizeDims||h!==this._lastResizeDims.cols||S!==this._lastResizeDims.rows){const d=this.activeSessionId?this.sessions.get(this.activeSessionId):null;d&&d.mode!=="shell"&&this.terminal&&this.isTerminalAtBottom()&&this.terminal.write("\x1B[3J\x1B[H\x1B[2J"),this._lastResizeDims={cols:h,rows:S};const w=typeof MobileDetection<"u"&&MobileDetection.getDeviceType?MobileDetection.getDeviceType():"desktop";let v=!1;if(this._wsReady&&this._wsSessionId===this.activeSessionId)try{this._ws.send(JSON.stringify({t:"z",c:h,r:S,v:w})),v=!0}catch{}v||fetch(`/api/sessions/${this.activeSessionId}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({cols:h,rows:S,viewportType:w})}).catch(()=>{})}}this.updateConnectionLines(),this._localEchoOverlay?.hasPending&&this._localEchoOverlay.rerender()},300)};window.addEventListener("resize",I),this.terminalResizeObserver&&this.terminalResizeObserver.disconnect(),this.terminalResizeObserver=new ResizeObserver(I),this.terminalResizeObserver.observe(s),this._pendingInput="",this._inputFlushTimeout=null,this._lastKeystrokeTime=0;const f=()=>{if(this._inputFlushTimeout=null,this._pendingInput&&this.activeSessionId){const r=this._pendingInput,_=this.activeSessionId;this._pendingInput="",this._sendInputAsync(_,r)}};this.terminal.onData(r=>{if(!/^\x1b\[<\d+;\d+;\d+[Mm]$/.test(r)&&(window.cjkActive||document.activeElement?.id==="cjkInput")){const h=document.getElementById("cjkInput");h?.classList.contains("cjk-input-visible")&&document.activeElement===this.terminal.textarea&&!window.CodemanTerminalInput?.shouldSuppressTerminalQueryResponse(r)&&(_crashDiag.log("CJK regain-focus (onData swallowed input)"),h.focus());return}if(this.activeSessionId){if(window.CodemanTerminalInput?.shouldSuppressTerminalQueryResponse(r))return;this._lastTerminalData={data:r,time:performance.now()};const h=this._localEchoEnabled&&this._echoPassthroughSessions?.has(this.activeSessionId);if(h&&(r==="\r"||r==="")&&this._echoPassthroughSessions.delete(this.activeSessionId),this._localEchoEnabled&&!h){if(r==="\x7F"){const d=this._localEchoOverlay?.removeChar();if(d==="flushed"){const{count:w,text:v}=this._localEchoOverlay.getFlushed();this._flushedOffsets?.has(this.activeSessionId)&&(w===0?(this._flushedOffsets.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId)):(this._flushedOffsets.set(this.activeSessionId,w),this._flushedTexts?.set(this.activeSessionId,v))),this._pendingInput+=r,f()}else d===!1&&(this._pendingInput+=r,f());return}if(/^[\r\n]+$/.test(r)){const d=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._localEchoOverlay?.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),d&&(this._pendingInput+=d,f()),setTimeout(()=>{this._pendingInput+="\r",f()},80);return}if(r.length>1&&r.charCodeAt(0)>=32){this._localEchoOverlay?.appendText(r);return}if(r.charCodeAt(0)<32){if(r.length>1&&r.charCodeAt(0)===27){if(r.startsWith(window.CodemanTerminalInput.BRACKETED_PASTE_START)){const w=!!this._localEchoOverlay?.pendingText;this._flushLocalEchoPending(),w?(f(),setTimeout(()=>{this._pendingInput+=r,f()},80)):(this._pendingInput+=r,f());return}if(window.CodemanTerminalInput.isComposerNavKey(r)){this._flushLocalEchoPending(),this._echoPassthroughSessions||(this._echoPassthroughSessions=new Set),this._echoPassthroughSessions.add(this.activeSessionId),this._pendingInput+=r,f();return}this._pendingInput+=r,f();return}if(this._restoringFlushedState){this._pendingInput+=r,f();return}if(r===" "){const w=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),w&&(this._pendingInput+=w),this._pendingInput+=r,this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null);let v="";try{const b=this._localEchoOverlay?.findPrompt?.();if(b){const T=this.terminal.buffer.active,k=T.getLine(T.viewportY+b.row);k&&(v=k.translateToString(!0).slice(b.col+2).trimEnd())}}catch{}this._tabCompletionBaseText=v,f(),this._tabCompletionSessionId=this.activeSessionId,this._tabCompletionRetries=0,this._tabCompletionFallback&&clearTimeout(this._tabCompletionFallback);const y=this;this._tabCompletionFallback=setTimeout(()=>{if(y._tabCompletionFallback=null,!y._tabCompletionSessionId||y._tabCompletionSessionId!==y.activeSessionId)return;const b=y._localEchoOverlay;!b||b.pendingText||y.terminal.write("",()=>{if(!y._tabCompletionSessionId)return;b.resetBufferDetection();const T=b.detectBufferText();T&&T!==y._tabCompletionBaseText&&(y._tabCompletionSessionId=null,y._tabCompletionRetries=0,y._tabCompletionBaseText=null,b.rerender())})},300);return}const d=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._localEchoOverlay?.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),d&&(this._pendingInput+=d),this._pendingInput+=r,this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),f();return}if(r.length===1&&r.charCodeAt(0)>=32){this._localEchoOverlay?.addChar(r);return}}if(this._pendingInput+=r,r.charCodeAt(0)<32||r.length>1){this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),f();return}const S=performance.now();S-this._lastKeystrokeTime>50?(this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),this._lastKeystrokeTime=S,f()):(this._lastKeystrokeTime=S,this._inputFlushTimeout||(this._inputFlushTimeout=setTimeout(f,0)))}})},registerFilePathLinkProvider(){const e=this;let t=-1;this.terminal.registerLinkProvider({provideLinks(s,i){s!==t&&(t=s,console.debug("[LinkProvider] Checking line:",s));const o=e.terminal.buffer.active;if(!o.getLine(s-1)){i(void 0);return}const n=e.terminal.cols,l=b=>o.getLine(b-1),m=b=>{if(b<=1)return!1;if(l(b)?.isWrapped)return!0;const T=l(b-1);return!!T&&T.translateToString(!0).length>=n},g=12;let c=s;for(;c>1&&s-c<g&&m(c);)c--;let p=s;for(;p<o.length&&p-c<g&&m(p+1);)p++;const E=[];for(let b=c;b<=p;b++){const T=l(b);if(!T)break;E.push(T.translateToString(b===p))}const C=E.join(""),I=b=>{let T=b;for(let k=0;k<E.length-1;k++){if(T<E[k].length)return{x:T+1,y:c+k};T-=E[k].length}return{x:T+1,y:c+E.length-1}};if(!C||!C.includes("/")){i(void 0);return}const f=[],r=/https?:\/\/(?:[^\s"'<>|;&)\]\x00-\x1f]|&(?!&))+/g,_=(b,T)=>{const k=b.replace(/[.,;:!?)&]+$/,""),A=C.indexOf(k,T);if(A===-1)return;const x=I(A),L=I(A+k.length);f.some(u=>u.range.start.x===x.x&&u.range.start.y===x.y)||f.push({text:k,range:{start:x,end:L},decorations:{pointerCursor:!0,underline:!0},activate(u,M){window.open(M,"_blank","noopener,noreferrer")},hover(){e._linkHovered=!0},leave(){e._linkHovered=!1}})},h=/\b(tail|cat|head|less|grep|watch|vim|nano)\s+(?:[^\s\/]+\s+){0,4}(\/[^\s"'<>|;&\n\x00-\x1f]+)/g,S=/(\/(?:home|tmp|var|etc|opt)[^\s"'<>|;&\n\x00-\x1f]*\.(?:log|txt|json|md|yaml|yml|csv|xml|sh|py|ts|js|png|jpe?g|gif|webp|bmp|svg|pdf))\b/g,d=/Bash\([^)]*?(\/(?:home|tmp|var|etc|opt)[^\s"'<>|;&\)\n\x00-\x1f]+)/g,w=new Set(["png","jpg","jpeg","gif","webp","bmp","svg","pdf"]),v=(b,T)=>{const k=C.indexOf(b,T);if(k===-1)return;const A=I(k),x=I(k+b.length);f.some(L=>L.range.start.x===A.x&&L.range.start.y===A.y)||f.push({text:b,range:{start:A,end:x},decorations:{pointerCursor:!0,underline:!0},activate(L,u){const M=(u.split(".").pop()||"").toLowerCase();if(w.has(M)){e.openFilePreview(u,e.activeSessionId);return}e.openLogViewerWindow(u,e.activeSessionId)},hover(){e._linkHovered=!0},leave(){e._linkHovered=!1}})};let y;for(r.lastIndex=0;(y=r.exec(C))!==null;)_(y[0],y.index);for(h.lastIndex=0;(y=h.exec(C))!==null;)v(y[2],y.index);for(S.lastIndex=0;(y=S.exec(C))!==null;)v(y[1],y.index);for(d.lastIndex=0;(y=d.exec(C))!==null;)v(y[1],y.index);f.length>0&&console.debug("[LinkProvider] Found links:",f.map(b=>b.text)),i(f.length>0?f:void 0)}}),console.log("[LinkProvider] File path link provider registered")},showWelcome(){if(this.shouldUseMobileOverview?.()){const t=document.getElementById("welcomeOverlay");t&&t.classList.remove("visible"),this.showMobileOverview(),this._updateCjkInputState?.();return}this.hideMobileOverview?.();const e=document.getElementById("welcomeOverlay");e&&(e.classList.add("visible"),this.loadTunnelStatus(),this.applyWelcomeCliVisibility(),this.loadHistorySessions(),this.initSearchPanel()),this._updateCjkInputState?.()},hideWelcome(){this.hideMobileOverview?.();const e=document.getElementById("welcomeOverlay");e&&e.classList.remove("visible");const t=document.getElementById("welcomeQr");t&&(clearTimeout(this._welcomeQrShrinkTimer),t.classList.remove("expanded")),this._updateCjkInputState?.()},async _fetchHistorySessions(){const s=(await(await fetch("/api/history/sessions")).json()).data?.sessions||[];if(s.length===0)return[];const i=new Map;for(const a of s){const n=a.projectKey||a.workingDir;i.has(n)||i.set(n,[]),i.get(n).push(a)}const o=[];for(const[,a]of i)o.push(...a.slice(0,3));return o.sort((a,n)=>new Date(n.lastModified)-new Date(a.lastModified)),o},async _fetchUnifiedSessions(e=60){const t=await fetch("/api/sessions/unified?limit="+e),s=await t.json().catch(()=>null);if(!t.ok||!s||s.success===!1||!s.data)throw new Error(s?.error||`unified sessions request failed (HTTP ${t.status})`);return s.data.sessions||[]},_resolveCaseLabel(e,t){if(!e)return"";let s=null;for(const i of t||[])if(!(!i||!i.path)){if(e===i.path)return`#${i.name}`;if(e.startsWith(i.path+"/")){const o=i.path.length;(!s||o>s.len)&&(s={name:i.name,suffix:e.slice(o),len:o})}}return s?`#${s.name}${s.suffix}`:e.split("/").pop()||e},_shortenHomePath(e){return(e||"").replace(/^\/home\/[^/]+\//,"~/").replace(/^\/Users\/[^/]+\//,"~/")},_buildHistoryItem(e,t,s){const i=s?.showViewAll!==!1,o=typeof e.sizeBytes=="number",a=o?e.sizeBytes<1024?`${e.sizeBytes}B`:e.sizeBytes<1048576?`${(e.sizeBytes/1024).toFixed(0)}K`:`${(e.sizeBytes/1048576).toFixed(1)}M`:"",n=typeof e.lastActivityAt=="number"?e.lastActivityAt:e.lastModified?Date.parse(e.lastModified):NaN;let l="";if(!Number.isNaN(n)){const u=new Date(n);l=u.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+u.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1})}const m=this._shortenHomePath(e.workingDir),g=this._resolveCaseLabel(e.workingDir,t),c=Array.isArray(e.sources)&&e.sources.includes("live"),p=e.pinned===!0,E=document.createElement("div");E.className="history-item"+(p?" is-pinned":""),E.title=e.workingDir||"";const C=document.createElement("div");C.className="history-item-main",C.addEventListener("click",s?.onActivate||(()=>{c&&this.sessions.has(e.sessionId)?this.selectSession(e.sessionId):this.resumeHistorySession(e.claudeSessionId||e.sessionId,e.workingDir||"",e.name)}));const I=document.createElement("div");I.className="history-item-text";const f=document.createElement("span");if(f.className="history-item-title",p){const u=document.createElement("span");u.className="history-item-pin",u.textContent="\u{1F4CC}",u.setAttribute("aria-label","Pinned"),u.title="Pinned",f.appendChild(u)}f.appendChild(document.createTextNode(e.name||e.firstPrompt||m));const r=document.createElement("div");if(r.className="history-item-badges",e.mode){const u=document.createElement("span");u.className="history-item-badge history-item-badge-mode",u.textContent=e.mode,r.appendChild(u)}if(c){const u=document.createElement("span");u.className="history-item-badge history-item-badge-live",u.textContent="LIVE",r.appendChild(u)}const _=document.createElement("span");_.className="history-item-subtitle",g.startsWith("#")&&_.classList.add("is-case"),_.textContent=g,I.append(f),r.childElementCount>0&&I.append(r),I.append(_);const h=document.createElement("span");h.className="history-item-meta",h.textContent=l;const S=document.createElement("button");S.className="history-item-expand",S.type="button",S.setAttribute("aria-haspopup","menu"),S.setAttribute("aria-label","Session actions"),S.setAttribute("aria-expanded","false"),S.textContent="\u22EF",C.append(I,h,S);const d=document.createElement("div");d.className="history-item-detail",d.hidden=!0;const w=document.createElement("div");w.className="history-detail-row";const v=document.createElement("span");v.className="history-detail-label",v.textContent="Prompt";const y=document.createElement("span");y.className="history-detail-value history-detail-prompt",y.textContent=e.firstPrompt||"(no prompt captured)",w.append(v,y);let b=null;if(e.lastPrompt&&e.lastPrompt!==e.firstPrompt){b=document.createElement("div"),b.className="history-detail-row";const u=document.createElement("span");u.className="history-detail-label",u.textContent="Last prompt";const M=document.createElement("span");M.className="history-detail-value history-detail-prompt",M.textContent=e.lastPrompt,b.append(u,M)}const T=document.createElement("div");T.className="history-detail-row";const k=document.createElement("span");k.className="history-detail-label",k.textContent="Path";const A=document.createElement("span");A.className="history-detail-value history-detail-path",A.textContent=m,T.append(k,A);const x=document.createElement("div");x.className="history-detail-row history-detail-meta";const L=[];if(l&&L.push(l),o&&L.push(a),L.push(e.sessionId.slice(0,8)),x.textContent=L.join(" \xB7 "),d.append(w),b&&d.append(b),d.append(T,x),i&&e.projectKey){const u=document.createElement("div");u.className="history-detail-row history-detail-actions";const M=document.createElement("button");M.type="button",M.className="history-view-all-btn",M.textContent="View all in this folder",M.addEventListener("click",R=>{R.stopPropagation(),this.openFolderHistoryModal(e.projectKey,e.workingDir,t)}),u.appendChild(M),d.appendChild(u)}return S.addEventListener("click",u=>{u.stopPropagation(),u.preventDefault(),this._openSessionRowMenu(u.currentTarget,e,t,E,d)}),E.append(C,d),E},_openSessionRowMenu(e,t,s,i,o){if(this._openRowMenuClose)try{this._openRowMenuClose()}catch{}const a=Array.isArray(t.sources)&&t.sources.includes("live")&&this.sessions.has(t.sessionId),n=document.createElement("div");n.className="session-row-menu",n.setAttribute("role","menu");let l=null,m=null,g=null;const c=()=>{document.removeEventListener("mousedown",l,!0),document.removeEventListener("keydown",m,!0),window.removeEventListener("scroll",g,!0),window.removeEventListener("resize",g,!0);try{n.remove()}catch{}this._openRowMenuEl===n&&(this._openRowMenuEl=null,this._openRowMenuClose=null)},p=(h,S,d)=>{const w=document.createElement("button");w.type="button",w.className="session-row-menu-item",w.setAttribute("role","menuitem");const v=document.createElement("span");if(v.className="session-row-menu-label",v.textContent=h,w.appendChild(v),d&&d.sublabel){const y=document.createElement("span");y.className="session-row-menu-sublabel",y.textContent=d.sublabel,w.appendChild(y)}w.addEventListener("click",async y=>{y.stopPropagation(),y.preventDefault(),await S()}),n.appendChild(w)};p(a?"Switch to session":"Resume session",()=>{a?this.selectSession(t.sessionId):this.resumeHistorySession(t.claudeSessionId||t.sessionId,t.workingDir||"",t.name),this.closeSessionManager?.(),c()});const E=t.pinned===!0;p(E?"Unpin session":"Pin to top",async()=>{await this._setSessionPinned(t.sessionId,!E)?(t.pinned=!E,this.showToast(E?"Unpinned":"Pinned to top","success")):this.showToast("Pin failed","error"),c()}),a&&p("Open folder",()=>{this.selectSession(t.sessionId),this.loadFileBrowser?.(t.sessionId),this.closeSessionManager?.(),c()}),t.workingDir&&p("Copy path",async()=>{const h=await this._copyText(t.workingDir);this.showToast(h?"Path copied":"Copy failed",h?"success":"error"),c()}),p("Show details",()=>{const h=i.classList.toggle("expanded");o.hidden=!h,e.setAttribute("aria-expanded",h?"true":"false"),c()}),document.body.appendChild(n);const C=e.getBoundingClientRect(),I=n.getBoundingClientRect(),f=4;let r=C.bottom+f;r+I.height>window.innerHeight&&C.top-f-I.height>=0&&(r=C.top-f-I.height);let _=C.right-I.width;_<f&&(_=f),_+I.width>window.innerWidth-f&&(_=Math.max(f,window.innerWidth-f-I.width)),n.style.top=`${Math.max(f,r)}px`,n.style.left=`${_}px`,l=h=>{n.contains(h.target)||e.contains(h.target)||c()},m=h=>{h.key==="Escape"&&(h.stopPropagation(),c())},g=()=>c(),document.addEventListener("mousedown",l,!0),document.addEventListener("keydown",m,!0),window.addEventListener("scroll",g,!0),window.addEventListener("resize",g,!0),this._openRowMenuEl=n,this._openRowMenuClose=c},async _setSessionPinned(e,t){try{const s=await fetch(`/api/sessions/${encodeURIComponent(e)}/pin`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"same-origin",body:JSON.stringify({pinned:t})});return s.ok?(await s.json())?.success===!0:!1}catch(s){return console.error("[_setSessionPinned]",s),!1}},_HISTORY_INITIAL_COUNT:4,async loadHistorySessions(){const e=document.getElementById("historySessions"),t=document.getElementById("historyList");if(!(!e||!t))try{const s=Array.isArray(this.cases)&&this.cases.length>0?Promise.resolve(this.cases):fetch("/api/cases").then(n=>n.ok?n.json():null).then(n=>n?.data||[]).catch(()=>[]),[i,o]=await Promise.all([this._fetchUnifiedSessions(60),s]);if(i.length===0){e.style.display="none";return}t.replaceChildren();const a=this._HISTORY_INITIAL_COUNT;for(let n=0;n<Math.min(a,i.length);n++)t.appendChild(this._buildHistoryItem(i[n],o));if(i.length>a){const n=document.createElement("button");n.className="history-show-more",n.textContent=`Show ${i.length-a} more`,n.addEventListener("click",()=>{for(let l=a;l<i.length;l++)t.insertBefore(this._buildHistoryItem(i[l],o),n);n.remove()}),t.appendChild(n)}e.style.display=""}catch(s){console.error("[loadHistorySessions]",s),e.style.display="none"}},_FOLDER_HISTORY_PAGE_SIZE:20,openFolderHistoryModal(e,t,s){this._closeFolderHistoryModal();const i=document.createElement("div");i.className="modal active folder-history-modal",i.id="folderHistoryModal";const o=document.createElement("div");o.className="modal-backdrop",o.addEventListener("click",()=>this._closeFolderHistoryModal());const a=document.createElement("div");a.className="modal-content modal-lg";const n=document.createElement("div");n.className="modal-header";const l=document.createElement("h3");l.textContent="Folder History";const m=document.createElement("div");m.className="folder-history-subtitle",m.textContent=this._shortenHomePath(t);const g=document.createElement("button");g.className="modal-close",g.setAttribute("aria-label","Close"),g.innerHTML="&times;",g.addEventListener("click",()=>this._closeFolderHistoryModal()),n.append(l,g);const c=document.createElement("div");c.className="modal-body";const p=document.createElement("div");p.className="folder-history-list",p.setAttribute("data-loading","true"),p.textContent="Loading...",c.append(m,p),a.append(n,c),i.append(o,a),document.body.appendChild(i),this._folderHistoryState={projectKey:e,workingDir:t,cases:s||[],offset:0,total:null,list:p},this._folderHistoryEscHandler=E=>{E.key==="Escape"&&this._closeFolderHistoryModal()},document.addEventListener("keydown",this._folderHistoryEscHandler),this._loadFolderHistoryPage()},async _loadFolderHistoryPage(){const e=this._folderHistoryState;if(!e)return;const{projectKey:t,cases:s,list:i}=e,o=this._FOLDER_HISTORY_PAGE_SIZE,a=e.offset,n=i.querySelector(".folder-history-more");n&&n.remove(),a===0&&(i.replaceChildren(),i.removeAttribute("data-loading"));try{const l=`/api/history/sessions?projectKey=${encodeURIComponent(t)}&offset=${a}&limit=${o}`,g=await(await fetch(l)).json(),c=g.data?.sessions||[];if(e.total=typeof g.data?.total=="number"?g.data.total:c.length+a,a===0&&c.length===0){const p=document.createElement("div");p.className="folder-history-empty",p.textContent="No conversations found in this folder.",i.appendChild(p);return}for(const p of c)i.appendChild(this._buildHistoryItem(p,s,{showViewAll:!1}));if(e.offset=a+c.length,e.offset<e.total){const p=e.total-e.offset,E=document.createElement("button");E.className="history-show-more folder-history-more",E.textContent=`Show ${Math.min(o,p)} more (${p} remaining)`,E.addEventListener("click",()=>this._loadFolderHistoryPage()),i.appendChild(E)}}catch(l){console.error("[loadFolderHistoryPage]",l);const m=document.createElement("div");m.className="folder-history-empty",m.textContent="Failed to load folder history.",i.appendChild(m)}},_closeFolderHistoryModal(){const e=document.getElementById("folderHistoryModal");e&&e.remove(),this._folderHistoryEscHandler&&(document.removeEventListener("keydown",this._folderHistoryEscHandler),this._folderHistoryEscHandler=null),this._folderHistoryState=null},_resolveResumeName(e,t){if(typeof e=="string"&&e.trim())return e;const s=(t||"").split("/").pop()||"session";let i=1;for(const[,o]of this.sessions){const a=o.name&&o.name.match(/^w(\d+)-/);if(a){const n=parseInt(a[1]);n>=i&&(i=n+1)}}return`w${i}-${s}`},async resumeHistorySession(e,t,s){document.getElementById("runModeMenu")?.classList.remove("active"),this._closeFolderHistoryModal();try{this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Resuming conversation ${e.slice(0,8)}...\x1B[0m`);const i=this._resolveResumeName(s,t),a=(this.cases||[]).find(E=>E.path===t)?.name||t.split("/").pop()||"",n=this.loadAppSettingsFromStorage(),l=this.buildEnvOverrides(this.getCaseSettings(a),n),m=this.getEffortSetting(n),c=await(await fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:t,name:i,resumeSessionId:e,...Object.keys(l).length>0?{envOverrides:l}:{},...m?{effort:m}:{}})})).json();if(!c.success)throw new Error(c.error);const p=c.data.session.id;await fetch(`/api/sessions/${p}/interactive`,{method:"POST"}),this.terminal.writeln(`\x1B[90m Session ${i} ready\x1B[0m`),await this.selectSession(p),this.terminal.focus()}catch(i){this.terminal.writeln(`\x1B[1;31m Error: ${i.message}\x1B[0m`)}},isTerminalAtBottom(){if(!this.terminal)return!0;const e=this.terminal.buffer.active;return e.viewportY>=e.baseY-2},_noteTerminalUserScroll(e){e<0?this._lastUserScrollUpAt=performance.now():this.isTerminalAtBottom()&&(this._lastUserScrollUpAt=null)},_maybeLoadMoreHistoryOnScroll(e){e>=0||this.terminal?.buffer?.active?.viewportY===0&&this._maybeRefetchFullHistory?.()},_estimateReplayRows(e,t){if(typeof e!="string"||!e)return 0;const s=t>0?t:80,i=e.replace(window.CodemanTerminalInput.REPLAY_ESCAPE_RE,"");let o=0;for(const a of i.split(`
2
- `)){const n=a.endsWith("\r")?a.length-1:a.length;o+=n>s?Math.ceil(n/s):1}return o},_replayWouldShrinkBuffer(e){const t=this.terminal,s=t?.buffer?.active?.length||0;if(!s)return!1;const i=t?.rows||24;return this._estimateReplayRows(e,t?.cols)+i<s},_smoothScrollBy(e){if(!e||(this._smoothScrollPending=(this._smoothScrollPending||0)+e,this._smoothScrollSession=this.activeSessionId,this._smoothScrollFrame))return;const t=()=>{this._smoothScrollFrame=null;const s=this._smoothScrollPending||0;if(!s)return;if(this.activeSessionId!==this._smoothScrollSession){this._smoothScrollPending=0;return}if(Math.abs(s)<1)return;const i=s*.22,o=s>0?Math.max(1,Math.floor(i)):Math.min(-1,Math.ceil(i));this._smoothScrollPending=s-o,this.terminal.scrollLines(o),this._maybeLoadMoreHistoryOnScroll(o),Math.abs(this._smoothScrollPending)>=1&&(this._smoothScrollFrame=requestAnimationFrame(t))};this._smoothScrollFrame=requestAnimationFrame(t)},_forwardScrollToApp(e,t,s){this._terminalViewportAtBottom()||this.terminal.scrollToBottom(),this._sendSyntheticSgrWheel(e,t,s)},_hasRecentUserScrollUp(){return typeof this._lastUserScrollUpAt!="number"?!1:performance.now()-this._lastUserScrollUpAt<window.CodemanTerminalInput.USER_SCROLL_STICKY_SUPPRESS_MS},batchTerminalWrite(e){if(this._isLoadingBuffer){this._loadBufferQueue&&this._loadBufferQueue.push(e);return}if(this.writeFrameScheduled||(this._wasAtBottomBeforeWrite=this.isTerminalAtBottom()),(this.activeSessionId?this.sessions.get(this.activeSessionId):null)?.flickerFilterEnabled??!1){if(e.includes("\x1B[2J")||e.includes("\x1B[H\x1B[J")||e.includes("\x1B[H")&&e.includes("\x1B[?25l")){this.flickerFilterActive=!0,this.flickerFilterBuffer+=e,this.flickerFilterTimeout&&clearTimeout(this.flickerFilterTimeout),this.flickerFilterTimeout=setTimeout(()=>{this.flickerFilterTimeout=null,this.flushFlickerBuffer()},SYNC_WAIT_TIMEOUT_MS);return}if(this.flickerFilterActive){this.flickerFilterBuffer+=e;return}}this.pendingWrites.push(e),this._scheduleTerminalWriteFlush()},_scheduleTerminalWriteFlush(){this.writeFrameScheduled||this.pendingWrites.length===0||(this.writeFrameScheduled=!0,this._safeYield(()=>{this.writeFrameScheduled=!1,this.flushPendingWrites()}))},flushFlickerBuffer(){this.flickerFilterBuffer&&(this.pendingWrites.push(this.flickerFilterBuffer),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._scheduleTerminalWriteFlush())},_flushLocalEchoPending(){const e=this._localEchoOverlay?.pendingText||"";this._localEchoOverlay?.clear(),this._localEchoOverlay?.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),e&&(this._pendingInput+=e)},_updateLocalEchoState(){const e=this.loadAppSettingsFromStorage(),t=this.activeSessionId?this.sessions.get(this.activeSessionId):null,i=!!((e.localEchoEnabled??MobileDetection.isTouchDevice())&&t);this._localEchoEnabled&&!i&&this._localEchoOverlay?.clear(),this._localEchoEnabled=i,this._localEchoOverlay&&t&&(t.mode==="opencode"?this._localEchoOverlay.setPrompt({type:"custom",offset:3,find:o=>{try{const a=o.buffer.active,n=a.cursorY,l=a.getLine(a.viewportY+n);if(!l)return null;const g=l.translateToString(!0).indexOf("\u2503");return g>=0?{row:n,col:g}:null}catch{return null}}}):t.mode==="shell"||t.mode==="codex"?(this._localEchoOverlay.clear(),this._localEchoEnabled=!1):this._localEchoOverlay.setPrompt({type:"custom",offset:0,find:o=>{try{const a=o.buffer.active;for(let n=o.rows-1;n>=0;n--){const l=a.getLine(a.viewportY+n);if(!l)continue;const g=l.translateToString(!0).lastIndexOf("\u276F");if(g>=0)return{row:n,col:g+2}}return{row:Math.max(0,Math.min(o.rows-1,a.cursorY)),col:Math.max(0,Math.min(o.cols-1,a.cursorX))}}catch{return null}}}))},_handleCjkInput(e){if(!this.activeSessionId){_crashDiag.log(`CJK send DROP no-session len=${e.length}`);return}_crashDiag.log(`CJK send\u2192${this.activeSessionId.slice(0,8)} len=${e.length}`),this._sendInputAsync(this.activeSessionId,e)},flushPendingWrites(){if(this.pendingWrites.length===0||!this.terminal)return;const e=performance.now(),t=this.pendingWrites.join("");this.pendingWrites=[];const s=t.length;s>16384&&_crashDiag.log(`FLUSH: ${(s/1024).toFixed(0)}KB`);const o=(this.activeSessionId&&this.sessions?this.sessions.get(this.activeSessionId):null)?.mode==="codex"?32768:65536;let a=!1;const n=this._hasRecentUserScrollUp()&&this.terminal.buffer?.active?this.terminal.buffer.active.viewportY:null;s<=o?this.terminal.write(t):(this.terminal.write(t.slice(0,o)),this.pendingWrites.push(t.slice(o)),a=!0,this._scheduleTerminalWriteFlush()),n!==null&&this.terminal.buffer?.active?.viewportY!==n&&typeof this.terminal.scrollToLine=="function"&&this.terminal.scrollToLine(n);const l=a?o:s,m=performance.now()-e;if((m>100||a)&&console.warn(`[CRASH-DIAG] flushPendingWrites: ${m.toFixed(0)}ms, ${(l/1024).toFixed(0)}KB written${a?", rest deferred":""} (total ${(s/1024).toFixed(0)}KB)`),this._wasAtBottomBeforeWrite&&!this._hasRecentUserScrollUp()&&this.terminal.scrollToBottom(),this._localEchoOverlay?.hasPending&&this._localEchoOverlay.rerender(),this._tabCompletionSessionId&&this._tabCompletionSessionId===this.activeSessionId&&this._localEchoOverlay&&!this._localEchoOverlay.pendingText){const g=this._localEchoOverlay,c=this;this.terminal.write("",()=>{if(!c._tabCompletionSessionId)return;g.resetBufferDetection();const p=g.detectBufferText();p?p===c._tabCompletionBaseText?(g.undoDetection(),c._tabCompletionRetries=(c._tabCompletionRetries||0)+1,c._tabCompletionRetries>60&&(c._tabCompletionSessionId=null,c._tabCompletionRetries=0)):(c._tabCompletionSessionId=null,c._tabCompletionRetries=0,c._tabCompletionBaseText=null,c._tabCompletionFallback&&(clearTimeout(c._tabCompletionFallback),c._tabCompletionFallback=null),g.rerender()):(c._tabCompletionRetries=(c._tabCompletionRetries||0)+1,c._tabCompletionRetries>60&&(c._tabCompletionSessionId=null,c._tabCompletionRetries=0))})}},_safeYield(e){let t=!1;const s=()=>{t||(t=!0,e())};requestAnimationFrame(s),setTimeout(s,50),this._workerYield(s)},_workerYield(e){try{if(this._yieldWorker===void 0){const t="onmessage=()=>setTimeout(()=>postMessage(0),0);",s=new Blob([t],{type:"application/javascript"}),i=URL.createObjectURL(s);this._yieldWorker=new Worker(i),URL.revokeObjectURL(i),this._yieldQueue=[],this._yieldWorker.onmessage=()=>{const o=this._yieldQueue.shift();o&&o()}}if(!this._yieldWorker)return;this._yieldQueue.push(e),this._yieldWorker.postMessage(0)}catch{this._yieldWorker=null}},scrollToLastNonEmptyLine(){if(!this.terminal?.buffer?.active){this.terminal?.scrollToBottom?.();return}const e=this.terminal.buffer.active,t=e.baseY+e.length;let s=-1;for(let i=t-1;i>=0;i--)if(e.getLine(i)?.translateToString(!0).trim()){s=i;break}if(s>=0&&typeof this.terminal.scrollToLine=="function"){let i=Math.max(0,s-this.terminal.rows+2);const o=Math.max(0,s);for(;i<o&&!e.getLine(i)?.translateToString(!0).trim();)i++;this.terminal.scrollToLine(i)}else this.terminal.scrollToBottom()},chunkedTerminalWrite(e,t=TERMINAL_CHUNK_SIZE,s){const i=++this._chunkedWriteGen,o=this._beginBufferLoad(s);return new Promise(a=>{if(!e||e.length===0){this._finishBufferLoad(o),a();return}const n=e.replace(DEC_SYNC_STRIP_RE,""),l=()=>{this._chunkedWriteGen===i&&this._finishBufferLoad(o),a()};if(n.length<=t){this.terminal.write(n,l);return}let m=0;const g=performance.now();let c=0;const p=()=>{if(this._chunkedWriteGen!==i){a();return}if(m>=n.length){const f=performance.now()-g;console.log(`[CRASH-DIAG] chunkedTerminalWrite complete: ${n.length} bytes in ${c} chunks, ${f.toFixed(0)}ms total`),this._safeYield(l);return}const E=performance.now(),C=n.slice(m,m+t);this.terminal.write(C);const I=performance.now()-E;c++,I>50&&console.warn(`[CRASH-DIAG] chunk #${c} write took ${I.toFixed(0)}ms (${C.length} bytes at offset ${m})`),m+=t,this._safeYield(p)};this._safeYield(p)})},_beginBufferLoad(e){this._bufferLoadSeq===void 0&&(this._bufferLoadSeq=0);const t=e===void 0?`buffer-${++this._bufferLoadSeq}`:e;return this._bufferLoadOwner=t,this._isLoadingBuffer=!0,this._loadBufferQueue=[],t},_finishBufferLoad(e,t){if(e!==void 0&&this._bufferLoadOwner!==e)return!1;const s=this._loadBufferQueue;if(this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,t?.flushQueued&&s&&s.length)for(const i of s)this.batchTerminalWrite(i);return!0},clearTerminal(){this.terminal.clear()},insertTerminalText(e){!this.activeSessionId||!e||(this._localEchoEnabled&&this._localEchoOverlay&&!this._echoPassthroughSessions?.has(this.activeSessionId)?this._localEchoOverlay.appendText(e):this.sendInput(e).catch(()=>{}),this.terminal?.focus())},clearTerminalInput(){if(this.activeSessionId){if(typeof CjkInput<"u"&&CjkInput.clear(),this._inputFlushTimeout&&(clearTimeout(this._inputFlushTimeout),this._inputFlushTimeout=null),this._pendingInput="",this._localEchoEnabled&&this._localEchoOverlay){const e=this._localEchoOverlay.getFlushed?.()||{count:0,text:""};this._localEchoOverlay.clear(),this._localEchoOverlay.suppressBufferDetection(),this._flushedOffsets?.delete(this.activeSessionId),this._flushedTexts?.delete(this.activeSessionId),e.count>0&&this.sendInput("\x7F".repeat(e.count)).catch(()=>{})}else this.sendInput("").catch(()=>{});this.showToast?.("Input cleared","success"),this.terminal?.focus()}},async restoreTerminalSize(){if(!this.activeSessionId){this.showToast("No active session","warning");return}const e=this.getTerminalDimensions();if(!e){this.showToast("Could not determine terminal size","error");return}try{await this.sendResize(this.activeSessionId,{force:!0}),this.showToast(`Terminal restored to ${e.cols}x${e.rows}`,"success")}catch(t){console.error("Failed to restore terminal size:",t),this.showToast("Failed to restore terminal size","error")}},sendPendingCtrlL(e){},shouldCopyTerminalSelectionFromShortcut(e){if(!e||e.type!=="keydown"||!e.ctrlKey&&!e.metaKey&&!e.altKey)return!1;const s=typeof this.getShortcutRegistry=="function"&&typeof this.matchesShortcutEvent=="function"?this.getShortcutRegistry().find(i=>i.id==="copy-selection"):null;return s?!s.disabled&&this.matchesShortcutEvent(e,s):!e.altKey&&(e.key||"").toLowerCase()==="c"},async copyTerminalSelection(e){const t=e??(this.terminal.hasSelection?.()?this.terminal.getSelection():"");if(!t)return!1;const s=await this._copyText(t);return s?(this.terminal.clearSelection?.(),this.showToast("Copied to clipboard","success")):this.showToast("Failed to copy","error"),this.terminal.focus(),s},async copyTerminal(){try{const e=this.terminal.buffer.active;let t="";for(let s=0;s<e.length;s++){const i=e.getLine(s);i&&(t+=i.translateToString(!0)+`
3
- `)}await navigator.clipboard.writeText(t.replace(/\n+$/,`
4
- `)),this.showToast("Copied to clipboard","success")}catch{this.showToast("Failed to copy","error")}},_syncMobileHelperTextareaToCursor(){if(!(!MobileDetection.isTouchDevice()||!this.terminal?.element))try{const e=this.terminal.element,t=this.terminal.element.querySelector(".xterm-cursor"),s=this.terminal.element.querySelector(".xterm-screen");if(!(e instanceof HTMLElement)||!(t instanceof HTMLElement)||!(s instanceof HTMLElement))return;const i=t.getBoundingClientRect(),o=s.getBoundingClientRect();if(!i.width&&!i.height)return;const a=Math.max(0,Math.round(i.left-o.left)),n=Math.max(0,Math.round(i.top-o.top));e.style.setProperty("--xterm-helper-left",`${a}px`),e.style.setProperty("--xterm-helper-top",`${n}px`)}catch{}},_dispatchSyntheticTerminalClick(e,t){const s=this.terminal?.element;if(!s||!Number.isFinite(e)||!Number.isFinite(t))return;const i={bubbles:!0,cancelable:!0,view:window,clientX:e,clientY:t,screenX:e,screenY:t,button:0,detail:1};try{s.dispatchEvent(new MouseEvent("mousedown",{...i,buttons:1})),s.dispatchEvent(new MouseEvent("mouseup",{...i,buttons:0}))}catch{}},_sessionUsesServerMouseStrip(){const e=this.sessions?.get(this.activeSessionId)?.mode||"claude";return e==="claude"||e==="codex"||e==="gemini"},_terminalViewportAtBottom(){const e=this.terminal?.buffer?.active;return!e||e.viewportY>=e.baseY},_clientPointToCell(e,t){if(!this.terminal||!Number.isFinite(e)||!Number.isFinite(t))return null;const s=this.terminal.element?.querySelector(".xterm-screen"),i=this.terminal._core?._renderService?.dimensions?.css?.cell;if(!s||!i?.width||!i?.height)return null;const o=s.getBoundingClientRect(),a=Math.max(1,Math.min(this.terminal.cols,Math.floor((e-o.left)/i.width)+1)),n=Math.max(1,Math.min(this.terminal.rows,Math.floor((t-o.top)/i.height)+1));return{col:a,row:n}},_sendSyntheticSgrTap(e,t){if(!this.activeSessionId||!this._terminalViewportAtBottom())return;const s=this._clientPointToCell(e,t);s&&this._sendInputAsync(this.activeSessionId,`\x1B[<0;${s.col};${s.row}M\x1B[<0;${s.col};${s.row}m`)},_cliVersionAtLeast(e,t){if(typeof e!="string")return!1;const s=e.trim().replace(/^v/,"").split(".").map(Number);if(s.length!==3||s.some(o=>!Number.isFinite(o)))return!1;const i=t.split(".").map(Number);for(let o=0;o<3;o++)if(s[o]!==i[o])return s[o]>i[o];return!0},_wheelScrollLines(e){const t=this._wheelScrollLinesFloat(e);return t?Math.round(t)||(t>0?1:-1):0},_wheelScrollLinesFloat(e){const t=e.shiftKey&&Math.abs(e.deltaX)>Math.abs(e.deltaY)?e.deltaX:e.deltaY;return t?e.deltaMode===1?t:e.deltaMode===2?t*(this.terminal?.rows||24):t/25:0},_shouldForwardWheelToApp(e){if(e.shiftKey||this.loadAppSettingsFromStorage?.()?.terminalWheelLocalScrollback)return!1;const t=this.terminal?.modes?.mouseTrackingMode;if(t&&t!=="none")return!1;const s=this.sessions?.get(this.activeSessionId);return!((s?.mode||"claude")!=="claude"||!this._cliVersionAtLeast(s?.cliVersion,"2.1.187"))},_sendSyntheticSgrWheel(e,t,s){if(!this.activeSessionId||!s)return;const i=this._clientPointToCell(e,t);if(!i)return;const o=s<0?64:65,a=Math.min(Math.abs(s),5);this._queueScrollBytes(`\x1B[<${o};${i.col};${i.row}M`.repeat(a))},_queueScrollBytes(e){if(!e||!this.activeSessionId)return;const t=this._wheelSgrQueue||"";t.length>512||(this._wheelSgrQueue=t+e,!this._wheelSgrFlushTimer&&(this._wheelSgrFlushTimer=setTimeout(()=>this._flushWheelSgrQueue(),40)))},_localScrollbackIsHollow(){if((this.sessions?.get(this.activeSessionId)?.mode||"claude")!=="claude")return!1;const t=this.terminal?.buffer?.active;return!t||t.type==="alternate"?!1:(t.baseY||0)===0},_maybePageCliTranscript(e,t){if(!t||e?.shiftKey||!this.activeSessionId||!this._localScrollbackIsHollow())return!1;this._pageKeySession!==this.activeSessionId&&(this._pageKeySession=this.activeSessionId,this._pageKeyPending=0);const s=window.CodemanTerminalInput,i=Math.max(2,Math.round((this.terminal?.rows||24)*s.PAGE_KEY_SCREEN_FRACTION)),o=(this._pageKeyPending||0)+t,a=Math.trunc(o/i);if(this._pageKeyPending=o-a*i,a){const n=a<0?s.KEY_PAGE_UP:s.KEY_PAGE_DOWN;this._queueScrollBytes(n.repeat(Math.min(Math.abs(a),s.PAGE_KEY_MAX_PER_BATCH)))}return this._logScrollRouting("page-keys"),!0},_logScrollRouting(e){const t=this.activeSessionId||"(none)",s=this.sessions?.get(t),i=!!this.loadAppSettingsFromStorage?.()?.terminalWheelLocalScrollback,o=this.terminal?.modes?.mouseTrackingMode||"none",a=this.terminal?.buffer?.active?.baseY??-1,n=`${e}|${s?.mode}|${s?.cliVersion}|${i}|${o}|${a>0}`;this._scrollRoutingLogged||(this._scrollRoutingLogged=new Map),this._scrollRoutingLogged.get(t)!==n&&(this._scrollRoutingLogged.set(t,n),console.log(`[scroll] ${t} \u2192 ${e} (mode=${s?.mode||"?"}, cliVersion=${s?.cliVersion||"unknown"}, localScrollbackOptOut=${i}, mouseTracking=${o}, localScrollbackRows=${a})`))},_flushWheelSgrQueue(){this._wheelSgrFlushTimer=null;const e=this._wheelSgrQueue;this._wheelSgrQueue="",e&&this.activeSessionId&&this._sendInputEphemeral(this.activeSessionId,e)},_handleDesktopTerminalClick(e){if(!this.terminal||!e?.isTrusted||e.button!==0||e.detail!==1||e.shiftKey||e.altKey||e.ctrlKey||e.metaKey)return;const t=this.terminal.modes?.mouseTrackingMode;t&&t!=="none"||this._sessionUsesServerMouseStrip()&&(this.terminal.hasSelection?.()||this._linkHovered||performance.now()<=(this._trustedTapMouseSuppressUntil||0)||e.target?.closest?.(".xterm-screen")&&this._sendSyntheticSgrTap(e.clientX,e.clientY))},_installMobileTapMouseGuard(){const e=this.terminal?.element;if(!e||e._codemanTapMouseGuardInstalled||typeof MobileDetection<"u"&&MobileDetection.isTouchDevice&&!MobileDetection.isTouchDevice())return;e._codemanTapMouseGuardInstalled=!0;const t=s=>{const i=this._trustedTapMouseSuppressUntil||0;!s.isTrusted||performance.now()>i||(s.preventDefault(),s.stopImmediatePropagation())};e.addEventListener("mousedown",t,!0),e.addEventListener("mouseup",t,!0)},_suppressTrustedTapMouseEvents(){const e=window.CodemanTerminalInput?.TOUCH_COMPAT_MOUSE_SUPPRESS_MS||450;this._trustedTapMouseSuppressUntil=performance.now()+e},increaseFontSize(){const e=this.terminal.options.fontSize||14;this.setFontSize(Math.min(e+2,24))},decreaseFontSize(){const e=this.terminal.options.fontSize||14;this.setFontSize(Math.max(e-2,10))},setFontSize(e){this.terminal.options.fontSize=e,document.getElementById("fontSizeDisplay").textContent=e,this.fitAddon.fit(),localStorage.setItem("codeman-font-size",e),this._localEchoOverlay?.refreshFont()},loadFontSize(){const e=localStorage.getItem("codeman-font-size");if(e){const t=parseInt(e,10);t>=10&&t<=24&&(this.terminal.options.fontSize=t,document.getElementById("fontSizeDisplay").textContent=t)}},getTerminalDimensions(){const s=this.fitAddon?.proposeDimensions();return s?{cols:Math.max(s.cols,40),rows:Math.max(s.rows,10)}:null},async sendResize(e,t={}){this.fitAddon&&this.fitAddon.fit();const s=this.getTerminalDimensions();if(!s)return!1;const i=this._lastResizeDims,o=!i||i.cols!==s.cols||i.rows!==s.rows;this._lastResizeDims={cols:s.cols,rows:s.rows};const a=typeof MobileDetection<"u"&&MobileDetection.getDeviceType?MobileDetection.getDeviceType():window.innerWidth<430?"mobile":window.innerWidth<768?"tablet":"desktop";if(!t.forceHttp&&this._wsReady&&this._wsSessionId===e)try{const l={t:"z",c:s.cols,r:s.rows,v:a};return t.force&&(l.f=!0),this._ws.send(JSON.stringify(l)),o}catch{}const n={...s,viewportType:a};return t.force&&(n.force=!0),await fetch(`/api/sessions/${e}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)}),o},async sendInput(e){!this.activeSessionId||!e||this._sendInputAsync(this.activeSessionId,e,{useMux:!0})},toggleDirInput(){const e=document.querySelector("#dirDisplay").parentElement,t=document.getElementById("dirInput");t.classList.contains("hidden")&&(t.classList.remove("hidden"),e.style.display="none",t.focus())},hideDirInput(){const e=document.querySelector("#dirDisplay").parentElement,t=document.getElementById("dirInput");setTimeout(()=>{t.classList.add("hidden"),e.style.display="";const s=t.value.trim();document.getElementById("dirDisplay").textContent=s||"No directory"},100)},applyTerminalSkin(e){const t={...window.CODEMAN_XTERM_THEMES[e]||window.CODEMAN_XTERM_THEMES["daylight-blue"]},s=window.codemanCurrentSkinIsLight(e)?4.5:1;if(this.terminal){this.terminal.options.minimumContrastRatio=s,this.terminal.options.theme=t,this._localEchoOverlay?.refreshFont();try{this.terminal.refresh(0,this.terminal.rows-1)}catch{}}if(this.teammateTerminals){for(const[,i]of this.teammateTerminals)if(i&&i.terminal){i.terminal.options.minimumContrastRatio=s,i.terminal.options.theme={...t};try{i.terminal.refresh(0,i.terminal.rows-1)}catch{}}}}}),(function(e){const i={session:"Sessions",event:"Events",file:"Files"};function o(a){if(!Number.isFinite(a))return"";const n=new Date(a);return n.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+n.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1})}e.CodemanSearch={SEARCH_DEBOUNCE_MS:250,SEARCH_LIMIT:60,SOURCE_LABELS:i,formatSearchTime:o}})(window),Object.assign(CodemanApp.prototype,{initSearchPanel(){const e=document.getElementById("searchInput");if(!e||this._searchPanelWired){this._searchPanelWired&&this._populateSearchCaseFilter();return}this._searchPanelWired=!0,this._searchTypes=new Set(["session","event","file"]),this._searchSecondary={caseLabel:"",status:"",days:""},this._searchDebounceTimer=null,this._searchSeq=0,this._searchLastData=null;const t=document.getElementById("searchClearBtn"),s=document.getElementById("searchResults");e.addEventListener("input",()=>{t&&(t.hidden=e.value.length===0),this._scheduleSearch()}),e.addEventListener("keydown",n=>{n.key==="Escape"&&e.value&&(n.stopPropagation(),this._clearSearch())}),t&&t.addEventListener("click",()=>this._clearSearch()),document.querySelectorAll("#searchFilters .search-filter-chip").forEach(n=>{n.addEventListener("click",()=>{const l=n.dataset.typeFilter;this._searchTypes.has(l)&&this._searchTypes.size===1||(this._searchTypes.has(l)?(this._searchTypes.delete(l),n.classList.remove("active")):(this._searchTypes.add(l),n.classList.add("active")),this._runSearch())})});const i=document.getElementById("searchCaseFilter"),o=document.getElementById("searchStatusFilter"),a=document.getElementById("searchDateFilter");i&&i.addEventListener("change",()=>{this._searchSecondary.caseLabel=i.value,this._renderSearch(this._searchLastData)}),o&&o.addEventListener("change",()=>{this._searchSecondary.status=o.value,this._renderSearch(this._searchLastData)}),a&&a.addEventListener("change",()=>{this._searchSecondary.days=a.value,this._renderSearch(this._searchLastData)}),this._populateSearchCaseFilter(),s&&(s.hidden=!0)},_populateSearchCaseFilter(){const e=document.getElementById("searchCaseFilter");if(!e)return;const t=Array.isArray(this.cases)?this.cases:[],s=Array.from(new Set(t.map(a=>a&&a.name).filter(Boolean))).sort(),i=e.value;e.innerHTML="";const o=document.createElement("option");o.value="",o.textContent="All cases",e.appendChild(o);for(const a of s){const n=document.createElement("option");n.value=a,n.textContent="#"+a,e.appendChild(n)}i&&s.includes(i)&&(e.value=i)},_scheduleSearch(){clearTimeout(this._searchDebounceTimer),this._searchDebounceTimer=setTimeout(()=>this._runSearch(),window.CodemanSearch.SEARCH_DEBOUNCE_MS)},_clearSearch(){const e=document.getElementById("searchInput"),t=document.getElementById("searchClearBtn");e&&(e.value=""),t&&(t.hidden=!0),this._searchLastData=null,this._renderSearch(null)},async _runSearch(){const e=document.getElementById("searchInput");if(!e)return;const t=e.value.trim();if(t.length===0){this._searchLastData=null,this._renderSearch(null);return}const s=Array.from(this._searchTypes),i=new URLSearchParams;i.set("q",t.slice(0,200)),s.length>0&&s.length<3&&i.set("types",s.join(",")),i.set("limit",String(window.CodemanSearch.SEARCH_LIMIT));const o=++this._searchSeq,a=await this._apiJson("/api/search?"+i.toString());o===this._searchSeq&&(a?this._searchLastData=a:this._searchLastData={query:t,groups:[],totalResults:0,truncated:!1,_error:!0},this._renderSearch(this._searchLastData))},_applySecondaryFilters(e){const{caseLabel:t,status:s,days:i}=this._searchSecondary;let o=e;if(t){const a="#"+t;o=o.filter(n=>(n.sessionName||"").includes(a)||n.sessionName===t)}if(s){const a=new Set((this.sessionOrder||[]).concat(Object.keys(this.sessions||{})));o=o.filter(n=>{const l=a.has(n.sessionId);return s==="active"?l:!l})}if(i){const a=Date.now()-Number(i)*24*60*60*1e3;o=o.filter(n=>Number.isFinite(n.timestamp)&&n.timestamp>=a)}return o},_renderSearch(e){const t=document.getElementById("searchResults"),s=document.getElementById("historyTitle"),i=document.getElementById("historyList");if(!t)return;const o=!!e;if(s&&(s.style.display=o?"none":""),i&&(i.style.display=o?"none":""),t.innerHTML="",!e){t.hidden=!0;return}if(t.hidden=!1,e._error){const l=document.createElement("div");l.className="search-empty",l.textContent="Search unavailable \u2014 check the query and try again.",t.appendChild(l);return}const a=(e.groups||[]).map(l=>({type:l.type,results:this._applySecondaryFilters(l.results||[])})).filter(l=>l.results.length>0);if(a.reduce((l,m)=>l+m.results.length,0)===0){const l=document.createElement("div");l.className="search-empty",l.textContent='No results for "'+(e.query||"")+'"',t.appendChild(l);return}for(const l of a){const m=document.createElement("div");m.className="search-group-header";const g=document.createElement("span");g.className="search-group-label",g.textContent=window.CodemanSearch.SOURCE_LABELS[l.type]||l.type;const c=document.createElement("span");c.className="search-group-count",c.textContent=String(l.results.length),m.append(g,c),t.appendChild(m);for(const p of l.results)t.appendChild(this._buildSearchResultCard(p))}if(e.truncated){const l=document.createElement("div");l.className="search-truncated",l.textContent="Showing the top matches \u2014 refine your search to narrow results.",t.appendChild(l)}},_buildSearchResultCard(e){const t=document.createElement("div");t.className="search-result-card",t.dataset.type=e.type,t.tabIndex=0,t.setAttribute("role","button");const s=document.createElement("div");s.className="search-result-top";const i=document.createElement("span");i.className="search-result-badge search-badge-"+e.type,i.textContent=(window.CodemanSearch.SOURCE_LABELS[e.type]||e.type).replace(/s$/,"");const o=document.createElement("span");o.className="search-result-name",o.textContent=e.sessionName||e.sessionId||"(session)";const a=document.createElement("span");a.className="search-result-time",a.textContent=window.CodemanSearch.formatSearchTime(e.timestamp),s.append(i,o,a);const n=document.createElement("div");n.className="search-result-snippet",n.textContent=e.snippet||"",t.append(s,n);const l=()=>this._jumpToSearchResult(e);return t.addEventListener("click",l),t.addEventListener("keydown",m=>{(m.key==="Enter"||m.key===" ")&&(m.preventDefault(),l())}),t},_jumpToSearchResult(e){const t=e&&e.jumpTo;if(t){typeof this.hideWelcome=="function"&&this.hideWelcome();try{t.kind==="run-summary"?this.openRunSummary(t.sessionId):t.kind==="file-preview"?this.openFilePreview(t.relativePath||"",t.sessionId,t.targetId||null):this.selectSession(t.sessionId)}catch(s){console.error("[search] jump failed",s)}}}});