aicodeman 1.33.2 → 1.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/dist/config/cli-registry/schema.d.ts +18 -0
  2. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  3. package/dist/config/cli-registry/schema.js +27 -0
  4. package/dist/config/cli-registry/schema.js.map +1 -1
  5. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  6. package/dist/config/cli-registry/stock.js +35 -0
  7. package/dist/config/cli-registry/stock.js.map +1 -1
  8. package/dist/config/cli-registry/types.d.ts +33 -0
  9. package/dist/config/cli-registry/types.d.ts.map +1 -1
  10. package/dist/hooks-config.d.ts.map +1 -1
  11. package/dist/hooks-config.js +5 -1
  12. package/dist/hooks-config.js.map +1 -1
  13. package/dist/mcp-sync.d.ts +105 -0
  14. package/dist/mcp-sync.d.ts.map +1 -0
  15. package/dist/mcp-sync.js +619 -0
  16. package/dist/mcp-sync.js.map +1 -0
  17. package/dist/mux-interface.d.ts +4 -0
  18. package/dist/mux-interface.d.ts.map +1 -1
  19. package/dist/session-cli-builder.d.ts +20 -1
  20. package/dist/session-cli-builder.d.ts.map +1 -1
  21. package/dist/session-cli-builder.js +33 -3
  22. package/dist/session-cli-builder.js.map +1 -1
  23. package/dist/session-cli-registry-bridge.d.ts +2 -0
  24. package/dist/session-cli-registry-bridge.d.ts.map +1 -1
  25. package/dist/session-cli-registry-bridge.js +15 -8
  26. package/dist/session-cli-registry-bridge.js.map +1 -1
  27. package/dist/session.d.ts +19 -0
  28. package/dist/session.d.ts.map +1 -1
  29. package/dist/session.js +29 -2
  30. package/dist/session.js.map +1 -1
  31. package/dist/tmux-manager.d.ts +2 -0
  32. package/dist/tmux-manager.d.ts.map +1 -1
  33. package/dist/tmux-manager.js +4 -2
  34. package/dist/tmux-manager.js.map +1 -1
  35. package/dist/types/index.d.ts +3 -1
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/dist/types/index.js +3 -1
  38. package/dist/types/index.js.map +1 -1
  39. package/dist/types/mcp-sync.d.ts +40 -0
  40. package/dist/types/mcp-sync.d.ts.map +1 -0
  41. package/dist/types/mcp-sync.js +8 -0
  42. package/dist/types/mcp-sync.js.map +1 -0
  43. package/dist/types/push.d.ts +37 -2
  44. package/dist/types/push.d.ts.map +1 -1
  45. package/dist/types/push.js +10 -3
  46. package/dist/types/push.js.map +1 -1
  47. package/dist/types/session.d.ts +34 -1
  48. package/dist/types/session.d.ts.map +1 -1
  49. package/dist/types/session.js +25 -1
  50. package/dist/types/session.js.map +1 -1
  51. package/dist/web/public/admin-ui.js.gz +0 -0
  52. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  53. package/dist/web/public/app.cf0db8ce.js +43 -0
  54. package/dist/web/public/app.cf0db8ce.js.br +0 -0
  55. package/dist/web/public/app.cf0db8ce.js.gz +0 -0
  56. package/dist/web/public/approvals-ui.js.gz +0 -0
  57. package/dist/web/public/{constants.cc927eb8.js → constants.d95fade9.js} +19 -2
  58. package/dist/web/public/constants.d95fade9.js.br +0 -0
  59. package/dist/web/public/constants.d95fade9.js.gz +0 -0
  60. package/dist/web/public/cron-ui.js.gz +0 -0
  61. package/dist/web/public/entrance-animations.js.gz +0 -0
  62. package/dist/web/public/home-sessions.js.gz +0 -0
  63. package/dist/web/public/host-wake-ui.js.gz +0 -0
  64. package/dist/web/public/i18n.398b8331.js +1 -0
  65. package/dist/web/public/i18n.398b8331.js.br +0 -0
  66. package/dist/web/public/i18n.398b8331.js.gz +0 -0
  67. package/dist/web/public/image-input.a216cf9e.js.gz +0 -0
  68. package/dist/web/public/index.html +125 -14
  69. package/dist/web/public/index.html.br +0 -0
  70. package/dist/web/public/index.html.gz +0 -0
  71. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  72. package/dist/web/public/keyboard-accessory.c1e7c7b7.js.gz +0 -0
  73. package/dist/web/public/mobile-handlers.23a85db1.js.gz +0 -0
  74. package/dist/web/public/mobile-ime-preview.2dbaf262.js +1 -0
  75. package/dist/web/public/mobile-ime-preview.2dbaf262.js.br +0 -0
  76. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  77. package/dist/web/public/mobile-overview.js.gz +0 -0
  78. package/dist/web/public/mobile.1aa4952d.css.gz +0 -0
  79. package/dist/web/public/notification-manager.50bb87d8.js.gz +0 -0
  80. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  81. package/dist/web/public/{panels-ui.67646701.js → panels-ui.6a5898d9.js} +69 -67
  82. package/dist/web/public/panels-ui.6a5898d9.js.br +0 -0
  83. package/dist/web/public/panels-ui.6a5898d9.js.gz +0 -0
  84. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  85. package/dist/web/public/{ralph-wizard.13a1831e.js → ralph-wizard.b6c0dd19.js} +2 -0
  86. package/dist/web/public/ralph-wizard.b6c0dd19.js.br +0 -0
  87. package/dist/web/public/{ralph-wizard.13a1831e.js.gz → ralph-wizard.b6c0dd19.js.gz} +0 -0
  88. package/dist/web/public/readmymind-ui.js.gz +0 -0
  89. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  90. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  91. package/dist/web/public/sanitize-html.e7147e76.js +1 -0
  92. package/dist/web/public/sanitize-html.e7147e76.js.br +0 -0
  93. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  94. package/dist/web/public/session-lineage.js +27 -11
  95. package/dist/web/public/session-lineage.js.br +0 -0
  96. package/dist/web/public/session-lineage.js.gz +0 -0
  97. package/dist/web/public/{session-ui.7575a231.js → session-ui.bf66c13b.js} +4 -4
  98. package/dist/web/public/session-ui.bf66c13b.js.br +0 -0
  99. package/dist/web/public/session-ui.bf66c13b.js.gz +0 -0
  100. package/dist/web/public/{settings-ui.c68e7f59.js → settings-ui.331d3b1d.js} +12 -10
  101. package/dist/web/public/settings-ui.331d3b1d.js.br +0 -0
  102. package/dist/web/public/settings-ui.331d3b1d.js.gz +0 -0
  103. package/dist/web/public/styles.8c485d26.css +1 -0
  104. package/dist/web/public/styles.8c485d26.css.br +0 -0
  105. package/dist/web/public/styles.8c485d26.css.gz +0 -0
  106. package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
  107. package/dist/web/public/sw.js +2 -2
  108. package/dist/web/public/sw.js.br +0 -0
  109. package/dist/web/public/sw.js.gz +0 -0
  110. package/dist/web/public/tab-layout-browser.4e0ed50c.js +1 -0
  111. package/dist/web/public/tab-layout-browser.4e0ed50c.js.br +0 -0
  112. package/dist/web/public/tab-layout-browser.4e0ed50c.js.gz +0 -0
  113. package/dist/web/public/tab-rail-resize.dcdc5772.js.gz +0 -0
  114. package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
  115. package/dist/web/public/terminal-split.js +248 -15
  116. package/dist/web/public/terminal-split.js.br +0 -0
  117. package/dist/web/public/terminal-split.js.gz +0 -0
  118. package/dist/web/public/terminal-ui.a4535311.js +2 -0
  119. package/dist/web/public/terminal-ui.a4535311.js.br +0 -0
  120. package/dist/web/public/terminal-ui.a4535311.js.gz +0 -0
  121. package/dist/web/public/ultracode-panel.js.gz +0 -0
  122. package/dist/web/public/ultracode-windows.js.gz +0 -0
  123. package/dist/web/public/upload.html.gz +0 -0
  124. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  125. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  126. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  127. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  128. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  129. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  130. package/dist/web/public/vendor/{xterm-predictive-echo.bd6882b8.js → xterm-predictive-echo.ae5e3d5d.js} +1 -1
  131. package/dist/web/public/vendor/{xterm-predictive-echo.bd6882b8.js.br → xterm-predictive-echo.ae5e3d5d.js.br} +0 -0
  132. package/dist/web/public/vendor/{xterm-predictive-echo.bd6882b8.js.gz → xterm-predictive-echo.ae5e3d5d.js.gz} +0 -0
  133. package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js +4 -0
  134. package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.br +0 -0
  135. package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.gz +0 -0
  136. package/dist/web/public/vendor/xterm.css.gz +0 -0
  137. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  138. package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
  139. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  140. package/dist/web/public/webview-tabs.js +36 -14
  141. package/dist/web/public/webview-tabs.js.br +0 -0
  142. package/dist/web/public/webview-tabs.js.gz +0 -0
  143. package/dist/web/reboot-restore-registry.d.ts +2 -1
  144. package/dist/web/reboot-restore-registry.d.ts.map +1 -1
  145. package/dist/web/reboot-restore-registry.js +2 -1
  146. package/dist/web/reboot-restore-registry.js.map +1 -1
  147. package/dist/web/routes/file-routes.d.ts.map +1 -1
  148. package/dist/web/routes/file-routes.js +2 -1
  149. package/dist/web/routes/file-routes.js.map +1 -1
  150. package/dist/web/routes/index.d.ts +2 -0
  151. package/dist/web/routes/index.d.ts.map +1 -1
  152. package/dist/web/routes/index.js +2 -0
  153. package/dist/web/routes/index.js.map +1 -1
  154. package/dist/web/routes/mcp-sync-routes.d.ts +29 -0
  155. package/dist/web/routes/mcp-sync-routes.d.ts.map +1 -0
  156. package/dist/web/routes/mcp-sync-routes.js +83 -0
  157. package/dist/web/routes/mcp-sync-routes.js.map +1 -0
  158. package/dist/web/routes/ralph-routes.d.ts.map +1 -1
  159. package/dist/web/routes/ralph-routes.js +2 -1
  160. package/dist/web/routes/ralph-routes.js.map +1 -1
  161. package/dist/web/routes/reboot-restore-routes.d.ts.map +1 -1
  162. package/dist/web/routes/reboot-restore-routes.js +2 -0
  163. package/dist/web/routes/reboot-restore-routes.js.map +1 -1
  164. package/dist/web/routes/session-routes.d.ts.map +1 -1
  165. package/dist/web/routes/session-routes.js +40 -17
  166. package/dist/web/routes/session-routes.js.map +1 -1
  167. package/dist/web/routes/webhook-routes.d.ts +21 -0
  168. package/dist/web/routes/webhook-routes.d.ts.map +1 -0
  169. package/dist/web/routes/webhook-routes.js +87 -0
  170. package/dist/web/routes/webhook-routes.js.map +1 -0
  171. package/dist/web/schemas.d.ts +47 -2
  172. package/dist/web/schemas.d.ts.map +1 -1
  173. package/dist/web/schemas.js +64 -0
  174. package/dist/web/schemas.js.map +1 -1
  175. package/dist/web/server.d.ts +4 -0
  176. package/dist/web/server.d.ts.map +1 -1
  177. package/dist/web/server.js +55 -25
  178. package/dist/web/server.js.map +1 -1
  179. package/dist/webhook-notify.d.ts +71 -0
  180. package/dist/webhook-notify.d.ts.map +1 -0
  181. package/dist/webhook-notify.js +282 -0
  182. package/dist/webhook-notify.js.map +1 -0
  183. package/package.json +2 -1
  184. package/skills/codeman/SKILL.md +26 -8
  185. package/skills/codeman/preamble.sh +16 -4
  186. package/skills/codeman/reference/endpoints.md +11 -3
  187. package/skills/codeman/reference/recipes.md +1 -1
  188. package/dist/web/public/app.91131d2c.js +0 -43
  189. package/dist/web/public/app.91131d2c.js.br +0 -0
  190. package/dist/web/public/app.91131d2c.js.gz +0 -0
  191. package/dist/web/public/constants.cc927eb8.js.br +0 -0
  192. package/dist/web/public/constants.cc927eb8.js.gz +0 -0
  193. package/dist/web/public/i18n.f094d01f.js +0 -1
  194. package/dist/web/public/i18n.f094d01f.js.br +0 -0
  195. package/dist/web/public/i18n.f094d01f.js.gz +0 -0
  196. package/dist/web/public/panels-ui.67646701.js.br +0 -0
  197. package/dist/web/public/panels-ui.67646701.js.gz +0 -0
  198. package/dist/web/public/ralph-wizard.13a1831e.js.br +0 -0
  199. package/dist/web/public/sanitize-html.bc7078d6.js +0 -1
  200. package/dist/web/public/sanitize-html.bc7078d6.js.br +0 -0
  201. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  202. package/dist/web/public/session-ui.7575a231.js.br +0 -0
  203. package/dist/web/public/session-ui.7575a231.js.gz +0 -0
  204. package/dist/web/public/settings-ui.c68e7f59.js.br +0 -0
  205. package/dist/web/public/settings-ui.c68e7f59.js.gz +0 -0
  206. package/dist/web/public/styles.3fe3312d.css +0 -1
  207. package/dist/web/public/styles.3fe3312d.css.br +0 -0
  208. package/dist/web/public/styles.3fe3312d.css.gz +0 -0
  209. package/dist/web/public/terminal-ui.d5223c0e.js +0 -2
  210. package/dist/web/public/terminal-ui.d5223c0e.js.br +0 -0
  211. package/dist/web/public/terminal-ui.d5223c0e.js.gz +0 -0
  212. package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js +0 -4
  213. package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.br +0 -0
  214. package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
@@ -26,7 +26,7 @@
26
26
  // not the current one, so the old constant key meant that cleanup never deleted
27
27
  // anything — hashed assets from every release ever deployed accumulated in one
28
28
  // bucket until the origin hit its storage quota.
29
- const BUILD_ID = '403212f981d2';
29
+ const BUILD_ID = '5b04d0ba6d2e';
30
30
  const CACHE_NAME = `codeman-${BUILD_ID}`;
31
31
 
32
32
  // Reverse-proxy base path: the worker is served at `<base>/sw.js`, so its own
@@ -48,7 +48,7 @@ const B = (p) => (p && p[0] === '/' ? SW_BASE + p : p);
48
48
  // handler caches every successful GET at runtime, but the precache warmed
49
49
  // nothing while looking like it did. Deriving it from the same manifest that
50
50
  // renames the files is the only thing that keeps the two from drifting again.
51
- const HASHED_ASSETS = ["styles.3fe3312d.css", "mobile.1aa4952d.css", "constants.cc927eb8.js", "i18n.f094d01f.js", "mobile-handlers.23a85db1.js", "voice-input.c4b51eb6.js", "notification-manager.50bb87d8.js", "keyboard-accessory.c1e7c7b7.js", "input-cjk.8bc46081.js", "terminal-keycode229-recovery.fb91b25b.js", "sanitize-html.bc7078d6.js", "app.91131d2c.js", "tab-rail-resize.dcdc5772.js", "terminal-ui.d5223c0e.js", "respawn-ui.ff0dae4c.js", "ralph-panel.b350e2ef.js", "settings-ui.c68e7f59.js", "panels-ui.67646701.js", "session-ui.7575a231.js", "ralph-wizard.13a1831e.js", "api-client.c9b1cddc.js", "subagent-windows.e6ca799f.js", "image-input.a216cf9e.js", "vendor/xterm-zerolag-input.6fee72f2.js", "vendor/xterm-predictive-echo.bd6882b8.js"];
51
+ const HASHED_ASSETS = ["styles.8c485d26.css", "mobile.1aa4952d.css", "constants.d95fade9.js", "i18n.398b8331.js", "mobile-handlers.23a85db1.js", "voice-input.c4b51eb6.js", "notification-manager.50bb87d8.js", "keyboard-accessory.c1e7c7b7.js", "input-cjk.8bc46081.js", "mobile-ime-preview.2dbaf262.js", "terminal-keycode229-recovery.fb91b25b.js", "sanitize-html.e7147e76.js", "tab-layout-browser.4e0ed50c.js", "app.cf0db8ce.js", "tab-rail-resize.dcdc5772.js", "terminal-ui.a4535311.js", "respawn-ui.ff0dae4c.js", "ralph-panel.b350e2ef.js", "settings-ui.331d3b1d.js", "panels-ui.6a5898d9.js", "session-ui.bf66c13b.js", "ralph-wizard.b6c0dd19.js", "api-client.c9b1cddc.js", "subagent-windows.e6ca799f.js", "image-input.a216cf9e.js", "vendor/xterm-zerolag-input.bbb5f84c.js", "vendor/xterm-predictive-echo.ae5e3d5d.js"];
52
52
 
53
53
  // Core app shell -- cached on install for instant startup
54
54
  const APP_SHELL = [
Binary file
Binary file
@@ -0,0 +1 @@
1
+ "use strict";(function(T){"use strict";const A="codeman:tab-groups-collapsed",$=e=>`${e.kind}:${e.id}`,O=e=>!!e&&(e.kind==="session"||e.kind==="webview")&&typeof e.id=="string"&&e.id.length>0,C=e=>Array.isArray(e)?e.filter(t=>typeof t=="string"&&t):[],f=e=>[...new Set(C(e))],G=e=>Array.isArray(e)?e.filter(O).map(t=>({kind:t.kind,id:t.id})):[];function L(e){if(!e||typeof e!="object")throw new Error("Invalid tab layout");const t=Array.isArray(e.groups)?e.groups:[];return{version:Number.isSafeInteger(e.version)&&e.version>=0?e.version:0,groups:t.filter(n=>n&&typeof n.id=="string"&&n.id.length>0).map(n=>({id:n.id,name:typeof n.name=="string"?n.name:"",refs:G(n.refs)})),ungrouped:G(e.ungrouped)}}function N(e){return!!e&&Array.isArray(e.groups)&&e.groups.length>0}function E(e){if(e===null)return[];try{const t=JSON.parse(e);return Array.isArray(t)?f(t):null}catch{return null}}function M(e,t){try{const n=E(e.getItem(A)),a=n||[];if(t===void 0)return{ids:a,ok:!0};const s=new Set(f(t)),r=a.filter(i=>s.has(i));return(!n||r.length!==a.length)&&e.setItem(A,JSON.stringify(r)),{ids:r,ok:!0}}catch{return{ids:[],ok:!1}}}function J(e,t){const n=f(t);try{return e.setItem(A,JSON.stringify(n)),{ids:n,ok:!0}}catch{return{ids:[],ok:!1}}}function K(e,t={}){if(!e)return null;const n=L(e);if(!N(n))return null;const a=f(t.liveSessionIds),s=f(t.openWebviewIds),r=new Set(a),i=new Set(s),d=new Set(C(t.collapsedGroupIds)),c=t.activeWebviewId?`webview:${t.activeWebviewId}`:t.activeSessionId?`session:${t.activeSessionId}`:"",p=o=>o.kind==="session"?r.has(o.id):i.has(o.id),u=new Set,l=[],y={},h={},I=[],_=(o,g,R)=>{const m=[],w=[];let D=0;for(const v of o){const b=$(v);if(u.has(b)||!p(v))continue;u.add(b),h[b]=g,D++;const x={kind:v.kind,id:v.id};if(R&&b!==c){y[b]=g,w.push(x);continue}m.push(x),l.push(x)}return{shown:m,hidden:w,count:D}};for(const o of n.groups){const g=d.has(o.id),{shown:R,hidden:m,count:w}=_(o.refs,o.id,g);I.push({id:o.id,name:o.name,refs:R,hidden:m,count:w,collapsed:g})}const q=[...a.map(o=>({kind:"session",id:o})),...s.map(o=>({kind:"webview",id:o}))],S=_([...n.ungrouped,...q],null,!1);return S.count>0&&I.push({id:null,name:"",refs:S.shown,hidden:[],count:S.count,collapsed:!1}),{sections:I,visibleRefs:l,hiddenTabGroupByRef:y,sectionByRef:h}}const k={action:2,idle:1};function W(e,t){const n={},a=e&&Array.isArray(e.sections)?e.sections:[];for(const s of a){if(s.id===null||!Array.isArray(s.hidden))continue;let r=null;for(const i of s.hidden){if(i.kind!=="session")continue;const d=t(i.id);k[d]&&(!r||k[d]>k[r])&&(r=d)}r&&(n[s.id]=r)}return n}function F(e,t,n){return t?JSON.stringify({collapsed:f(n).sort(),sections:t.sections.map(a=>[a.id,a.name,a.count,a.refs.map($),(a.hidden||[]).map($)])}):null}function B(e,t,n){return(e&&Array.isArray(e.sections)?e.sections:[]).map((s,r)=>{const i=s.refs.map(h=>t(h)).join("");if(s.id===null)return`<section class="tab-layout-group tab-layout-ungrouped" role="presentation" data-tab-group-id=""><div class="tab-layout-group-header tab-layout-ungrouped-header" aria-hidden="true"><span class="tab-layout-group-name">Ungrouped</span><span class="tab-layout-group-count">${s.count}</span></div><div class="tab-layout-group-refs" role="presentation">${i}</div></section>`;const d=n(s.id),c=`tab-layout-group-refs-${r}`,p=`tab-layout-group-name-${r}`,u=s.count===0,l=!s.collapsed&&!u,y=u?"":` aria-expanded="${l?"true":"false"}"`;return`<section class="tab-layout-group${s.collapsed?" tab-layout-group--collapsed":""}" role="presentation" data-tab-group-id="${d}"><div class="tab-layout-group-header tab-layout-group-toggle" role="treeitem" tabindex="-1" data-tab-group-header="${d}"${y}${l?` aria-owns="${c}"`:""} onclick="app.toggleTabGroupCollapsed(this.dataset.tabGroupHeader)"><span class="tab-layout-group-chevron" aria-hidden="true"></span><span class="tab-layout-group-name" id="${p}" data-i18n-skip>${n(s.name)}</span><span class="tab-layout-group-count">${s.count}</span></div><div class="tab-layout-group-refs" id="${c}" ${l?`role="group" aria-labelledby="${p}"`:'role="presentation"'}>${i}</div></section>`}).join("")}function P(e){const t=Number.isFinite(e.retryDelayMs)?e.retryDelayMs:5e3,n=Number.isFinite(e.maxRetryDelayMs)?e.maxRetryDelayMs:6e4,a=Number.isSafeInteger(e.maxRetries)?e.maxRetries:4;let s=0,r=!1,i=null,d=0;const c=()=>{i!==null&&e.cancelRetry&&e.cancelRetry(i),i=null},p=async()=>{if(r)return!1;const u=++s;c();try{const l=await e.fetchLayout();return r||u!==s?!1:(d=0,e.applyLayout(l),!0)}catch{if(r||u!==s)return!1;if(d++,e.applyFallback(),d<=a){const y=Math.min(n,t*2**(d-1));i=e.scheduleRetry(()=>p(),y)}return!1}};return{load:p,dispose(){r=!0,s++,c()}}}T.CodemanTabLayout={normalizeLayout:L,hasGroups:N,project:K,hiddenGroupAlerts:W,structureKey:F,renderProjection:B,createLoadCoordinator:P,loadCollapsedGroupIds:M,saveCollapsedGroupIds:J}})(typeof window<"u"?window:globalThis);
@@ -14,6 +14,9 @@
14
14
  */
15
15
 
16
16
  (function (global) {
17
+ // How long a scroll-to-top history pull may hold Pane B's live output.
18
+ const HISTORY_PULL_TIMEOUT_MS = 10000;
19
+
17
20
  /**
18
21
  * Minimal chunked write for Pane B's own xterm instance — write() in
19
22
  * TERMINAL_CHUNK_SIZE slices, yielding a frame between each, instead of one
@@ -68,10 +71,21 @@
68
71
  this.fitAddon = null;
69
72
  this.ws = null;
70
73
  this._wsReady = false;
74
+ this._wsClosed = false;
71
75
  this._destroyed = false;
72
76
  // Single-flight state for _loadBuffer()/_refreshBuffer() below.
73
77
  this._bufferLoading = false;
74
78
  this._bufferRefreshPending = false;
79
+ // Scroll-to-top history pull (shell panes only), see _maybeLoadMoreHistory().
80
+ // `_liveQueue` is non-null from the pull's response until its finally
81
+ // block: live frames are held there with their arrival time instead of
82
+ // written under the replay. `_markerOwed` is the "disconnected" marker a
83
+ // load still has to write (see _onSocketClosed()/_stampMarkerIfOwed()).
84
+ this._historyPullAt = 0;
85
+ this._historyPullUseless = false;
86
+ this._liveQueue = null;
87
+ this._markerOwed = false;
88
+ this._onWheel = null;
75
89
  }
76
90
 
77
91
  async connect() {
@@ -95,6 +109,8 @@
95
109
  this.terminal.open(this.mountEl);
96
110
  this.fitAddon.fit();
97
111
 
112
+ this._installWheelListener();
113
+
98
114
  this.terminal.onData((data) => {
99
115
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
100
116
  this.ws.send(JSON.stringify({ t: 'i', d: data }));
@@ -156,14 +172,17 @@
156
172
  // session (this.sessionId), never the primary pane's
157
173
  // activeSessionId, and has no local-echo overlay of its own to flush
158
174
  // first (Pane B is deliberately plainer — see the fileoverview).
159
- if (ev.key === 'Enter' && (ev.shiftKey || ev.ctrlKey) && ev.type === 'keydown') {
160
- fetch(`/api/sessions/${this.sessionId}/send-key`, {
161
- method: 'POST',
162
- headers: { 'Content-Type': 'application/json' },
163
- body: JSON.stringify({ key: ev.ctrlKey ? 'C-Enter' : 'S-Enter' }),
164
- }).catch(() => {
165
- /* Best-effort, matching this pane's tolerance elsewhere. */
166
- });
175
+ // Swallow keypress/keyup too (xterm would send \r for a Shift-only keypress); only keydown sends.
176
+ if (ev.key === 'Enter' && (ev.shiftKey || ev.ctrlKey)) {
177
+ if (ev.type === 'keydown') {
178
+ fetch(`/api/sessions/${this.sessionId}/send-key`, {
179
+ method: 'POST',
180
+ headers: { 'Content-Type': 'application/json' },
181
+ body: JSON.stringify({ key: ev.ctrlKey ? 'C-Enter' : 'S-Enter' }),
182
+ }).catch(() => {
183
+ /* Best-effort, matching this pane's tolerance elsewhere. */
184
+ });
185
+ }
167
186
  return false;
168
187
  }
169
188
  // Smart copy (mirrors terminal-ui.js's Ctrl+C gate, #211): with a
@@ -254,9 +273,9 @@
254
273
  try {
255
274
  const msg = JSON.parse(event.data);
256
275
  if (msg.t === 'o') {
257
- this.terminal.write(msg.d);
276
+ this._onLiveOutput(msg.d);
258
277
  } else if (msg.t === 'c') {
259
- this.terminal.clear();
278
+ this._onLiveClear();
260
279
  } else if (msg.t === 'r') {
261
280
  // Server-triggered refresh (SSE backpressure cleared, terminal
262
281
  // data was dropped). The primary pane routes this to
@@ -280,16 +299,49 @@
280
299
  // normal while it quietly ate everything typed into it. v1 scope is
281
300
  // "say so", not reconnect — collapsing the split would lose the
282
301
  // user's place in Pane B's scrollback for a transient blip.
283
- this.ws.onclose = () => {
284
- this._wsReady = false;
285
- this.terminal?.write('\r\n\x1b[2m[Pane B disconnected — close and reopen the split to reconnect]\x1b[0m\r\n');
286
- };
302
+ this.ws.onclose = () => this._onSocketClosed();
287
303
 
288
304
  this.ws.onerror = () => {
289
305
  // onclose fires after onerror — cleanup happens there.
290
306
  };
291
307
  }
292
308
 
309
+ // The socket's close, split out of connect() so the tests can drive it.
310
+ // While any load runs (a history pull or a `{t:'r'}` refresh) the marker is
311
+ // only owed, and that load's finally block settles it (_stampMarkerIfOwed()):
312
+ // written now, it would sit above the output a pull is still holding (flushed
313
+ // after it on a skip, a downgrade or a failed fetch), above a refresh's
314
+ // replay, or in the middle of a chunked replay. A pull still waiting for its
315
+ // response holds the marker too, for as long as the request takes (up to its
316
+ // budget, see _pullHistory()).
317
+ _onSocketClosed() {
318
+ this._wsReady = false;
319
+ this._wsClosed = true;
320
+ if (this._bufferLoading) this._markerOwed = true;
321
+ else this._writeDisconnectedMarker();
322
+ }
323
+
324
+ // Settles a marker the pane owes: set when a close lands during a load (the
325
+ // replay would otherwise sit below it) or when a load wipes the terminal on
326
+ // a closed socket. Called from each load's own finally, just before
327
+ // _endBufferLoad() starts any trailing refresh.
328
+ _stampMarkerIfOwed() {
329
+ // A trailing refresh is about to clear() synchronously, while xterm parses
330
+ // a write() on a later tick: a marker written here would land in the
331
+ // freshly cleared buffer ABOVE that refresh's replay, a second, stale copy.
332
+ // The refresh re-owes the marker on a closed socket and stamps it itself.
333
+ if (this._bufferRefreshPending && !this._destroyed) return;
334
+ const owed = this._markerOwed;
335
+ this._markerOwed = false;
336
+ if (owed && this._wsClosed && !this._destroyed) this._writeDisconnectedMarker();
337
+ }
338
+
339
+ // Extracted so both _onSocketClosed() and a load that ends owing it on a
340
+ // closed socket can write it (see _stampMarkerIfOwed()).
341
+ _writeDisconnectedMarker() {
342
+ this.terminal?.write('\r\n\x1b[2m[Pane B disconnected — close and reopen the split to reconnect]\x1b[0m\r\n');
343
+ }
344
+
293
345
  // Fetches and writes the session's current scrollback. Used both by
294
346
  // connect() (initial load) and by the `{t:'r'}` server-refresh frame
295
347
  // (below) — the primary pane's own _onSessionNeedsRefresh (app.js) is
@@ -324,14 +376,187 @@
324
376
  } catch {
325
377
  /* Best-effort — live output still arrives once the socket connects. */
326
378
  } finally {
327
- this._bufferLoading = false;
379
+ this._stampMarkerIfOwed();
380
+ this._endBufferLoad();
328
381
  }
382
+ }
383
+
384
+ // Ends a single-flight load (initial, refresh or history pull): clears the
385
+ // flag, then runs the ONE trailing refresh that arrived while it was busy.
386
+ _endBufferLoad() {
387
+ this._bufferLoading = false;
329
388
  if (this._bufferRefreshPending && !this._destroyed) {
330
389
  this._bufferRefreshPending = false;
331
390
  this._refreshBuffer();
332
391
  }
333
392
  }
334
393
 
394
+ // Live terminal output. Written straight through, except while a history
395
+ // pull is replaying: a capture is current only up to the instant tmux took
396
+ // it, so a frame arriving mid-replay is held with its arrival time and
397
+ // replayed behind the snapshot by _pullHistory() (the primary pane's
398
+ // _finishBufferLoad `since` rule), never written underneath it.
399
+ _onLiveOutput(data) {
400
+ if (this._liveQueue) this._liveQueue.push({ at: performance.now(), data });
401
+ else this.terminal?.write(data);
402
+ }
403
+
404
+ // The server's `{t:'c'}` clear frame takes the same route as output, for the
405
+ // same reason: clearing straight away, mid-replay, would wipe the half-written
406
+ // snapshot and leave _pullHistory() measuring a buffer that is no longer the
407
+ // one it is restoring. Queued, it lands in order with the frames around it.
408
+ _onLiveClear() {
409
+ if (this._liveQueue) this._liveQueue.push({ at: performance.now(), clear: true });
410
+ else this.terminal?.clear();
411
+ }
412
+
413
+ // Capture phase, because xterm's own wheel handler stopPropagation()s every
414
+ // event it consumes, so a bubbling listener here would never see the wheel
415
+ // while the pane still has scrollback to scroll. Passive: this only observes,
416
+ // xterm keeps doing the scrolling.
417
+ _installWheelListener() {
418
+ this._onWheel = (ev) => {
419
+ if (ev.deltaY < 0) this._maybeLoadMoreHistory();
420
+ };
421
+ this.mountEl.addEventListener('wheel', this._onWheel, { capture: true, passive: true });
422
+ }
423
+
424
+ // Wheel-up at the top of a SHELL pane's scrollback. tmux repaints a burst of
425
+ // output (`cat` of a file longer than the screen) instead of scrolling it,
426
+ // so this pane's xterm ends up with about one screen of scrollback while
427
+ // tmux holds every line — and nothing here ever went back to ask, so the
428
+ // history was unreachable. The primary pane has the same pull
429
+ // (app.js _maybeRefetchFullHistory); Pane B is a separate xterm and needs its
430
+ // own. Shell only: a non-shell CLI's history is out of scope for this pull
431
+ // (its load already takes `full=1`; codex and Claude's inline renderer do
432
+ // grow tmux history, this just isn't how they recover it). The alternate-
433
+ // screen skip (nano, vim, less) only matters for a direct-PTY shell — under
434
+ // tmux the browser xterm never enters the alternate buffer.
435
+ _maybeLoadMoreHistory() {
436
+ if (this.sessionMode !== 'shell' || this._destroyed || !this.terminal) return;
437
+ if (this._bufferLoading) return;
438
+ // Mirrors app.js _maybeRefetchFullHistory and this pane's own
439
+ // _sendResize(): a detached session's own window already owns its PTY
440
+ // size and scrollback, so Pane B has nothing of its own to reconcile.
441
+ if (this.detachedSessions?.has(this.sessionId)) return;
442
+ const active = this.terminal.buffer.active;
443
+ if (active.type !== 'normal' || active.viewportY !== 0) return;
444
+ // Momentum scrolling fires this dozens of times per flick, so cooldown
445
+ // rather than latch; a pull that could only have downgraded the pane
446
+ // waits far longer.
447
+ const cooldown = this._historyPullUseless ? 60000 : 4000;
448
+ const now = Date.now();
449
+ if (now - this._historyPullAt < cooldown) return;
450
+ this._historyPullAt = now;
451
+ void this._pullHistory();
452
+ }
453
+
454
+ // Pulls a BOUNDED window of tmux's full history (the same TERMINAL_TAIL_SIZE
455
+ // a tab switch loads, so a multi-megabyte capture never lands on xterm's
456
+ // main thread) and replays it under the reader's current place. Holds the
457
+ // single-flight flag across the fetch AND the replay, like _loadBuffer().
458
+ async _pullHistory() {
459
+ this._bufferLoading = true;
460
+ let replayed = false;
461
+ let capturedAt = 0;
462
+ // Two budgets on one signal. The request itself gets the primary pane's
463
+ // (CodemanFetchDeadline, constants.js): live output is not held while it
464
+ // runs, but the single-flight flag is, so a coalesced `{t:'r'}` refresh and
465
+ // the marker owed by a close (_onSocketClosed()) both wait for it, at worst
466
+ // for that whole budget. Once the headers land live output IS held, so the
467
+ // body read gets the short one instead: a body that hangs would otherwise
468
+ // freeze the pane for the long budget. Aborting lands in the catch below,
469
+ // which releases the flag and the queue. AbortSignal.timeout() alone cannot
470
+ // be re-armed, hence the controller; without AbortController the pull
471
+ // simply has no deadline.
472
+ const controller = global.AbortController ? new global.AbortController() : null;
473
+ let abortTimer = null;
474
+ const armDeadline = (ms) => {
475
+ if (!controller) return;
476
+ clearTimeout(abortTimer);
477
+ abortTimer = setTimeout(() => controller.abort(), ms);
478
+ };
479
+ try {
480
+ armDeadline(global.CodemanFetchDeadline?.terminalFetchDeadlineMs?.({ full: true }) ?? HISTORY_PULL_TIMEOUT_MS);
481
+ const res = await fetch(`/api/sessions/${this.sessionId}/terminal?full=1&tail=${TERMINAL_TAIL_SIZE}`, {
482
+ signal: controller?.signal,
483
+ });
484
+ armDeadline(HISTORY_PULL_TIMEOUT_MS);
485
+ // The cutoff below is the response's arrival, the same `since` rule the
486
+ // primary pane uses (_finishBufferLoad). It is a client clock standing in
487
+ // for the instant tmux took the capture, which lies somewhere in the
488
+ // round trip, so a frame in that window can be lost or doubled. Bounded
489
+ // by one round trip and not closable without a server-side capture time.
490
+ capturedAt = performance.now();
491
+ // Opened only now: a frame from before the response is either replaced by
492
+ // the capture or written unchanged, so holding it for the round trip
493
+ // bought nothing and froze the pane for as long as the fetch took.
494
+ this._liveQueue = [];
495
+ const payload = (await res.json())?.data;
496
+ clearTimeout(abortTimer);
497
+ const buffer = payload?.terminalBuffer;
498
+ const term = this.terminal;
499
+ if (!buffer || !term || this._destroyed) return;
500
+ const rowsBefore = term.buffer.active.length;
501
+ const rowsIncoming = global.app?._estimateReplayRows?.(buffer, term.cols) ?? buffer.split('\n').length;
502
+ // xterm keeps at most `scrollback + rows` rows while tmux keeps far more
503
+ // lines, so a window of short lines can carry more rows than this pane
504
+ // can ever hold, and `rowsIncoming <= rowsBefore` would never come true.
505
+ const scrollbackCap = term.options?.scrollback || 0;
506
+ const paneFull = scrollbackCap > 0 && rowsBefore >= scrollbackCap + term.rows;
507
+ // Nothing to gain (this also covers a downgrade, which would delete
508
+ // history mid-scroll), and a reset+rewrite would jump the viewport. An
509
+ // untruncated window IS all of tmux's history and the next burst can add
510
+ // more, so keep the 4 s cooldown. A truncated window can never reach past
511
+ // what the pane shows, and every ask costs the server a capture-pane of
512
+ // the whole history (`tail` is cut after it): back off to 60 s, as the
513
+ // primary pane does (app.js _maybeRefetchFullHistory). A full pane backs
514
+ // off too, since no window can ever fit in it.
515
+ if (rowsIncoming <= rowsBefore || paneFull) {
516
+ if (payload.truncated || paneFull) this._historyPullUseless = true;
517
+ return;
518
+ }
519
+ this._historyPullUseless = false;
520
+ term.write('\x1bc');
521
+ replayed = true;
522
+ if (this._wsClosed) this._markerOwed = true;
523
+ await writeChunked(term, buffer, () => this._destroyed);
524
+ if (this._destroyed || !this.terminal) return;
525
+ // xterm parses asynchronously: an empty write's callback fires only
526
+ // after everything before it, so the row count below is the settled one.
527
+ await new Promise((resolve) => this.terminal.write('', resolve));
528
+ if (this._destroyed || !this.terminal) return;
529
+ // The replay grew the buffer UPWARD, so what was row 0 is now `delta`
530
+ // rows down; land there and the recovered history sits above it.
531
+ const delta = this.terminal.buffer.active.length - rowsBefore;
532
+ if (delta > 0) this.terminal.scrollToLine(delta);
533
+ else this.terminal.scrollToTop();
534
+ } catch {
535
+ /* Best-effort — live output keeps arriving whatever happens here. */
536
+ } finally {
537
+ clearTimeout(abortTimer);
538
+ const queued = this._liveQueue ?? [];
539
+ this._liveQueue = null;
540
+ // After a replay, only frames that arrived after the capture are news;
541
+ // earlier ones are already in it. With no replay, every held frame is.
542
+ const cutoff = replayed ? capturedAt : 0;
543
+ for (const entry of queued) {
544
+ if (entry.at < cutoff) continue;
545
+ if (entry.clear) this.terminal?.clear();
546
+ else this.terminal?.write(entry.data);
547
+ }
548
+ // Settled after the queue flush so the marker is the last thing on
549
+ // screen: a close during the pull wrote nothing (_onSocketClosed() defers
550
+ // it while a load runs), and a replay's own `\x1bc` (flagged above) wipes
551
+ // one written before it, which would paint a fresh, current-looking
552
+ // history while onData keeps silently dropping every keystroke on the
553
+ // dead socket. With a trailing refresh pending (_endBufferLoad) the marker
554
+ // is left to that refresh, which writes it below its own replay.
555
+ this._stampMarkerIfOwed();
556
+ this._endBufferLoad();
557
+ }
558
+ }
559
+
335
560
  // The `{t:'r'}` server-refresh path: clear, then replay. Two refresh
336
561
  // frames in a row used to start two concurrent replays, each clearing
337
562
  // the terminal under the other's chunked write. A refresh that arrives
@@ -344,6 +569,10 @@
344
569
  return;
345
570
  }
346
571
  this.terminal?.clear();
572
+ // The clear wipes a "disconnected" marker (a `{t:'r'}` frame can queue a
573
+ // trailing refresh behind a pull that the socket's close then interrupts),
574
+ // so a refresh on a closed socket owes it back once its replay is written.
575
+ if (this._wsClosed) this._markerOwed = true;
347
576
  void this._loadBuffer();
348
577
  }
349
578
 
@@ -381,6 +610,10 @@
381
610
 
382
611
  destroy() {
383
612
  this._destroyed = true;
613
+ if (this._onWheel) {
614
+ this.mountEl?.removeEventListener('wheel', this._onWheel, { capture: true });
615
+ this._onWheel = null;
616
+ }
384
617
  if (this.ws) {
385
618
  this.ws.onopen = null;
386
619
  this.ws.onmessage = null;