Compare commits
12 Commits
v0.13.1-de
...
v0.13.3-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d6a5bcdc0 | ||
|
|
514b187b00 | ||
|
|
240acb7729 | ||
|
|
0af79002ed | ||
|
|
f3981a1cce | ||
|
|
031e8d5717 | ||
|
|
995fb3b6a3 | ||
|
|
aeb0ff11b3 | ||
|
|
b61cfbd9f9 | ||
|
|
481dd1a88a | ||
|
|
3f6cdd36f3 | ||
|
|
fe932c8307 |
171
README.md
171
README.md
@@ -1,128 +1,127 @@
|
|||||||
# CodeNomad
|
# CodeNomad
|
||||||
|
|
||||||
## A fast, multi-instance workspace for running OpenCode sessions.
|
## The AI Coding Cockpit for OpenCode
|
||||||
|
|
||||||
CodeNomad is built for people who live inside OpenCode for hours on end and need a cockpit, not a kiosk. It delivers a premium, low-latency workspace that favors speed, clarity, and direct control.
|
CodeNomad transforms OpenCode from a terminal tool into a **premium desktop workspace** — built for developers who live inside AI coding sessions for hours and need control, speed, and clarity.
|
||||||
|
|
||||||
|
> OpenCode gives you the engine. CodeNomad gives you the cockpit.
|
||||||
|
|
||||||

|

|
||||||
_Manage multiple OpenCode sessions side-by-side._
|
|
||||||
|
|
||||||
<details>
|
---
|
||||||
<summary>📸 More Screenshots</summary>
|
|
||||||
|
|
||||||

|
## Features
|
||||||
_Global command palette for keyboard-first control._
|
|
||||||
|
|
||||||

|
- **🚀 Multi-Instance Workspace**
|
||||||
_Rich media previews for images and assets._
|
- **🌐 Remote Access**
|
||||||
|
- **🧠 Session Management**
|
||||||
|
- **🎙️ Voice Input & Speech**
|
||||||
|
- **🌳 Git Worktrees**
|
||||||
|
- **💬 Rich Message Experience**
|
||||||
|
- **⌨️ Command Palette**
|
||||||
|
- **📁 File System Browser**
|
||||||
|
- **🔐 Authentication & Security**
|
||||||
|
- **🔔 Notifications**
|
||||||
|
- **🎨 Theming**
|
||||||
|
- **🌍 Internationalization**
|
||||||
|
|
||||||

|
---
|
||||||
_Browser support via CodeNomad Server._
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Choose the way that fits your workflow:
|
### 🖥️ Desktop App
|
||||||
|
|
||||||
### 🖥️ Desktop App (Recommended)
|
Available as both Electron and Tauri builds — choose based on your preference.
|
||||||
The best experience. A native application (Electron-based) with global shortcuts, deeper system integration, and a dedicated window.
|
|
||||||
|
|
||||||
- **Download**: Grab the latest installer for macOS, Windows, or Linux from the [Releases Page](https://github.com/shantur/CodeNomad/releases).
|
Download the latest installer for your platform from [Releases](https://github.com/shantur/CodeNomad/releases).
|
||||||
- **Run**: Install and launch like any other app.
|
|
||||||
|
|
||||||
### 🦀 Tauri App (Experimental)
|
| Platform | Formats |
|
||||||
We are also working on a lightweight, high-performance version built with [Tauri](https://tauri.app). It is currently in active development.
|
|----------|---------|
|
||||||
|
| macOS | DMG, ZIP (Universal: Intel + Apple Silicon) |
|
||||||
- **Download**: Experimental builds are available on the [Releases Page](https://github.com/shantur/CodeNomad/releases).
|
| Windows | NSIS Installer, ZIP (x64, ARM64) |
|
||||||
- **Source**: Check out `packages/tauri-app` if you're interested in contributing.
|
| Linux | AppImage, deb, tar.gz (x64, ARM64) |
|
||||||
|
|
||||||
### 💻 CodeNomad Server
|
### 💻 CodeNomad Server
|
||||||
Run CodeNomad as a local server and access it via your web browser. Perfect for remote development (SSH/VPN) or running as a service.
|
|
||||||
|
Run as a local server and access via browser. Perfect for remote development.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx @neuralnomads/codenomad --launch
|
npx @neuralnomads/codenomad --launch
|
||||||
```
|
```
|
||||||
|
|
||||||
Full server/CLI documentation (flags + env vars, TLS, auth, remote access):
|
See [Server Documentation](packages/server/README.md) for flags, TLS, auth, and remote access.
|
||||||
- [packages/server/README.md](packages/server/README.md)
|
|
||||||
|
|
||||||
To see all available options:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @neuralnomads/codenomad --help
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🧪 Dev Releases
|
### 🧪 Dev Releases
|
||||||
Bleeding-edge builds are published as GitHub pre-releases and are generated automatically from the `dev` branch.
|
|
||||||
|
Bleeding-edge builds from the `dev` branch:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx @neuralnomads/codenomad-dev --launch
|
npx @neuralnomads/codenomad-dev --launch
|
||||||
```
|
```
|
||||||
|
|
||||||
## Highlights
|
---
|
||||||
|
|
||||||
- **Multi-Instance**: Juggle several OpenCode sessions side-by-side with tabs.
|
|
||||||
- **Long-Session Native**: Scroll through massive transcripts without hitches.
|
|
||||||
- **Command Palette**: A single global palette to jump tabs, launch tools, and control everything.
|
|
||||||
- **Deep Task Awareness**: Monitor background tasks and child sessions without losing flow.
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- **[OpenCode CLI](https://opencode.ai)**: Must be installed and available in your `PATH`.
|
- **[OpenCode CLI](https://opencode.ai)** — must be installed and in your `PATH`
|
||||||
- **Node.js 18+**: Required if running the CLI server or building from source.
|
- **Node.js 18+** — for server mode or building from source
|
||||||
|
|
||||||
## Troubleshooting
|
---
|
||||||
|
|
||||||
### macOS says the app is damaged
|
## Development
|
||||||
If macOS reports that "CodeNomad.app is damaged and can't be opened," Gatekeeper flagged the download because the app is not yet notarized. You can clear the quarantine flag after moving CodeNomad into `/Applications`:
|
|
||||||
|
|
||||||
```bash
|
CodeNomad is a monorepo built with:
|
||||||
xattr -l /Applications/CodeNomad.app
|
|
||||||
xattr -dr com.apple.quarantine /Applications/CodeNomad.app
|
|
||||||
```
|
|
||||||
|
|
||||||
After removing the quarantine attribute, launch the app normally. On Intel Macs you may also need to approve CodeNomad from **System Settings → Privacy & Security** the first time you run it.
|
|
||||||
|
|
||||||
### Linux (Wayland + NVIDIA): Tauri AppImage closes immediately
|
|
||||||
On some Wayland compositor + NVIDIA driver setups, WebKitGTK can fail to initialize its DMA-BUF/GBM path and the Tauri build may exit right away.
|
|
||||||
|
|
||||||
Try running with one of these environment variables:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Most reliable workaround (can reduce rendering performance)
|
|
||||||
WEBKIT_DISABLE_DMABUF_RENDERER=1 codenomad
|
|
||||||
|
|
||||||
# Alternative for some Wayland setups
|
|
||||||
__NV_DISABLE_EXPLICIT_SYNC=1 codenomad
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're running the Tauri AppImage and want the workaround applied every time, create a tiny wrapper script on your `PATH`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
export WEBKIT_DISABLE_DMABUF_RENDERER=1
|
|
||||||
exec ~/.local/share/bauh/appimage/installed/codenomad/CodeNomad-Tauri-0.4.0-linux-x64.AppImage "$@"
|
|
||||||
```
|
|
||||||
|
|
||||||
Upstream tracking: https://github.com/tauri-apps/tauri/issues/10702
|
|
||||||
|
|
||||||
## Architecture & Development
|
|
||||||
|
|
||||||
CodeNomad is a monorepo split into specialized packages. If you want to contribute or build from source, check out the individual package documentation:
|
|
||||||
|
|
||||||
| Package | Description |
|
| Package | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| **[packages/electron-app](packages/electron-app/README.md)** | The native desktop application shell. Wraps the UI and Server. |
|
| **[packages/server](packages/server/README.md)** | Core logic & CLI — workspaces, OpenCode proxy, API, auth, speech |
|
||||||
| **[packages/server](packages/server/README.md)** | The core logic and CLI. Manages workspaces, proxies OpenCode, and serves the API. |
|
| **[packages/ui](packages/ui/README.md)** | SolidJS frontend — reactive, fast, beautiful |
|
||||||
| **[packages/ui](packages/ui/README.md)** | The SolidJS-based frontend. Fast, reactive, and beautiful. |
|
| **[packages/electron-app](packages/electron-app/README.md)** | Desktop shell — process management, IPC, native dialogs |
|
||||||
|
| **[packages/tauri-app](packages/tauri-app)** | Tauri desktop shell (experimental) |
|
||||||
|
|
||||||
### Quick Build
|
### Quick Start
|
||||||
To build the Desktop App from source:
|
|
||||||
|
|
||||||
1. Clone the repo.
|
```bash
|
||||||
2. Run `npm install` (requires pnpm or npm 7+ for workspaces).
|
git clone https://github.com/NeuralNomadsAI/CodeNomad.git
|
||||||
3. Run `npm run build --workspace @neuralnomads/codenomad-electron-app`.
|
cd CodeNomad
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
[](https://star-history.com/#NeuralNomadsAI/CodeNomad&Date)
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>macOS: "CodeNomad.app is damaged and can't be opened"</strong></summary>
|
||||||
|
|
||||||
|
Gatekeeper flag due to missing notarization. Clear the quarantine attribute:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xattr -dr com.apple.quarantine /Applications/CodeNomad.app
|
||||||
|
```
|
||||||
|
|
||||||
|
On Intel Macs, also check **System Settings → Privacy & Security** on first launch.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Linux (Wayland + NVIDIA): Tauri App closes immediately</strong></summary>
|
||||||
|
|
||||||
|
WebKitGTK DMA-BUF/GBM issue. Run with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WEBKIT_DISABLE_DMABUF_RENDERER=1 codenomad
|
||||||
|
```
|
||||||
|
|
||||||
|
See full workaround in the original README.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
|
[](https://star-history.com/#NeuralNomadsAI/CodeNomad&Date)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Built with ♥ by [Neural Nomads](https://github.com/NeuralNomadsAI)** · [MIT License](LICENSE)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 845 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 835 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 966 KiB After Width: | Height: | Size: 1.1 MiB |
81
package-lock.json
generated
81
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "codenomad-workspace",
|
"name": "codenomad-workspace",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "codenomad-workspace",
|
"name": "codenomad-workspace",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"7zip-bin": "^5.2.0",
|
"7zip-bin": "^5.2.0",
|
||||||
@@ -64,7 +64,6 @@
|
|||||||
"version": "7.28.5",
|
"version": "7.28.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
@@ -3381,7 +3380,6 @@
|
|||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.20.7",
|
"@babel/parser": "^7.20.7",
|
||||||
"@babel/types": "^7.20.7",
|
"@babel/types": "^7.20.7",
|
||||||
@@ -3483,7 +3481,6 @@
|
|||||||
"version": "22.19.0",
|
"version": "22.19.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
@@ -3558,7 +3555,6 @@
|
|||||||
"integrity": "sha512-MCbrb508JZHqe7bUibmZj/lyojdhLRnfkmyXnkrCM2zVrjTgL89U8UEfInpKTvPeTnxsw2hmyZxnhsdNR6yhwg==",
|
"integrity": "sha512-MCbrb508JZHqe7bUibmZj/lyojdhLRnfkmyXnkrCM2zVrjTgL89U8UEfInpKTvPeTnxsw2hmyZxnhsdNR6yhwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
"colorette": "^2.0.20",
|
"colorette": "^2.0.20",
|
||||||
@@ -3641,7 +3637,6 @@
|
|||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -3844,6 +3839,7 @@
|
|||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver-utils": "^2.1.0",
|
"archiver-utils": "^2.1.0",
|
||||||
"async": "^3.2.4",
|
"async": "^3.2.4",
|
||||||
@@ -3861,6 +3857,7 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^7.1.4",
|
"glob": "^7.1.4",
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
@@ -3881,6 +3878,7 @@
|
|||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-util-is": "~1.0.0",
|
"core-util-is": "~1.0.0",
|
||||||
"inherits": "~2.0.3",
|
"inherits": "~2.0.3",
|
||||||
@@ -3894,12 +3892,14 @@
|
|||||||
"node_modules/archiver-utils/node_modules/safe-buffer": {
|
"node_modules/archiver-utils/node_modules/safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/archiver-utils/node_modules/string_decoder": {
|
"node_modules/archiver-utils/node_modules/string_decoder": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
@@ -4213,6 +4213,7 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer": "^5.5.0",
|
"buffer": "^5.5.0",
|
||||||
"inherits": "^2.0.4",
|
"inherits": "^2.0.4",
|
||||||
@@ -4276,7 +4277,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -4767,6 +4767,7 @@
|
|||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-crc32": "^0.2.13",
|
"buffer-crc32": "^0.2.13",
|
||||||
"crc32-stream": "^4.0.2",
|
"crc32-stream": "^4.0.2",
|
||||||
@@ -4896,6 +4897,7 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"crc32": "bin/crc32.njs"
|
"crc32": "bin/crc32.njs"
|
||||||
},
|
},
|
||||||
@@ -4907,6 +4909,7 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"crc-32": "^1.2.0",
|
"crc-32": "^1.2.0",
|
||||||
"readable-stream": "^3.4.0"
|
"readable-stream": "^3.4.0"
|
||||||
@@ -5272,7 +5275,6 @@
|
|||||||
"version": "24.13.3",
|
"version": "24.13.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-builder-lib": "24.13.3",
|
"app-builder-lib": "24.13.3",
|
||||||
"builder-util": "24.13.1",
|
"builder-util": "24.13.1",
|
||||||
@@ -5439,6 +5441,7 @@
|
|||||||
"version": "24.13.3",
|
"version": "24.13.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-builder-lib": "24.13.3",
|
"app-builder-lib": "24.13.3",
|
||||||
"archiver": "^5.3.1",
|
"archiver": "^5.3.1",
|
||||||
@@ -5450,6 +5453,7 @@
|
|||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^6.0.1",
|
"jsonfile": "^6.0.1",
|
||||||
@@ -5463,6 +5467,7 @@
|
|||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"universalify": "^2.0.0"
|
"universalify": "^2.0.0"
|
||||||
},
|
},
|
||||||
@@ -5474,6 +5479,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
@@ -6191,7 +6197,8 @@
|
|||||||
"node_modules/fs-constants": {
|
"node_modules/fs-constants": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
@@ -7408,7 +7415,8 @@
|
|||||||
"node_modules/isarray": {
|
"node_modules/isarray": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/isbinaryfile": {
|
"node_modules/isbinaryfile": {
|
||||||
"version": "5.0.6",
|
"version": "5.0.6",
|
||||||
@@ -7458,7 +7466,6 @@
|
|||||||
"version": "1.21.7",
|
"version": "1.21.7",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
@@ -7590,6 +7597,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"readable-stream": "^2.0.5"
|
"readable-stream": "^2.0.5"
|
||||||
},
|
},
|
||||||
@@ -7601,6 +7609,7 @@
|
|||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-util-is": "~1.0.0",
|
"core-util-is": "~1.0.0",
|
||||||
"inherits": "~2.0.3",
|
"inherits": "~2.0.3",
|
||||||
@@ -7614,12 +7623,14 @@
|
|||||||
"node_modules/lazystream/node_modules/safe-buffer": {
|
"node_modules/lazystream/node_modules/safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/lazystream/node_modules/string_decoder": {
|
"node_modules/lazystream/node_modules/string_decoder": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
@@ -7684,22 +7695,26 @@
|
|||||||
"node_modules/lodash.defaults": {
|
"node_modules/lodash.defaults": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/lodash.difference": {
|
"node_modules/lodash.difference": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/lodash.flatten": {
|
"node_modules/lodash.flatten": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isplainobject": {
|
"node_modules/lodash.isplainobject": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/lodash.sortby": {
|
"node_modules/lodash.sortby": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
@@ -7711,7 +7726,8 @@
|
|||||||
"node_modules/lodash.union": {
|
"node_modules/lodash.union": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/lowercase-keys": {
|
"node_modules/lowercase-keys": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -8515,7 +8531,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -8663,7 +8678,8 @@
|
|||||||
"node_modules/process-nextick-args": {
|
"node_modules/process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/process-warning": {
|
"node_modules/process-warning": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@@ -8912,6 +8928,7 @@
|
|||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"string_decoder": "^1.1.1",
|
"string_decoder": "^1.1.1",
|
||||||
@@ -8925,6 +8942,7 @@
|
|||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimatch": "^5.1.0"
|
"minimatch": "^5.1.0"
|
||||||
}
|
}
|
||||||
@@ -9227,7 +9245,6 @@
|
|||||||
"version": "4.52.5",
|
"version": "4.52.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
},
|
},
|
||||||
@@ -9451,7 +9468,6 @@
|
|||||||
"node_modules/seroval": {
|
"node_modules/seroval": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
@@ -9775,7 +9791,6 @@
|
|||||||
"node_modules/solid-js": {
|
"node_modules/solid-js": {
|
||||||
"version": "1.9.10",
|
"version": "1.9.10",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.1.0",
|
"csstype": "^3.1.0",
|
||||||
"seroval": "~1.3.0",
|
"seroval": "~1.3.0",
|
||||||
@@ -9916,6 +9931,7 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safe-buffer": "~5.2.0"
|
"safe-buffer": "~5.2.0"
|
||||||
}
|
}
|
||||||
@@ -10249,6 +10265,7 @@
|
|||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bl": "^4.0.3",
|
"bl": "^4.0.3",
|
||||||
"end-of-stream": "^1.4.1",
|
"end-of-stream": "^1.4.1",
|
||||||
@@ -10441,7 +10458,6 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -10691,7 +10707,6 @@
|
|||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -11039,7 +11054,6 @@
|
|||||||
"version": "5.4.21",
|
"version": "5.4.21",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.43",
|
"postcss": "^8.4.43",
|
||||||
@@ -11524,7 +11538,6 @@
|
|||||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fast-uri": "^3.0.1",
|
"fast-uri": "^3.0.1",
|
||||||
@@ -11719,7 +11732,6 @@
|
|||||||
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
},
|
},
|
||||||
@@ -12008,6 +12020,7 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver-utils": "^3.0.4",
|
"archiver-utils": "^3.0.4",
|
||||||
"compress-commons": "^4.1.2",
|
"compress-commons": "^4.1.2",
|
||||||
@@ -12021,6 +12034,7 @@
|
|||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^7.2.3",
|
"glob": "^7.2.3",
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
@@ -12040,7 +12054,6 @@
|
|||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.25.76",
|
"version": "3.25.76",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
@@ -12055,7 +12068,7 @@
|
|||||||
},
|
},
|
||||||
"packages/electron-app": {
|
"packages/electron-app": {
|
||||||
"name": "@neuralnomads/codenomad-electron-app",
|
"name": "@neuralnomads/codenomad-electron-app",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codenomad/ui": "file:../ui",
|
"@codenomad/ui": "file:../ui",
|
||||||
@@ -12092,7 +12105,7 @@
|
|||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^8.5.0",
|
"@fastify/cors": "^8.5.0",
|
||||||
@@ -12134,7 +12147,7 @@
|
|||||||
},
|
},
|
||||||
"packages/tauri-app": {
|
"packages/tauri-app": {
|
||||||
"name": "@codenomad/tauri-app",
|
"name": "@codenomad/tauri-app",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.9.4"
|
"@tauri-apps/cli": "^2.9.4"
|
||||||
@@ -12142,7 +12155,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@codenomad/ui",
|
"name": "@codenomad/ui",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@git-diff-view/solid": "^0.0.8",
|
"@git-diff-view/solid": "^0.0.8",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "codenomad-workspace",
|
"name": "codenomad-workspace",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "CodeNomad monorepo workspace",
|
"description": "CodeNomad monorepo workspace",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"build:mac-x64": "npm run build:mac-x64 --workspace @neuralnomads/codenomad-electron-app",
|
"build:mac-x64": "npm run build:mac-x64 --workspace @neuralnomads/codenomad-electron-app",
|
||||||
"build:binaries": "npm run build:binaries --workspace @neuralnomads/codenomad-electron-app",
|
"build:binaries": "npm run build:binaries --workspace @neuralnomads/codenomad-electron-app",
|
||||||
"typecheck": "npm run typecheck --workspace @codenomad/ui && npm run typecheck --workspace @neuralnomads/codenomad-electron-app",
|
"typecheck": "npm run typecheck --workspace @codenomad/ui && npm run typecheck --workspace @neuralnomads/codenomad-electron-app",
|
||||||
"bumpVersion": "npm version --workspaces --include-workspace-root --no-git-tag-version && npm run sync:version --workspace @codenomad/tauri-app"
|
"bumpVersion": "node ./scripts/bump-version.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"7zip-bin": "^5.2.0",
|
"7zip-bin": "^5.2.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"minServerVersion": "0.13.1",
|
"minServerVersion": "0.13.3",
|
||||||
"latestServerUrl": "https://github.com/NeuralNomadsAI/CodeNomad/releases/latest"
|
"latestServerUrl": "https://github.com/NeuralNomadsAI/CodeNomad/releases/latest"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,23 @@ export interface Env {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
async fetch(request: Request, env: Env): Promise<Response> {
|
async fetch(request: Request, env: Env): Promise<Response> {
|
||||||
|
const url = new URL(request.url)
|
||||||
|
|
||||||
|
if (url.pathname === "/version.json") {
|
||||||
|
const response = await env.ASSETS.fetch(request)
|
||||||
|
|
||||||
|
const newHeaders = new Headers(response.headers)
|
||||||
|
newHeaders.set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate")
|
||||||
|
newHeaders.set("Pragma", "no-cache")
|
||||||
|
newHeaders.set("Expires", "0")
|
||||||
|
|
||||||
|
return new Response(response.body, {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
headers: newHeaders,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return env.ASSETS.fetch(request)
|
return env.ASSETS.fetch(request)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@neuralnomads/codenomad-electron-app",
|
"name": "@neuralnomads/codenomad-electron-app",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"description": "CodeNomad - AI coding assistant",
|
"description": "CodeNomad - AI coding assistant",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/plugin": "1.3.2"
|
"@opencode-ai/plugin": "1.3.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4
packages/server/package-lock.json
generated
4
packages/server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^8.5.0",
|
"@fastify/cors": "^8.5.0",
|
||||||
"@fastify/reply-from": "^9.8.0",
|
"@fastify/reply-from": "^9.8.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@neuralnomads/codenomad",
|
"name": "@neuralnomads/codenomad",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"description": "CodeNomad Server",
|
"description": "CodeNomad Server",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
2
packages/tauri-app/Cargo.lock
generated
2
packages/tauri-app/Cargo.lock
generated
@@ -458,7 +458,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codenomad-tauri"
|
name = "codenomad-tauri"
|
||||||
version = "0.12.3"
|
version = "0.13.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@codenomad/tauri-app",
|
"name": "@codenomad/tauri-app",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "codenomad-tauri"
|
name = "codenomad-tauri"
|
||||||
version = "0.12.3"
|
version = "0.13.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "CodeNomad",
|
"productName": "CodeNomad",
|
||||||
"version": "0.12.3",
|
"version": "0.13.3",
|
||||||
"identifier": "ai.neuralnomads.codenomad.client",
|
"identifier": "ai.neuralnomads.codenomad.client",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev:bootstrap",
|
"beforeDevCommand": "npm run dev:bootstrap",
|
||||||
"beforeBuildCommand": "npm run bundle:server",
|
"beforeBuildCommand": "npm run bundle:server",
|
||||||
"frontendDist": "resources/ui-loading"
|
"frontendDist": "resources/ui-loading"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"app": {
|
"app": {
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
"windows": [
|
"windows": [
|
||||||
@@ -33,9 +30,13 @@
|
|||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"assetProtocol": {
|
"assetProtocol": {
|
||||||
"scope": ["**"]
|
"scope": [
|
||||||
|
"**"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"capabilities": ["main-window-native-dialogs"]
|
"capabilities": [
|
||||||
|
"main-window-native-dialogs"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
@@ -44,7 +45,17 @@
|
|||||||
"resources/server",
|
"resources/server",
|
||||||
"resources/ui-loading"
|
"resources/ui-loading"
|
||||||
],
|
],
|
||||||
"icon": ["icon.icns", "icon.ico", "icon.png"],
|
"icon": [
|
||||||
"targets": ["app", "appimage", "deb", "rpm", "nsis"]
|
"icon.icns",
|
||||||
|
"icon.ico",
|
||||||
|
"icon.png"
|
||||||
|
],
|
||||||
|
"targets": [
|
||||||
|
"app",
|
||||||
|
"appimage",
|
||||||
|
"deb",
|
||||||
|
"rpm",
|
||||||
|
"nsis"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@codenomad/ui",
|
"name": "@codenomad/ui",
|
||||||
"version": "0.13.1",
|
"version": "0.13.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
|||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
class="selector-button selector-button-secondary w-auto px-3 py-1.5 inline-flex items-center justify-center gap-1.5"
|
class="selector-button selector-button-secondary w-auto px-3 py-1.5 inline-flex items-center justify-center gap-1.5"
|
||||||
aria-label={t("folderSelection.links.githubStars")}
|
aria-label={t("folderSelection.links.githubStars")}
|
||||||
title={t("folderSelection.links.githubStars")}
|
title={githubStars() !== null ? `${t("folderSelection.links.githubStars")}: ${githubStars()!.toLocaleString()}` : t("folderSelection.links.githubStars")}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
void openExternalUrl(GITHUB_URL, "folder-selection")
|
void openExternalUrl(GITHUB_URL, "folder-selection")
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import SessionSidebar from "./shell/SessionSidebar"
|
|||||||
import { useSessionSidebarRequests } from "./shell/useSessionSidebarRequests"
|
import { useSessionSidebarRequests } from "./shell/useSessionSidebarRequests"
|
||||||
import RightPanel from "./shell/right-panel/RightPanel"
|
import RightPanel from "./shell/right-panel/RightPanel"
|
||||||
import { useDrawerChrome } from "./shell/useDrawerChrome"
|
import { useDrawerChrome } from "./shell/useDrawerChrome"
|
||||||
import { getSessionStatus } from "../../stores/session-status"
|
import { getRetrySeconds, getSessionRetry, getSessionStatus } from "../../stores/session-status"
|
||||||
import { Maximize2, ShieldAlert } from "lucide-solid"
|
import { Maximize2, ShieldAlert } from "lucide-solid"
|
||||||
|
|
||||||
import type { LayoutMode } from "./shell/types"
|
import type { LayoutMode } from "./shell/types"
|
||||||
@@ -104,6 +104,7 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||||||
const [selectedBackgroundProcess, setSelectedBackgroundProcess] = createSignal<BackgroundProcess | null>(null)
|
const [selectedBackgroundProcess, setSelectedBackgroundProcess] = createSignal<BackgroundProcess | null>(null)
|
||||||
const [showBackgroundOutput, setShowBackgroundOutput] = createSignal(false)
|
const [showBackgroundOutput, setShowBackgroundOutput] = createSignal(false)
|
||||||
const [permissionModalOpen, setPermissionModalOpen] = createSignal(false)
|
const [permissionModalOpen, setPermissionModalOpen] = createSignal(false)
|
||||||
|
const [now, setNow] = createSignal(Date.now())
|
||||||
|
|
||||||
// Worktree selector manages its own dialogs.
|
// Worktree selector manages its own dialogs.
|
||||||
const [showSessionSearch, setShowSessionSearch] = createSignal(false)
|
const [showSessionSearch, setShowSessionSearch] = createSignal(false)
|
||||||
@@ -237,6 +238,12 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||||||
window.localStorage.setItem(RIGHT_DRAWER_STORAGE_KEY, rightDrawerWidth().toString())
|
window.localStorage.setItem(RIGHT_DRAWER_STORAGE_KEY, rightDrawerWidth().toString())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (typeof window === "undefined") return
|
||||||
|
const timer = window.setInterval(() => setNow(Date.now()), 1000)
|
||||||
|
onCleanup(() => window.clearInterval(timer))
|
||||||
|
})
|
||||||
|
|
||||||
const connectionStatus = () => sseManager.getStatus(props.instance.id)
|
const connectionStatus = () => sseManager.getStatus(props.instance.id)
|
||||||
const connectionStatusClass = () => {
|
const connectionStatusClass = () => {
|
||||||
const status = connectionStatus()
|
const status = connectionStatus()
|
||||||
@@ -306,17 +313,28 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const status = getSessionStatus(props.instance.id, activeSessionId)
|
const status = getSessionStatus(props.instance.id, activeSessionId)
|
||||||
const text =
|
const retry = getSessionRetry(props.instance.id, activeSessionId)
|
||||||
status === "working"
|
const text = retry
|
||||||
|
? (() => {
|
||||||
|
const seconds = getRetrySeconds(retry.next, now())
|
||||||
|
return seconds > 0 ? t("sessionList.status.retryingIn", { seconds: String(seconds) }) : t("sessionList.status.retrying")
|
||||||
|
})()
|
||||||
|
: status === "working"
|
||||||
? t("sessionList.status.working")
|
? t("sessionList.status.working")
|
||||||
: status === "compacting"
|
: status === "compacting"
|
||||||
? t("sessionList.status.compacting")
|
? t("sessionList.status.compacting")
|
||||||
: t("sessionList.status.idle")
|
: t("sessionList.status.idle")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
className: `session-${status}`,
|
className: `session-${retry ? "retrying" : status}`,
|
||||||
text,
|
text,
|
||||||
showAlertIcon: false,
|
showAlertIcon: false,
|
||||||
|
title: retry
|
||||||
|
? t("sessionList.status.retryTooltip", {
|
||||||
|
message: retry.message,
|
||||||
|
attempt: String(retry.attempt),
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -324,7 +342,7 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||||||
const pill = activeSessionStatusPill()
|
const pill = activeSessionStatusPill()
|
||||||
if (!pill) return null
|
if (!pill) return null
|
||||||
return (
|
return (
|
||||||
<span class={`status-indicator session-status session-status-list ${pill.className}`}>
|
<span class={`status-indicator session-status session-status-list ${pill.className}`} title={pill.title}>
|
||||||
{pill.showAlertIcon ? <ShieldAlert class="w-3.5 h-3.5" aria-hidden="true" /> : <span class="status-dot" />}
|
{pill.showAlertIcon ? <ShieldAlert class="w-3.5 h-3.5" aria-hidden="true" /> : <span class="status-dot" />}
|
||||||
{pill.text}
|
{pill.text}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -123,7 +123,11 @@ export function Markdown(props: MarkdownProps) {
|
|||||||
version: () => resolved().version,
|
version: () => resolved().version,
|
||||||
})
|
})
|
||||||
|
|
||||||
const commitCacheEntry = (snapshot: ReturnType<typeof resolved>, renderedHtml: string) => {
|
const commitCacheEntry = (
|
||||||
|
snapshot: ReturnType<typeof resolved>,
|
||||||
|
renderedHtml: string,
|
||||||
|
options?: { cache?: boolean },
|
||||||
|
) => {
|
||||||
const cacheEntry: RenderCache = {
|
const cacheEntry: RenderCache = {
|
||||||
text: snapshot.text,
|
text: snapshot.text,
|
||||||
html: renderedHtml,
|
html: renderedHtml,
|
||||||
@@ -131,7 +135,9 @@ export function Markdown(props: MarkdownProps) {
|
|||||||
mode: `${snapshot.version}:${snapshot.escapeRawHtml ? "escaped" : "raw"}`,
|
mode: `${snapshot.version}:${snapshot.escapeRawHtml ? "escaped" : "raw"}`,
|
||||||
}
|
}
|
||||||
setHtml(renderedHtml)
|
setHtml(renderedHtml)
|
||||||
cacheHandle.set(cacheEntry)
|
if (options?.cache ?? true) {
|
||||||
|
cacheHandle.set(cacheEntry)
|
||||||
|
}
|
||||||
notifyRendered()
|
notifyRendered()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,9 +148,10 @@ export function Markdown(props: MarkdownProps) {
|
|||||||
suppressHighlight: !snapshot.highlightEnabled,
|
suppressHighlight: !snapshot.highlightEnabled,
|
||||||
escapeRawHtml: snapshot.escapeRawHtml,
|
escapeRawHtml: snapshot.escapeRawHtml,
|
||||||
})
|
})
|
||||||
|
const shouldCache = !snapshot.highlightEnabled || !markdown.hasPendingCodeHighlight(snapshot.text)
|
||||||
|
|
||||||
if (latestRequestKey === snapshot.requestKey) {
|
if (latestRequestKey === snapshot.requestKey) {
|
||||||
commitCacheEntry(snapshot, rendered)
|
commitCacheEntry(snapshot, rendered, { cache: shouldCache })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ import { usePromptAttachments } from "./prompt-input/usePromptAttachments"
|
|||||||
import { usePromptPicker } from "./prompt-input/usePromptPicker"
|
import { usePromptPicker } from "./prompt-input/usePromptPicker"
|
||||||
import { usePromptKeyDown } from "./prompt-input/usePromptKeyDown"
|
import { usePromptKeyDown } from "./prompt-input/usePromptKeyDown"
|
||||||
import { usePromptVoiceInput } from "./prompt-input/usePromptVoiceInput"
|
import { usePromptVoiceInput } from "./prompt-input/usePromptVoiceInput"
|
||||||
import { canUseConversationMode, isConversationModeEnabled, toggleConversationMode } from "../stores/conversation-speech"
|
import {
|
||||||
|
canUseConversationMode,
|
||||||
|
clearConversationPlaybackForInstance,
|
||||||
|
isConversationModeEnabled,
|
||||||
|
toggleConversationMode,
|
||||||
|
} from "../stores/conversation-speech"
|
||||||
const log = getLogger("actions")
|
const log = getLogger("actions")
|
||||||
const LazyUnifiedPicker = lazy(() => import("./unified-picker"))
|
const LazyUnifiedPicker = lazy(() => import("./unified-picker"))
|
||||||
|
|
||||||
@@ -492,6 +497,8 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
const beginVoicePress = (event?: PointerEvent | KeyboardEvent) => {
|
const beginVoicePress = (event?: PointerEvent | KeyboardEvent) => {
|
||||||
if (voiceButtonPressed || props.disabled || voiceInput.isTranscribing() || !voiceInput.canUseVoiceInput()) return
|
if (voiceButtonPressed || props.disabled || voiceInput.isTranscribing() || !voiceInput.canUseVoiceInput()) return
|
||||||
voiceButtonPressed = true
|
voiceButtonPressed = true
|
||||||
|
// Treat a mic press as barge-in: stop any active assistant speech before listening.
|
||||||
|
clearConversationPlaybackForInstance(props.instanceId)
|
||||||
|
|
||||||
if (event instanceof PointerEvent) {
|
if (event instanceof PointerEvent) {
|
||||||
const target = event.currentTarget
|
const target = event.currentTarget
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, For, Show, createSignal, createMemo, createEffect, JSX, onCleanup } from "solid-js"
|
import { Component, For, Show, createSignal, createMemo, createEffect, JSX, onCleanup } from "solid-js"
|
||||||
import type { SessionStatus } from "../types/session"
|
import type { SessionStatus } from "../types/session"
|
||||||
import type { SessionThread } from "../stores/session-state"
|
import type { SessionThread } from "../stores/session-state"
|
||||||
import { getSessionStatus } from "../stores/session-status"
|
import { getRetrySeconds, getSessionRetry, getSessionStatus } from "../stores/session-status"
|
||||||
import { Bot, User, Copy, Trash2, Pencil, ShieldAlert, ChevronDown, Search, Square, CheckSquare, MinusSquare, Split, RotateCw } from "lucide-solid"
|
import { Bot, User, Copy, Trash2, Pencil, ShieldAlert, ChevronDown, Search, Square, CheckSquare, MinusSquare, Split, RotateCw } from "lucide-solid"
|
||||||
import KeyboardHint from "./keyboard-hint"
|
import KeyboardHint from "./keyboard-hint"
|
||||||
import SessionRenameDialog from "./session-rename-dialog"
|
import SessionRenameDialog from "./session-rename-dialog"
|
||||||
@@ -55,6 +55,13 @@ const SessionList: Component<SessionListProps> = (props) => {
|
|||||||
|
|
||||||
const [selectedSessionIds, setSelectedSessionIds] = createSignal<Set<string>>(new Set())
|
const [selectedSessionIds, setSelectedSessionIds] = createSignal<Set<string>>(new Set())
|
||||||
const [reloadingSessionIds, setReloadingSessionIds] = createSignal<Set<string>>(new Set())
|
const [reloadingSessionIds, setReloadingSessionIds] = createSignal<Set<string>>(new Set())
|
||||||
|
const [now, setNow] = createSignal(Date.now())
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (typeof window === "undefined") return
|
||||||
|
const timer = window.setInterval(() => setNow(Date.now()), 1000)
|
||||||
|
onCleanup(() => window.clearInterval(timer))
|
||||||
|
})
|
||||||
|
|
||||||
const normalizeSessionLabel = (sessionId: string) => {
|
const normalizeSessionLabel = (sessionId: string) => {
|
||||||
const session = sessionStateSessions().get(props.instanceId)?.get(sessionId)
|
const session = sessionStateSessions().get(props.instanceId)?.get(sessionId)
|
||||||
@@ -400,7 +407,13 @@ const SessionList: Component<SessionListProps> = (props) => {
|
|||||||
const isActive = () => props.activeSessionId === rowProps.sessionId
|
const isActive = () => props.activeSessionId === rowProps.sessionId
|
||||||
const title = () => session()?.title || t("sessionList.session.untitled")
|
const title = () => session()?.title || t("sessionList.session.untitled")
|
||||||
const status = () => getSessionStatus(props.instanceId, rowProps.sessionId)
|
const status = () => getSessionStatus(props.instanceId, rowProps.sessionId)
|
||||||
|
const retry = () => getSessionRetry(props.instanceId, rowProps.sessionId)
|
||||||
const statusLabel = () => {
|
const statusLabel = () => {
|
||||||
|
const retryState = retry()
|
||||||
|
if (retryState) {
|
||||||
|
const seconds = getRetrySeconds(retryState.next, now())
|
||||||
|
return seconds > 0 ? t("sessionList.status.retryingIn", { seconds: String(seconds) }) : t("sessionList.status.retrying")
|
||||||
|
}
|
||||||
switch (formatSessionStatus(status())) {
|
switch (formatSessionStatus(status())) {
|
||||||
case "working":
|
case "working":
|
||||||
return t("sessionList.status.working")
|
return t("sessionList.status.working")
|
||||||
@@ -413,13 +426,21 @@ const SessionList: Component<SessionListProps> = (props) => {
|
|||||||
const needsPermission = () => Boolean(session()?.pendingPermission)
|
const needsPermission = () => Boolean(session()?.pendingPermission)
|
||||||
const needsQuestion = () => Boolean((session() as any)?.pendingQuestion)
|
const needsQuestion = () => Boolean((session() as any)?.pendingQuestion)
|
||||||
const needsInput = () => needsPermission() || needsQuestion()
|
const needsInput = () => needsPermission() || needsQuestion()
|
||||||
const statusClassName = () => (needsInput() ? "session-permission" : `session-${status()}`)
|
const statusClassName = () => (needsInput() ? "session-permission" : `session-${retry() ? "retrying" : status()}`)
|
||||||
const statusText = () =>
|
const statusText = () =>
|
||||||
needsPermission()
|
needsPermission()
|
||||||
? t("sessionList.status.needsPermission")
|
? t("sessionList.status.needsPermission")
|
||||||
: needsQuestion()
|
: needsQuestion()
|
||||||
? t("sessionList.status.needsInput")
|
? t("sessionList.status.needsInput")
|
||||||
: statusLabel()
|
: statusLabel()
|
||||||
|
const statusTooltip = () => {
|
||||||
|
const retryState = retry()
|
||||||
|
if (!retryState) return undefined
|
||||||
|
return t("sessionList.status.retryTooltip", {
|
||||||
|
message: retryState.message,
|
||||||
|
attempt: String(retryState.attempt),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const isSelected = () => selectedSessionIds().has(rowProps.sessionId)
|
const isSelected = () => selectedSessionIds().has(rowProps.sessionId)
|
||||||
|
|
||||||
@@ -499,7 +520,7 @@ const SessionList: Component<SessionListProps> = (props) => {
|
|||||||
<ChevronDown class={`w-3.5 h-3.5 transition-transform ${rowProps.expanded ? "" : "-rotate-90"}`} />
|
<ChevronDown class={`w-3.5 h-3.5 transition-transform ${rowProps.expanded ? "" : "-rotate-90"}`} />
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
</Show>
|
||||||
<span class={`status-indicator session-status session-status-list ${statusClassName()}`}>
|
<span class={`status-indicator session-status session-status-list ${statusClassName()}`} title={statusTooltip()}>
|
||||||
{needsInput() ? <ShieldAlert class="w-3.5 h-3.5" aria-hidden="true" /> : <span class="status-dot" />}
|
{needsInput() ? <ShieldAlert class="w-3.5 h-3.5" aria-hidden="true" /> : <span class="status-dot" />}
|
||||||
{statusText()}
|
{statusText()}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ export function formatCompactCount(value: number): string {
|
|||||||
return `${(value / 1_000_000).toFixed(1)}M`
|
return `${(value / 1_000_000).toFixed(1)}M`
|
||||||
}
|
}
|
||||||
if (value >= 10_000) {
|
if (value >= 10_000) {
|
||||||
return `${Math.round(value / 1_000)}K`
|
|
||||||
}
|
|
||||||
if (value >= 1_000) {
|
|
||||||
const label = `${(value / 1_000).toFixed(1)}K`
|
const label = `${(value / 1_000).toFixed(1)}K`
|
||||||
return label.replace(/\.0K$/, "K")
|
return label.replace(/\.0K$/, "K")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export const sessionMessages = {
|
|||||||
"sessionList.status.working": "Working",
|
"sessionList.status.working": "Working",
|
||||||
"sessionList.status.compacting": "Compacting",
|
"sessionList.status.compacting": "Compacting",
|
||||||
"sessionList.status.idle": "Idle",
|
"sessionList.status.idle": "Idle",
|
||||||
|
"sessionList.status.retrying": "Retrying",
|
||||||
|
"sessionList.status.retryingIn": "Retrying in {seconds}s",
|
||||||
|
"sessionList.status.retryTooltip": "{message} (Attempt {attempt})",
|
||||||
|
"sessionList.status.retryToast": "{countdown}: {message} (Attempt {attempt})",
|
||||||
"sessionList.status.needsPermission": "Needs Permission",
|
"sessionList.status.needsPermission": "Needs Permission",
|
||||||
"sessionList.status.needsInput": "Needs Input",
|
"sessionList.status.needsInput": "Needs Input",
|
||||||
"sessionList.expand.collapseAriaLabel": "Collapse session",
|
"sessionList.expand.collapseAriaLabel": "Collapse session",
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export const sessionMessages = {
|
|||||||
"sessionList.status.working": "Trabajando",
|
"sessionList.status.working": "Trabajando",
|
||||||
"sessionList.status.compacting": "Compactando",
|
"sessionList.status.compacting": "Compactando",
|
||||||
"sessionList.status.idle": "Inactiva",
|
"sessionList.status.idle": "Inactiva",
|
||||||
|
"sessionList.status.retrying": "Reintentando",
|
||||||
|
"sessionList.status.retryingIn": "Reintentando en {seconds}s",
|
||||||
|
"sessionList.status.retryTooltip": "{message} (Intento {attempt})",
|
||||||
|
"sessionList.status.retryToast": "{countdown}: {message} (Intento {attempt})",
|
||||||
"sessionList.status.needsPermission": "Requiere permiso",
|
"sessionList.status.needsPermission": "Requiere permiso",
|
||||||
"sessionList.status.needsInput": "Requiere entrada",
|
"sessionList.status.needsInput": "Requiere entrada",
|
||||||
"sessionList.expand.collapseAriaLabel": "Colapsar sesión",
|
"sessionList.expand.collapseAriaLabel": "Colapsar sesión",
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export const sessionMessages = {
|
|||||||
"sessionList.status.working": "En cours",
|
"sessionList.status.working": "En cours",
|
||||||
"sessionList.status.compacting": "Compactage",
|
"sessionList.status.compacting": "Compactage",
|
||||||
"sessionList.status.idle": "Inactif",
|
"sessionList.status.idle": "Inactif",
|
||||||
|
"sessionList.status.retrying": "Nouvelle tentative",
|
||||||
|
"sessionList.status.retryingIn": "Nouvelle tentative dans {seconds}s",
|
||||||
|
"sessionList.status.retryTooltip": "{message} (Tentative {attempt})",
|
||||||
|
"sessionList.status.retryToast": "{countdown} : {message} (Tentative {attempt})",
|
||||||
"sessionList.status.needsPermission": "Autorisation requise",
|
"sessionList.status.needsPermission": "Autorisation requise",
|
||||||
"sessionList.status.needsInput": "Entrée requise",
|
"sessionList.status.needsInput": "Entrée requise",
|
||||||
"sessionList.expand.collapseAriaLabel": "Réduire la session",
|
"sessionList.expand.collapseAriaLabel": "Réduire la session",
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export const sessionMessages = {
|
|||||||
"sessionList.status.working": "עובד",
|
"sessionList.status.working": "עובד",
|
||||||
"sessionList.status.compacting": "מסכם",
|
"sessionList.status.compacting": "מסכם",
|
||||||
"sessionList.status.idle": "מוכן",
|
"sessionList.status.idle": "מוכן",
|
||||||
|
"sessionList.status.retrying": "מנסה שוב",
|
||||||
|
"sessionList.status.retryingIn": "מנסה שוב בעוד {seconds}ש׳",
|
||||||
|
"sessionList.status.retryTooltip": "{message} (ניסיון {attempt})",
|
||||||
|
"sessionList.status.retryToast": "{countdown}: {message} (ניסיון {attempt})",
|
||||||
"sessionList.status.needsPermission": "נדרש אישור",
|
"sessionList.status.needsPermission": "נדרש אישור",
|
||||||
"sessionList.status.needsInput": "נדרש קלט",
|
"sessionList.status.needsInput": "נדרש קלט",
|
||||||
"sessionList.expand.collapseAriaLabel": "כווץ סשן",
|
"sessionList.expand.collapseAriaLabel": "כווץ סשן",
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export const sessionMessages = {
|
|||||||
"sessionList.status.working": "作業中",
|
"sessionList.status.working": "作業中",
|
||||||
"sessionList.status.compacting": "圧縮中",
|
"sessionList.status.compacting": "圧縮中",
|
||||||
"sessionList.status.idle": "待機中",
|
"sessionList.status.idle": "待機中",
|
||||||
|
"sessionList.status.retrying": "再試行中",
|
||||||
|
"sessionList.status.retryingIn": "{seconds}秒後に再試行",
|
||||||
|
"sessionList.status.retryTooltip": "{message}({attempt}回目)",
|
||||||
|
"sessionList.status.retryToast": "{countdown}: {message}({attempt}回目)",
|
||||||
"sessionList.status.needsPermission": "許可待ち",
|
"sessionList.status.needsPermission": "許可待ち",
|
||||||
"sessionList.status.needsInput": "入力待ち",
|
"sessionList.status.needsInput": "入力待ち",
|
||||||
"sessionList.expand.collapseAriaLabel": "セッションを折りたたむ",
|
"sessionList.expand.collapseAriaLabel": "セッションを折りたたむ",
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export const sessionMessages = {
|
|||||||
"sessionList.status.working": "Работает",
|
"sessionList.status.working": "Работает",
|
||||||
"sessionList.status.compacting": "Компактация",
|
"sessionList.status.compacting": "Компактация",
|
||||||
"sessionList.status.idle": "Простой",
|
"sessionList.status.idle": "Простой",
|
||||||
|
"sessionList.status.retrying": "Повтор",
|
||||||
|
"sessionList.status.retryingIn": "Повтор через {seconds}с",
|
||||||
|
"sessionList.status.retryTooltip": "{message} (Попытка {attempt})",
|
||||||
|
"sessionList.status.retryToast": "{countdown}: {message} (Попытка {attempt})",
|
||||||
"sessionList.status.needsPermission": "Требуется разрешение",
|
"sessionList.status.needsPermission": "Требуется разрешение",
|
||||||
"sessionList.status.needsInput": "Требуется ввод",
|
"sessionList.status.needsInput": "Требуется ввод",
|
||||||
"sessionList.expand.collapseAriaLabel": "Свернуть сессию",
|
"sessionList.expand.collapseAriaLabel": "Свернуть сессию",
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export const sessionMessages = {
|
|||||||
"sessionList.status.working": "工作中",
|
"sessionList.status.working": "工作中",
|
||||||
"sessionList.status.compacting": "压缩中",
|
"sessionList.status.compacting": "压缩中",
|
||||||
"sessionList.status.idle": "空闲",
|
"sessionList.status.idle": "空闲",
|
||||||
|
"sessionList.status.retrying": "重试中",
|
||||||
|
"sessionList.status.retryingIn": "{seconds} 秒后重试",
|
||||||
|
"sessionList.status.retryTooltip": "{message}(第 {attempt} 次尝试)",
|
||||||
|
"sessionList.status.retryToast": "{countdown}: {message}(第 {attempt} 次尝试)",
|
||||||
"sessionList.status.needsPermission": "需要权限",
|
"sessionList.status.needsPermission": "需要权限",
|
||||||
"sessionList.status.needsInput": "需要输入",
|
"sessionList.status.needsInput": "需要输入",
|
||||||
"sessionList.expand.collapseAriaLabel": "折叠会话",
|
"sessionList.expand.collapseAriaLabel": "折叠会话",
|
||||||
|
|||||||
@@ -120,14 +120,7 @@ function resolveLanguage(token: string): { canonical: string | null; raw: string
|
|||||||
return { canonical: null, raw: normalized }
|
return { canonical: null, raw: normalized }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureLanguages(content: string) {
|
function collectCodeFenceLanguages(content: string): string[] {
|
||||||
if (highlightSuppressed) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract code-fence language tokens via `marked` so we correctly handle code blocks
|
|
||||||
// that contain backticks (e.g. JS template literals). Regex-based fence scans tend
|
|
||||||
// to miss these and prevent languages from loading.
|
|
||||||
const foundLanguages = new Set<string>()
|
const foundLanguages = new Set<string>()
|
||||||
try {
|
try {
|
||||||
const tokens = marked.lexer(content) as any
|
const tokens = marked.lexer(content) as any
|
||||||
@@ -139,10 +132,44 @@ async function ensureLanguages(content: string) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch {
|
} catch {
|
||||||
// If tokenization fails for any reason, skip language preloading.
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...foundLanguages]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasPendingCodeHighlight(content: string): boolean {
|
||||||
|
const languages = collectCodeFenceLanguages(content)
|
||||||
|
for (const token of languages) {
|
||||||
|
const rawToken = normalizeLanguageToken(token)
|
||||||
|
if (!rawToken || rawToken === "text") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const { canonical, raw } = resolveLanguage(token)
|
||||||
|
const langKey = canonical || raw
|
||||||
|
if (langKey === "text" || raw === "text") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!highlighter || !loadedLanguages.has(langKey)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureLanguages(content: string) {
|
||||||
|
if (highlightSuppressed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract code-fence language tokens via `marked` so we correctly handle code blocks
|
||||||
|
// that contain backticks (e.g. JS template literals). Regex-based fence scans tend
|
||||||
|
// to miss these and prevent languages from loading.
|
||||||
|
const foundLanguages = collectCodeFenceLanguages(content)
|
||||||
|
|
||||||
// Queue language loading tasks
|
// Queue language loading tasks
|
||||||
for (const token of foundLanguages) {
|
for (const token of foundLanguages) {
|
||||||
const rawToken = normalizeLanguageToken(token)
|
const rawToken = normalizeLanguageToken(token)
|
||||||
|
|||||||
@@ -102,9 +102,11 @@ export function showToastNotification(payload: ToastPayload): ToastHandle {
|
|||||||
</button>
|
</button>
|
||||||
<div class="flex items-start gap-3 pr-6">
|
<div class="flex items-start gap-3 pr-6">
|
||||||
<span class={`mt-1 inline-block h-2.5 w-2.5 rounded-full ${accent.badge}`} />
|
<span class={`mt-1 inline-block h-2.5 w-2.5 rounded-full ${accent.badge}`} />
|
||||||
<div class="flex-1 text-sm leading-snug">
|
<div class="min-w-0 flex-1 text-sm leading-snug">
|
||||||
{payload.title && <p class={`font-semibold ${accent.headline}`}>{payload.title}</p>}
|
{payload.title && <p class={`break-words ${accent.headline} font-semibold`}>{payload.title}</p>}
|
||||||
<p class={`${accent.body} ${payload.title ? "mt-1" : ""}`}>{payload.message}</p>
|
<p class={`${accent.body} ${payload.title ? "mt-1" : ""} whitespace-pre-wrap break-words [overflow-wrap:anywhere]`}>
|
||||||
|
{payload.message}
|
||||||
|
</p>
|
||||||
{payload.action && (
|
{payload.action && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { mapSdkSessionStatus, type Session, type SessionStatus } from "../types/session"
|
import { mapSdkSessionRetry, mapSdkSessionStatus, type Session, type SessionStatus } from "../types/session"
|
||||||
import type { Message } from "../types/message"
|
import type { Message } from "../types/message"
|
||||||
import type { FileDiff } from "@opencode-ai/sdk/v2/client"
|
import type { FileDiff } from "@opencode-ai/sdk/v2/client"
|
||||||
|
|
||||||
@@ -149,12 +149,15 @@ async function fetchSessions(instanceId: string): Promise<void> {
|
|||||||
const existingStatus = existingSession?.status
|
const existingStatus = existingSession?.status
|
||||||
|
|
||||||
let status: SessionStatus
|
let status: SessionStatus
|
||||||
|
let retry = existingSession?.retry ?? null
|
||||||
if (existingStatus === "compacting") {
|
if (existingStatus === "compacting") {
|
||||||
status = "compacting"
|
status = "compacting"
|
||||||
|
retry = null
|
||||||
} else {
|
} else {
|
||||||
const rawStatus = (apiSession as any)?.status ?? statusById[apiSession.id]
|
const rawStatus = (apiSession as any)?.status ?? statusById[apiSession.id]
|
||||||
const hasType = rawStatus && typeof rawStatus === "object" && typeof rawStatus.type === "string"
|
const hasType = rawStatus && typeof rawStatus === "object" && typeof rawStatus.type === "string"
|
||||||
status = hasType ? mapSdkSessionStatus(rawStatus) : existingStatus ?? "idle"
|
status = hasType ? mapSdkSessionStatus(rawStatus) : existingStatus ?? "idle"
|
||||||
|
retry = hasType ? mapSdkSessionRetry(rawStatus) : retry
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionMap.set(apiSession.id, {
|
sessionMap.set(apiSession.id, {
|
||||||
@@ -165,6 +168,7 @@ async function fetchSessions(instanceId: string): Promise<void> {
|
|||||||
agent: existingSession?.agent ?? "",
|
agent: existingSession?.agent ?? "",
|
||||||
model: existingSession?.model ?? { providerId: "", modelId: "" },
|
model: existingSession?.model ?? { providerId: "", modelId: "" },
|
||||||
status,
|
status,
|
||||||
|
retry,
|
||||||
version: apiSession.version,
|
version: apiSession.version,
|
||||||
time: {
|
time: {
|
||||||
...apiSession.time,
|
...apiSession.time,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import type { PermissionReplyEventPropertiesLike, PermissionRequestLike } from "
|
|||||||
import { getQuestionId, getQuestionSessionId, getRequestIdFromQuestionReply } from "../types/question"
|
import { getQuestionId, getQuestionSessionId, getRequestIdFromQuestionReply } from "../types/question"
|
||||||
import type { QuestionRequest } from "../types/question"
|
import type { QuestionRequest } from "../types/question"
|
||||||
import type { EventQuestionReplied, EventQuestionRejected } from "@opencode-ai/sdk/v2"
|
import type { EventQuestionReplied, EventQuestionRejected } from "@opencode-ai/sdk/v2"
|
||||||
import { showToastNotification, ToastVariant } from "../lib/notifications"
|
import { showToastNotification, type ToastHandle, ToastVariant } from "../lib/notifications"
|
||||||
import { sendOsNotification } from "../lib/os-notifications"
|
import { sendOsNotification } from "../lib/os-notifications"
|
||||||
import { preferences } from "./preferences"
|
import { preferences } from "./preferences"
|
||||||
import {
|
import {
|
||||||
@@ -39,7 +39,14 @@ import {
|
|||||||
removeQuestionFromQueue,
|
removeQuestionFromQueue,
|
||||||
} from "./instances"
|
} from "./instances"
|
||||||
import { showAlertDialog } from "./alerts"
|
import { showAlertDialog } from "./alerts"
|
||||||
import { createClientSession, mapSdkSessionStatus, type Session, type SessionStatus } from "../types/session"
|
import {
|
||||||
|
createClientSession,
|
||||||
|
mapSdkSessionRetry,
|
||||||
|
mapSdkSessionStatus,
|
||||||
|
type Session,
|
||||||
|
type SessionRetryState,
|
||||||
|
type SessionStatus,
|
||||||
|
} from "../types/session"
|
||||||
import { ensureSessionParentExpanded, sessions, setSessions, syncInstanceSessionIndicator, withSession } from "./session-state"
|
import { ensureSessionParentExpanded, sessions, setSessions, syncInstanceSessionIndicator, withSession } from "./session-state"
|
||||||
import { normalizeMessagePart } from "./message-v2/normalizers"
|
import { normalizeMessagePart } from "./message-v2/normalizers"
|
||||||
import { updateSessionInfo } from "./message-v2/session-info"
|
import { updateSessionInfo } from "./message-v2/session-info"
|
||||||
@@ -67,6 +74,15 @@ import { handleConversationAssistantPartUpdated } from "./conversation-speech"
|
|||||||
|
|
||||||
const log = getLogger("sse")
|
const log = getLogger("sse")
|
||||||
const pendingSessionFetches = new Map<string, Promise<void>>()
|
const pendingSessionFetches = new Map<string, Promise<void>>()
|
||||||
|
let activeRetryToast: ToastHandle | null = null
|
||||||
|
|
||||||
|
function isSameRetryState(left: SessionRetryState | null | undefined, right: SessionRetryState | null | undefined): boolean {
|
||||||
|
const a = left ?? null
|
||||||
|
const b = right ?? null
|
||||||
|
if (a === b) return true
|
||||||
|
if (!a || !b) return false
|
||||||
|
return a.attempt === b.attempt && a.message === b.message && a.next === b.next
|
||||||
|
}
|
||||||
|
|
||||||
function shouldSendOsNotification(kind: "needsInput" | "idle"): boolean {
|
function shouldSendOsNotification(kind: "needsInput" | "idle"): boolean {
|
||||||
if (typeof document === "undefined") return false
|
if (typeof document === "undefined") return false
|
||||||
@@ -131,18 +147,20 @@ interface TuiToastEvent {
|
|||||||
|
|
||||||
const ALLOWED_TOAST_VARIANTS = new Set<ToastVariant>(["info", "success", "warning", "error"])
|
const ALLOWED_TOAST_VARIANTS = new Set<ToastVariant>(["info", "success", "warning", "error"])
|
||||||
|
|
||||||
function applySessionStatus(instanceId: string, sessionId: string, status: SessionStatus) {
|
function applySessionStatus(instanceId: string, sessionId: string, status: SessionStatus, retry?: SessionRetryState | null) {
|
||||||
let parentToExpand: string | null = null
|
let parentToExpand: string | null = null
|
||||||
|
|
||||||
withSession(instanceId, sessionId, (session) => {
|
withSession(instanceId, sessionId, (session) => {
|
||||||
const current = session.status ?? "idle"
|
const current = session.status ?? "idle"
|
||||||
if (current === status) return false
|
const nextRetry = retry ?? null
|
||||||
|
if (current === status && isSameRetryState(session.retry, nextRetry)) return false
|
||||||
|
|
||||||
if (current === "compacting" && status !== "compacting") {
|
if (current === "compacting" && status !== "compacting") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
session.status = status
|
session.status = status
|
||||||
|
session.retry = status === "working" ? nextRetry : null
|
||||||
|
|
||||||
// Auto-expand the parent thread when a child session starts working.
|
// Auto-expand the parent thread when a child session starts working.
|
||||||
// Users can still collapse it; we only expand on the transition.
|
// Users can still collapse it; we only expand on the transition.
|
||||||
@@ -172,6 +190,7 @@ async function fetchSessionInfo(instanceId: string, sessionId: string, directory
|
|||||||
)
|
)
|
||||||
|
|
||||||
let fetchedStatus: SessionStatus = "idle"
|
let fetchedStatus: SessionStatus = "idle"
|
||||||
|
let fetchedRetry: SessionRetryState | null = null
|
||||||
try {
|
try {
|
||||||
let statuses: Record<string, any> = {}
|
let statuses: Record<string, any> = {}
|
||||||
try {
|
try {
|
||||||
@@ -187,11 +206,13 @@ async function fetchSessionInfo(instanceId: string, sessionId: string, directory
|
|||||||
const rawStatus = (info as any)?.status ?? statuses?.[sessionId]
|
const rawStatus = (info as any)?.status ?? statuses?.[sessionId]
|
||||||
const hasType = rawStatus && typeof rawStatus === "object" && typeof rawStatus.type === "string"
|
const hasType = rawStatus && typeof rawStatus === "object" && typeof rawStatus.type === "string"
|
||||||
fetchedStatus = hasType ? mapSdkSessionStatus(rawStatus) : "idle"
|
fetchedStatus = hasType ? mapSdkSessionStatus(rawStatus) : "idle"
|
||||||
|
fetchedRetry = hasType ? mapSdkSessionRetry(rawStatus) : null
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to fetch session status", error)
|
log.error("Failed to fetch session status", error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetched = createClientSession(info, instanceId, "", { providerId: "", modelId: "" }, fetchedStatus)
|
const fetched = createClientSession(info, instanceId, "", { providerId: "", modelId: "" }, fetchedStatus)
|
||||||
|
fetched.retry = fetchedRetry
|
||||||
|
|
||||||
let updatedInstanceSessions: Map<string, Session> | undefined
|
let updatedInstanceSessions: Map<string, Session> | undefined
|
||||||
let shouldExpandParent: string | null = null
|
let shouldExpandParent: string | null = null
|
||||||
@@ -205,6 +226,7 @@ async function fetchSessionInfo(instanceId: string, sessionId: string, directory
|
|||||||
agent: existing?.agent ?? fetched.agent,
|
agent: existing?.agent ?? fetched.agent,
|
||||||
model: existing?.model ?? fetched.model,
|
model: existing?.model ?? fetched.model,
|
||||||
status: existing?.status === "compacting" ? "compacting" : fetched.status,
|
status: existing?.status === "compacting" ? "compacting" : fetched.status,
|
||||||
|
retry: existing?.status === "compacting" ? null : fetched.retry,
|
||||||
pendingPermission: existing?.pendingPermission ?? fetched.pendingPermission,
|
pendingPermission: existing?.pendingPermission ?? fetched.pendingPermission,
|
||||||
pendingQuestion: existing?.pendingQuestion ?? false,
|
pendingQuestion: existing?.pendingQuestion ?? false,
|
||||||
}
|
}
|
||||||
@@ -231,14 +253,20 @@ async function fetchSessionInfo(instanceId: string, sessionId: string, directory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureSessionStatus(instanceId: string, sessionId: string, status: SessionStatus, directory?: string) {
|
function ensureSessionStatus(
|
||||||
|
instanceId: string,
|
||||||
|
sessionId: string,
|
||||||
|
status: SessionStatus,
|
||||||
|
directory?: string,
|
||||||
|
retry?: SessionRetryState | null,
|
||||||
|
) {
|
||||||
const instanceSessions = sessions().get(instanceId)
|
const instanceSessions = sessions().get(instanceId)
|
||||||
const existing = instanceSessions?.get(sessionId)
|
const existing = instanceSessions?.get(sessionId)
|
||||||
if (existing) {
|
if (existing) {
|
||||||
if ((existing.status ?? "idle") === status) {
|
if ((existing.status ?? "idle") === status && isSameRetryState(existing.retry, retry)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
applySessionStatus(instanceId, sessionId, status)
|
applySessionStatus(instanceId, sessionId, status, retry)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +278,7 @@ function ensureSessionStatus(instanceId: string, sessionId: string, status: Sess
|
|||||||
const pending = (async () => {
|
const pending = (async () => {
|
||||||
const fetched = await fetchSessionInfo(instanceId, sessionId, directory)
|
const fetched = await fetchSessionInfo(instanceId, sessionId, directory)
|
||||||
if (!fetched) return
|
if (!fetched) return
|
||||||
applySessionStatus(instanceId, sessionId, status)
|
applySessionStatus(instanceId, sessionId, status, retry)
|
||||||
})()
|
})()
|
||||||
|
|
||||||
pendingSessionFetches.set(key, pending)
|
pendingSessionFetches.set(key, pending)
|
||||||
@@ -428,6 +456,7 @@ function handleSessionUpdate(instanceId: string, event: EventSessionUpdated): vo
|
|||||||
modelId: "",
|
modelId: "",
|
||||||
},
|
},
|
||||||
status: "idle",
|
status: "idle",
|
||||||
|
retry: null,
|
||||||
version: info.version || "0",
|
version: info.version || "0",
|
||||||
time: info.time
|
time: info.time
|
||||||
? { ...info.time }
|
? { ...info.time }
|
||||||
@@ -461,6 +490,7 @@ function handleSessionUpdate(instanceId: string, event: EventSessionUpdated): vo
|
|||||||
...existingSession,
|
...existingSession,
|
||||||
title: info.title || existingSession.title,
|
title: info.title || existingSession.title,
|
||||||
status: existingSession.status ?? "idle",
|
status: existingSession.status ?? "idle",
|
||||||
|
retry: existingSession.retry ?? null,
|
||||||
time: mergedTime,
|
time: mergedTime,
|
||||||
revert: info.revert
|
revert: info.revert
|
||||||
? {
|
? {
|
||||||
@@ -532,8 +562,29 @@ function handleSessionStatus(instanceId: string, event: EventSessionStatus): voi
|
|||||||
const sessionId = event.properties?.sessionID
|
const sessionId = event.properties?.sessionID
|
||||||
if (!sessionId) return
|
if (!sessionId) return
|
||||||
|
|
||||||
const status = mapSdkSessionStatus(event.properties.status)
|
const rawStatus = event.properties.status
|
||||||
ensureSessionStatus(instanceId, sessionId, status, (event as any)?.directory)
|
const status = mapSdkSessionStatus(rawStatus)
|
||||||
|
const retry = mapSdkSessionRetry(rawStatus)
|
||||||
|
ensureSessionStatus(instanceId, sessionId, status, (event as any)?.directory, retry)
|
||||||
|
if (retry) {
|
||||||
|
const remainingSeconds = Math.max(0, Math.round((retry.next - Date.now()) / 1000))
|
||||||
|
const countdown =
|
||||||
|
remainingSeconds > 0
|
||||||
|
? tGlobal("sessionList.status.retryingIn", { seconds: String(remainingSeconds) })
|
||||||
|
: tGlobal("sessionList.status.retrying")
|
||||||
|
const label = getSessionTitle(instanceId, sessionId)
|
||||||
|
activeRetryToast?.dismiss()
|
||||||
|
activeRetryToast = showToastNotification({
|
||||||
|
title: label || getInstanceDisplayName(instanceId),
|
||||||
|
message: tGlobal("sessionList.status.retryToast", {
|
||||||
|
countdown,
|
||||||
|
message: retry.message,
|
||||||
|
attempt: String(retry.attempt),
|
||||||
|
}),
|
||||||
|
variant: "error",
|
||||||
|
duration: 7000,
|
||||||
|
})
|
||||||
|
}
|
||||||
log.info(`[SSE] Session status updated: ${sessionId}`, { status })
|
log.info(`[SSE] Session status updated: ${sessionId}`, { status })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,6 +598,7 @@ function handleSessionCompacted(instanceId: string, event: EventSessionCompacted
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
withSession(instanceId, sessionID, (session) => {
|
withSession(instanceId, sessionID, (session) => {
|
||||||
session.status = "working"
|
session.status = "working"
|
||||||
|
session.retry = null
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
ensureSessionStatus(instanceId, sessionID, "working", (event as any)?.directory)
|
ensureSessionStatus(instanceId, sessionID, "working", (event as any)?.directory)
|
||||||
|
|||||||
@@ -353,6 +353,9 @@ function setSessionStatus(instanceId: string, sessionId: string, status: Session
|
|||||||
if (session.status === status) return false
|
if (session.status === status) return false
|
||||||
const previous = session.status
|
const previous = session.status
|
||||||
session.status = status
|
session.status = status
|
||||||
|
if (status !== "working") {
|
||||||
|
session.retry = null
|
||||||
|
}
|
||||||
|
|
||||||
// If a child session starts working, auto-expand its parent thread once.
|
// If a child session starts working, auto-expand its parent thread once.
|
||||||
// Users can still collapse it afterwards; we only expand on the transition.
|
// Users can still collapse it afterwards; we only expand on the transition.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Session, SessionStatus } from "../types/session"
|
import type { Session, SessionRetryState, SessionStatus } from "../types/session"
|
||||||
import { getInstanceSessionIndicatorStatusCached, sessions } from "./session-state"
|
import { getInstanceSessionIndicatorStatusCached, sessions } from "./session-state"
|
||||||
|
|
||||||
function getSession(instanceId: string, sessionId: string): Session | null {
|
function getSession(instanceId: string, sessionId: string): Session | null {
|
||||||
@@ -14,6 +14,15 @@ export function getSessionStatus(instanceId: string, sessionId: string): Session
|
|||||||
return session.status ?? "idle"
|
return session.status ?? "idle"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSessionRetry(instanceId: string, sessionId: string): SessionRetryState | null {
|
||||||
|
const session = getSession(instanceId, sessionId)
|
||||||
|
return session?.retry ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRetrySeconds(next: number, now = Date.now()): number {
|
||||||
|
return Math.max(0, Math.round((next - now) / 1000))
|
||||||
|
}
|
||||||
|
|
||||||
export type InstanceSessionIndicatorStatus = "permission" | SessionStatus
|
export type InstanceSessionIndicatorStatus = "permission" | SessionStatus
|
||||||
|
|
||||||
export function getInstanceSessionIndicatorStatus(instanceId: string): InstanceSessionIndicatorStatus {
|
export function getInstanceSessionIndicatorStatus(instanceId: string): InstanceSessionIndicatorStatus {
|
||||||
|
|||||||
@@ -184,6 +184,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status-indicator.session-status.session-working,
|
.status-indicator.session-status.session-working,
|
||||||
|
.status-indicator.session-status.session-retrying,
|
||||||
.status-indicator.session-status.session-compacting,
|
.status-indicator.session-status.session-compacting,
|
||||||
.status-indicator.session-status.session-idle {
|
.status-indicator.session-status.session-idle {
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
@@ -194,6 +195,11 @@
|
|||||||
--session-status-dot: var(--session-status-working-fg);
|
--session-status-dot: var(--session-status-working-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-indicator.session-status.session-retrying {
|
||||||
|
color: var(--status-error);
|
||||||
|
--session-status-dot: var(--status-error);
|
||||||
|
}
|
||||||
|
|
||||||
.status-indicator.session-status.session-compacting {
|
.status-indicator.session-status.session-compacting {
|
||||||
color: var(--session-status-compacting-fg);
|
color: var(--session-status-compacting-fg);
|
||||||
--session-status-dot: var(--session-status-compacting-fg);
|
--session-status-dot: var(--session-status-compacting-fg);
|
||||||
@@ -222,6 +228,10 @@
|
|||||||
background-color: var(--session-status-working-bg);
|
background-color: var(--session-status-working-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-indicator.session-status.session-retrying.session-status-list {
|
||||||
|
background-color: var(--status-error-bg);
|
||||||
|
}
|
||||||
|
|
||||||
.status-indicator.session-status.session-compacting.session-status-list {
|
.status-indicator.session-status.session-compacting.session-status-list {
|
||||||
background-color: var(--session-status-compacting-bg);
|
background-color: var(--session-status-compacting-bg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -416,6 +416,7 @@ session-sidebar-controls .selector-trigger-primary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status-indicator.session-status.session-working,
|
.status-indicator.session-status.session-working,
|
||||||
|
.status-indicator.session-status.session-retrying,
|
||||||
.status-indicator.session-status.session-compacting,
|
.status-indicator.session-status.session-compacting,
|
||||||
.status-indicator.session-status.session-idle {
|
.status-indicator.session-status.session-idle {
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
@@ -426,6 +427,11 @@ session-sidebar-controls .selector-trigger-primary {
|
|||||||
--session-status-dot: var(--session-status-working-fg);
|
--session-status-dot: var(--session-status-working-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-indicator.session-status.session-retrying {
|
||||||
|
color: var(--status-error);
|
||||||
|
--session-status-dot: var(--status-error);
|
||||||
|
}
|
||||||
|
|
||||||
.status-indicator.session-status.session-compacting {
|
.status-indicator.session-status.session-compacting {
|
||||||
color: var(--session-status-compacting-fg);
|
color: var(--session-status-compacting-fg);
|
||||||
--session-status-dot: var(--session-status-compacting-fg);
|
--session-status-dot: var(--session-status-compacting-fg);
|
||||||
@@ -454,6 +460,10 @@ session-sidebar-controls .selector-trigger-primary {
|
|||||||
background-color: var(--session-status-working-bg);
|
background-color: var(--session-status-working-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-indicator.session-status.session-retrying.session-status-list {
|
||||||
|
background-color: var(--status-error-bg);
|
||||||
|
}
|
||||||
|
|
||||||
.status-indicator.session-status.session-compacting.session-status-list {
|
.status-indicator.session-status.session-compacting.session-status-list {
|
||||||
background-color: var(--session-status-compacting-bg);
|
background-color: var(--session-status-compacting-bg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ export type {
|
|||||||
|
|
||||||
export type SessionStatus = "idle" | "working" | "compacting"
|
export type SessionStatus = "idle" | "working" | "compacting"
|
||||||
|
|
||||||
|
export interface SessionRetryState {
|
||||||
|
attempt: number
|
||||||
|
message: string
|
||||||
|
next: number
|
||||||
|
}
|
||||||
|
|
||||||
export function mapSdkSessionStatus(status: SDKSessionStatus | null | undefined): SessionStatus {
|
export function mapSdkSessionStatus(status: SDKSessionStatus | null | undefined): SessionStatus {
|
||||||
if (!status || status.type === "idle") {
|
if (!status || status.type === "idle") {
|
||||||
return "idle"
|
return "idle"
|
||||||
@@ -26,6 +32,18 @@ export function mapSdkSessionStatus(status: SDKSessionStatus | null | undefined)
|
|||||||
return "working"
|
return "working"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mapSdkSessionRetry(status: SDKSessionStatus | null | undefined): SessionRetryState | null {
|
||||||
|
if (!status || status.type !== "retry") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
attempt: typeof status.attempt === "number" ? status.attempt : 1,
|
||||||
|
message: typeof status.message === "string" ? status.message : "",
|
||||||
|
next: typeof status.next === "number" ? status.next : Date.now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Our client-specific Session interface extending SDK Session
|
// Our client-specific Session interface extending SDK Session
|
||||||
export interface Session
|
export interface Session
|
||||||
extends Omit<import("@opencode-ai/sdk").Session, "projectID" | "directory" | "parentID"> {
|
extends Omit<import("@opencode-ai/sdk").Session, "projectID" | "directory" | "parentID"> {
|
||||||
@@ -40,6 +58,7 @@ export interface Session
|
|||||||
pendingPermission?: boolean // Indicates if session is waiting on user permission
|
pendingPermission?: boolean // Indicates if session is waiting on user permission
|
||||||
pendingQuestion?: boolean // Indicates if session is waiting on user input
|
pendingQuestion?: boolean // Indicates if session is waiting on user input
|
||||||
status: SessionStatus // Single source of truth for session status
|
status: SessionStatus // Single source of truth for session status
|
||||||
|
retry?: SessionRetryState | null // Retry metadata for transient backoff states
|
||||||
diff?: FileDiff[] // Session-level file diffs (hydrated via session.diff)
|
diff?: FileDiff[] // Session-level file diffs (hydrated via session.diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
40
scripts/bump-version.js
Normal file
40
scripts/bump-version.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const { spawnSync } = require("child_process")
|
||||||
|
|
||||||
|
const versionArgs = process.argv.slice(2)
|
||||||
|
|
||||||
|
if (versionArgs.length === 0) {
|
||||||
|
console.error("[bumpVersion] missing version argument (example: npm run bumpVersion -- patch)")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm"
|
||||||
|
|
||||||
|
function runStep(args, label) {
|
||||||
|
const result = spawnSync(npmCommand, args, {
|
||||||
|
stdio: "inherit",
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
console.error(`[bumpVersion] failed during ${label}: ${result.error.message}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.status !== 0) {
|
||||||
|
process.exit(result.status ?? 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runStep(
|
||||||
|
[
|
||||||
|
"version",
|
||||||
|
...versionArgs,
|
||||||
|
"--workspaces",
|
||||||
|
"--include-workspace-root",
|
||||||
|
"--no-git-tag-version",
|
||||||
|
],
|
||||||
|
"npm version"
|
||||||
|
)
|
||||||
|
|
||||||
|
runStep(["run", "sync:version", "--workspace", "@codenomad/tauri-app"], "tauri version sync")
|
||||||
Reference in New Issue
Block a user