aicodeman 0.9.7 → 0.9.9

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 (177) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -3
  3. package/dist/file-stream-manager.d.ts.map +1 -1
  4. package/dist/file-stream-manager.js +6 -2
  5. package/dist/file-stream-manager.js.map +1 -1
  6. package/dist/mux-interface.d.ts +11 -1
  7. package/dist/mux-interface.d.ts.map +1 -1
  8. package/dist/session.d.ts +39 -2
  9. package/dist/session.d.ts.map +1 -1
  10. package/dist/session.js +75 -8
  11. package/dist/session.js.map +1 -1
  12. package/dist/tmux-manager.d.ts +44 -3
  13. package/dist/tmux-manager.d.ts.map +1 -1
  14. package/dist/tmux-manager.js +446 -18
  15. package/dist/tmux-manager.js.map +1 -1
  16. package/dist/types/api.d.ts +10 -17
  17. package/dist/types/api.d.ts.map +1 -1
  18. package/dist/types/api.js +32 -3
  19. package/dist/types/api.js.map +1 -1
  20. package/dist/types/session.d.ts +17 -2
  21. package/dist/types/session.d.ts.map +1 -1
  22. package/dist/types/session.js +1 -1
  23. package/dist/types/session.js.map +1 -1
  24. package/dist/utils/codex-cli-resolver.d.ts +21 -0
  25. package/dist/utils/codex-cli-resolver.d.ts.map +1 -0
  26. package/dist/utils/codex-cli-resolver.js +64 -0
  27. package/dist/utils/codex-cli-resolver.js.map +1 -0
  28. package/dist/utils/index.d.ts +2 -0
  29. package/dist/utils/index.d.ts.map +1 -1
  30. package/dist/utils/index.js +2 -0
  31. package/dist/utils/index.js.map +1 -1
  32. package/dist/utils/push-endpoint-validation.d.ts +6 -0
  33. package/dist/utils/push-endpoint-validation.d.ts.map +1 -0
  34. package/dist/utils/push-endpoint-validation.js +80 -0
  35. package/dist/utils/push-endpoint-validation.js.map +1 -0
  36. package/dist/web/public/{api-client.3adebdc2.js → api-client.c9b1cddc.js} +10 -1
  37. package/dist/web/public/api-client.c9b1cddc.js.br +0 -0
  38. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  39. package/dist/web/public/app.a8663e79.js +35 -0
  40. package/dist/web/public/app.a8663e79.js.br +0 -0
  41. package/dist/web/public/app.a8663e79.js.gz +0 -0
  42. package/dist/web/public/{constants.5b68d2de.js → constants.74211deb.js} +1 -0
  43. package/dist/web/public/constants.74211deb.js.br +0 -0
  44. package/dist/web/public/constants.74211deb.js.gz +0 -0
  45. package/dist/web/public/{image-input.7cade6a8.js → image-input.0ea86695.js} +1 -1
  46. package/dist/web/public/{image-input.7cade6a8.js.br → image-input.0ea86695.js.br} +0 -0
  47. package/dist/web/public/image-input.0ea86695.js.gz +0 -0
  48. package/dist/web/public/index.html +66 -21
  49. package/dist/web/public/index.html.br +0 -0
  50. package/dist/web/public/index.html.gz +0 -0
  51. package/dist/web/public/input-cjk.b8686b5e.js +1 -0
  52. package/dist/web/public/input-cjk.b8686b5e.js.br +0 -0
  53. package/dist/web/public/input-cjk.b8686b5e.js.gz +0 -0
  54. package/dist/web/public/{keyboard-accessory.cdfd8c04.js → keyboard-accessory.bc753cc7.js} +3 -2
  55. package/dist/web/public/keyboard-accessory.bc753cc7.js.br +0 -0
  56. package/dist/web/public/keyboard-accessory.bc753cc7.js.gz +0 -0
  57. package/dist/web/public/{mobile-handlers.1e2a8ef8.js → mobile-handlers.d54d97d6.js} +26 -10
  58. package/dist/web/public/mobile-handlers.d54d97d6.js.br +0 -0
  59. package/dist/web/public/mobile-handlers.d54d97d6.js.gz +0 -0
  60. package/dist/web/public/mobile.959f6fe2.css +1 -0
  61. package/dist/web/public/mobile.959f6fe2.css.br +0 -0
  62. package/dist/web/public/mobile.959f6fe2.css.gz +0 -0
  63. package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
  64. package/dist/web/public/orchestrator-panel.js +3 -3
  65. package/dist/web/public/orchestrator-panel.js.br +0 -0
  66. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  67. package/dist/web/public/{panels-ui.5192a2c0.js → panels-ui.6bb3169f.js} +4 -4
  68. package/dist/web/public/panels-ui.6bb3169f.js.br +0 -0
  69. package/dist/web/public/panels-ui.6bb3169f.js.gz +0 -0
  70. package/dist/web/public/{ralph-panel.61076370.js → ralph-panel.6de2d0f8.js} +2 -2
  71. package/dist/web/public/{ralph-panel.61076370.js.br → ralph-panel.6de2d0f8.js.br} +0 -0
  72. package/dist/web/public/{ralph-panel.61076370.js.gz → ralph-panel.6de2d0f8.js.gz} +0 -0
  73. package/dist/web/public/{ralph-wizard.52d533d2.js → ralph-wizard.a6b2d36b.js} +6 -6
  74. package/dist/web/public/ralph-wizard.a6b2d36b.js.br +0 -0
  75. package/dist/web/public/ralph-wizard.a6b2d36b.js.gz +0 -0
  76. package/dist/web/public/{respawn-ui.5377f958.js → respawn-ui.2d249da9.js} +1 -1
  77. package/dist/web/public/{respawn-ui.5377f958.js.br → respawn-ui.2d249da9.js.br} +0 -0
  78. package/dist/web/public/{respawn-ui.5377f958.js.gz → respawn-ui.2d249da9.js.gz} +0 -0
  79. package/dist/web/public/session-ui.512816d8.js +36 -0
  80. package/dist/web/public/session-ui.512816d8.js.br +0 -0
  81. package/dist/web/public/session-ui.512816d8.js.gz +0 -0
  82. package/dist/web/public/settings-ui.21b009ca.js +55 -0
  83. package/dist/web/public/settings-ui.21b009ca.js.br +0 -0
  84. package/dist/web/public/settings-ui.21b009ca.js.gz +0 -0
  85. package/dist/web/public/styles.f3a0faa3.css +1 -0
  86. package/dist/web/public/styles.f3a0faa3.css.br +0 -0
  87. package/dist/web/public/styles.f3a0faa3.css.gz +0 -0
  88. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
  89. package/dist/web/public/sw.js.gz +0 -0
  90. package/dist/web/public/terminal-ui.6ce91b0b.js +3 -0
  91. package/dist/web/public/terminal-ui.6ce91b0b.js.br +0 -0
  92. package/dist/web/public/terminal-ui.6ce91b0b.js.gz +0 -0
  93. package/dist/web/public/upload.html.gz +0 -0
  94. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  95. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  96. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  97. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  98. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  99. package/dist/web/public/vendor/xterm.css.gz +0 -0
  100. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  101. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  102. package/dist/web/routes/case-routes.d.ts.map +1 -1
  103. package/dist/web/routes/case-routes.js +1 -2
  104. package/dist/web/routes/case-routes.js.map +1 -1
  105. package/dist/web/routes/clipboard-routes.d.ts.map +1 -1
  106. package/dist/web/routes/clipboard-routes.js +3 -2
  107. package/dist/web/routes/clipboard-routes.js.map +1 -1
  108. package/dist/web/routes/file-routes.d.ts.map +1 -1
  109. package/dist/web/routes/file-routes.js +5 -2
  110. package/dist/web/routes/file-routes.js.map +1 -1
  111. package/dist/web/routes/hook-event-routes.js +1 -1
  112. package/dist/web/routes/hook-event-routes.js.map +1 -1
  113. package/dist/web/routes/mux-routes.js +3 -3
  114. package/dist/web/routes/mux-routes.js.map +1 -1
  115. package/dist/web/routes/plan-routes.js +1 -1
  116. package/dist/web/routes/plan-routes.js.map +1 -1
  117. package/dist/web/routes/push-routes.js +2 -2
  118. package/dist/web/routes/push-routes.js.map +1 -1
  119. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  120. package/dist/web/routes/ralph-routes.js +6 -6
  121. package/dist/web/routes/ralph-routes.js.map +1 -1
  122. package/dist/web/routes/respawn-routes.d.ts.map +1 -1
  123. package/dist/web/routes/respawn-routes.js +17 -17
  124. package/dist/web/routes/respawn-routes.js.map +1 -1
  125. package/dist/web/routes/scheduled-routes.js +2 -2
  126. package/dist/web/routes/scheduled-routes.js.map +1 -1
  127. package/dist/web/routes/session-routes.d.ts.map +1 -1
  128. package/dist/web/routes/session-routes.js +55 -29
  129. package/dist/web/routes/session-routes.js.map +1 -1
  130. package/dist/web/routes/system-routes.d.ts.map +1 -1
  131. package/dist/web/routes/system-routes.js +20 -17
  132. package/dist/web/routes/system-routes.js.map +1 -1
  133. package/dist/web/routes/ws-routes.d.ts.map +1 -1
  134. package/dist/web/routes/ws-routes.js +21 -1
  135. package/dist/web/routes/ws-routes.js.map +1 -1
  136. package/dist/web/schemas.d.ts +28 -0
  137. package/dist/web/schemas.d.ts.map +1 -1
  138. package/dist/web/schemas.js +38 -6
  139. package/dist/web/schemas.js.map +1 -1
  140. package/dist/web/server.d.ts.map +1 -1
  141. package/dist/web/server.js +72 -18
  142. package/dist/web/server.js.map +1 -1
  143. package/package.json +6 -3
  144. package/dist/web/public/api-client.3adebdc2.js.br +0 -0
  145. package/dist/web/public/api-client.3adebdc2.js.gz +0 -0
  146. package/dist/web/public/app.c860ea08.js +0 -34
  147. package/dist/web/public/app.c860ea08.js.br +0 -0
  148. package/dist/web/public/app.c860ea08.js.gz +0 -0
  149. package/dist/web/public/constants.5b68d2de.js.br +0 -0
  150. package/dist/web/public/constants.5b68d2de.js.gz +0 -0
  151. package/dist/web/public/image-input.7cade6a8.js.gz +0 -0
  152. package/dist/web/public/input-cjk.88082175.js +0 -1
  153. package/dist/web/public/input-cjk.88082175.js.br +0 -0
  154. package/dist/web/public/input-cjk.88082175.js.gz +0 -0
  155. package/dist/web/public/keyboard-accessory.cdfd8c04.js.br +0 -0
  156. package/dist/web/public/keyboard-accessory.cdfd8c04.js.gz +0 -0
  157. package/dist/web/public/mobile-handlers.1e2a8ef8.js.br +0 -0
  158. package/dist/web/public/mobile-handlers.1e2a8ef8.js.gz +0 -0
  159. package/dist/web/public/mobile.26dc30d6.css +0 -1
  160. package/dist/web/public/mobile.26dc30d6.css.br +0 -0
  161. package/dist/web/public/mobile.26dc30d6.css.gz +0 -0
  162. package/dist/web/public/panels-ui.5192a2c0.js.br +0 -0
  163. package/dist/web/public/panels-ui.5192a2c0.js.gz +0 -0
  164. package/dist/web/public/ralph-wizard.52d533d2.js.br +0 -0
  165. package/dist/web/public/ralph-wizard.52d533d2.js.gz +0 -0
  166. package/dist/web/public/session-ui.3e0cf024.js +0 -36
  167. package/dist/web/public/session-ui.3e0cf024.js.br +0 -0
  168. package/dist/web/public/session-ui.3e0cf024.js.gz +0 -0
  169. package/dist/web/public/settings-ui.2b70e2c8.js +0 -55
  170. package/dist/web/public/settings-ui.2b70e2c8.js.br +0 -0
  171. package/dist/web/public/settings-ui.2b70e2c8.js.gz +0 -0
  172. package/dist/web/public/styles.e87cb785.css +0 -1
  173. package/dist/web/public/styles.e87cb785.css.br +0 -0
  174. package/dist/web/public/styles.e87cb785.css.gz +0 -0
  175. package/dist/web/public/terminal-ui.37caa926.js +0 -3
  176. package/dist/web/public/terminal-ui.37caa926.js.br +0 -0
  177. package/dist/web/public/terminal-ui.37caa926.js.gz +0 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @fileoverview SSRF guard for web-push subscription endpoints (security review M7).
3
+ *
4
+ * A push `endpoint` is an attacker-suppliable URL that the server fetches via
5
+ * `webpush.sendNotification`. On the no-auth loopback default a local page (or any
6
+ * non-browser client) could register an endpoint pointing at the cloud metadata
7
+ * service (169.254.169.254) or an internal host, turning the server into an SSRF
8
+ * proxy. We require https and reject IP-literal hosts in private/loopback/
9
+ * link-local/reserved ranges. DNS-named hosts are allowed (every real push service
10
+ * — FCM, Mozilla, Apple, WNS — uses a public DNS name); this is checked both at
11
+ * subscribe time (schema) and again at send time (defense-in-depth).
12
+ *
13
+ * Note: a hostname that *resolves* to an internal IP (DNS rebinding) is not caught
14
+ * here without async resolution; the realistic, documented vector (a direct
15
+ * internal IP literal) is closed.
16
+ */
17
+ import { isIP } from 'node:net';
18
+ /** True if `host` is an IP literal in a private, loopback, link-local, or reserved range. */
19
+ function isPrivateOrReservedIp(host) {
20
+ const kind = isIP(host);
21
+ if (kind === 0)
22
+ return false; // not an IP literal — a DNS name
23
+ if (kind === 4) {
24
+ const [a, b] = host.split('.').map(Number);
25
+ if (a === 0 || a === 10 || a === 127)
26
+ return true; // unspecified, private, loopback
27
+ if (a === 169 && b === 254)
28
+ return true; // link-local (incl. 169.254.169.254 metadata)
29
+ if (a === 172 && b >= 16 && b <= 31)
30
+ return true; // private
31
+ if (a === 192 && b === 168)
32
+ return true; // private
33
+ if (a === 100 && b >= 64 && b <= 127)
34
+ return true; // CGNAT (RFC 6598)
35
+ if (a >= 224)
36
+ return true; // multicast + reserved (224.0.0.0+)
37
+ return false;
38
+ }
39
+ // IPv6
40
+ const h = host.toLowerCase();
41
+ if (h === '::1' || h === '::')
42
+ return true; // loopback, unspecified
43
+ if (h.startsWith('fe8') || h.startsWith('fe9') || h.startsWith('fea') || h.startsWith('feb'))
44
+ return true; // fe80::/10 link-local
45
+ if (h.startsWith('fc') || h.startsWith('fd'))
46
+ return true; // fc00::/7 unique-local
47
+ // IPv4-mapped (::ffff:a.b.c.d). URL/Node may normalize the dotted tail to hex
48
+ // (::ffff:7f00:1), so handle both forms and re-check the embedded IPv4.
49
+ const mappedDotted = h.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
50
+ if (mappedDotted)
51
+ return isPrivateOrReservedIp(mappedDotted[1]);
52
+ const mappedHex = h.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
53
+ if (mappedHex) {
54
+ const hi = parseInt(mappedHex[1], 16);
55
+ const lo = parseInt(mappedHex[2], 16);
56
+ return isPrivateOrReservedIp(`${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`);
57
+ }
58
+ return false;
59
+ }
60
+ /**
61
+ * Validate a web-push endpoint URL is safe to fetch server-side.
62
+ * Requires an https URL whose host is not an internal/reserved IP literal.
63
+ */
64
+ export function isSafePushEndpoint(endpoint) {
65
+ let url;
66
+ try {
67
+ url = new URL(endpoint);
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ if (url.protocol !== 'https:')
73
+ return false;
74
+ if (!url.hostname)
75
+ return false;
76
+ // URL.hostname wraps IPv6 literals in brackets ([::1]); strip them for isIP().
77
+ const host = url.hostname.replace(/^\[|\]$/g, '');
78
+ return !isPrivateOrReservedIp(host);
79
+ }
80
+ //# sourceMappingURL=push-endpoint-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-endpoint-validation.js","sourceRoot":"","sources":["../../src/utils/push-endpoint-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC,6FAA6F;AAC7F,SAAS,qBAAqB,CAAC,IAAY;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,iCAAiC;IAE/D,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QACf,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,CAAC,iCAAiC;QACpF,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,CAAC,8CAA8C;QACvF,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,IAAI,CAAC,CAAC,UAAU;QAC5D,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,CAAC,UAAU;QACnD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,GAAG;YAAE,OAAO,IAAI,CAAC,CAAC,mBAAmB;QACtE,IAAI,CAAC,IAAI,GAAG;YAAE,OAAO,IAAI,CAAC,CAAC,oCAAoC;QAC/D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO;IACP,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7B,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,CAAC,wBAAwB;IACpE,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,uBAAuB;IAClI,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,wBAAwB;IACnF,8EAA8E;IAC9E,wEAAwE;IACxE,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAC9E,IAAI,YAAY;QAAE,OAAO,qBAAqB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;IACtE,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,EAAE,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtC,MAAM,EAAE,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtC,OAAO,qBAAqB,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;IACpG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,CAAC,GAAG,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAChC,+EAA+E;IAC/E,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC"}
@@ -44,11 +44,20 @@ Object.assign(CodemanApp.prototype, {
44
44
  async _apiJson(path, opts = {}) {
45
45
  const res = await this._api(path, opts);
46
46
  if (!res || !res.ok) return null;
47
+ let body;
47
48
  try {
48
- return await res.json();
49
+ body = await res.json();
49
50
  } catch {
50
51
  return null;
51
52
  }
53
+ // Uniform API envelope (stable HTTP contract): unwrap { success:true, data } → data;
54
+ // { success:false } → null (errors also surface as a non-ok HTTP status above).
55
+ // Legacy/bare bodies pass through unchanged.
56
+ if (body && typeof body === 'object') {
57
+ if (body.success === false) return null;
58
+ if (body.success === true && 'data' in body) return body.data;
59
+ }
60
+ return body;
52
61
  },
53
62
 
54
63
  /**
@@ -0,0 +1,35 @@
1
+ "use strict";const _crashDiag={_entries:[],_maxEntries:50,log(c){const e=`${new Date().toISOString().slice(11,23)} ${c}`;this._entries.push(e),this._entries.length>this._maxEntries&&this._entries.shift();try{localStorage.setItem("codeman-crash-diag",this._entries.join(`
2
+ `))}catch{}}};try{const c=localStorage.getItem("codeman-crash-diag");c&&console.log(`[CRASH-DIAG] Previous session breadcrumbs:
3
+ `+c)}catch{}if(_crashDiag.log("PAGE LOAD"),setInterval(()=>{try{localStorage.setItem("codeman-crash-heartbeat",String(Date.now())),_crashDiag._entries.length>0&&navigator.sendBeacon("/api/crash-diag",JSON.stringify({data:_crashDiag._entries.join(`
4
+ `)}))}catch{}},2e3),window.addEventListener("error",c=>{_crashDiag.log(`ERROR: ${c.message} at ${c.filename}:${c.lineno}`),console.error("[CRASH-DIAG] Uncaught error:",c.message,`
5
+ File:`,c.filename,":",c.lineno,":",c.colno,`
6
+ Stack:`,c.error?.stack)}),window.addEventListener("unhandledrejection",c=>{_crashDiag.log(`UNHANDLED: ${c.reason?.message||c.reason}`),console.error("[CRASH-DIAG] Unhandled promise rejection:",c.reason?.message||c.reason,`
7
+ Stack:`,c.reason?.stack)}),typeof PerformanceObserver<"u")try{new PerformanceObserver(e=>{for(const t of e.getEntries())t.duration>200&&(_crashDiag.log(`LONG_TASK: ${t.duration.toFixed(0)}ms`),console.warn(`[CRASH-DIAG] Long task: ${t.duration.toFixed(0)}ms (type: ${t.entryType}, name: ${t.name})`))}).observe({type:"longtask",buffered:!0})}catch{}const _origGetContext=HTMLCanvasElement.prototype.getContext;HTMLCanvasElement.prototype.getContext=function(c,...e){const t=_origGetContext.call(this,c,...e);return(c==="webgl2"||c==="webgl")&&(this.addEventListener("webglcontextlost",s=>{_crashDiag.log(`WEBGL_LOST: ${this.width}x${this.height}`),console.error("[CRASH-DIAG] WebGL context LOST on canvas",this.width,"x",this.height,"\u2014 prevented:",s.defaultPrevented)}),this.addEventListener("webglcontextrestored",()=>{_crashDiag.log("WEBGL_RESTORED"),console.warn("[CRASH-DIAG] WebGL context restored")})),t};const _SSE_HANDLER_MAP=[[SSE_EVENTS.INIT,"_onInit"],[SSE_EVENTS.SESSION_CREATED,"_onSessionCreated"],[SSE_EVENTS.SESSION_UPDATED,"_onSessionUpdated"],[SSE_EVENTS.SESSION_DELETED,"_onSessionDeleted"],[SSE_EVENTS.SESSION_TERMINAL,"_onSSETerminal"],[SSE_EVENTS.SESSION_NEEDS_REFRESH,"_onSSENeedsRefresh"],[SSE_EVENTS.SESSION_CLEAR_TERMINAL,"_onSSEClearTerminal"],[SSE_EVENTS.SESSION_COMPLETION,"_onSessionCompletion"],[SSE_EVENTS.SESSION_ERROR,"_onSessionError"],[SSE_EVENTS.SESSION_EXIT,"_onSessionExit"],[SSE_EVENTS.SESSION_IDLE,"_onSessionIdle"],[SSE_EVENTS.SESSION_WORKING,"_onSessionWorking"],[SSE_EVENTS.SESSION_AUTO_CLEAR,"_onSessionAutoClear"],[SSE_EVENTS.SESSION_CLI_INFO,"_onSessionCliInfo"],[SSE_EVENTS.SCHEDULED_CREATED,"_onScheduledCreated"],[SSE_EVENTS.SCHEDULED_UPDATED,"_onScheduledUpdated"],[SSE_EVENTS.SCHEDULED_COMPLETED,"_onScheduledCompleted"],[SSE_EVENTS.SCHEDULED_STOPPED,"_onScheduledStopped"],[SSE_EVENTS.RESPAWN_STARTED,"_onRespawnStarted"],[SSE_EVENTS.RESPAWN_STOPPED,"_onRespawnStopped"],[SSE_EVENTS.RESPAWN_STATE_CHANGED,"_onRespawnStateChanged"],[SSE_EVENTS.RESPAWN_CYCLE_STARTED,"_onRespawnCycleStarted"],[SSE_EVENTS.RESPAWN_BLOCKED,"_onRespawnBlocked"],[SSE_EVENTS.RESPAWN_AUTO_ACCEPT_SENT,"_onRespawnAutoAcceptSent"],[SSE_EVENTS.RESPAWN_DETECTION_UPDATE,"_onRespawnDetectionUpdate"],[SSE_EVENTS.RESPAWN_TIMER_STARTED,"_onRespawnTimerStarted"],[SSE_EVENTS.RESPAWN_TIMER_CANCELLED,"_onRespawnTimerCancelled"],[SSE_EVENTS.RESPAWN_TIMER_COMPLETED,"_onRespawnTimerCompleted"],[SSE_EVENTS.RESPAWN_ERROR,"_onRespawnError"],[SSE_EVENTS.RESPAWN_ACTION_LOG,"_onRespawnActionLog"],[SSE_EVENTS.TASK_CREATED,"_onTaskCreated"],[SSE_EVENTS.TASK_COMPLETED,"_onTaskCompleted"],[SSE_EVENTS.TASK_FAILED,"_onTaskFailed"],[SSE_EVENTS.TASK_UPDATED,"_onTaskUpdated"],[SSE_EVENTS.MUX_CREATED,"_onMuxCreated"],[SSE_EVENTS.MUX_KILLED,"_onMuxKilled"],[SSE_EVENTS.MUX_DIED,"_onMuxDied"],[SSE_EVENTS.MUX_STATS_UPDATED,"_onMuxStatsUpdated"],[SSE_EVENTS.SESSION_RALPH_LOOP_UPDATE,"_onRalphLoopUpdate"],[SSE_EVENTS.SESSION_RALPH_TODO_UPDATE,"_onRalphTodoUpdate"],[SSE_EVENTS.SESSION_RALPH_COMPLETION_DETECTED,"_onRalphCompletionDetected"],[SSE_EVENTS.SESSION_RALPH_STATUS_UPDATE,"_onRalphStatusUpdate"],[SSE_EVENTS.SESSION_CIRCUIT_BREAKER_UPDATE,"_onCircuitBreakerUpdate"],[SSE_EVENTS.SESSION_EXIT_GATE_MET,"_onExitGateMet"],[SSE_EVENTS.SESSION_BASH_TOOL_START,"_onBashToolStart"],[SSE_EVENTS.SESSION_BASH_TOOL_END,"_onBashToolEnd"],[SSE_EVENTS.SESSION_BASH_TOOLS_UPDATE,"_onBashToolsUpdate"],[SSE_EVENTS.HOOK_IDLE_PROMPT,"_onHookIdlePrompt"],[SSE_EVENTS.HOOK_PERMISSION_PROMPT,"_onHookPermissionPrompt"],[SSE_EVENTS.HOOK_ELICITATION_DIALOG,"_onHookElicitationDialog"],[SSE_EVENTS.HOOK_STOP,"_onHookStop"],[SSE_EVENTS.HOOK_TEAMMATE_IDLE,"_onHookTeammateIdle"],[SSE_EVENTS.HOOK_TASK_COMPLETED,"_onHookTaskCompleted"],[SSE_EVENTS.SUBAGENT_DISCOVERED,"_onSubagentDiscovered"],[SSE_EVENTS.SUBAGENT_UPDATED,"_onSubagentUpdated"],[SSE_EVENTS.SUBAGENT_TOOL_CALL,"_onSubagentToolCall"],[SSE_EVENTS.SUBAGENT_PROGRESS,"_onSubagentProgress"],[SSE_EVENTS.SUBAGENT_MESSAGE,"_onSubagentMessage"],[SSE_EVENTS.SUBAGENT_TOOL_RESULT,"_onSubagentToolResult"],[SSE_EVENTS.SUBAGENT_COMPLETED,"_onSubagentCompleted"],[SSE_EVENTS.IMAGE_DETECTED,"_onImageDetected"],[SSE_EVENTS.TUNNEL_STARTED,"_onTunnelStarted"],[SSE_EVENTS.TUNNEL_STOPPED,"_onTunnelStopped"],[SSE_EVENTS.TUNNEL_PROGRESS,"_onTunnelProgress"],[SSE_EVENTS.TUNNEL_ERROR,"_onTunnelError"],[SSE_EVENTS.TUNNEL_QR_ROTATED,"_onTunnelQrRotated"],[SSE_EVENTS.TUNNEL_QR_REGENERATED,"_onTunnelQrRegenerated"],[SSE_EVENTS.TUNNEL_QR_AUTH_USED,"_onTunnelQrAuthUsed"],[SSE_EVENTS.PLAN_SUBAGENT,"_onPlanSubagent"],[SSE_EVENTS.PLAN_PROGRESS,"_onPlanProgress"],[SSE_EVENTS.PLAN_STARTED,"_onPlanStarted"],[SSE_EVENTS.PLAN_CANCELLED,"_onPlanCancelled"],[SSE_EVENTS.PLAN_COMPLETED,"_onPlanCompleted"],[SSE_EVENTS.ORCHESTRATOR_STATE_CHANGED,"_onOrchestratorStateChanged"],[SSE_EVENTS.ORCHESTRATOR_PLAN_PROGRESS,"_onOrchestratorPlanProgress"],[SSE_EVENTS.ORCHESTRATOR_PLAN_READY,"_onOrchestratorPlanReady"],[SSE_EVENTS.ORCHESTRATOR_PHASE_STARTED,"_onOrchestratorPhaseStarted"],[SSE_EVENTS.ORCHESTRATOR_PHASE_COMPLETED,"_onOrchestratorPhaseCompleted"],[SSE_EVENTS.ORCHESTRATOR_PHASE_FAILED,"_onOrchestratorPhaseFailed"],[SSE_EVENTS.ORCHESTRATOR_VERIFICATION,"_onOrchestratorVerification"],[SSE_EVENTS.ORCHESTRATOR_TASK_ASSIGNED,"_onOrchestratorTaskAssigned"],[SSE_EVENTS.ORCHESTRATOR_TASK_COMPLETED,"_onOrchestratorTaskCompleted"],[SSE_EVENTS.ORCHESTRATOR_TASK_FAILED,"_onOrchestratorTaskFailed"],[SSE_EVENTS.ORCHESTRATOR_COMPLETED,"_onOrchestratorCompleted"],[SSE_EVENTS.ORCHESTRATOR_ERROR,"_onOrchestratorError"],[SSE_EVENTS.CLIPBOARD_WRITE,"_onClipboardWrite"]];function parseSessionPrefix(c){if(!c)return null;const e=c.match(/^(w\d+-[a-zA-Z0-9_-]+|s\d+-[a-zA-Z0-9_-]+)/);if(!e)return null;const t=e[1],s=c.slice(t.length);return s===""?{prefix:t,suffix:""}:s.startsWith(": ")?{prefix:t,suffix:s.slice(2)}:null}class CodemanApp{constructor(){this.sessions=new Map,this._shortIdCache=new Map,this.sessionOrder=[],this.draggedTabId=null,this.cases=[],this.currentRun=null,this.totalTokens=0,this.globalStats=null,this.eventSource=null,this._clientId=typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"c-"+Math.random().toString(36).slice(2)+Date.now().toString(36),this.terminal=null,this.fitAddon=null,this.activeSessionId=null,this.soloSessionId=this._detectSoloSessionId(),this.isSoloWindow=!!this.soloSessionId,this.detachedSessions=new Set,this.detachedWindows=new Map,this._detachWatchTimers=new Map,this.windowChannel=null,this._redockGrace=new Map,this._detachPingPending=null,this._detachLivenessTimer=null,this._detachOrphanStrikes=new Map,this._initGeneration=0,this._initFallbackTimer=null,this._selectGeneration=0,this.terminalLoadStates=new Map,this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.timerCountdownInterval=null,this.terminalBuffers=new Map,this.editingSessionId=null,this.pendingCloseSessionId=null,this.muxSessions=[],this.ralphStates=new Map,this.subagents=new Map,this.subagentActivity=new Map,this.subagentToolResults=new Map,this.activeSubagentId=null,this.subagentPanelVisible=!1,this.subagentWindows=new Map,this.subagentWindowZIndex=ZINDEX_SUBAGENT_BASE,this.minimizedSubagents=new Map,this._subagentHideTimeout=null,this.subagentParentMap=new Map,this.teams=new Map,this.teamTasks=new Map,this.teammateMap=new Map,this.teammatePanesByName=new Map,this.teammateTerminals=new Map,this.terminalBufferCache=new Map,this.ralphStatePanelCollapsed=!0,this.ralphClosedSessions=new Set,this.planSubagents=new Map,this.planSubagentWindowZIndex=ZINDEX_PLAN_SUBAGENT_BASE,this.planGenerationStopped=!1,this.planAgentsMinimized=!1,this.wizardDragState=null,this.wizardDragListeners=null,this.wizardPosition=null,this.projectInsights=new Map,this.logViewerWindows=new Map,this.logViewerWindowZIndex=ZINDEX_LOG_VIEWER_BASE,this.projectInsightsPanelVisible=!1,this.orchestratorState=null,this.orchestratorPanelVisible=!1,this.currentSessionWorkingDir=null,this.imagePopups=new Map,this.imagePopupZIndex=ZINDEX_IMAGE_POPUP_BASE,this.fileBrowserData=null,this.fileBrowserExpandedDirs=new Set,this.fileBrowserFilter="",this.fileBrowserAllExpanded=!1,this.fileBrowserDragListeners=null,this.filePreviewContent="",this._toastContainer=null,this._tunnelUrl=null,this.tabAlerts=new Map,this.pendingHooks=new Map,this._ws=null,this._wsSessionId=null,this._wsReady=!1,this.pendingWrites=[],this.writeFrameScheduled=!1,this._wasAtBottomBeforeWrite=!0,this.syncWaitTimeout=null,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadSeq=0,this._bufferLoadOwner=null,this.flickerFilterBuffer="",this.flickerFilterActive=!1,this.flickerFilterTimeout=null,this._debounceTimers=Object.create(null),this.systemStatsInterval=null,this.sseReconnectTimeout=null,this._sseListenerCleanup=null,this.reconnectAttempts=0,this.maxReconnectAttempts=10,this.isOnline=navigator.onLine,this._inputQueue=new Map,this._inputQueueMaxBytes=64*1024,this._connectionStatus="connected",this._inputSendChain=Promise.resolve(),this._localEchoOverlay=null,this._localEchoEnabled=!1,this._restoringFlushedState=!1,this.activeFocusTrap=null,this.notificationManager=new NotificationManager(this),this.idleTimers=new Map,this._elemCache={},this.init()}$(e){return this._elemCache[e]||(this._elemCache[e]=document.getElementById(e)),this._elemCache[e]}_clearTimer(e){this[e]&&(clearTimeout(this[e]),this[e]=null)}_isStaleSelect(e){return e!==this._selectGeneration?(this._isLoadingBuffer&&this._finishBufferLoad(e),this._restoringFlushedState=!1,!0):!1}formatTokens(e){if(e>=1e6){const t=e/1e6;return t>=10?`${t.toFixed(1)}m`:`${t.toFixed(2)}m`}else if(e>=1e3){const t=e/1e3;return t>=100?`${t.toFixed(0)}k`:`${t.toFixed(1)}k`}return String(e)}estimateCost(e,t){const s=e/1e6*15,i=t/1e6*75;return s+i}setPendingHook(e,t){this.pendingHooks.has(e)||this.pendingHooks.set(e,new Set),this.pendingHooks.get(e).add(t),this.updateTabAlertFromHooks(e)}clearPendingHooks(e,t=null){const s=this.pendingHooks.get(e);s&&(t?s.delete(t):s.clear(),s.size===0&&this.pendingHooks.delete(e),this.updateTabAlertFromHooks(e))}updateTabAlertFromHooks(e){const t=this.pendingHooks.get(e);!t||t.size===0?this.tabAlerts.delete(e):t.has("permission_prompt")||t.has("elicitation_dialog")?this.tabAlerts.set(e,"action"):t.has("idle_prompt")&&this.tabAlerts.set(e,"idle"),this.renderSessionTabs()}init(){MobileDetection.init(),this._initWindowChannel(),this.isSoloWindow&&document.body.classList.add("solo-mode"),KeyboardHandler.init(),SwipeHandler.init(),VoiceInput.init(),KeyboardAccessoryBar.init(),this.loadAppSettingsFromStorage().extendedKeyboardBar&&KeyboardAccessoryBar.setMode("extended"),this.bindMobileHeaderUtilityToggle?.(),this.applyHeaderVisibilitySettings(),this.applyTabWrapSettings(),this.applyMonitorVisibility(),document.documentElement.classList.remove("mobile-init"),requestAnimationFrame(()=>{this.initTerminal(),this.loadFontSize(),this.connectSSE(),this._initFallbackTimer=setTimeout(()=>{this._initGeneration===0&&this.loadState()},3e3)}),this.registerServiceWorker(),this.loadTunnelStatus();const t=fetch("/api/settings").then(s=>s.ok?s.json():null).then(s=>s?.data??null).catch(()=>null);if(this.loadQuickStartCases(null,t),this._initRunMode(),this.setupEventListeners(),MobileDetection.isTouchDevice()){const s=i=>{i&&i.addEventListener("touchstart",n=>{if(!KeyboardHandler.keyboardVisible)return;const o=n.target.closest("button");o&&(n.preventDefault(),o.click(),typeof app<"u"&&app.terminal&&app.terminal.focus())},{passive:!1})};s(document.querySelector(".toolbar")),s(document.querySelector(".welcome-overlay"))}this.setupOnlineDetection(),this.loadAppSettingsFromServer(t).then(()=>{this.applyHeaderVisibilitySettings(),this.applyTabWrapSettings(),this.applyMonitorVisibility()}),document.body.classList.add("app-loaded")}_initWebGL(){if(!(typeof WebglAddon>"u"))try{this._webglAddon=new WebglAddon.WebglAddon,this._webglAddon.onContextLoss(()=>{console.error("[CRASH-DIAG] WebGL context LOST \u2014 falling back to canvas renderer"),_crashDiag.log("WEBGL_LOST"),this._disableWebGLSticky("context-lost"),this._disposeWebGLObserver(),this._webglAddon?.dispose(),this._webglAddon=null,this._scheduleTerminalRepaint()}),this.terminal.loadAddon(this._webglAddon),console.log("[CRASH-DIAG] WebGL renderer enabled"),this._installWebGLLongTaskGuard()}catch{}}_installWebGLLongTaskGuard(){if(typeof PerformanceObserver>"u"||this._webglLongTaskObserver)return;const e=performance.now(),t=[];try{this._webglLongTaskObserver=new PerformanceObserver(s=>{if(!this._webglAddon)return;const i=performance.now();i-e<WEBGL_FALLBACK.GRACE_MS||evaluateWebGLLongTaskTrip(t,s.getEntries(),i)&&(console.warn(`[CRASH-DIAG] WebGL long-task threshold (${t.length} stalls/${WEBGL_FALLBACK.WINDOW_MS}ms) \u2014 falling back to canvas renderer`),_crashDiag.log(`WEBGL_FALLBACK: ${t.length}`),this._disableWebGLSticky("long-tasks"),this._disposeWebGLObserver(),this._webglAddon?.dispose(),this._webglAddon=null,this._scheduleTerminalRepaint())}),this._webglLongTaskObserver.observe({type:"longtask",buffered:!1})}catch{}}_disposeWebGLObserver(){if(this._webglLongTaskObserver){try{this._webglLongTaskObserver.disconnect()}catch{}this._webglLongTaskObserver=null}}_scheduleTerminalRepaint(){if(this._terminalRepaintScheduled)return;this._terminalRepaintScheduled=!0,(typeof requestAnimationFrame=="function"?requestAnimationFrame:t=>setTimeout(t,0))(()=>{this._terminalRepaintScheduled=!1;try{this.terminal?.refresh(0,this.terminal.rows-1)}catch{}})}_disableWebGLSticky(e){try{localStorage.setItem("codeman-webgl-disabled",JSON.stringify({reason:e,at:Date.now()}))}catch{}}setupEventListeners(){const e=[{key:"?",altKey:"/",ctrl:!0,action:()=>this.showHelp()},{key:"w",ctrl:!0,action:()=>this.killActiveSession()},{key:"Tab",ctrl:!0,action:()=>this.nextSession()},{key:"l",ctrl:!0,action:()=>this.clearTerminal()},{key:"R",ctrl:!0,shift:!0,action:()=>this.restoreTerminalSize()},{key:"=",altKey:"+",ctrl:!0,action:()=>this.increaseFontSize()},{key:"-",ctrl:!0,action:()=>this.decreaseFontSize()},{key:"V",ctrl:!0,shift:!0,action:()=>VoiceInput.toggle()},{key:"{",ctrl:!0,shift:!0,action:()=>this.moveActiveTabLeft()},{key:"}",ctrl:!0,shift:!0,action:()=>this.moveActiveTabRight()}];document.addEventListener("keydown",s=>{if(!(s.isComposing||s.keyCode===229)){if(s.key==="Escape"&&(this.closeAllPanels(),this.closeHelp()),s.altKey&&!s.ctrlKey&&!s.shiftKey&&s.key>="1"&&s.key<="9"){const i=parseInt(s.key)-1;i<this.sessionOrder.length&&(s.preventDefault(),this.selectSession(this.sessionOrder[i]));return}for(const i of e){const n=s.key===i.key||i.altKey&&s.key===i.altKey,o=i.ctrl?s.ctrlKey||s.metaKey:!0,a=i.shift?s.shiftKey:!s.shiftKey;if(n&&o&&a){s.preventDefault(),i.action();return}}}},!0);const t=this.$("headerTokens");t&&!t._statsHandlerAttached&&(t.classList.add("clickable"),t._statsHandlerAttached=!0,t.addEventListener("click",()=>this.openTokenStats())),this.setupColorPicker()}_updateSseSubscription(e){try{const t=JSON.stringify({clientId:this._clientId,sessions:e?[e]:null});fetch("/api/events/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:t,keepalive:!0}).catch(()=>{})}catch{}}_detectSoloSessionId(){try{if(typeof window<"u"&&typeof window.__CODEMAN_SOLO__=="string"&&window.__CODEMAN_SOLO__)return window.__CODEMAN_SOLO__;const e=location.pathname.match(/^\/session\/([^/]+)\/?$/);return e?decodeURIComponent(e[1]):null}catch{return null}}detachSession(e){if(this.isSoloWindow||!this.sessions.has(e)||this.detachedSessions.has(e)&&this._raiseDetached(e))return;const t="width=960,height=680,menubar=no,toolbar=no,location=no,status=no";let s=null;try{s=window.open("/session/"+encodeURIComponent(e),"codeman-session-"+e,t)}catch{}if(!s){this.showToast?.("Pop-out blocked \u2014 allow popups for this site to detach a session","error");return}this.detachedWindows.set(e,s),this._markDetached(e,!0),this._watchDetachedWindow(e,s),this._postWindowMessage({type:"detached",id:e});try{s.focus()}catch{}}_raiseDetached(e){const t=this.detachedWindows.get(e);if(t&&!t.closed){try{t.focus()}catch{}return!0}return t&&t.closed?(this._redock(e),!1):(this._postWindowMessage({type:"focus-request",id:e}),!0)}redockSession(e){const t=this.detachedWindows.get(e);if(t&&!t.closed)try{t.close()}catch{}this._postWindowMessage({type:"close-request",id:e}),this._redock(e)}_redock(e){const t=this._detachWatchTimers.get(e);t&&(clearInterval(t),this._detachWatchTimers.delete(e)),this._cancelPendingRedock(e),this._detachOrphanStrikes.delete(e),this.detachedWindows.delete(e),this._markDetached(e,!1)}_scheduleRedock(e){if(this._redockGrace.has(e))return;const t=setTimeout(()=>{this._redockGrace.delete(e),this._redock(e)},1500);this._redockGrace.set(e,t)}_cancelPendingRedock(e){const t=this._redockGrace.get(e);t&&(clearTimeout(t),this._redockGrace.delete(e))}_markDetached(e,t){t?this.detachedSessions.add(e):this.detachedSessions.delete(e);const s=this.$("sessionTabs"),i=s&&s.querySelector(`.session-tab[data-id="${e}"]`);i&&i.classList.toggle("detached",t)}_watchDetachedWindow(e,t){const s=this._detachWatchTimers.get(e);s&&clearInterval(s);const i=setInterval(()=>{(!t||t.closed)&&(clearInterval(i),this._detachWatchTimers.delete(e),this._redock(e))},800);this._detachWatchTimers.set(e,i)}_initWindowChannel(){if(!(typeof BroadcastChannel>"u")){try{this.windowChannel=new BroadcastChannel("codeman-windows")}catch{this.windowChannel=null;return}if(this.windowChannel.onmessage=e=>this._onWindowMessage(e.data),this.isSoloWindow){this._postWindowMessage({type:"detached",id:this.soloSessionId});const e=()=>this._postWindowMessage({type:"redocked",id:this.soloSessionId});window.addEventListener("pagehide",e),window.addEventListener("beforeunload",e)}else this._postWindowMessage({type:"roll-call"}),this._startDetachLiveness()}}_postWindowMessage(e){try{this.windowChannel&&this.windowChannel.postMessage(e)}catch{}}_onWindowMessage(e){if(!(!e||typeof e!="object")){if(this.isSoloWindow){if(e.type==="roll-call"){this._postWindowMessage({type:"detached",id:this.soloSessionId});return}if(e.id!==this.soloSessionId)return;if(e.type==="close-request")try{window.close()}catch{}else if(e.type==="focus-request")try{window.focus()}catch{}return}e.type==="detached"&&e.id?(this._cancelPendingRedock(e.id),this._detachPingPending?.delete(e.id),this._detachOrphanStrikes.delete(e.id),this._markDetached(e.id,!0)):e.type==="redocked"&&e.id?this._scheduleRedock(e.id):e.type==="detach-request"&&e.id&&this.detachSession(e.id)}}_startDetachLiveness(){this._detachLivenessTimer||(this._detachLivenessTimer=setInterval(()=>this._pingDetached(),5e3))}_pingDetached(){const e=[];for(const t of this.detachedSessions){const s=this.detachedWindows.get(t);s?s.closed&&this._redock(t):e.push(t)}e.length&&(this._detachPingPending=new Set(e),this._postWindowMessage({type:"roll-call"}),setTimeout(()=>{if(this._detachPingPending){for(const t of this._detachPingPending){const s=(this._detachOrphanStrikes.get(t)||0)+1;s>=2?(this._detachOrphanStrikes.delete(t),this._redock(t)):this._detachOrphanStrikes.set(t,s)}this._detachPingPending=null}},1200))}_applySoloMode(){document.body.classList.add("solo-mode");const e=this.sessions.get(this.soloSessionId);if(!e){this._showSoloSessionGone();return}this.activeSessionId=null,this.selectSession(this.soloSessionId);const t=this.getSessionName(e)||"Session",s=document.getElementById("soloSessionTitle");s&&(s.textContent=t,s.style.display="");const i=document.getElementById("soloRedockBtn");i&&(i.style.display=""),document.title=t+" \u2014 Codeman",this.notificationManager&&(this.notificationManager.originalTitle=document.title);const n=document.querySelector(".header-brand .logo");n&&(n.onclick=o=>{o.preventDefault()})}_showSoloSessionGone(){if(document.body.classList.add("solo-mode"),document.querySelector(".solo-gone-overlay"))return;const e=document.createElement("div");e.className="solo-gone-overlay",e.innerHTML='<h2>Session unavailable</h2><p>This session has ended or is no longer available.</p><button class="btn-primary" onclick="window.close()">Close window</button>',document.body.appendChild(e),document.title="Session ended \u2014 Codeman"}connectSSE(){if(!navigator.onLine){this.setConnectionStatus("offline");return}this._clearTimer("sseReconnectTimeout"),this._sseListenerCleanup&&(this._sseListenerCleanup(),this._sseListenerCleanup=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.reconnectAttempts===0?this.setConnectionStatus("connecting"):this.setConnectionStatus("reconnecting");const e=new URLSearchParams({clientId:this._clientId});this.activeSessionId&&e.set("sessions",this.activeSessionId),this.eventSource=new EventSource(`/api/events?${e.toString()}`);const t=[],s=(i,n)=>{this.eventSource.addEventListener(i,n),t.push({event:i,handler:n})};if(this._sseListenerCleanup=()=>{for(const{event:i,handler:n}of t)this.eventSource&&this.eventSource.removeEventListener(i,n);t.length=0},this.eventSource.onopen=()=>{this.reconnectAttempts=0,this.setConnectionStatus("connected")},this.eventSource.onerror=()=>{this.reconnectAttempts++,this.reconnectAttempts>=this.maxReconnectAttempts?this.setConnectionStatus("disconnected"):this.setConnectionStatus("reconnecting"),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this._clearTimer("sseReconnectTimeout");const i=this.reconnectAttempts<=1?200:Math.min(500*Math.pow(2,this.reconnectAttempts-2),3e4);this.sseReconnectTimeout=setTimeout(()=>this.connectSSE(),i)},!this._sseHandlerWrappers){this._sseHandlerWrappers=new Map;for(const[i,n]of _SSE_HANDLER_MAP){const o=this[n];this._sseHandlerWrappers.set(i,a=>{try{o.call(this,a.data?JSON.parse(a.data):{})}catch(r){console.error(`[SSE] Error handling ${i}:`,r)}})}}for(const[i]of _SSE_HANDLER_MAP)s(i,this._sseHandlerWrappers.get(i))}_onInit(e){_crashDiag.log(`INIT: ${e.sessions?.length||0} sessions`),this.handleInit(e)}_onSessionCreated(e){this.sessions.set(e.id,e),this.sessionOrder.includes(e.id)||(this.sessionOrder.push(e.id),this.saveSessionOrder()),this.renderSessionTabs(),this.updateCost(),this.sessions.size===1&&this.startSystemStatsPolling()}_onSessionUpdated(e){const t=e.session||e,s=this.sessions.get(t.id),i=t.claudeSessionId&&(!s||!s.claudeSessionId);this.sessions.set(t.id,t),this.renderSessionTabs(),this.updateCost(),t.id===this.activeSessionId&&t.tokens&&this.updateRespawnTokens(t.tokens),this.updateSubagentParentNames(t.id),i&&(this.recheckOrphanSubagents(),requestAnimationFrame(()=>{this.updateConnectionLines()}))}_onSessionDeleted(e){if(this._wsSessionId===e.id&&this._disconnectWs(),this.isSoloWindow&&e.id===this.soloSessionId&&this._showSoloSessionGone(),this.detachedSessions.has(e.id)&&this._redock(e.id),this._cleanupSessionData(e.id),this.activeSessionId===e.id){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome()}this.renderSessionTabs(),this.renderRalphStatePanel(),this.renderProjectInsightsPanel(),this.sessions.size===0&&this.stopSystemStatsPolling()}_onSSETerminal(e){this._wsReady&&this._wsSessionId===e.id||this._onSessionTerminal(e)}_onSSENeedsRefresh(e){this._wsReady&&this._wsSessionId===e?.id||this._onSessionNeedsRefresh(e)}_onSSEClearTerminal(e){this._wsReady&&this._wsSessionId===e?.id||this._onSessionClearTerminal(e)}_onSessionTerminal(e){if(e.id===this.activeSessionId){if(e.data.length>32768&&_crashDiag.log(`TERMINAL: ${(e.data.length/1024).toFixed(0)}KB`),(this.pendingWrites?.reduce((s,i)=>s+i.length,0)||0)+(this.flickerFilterBuffer?.length||0)>131072){this._clientDropRecoveryTimer||(this._clientDropRecoveryTimer=setTimeout(()=>{this._clientDropRecoveryTimer=null,this._onSessionNeedsRefresh()},2e3));return}this.batchTerminalWrite(e.data)}}_sanitizeHtml(e){const t=document.createElement("template");t.innerHTML=e;const s=t.content;for(const n of s.querySelectorAll("script, iframe, object, embed, form, base, meta, link, style"))n.remove();for(const n of s.querySelectorAll("*"))for(const o of[...n.attributes]){const a=o.name.toLowerCase();if(a.startsWith("on"))n.removeAttribute(o.name);else if(["href","src","action","xlink:href","formaction"].includes(a)){const r=o.value.replace(/\s/g,"").toLowerCase();(r.startsWith("javascript:")||r.startsWith("vbscript:")||r.startsWith("data:text/html"))&&n.removeAttribute(o.name)}}const i=document.createElement("div");return i.appendChild(s),i.innerHTML}_cleanTerminalBuffer(e){const t=e.replace(/\x1b\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]/g,"").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b[PX^_][^\x1b]*\x1b\\/g,"").replace(/\x1b[NO()][A-Z0-9]?/g,"").replace(/\x1b[>=<78cDEHM]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"").replace(/\r\n/g,`
8
+ `).replace(/\r/g,`
9
+ `),s=[/^\s*❯\s*/,/^\s*[⏵⏺⏸⏹]+\s*/,/^\s*✻\s*(Crunching|Crunched|Thinking)/i,/bypass permissions/i,/\bshift\+tab to cycle\b/i,/^\s*focus\s*$/,/^\s*new task\?/i,/\/clear to save/i,/^\s*─{5,}\s*$/,/\[(Opus|Sonnet|Haiku|GPT|Claude)[\s\S]*(tokens?|\$|¥|%|↑|↓)/i,/^\s*\[\d+[km]?\/\d+[km]?\]/i,/[█░▓▒]{3,}/,/^\s*\(.*\s*(tokens?|context).*\)\s*$/i];return t.split(`
10
+ `).filter(o=>o.trim()?!s.some(r=>r.test(o)):!0).join(`
11
+ `).replace(/[ \t]+$/gm,"").replace(/\n{4,}/g,`
12
+
13
+
14
+ `).trim()}_preprocessAsciiArt(e){const t=/[─-╿▀-▟]/,s=/```[\s\S]*?```/g,i=[];return e.replace(s,a=>(i.push(a),`__CODEMAN_FENCE_${i.length-1}__`)).split(/(\n{2,})/).map(a=>/^\n{2,}$/.test(a)||!a.trim()||a.includes("__CODEMAN_FENCE_")?a:t.test(a)?"\n```\n"+a+"\n```\n":a).join("").replace(/__CODEMAN_FENCE_(\d+)__/g,(a,r)=>i[Number(r)])}_renderMarkdown(e){const t=e||"";if(typeof marked<"u"&&marked.parse)try{const i=this._preprocessAsciiArt(t);let n=this._sanitizeHtml(marked.parse(i,{breaks:!0,gfm:!0}));n=n.replace(/<table>/g,'<div class="rv-table-wrap"><table>').replace(/<\/table>/g,"</table></div>");const o=/[─-╿▀-▟]/,a=document.createElement("template");return a.innerHTML=n,a.content.querySelectorAll("pre > code").forEach(r=>{const l=r.parentElement,h=o.test(r.textContent||""),d=document.createElement("div");d.className=h?"rv-code-wrap rv-diagram-wrap":"rv-code-wrap";const p=document.createElement("div");p.className="rv-code-actions";const f=document.createElement("button");if(f.className="rv-copy-btn",f.type="button",f.setAttribute("aria-label","Copy code"),f.setAttribute("title","Copy code"),p.appendChild(f),h){l.classList.add("rv-diagram");const g=document.createElement("button");g.className="rv-wrap-toggle",g.type="button",g.setAttribute("aria-label","Toggle line wrapping"),g.setAttribute("title","Toggle line wrapping"),p.appendChild(g)}l.parentNode.insertBefore(d,l),d.appendChild(p),d.appendChild(l)}),a.innerHTML}catch{}return`<pre style="white-space:pre-wrap;word-break:break-word">${t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}</pre>`}_bindResponseViewerInteractions(e){!e||e.dataset.rvBound==="1"||(e.dataset.rvBound="1",e.addEventListener("click",async t=>{const s=t.target.closest(".rv-copy-btn");if(s){t.preventDefault(),t.stopPropagation();const r=s.closest(".rv-code-wrap")?.querySelector("pre code"),l=r?await this._copyText(r.textContent||""):!1;s.classList.remove("rv-copied","rv-copy-failed"),s.classList.add(l?"rv-copied":"rv-copy-failed"),clearTimeout(s._resetTimer),s._resetTimer=setTimeout(()=>{s.classList.remove("rv-copied","rv-copy-failed")},1500);return}const i=t.target.closest(".rv-wrap-toggle");if(!i)return;t.preventDefault(),t.stopPropagation();const n=i.closest(".rv-diagram-wrap"),o=n?.querySelector("pre.rv-diagram");if(!o||!n)return;const a=o.classList.toggle("rv-nowrap");n.classList.toggle("rv-wrap-nowrap",a)}))}async _copyText(e){if(!e)return!1;try{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0}catch{}try{const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.cssText="position:fixed;top:0;left:0;opacity:0;pointer-events:none",document.body.appendChild(t),t.select();const s=document.execCommand("copy");return document.body.removeChild(t),s}catch{return!1}}async toggleResponseViewer(){const e=document.getElementById("responseViewer"),t=document.getElementById("responseViewerBackdrop");if(!e)return;if(e.classList.contains("visible")){e.classList.remove("visible"),t.classList.remove("visible");return}if(this.activeSessionId)try{let o=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response`)).json())?.data??{}).text||"";if(!o){const d=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal`)).json())?.data??{};d.terminalBuffer&&(o=this._cleanTerminalBuffer(d.terminalBuffer))}const a=document.getElementById("responseViewerBody");a.innerHTML=this._renderMarkdown(o),this._bindResponseViewerInteractions(a);const r=document.getElementById("responseViewerTitle"),l=document.getElementById("responseViewerMore");r&&(r.textContent="Last Response"),l&&(l.style.display="",l.textContent="More"),e.classList.add("visible"),t.classList.add("visible"),a.scrollTop=0}catch(i){console.error("Failed to load response:",i)}}async loadFullContext(){if(!this.activeSessionId)return;const e=document.getElementById("responseViewerMore");e&&(e.textContent="...");try{const i=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response?context=full`)).json())?.data??{}).messages||[],n=document.getElementById("responseViewerBody"),o=document.getElementById("responseViewerTitle");if(!n)return;if(i.length===0){n.textContent="No conversation history available";return}n.innerHTML="";for(const a of i){const r=document.createElement("div"),l=a.role==="user";r.className="rv-message "+(l?"rv-msg-user":"rv-msg-assistant");const h=document.createElement("div");h.className="rv-role "+(l?"rv-role-user":"rv-role-assistant"),h.textContent=l?"You":"Claude",r.appendChild(h);const d=document.createElement("div");d.className="rv-text",d.innerHTML=this._renderMarkdown(a.text),r.appendChild(d),n.appendChild(r)}this._bindResponseViewerInteractions(n),o&&(o.textContent=`Conversation (${i.length} messages)`),e&&(e.style.display="none"),n.scrollTop=n.scrollHeight}catch(t){console.error("Failed to load context:",t)}finally{e&&(e.textContent="More")}}async _onSessionNeedsRefresh(){if(!(!this.activeSessionId||!this.terminal)&&!this._isLoadingBuffer)try{const t=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal?tail=${TERMINAL_TAIL_SIZE}`)).json())?.data??{};t.terminalBuffer&&(this.terminal.clear(),this.terminal.reset(),await this.chunkedTerminalWrite(t.terminalBuffer),this.terminal.scrollToBottom(),this._localEchoOverlay?.rerender(),this.activeSessionId&&this.sendResize(this.activeSessionId))}catch(e){console.error("needsRefresh reload failed:",e)}}async _onSessionClearTerminal(e){if(e.id===this.activeSessionId){if(this._isLoadingBuffer)return;try{const s=(await(await fetch(`/api/sessions/${e.id}/terminal`)).json())?.data??{};if(this.terminal.clear(),this.terminal.reset(),s.terminalBuffer){const i=s.terminalBuffer.replace(DEC_SYNC_STRIP_RE,"");await this.chunkedTerminalWrite(i)}this.sendResize(e.id),this._localEchoOverlay?.rerender()}catch(t){console.error("clearTerminal refresh failed:",t)}}}_onSessionCompletion(e){this.totalCost+=e.cost||0,this.updateCost(),e.id===this.activeSessionId&&(this.terminal.writeln(""),this.terminal.writeln(`\x1B[1;32m Done (Cost: $${(e.cost||0).toFixed(4)})\x1B[0m`))}_onSessionError(e){e.id===this.activeSessionId&&this.terminal.writeln(`\x1B[1;31m Error: ${e.error}\x1B[0m`),this._notifySession(e.id,"critical","session-error","Session Error",e.error||"Unknown error")}_onSessionExit(e){this._wsSessionId===e.id&&this._disconnectWs();const t=this.sessions.get(e.id);t&&(t.status="stopped",this.renderSessionTabs(),e.id===this.activeSessionId&&this._updateLocalEchoState()),e.code&&e.code!==0&&this._notifySession(e.id,"critical","session-crash","Session Crashed",`Exited with code ${e.code}`)}_onSessionIdle(e){const t=this.sessions.get(e.id);if(t&&(t.status="idle",this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState()),!this.respawnStatus[e.id]?.enabled){const s=this.notificationManager?.preferences?.stuckThresholdMs||6e5;clearTimeout(this.idleTimers.get(e.id)),this.idleTimers.set(e.id,setTimeout(()=>{this._notifySession(e.id,"warning","session-stuck","Session Idle",`Idle for ${Math.round(s/6e4)}+ minutes`),this.idleTimers.delete(e.id)},s))}}_onSessionWorking(e){const t=this.sessions.get(e.id);t&&(t.status="busy",this.pendingHooks.has(e.id)||this.tabAlerts.delete(e.id),this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState());const s=this.idleTimers.get(e.id);s&&(clearTimeout(s),this.idleTimers.delete(e.id))}_onSessionAutoClear(e){e.sessionId===this.activeSessionId&&(this.showToast(`Auto-cleared at ${e.tokens.toLocaleString()} tokens`,"info"),this.updateRespawnTokens(0)),this._notifySession(e.sessionId,"info","auto-clear","Auto-Cleared",`Context reset at ${(e.tokens||0).toLocaleString()} tokens`)}_onSessionCliInfo(e){const t=this.sessions.get(e.sessionId);t&&(e.version&&(t.cliVersion=e.version),e.model&&(t.cliModel=e.model),e.accountType&&(t.cliAccountType=e.accountType),e.latestVersion&&(t.cliLatestVersion=e.latestVersion)),e.sessionId===this.activeSessionId&&this.updateCliInfoDisplay()}_onScheduledCreated(e){this.currentRun=e,this.showTimer()}_onScheduledUpdated(e){this.currentRun=e,this.updateTimer()}_onScheduledCompleted(e){this.currentRun=e,this.hideTimer(),this.showToast("Scheduled run completed!","success")}_onScheduledStopped(){this.currentRun=null,this.hideTimer()}setConnectionStatus(e){this._connectionStatus=e,this._updateConnectionIndicator(),e==="connected"&&this._inputQueue.size>0&&this._drainInputQueues()}_connectWs(e){this._disconnectWs();const s=`${location.protocol==="https:"?"wss:":"ws:"}//${location.host}/ws/sessions/${e}/terminal`,i=new WebSocket(s);this._ws=i,this._wsSessionId=e,i.onopen=()=>{this._ws===i&&(this._wsReady=!0,this._wsReconnectAttempts=0,this.sendResize(e)?.catch?.(()=>{}))},i.onmessage=n=>{if(this._ws===i)try{const o=JSON.parse(n.data);o.t==="o"?this._onSessionTerminal({id:e,data:o.d}):o.t==="c"?this._onSessionClearTerminal({id:e}):o.t==="r"&&this._onSessionNeedsRefresh({id:e})}catch{}},i.onclose=n=>{if(this._ws===i&&(this._ws=null,this._wsSessionId=null,this._wsReady=!1,n.code<4004&&this.activeSessionId===e)){const o=Math.min(1e3*Math.pow(2,this._wsReconnectAttempts||0),1e4);this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},o)}},i.onerror=()=>{}}_disconnectWs(){this._clearTimer("_wsReconnectTimer"),this._wsReconnectAttempts=0,this._ws&&(this._ws.onclose=null,this._ws.close(),this._ws=null,this._wsSessionId=null,this._wsReady=!1)}_sendInputAsync(e,t){if(!this.isOnline||this._connectionStatus==="disconnected"){this._enqueueInput(e,t);return}if(this._wsReady&&this._wsSessionId===e)try{this._ws.send(JSON.stringify({t:"i",d:t})),this.clearPendingHooks(e);return}catch{}this._inputSendChain=this._inputSendChain.then(()=>{fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:t}),keepalive:t.length<65536}).then(i=>{i.ok?this.clearPendingHooks(e):this._enqueueInput(e,t)}).catch(()=>{this._enqueueInput(e,t)})})}_enqueueInput(e,t){let i=(this._inputQueue.get(e)||"")+t;i.length>this._inputQueueMaxBytes&&(i=i.slice(i.length-this._inputQueueMaxBytes)),this._inputQueue.set(e,i),this._updateConnectionIndicator()}async _drainInputQueues(){if(this._inputQueue.size===0)return;const e=new Map(this._inputQueue);this._inputQueue.clear(),this._updateConnectionIndicator();for(const[t,s]of e)(await this._apiPost(`/api/sessions/${t}/input`,{input:s}))?.ok||this._enqueueInput(t,s);this._updateConnectionIndicator()}_updateConnectionIndicator(){const e=this.$("connectionIndicator"),t=this.$("connectionDot"),s=this.$("connectionText");if(!e||!t||!s)return;let i=0;for(const r of this._inputQueue.values())i+=r.length;const n=this._connectionStatus,o=i>0;if((n==="connected"||n==="connecting")&&!o){e.style.display="none";return}e.style.display="flex",t.className="connection-dot";const a=r=>r<1024?`${r}B`:`${(r/1024).toFixed(1)}KB`;n==="connected"&&o?(t.classList.add("draining"),s.textContent=`Sending ${a(i)}...`):n==="reconnecting"?(t.classList.add("reconnecting"),s.textContent=o?`Reconnecting (${a(i)} queued)`:"Reconnecting..."):(t.classList.add("offline"),s.textContent=o?`Offline (${a(i)} queued)`:"Offline")}setupOnlineDetection(){window.addEventListener("online",()=>{this.isOnline=!0,this.reconnectAttempts=0,this.connectSSE()}),window.addEventListener("offline",()=>{this.isOnline=!1,this.setConnectionStatus("offline")})}_updateCjkInputState(){const e=document.getElementById("cjkInput");if(!e)return;const t=this.loadAppSettingsFromStorage(),s=this.getDefaultSettings?.()||{},i=this._serverCjkOverride||(t.cjkInputEnabled??s.cjkInputEnabled??!1);e.classList.toggle("cjk-input-visible",!!i),document.body.classList.toggle("cjk-input-visible",!!i),e.style.display=i?"block":"none",e.setAttribute("aria-hidden",i?"false":"true"),i&&e.value==="\u200B"&&(e.value=""),i||(window.cjkActive=!1),typeof KeyboardHandler<"u"&&KeyboardHandler.updateLayoutForKeyboard()}_resetAllAppState(){this.sessions.clear(),this.ralphStates.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.projectInsights.clear(),this.teams.clear(),this.teamTasks.clear();for(const e of this.idleTimers.values())clearTimeout(e);if(this.idleTimers.clear(),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this._localEchoOverlay?.rerender(),this.pendingHooks.clear(),this._parentNameCache&&this._parentNameCache.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),MobileDetection.cleanup(),KeyboardHandler.cleanup(),MobileDetection.init(),KeyboardHandler.init(),this.bindMobileHeaderUtilityToggle?.(),this.tabAlerts.clear(),this._shownCompletions&&this._shownCompletions.clear(),this.notificationManager?.titleFlashInterval&&(clearInterval(this.notificationManager.titleFlashInterval),this.notificationManager.titleFlashInterval=null),this.notificationManager?.groupingMap){for(const{timeout:e}of this.notificationManager.groupingMap.values())clearTimeout(e);this.notificationManager.groupingMap.clear()}this.terminalResizeObserver&&(this.terminalResizeObserver.disconnect(),this.terminalResizeObserver=null),this.planLoadingTimer&&(clearInterval(this.planLoadingTimer),this.planLoadingTimer=null),this.timerCountdownInterval&&(clearInterval(this.timerCountdownInterval),this.timerCountdownInterval=null),this.runSummaryAutoRefreshTimer&&(clearInterval(this.runSummaryAutoRefreshTimer),this.runSummaryAutoRefreshTimer=null)}handleInit(e){this._clearTimer("_initFallbackTimer");const t=++this._initGeneration;if(this._serverCjkOverride=e.inputCjkForm||!1,this._updateCjkInputState(),e.version){const n=this.$("versionDisplay"),o=this.$("headerVersion");n&&(n.textContent=`v${e.version}`,n.title=`Codeman v${e.version}`),o&&(o.textContent=`v${e.version}`,o.title=`Codeman v${e.version}`)}VoiceInput.cleanup(),this._resetAllAppState(),e.sessions.forEach(n=>{this.sessions.set(n.id,n),(n.ralphLoop||n.ralphTodos)&&!this.ralphClosedSessions.has(n.id)&&this.ralphStates.set(n.id,{loop:n.ralphLoop||null,todos:n.ralphTodos||[]})});try{localStorage.removeItem("codeman-tab-meta")}catch{}this.syncSessionOrder(),e.respawnStatus?this.respawnStatus=e.respawnStatus:this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},e.globalStats&&(this.globalStats=e.globalStats),this.totalCost=e.sessions.reduce((n,o)=>n+(o.totalCost||0),0),this.totalCost+=e.scheduledRuns.reduce((n,o)=>n+(o.totalCost||0),0);const s=e.scheduledRuns.find(n=>n.status==="running");if(s&&(this.currentRun=s,this.showTimer()),this.updateCost(),this.renderSessionTabs(),this.sessions.size>0?this.startSystemStatsPolling():this.stopSystemStatsPolling(),this.cleanupAllFloatingWindows(),e.subagents&&(this.subagents.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),e.subagents.forEach(n=>{this.subagents.set(n.agentId,n)}),this.renderSubagentPanel(),this.subagentParentMap.clear(),this.loadSubagentParentMap().then(()=>{for(const[n,o]of this.subagentParentMap){const a=this.subagents.get(n);if(a&&this.sessions.has(o)){a.parentSessionId=o;const r=this.sessions.get(o);r&&(a.parentSessionName=this.getSessionName(r)),this.subagents.set(n,a)}}for(const[n]of this.subagents)this.subagentParentMap.has(n)||this.findParentSessionForSubagent(n);this.restoreSubagentWindowStates()})),t!==this._initGeneration)return;if(this.isSoloWindow){this._applySoloMode();return}const i=this.activeSessionId;if(this.activeSessionId=null,this.sessionOrder.length>0){let n=i;if(!n||!this.sessions.has(n))try{n=localStorage.getItem("codeman-active-session")}catch{}n&&this.sessions.has(n)?this.selectSession(n):this.selectSession(this.sessionOrder[0])}}async loadState(){try{const t=await(await fetch("/api/status")).json();this.handleInit(t?.data??{})}catch(e){console.error("Failed to load state:",e)}}_debouncedCall(e,t,s=100){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{this._debounceTimers[e]=null,t.call(this)},s)}renderSessionTabs(){this._inlineRenameActive||this._debouncedCall("sessionTabs",this._renderSessionTabsImmediate)}_updateActiveTabImmediate(e){const t=this.$("sessionTabs");if(!t)return;const s=t.querySelectorAll(".session-tab[data-id]");for(const i of s)i.dataset.id===e?i.classList.add("active"):i.classList.remove("active")}_setTerminalLoadState(e,t,s){this.terminalLoadStates.set(e,{generation:t,phase:s}),this._updateTerminalLoadTab(e)}_clearTerminalLoadState(e,t){const s=this.terminalLoadStates.get(e);s&&s.generation!==t||(this.terminalLoadStates.delete(e),this._updateTerminalLoadTab(e))}_updateTerminalLoadTab(e){const t=this.$("sessionTabs")?.querySelector(`.session-tab[data-id="${e}"]`);if(!t)return;const s=this.terminalLoadStates.get(e);if(t.classList.toggle("tab-loading",!!s),s){if(t.setAttribute("aria-busy","true"),t.dataset.loadPhase=s.phase,!t.querySelector(".tab-load-spinner")){const i=document.createElement("span");i.className="tab-load-spinner",i.setAttribute("aria-hidden","true");const n=t.querySelector(".tab-number");n?n.insertAdjacentElement("afterend",i):t.insertBefore(i,t.firstChild)}}else t.setAttribute("aria-busy","false"),delete t.dataset.loadPhase,t.querySelector(".tab-load-spinner")?.remove()}_renderSessionTabsImmediate(){const e=this.$("sessionTabs"),t=e.querySelectorAll(".session-tab[data-id]"),s=new Set([...t].map(o=>o.dataset.id)),i=new Set(this.sessions.keys());if(s.size===i.size&&[...s].every(o=>i.has(o)))for(const[o,a]of this.sessions){const r=e.querySelector(`.session-tab[data-id="${o}"]`);if(!r)continue;const l=o===this.activeSessionId,h=a.status||"idle",d=this.getSessionName(a),p=a.taskStats||{running:0,total:0},f=p.running>0,g=this.terminalLoadStates.get(o);if(l&&!r.classList.contains("active")?r.classList.add("active"):!l&&r.classList.contains("active")&&r.classList.remove("active"),r.classList.toggle("tab-loading",!!g),g){if(r.setAttribute("aria-busy","true"),r.dataset.loadPhase=g.phase,!r.querySelector(".tab-load-spinner")){const u=document.createElement("span");u.className="tab-load-spinner",u.setAttribute("aria-hidden","true");const T=r.querySelector(".tab-number");T?T.insertAdjacentElement("afterend",u):r.insertBefore(u,r.firstChild)}}else r.setAttribute("aria-busy","false"),delete r.dataset.loadPhase,r.querySelector(".tab-load-spinner")?.remove();const _=this.tabAlerts.get(o),y=_==="action",m=_==="idle",b=r.classList.contains("tab-alert-action"),C=r.classList.contains("tab-alert-idle");if(y&&!b?(r.classList.add("tab-alert-action"),r.classList.remove("tab-alert-idle")):m&&!C?(r.classList.add("tab-alert-idle"),r.classList.remove("tab-alert-action")):!_&&(b||C)&&r.classList.remove("tab-alert-action","tab-alert-idle"),!r.querySelector(".tab-number")){const u=this.sessionOrder.indexOf(o);if(u>=0&&u<9){const T=document.createElement("span");T.className="tab-number",T.textContent=String(u+1),r.insertBefore(T,r.firstChild)}}const S=r.querySelector(".tab-status");S&&!S.classList.contains(h)&&(S.className=`tab-status ${h}`);const w=r.querySelector(".tab-name");if(w&&w.textContent!==d){const u=parseSessionPrefix(d);u&&u.suffix?w.innerHTML='<span class="tab-prefix">'+escapeHtml(u.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(u.suffix)+"</span>":w.textContent=d}const v=r.querySelector(".tab-badge");if(f)if(v)v.textContent!==String(p.running)&&(v.textContent=p.running);else{this._fullRenderSessionTabs();return}else if(v){this._fullRenderSessionTabs();return}const E=r.querySelector(".tab-subagent-badge"),L=this.minimizedSubagents.get(o),A=L?.size||0;if(A>0&&E){const u=E.querySelector(".subagent-label"),T=A===1?"AGENT":`AGENTS (${A})`;u&&u.textContent!==T&&(u.textContent=T);const O=E.querySelector(".subagent-dropdown");if(O){const I=this.renderSubagentTabBadge(o,L),R=document.createElement("div");R.innerHTML=I;const k=R.querySelector(".subagent-dropdown");k&&(O.innerHTML=k.innerHTML)}}else if(A>0&&!E){const u=this.renderSubagentTabBadge(o,L),T=r.querySelector(".tab-gear");T&&T.insertAdjacentHTML("beforebegin",u)}else A===0&&E&&E.remove()}else this._fullRenderSessionTabs()}_fullRenderSessionTabs(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs");document.querySelectorAll("body > .subagent-dropdown").forEach(n=>n.remove()),this.cancelHideSubagentDropdown();const t=[];let s=this.sessionOrder;MobileDetection.getDeviceType()==="mobile"&&this.activeSessionId&&(s=[this.activeSessionId,...this.sessionOrder.filter(n=>n!==this.activeSessionId)]);let i=0;for(const n of s){const o=this.sessions.get(n);if(!o)continue;const a=n===this.activeSessionId,r=o.status||"idle",l=this.getSessionName(o),h=o.mode||"claude",d=o.color||"default",p=o.taskStats||{running:0,total:0},f=p.running>0,g=this.tabAlerts.get(n),_=g==="action"?" tab-alert-action":g==="idle"?" tab-alert-idle":"",y=this.terminalLoadStates.get(n),m=this.minimizedSubagents.get(n),C=(m?.size||0)>0?this.renderSubagentTabBadge(n,m):"",S=o.workingDir&&o.workingDir.split("/").pop()||"",v=(this._tallTabsEnabled??!1)&&o.name&&S&&S!==l;t.push(`<div class="session-tab ${a?"active":""}${_}${y?" tab-loading":""}" data-id="${n}" data-color="${d}" ${y?`data-load-phase="${escapeHtml(y.phase)}"`:""} onclick="app.handleSessionTabClick(event, '${escapeHtml(n)}')" oncontextmenu="event.preventDefault(); app.startInlineRename('${escapeHtml(n)}')" tabindex="0" role="tab" aria-selected="${a?"true":"false"}" aria-busy="${y?"true":"false"}" aria-label="${escapeHtml(l)} session" ${o.workingDir?`title="${escapeHtml(o.workingDir)}"`:""}>
15
+ ${i<9?'<span class="tab-number">'+(i+1)+"</span>":""}
16
+ ${y?'<span class="tab-load-spinner" aria-hidden="true"></span>':""}
17
+ <span class="tab-status ${r}" aria-hidden="true"></span>
18
+ <span class="tab-info">
19
+ <span class="tab-name-row">
20
+ ${h==="shell"?'<span class="tab-mode shell" aria-hidden="true">sh</span>':h==="opencode"?'<span class="tab-mode opencode" aria-hidden="true">oc</span>':h==="codex"?'<span class="tab-mode codex" aria-hidden="true">cx</span>':""}
21
+ <span class="tab-name" data-session-id="${n}">${(()=>{const E=parseSessionPrefix(l);return E&&E.suffix?'<span class="tab-prefix">'+escapeHtml(E.prefix)+'</span><span class="tab-suffix">: '+escapeHtml(E.suffix)+"</span>":escapeHtml(l)})()}</span>
22
+ <span class="tab-detached-badge" aria-hidden="true">detached</span>
23
+ </span>
24
+ ${v?`<span class="tab-folder">\u{1F4C1} ${escapeHtml(S)}</span>`:""}
25
+ </span>
26
+ ${f?`<span class="tab-badge" onclick="event.stopPropagation(); app.toggleTaskPanel()" aria-label="${p.running} running tasks">${p.running}</span>`:""}
27
+ ${C}
28
+ <span class="tab-gear" onclick="event.stopPropagation(); app.openSessionOptions('${escapeHtml(n)}')" title="Session options" aria-label="Session options" tabindex="0">&#x2699;</span>
29
+ <span class="tab-detach" onclick="event.stopPropagation(); app.detachSession('${escapeHtml(n)}')" title="Open in a new window" aria-label="Open session in a new window" tabindex="0">&#x29C9;</span>
30
+ <span class="tab-close" onclick="event.stopPropagation(); app.requestCloseSession('${escapeHtml(n)}')" title="Close session" aria-label="Close session" tabindex="0">&times;</span>
31
+ </div>`),i++}e.innerHTML=t.join(""),this.setupTabDragHandlers(),this.setupTabKeyboardNavigation(e),this.updateConnectionLines()}setupTabKeyboardNavigation(e){this._tabKeydownHandler&&e.removeEventListener("keydown",this._tabKeydownHandler),this._tabKeydownHandler=t=>{if(!["ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(t.key))return;const s=[...e.querySelectorAll(".session-tab")],i=s.indexOf(document.activeElement);if((t.key==="Enter"||t.key===" ")&&i>=0){t.preventDefault();const o=s[i].dataset.id;this.selectSession(o,{forceReload:!0});return}if(i<0)return;let n;switch(t.key){case"ArrowLeft":n=i>0?i-1:s.length-1;break;case"ArrowRight":n=i<s.length-1?i+1:0;break;case"Home":n=0;break;case"End":n=s.length-1;break;default:return}t.preventDefault(),s[n]?.focus()},e.addEventListener("keydown",this._tabKeydownHandler)}handleSessionTabClick(e,t){return e?.preventDefault?.(),!(typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible===!0)&&MobileDetection.isTouchDevice()&&document.activeElement?.blur?.(),this.selectSession(t,{forceReload:!0})}syncSessionOrder(){const e=new Set(this.sessions.keys()),s=this.loadSessionOrder().filter(o=>e.has(o)),i=new Set(s),n=[...e].filter(o=>!i.has(o));this.sessionOrder=[...s,...n]}loadSessionOrder(){try{const e=localStorage.getItem("codeman-session-order");return e?JSON.parse(e):[]}catch{return[]}}saveSessionOrder(){try{localStorage.setItem("codeman-session-order",JSON.stringify(this.sessionOrder))}catch{}}setupTabDragHandlers(){const e=this.$("sessionTabs");e.querySelectorAll(".session-tab[data-id]").forEach(s=>{s.setAttribute("draggable","true"),s.addEventListener("dragstart",i=>{this.draggedTabId=s.dataset.id,s.classList.add("dragging"),i.dataTransfer.effectAllowed="move",i.dataTransfer.setData("text/plain",s.dataset.id)}),s.addEventListener("dragend",()=>{s.classList.remove("dragging"),this.draggedTabId=null,e.querySelectorAll(".session-tab").forEach(i=>{i.classList.remove("drag-over-left","drag-over-right")})}),s.addEventListener("dragover",i=>{if(i.preventDefault(),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;i.dataTransfer.dropEffect="move";const n=s.getBoundingClientRect(),o=n.left+n.width/2,a=i.clientX<o;s.classList.toggle("drag-over-left",a),s.classList.toggle("drag-over-right",!a)}),s.addEventListener("dragleave",()=>{s.classList.remove("drag-over-left","drag-over-right")}),s.addEventListener("drop",i=>{if(i.preventDefault(),s.classList.remove("drag-over-left","drag-over-right"),!this.draggedTabId||this.draggedTabId===s.dataset.id)return;const n=s.dataset.id,o=this.draggedTabId,a=s.getBoundingClientRect(),r=a.left+a.width/2,l=i.clientX<r,h=this.sessionOrder.indexOf(o);let d=this.sessionOrder.indexOf(n);h===-1||d===-1||(this.sessionOrder.splice(h,1),d=this.sessionOrder.indexOf(n),d!==-1&&(l?this.sessionOrder.splice(d,0,o):this.sessionOrder.splice(d+1,0,o),this.saveSessionOrder(),this._fullRenderSessionTabs()))})})}moveActiveTabLeft(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e<=0||([this.sessionOrder[e-1],this.sessionOrder[e]]=[this.sessionOrder[e],this.sessionOrder[e-1]],this.saveSessionOrder(),this._fullRenderSessionTabs())}moveActiveTabRight(){if(!this.activeSessionId)return;const e=this.sessionOrder.indexOf(this.activeSessionId);e===-1||e>=this.sessionOrder.length-1||([this.sessionOrder[e],this.sessionOrder[e+1]]=[this.sessionOrder[e+1],this.sessionOrder[e]],this.saveSessionOrder(),this._fullRenderSessionTabs())}getShortId(e){if(!e)return"";let t=this._shortIdCache.get(e);return t||(t=e.slice(0,8),this._shortIdCache.set(e,t)),t}getSessionName(e){return e.name?e.name:e.workingDir?e.workingDir.split("/").pop()||e.workingDir:this.getShortId(e.id)}_notifySession(e,t,s,i,n){const o=this.sessions.get(e);this.notificationManager?.notify({urgency:t,category:s,sessionId:e,sessionName:o?.name||this.getShortId(e),title:i,message:n})}_cleanupPreviousSession(e){this._disconnectWs();const t=document.getElementById("cjkInput");t&&(t.value=""),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._tabCompletionSessionId=null,this._tabCompletionRetries=0,this._tabCompletionBaseText=null,this._clearTimer("_tabCompletionFallback"),this._clearTimer("_clientDropRecoveryTimer"),this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1;try{const s=this.terminal?._core?._compositionHelper;if(s?._isComposing){s._isComposing=!1;const i=this.terminal?.element?.querySelector(".xterm-helper-textarea");i&&i.dispatchEvent(new CompositionEvent("compositionend",{data:""}))}}catch{}if(this.activeSessionId){const s=this._localEchoOverlay?.pendingText||"",i=this._localEchoOverlay?.getFlushed()?.count||0,n=this._localEchoOverlay?.getFlushed()?.text||"";s&&this._sendInputAsync(this.activeSessionId,s);const o=i+s.length;o>0&&(this._flushedOffsets||(this._flushedOffsets=new Map),this._flushedTexts||(this._flushedTexts=new Map),this._flushedOffsets.set(this.activeSessionId,o),this._flushedTexts.set(this.activeSessionId,n+s))}this._localEchoOverlay?.clear(),this._localEchoOverlay&&!this._flushedOffsets?.has(e)&&this._localEchoOverlay.suppressBufferDetection()}_resetTerminalForReplay(){this.terminal.reset(),this.terminal.write("\x1B[3J\x1B[H\x1B[2J")}_shouldFocusTerminalForTabSwitch(){return typeof MobileDetection>"u"||!MobileDetection.isTouchDevice()?!0:typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible}async selectSession(e,t={}){if(!this.isSoloWindow&&this.detachedSessions.has(e)&&this._raiseDetached(e))return;const s=t?.forceReload===!0;if(this.activeSessionId===e&&!s)return;this.activeSessionId===e&&s&&(this.terminalBufferCache?.delete(e),this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this.activeSessionId=null);const i=this._shouldFocusTerminalForTabSwitch();i&&this.terminal&&this.terminal.focus();const n=performance.now(),o=this.sessions.get(e)?.name||e.slice(0,8);_crashDiag.log(`SELECT: ${o}`),console.log(`[CRASH-DIAG] selectSession START: ${e.slice(0,8)}`);const a=++this._selectGeneration;if(this._setTerminalLoadState(e,a,"resizing"),a!==this._selectGeneration){this._clearTerminalLoadState(e,a);return}this._cleanupPreviousSession(e),this.activeSessionId=e;try{localStorage.setItem("codeman-active-session",e)}catch{}this._updateSseSubscription(e),this.hideWelcome(),this.clearPendingHooks(e,"idle_prompt"),this._updateActiveTabImmediate(e),this.renderSessionTabs(),this._updateLocalEchoState(),this._flushedOffsets?.has(e)&&this._localEchoOverlay&&this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const r=document.querySelector(`.session-tab.active[data-id="${e}"]`);r&&(r.classList.add("tab-glow"),r.addEventListener("animationend",()=>r.classList.remove("tab-glow"),{once:!0}));const l=this.sessions.get(e);if(this.currentSessionWorkingDir=l?.workingDir||null,l&&l.pid===null)try{const d=l.mode==="shell"?`/api/sessions/${e}/shell`:`/api/sessions/${e}/interactive`;await fetch(d,{method:"POST"}),l.status="busy"}catch(d){console.error("Failed to attach to restored session:",d)}this._restoringFlushedState=!0;const h=this._beginBufferLoad(a);try{this.fitAddon&&this.fitAddon.fit();const d=await this.sendResize(e,{forceHttp:!0}).catch(()=>!1);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}const p=l&&(l.status==="busy"||l.status==="working"),f=this.terminalBufferCache.get(e);if(f&&!p){if(_crashDiag.log(`CACHE_WRITE: ${(f.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),await this.chunkedTerminalWrite(f,TERMINAL_CHUNK_SIZE,h),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom(),_crashDiag.log("CACHE_DONE")}else p&&(this._resetTerminalForReplay(),_crashDiag.log("CACHE_SKIP_BUSY"));if(l?.mode!=="shell"&&d&&(await new Promise(m=>setTimeout(m,TUI_REDRAW_SETTLE_MS)),this._isStaleSelect(a))){this._clearTerminalLoadState(e,a);return}this._setTerminalLoadState(e,a,"fetching"),_crashDiag.log("FETCH_START");const g=await fetch(`/api/sessions/${e}/terminal?tail=${TERMINAL_TAIL_SIZE}`);if(this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}const _=(await g.json())?.data??{};if(_crashDiag.log(`FETCH_DONE: ${_.terminalBuffer?(_.terminalBuffer.length/1024).toFixed(0)+"KB":"empty"} truncated=${_.truncated}`),_.terminalBuffer){if(p||_.terminalBuffer!==f){if(_crashDiag.log(`REWRITE: ${(_.terminalBuffer.length/1024).toFixed(0)}KB`),this._setTerminalLoadState(e,a,"replaying"),this._resetTerminalForReplay(),_.truncated&&this.terminal.write(`\x1B[90m... (earlier output truncated for performance) ...\x1B[0m\r
32
+ \r
33
+ `),await this.chunkedTerminalWrite(_.terminalBuffer,TERMINAL_CHUNK_SIZE,h),this._isStaleSelect(a)){this._clearTerminalLoadState(e,a);return}this.terminal.scrollToBottom()}if(this.terminalBufferCache.set(e,_.terminalBuffer),this.terminalBufferCache.size>20){const b=this.terminalBufferCache.keys().next().value;this.terminalBufferCache.delete(b)}}else f||this._resetTerminalForReplay();if(this._isLoadingBuffer&&this._finishBufferLoad(h),this._restoringFlushedState=!1,this._flushedOffsets?.has(e)&&this._localEchoOverlay){this._localEchoOverlay.setFlushed(this._flushedOffsets.get(e),this._flushedTexts?.get(e)||"",!1);const m=this._localEchoOverlay;this.terminal.write("",()=>{m.hasPending&&m.rerender()})}this.sendResize(e),(typeof requestIdleCallback=="function"?requestIdleCallback:m=>setTimeout(m,16))(()=>{if(a!==this._selectGeneration)return;this.respawnStatus[e]?(this.showRespawnBanner(),this.updateRespawnBanner(this.respawnStatus[e].state),document.getElementById("respawnCycleCount").textContent=this.respawnStatus[e].cycleCount||0,this.updateCountdownTimerDisplay(),this.updateActionLogDisplay(),Object.keys(this.respawnCountdownTimers[e]||{}).length>0&&this.startCountdownInterval()):(this.hideRespawnBanner(),this.stopCountdownInterval());const m=document.getElementById("taskPanel");m&&m.classList.contains("open")&&this.renderTaskPanel();const b=this.sessions.get(e);if(b&&(b.ralphLoop||b.ralphTodos)&&this.updateRalphState(e,{loop:b.ralphLoop,todos:b.ralphTodos}),this.renderRalphStatePanel(),this.updateCliInfoDisplay(),this.renderProjectInsightsPanel(),this.updateSubagentWindowVisibility(),this.loadAppSettingsFromStorage().showFileBrowser){const S=this.$("fileBrowserPanel");if(S&&(S.classList.add("visible"),this.loadFileBrowser(e),!this.fileBrowserDragListeners)){const w=S.querySelector(".file-browser-header");if(w){const v=()=>{if(!S.style.left){const E=S.getBoundingClientRect();S.style.left=`${E.left}px`,S.style.top=`${E.top}px`,S.style.right="auto"}};w.addEventListener("mousedown",v),w.addEventListener("touchstart",v,{passive:!0}),this.fileBrowserDragListeners=this.makeWindowDraggable(S,w),this.fileBrowserDragListeners._onFirstDrag=v}}}}),this._connectWs(e),_crashDiag.log("FOCUS"),i&&this.terminal&&this.terminal.focus(),this.scrollToLastNonEmptyLine(),this._clearTerminalLoadState(e,a),_crashDiag.log(`SELECT_DONE: ${(performance.now()-n).toFixed(0)}ms`),console.log(`[CRASH-DIAG] selectSession DONE: ${e.slice(0,8)} in ${(performance.now()-n).toFixed(0)}ms`)}catch(d){this._isLoadingBuffer&&this._finishBufferLoad(h),this._restoringFlushedState=!1,this._setTerminalLoadState(e,a,"failed"),console.error("Failed to load session terminal:",d)}}_cleanupSessionData(e){this._activeRename?.sessionId===e&&this._activeRename.cancel(),this.sessions.delete(e);const t=this.sessionOrder.indexOf(e);t!==-1&&(this.sessionOrder.splice(t,1),this.saveSessionOrder()),this.terminalBuffers.delete(e),this.terminalBufferCache.delete(e),this._flushedOffsets?.delete(e),this._flushedTexts?.delete(e),this._inputQueue.delete(e),this.ralphStates.delete(e),this.ralphClosedSessions.delete(e),this.projectInsights.delete(e),this.pendingHooks.delete(e),this.tabAlerts.delete(e),this.terminalLoadStates.delete(e),this.clearCountdownTimers(e),this.closeSessionLogViewerWindows(e),this.closeSessionImagePopups(e),this.closeSessionSubagentWindows(e,!0);const s=this.idleTimers.get(e);s&&(clearTimeout(s),this.idleTimers.delete(e)),delete this.respawnStatus[e],delete this.respawnTimers[e],delete this.respawnCountdownTimers[e],delete this.respawnActionLogs[e]}async closeSession(e,t=!0){try{if(await this._apiDelete(`/api/sessions/${e}?killMux=${t}`),this._cleanupSessionData(e),this.activeSessionId===e){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}if(this.sessionOrder.length>0&&this.sessions.size>0){const s=this.sessionOrder[0];this.selectSession(s)}else this.terminal.clear(),this.showWelcome(),this.renderRalphStatePanel()}this.renderSessionTabs(),t?this.showToast("Session closed and tmux killed","success"):this.showToast("Tab hidden, tmux still running","info")}catch{this.showToast("Failed to close session","error")}}requestCloseSession(e){const t=this.sessions.get(e);if(!t)return;this.pendingCloseSessionId=e;const s=this.getSessionName(t),i=document.getElementById("closeConfirmSessionName");i.textContent=s;const n=document.getElementById("closeConfirmKillTitle");n&&(n.textContent=t.mode==="opencode"?"Kill Tmux & OpenCode":t.mode==="codex"?"Kill Tmux & Codex":"Kill Tmux & Claude Code"),document.getElementById("closeConfirmModal").classList.add("active")}cancelCloseSession(){this.pendingCloseSessionId=null,document.getElementById("closeConfirmModal").classList.remove("active")}async confirmCloseSession(e=!0){const t=this.pendingCloseSessionId;this.cancelCloseSession(),t&&await this.closeSession(t,e)}nextSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)+1)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}prevSession(){if(this.sessionOrder.length<=1)return;const t=(this.sessionOrder.indexOf(this.activeSessionId)-1+this.sessionOrder.length)%this.sessionOrder.length;this.selectSession(this.sessionOrder[t])}goHome(){this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.terminal.clear(),this.showWelcome(),this.renderSessionTabs(),this.renderRalphStatePanel()}ralphWizardStep=1;ralphWizardConfig={taskDescription:"",completionPhrase:"COMPLETE",maxIterations:10,caseName:"testcase",enableRespawn:!1,generatedPlan:null,planGenerated:!1,skipPlanGeneration:!1,planDetailLevel:"detailed",existingPlan:null,useExistingPlan:!1};planLoadingTimer=null;planLoadingStartTime=null;async killActiveSession(){if(!this.activeSessionId){this.showToast("No active session","warning");return}await this.closeSession(this.activeSessionId)}async killAllSessions(){if(this.sessions.size!==0&&confirm(`Kill all ${this.sessions.size} session(s)?`))try{await this._apiDelete("/api/sessions"),this.sessions.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this.terminalLoadStates.clear(),this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.respawnStatus={},this.respawnCountdownTimers={},this.respawnActionLogs={},this.stopCountdownInterval(),this.hideRespawnBanner(),this.renderSessionTabs(),this.terminal.clear(),this.showWelcome(),this.showToast("All sessions killed","success")}catch{this.showToast("Failed to kill sessions","error")}}showTimer(){document.getElementById("timerBanner").style.display="flex",this.updateTimer(),this.timerInterval=setInterval(()=>this.updateTimer(),1e3)}hideTimer(){document.getElementById("timerBanner").style.display="none",this.timerInterval&&(clearInterval(this.timerInterval),this.timerInterval=null)}updateTimer(){if(!this.currentRun||this.currentRun.status!=="running")return;const e=Date.now(),t=Math.max(0,this.currentRun.endAt-e),s=this.currentRun.endAt-this.currentRun.startedAt,i=e-this.currentRun.startedAt,n=Math.min(100,i/s*100);document.getElementById("timerValue").textContent=this.formatTime(t),document.getElementById("timerProgress").style.width=`${n}%`,document.getElementById("timerMeta").textContent=`${this.currentRun.completedTasks} tasks | $${this.currentRun.totalCost.toFixed(2)}`}async stopCurrentRun(){if(this.currentRun)try{await fetch(`/api/scheduled/${this.currentRun.id}`,{method:"DELETE"})}catch{this.showToast("Failed to stop run","error")}}formatTime(e){const t=Math.floor(e/1e3),s=Math.floor(t/3600),i=Math.floor(t%3600/60),n=t%60;return`${s.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}updateCost(){this.updateTokens()}updateTokens(){this._clearTimer("_updateTokensTimeout"),this._updateTokensTimeout=setTimeout(()=>{this._updateTokensTimeout=null,this._updateTokensImmediate()},200)}_updateTokensImmediate(){let e=0,t=0;this.globalStats?(e=this.globalStats.totalInputTokens||0,t=this.globalStats.totalOutputTokens||0):this.sessions.forEach(a=>{a.tokens&&(e+=a.tokens.input||0,t+=a.tokens.output||0)});const s=e+t;this.totalTokens=s;const i=this.formatTokens(s),n=this.estimateCost(e,t),o=this.$("headerTokens");if(o){const r=this.loadAppSettingsFromStorage().showCost??!1;o.textContent=s>0?r?`${i} tokens \xB7 $${n.toFixed(2)}`:`${i} tokens`:"0 tokens",o.title=this.globalStats?`Lifetime: ${this.globalStats.totalSessionsCreated} sessions created${r?`
34
+ Estimated cost based on Claude Opus pricing`:""}`:`Token usage across active sessions${r?`
35
+ Estimated cost based on Claude Opus pricing`:""}`}}}try{for(let c=0;c<localStorage.length;c++){const e=localStorage.key(c);if(e&&(e.startsWith("claudeman-")||e.startsWith("claudeman_"))){const t=e.replace(/^claudeman[-_]/,s=>"codeman"+s.charAt(s.length-1));localStorage.getItem(t)===null&&localStorage.setItem(t,localStorage.getItem(e))}}}catch{}let app;document.addEventListener("DOMContentLoaded",()=>{app=new CodemanApp,window.app=app}),window.MobileDetection=MobileDetection;
@@ -57,6 +57,7 @@ const TERMINAL_CHUNK_SIZE = 32 * 1024; // 32KB chunks for terminal buffer l
57
57
  const TERMINAL_TAIL_SIZE = 1024 * 1024; // 1MB tail for initial load (more scrollback on tab switch)
58
58
  const SYNC_WAIT_TIMEOUT_MS = 50; // Wait timeout for terminal sync
59
59
  const STATS_POLLING_INTERVAL_MS = 2000; // System stats polling
60
+ const TUI_REDRAW_SETTLE_MS = 400; // Grace for a TUI to redraw after a real resize, before fetching its buffer
60
61
 
61
62
  // Z-index base values for layered floating windows
62
63
  const ZINDEX_SUBAGENT_BASE = 1000;
@@ -149,7 +149,7 @@ Object.assign(CodemanApp.prototype, {
149
149
  }
150
150
 
151
151
  const data = await resp.json();
152
- return data.path;
152
+ return data.data.path;
153
153
  },
154
154
 
155
155
  // Decode an image File through the browser and re-encode it to a format the
@@ -16,16 +16,16 @@
16
16
  <link rel="manifest" href="manifest.json">
17
17
  <title>Codeman</title>
18
18
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
19
- <link rel="stylesheet" href="styles.e87cb785.css">
20
- <link rel="stylesheet" href="mobile.26dc30d6.css" media="(max-width: 1023px)">
19
+ <link rel="stylesheet" href="styles.f3a0faa3.css">
20
+ <link rel="stylesheet" href="mobile.959f6fe2.css" media="(max-width: 1023px)">
21
21
  <!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
22
22
  <link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
23
23
  <noscript><link rel="stylesheet" href="vendor/xterm.css"></noscript>
24
24
  <!-- Preload critical resources — lets browser discover these during HTML parse
25
25
  instead of waiting until <script> tags at bottom-of-body are reached. -->
26
26
  <link rel="preload" href="vendor/xterm.min.js" as="script">
27
- <link rel="preload" href="constants.5b68d2de.js" as="script">
28
- <link rel="preload" href="app.c860ea08.js" as="script">
27
+ <link rel="preload" href="constants.74211deb.js" as="script">
28
+ <link rel="preload" href="app.a8663e79.js" as="script">
29
29
  <!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
30
30
  'defer' preserves execution order (xterm loads before fit addon). -->
31
31
  <script defer src="vendor/xterm.min.js"></script>
@@ -69,6 +69,10 @@
69
69
  <span class="logo" onclick="app.goHome()" title="Go to main page">Codeman</span>
70
70
  </div>
71
71
 
72
+ <button class="mobile-header-utility-toggle" id="mobileHeaderUtilityToggle" type="button" aria-label="Toggle header utilities" aria-controls="headerRight" aria-expanded="false" title="Header utilities">
73
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>
74
+ </button>
75
+
72
76
  <!-- Session Tabs -->
73
77
  <div class="session-tabs" id="sessionTabs" role="tablist" aria-label="Session tabs">
74
78
  </div>
@@ -76,7 +80,7 @@
76
80
  <!-- Detached single-session window title (shown only in solo mode) -->
77
81
  <div class="solo-session-title" id="soloSessionTitle" style="display: none;" aria-live="polite"></div>
78
82
 
79
- <div class="header-right">
83
+ <div class="header-right mobile-collapsed" id="headerRight">
80
84
  <button class="btn-icon-header btn-solo-redock" id="soloRedockBtn" style="display: none;" onclick="window.close()" title="Re-dock to dashboard (close window)" aria-label="Re-dock session to dashboard">&#x229E;</button>
81
85
  <button class="tunnel-indicator" id="tunnelIndicator" style="display: none;" onclick="app.toggleTunnelPanel()" title="Cloudflare Tunnel" aria-label="Tunnel status">
82
86
  <span class="tunnel-dot"></span>
@@ -106,7 +110,7 @@
106
110
  <span class="stat-value" id="statMem">--</span>
107
111
  </div>
108
112
  </div>
109
- <button class="btn-icon-header btn-response-viewer-header" onclick="app.toggleResponseViewer()" title="View last response" aria-label="View last response"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
113
+ <button class="btn-icon-header btn-response-viewer-header btn-response-viewer-header--hidden" onclick="app.toggleResponseViewer()" title="View last response" aria-label="View last response"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
110
114
  <button class="btn-icon-header btn-multimonitor btn-multimonitor--hidden" onclick="app.launchMultiMonitor()" title="Open Codeman across all displays" aria-label="Open Codeman across all displays"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg></button>
111
115
  <button class="btn-icon-header btn-notifications" onclick="app.toggleNotifications()" title="Notifications" aria-label="Toggle notifications" style="display:none;">
112
116
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
@@ -380,6 +384,9 @@
380
384
  <button class="run-mode-option" data-mode="opencode" onclick="app.setRunMode('opencode')">
381
385
  <span class="run-mode-dot opencode"></span>OpenCode
382
386
  </button>
387
+ <button class="run-mode-option" data-mode="codex" onclick="app.setRunMode('codex')">
388
+ <span class="run-mode-dot codex"></span>Codex
389
+ </button>
383
390
  <div class="run-mode-sep"></div>
384
391
  <div class="run-mode-header">Recent Sessions</div>
385
392
  <div class="run-mode-history" id="runModeHistory"></div>
@@ -891,6 +898,7 @@
891
898
  <div class="modal-tabs">
892
899
  <button class="modal-tab-btn active" data-tab="settings-display">Display</button>
893
900
  <button class="modal-tab-btn" data-tab="settings-claude">Claude CLI</button>
901
+ <button class="modal-tab-btn" data-tab="settings-codex">Codex CLI</button>
894
902
  <button class="modal-tab-btn" data-tab="settings-models">Models</button>
895
903
  <button class="modal-tab-btn" data-tab="settings-paths">Paths</button>
896
904
  <button class="modal-tab-btn" data-tab="settings-notifications">Notifications</button>
@@ -981,6 +989,13 @@
981
989
  <span class="slider"></span>
982
990
  </label>
983
991
  </div>
992
+ <div class="settings-item" title="Show the response viewer (eye) button in header">
993
+ <span class="settings-item-label">Response Viewer</span>
994
+ <label class="switch switch-sm">
995
+ <input type="checkbox" id="appSettingsShowResponseViewer">
996
+ <span class="slider"></span>
997
+ </label>
998
+ </div>
984
999
  <div class="settings-item" title="Show the multi-monitor button in the header (opens Codeman spanned across all displays)">
985
1000
  <span class="settings-item-label">Multi-monitor Button</span>
986
1001
  <label class="switch switch-sm">
@@ -1133,13 +1148,26 @@
1133
1148
  </label>
1134
1149
  <span class="form-hint">Enable experimental Agent Teams for all new Claude sessions (disabled by default)</span>
1135
1150
  </div>
1151
+ <div class="form-row">
1152
+ <label>Claude Model</label>
1153
+ <select id="appSettingsClaudeModel" class="form-select">
1154
+ <option value="">Default (CLI setting)</option>
1155
+ <option value="claude-fable-5[1m]">Fable 5 (1M context)</option>
1156
+ <option value="claude-fable-5">Fable 5</option>
1157
+ <option value="opus[1m]">Opus (1M context)</option>
1158
+ <option value="opus">Opus</option>
1159
+ <option value="sonnet">Sonnet</option>
1160
+ <option value="haiku">Haiku</option>
1161
+ </select>
1162
+ <span class="form-hint">Model for new Claude sessions (pinned via the case's .claude/settings.local.json) — takes precedence over the 1M Opus toggle below</span>
1163
+ </div>
1136
1164
  <div class="form-row form-row-switch">
1137
1165
  <label>1M Opus Context</label>
1138
1166
  <label class="switch">
1139
1167
  <input type="checkbox" id="appSettingsOpusContext1m">
1140
1168
  <span class="slider"></span>
1141
1169
  </label>
1142
- <span class="form-hint">Use 1M token context window (model: opus[1m]) for all new sessions</span>
1170
+ <span class="form-hint">Use 1M token context window (model: opus[1m]) for all new sessions — ignored when a Claude Model is selected above</span>
1143
1171
  </div>
1144
1172
  <div class="form-row">
1145
1173
  <label>Thinking Effort</label>
@@ -1170,12 +1198,25 @@
1170
1198
  <span class="form-hint">Process priority (-20 to 19, higher = lower priority, default: 10)</span>
1171
1199
  </div>
1172
1200
  </div>
1201
+ <!-- Codex CLI Tab -->
1202
+ <div class="modal-tab-content hidden" id="settings-codex">
1203
+ <div class="form-section-header">Codex CLI</div>
1204
+ <div class="form-row form-row-switch">
1205
+ <label>Bypass Approvals and Sandbox</label>
1206
+ <label class="switch">
1207
+ <input type="checkbox" id="appSettingsCodexDangerouslyBypassApprovals">
1208
+ <span class="slider"></span>
1209
+ </label>
1210
+ <span class="form-hint">Start new Codex sessions with --dangerously-bypass-approvals-and-sandbox</span>
1211
+ </div>
1212
+ </div>
1173
1213
  <!-- Models Tab -->
1174
1214
  <div class="modal-tab-content hidden" id="settings-models">
1175
1215
  <div class="form-row">
1176
1216
  <label>Default Model</label>
1177
1217
  <select id="appSettingsDefaultModel" class="form-select">
1178
1218
  <option value="">Default (CLI default)</option>
1219
+ <option value="claude-fable-5">Fable 5 (Most powerful)</option>
1179
1220
  <option value="opus">Opus (Most capable)</option>
1180
1221
  <option value="sonnet">Sonnet (Balanced)</option>
1181
1222
  <option value="haiku">Haiku (Fast & cheap)</option>
@@ -1199,6 +1240,7 @@
1199
1240
  <option value="haiku">Haiku</option>
1200
1241
  <option value="sonnet">Sonnet</option>
1201
1242
  <option value="opus">Opus</option>
1243
+ <option value="claude-fable-5">Fable 5</option>
1202
1244
  </select>
1203
1245
  <span class="form-hint">Quick searches, codebase exploration</span>
1204
1246
  </div>
@@ -1209,6 +1251,7 @@
1209
1251
  <option value="haiku">Haiku</option>
1210
1252
  <option value="sonnet">Sonnet</option>
1211
1253
  <option value="opus">Opus</option>
1254
+ <option value="claude-fable-5">Fable 5</option>
1212
1255
  </select>
1213
1256
  <span class="form-hint">Code writing, feature implementation</span>
1214
1257
  </div>
@@ -1219,6 +1262,7 @@
1219
1262
  <option value="haiku">Haiku</option>
1220
1263
  <option value="sonnet">Sonnet</option>
1221
1264
  <option value="opus">Opus</option>
1265
+ <option value="claude-fable-5">Fable 5</option>
1222
1266
  </select>
1223
1267
  <span class="form-hint">Writing and running tests</span>
1224
1268
  </div>
@@ -1229,6 +1273,7 @@
1229
1273
  <option value="haiku">Haiku</option>
1230
1274
  <option value="sonnet">Sonnet</option>
1231
1275
  <option value="opus">Opus</option>
1276
+ <option value="claude-fable-5">Fable 5</option>
1232
1277
  </select>
1233
1278
  <span class="form-hint">Code review, quality checks</span>
1234
1279
  </div>
@@ -1834,23 +1879,23 @@
1834
1879
  <!-- Lines drawn dynamically -->
1835
1880
  </svg>
1836
1881
 
1837
- <script defer src="constants.5b68d2de.js"></script>
1838
- <script defer src="mobile-handlers.1e2a8ef8.js"></script>
1882
+ <script defer src="constants.74211deb.js"></script>
1883
+ <script defer src="mobile-handlers.d54d97d6.js"></script>
1839
1884
  <script defer src="voice-input.085e9e73.js"></script>
1840
1885
  <script defer src="notification-manager.9c984ac2.js"></script>
1841
- <script defer src="keyboard-accessory.cdfd8c04.js"></script>
1842
- <script defer src="input-cjk.88082175.js"></script>
1843
- <script defer src="app.c860ea08.js"></script>
1844
- <script defer src="terminal-ui.37caa926.js"></script>
1845
- <script defer src="respawn-ui.5377f958.js"></script>
1846
- <script defer src="ralph-panel.61076370.js"></script>
1886
+ <script defer src="keyboard-accessory.bc753cc7.js"></script>
1887
+ <script defer src="input-cjk.b8686b5e.js"></script>
1888
+ <script defer src="app.a8663e79.js"></script>
1889
+ <script defer src="terminal-ui.6ce91b0b.js"></script>
1890
+ <script defer src="respawn-ui.2d249da9.js"></script>
1891
+ <script defer src="ralph-panel.6de2d0f8.js"></script>
1847
1892
  <script defer src="orchestrator-panel.js"></script>
1848
- <script defer src="settings-ui.2b70e2c8.js"></script>
1849
- <script defer src="panels-ui.5192a2c0.js"></script>
1850
- <script defer src="session-ui.3e0cf024.js"></script>
1851
- <script defer src="ralph-wizard.52d533d2.js"></script>
1852
- <script defer src="api-client.3adebdc2.js"></script>
1893
+ <script defer src="settings-ui.21b009ca.js"></script>
1894
+ <script defer src="panels-ui.6bb3169f.js"></script>
1895
+ <script defer src="session-ui.512816d8.js"></script>
1896
+ <script defer src="ralph-wizard.a6b2d36b.js"></script>
1897
+ <script defer src="api-client.c9b1cddc.js"></script>
1853
1898
  <script defer src="subagent-windows.a366a4ad.js"></script>
1854
- <script defer src="image-input.7cade6a8.js"></script>
1899
+ <script defer src="image-input.0ea86695.js"></script>
1855
1900
  </body>
1856
1901
  </html>
Binary file
Binary file
@@ -0,0 +1 @@
1
+ "use strict";const CjkInput=(()=>{let e=null,c=null,s=null,r=null,p=!1,u=!1;const n={},l="\u200B",d={ArrowUp:"\x1B[A",ArrowDown:"\x1B[B",ArrowLeft:"\x1B[D",ArrowRight:"\x1B[C",Home:"\x1B[H",End:"\x1B[F",Tab:" "},k={c:"",d:"",l:"\f",z:"",a:"",e:""};function v(i){return i.replace(/\u200B/g,"")}function f(){e.value=l,e.setSelectionRange(1,1)}function o(){return!!(e&&typeof MobileDetection<"u"&&MobileDetection.isTouchDevice()&&e.classList.contains("cjk-input-visible"))}function y(){o()?e.value="":f()}function a(){return!v(e.value)}function m(){const i=v(e.value);i&&r(i),f()}return{init({send:i}){return p&&this.destroy(),r=i,u=!1,e=document.getElementById("cjkInput"),e?(c=document.getElementById("terminalContainer"),y(),n.mousedown=t=>{t.stopPropagation()},n.focus=()=>{if(window.cjkActive=!0,o()&&e.value===l){e.value="";return}!e.value&&!o()&&f()},n.blur=()=>{window.cjkActive=!1},e.addEventListener("mousedown",n.mousedown),e.addEventListener("focus",n.focus),e.addEventListener("blur",n.blur),n.xtermFocusRedirect=()=>{o()&&e.focus()},c&&(s=c.querySelector(".xterm-helper-textarea"),s&&s.addEventListener("focus",n.xtermFocusRedirect,{capture:!0})),n.compositionstart=()=>{if(u=!0,o()){e.value===l&&(e.value="");return}e.value===l&&(e.value="")},n.compositionend=()=>{u=!1,!o()&&setTimeout(m,0)},e.addEventListener("compositionstart",n.compositionstart),e.addEventListener("compositionend",n.compositionend),n.keydown=t=>{if(t.key==="Enter"){t.preventDefault(),u=!1;const x=v(e.value);r(x?x+"\r":"\r"),y();return}if(t.key==="Escape"){t.preventDefault(),u=!1,y();return}if(t.ctrlKey&&k[t.key]){t.preventDefault(),r(k[t.key]);return}if(!u){if(o()){if(t.key==="Backspace"&&a()){t.preventDefault(),r("\x7F");return}d[t.key]&&a()&&(t.preventDefault(),r(d[t.key]));return}if(t.key==="Backspace"&&a()){t.preventDefault(),r("\x7F"),f();return}if(d[t.key]&&a()){t.preventDefault(),r(d[t.key]);return}if(t.key.length===1&&!t.ctrlKey&&!t.altKey&&!t.metaKey&&a()){t.preventDefault(),r(t.key);return}}},e.addEventListener("keydown",n.keydown),n.input=t=>{if(o()){e.value.includes(l)&&(e.value=v(e.value));return}if(t.inputType==="deleteContentBackward"||t.inputType==="deleteWordBackward"){if(a()){r("\x7F"),f();return}e.value.startsWith(l)||(e.value=l+e.value,e.setSelectionRange(1,1));return}if(u){if(t.inputType==="insertText"){m();return}return}m()},e.addEventListener("input",n.input),p=!0,this):this},destroy(){if(e)for(const[i,t]of Object.entries(n))t&&e.removeEventListener(i,t);s&&n.xtermFocusRedirect&&s.removeEventListener("focus",n.xtermFocusRedirect,{capture:!0}),window.cjkActive=!1,u=!1,c=null,s=null;for(const i of Object.keys(n))delete n[i];p=!1},get element(){return e}}})();
@@ -4,7 +4,7 @@
4
4
  * Defines two exports:
5
5
  *
6
6
  * - KeyboardAccessoryBar (singleton object) — Quick action buttons shown above the virtual
7
- * keyboard on mobile: arrow up/down, /init, /clear, paste, and dismiss.
7
+ * keyboard on mobile: arrow up/down, /init, /clear, paste, Esc, and dismiss.
8
8
  * The paste button opens a dialog that handles both text paste and image attach
9
9
  * (native picker + best-effort image paste, routed through app._uploadAndInsertImages).
10
10
  * Destructive actions (/clear) require double-tap confirmation (2s amber state).
@@ -37,7 +37,7 @@ const KeyboardAccessoryBar = {
37
37
  element: null,
38
38
  _mode: 'simple', // 'simple' or 'extended'
39
39
 
40
- /** HTML for simple mode: arrows, commands, paste, dismiss */
40
+ /** HTML for simple mode: arrows, commands, paste, Esc, dismiss */
41
41
  _simpleButtons: `
42
42
  <button class="accessory-btn accessory-btn-arrow" data-action="scroll-up" title="Arrow up">
43
43
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
@@ -57,6 +57,7 @@ const KeyboardAccessoryBar = {
57
57
  <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
58
58
  </svg>
59
59
  </button>
60
+ <button class="accessory-btn" data-action="esc" title="Escape">Esc</button>
60
61
  <button class="accessory-btn accessory-btn-dismiss" data-action="dismiss" title="Dismiss keyboard">
61
62
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
62
63
  <path d="M19 9l-7 7-7-7"/>