CRITICAL FIX: Remove recursion from listDirectoryForTree function.
The backend was recursively fetching ALL subdirectories and returning
them as a flat list, which completely broke the lazy-loading model.
Changes:
- Remove recursive call in listDirectoryForTree
- Backend now returns ONLY immediate children
- Frontend handles lazy loading by requesting children on expand
- This matches the intended architecture where frontend controls tree
This fixes the issue where directory contents were duplicated and
the tree structure was corrupted when expanding/collapsing.
- Add debug logging to track project directory resolution
- Improve path traversal security check with proper separator handling
- Log entry count and first 10 entries for debugging
- Fix project root validation to prevent listing workspace root
The issue was that the tree was listing from the workspace root
(containing gadget-code/, gadget-drone/, packages/) instead of the
specific project directory. Added logging to help diagnose.
Add end-to-end abort support: AbortSignal in @gadget/ai providers,
abortWorkOrder socket message, drone AbortController handling,
Cancel button and double-Esc in frontend, and aborted turn status display.
1. Vite config: make HTTPS conditional on SSL cert/key files existing
(pre-existing issue, broke builds when certs not present)
2. Drone reconnect handler: use socket.io Manager-level 'reconnect'
event (this.socket.io.on) instead of Socket-level event, and add
explicit type annotation for attemptNumber parameter
This commit addresses two interrelated issues causing drones to
de-register and users to be forcibly signed out:
## Heartbeat Timeout Fixes
1. Move heartbeat interval to a Web Worker (not subject to browser
tab throttling). Chrome throttles setInterval in background tabs
to ~1/min, which causes the 19s heartbeat to miss the drone's
timeout timer. The Web Worker fires reliably regardless of tab
visibility.
2. Add visibilitychange handler: when the tab becomes visible again,
send an immediate heartbeat to reset the drone's timer after any
throttling that may have occurred.
3. Fix onReleaseSessionLock to clear the heartbeat timer. Previously,
releasing the lock left the 60s timer running, causing a spurious
timeout and status emit after the lock was already released.
4. Increase drone heartbeat timeout from 60s to 120s. With the Web
Worker fix, heartbeats should be reliable, but doubling the timeout
provides a generous safety margin.
5. Add socket disconnect/reconnect handlers on the drone side. On
disconnect, clear the heartbeat timer. On reconnect, re-emit drone
status so the platform knows the drone is alive.
6. Configure Socket.IO pingInterval/pingTimeout explicitly (25s/60s)
instead of relying on defaults.
## JWT Expiration Fixes
1. Increase WebToken DB record expiration from 1 hour to 7 days. The
1-hour expiration was the real session lifetime gate (the JWT crypto
exp was already 24h), and it was far too aggressive for a dev tool.
2. Fix web /auth/renew-token endpoint to use req.user from the session
cookie instead of verifyJsonWebToken(req.body.token). This eliminates
the catch-22 where an expired token cannot be used to request its
own renewal.
3. Fix token refresh response parsing. The API v1 renew-token endpoint
returns { success: true, token } at the top level, but the frontend
was looking for json.data?.token, causing every refresh to fail.
4. Add proactive token refresh: check the JWT exp claim before each
request and refresh if expiring within 5 minutes. This avoids
unnecessary 401 errors and the resulting socket disconnections.
5. Update socket JWT on token renewal via a callback registered in
App.tsx. This ensures that future socket reconnections use the new
token instead of the expired one.
## Files Modified
- gadget-code/frontend/src/workers/heartbeat.worker.ts (NEW)
- gadget-code/frontend/src/lib/socket.ts
- gadget-code/frontend/src/lib/api.ts
- gadget-code/frontend/src/App.tsx
- gadget-code/src/services/session.ts
- gadget-code/src/controllers/auth.ts
- gadget-code/src/services/socket.ts
- gadget-drone/src/gadget-drone.ts
- Add isProcessingWorkOrder flag to track Agent work order processing
- Update onRequestWorkspaceMode with mode transition matrix validation
- Idle → User/Agent: Always allowed
- User → Agent: Always allowed (file editor checks for future)
- Agent → User: Only if !isProcessingWorkOrder
- All other transitions: Rejected with reason
- Extend RequestWorkspaceModeCallback with optional reason parameter
- Update frontend socket client to capture rejection reason
- Update handleWorkspaceModeChange to display rejection reason in toast
- Update WorkspaceModeIndicator to allow mode transitions per matrix
- Fix FilesPanel RW/RO indicator swap bug
- Document mode transition matrix and behavior in workspace-management.md
- Create WorkspaceService for managing .gadget/ directory
- Implement workspace.json for persistent identity (workspaceId UUID)
- Add work order cache for crash recovery
- Update drone registration to include workspaceId
- Add crash recovery socket events (requestCrashRecovery, crashRecoveryResponse)
- Implement crash recovery handler in DroneSession
- Write work order cache before processing, remove after completion
- Resolve duplicate DroneStatus enum (import from @gadget/api)
- Fix IAiProvider interface conflict with DB→runtime mapper
- Add callId to ToolCallMessage and ChatToolCallSchema
- Fix ChatTurnStats schema field name (thinkingTokenCount)
- Add provider/selectedModel to ChatSession interface and model
- Implement CodeSession.onSubmitPrompt() to create ChatTurn and send work orders
- Add drone/chat session tracking to CodeSession
- Add unit tests for CodeSession (9 tests, all passing)