Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Self-hosted multi-model AI chat platform powered by the official `@github/copilo
| Language | TypeScript 5.7 (strict mode, ES2022) |
| Framework | SvelteKit 5 with `adapter-node` |
| Reactivity | Svelte 5 runes ($state, $derived, $effect, $props) |
| AI Engine | `@github/copilot-sdk` ^0.1.32 |
| AI Engine | `@github/copilot-sdk` ^1.0.0 (client `mode: "empty"`) |
| WebSocket | `ws` ^8.18 via custom server.js |
| Markdown | `marked` + `dompurify` + `highlight.js` (npm, bundled by Vite) |
| Security | Custom CSP/HSTS in hooks.server.ts, rate limiting, DOMPurify |
Expand Down Expand Up @@ -180,6 +180,7 @@ src/
| `VAPID_SUBJECT` | No | — | VAPID subject (mailto: or https: URL) |
| `PUSH_STORE_PATH` | No | ./data/push-subscriptions | Directory for push subscription storage |
| `ENABLE_REMOTE_SESSIONS` | No | true | Enable cloud/remote session publishing on the SDK client. Sessions still need per-session `remoteSession: "export"|"on"` opt-in. Set to `false` to hard-disable. |
| `COPILOT_CLIENT_MODE` | No | empty | SDK client mode: `empty` (multi-user safe; features re-enabled per session via `buildEmptyModeSessionDefaults()`) or `copilot-cli` (full ambient capabilities) |

## Build & Run

Expand Down
26 changes: 23 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ jobs:
needs: check
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
project: [desktop, mobile]
steps:
- uses: actions/checkout@v4

Expand All @@ -96,11 +100,27 @@ jobs:
- name: Build
run: npm run build

- name: Get Playwright version
id: playwright-version
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"

- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}

- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium

- name: Run Playwright tests
run: npx playwright test --project=desktop
- name: Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps chromium

- name: Run Playwright tests (${{ matrix.project }})
run: npx playwright test --project=${{ matrix.project }} --reporter=github,html
env:
PORT: '3001'
GITHUB_CLIENT_ID: test-client-id
Expand All @@ -111,7 +131,7 @@ jobs:
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
name: playwright-report-${{ matrix.project }}
path: playwright-report/
retention-days: 7

Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@

- **Every Copilot model** — Claude Opus 4.6, GPT-5.4, Gemini 3 Pro, Claude Sonnet 4.6, and more — switch mid-conversation, keep full history
- **Autopilot agents** — plan, code, run tests, and open PRs autonomously with live tool execution
- **Remote session publishing** — opt sessions into being visible on github.com / Mobile via `remoteSession: "export" | "on"` (powered by SDK 1.0.0-beta.8); the browser app stays the steering surface, GitHub gets monitor visibility. Server-side toggle: `ENABLE_REMOTE_SESSIONS` (default on); per-session opt-in still required.
- **Remote session publishing** — opt sessions into being visible on github.com / Mobile via the Remote Sessions setting (Off / Export / Full remote); a banner links to the live session on GitHub. Server-side toggle: `ENABLE_REMOTE_SESSIONS` (default on); per-session opt-in still required.
- **Cloud sessions** — create sessions that run on GitHub's cloud agent against any repository (owner/repo/branch form in the Sessions panel)
- **Resume last session** — `GET /api/sessions/last` returns metadata for the user's most recent local session for one-tap continue-on-any-device flows
- **Extended thinking** — live reasoning traces with collapsible "Thinking…" blocks
- **Voice input** — speech-to-text via Web Speech API; mic button replaces send when input is empty (ChatGPT-style UX) — toggle in Settings
Expand Down Expand Up @@ -140,7 +141,8 @@ Open [localhost:3000](http://localhost:3000). Log in with GitHub. Done.
| `VAPID_PRIVATE_KEY` | — | Push notifications (base64url) |
| `VAPID_SUBJECT` | — | Push subject (`mailto:` or `https:`) |
| `PUSH_STORE_PATH` | `/data/push-subscriptions` | Push subscription storage |
| `ENABLE_REMOTE_SESSIONS` | `true` | Allow sessions to opt into cloud publishing (`remoteSession: "export"\|"on"`). Set to `false` to hard-disable. |
| `ENABLE_REMOTE_SESSIONS` | `true` | Allow sessions to opt into cloud publishing (`remoteSession: "export"\|"on"`) and cloud-agent sessions. Set to `false` to hard-disable. |
| `COPILOT_CLIENT_MODE` | `empty` | SDK client mode. `empty` (recommended for servers) starts with all ambient capabilities off and re-enables only what the app needs. Set to `copilot-cli` to restore full CLI-equivalent behavior. |

</details>

Expand Down Expand Up @@ -209,17 +211,21 @@ The Sessions panel auto-refreshes every 30 seconds. Use `COPILOT_CONFIG_DIR` to

</details>

### Remote session publishing (SDK 1.0.0-beta.8)
### Remote session publishing

A chat can opt into being **published** to github.com / Copilot Mobile by passing one of these values when the session is created:
A chat can opt into being **published** to github.com / Copilot Mobile. Pick the default in **Settings → Remote Sessions** (applies to new sessions, or hit "Apply to current session"):

| Mode | Effect |
|---|---|
| `"off"` *(default)* | Local only. Nothing leaves the server. |
| `"export"` | Read-only mirror — session events stream to GitHub so it shows up on github.com/copilot and Mobile in monitor mode. |
| `"on"` | Full remote-steerable — the session is steerable from github.com / Mobile as well as from this app. |

This is sent over WebSocket as `{ type: "new_session", remoteSession: "on", ... }` and threaded through to the SDK's `sessionConfig.remoteSession`. The server-wide kill switch is `ENABLE_REMOTE_SESSIONS=false`.
This is sent over WebSocket as `{ type: "new_session", remoteSession: "on", ... }` and threaded through to the SDK's `sessionConfig.remoteSession`; runtime toggling uses `{ type: "remote_toggle", mode }` (SDK `session.rpc.remote`). When GitHub returns the session URL, the app shows a banner with an "Open on GitHub" link. The server-wide kill switch is `ENABLE_REMOTE_SESSIONS=false`.

### Cloud sessions (GitHub cloud agent)

From the Sessions panel, **New cloud session** creates a session that runs on GitHub's cloud agent infrastructure instead of locally — give it a repository (`owner` / `name` / optional `branch`) and the agent works against that repo. Sent as `{ type: "new_cloud_session", repository }`; the session ID is assigned by GitHub. Requires `ENABLE_REMOTE_SESSIONS` and cloud-agent entitlements on your account.

> **What's not in this release:** the app does **not** include an in-app browser for *other* remote sessions (the ones running elsewhere on your account) and does **not** let you steer arbitrary remote sessions from this UI — the SDK exposes no public REST endpoint for listing them, and the github.com remote-sessions view talks to an internal API that requires a Copilot bearer integrators can't currently mint. To view all your remote sessions, use github.com or the Copilot Mobile app. PRs welcome once the SDK surfaces a public list API.

Expand Down Expand Up @@ -334,7 +340,7 @@ Device Flow OAuth (same as GitHub CLI). Tokens are server-side only, never sent

## Built With

SvelteKit 5 · Svelte 5 runes · TypeScript 5.7 · Node.js 24 · [`@github/copilot-sdk`](https://github.com/github/copilot-sdk) v1.0.0-beta.8 · Vite · `ws` · Web Speech API · Vitest · Playwright · Docker · Bicep
SvelteKit 5 · Svelte 5 runes · TypeScript 5.7 · Node.js 24 · [`@github/copilot-sdk`](https://github.com/github/copilot-sdk) v1.0.0 · Vite · `ws` · Web Speech API · Vitest · Playwright · Docker · Bicep

## Contributing

Expand Down
13 changes: 12 additions & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Browser (Svelte 5 SPA)
| Language | TypeScript 5.7 (strict mode, ES2022) |
| Framework | SvelteKit 5 with `adapter-node` |
| Reactivity | Svelte 5 runes ($state, $derived, $effect, $props) |
| AI Engine | `@github/copilot-sdk` ^0.2.0 |
| AI Engine | `@github/copilot-sdk` ^1.0.0 (client `mode: "empty"` by default) |
| Real-time | WebSocket (`ws` ^8.18) via custom `server.js` entry |
| Markdown | `marked` + `dompurify` + `highlight.js` |
| Security | Custom CSP/HSTS headers in hooks.server.ts, rate limiting, DOMPurify |
Expand Down Expand Up @@ -363,6 +363,17 @@ All push API endpoints require GitHub authentication.
| `CHAT_STATE_PATH` | — | `.chat-state` (dev) / `/data/chat-state` (prod) | Persisted chat state directory |
| `PUSH_STORE_PATH` | — | `/data/push-subscriptions` | Push subscription storage |
| `COPILOT_CONFIG_DIR` | — | `~/.copilot` | SDK config/session directory (Azure: `/data/copilot-home`) |
| `COPILOT_CLIENT_MODE` | — | `empty` | SDK client mode — `empty` (multi-user safe; app re-enables features per session) or `copilot-cli` (full CLI-equivalent ambient capabilities) |
| `ENABLE_REMOTE_SESSIONS` | — | `true` | Allow remote publishing (`remoteSession`) and cloud-agent sessions; `false` hard-disables |

### SDK client mode

The server creates each per-user `CopilotClient` with `mode: "empty"` (SDK 1.0.0): sessions start with no ambient capabilities, and `buildEmptyModeSessionDefaults()` in `src/lib/server/copilot/session.ts` explicitly re-enables what the app needs — all built-in/MCP/custom tools via `ToolSet`, skills, config discovery, host git operations, the session store, on-demand instruction discovery, persistent MCP OAuth tokens, and embedding cache. File hooks, telemetry, and plugins stay off. Set `COPILOT_CLIENT_MODE=copilot-cli` to restore the old behavior.

### Remote & cloud sessions

- **Remote publishing** — `new_session` accepts `remoteSession: "off"|"export"|"on"`; `remote_toggle` flips it at runtime via `session.rpc.remote.enable()/disable()`. The remote URL arrives via the SDK `session.info` event (`infoType: "remote"`) and is forwarded as `remote_session_url`; the UI renders a banner linking to github.com.
- **Cloud sessions** — `new_cloud_session` (validated `repository: { owner, name, branch? }`) creates a session with `cloud: { repository }` running on GitHub's cloud agent; the session ID is server-assigned. Handlers: `src/lib/server/ws/message-handlers/cloud-session.ts` and `remote.ts`.

## Deployment

Expand Down
Binary file modified docs/screenshots/login-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/login-ipad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/login-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/usecase-autopilot-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/usecase-autopilot-ipad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/usecase-autopilot-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/usecase-code-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/usecase-code-ipad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/usecase-code-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/usecase-reasoning-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/usecase-reasoning-ipad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/usecase-reasoning-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 40 additions & 40 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"node": ">=24.0.0"
},
"dependencies": {
"@github/copilot-sdk": "1.0.0-beta.8",
"@github/copilot-sdk": "^1.0.0",
"@sveltejs/adapter-node": "^5.5.4",
"@sveltejs/kit": "^2.61.1",
"dompurify": "^3.4.7",
Expand Down
1 change: 1 addition & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default defineConfig({
GITHUB_CLIENT_ID: 'test-client-id',
SESSION_SECRET: 'test-secret-for-playwright',
NODE_ENV: 'development',
E2E_DISABLE_RATE_LIMIT: 'true',
},
},
});
5 changes: 5 additions & 0 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ setInterval(() => {
}, RATE_LIMIT_WINDOW);

const rateLimit: Handle = async ({ event, resolve }) => {
// Never allow test-only rate-limit bypass in production.
if (process.env.E2E_DISABLE_RATE_LIMIT === 'true' && process.env.NODE_ENV !== 'production') {
return resolve(event);
}

const ip = event.getClientAddress();
const now = Date.now();

Expand Down
3 changes: 2 additions & 1 deletion src/lib/components/chat/ChatMessage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
if (message.cost != null) parts.push(`cost: ${message.cost}×`);
if (message.duration != null) parts.push(`${message.duration}ms`);
const premium = message.copilotUsage?.reduce((acc, item) => acc + (item.premiumRequests ?? 0), 0) ?? 0;
if (premium > 0) parts.push(`premium: ${premium}`);
if (premium > 0) parts.push(`AIC: ${premium}`);
return parts.length > 0 ? `tokens — ${parts.join(' · ')}` : '';
});

Expand All @@ -81,6 +81,7 @@
mcpToolName: message.mcpToolName,
status: message.toolStatus ?? 'running',
message: message.toolProgressMessage,
error: message.toolError,
progressMessages: message.toolProgressMessages,
};
});
Expand Down
Loading
Loading