Compare commits

..

6 Commits

Author SHA1 Message Date
Shantur Rathore
43db9cdb50 Release v0.14.0 - Sidecars, Remote access from app, more speedy (#325)
## Thanks for contributions
- PR #262 “fix(server): show sane remote URLs for 0.0.0.0 binds” by
@VooDisss
- PR #270 “Implement shared compact split and unified tool-call diff
layout” by @VooDisss
- PR #272 “Add log level configuration support” by @bluelovers
- PR #274 “perf(ui): fix O(n²) reactive subscriptions in timeline
effects (HUGE SPEED IMPROVEMENT)” by @pascalandr
- PR #285 “fix(ui): preserve prompt text when dismissing mention picker”
by @app/codenomadbot
- PR #291 “perf(ui): virtualize message timeline rendering, #274
follow-up ( BIG SPEED IMPROVEMENT )” by @pascalandr
- PR #309 “fix(ui): stop forced auto-follow during streaming” by
@VooDisss
- PR #320 “fix(tauri): own Windows CLI subtree with a job object” by
@pascalandr

## Highlights
- **Remote access is much easier to use**: Connect to remote CodeNomad
servers directly from the app and have full app experience remotely.
- **Sidecars**: Open locally hosted services directly in CodeNomad.
OpenVSCode Server, Terminal ( using ttyd ) and many more all available
remotely and on the move.
- **Background task notifications**: LLMs can now run background task
asynchronously and be notified when the task is done. No need to wait or
periodically checking the status.
- **Long conversations are much smoother**: Large sessions and busy
timelines render much faster, and chat scrolling behaves better while
responses stream in.
- **Clearer diffs and output views**: Tool-call diffs and session
changes now render more consistently with improved split/unified layouts
and support for unified diff patch formats.

## What’s Improved
- **Big timeline performance gains**: The message timeline got two major
performance passes, including virtualization, so large sessions stay
responsive much longer.
- **More useful context information**: Token stats are shown more
clearly, and context window reporting is simpler to understand.
- **Smoother voice and speech flows**: Speech input stays chained
correctly, dictated text inserts more naturally into prompts, and voice
mode state stays synced across listeners.
- **More dependable desktop behavior**: Desktop auth isolation is safer
per app, startup is more stable, and Windows process cleanup is more
reliable.

## Fixes
- **Less frustrating auto-scroll behavior**: Streaming reasoning, tool
output, and oversized assistant replies no longer pull you to the bottom
as aggressively, and you can toggle hold behavior for long replies.
- **More reliable timeline previews**: Message preview tooltips now
escape clipping so previews remain usable.
- **Safer prompt editing**: Dismissing the mention picker no longer
wipes your draft text.
- **Better remote session stability**: Remote keyboard handling,
reconnect flows, TLS exception scoping, and remote URL display are more
predictable.
- **Better small-screen and desktop polish**: The settings modal behaves
better on phones, speech input stays pinned correctly, and Windows
desktop builds handle CLI subprocess ownership more safely.

## Docs
- **Refreshed project docs**: README and supporting presentation content
were updated to better reflect the current product.

### Contributors
- @pascalandr
- @VooDisss
- @bluelovers

**Full Changelog**:
https://github.com/NeuralNomadsAI/CodeNomad/compare/v0.13.3...v0.14.0
2026-04-16 09:25:09 +01:00
Shantur Rathore
03ed3d3b2c Merge branch 'dev' of github.com:NeuralNomadsAI/CodeNomad into dev 2026-04-16 08:43:33 +01:00
Shantur Rathore
a111de1af8 Minimum version to 0.14.0 2026-04-16 08:43:16 +01:00
Shantur Rathore
8a3b162be9 Bump version to 0.14.0 2026-04-16 08:42:33 +01:00
Shantur Rathore
c62cb3ce4a fix(server): share voice mode state across listeners 2026-04-13 21:36:49 +01:00
Shantur Rathore
d9811e735d fix(server): reject stale voice mode enables 2026-04-13 20:37:31 +01:00
15 changed files with 60 additions and 32 deletions

12
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "codenomad-workspace",
"version": "0.13.3",
"version": "0.14.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codenomad-workspace",
"version": "0.13.3",
"version": "0.14.0",
"license": "MIT",
"dependencies": {
"7zip-bin": "^5.2.0",
@@ -12068,7 +12068,7 @@
},
"packages/electron-app": {
"name": "@neuralnomads/codenomad-electron-app",
"version": "0.13.3",
"version": "0.14.0",
"license": "MIT",
"dependencies": {
"@codenomad/ui": "file:../ui",
@@ -12105,7 +12105,7 @@
},
"packages/server": {
"name": "@neuralnomads/codenomad",
"version": "0.13.3",
"version": "0.14.0",
"license": "MIT",
"dependencies": {
"@fastify/cors": "^8.5.0",
@@ -12147,7 +12147,7 @@
},
"packages/tauri-app": {
"name": "@codenomad/tauri-app",
"version": "0.13.3",
"version": "0.14.0",
"license": "MIT",
"devDependencies": {
"@tauri-apps/cli": "^2.9.4"
@@ -12155,7 +12155,7 @@
},
"packages/ui": {
"name": "@codenomad/ui",
"version": "0.13.3",
"version": "0.14.0",
"license": "MIT",
"dependencies": {
"@git-diff-view/solid": "^0.0.8",

View File

@@ -1,6 +1,6 @@
{
"name": "codenomad-workspace",
"version": "0.13.3",
"version": "0.14.0",
"private": true,
"description": "CodeNomad monorepo workspace",
"license": "MIT",

View File

@@ -1,4 +1,4 @@
{
"minServerVersion": "0.13.3",
"minServerVersion": "0.14.0",
"latestServerUrl": "https://github.com/NeuralNomadsAI/CodeNomad/releases/latest"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@neuralnomads/codenomad-electron-app",
"version": "0.13.3",
"version": "0.14.0",
"description": "CodeNomad - AI coding assistant",
"license": "MIT",
"author": {

View File

@@ -1,12 +1,12 @@
{
"name": "@neuralnomads/codenomad",
"version": "0.13.3",
"version": "0.14.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@neuralnomads/codenomad",
"version": "0.13.3",
"version": "0.14.0",
"dependencies": {
"@fastify/cors": "^8.5.0",
"@fastify/reply-from": "^9.8.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@neuralnomads/codenomad",
"version": "0.13.3",
"version": "0.14.0",
"description": "CodeNomad Server",
"license": "MIT",
"author": {

View File

@@ -25,6 +25,9 @@ import { resolveNetworkAddresses, resolveRemoteAddresses } from "./server/networ
import { startDevReleaseMonitor } from "./releases/dev-release-monitor"
import { SpeechService } from "./speech/service"
import { SideCarManager } from "./sidecars/manager"
import { ClientConnectionManager } from "./clients/connection-manager"
import { PluginChannelManager } from "./plugins/channel"
import { VoiceModeManager } from "./plugins/voice-mode"
const require = createRequire(import.meta.url)
@@ -378,6 +381,14 @@ async function main() {
const remoteAccessEnabled = options.host === "0.0.0.0" || !isLoopbackHost(options.host)
const clientConnectionManager = new ClientConnectionManager(logger.child({ component: "client-connections" }))
const pluginChannel = new PluginChannelManager(logger.child({ component: "plugin-channel" }))
const voiceModeManager = new VoiceModeManager({
connections: clientConnectionManager,
channel: pluginChannel,
logger: logger.child({ component: "voice-mode" }),
})
const httpsPortExplicit = programHasArg(process.argv.slice(2), "--https-port") || Boolean(process.env.CLI_HTTPS_PORT)
const httpPortExplicit = programHasArg(process.argv.slice(2), "--http-port") || Boolean(process.env.CLI_HTTP_PORT)
@@ -408,6 +419,9 @@ async function main() {
speechService,
sidecarManager,
authManager,
clientConnectionManager,
pluginChannel,
voiceModeManager,
uiStaticDir: uiResolution.uiStaticDir ?? DEFAULT_UI_STATIC_DIR,
uiDevServerUrl: uiResolution.uiDevServerUrl,
logger,
@@ -430,6 +444,9 @@ async function main() {
speechService,
sidecarManager,
authManager,
clientConnectionManager,
pluginChannel,
voiceModeManager,
uiStaticDir: uiResolution.uiStaticDir ?? DEFAULT_UI_STATIC_DIR,
uiDevServerUrl: undefined,
logger,
@@ -534,6 +551,12 @@ async function main() {
logger.error({ err: error }, "SideCar manager shutdown failed")
}
try {
clientConnectionManager.shutdown()
} catch (error) {
logger.warn({ err: error }, "Client connection manager shutdown failed")
}
try {
await workspaceManager.shutdown()
logger.info("Workspace manager shutdown complete")

View File

@@ -19,13 +19,13 @@ export class VoiceModeManager {
})
}
setEnabled(instanceId: string, connection: ClientConnectionRef, enabled: boolean): void {
setEnabled(instanceId: string, connection: ClientConnectionRef, enabled: boolean): boolean {
if (enabled && !this.options.connections.isConnected(connection)) {
this.options.logger.debug(
{ instanceId, clientId: connection.clientId, connectionId: connection.connectionId },
"Ignoring voice mode enable for disconnected client connection",
)
return
return false
}
const key = getConnectionKey(connection)
@@ -44,6 +44,7 @@ export class VoiceModeManager {
this.options.logger.debug({ instanceId, clientId: connection.clientId, connectionId: connection.connectionId, enabled }, "Voice mode updated for client connection")
this.publishIfChanged(instanceId)
return true
}
syncInstance(instanceId: string): void {
@@ -76,7 +77,10 @@ export class VoiceModeManager {
this.aggregateByInstance.delete(instanceId)
}
this.options.logger.debug({ instanceId, enabled }, "Broadcasting aggregate voice mode")
this.options.logger.debug(
{ instanceId, enabled },
"Broadcasting aggregate voice mode",
)
this.options.channel.send(instanceId, buildVoiceModeEvent(enabled))
}
}

View File

@@ -54,6 +54,9 @@ interface HttpServerDeps {
speechService: SpeechService
sidecarManager: SideCarManager
authManager: AuthManager
clientConnectionManager: ClientConnectionManager
pluginChannel: PluginChannelManager
voiceModeManager: VoiceModeManager
uiStaticDir: string
uiDevServerUrl?: string
logger: Logger
@@ -182,13 +185,6 @@ export function createHttpServer(deps: HttpServerDeps) {
eventBus: deps.eventBus,
logger: deps.logger.child({ component: "background-processes" }),
})
const clientConnectionManager = new ClientConnectionManager(deps.logger.child({ component: "client-connections" }))
const pluginChannel = new PluginChannelManager(deps.logger.child({ component: "plugin-channel" }))
const voiceModeManager = new VoiceModeManager({
connections: clientConnectionManager,
channel: pluginChannel,
logger: deps.logger.child({ component: "voice-mode" }),
})
registerAuthRoutes(app, { authManager: deps.authManager })
@@ -268,7 +264,7 @@ export function createHttpServer(deps: HttpServerDeps) {
eventBus: deps.eventBus,
registerClient: registerSseClient,
logger: sseLogger,
connectionManager: clientConnectionManager,
connectionManager: deps.clientConnectionManager,
})
registerWorktreeRoutes(app, { workspaceManager: deps.workspaceManager })
registerStorageRoutes(app, {
@@ -289,8 +285,8 @@ export function createHttpServer(deps: HttpServerDeps) {
workspaceManager: deps.workspaceManager,
eventBus: deps.eventBus,
logger: proxyLogger,
channel: pluginChannel,
voiceModeManager,
channel: deps.pluginChannel,
voiceModeManager: deps.voiceModeManager,
})
registerBackgroundProcessRoutes(app, { backgroundProcessManager })
registerInstanceProxyRoutes(app, { workspaceManager: deps.workspaceManager, logger: proxyLogger })
@@ -356,7 +352,6 @@ export function createHttpServer(deps: HttpServerDeps) {
},
stop: () => {
closeSseClients()
clientConnectionManager.shutdown()
return app.close()
},
}

View File

@@ -66,11 +66,17 @@ export function registerPluginRoutes(app: FastifyInstance, deps: RouteDeps) {
}
const payload = VoiceModeStateSchema.parse(request.body ?? {})
deps.voiceModeManager.setEnabled(
const applied = deps.voiceModeManager.setEnabled(
request.params.id,
{ clientId: payload.clientId, connectionId: payload.connectionId },
payload.enabled,
)
if (payload.enabled && !applied) {
reply.code(409).send({ error: "Client connection not active for voice mode enable" })
return
}
return { enabled: payload.enabled }
})

View File

@@ -458,7 +458,7 @@ dependencies = [
[[package]]
name = "codenomad-tauri"
version = "0.13.3"
version = "0.14.0"
dependencies = [
"anyhow",
"dirs 5.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "@codenomad/tauri-app",
"version": "0.13.3",
"version": "0.14.0",
"private": true,
"license": "MIT",
"scripts": {

View File

@@ -1,6 +1,6 @@
[package]
name = "codenomad-tauri"
version = "0.13.3"
version = "0.14.0"
edition = "2021"
license = "MIT"

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "CodeNomad",
"version": "0.13.3",
"version": "0.14.0",
"identifier": "ai.neuralnomads.codenomad.client",
"build": {
"beforeDevCommand": "npm run dev:bootstrap",

View File

@@ -1,6 +1,6 @@
{
"name": "@codenomad/ui",
"version": "0.13.3",
"version": "0.14.0",
"private": true,
"license": "MIT",
"type": "module",