Commit 75d1f65
feat: v4.0.0 — session persistence, PWA push, UI overhaul, Azure hardening
* feat: session persistence, push notifications, PWA, security hardening
- Session persistence: ChatStateStore persists chat history to disk, cold resume on reconnect
- Tab ID switched to localStorage for browser-close survival
- Session watcher: fs.watch with debounce for CLI↔browser autosync
- PWA: manifest.json, service worker with precaching + push handling
- Push notifications: web-push with VAPID, subscription store, server-side push when WS disconnected
- Security: auth guards on all endpoints, token revalidation, CSP hardening, CSRF, SSRF protection
- Azure: VNet with 3 subnets, Key Vault, Premium ACR, storage hardening, diagnostics
- Docker: new data dirs, VAPID env vars, volume mount updates
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: update architecture, security, readme, copilot-instructions post-implementation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: lazy-init singletons to avoid config access at build time
push-singleton and chat-state-singleton were eagerly calling config.*
at module import time, causing npm run build to fail with
'Missing required env var: SESSION_SECRET' during SvelteKit's
post-build analysis step.
Fixed by wrapping both singletons in Proxy with lazy initialization —
config is only accessed on first actual method call at runtime.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: add VAPID and data path vars to .env.example
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: update app logo with new Copilot Unleashed SVG branding
- Add new logo SVG (logo-copilot unleashed.svg → logo.svg) with dark
gradient brackets, spark effect, and sparkle stars on #09090b background
- Regenerate all PNG icons (favicon, 192, 512, maskable) from new SVG
- Update TopBar.svelte and DeviceFlowLogin.svelte: /img/logo.png → /img/logo.svg
- Update app.html: add SVG favicon link (rel=icon svg+xml) + apple-touch-icon uses icon-192
- Update manifest.json: add SVG icon entry (sizes=any) as first icon
- Update generate-pwa-icons.mjs: use logo.svg as source instead of favicon.png
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: use logo without background for in-app display, add favicon.svg
- Create logo-no-bg.svg: same graphics as logo.svg but no dark background
rect or border — transparent background for use in header and login page
- TopBar.svelte + DeviceFlowLogin.svelte: switch to logo-no-bg.svg
- Add static/favicon.svg (full icon with background) for browser favicon
- app.html: point SVG favicon link to /favicon.svg (root-level, conventional)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: remove old logo files
- Remove original 'logo-copilot unleashed.svg' (now logo.svg/logo-no-bg.svg)
- Remove logo.png (replaced by SVG logos)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: restore chat history on warm reconnect and persist during resume
Two bugs that caused conversation history to not show after browser reopen:
1. Warm reconnect (pool entry still alive within TTL window) sent
session_reconnected without any message history. Now loads persisted
state and sends cold_resume so the client repopulates messages.
2. wireSessionEvents in resume-session.ts was called without userLogin
and tabId, so assistant messages from resumed sessions were never
persisted to ChatStateStore. Now passes both parameters.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: support concurrent permission prompts and fix file attachment z-index
Bug 1 — Permission queue:
Previously, every layer used a single-slot for permission requests.
When the SDK fired multiple concurrent permission requests, each new
one overwrote the previous — losing its resolve callback forever.
Now uses Map<requestId, resolver> at every layer:
- PoolEntry: permissionResolves Map + pendingPermissionPrompts Map
- makePermissionHandler: adds to map instead of overwriting
- handlePermissionResponse: looks up by requestId from client
- Reconnect: re-sends ALL pending permission prompts
- Client chat store: pendingPermissions array (was single | null)
- Page template: {#each} over all pending permissions
Bug 2 — File attachment preview clipped:
The .file-preview-row was inside .input-container which has
overflow: hidden (for border-radius clipping). Moved the file
preview row outside and above .input-container so it renders in
the normal .input-area flow without being clipped.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ui): align user messages right, Copilot left for visual distinction
User messages now appear right-aligned with border-right, mirroring
universal chat UX (WhatsApp/iMessage). Assistant messages remain
left-aligned with border-left. No color change — layout alone
communicates sender identity clearly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(sessions): filter empty SDK sessions from list, strip /home/node cwd
The Copilot SDK creates a session entry for every connection,
including ones where no messages were ever sent. These appear in
the session list as empty entries labelled '/home/node'.
Fix: after merging SDK + filesystem sessions, discard entries with
no title, no checkpoints, no plan, and a cwd that is just the
container home dir. Also strip cwd='/home/node' from enriched
metadata so it never surfaces in the UI.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(sessions): resume existing SDK session on page refresh instead of creating new one
Previously, every browser refresh sent new_session unconditionally,
creating a blank SDK session even when a previous session existed.
This caused the session list to fill up with empty '/home/node' entries.
New flow on 'connected':
- Wait up to 400ms for a cold_resume message from the server
- If cold_resume arrives with sdkSessionId → resume_session (no new session created)
- If cold_resume has no sdkSessionId (history but no live SDK session) → new_session
- If timeout fires (no cold_resume) → new_session as before
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* perf(sessions): eliminate 400ms delay — include sdkSessionId in connected message
Server now pre-loads persisted chat state before sending 'connected',
embedding sdkSessionId and hasPersistedState directly in the message.
Client decides instantly on receipt: resume_session if sdkSessionId
exists, requestNewSession otherwise. No timer, no delay, no race.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ui): loading animation during session restore, eliminate flash
Show a pulsing logo + 'Restoring session…' text while the server
loads persisted state and the SDK session resumes. The empty chat
(Banner) is never shown during restore — only after confirmation
that there is no previous session. sessionLoading starts true and
clears on cold_resume / session_created / session_resumed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(deployer): add deployer IP address parameter for ACR access control
* fix(ui): use skeleton shimmer for session loading, fix stuck state
Replace custom logo pulse animation with the app's standard skeleton
shimmer bars (same pattern as SessionsSheet loading). Fixes:
- sessionLoading now also clears on session_reconnected
- If resume fails (error message while loading), auto-fallback to
requestNewSession() instead of staying stuck forever
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ui): redesign Banner and TopBar following ChatGPT/Claude patterns
Banner (empty chat):
- Logo centered + 'Hello, {username}' greeting (like Claude/Gemini)
- Simplified hints on one line, no version number
- Clean, focused, inviting
TopBar:
- Shows 'New chat' text when no session title (no redundant logo)
- Session title shown when active session exists
- Removed brand-group/logo/gradient from top bar (logo lives in Banner)
Login page: unchanged (already well-designed)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: bump version to 4.0.0
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(infra): wire deployerIpAddress to Bicep params, auto-detect IP preprovision
- Add deployerIpAddress to main.parameters.json so azd passes it to Bicep
- Add preprovision hook to auto-detect current public IP via ipify.org
- This ensures ACR IP allowlist is always set to the deployer's current IP
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ui): enhance attachment menu positioning and visibility in ChatInput component
* feat(ui): add brand group to TopBar for improved visibility when no session title
* docs: consolidate duplicate feature entries in README
Merge 'Persistent sessions' + 'Session persistence' into one entry.
Merge 'CLI ↔ Browser sync' + 'CLI ↔ Browser autosync' into one entry.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: remove broken dev:local script and all references
The dev:local script bypassed server.js (express-session + WebSocket),
so it never worked properly. Keep only npm run dev (Docker Compose).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ui): remove New Chat button from TopBar (keep in Sidebar only)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update infra/modules/storage.bicep
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Potential fix for code scanning alert no. 10: Clear-text logging of sensitive information
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* refactor(infra): lean architecture — drop VNet, scale to zero, ACR Basic
BREAKING: Complete infrastructure overhaul for cost/simplicity:
Removed (900+ lines deleted):
- VNet + 3 subnets + 3 NSGs + 3 private endpoints + 3 DNS zones
- App Insights + diagnostics module
- Storage account (subscription policy blocks shared key access)
- ACR Premium → Basic ($5/mo vs $50/mo)
- abbreviations.json + container-apps.json ARM template
Simplified:
- Key Vault: RBAC-only (no network restrictions, no PE)
- ACR: Basic SKU, deployer IP allowlist (no PE)
- Container Apps: no VNet, scale-to-zero (minReplicas: 0), EmptyDir volume
- Monitoring: Log Analytics only (no App Insights)
- Naming: readable cu-{env} pattern with 4-char hash for global uniqueness
Volume: EmptyDir (ephemeral) — subscription Azure Policy blocks
allowSharedKeyAccess on storage accounts, preventing SMB mounts.
Data at /data persists across container restarts but not scaling events.
Cost: ~$85-90/mo → ~$10-20/mo (75-85% savings)
Security: RBAC + managed identity (same protection, less complexity)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Potential fix for code scanning alert no. 14: Clear-text logging of sensitive information
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* fix(security): resolve GHAS findings — ReDoS regex + CI permissions
- file-mentions.ts: Replace nested quantifier regex with flat character
class to eliminate catastrophic backtracking (GHAS #9, high severity)
- ci.yml: Add explicit permissions (contents: read) to all workflow jobs
following least-privilege principle (GHAS #5, #6, #7, medium severity)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>1 parent 58d2788 commit 75d1f65
89 files changed
Lines changed: 4979 additions & 374 deletions
File tree
- .github
- workflows
- docs
- infra
- modules
- scripts
- src
- lib
- components
- server
- auth
- copilot
- push
- ws
- message-handlers
- stores
- types
- utils
- routes
- api
- client-error
- push
- subscribe
- unsubscribe
- vapid-key
- skills
- version
- static
- img
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
20 | 24 | | |
21 | 25 | | |
22 | 26 | | |
| |||
46 | 50 | | |
47 | 51 | | |
48 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
49 | 63 | | |
50 | 64 | | |
51 | 65 | | |
52 | | - | |
| 66 | + | |
53 | 67 | | |
54 | 68 | | |
55 | 69 | | |
56 | 70 | | |
57 | 71 | | |
58 | | - | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
59 | 91 | | |
60 | | - | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
61 | 96 | | |
62 | 97 | | |
63 | 98 | | |
64 | | - | |
| 99 | + | |
65 | 100 | | |
66 | 101 | | |
67 | 102 | | |
| |||
76 | 111 | | |
77 | 112 | | |
78 | 113 | | |
79 | | - | |
80 | | - | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
81 | 119 | | |
82 | 120 | | |
83 | 121 | | |
| |||
105 | 143 | | |
106 | 144 | | |
107 | 145 | | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
108 | 150 | | |
109 | 151 | | |
110 | 152 | | |
| |||
113 | 155 | | |
114 | 156 | | |
115 | 157 | | |
116 | | - | |
| 158 | + | |
117 | 159 | | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
118 | 163 | | |
119 | 164 | | |
120 | 165 | | |
| |||
126 | 171 | | |
127 | 172 | | |
128 | 173 | | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
129 | 179 | | |
130 | 180 | | |
131 | 181 | | |
| |||
138 | 188 | | |
139 | 189 | | |
140 | 190 | | |
141 | | - | |
142 | 191 | | |
143 | 192 | | |
144 | 193 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
9 | 12 | | |
10 | 13 | | |
11 | 14 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
17 | 16 | | |
18 | 17 | | |
19 | 18 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | | - | |
55 | | - | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
| |||
143 | 144 | | |
144 | 145 | | |
145 | 146 | | |
146 | | - | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
147 | 222 | | |
148 | 223 | | |
149 | 224 | | |
| |||
170 | 245 | | |
171 | 246 | | |
172 | 247 | | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
173 | 253 | | |
174 | 254 | | |
175 | 255 | | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
176 | 264 | | |
177 | 265 | | |
178 | 266 | | |
179 | 267 | | |
180 | 268 | | |
181 | 269 | | |
182 | | - | |
| 270 | + | |
183 | 271 | | |
184 | 272 | | |
185 | 273 | | |
| |||
303 | 391 | | |
304 | 392 | | |
305 | 393 | | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
306 | 397 | | |
307 | 398 | | |
308 | 399 | | |
309 | 400 | | |
310 | 401 | | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
311 | 427 | | |
312 | 428 | | |
313 | 429 | | |
| |||
0 commit comments