@slycode/slycode 0.2.12 → 0.2.14

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 (51) hide show
  1. package/dist/bridge/session-manager.js +28 -3
  2. package/dist/bridge/session-manager.js.map +1 -1
  3. package/dist/bridge/types.d.ts +3 -0
  4. package/dist/messaging/state.js +1 -0
  5. package/dist/messaging/state.js.map +1 -1
  6. package/dist/scripts/kanban.js +26 -1
  7. package/dist/web/.next/BUILD_ID +1 -1
  8. package/dist/web/.next/build-manifest.json +2 -2
  9. package/dist/web/.next/server/app/_global-error.html +2 -2
  10. package/dist/web/.next/server/app/_global-error.rsc +1 -1
  11. package/dist/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/dist/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/dist/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/dist/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/dist/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/dist/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/dist/web/.next/server/app/_not-found.html +1 -1
  18. package/dist/web/.next/server/app/_not-found.rsc +2 -2
  19. package/dist/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  20. package/dist/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/dist/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  22. package/dist/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/dist/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/dist/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/dist/web/.next/server/app/page/react-loadable-manifest.json +1 -1
  26. package/dist/web/.next/server/app/page_client-reference-manifest.js +1 -1
  27. package/dist/web/.next/server/app/project/[id]/page/react-loadable-manifest.json +1 -1
  28. package/dist/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
  29. package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__1f5fc489._.js +1 -1
  30. package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__bcbe4bf2._.js +1 -1
  31. package/dist/web/.next/server/pages/404.html +1 -1
  32. package/dist/web/.next/server/pages/500.html +2 -2
  33. package/dist/web/.next/static/chunks/{4d5e1b83ce67acfa.js → 0a4b215957655f38.js} +1 -1
  34. package/dist/web/.next/static/chunks/2744d72103f49934.css +1 -0
  35. package/dist/web/.next/static/chunks/c5d50c930f4e26a6.js +4 -0
  36. package/dist/web/.next/static/chunks/{24c966c8b9e12be5.js → c7a853519f3ebcb8.js} +1 -1
  37. package/dist/web/.next/static/chunks/{29b5d391d655a999.js → cfec56fdddd52361.js} +2 -2
  38. package/dist/web/.next/static/chunks/{02a9b2dcd9d8b87c.js → e52d73ad4544e983.js} +1 -1
  39. package/dist/web/src/components/ClaudeTerminalPanel.tsx +30 -1
  40. package/dist/web/src/components/Terminal.tsx +2 -2
  41. package/dist/web/tsconfig.tsbuildinfo +1 -1
  42. package/lib/cli/stop.d.ts.map +1 -1
  43. package/lib/cli/stop.js +38 -18
  44. package/lib/cli/stop.js.map +1 -1
  45. package/package.json +1 -1
  46. package/templates/kanban-seed.json +1 -1
  47. package/dist/web/.next/static/chunks/17bf7ad67057dc74.js +0 -4
  48. package/dist/web/.next/static/chunks/3df3846316317676.css +0 -1
  49. /package/dist/web/.next/static/{rlJUIaeDUP-JDj2nvHySS → y4wg5RLBkOtdMpGszS8Fs}/_buildManifest.js +0 -0
  50. /package/dist/web/.next/static/{rlJUIaeDUP-JDj2nvHySS → y4wg5RLBkOtdMpGszS8Fs}/_clientMiddlewareManifest.json +0 -0
  51. /package/dist/web/.next/static/{rlJUIaeDUP-JDj2nvHySS → y4wg5RLBkOtdMpGszS8Fs}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,33525,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0}),Object.defineProperty(n,"warnOnce",{enumerable:!0,get:function(){return s}});let s=e=>{}},87365,e=>{"use strict";let t="slycode:terminal-prompt";function n(e,s=!0){let i=new CustomEvent(t,{detail:{prompt:e,autoSubmit:s}});window.dispatchEvent(i)}function s(e){let n=t=>{e(t.detail)};return window.addEventListener(t,n),()=>window.removeEventListener(t,n)}e.s(["onTerminalPrompt",()=>s,"pushToTerminal",()=>n])},21146,e=>{"use strict";function t(...e){"1"===localStorage.getItem("cm-debug")&&console.log(`[CM ${new Date().toISOString().slice(11,23)}]`,...e)}class n{connections=new Map;statusListeners=new Set;_status="connected";connectionIdCounter=0;isPageVisible=!0;visibilityChangeTime=null;healthCheckInterval=null;lastHealthCheckSuccess=null;consecutiveHealthFailures=0;statusDowngradeTimeout=null;constructor(){this.setupVisibilityListener(),this.setupNetworkListener(),this.setupHealthCheck()}setupVisibilityListener(){document.addEventListener("visibilitychange",()=>{let e=this.isPageVisible;this.isPageVisible="visible"===document.visibilityState,!e&&this.isPageVisible?(this.visibilityChangeTime=Date.now(),this.handleTabWake()):e&&!this.isPageVisible&&(this.visibilityChangeTime=Date.now())})}setupNetworkListener(){window.addEventListener("online",()=>{this.doHealthCheck()}),window.addEventListener("offline",()=>{this.updateStatus("disconnected")})}setupHealthCheck(){this.healthCheckInterval=setInterval(()=>{if(this.isPageVisible&&0!==this.connections.size){if(this.hasRecentlyActiveConnection()){this.consecutiveHealthFailures=0,"connected"!==this._status&&this.reconnectBroken();return}this.doHealthCheck()}},2e4)}hasRecentlyActiveConnection(){let e=Date.now();for(let t of this.connections.values())if(t.eventSource?.readyState===EventSource.OPEN&&t.lastConnected&&e-t.lastConnected<6e4)return!0;return!1}async doHealthCheck(){t("HEALTH_CHECK — fetching /api/bridge/health");try{let e=new AbortController,n=setTimeout(()=>e.abort(),3e3),s=await fetch("/api/bridge/health",{method:"GET",signal:e.signal});clearTimeout(n),s.ok?(t("HEALTH_CHECK — OK"),this.consecutiveHealthFailures=0,this.lastHealthCheckSuccess=Date.now(),"connected"!==this._status&&this.reconnectBroken()):(t(`HEALTH_CHECK — FAIL status=${s.status}`),this.handleHealthCheckFailure())}catch(e){t(`HEALTH_CHECK — ERROR`,e),this.handleHealthCheckFailure()}}handleHealthCheckFailure(){this.consecutiveHealthFailures++,t(`HEALTH_FAIL — consecutive=${this.consecutiveHealthFailures}`),this.consecutiveHealthFailures>=2&&"connected"===this._status&&this.updateStatus("disconnected")}handleTabWake(){t("TAB_WAKE — force-reconnecting all connections"),this.reconnectAll(!0)}calculateBackoff(e){let t=Math.min(1e3*Math.pow(2,e),3e4),n=.2*t*(2*Math.random()-1);return Math.round(t+n)}createManagedEventSource(e,t){let n=`conn-${++this.connectionIdCounter}`;return this.connections.set(n,{url:e,handlers:t,eventSource:null,retryCount:0,retryTimeout:null,lastConnected:null,id:n}),this.connect(n),n}connect(e){let n=this.connections.get(e);if(n){n.eventSource&&(n.eventSource.close(),n.eventSource=null),n.retryTimeout&&(clearTimeout(n.retryTimeout),n.retryTimeout=null);try{let s=new EventSource(n.url);n.eventSource=s,s.onopen=()=>{n.retryCount=0,n.lastConnected=Date.now(),this.consecutiveHealthFailures=0,t(`OPEN ${e} → ${n.url}`),this.updateOverallStatus(),n.handlers.onOpen?.()},s.onerror=i=>{t(`ERROR ${e} → ${n.url} readyState=${s.readyState} (0=CONNECTING, 1=OPEN, 2=CLOSED)`,i),n.handlers.onError?.(i),s.readyState===EventSource.CLOSED&&(t(`CLOSED ${e} — scheduling reconnect`),this.scheduleReconnect(e))},s.onmessage=e=>{n.lastConnected=Date.now(),n.handlers.onMessage?.(e)},s.addEventListener("heartbeat",()=>{n.lastConnected=Date.now(),t(`HEARTBEAT ${e} → ${n.url}`)}),Object.entries(n.handlers).forEach(([e,t])=>{"onOpen"!==e&&"onError"!==e&&"onMessage"!==e&&t&&s.addEventListener(e,e=>{n.lastConnected=Date.now(),t(e)})})}catch(t){console.error(`ConnectionManager: Failed to create EventSource for ${n.url}`,t),this.scheduleReconnect(e)}}}scheduleReconnect(e){let t=this.connections.get(e);if(!t)return;t.retryTimeout&&clearTimeout(t.retryTimeout);let n=this.calculateBackoff(t.retryCount);t.retryCount++,this.updateOverallStatus(),t.retryTimeout=setTimeout(()=>{t.retryTimeout=null,this.connect(e)},n)}reconnect(e,t=!1){let n=this.connections.get(e);n&&(t?(n.retryCount=0,this.updateStatus("reconnecting"),this.connect(e)):this.scheduleReconnect(e))}reconnectAll(e=!1){this.connections.size>0&&this.updateStatus("reconnecting"),this.connections.forEach((t,n)=>{this.reconnect(n,e)})}reconnectBroken(){let e=!1;this.connections.forEach((t,n)=>{t.eventSource?.readyState!==EventSource.OPEN&&(e=!0,this.reconnect(n,!0))}),e||this.updateOverallStatus()}closeConnection(e){let t=this.connections.get(e);t&&(t.retryTimeout&&clearTimeout(t.retryTimeout),t.eventSource&&t.eventSource.close(),this.connections.delete(e),this.updateOverallStatus())}updateOverallStatus(){if(0===this.connections.size){this.updateStatus("connected"),this.clearDowngradeTimeout();return}let e=0,t=0;this.connections.forEach(n=>{n.eventSource?.readyState===EventSource.OPEN?e++:(n.retryTimeout||n.eventSource?.readyState===EventSource.CONNECTING)&&t++}),e>0?(this.updateStatus("connected"),this.clearDowngradeTimeout()):t>0?this.scheduleDowngrade("reconnecting"):this.scheduleDowngrade("disconnected")}scheduleDowngrade(e){"connected"!==this._status?this.updateStatus(e):this.statusDowngradeTimeout||(this.statusDowngradeTimeout=setTimeout(()=>{this.statusDowngradeTimeout=null,this.updateOverallStatus()},3e3))}clearDowngradeTimeout(){this.statusDowngradeTimeout&&(clearTimeout(this.statusDowngradeTimeout),this.statusDowngradeTimeout=null)}updateStatus(e){if(this._status!==e){let n=Array.from(this.connections.values()).map(e=>({id:e.id,url:e.url.replace(/.*\/api\//,"/api/"),readyState:e.eventSource?.readyState,lastConnectedAge:e.lastConnected?Math.round((Date.now()-e.lastConnected)/1e3)+"s":"never",hasRetryPending:!!e.retryTimeout}));t(`STATUS ${this._status} → ${e}`,JSON.stringify(n,null,2)),this._status=e,this.statusListeners.forEach(t=>t(e))}}subscribe(e){return this.statusListeners.add(e),e(this._status),()=>{this.statusListeners.delete(e)}}get status(){return this._status}get pageVisible(){return this.isPageVisible}get connectionCount(){return this.connections.size}}let s=new n;e.s(["connectionManager",0,s])},54423,e=>{e.v(t=>Promise.all(["static/chunks/54d5670f5fa2abbe.css","static/chunks/4d5e1b83ce67acfa.js"].map(t=>e.l(t))).then(()=>t(81485)))},90178,e=>{e.v(e=>Promise.resolve().then(()=>e(87365)))}]);
1
+ (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,33525,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0}),Object.defineProperty(n,"warnOnce",{enumerable:!0,get:function(){return s}});let s=e=>{}},87365,e=>{"use strict";let t="slycode:terminal-prompt";function n(e,s=!0){let i=new CustomEvent(t,{detail:{prompt:e,autoSubmit:s}});window.dispatchEvent(i)}function s(e){let n=t=>{e(t.detail)};return window.addEventListener(t,n),()=>window.removeEventListener(t,n)}e.s(["onTerminalPrompt",()=>s,"pushToTerminal",()=>n])},21146,e=>{"use strict";function t(...e){"1"===localStorage.getItem("cm-debug")&&console.log(`[CM ${new Date().toISOString().slice(11,23)}]`,...e)}class n{connections=new Map;statusListeners=new Set;_status="connected";connectionIdCounter=0;isPageVisible=!0;visibilityChangeTime=null;healthCheckInterval=null;lastHealthCheckSuccess=null;consecutiveHealthFailures=0;statusDowngradeTimeout=null;constructor(){this.setupVisibilityListener(),this.setupNetworkListener(),this.setupHealthCheck()}setupVisibilityListener(){document.addEventListener("visibilitychange",()=>{let e=this.isPageVisible;this.isPageVisible="visible"===document.visibilityState,!e&&this.isPageVisible?(this.visibilityChangeTime=Date.now(),this.handleTabWake()):e&&!this.isPageVisible&&(this.visibilityChangeTime=Date.now())})}setupNetworkListener(){window.addEventListener("online",()=>{this.doHealthCheck()}),window.addEventListener("offline",()=>{this.updateStatus("disconnected")})}setupHealthCheck(){this.healthCheckInterval=setInterval(()=>{if(this.isPageVisible&&0!==this.connections.size){if(this.hasRecentlyActiveConnection()){this.consecutiveHealthFailures=0,"connected"!==this._status&&this.reconnectBroken();return}this.doHealthCheck()}},2e4)}hasRecentlyActiveConnection(){let e=Date.now();for(let t of this.connections.values())if(t.eventSource?.readyState===EventSource.OPEN&&t.lastConnected&&e-t.lastConnected<6e4)return!0;return!1}async doHealthCheck(){t("HEALTH_CHECK — fetching /api/bridge/health");try{let e=new AbortController,n=setTimeout(()=>e.abort(),3e3),s=await fetch("/api/bridge/health",{method:"GET",signal:e.signal});clearTimeout(n),s.ok?(t("HEALTH_CHECK — OK"),this.consecutiveHealthFailures=0,this.lastHealthCheckSuccess=Date.now(),"connected"!==this._status&&this.reconnectBroken()):(t(`HEALTH_CHECK — FAIL status=${s.status}`),this.handleHealthCheckFailure())}catch(e){t(`HEALTH_CHECK — ERROR`,e),this.handleHealthCheckFailure()}}handleHealthCheckFailure(){this.consecutiveHealthFailures++,t(`HEALTH_FAIL — consecutive=${this.consecutiveHealthFailures}`),this.consecutiveHealthFailures>=2&&"connected"===this._status&&this.updateStatus("disconnected")}handleTabWake(){t("TAB_WAKE — force-reconnecting all connections"),this.reconnectAll(!0)}calculateBackoff(e){let t=Math.min(1e3*Math.pow(2,e),3e4),n=.2*t*(2*Math.random()-1);return Math.round(t+n)}createManagedEventSource(e,t){let n=`conn-${++this.connectionIdCounter}`;return this.connections.set(n,{url:e,handlers:t,eventSource:null,retryCount:0,retryTimeout:null,lastConnected:null,id:n}),this.connect(n),n}connect(e){let n=this.connections.get(e);if(n){n.eventSource&&(n.eventSource.close(),n.eventSource=null),n.retryTimeout&&(clearTimeout(n.retryTimeout),n.retryTimeout=null);try{let s=new EventSource(n.url);n.eventSource=s,s.onopen=()=>{n.retryCount=0,n.lastConnected=Date.now(),this.consecutiveHealthFailures=0,t(`OPEN ${e} → ${n.url}`),this.updateOverallStatus(),n.handlers.onOpen?.()},s.onerror=i=>{t(`ERROR ${e} → ${n.url} readyState=${s.readyState} (0=CONNECTING, 1=OPEN, 2=CLOSED)`,i),n.handlers.onError?.(i),s.readyState===EventSource.CLOSED&&(t(`CLOSED ${e} — scheduling reconnect`),this.scheduleReconnect(e))},s.onmessage=e=>{n.lastConnected=Date.now(),n.handlers.onMessage?.(e)},s.addEventListener("heartbeat",()=>{n.lastConnected=Date.now(),t(`HEARTBEAT ${e} → ${n.url}`)}),Object.entries(n.handlers).forEach(([e,t])=>{"onOpen"!==e&&"onError"!==e&&"onMessage"!==e&&t&&s.addEventListener(e,e=>{n.lastConnected=Date.now(),t(e)})})}catch(t){console.error(`ConnectionManager: Failed to create EventSource for ${n.url}`,t),this.scheduleReconnect(e)}}}scheduleReconnect(e){let t=this.connections.get(e);if(!t)return;t.retryTimeout&&clearTimeout(t.retryTimeout);let n=this.calculateBackoff(t.retryCount);t.retryCount++,this.updateOverallStatus(),t.retryTimeout=setTimeout(()=>{t.retryTimeout=null,this.connect(e)},n)}reconnect(e,t=!1){let n=this.connections.get(e);n&&(t?(n.retryCount=0,this.updateStatus("reconnecting"),this.connect(e)):this.scheduleReconnect(e))}reconnectAll(e=!1){this.connections.size>0&&this.updateStatus("reconnecting"),this.connections.forEach((t,n)=>{this.reconnect(n,e)})}reconnectBroken(){let e=!1;this.connections.forEach((t,n)=>{t.eventSource?.readyState!==EventSource.OPEN&&(e=!0,this.reconnect(n,!0))}),e||this.updateOverallStatus()}closeConnection(e){let t=this.connections.get(e);t&&(t.retryTimeout&&clearTimeout(t.retryTimeout),t.eventSource&&t.eventSource.close(),this.connections.delete(e),this.updateOverallStatus())}updateOverallStatus(){if(0===this.connections.size){this.updateStatus("connected"),this.clearDowngradeTimeout();return}let e=0,t=0;this.connections.forEach(n=>{n.eventSource?.readyState===EventSource.OPEN?e++:(n.retryTimeout||n.eventSource?.readyState===EventSource.CONNECTING)&&t++}),e>0?(this.updateStatus("connected"),this.clearDowngradeTimeout()):t>0?this.scheduleDowngrade("reconnecting"):this.scheduleDowngrade("disconnected")}scheduleDowngrade(e){"connected"!==this._status?this.updateStatus(e):this.statusDowngradeTimeout||(this.statusDowngradeTimeout=setTimeout(()=>{this.statusDowngradeTimeout=null,this.updateOverallStatus()},3e3))}clearDowngradeTimeout(){this.statusDowngradeTimeout&&(clearTimeout(this.statusDowngradeTimeout),this.statusDowngradeTimeout=null)}updateStatus(e){if(this._status!==e){let n=Array.from(this.connections.values()).map(e=>({id:e.id,url:e.url.replace(/.*\/api\//,"/api/"),readyState:e.eventSource?.readyState,lastConnectedAge:e.lastConnected?Math.round((Date.now()-e.lastConnected)/1e3)+"s":"never",hasRetryPending:!!e.retryTimeout}));t(`STATUS ${this._status} → ${e}`,JSON.stringify(n,null,2)),this._status=e,this.statusListeners.forEach(t=>t(e))}}subscribe(e){return this.statusListeners.add(e),e(this._status),()=>{this.statusListeners.delete(e)}}get status(){return this._status}get pageVisible(){return this.isPageVisible}get connectionCount(){return this.connections.size}}let s=new n;e.s(["connectionManager",0,s])},54423,e=>{e.v(t=>Promise.all(["static/chunks/54d5670f5fa2abbe.css","static/chunks/0a4b215957655f38.js"].map(t=>e.l(t))).then(()=>t(81485)))},90178,e=>{e.v(e=>Promise.resolve().then(()=>e(87365)))}]);
@@ -140,6 +140,8 @@ export function ClaudeTerminalPanel({
140
140
  const [screenshotToast, setScreenshotToast] = useState<{ filename: string; previewUrl: string; status: 'uploading' | 'done' | 'error'; message?: string } | null>(null);
141
141
  const screenshotToastTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
142
142
  const imagePasteInProgressRef = useRef(false);
143
+ // Exit output toast state (persists across terminal unmount)
144
+ const [exitToast, setExitToast] = useState<{ code: number; output: string } | null>(null);
143
145
 
144
146
  // Instruction file check state
145
147
  const [instructionFileCheck, setInstructionFileCheck] = useState<{ needed: boolean; targetFile?: string; copySource?: string } | null>(null);
@@ -291,6 +293,7 @@ export function ClaudeTerminalPanel({
291
293
  const startSession = async (command?: SlyActionItem | { prompt: string } | null, customPromptText?: string) => {
292
294
  setIsStarting(true);
293
295
  setShowCustomPrompt(false);
296
+ setExitToast(null);
294
297
  try {
295
298
  // Build prompt if action provided — context is opt-in via {{cardContext}} etc.
296
299
  let prompt: string | undefined;
@@ -386,9 +389,12 @@ export function ClaudeTerminalPanel({
386
389
  }
387
390
  };
388
391
 
389
- const handleSessionExit = () => {
392
+ const handleSessionExit = (code: number, output?: string) => {
390
393
  setShowTerminal(false);
391
394
  setIsConnected(false);
395
+ if (output && code !== 0) {
396
+ setExitToast({ code, output });
397
+ }
392
398
  fetchSessionInfo();
393
399
  };
394
400
 
@@ -753,6 +759,29 @@ export function ClaudeTerminalPanel({
753
759
  </div>
754
760
  )}
755
761
 
762
+ {/* Exit output toast — persists after terminal unmounts */}
763
+ {exitToast && (
764
+ <div className="absolute bottom-3 right-3 left-3 z-50 rounded-lg border border-red-500/30 bg-void-800/95 shadow-(--shadow-overlay) backdrop-blur-sm">
765
+ <div className="flex items-start justify-between gap-2 px-3 py-2">
766
+ <div className="flex items-center gap-1.5 text-xs font-medium text-red-400">
767
+ <svg className="h-3.5 w-3.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
768
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
769
+ </svg>
770
+ Session exited (code: {exitToast.code})
771
+ </div>
772
+ <button
773
+ onClick={() => setExitToast(null)}
774
+ className="text-void-500 hover:text-void-300 flex-shrink-0"
775
+ >
776
+ <svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
777
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
778
+ </svg>
779
+ </button>
780
+ </div>
781
+ <pre className="max-h-32 overflow-auto px-3 pb-2 text-xs text-void-300 font-mono whitespace-pre-wrap">{exitToast.output}</pre>
782
+ </div>
783
+ )}
784
+
756
785
  {/* Footer controls - only when running */}
757
786
  {isRunning && (
758
787
  <div className={`flex flex-shrink-0 items-center gap-2 px-3 py-2 ${footerClassName || 'border-t border-void-700 bg-void-800'}`}>
@@ -17,7 +17,7 @@ interface TerminalProps {
17
17
  bridgeUrl?: string;
18
18
  tintColor?: string;
19
19
  onConnectionChange?: (connected: boolean) => void;
20
- onSessionExit?: (code: number) => void;
20
+ onSessionExit?: (code: number, output?: string) => void;
21
21
  onReady?: (handle: TerminalHandle) => void;
22
22
  onImagePaste?: (file: File) => void;
23
23
  }
@@ -375,7 +375,7 @@ export const Terminal = forwardRef<TerminalHandle, TerminalProps>(function Termi
375
375
  try {
376
376
  const msg = JSON.parse(event.data);
377
377
  terminal.write(`\r\n\x1b[33mSession exited (code: ${msg.code})\x1b[0m\r\n`);
378
- onSessionExitRef.current?.(msg.code);
378
+ onSessionExitRef.current?.(msg.code, msg.output);
379
379
  } catch {
380
380
  terminal.write('\r\n\x1b[33mSession exited\x1b[0m\r\n');
381
381
  }