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.
- package/dist/config/cli-registry/schema.d.ts +18 -0
- package/dist/config/cli-registry/schema.d.ts.map +1 -1
- package/dist/config/cli-registry/schema.js +27 -0
- package/dist/config/cli-registry/schema.js.map +1 -1
- package/dist/config/cli-registry/stock.d.ts.map +1 -1
- package/dist/config/cli-registry/stock.js +35 -0
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +33 -0
- package/dist/config/cli-registry/types.d.ts.map +1 -1
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +5 -1
- package/dist/hooks-config.js.map +1 -1
- package/dist/mcp-sync.d.ts +105 -0
- package/dist/mcp-sync.d.ts.map +1 -0
- package/dist/mcp-sync.js +619 -0
- package/dist/mcp-sync.js.map +1 -0
- package/dist/mux-interface.d.ts +4 -0
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/session-cli-builder.d.ts +20 -1
- package/dist/session-cli-builder.d.ts.map +1 -1
- package/dist/session-cli-builder.js +33 -3
- package/dist/session-cli-builder.js.map +1 -1
- package/dist/session-cli-registry-bridge.d.ts +2 -0
- package/dist/session-cli-registry-bridge.d.ts.map +1 -1
- package/dist/session-cli-registry-bridge.js +15 -8
- package/dist/session-cli-registry-bridge.js.map +1 -1
- package/dist/session.d.ts +19 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +29 -2
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts +2 -0
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +4 -2
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/mcp-sync.d.ts +40 -0
- package/dist/types/mcp-sync.d.ts.map +1 -0
- package/dist/types/mcp-sync.js +8 -0
- package/dist/types/mcp-sync.js.map +1 -0
- package/dist/types/push.d.ts +37 -2
- package/dist/types/push.d.ts.map +1 -1
- package/dist/types/push.js +10 -3
- package/dist/types/push.js.map +1 -1
- package/dist/types/session.d.ts +34 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js +25 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.cf0db8ce.js +43 -0
- package/dist/web/public/app.cf0db8ce.js.br +0 -0
- package/dist/web/public/app.cf0db8ce.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.cc927eb8.js → constants.d95fade9.js} +19 -2
- package/dist/web/public/constants.d95fade9.js.br +0 -0
- package/dist/web/public/constants.d95fade9.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/host-wake-ui.js.gz +0 -0
- package/dist/web/public/i18n.398b8331.js +1 -0
- package/dist/web/public/i18n.398b8331.js.br +0 -0
- package/dist/web/public/i18n.398b8331.js.gz +0 -0
- package/dist/web/public/image-input.a216cf9e.js.gz +0 -0
- package/dist/web/public/index.html +125 -14
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.c1e7c7b7.js.gz +0 -0
- package/dist/web/public/mobile-handlers.23a85db1.js.gz +0 -0
- package/dist/web/public/mobile-ime-preview.2dbaf262.js +1 -0
- package/dist/web/public/mobile-ime-preview.2dbaf262.js.br +0 -0
- package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.1aa4952d.css.gz +0 -0
- package/dist/web/public/notification-manager.50bb87d8.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.67646701.js → panels-ui.6a5898d9.js} +69 -67
- package/dist/web/public/panels-ui.6a5898d9.js.br +0 -0
- package/dist/web/public/panels-ui.6a5898d9.js.gz +0 -0
- package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
- package/dist/web/public/{ralph-wizard.13a1831e.js → ralph-wizard.b6c0dd19.js} +2 -0
- package/dist/web/public/ralph-wizard.b6c0dd19.js.br +0 -0
- package/dist/web/public/{ralph-wizard.13a1831e.js.gz → ralph-wizard.b6c0dd19.js.gz} +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js +1 -0
- package/dist/web/public/sanitize-html.e7147e76.js.br +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
- package/dist/web/public/session-lineage.js +27 -11
- package/dist/web/public/session-lineage.js.br +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/{session-ui.7575a231.js → session-ui.bf66c13b.js} +4 -4
- package/dist/web/public/session-ui.bf66c13b.js.br +0 -0
- package/dist/web/public/session-ui.bf66c13b.js.gz +0 -0
- package/dist/web/public/{settings-ui.c68e7f59.js → settings-ui.331d3b1d.js} +12 -10
- package/dist/web/public/settings-ui.331d3b1d.js.br +0 -0
- package/dist/web/public/settings-ui.331d3b1d.js.gz +0 -0
- package/dist/web/public/styles.8c485d26.css +1 -0
- package/dist/web/public/styles.8c485d26.css.br +0 -0
- package/dist/web/public/styles.8c485d26.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js +2 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-layout-browser.4e0ed50c.js +1 -0
- package/dist/web/public/tab-layout-browser.4e0ed50c.js.br +0 -0
- package/dist/web/public/tab-layout-browser.4e0ed50c.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.dcdc5772.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
- package/dist/web/public/terminal-split.js +248 -15
- package/dist/web/public/terminal-split.js.br +0 -0
- package/dist/web/public/terminal-split.js.gz +0 -0
- package/dist/web/public/terminal-ui.a4535311.js +2 -0
- package/dist/web/public/terminal-ui.a4535311.js.br +0 -0
- package/dist/web/public/terminal-ui.a4535311.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/{xterm-predictive-echo.bd6882b8.js → xterm-predictive-echo.ae5e3d5d.js} +1 -1
- package/dist/web/public/vendor/{xterm-predictive-echo.bd6882b8.js.br → xterm-predictive-echo.ae5e3d5d.js.br} +0 -0
- package/dist/web/public/vendor/{xterm-predictive-echo.bd6882b8.js.gz → xterm-predictive-echo.ae5e3d5d.js.gz} +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js +4 -0
- package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.br +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +36 -14
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/reboot-restore-registry.d.ts +2 -1
- package/dist/web/reboot-restore-registry.d.ts.map +1 -1
- package/dist/web/reboot-restore-registry.js +2 -1
- package/dist/web/reboot-restore-registry.js.map +1 -1
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +2 -1
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/index.d.ts +2 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +2 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/mcp-sync-routes.d.ts +29 -0
- package/dist/web/routes/mcp-sync-routes.d.ts.map +1 -0
- package/dist/web/routes/mcp-sync-routes.js +83 -0
- package/dist/web/routes/mcp-sync-routes.js.map +1 -0
- package/dist/web/routes/ralph-routes.d.ts.map +1 -1
- package/dist/web/routes/ralph-routes.js +2 -1
- package/dist/web/routes/ralph-routes.js.map +1 -1
- package/dist/web/routes/reboot-restore-routes.d.ts.map +1 -1
- package/dist/web/routes/reboot-restore-routes.js +2 -0
- package/dist/web/routes/reboot-restore-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +40 -17
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/webhook-routes.d.ts +21 -0
- package/dist/web/routes/webhook-routes.d.ts.map +1 -0
- package/dist/web/routes/webhook-routes.js +87 -0
- package/dist/web/routes/webhook-routes.js.map +1 -0
- package/dist/web/schemas.d.ts +47 -2
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +64 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +4 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +55 -25
- package/dist/web/server.js.map +1 -1
- package/dist/webhook-notify.d.ts +71 -0
- package/dist/webhook-notify.d.ts.map +1 -0
- package/dist/webhook-notify.js +282 -0
- package/dist/webhook-notify.js.map +1 -0
- package/package.json +2 -1
- package/skills/codeman/SKILL.md +26 -8
- package/skills/codeman/preamble.sh +16 -4
- package/skills/codeman/reference/endpoints.md +11 -3
- package/skills/codeman/reference/recipes.md +1 -1
- package/dist/web/public/app.91131d2c.js +0 -43
- package/dist/web/public/app.91131d2c.js.br +0 -0
- package/dist/web/public/app.91131d2c.js.gz +0 -0
- package/dist/web/public/constants.cc927eb8.js.br +0 -0
- package/dist/web/public/constants.cc927eb8.js.gz +0 -0
- package/dist/web/public/i18n.f094d01f.js +0 -1
- package/dist/web/public/i18n.f094d01f.js.br +0 -0
- package/dist/web/public/i18n.f094d01f.js.gz +0 -0
- package/dist/web/public/panels-ui.67646701.js.br +0 -0
- package/dist/web/public/panels-ui.67646701.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.br +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js +0 -1
- package/dist/web/public/sanitize-html.bc7078d6.js.br +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-ui.7575a231.js.br +0 -0
- package/dist/web/public/session-ui.7575a231.js.gz +0 -0
- package/dist/web/public/settings-ui.c68e7f59.js.br +0 -0
- package/dist/web/public/settings-ui.c68e7f59.js.gz +0 -0
- package/dist/web/public/styles.3fe3312d.css +0 -1
- package/dist/web/public/styles.3fe3312d.css.br +0 -0
- package/dist/web/public/styles.3fe3312d.css.gz +0 -0
- package/dist/web/public/terminal-ui.d5223c0e.js +0 -2
- package/dist/web/public/terminal-ui.d5223c0e.js.br +0 -0
- package/dist/web/public/terminal-ui.d5223c0e.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js +0 -4
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.br +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/web/public/sw.js
CHANGED
|
@@ -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 = '
|
|
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.
|
|
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 = [
|
package/dist/web/public/sw.js.br
CHANGED
|
Binary file
|
package/dist/web/public/sw.js.gz
CHANGED
|
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);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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.
|
|
276
|
+
this._onLiveOutput(msg.d);
|
|
258
277
|
} else if (msg.t === 'c') {
|
|
259
|
-
this.
|
|
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.
|
|
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;
|
|
Binary file
|
|
Binary file
|