mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-22 17:22:47 +00:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbb2b40855 | ||
|
|
c062278595 | ||
|
|
b9cdfb6528 | ||
|
|
1361da5ef6 | ||
|
|
3377ffed29 | ||
|
|
c56638ea8c | ||
|
|
185ab3f300 | ||
|
|
1694828275 | ||
|
|
74a377ff6a | ||
|
|
63923fdd07 | ||
|
|
11e76f6655 | ||
|
|
4a3e6dea63 | ||
|
|
f35d860725 |
+37
-49
@@ -1,82 +1,70 @@
|
||||
# Changelog
|
||||
|
||||
## 0.7.0-beta.3 - 2026-08-31
|
||||
## 0.7.1 - 2026-09-01
|
||||
|
||||
### Added
|
||||
|
||||
- Added readable prompts, details, and results for Paseo tool calls ([#4066](https://github.com/getpaseo/paseo/pull/4066))
|
||||
- Added zoom and pan to workspace images and assistant timeline previews ([#4032](https://github.com/getpaseo/paseo/pull/4032), [#4049](https://github.com/getpaseo/paseo/pull/4049), [#4064](https://github.com/getpaseo/paseo/pull/4064))
|
||||
- Added host navigation from plugin surfaces to agents and workspaces ([#3901](https://github.com/getpaseo/paseo/pull/3901) by [@omercnet](https://github.com/omercnet))
|
||||
- Added Astro syntax highlighting to files, the editor, and diffs ([#3997](https://github.com/getpaseo/paseo/pull/3997))
|
||||
- Added Claude Fable 5.1 with a 1M context window ([#4179](https://github.com/getpaseo/paseo/pull/4179))
|
||||
|
||||
### Improved
|
||||
|
||||
- Reused one OpenCode helper across ordinary agent sessions instead of spawning one per agent ([#4009](https://github.com/getpaseo/paseo/pull/4009))
|
||||
- Kept cold-start pull request status checks on the shared GitHub batch path ([#4025](https://github.com/getpaseo/paseo/pull/4025))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Android first-message submissions crashing when the keyboard restarted during composer teardown ([#4044](https://github.com/getpaseo/paseo/pull/4044))
|
||||
- Fixed long mobile drafts hiding composer controls behind the keyboard ([#4051](https://github.com/getpaseo/paseo/pull/4051))
|
||||
- Fixed web and Electron composers losing focus after submission ([#4067](https://github.com/getpaseo/paseo/pull/4067))
|
||||
- Fixed Android dictation leaving Bluetooth audio in call-quality routing after capture stopped ([#4069](https://github.com/getpaseo/paseo/pull/4069))
|
||||
- Fixed dictation retries timing out after an abandoned partial transcript ([#4065](https://github.com/getpaseo/paseo/pull/4065))
|
||||
- Fixed the model selector crashing in iPad desktop layouts ([#3992](https://github.com/getpaseo/paseo/pull/3992) by [@yzim](https://github.com/yzim))
|
||||
- Fixed archived agents disappearing when opened from History ([#4033](https://github.com/getpaseo/paseo/pull/4033))
|
||||
- Fixed Grok unified-billing accounts showing zero credits instead of weekly usage ([#4029](https://github.com/getpaseo/paseo/pull/4029) by [@Lite-G](https://github.com/Lite-G), [@claude](https://github.com/claude))
|
||||
- Fixed slow Pi and OMP startup requests timing out after 30 seconds ([#4008](https://github.com/getpaseo/paseo/pull/4008))
|
||||
- Fixed Android system navigation covering the Commits controls ([#4005](https://github.com/getpaseo/paseo/pull/4005))
|
||||
- Fixed new agent work appearing as reopened tasks ([#4068](https://github.com/getpaseo/paseo/pull/4068))
|
||||
|
||||
## 0.7.0-beta.2 - 2026-08-28
|
||||
|
||||
### Added
|
||||
|
||||
- Added SSH connections to existing remote daemons from Desktop and CLI ([#3989](https://github.com/getpaseo/paseo/pull/3989) by [@reidlevesque](https://github.com/reidlevesque))
|
||||
- Added workspace management and layout actions to the Command Center ([#3013](https://github.com/getpaseo/paseo/pull/3013) by [@cleiter](https://github.com/cleiter))
|
||||
- Added contextual composer-pill contributions for plugins ([#3956](https://github.com/getpaseo/paseo/pull/3956))
|
||||
- Added host UI primitives for client plugins ([#3967](https://github.com/getpaseo/paseo/pull/3967))
|
||||
- Added PR and MR number search to the Command Center ([#3008](https://github.com/getpaseo/paseo/pull/3008) by [@cleiter](https://github.com/cleiter))
|
||||
- Added session commands to agent SDK handles ([#3719](https://github.com/getpaseo/paseo/pull/3719) by [@gpambrozio](https://github.com/gpambrozio), [@marvin-ambrozio](https://github.com/marvin-ambrozio))
|
||||
|
||||
### Improved
|
||||
|
||||
- Batched GitHub pull request polling within the reserved API budget ([#3825](https://github.com/getpaseo/paseo/pull/3825) by [@dezchai](https://github.com/dezchai))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed daemon reconnects stalling while Git processes were queued ([#3945](https://github.com/getpaseo/paseo/pull/3945))
|
||||
- Fixed restored workspace tabs entering reconciliation loops ([#3987](https://github.com/getpaseo/paseo/pull/3987))
|
||||
- Fixed immediate dictation submissions omitting the newest speech segment ([#3968](https://github.com/getpaseo/paseo/pull/3968))
|
||||
- Fixed archived workspaces returning from durable cache ([#3975](https://github.com/getpaseo/paseo/pull/3975))
|
||||
- Fixed commits and squash merges bypassing configured Git signing ([#3976](https://github.com/getpaseo/paseo/pull/3976))
|
||||
- Fixed Markdown literal characters being replaced by typographic symbols ([#3253](https://github.com/getpaseo/paseo/pull/3253) by [@cleiter](https://github.com/cleiter))
|
||||
|
||||
## 0.7.0-beta.1 - 2026-08-27
|
||||
## 0.7.0 - 2026-08-31
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed the project license to Apache-2.0 ([#3944](https://github.com/getpaseo/paseo/pull/3944))
|
||||
- Changed Cmd/Ctrl+E to toggle Explorer visibility without selecting a view ([#3896](https://github.com/getpaseo/paseo/pull/3896))
|
||||
- Changed the default mobile content text size from 15px to 16px ([#4111](https://github.com/getpaseo/paseo/pull/4111))
|
||||
|
||||
### Added
|
||||
|
||||
- Added plugin installation and updates directly from Git repositories ([#3920](https://github.com/getpaseo/paseo/pull/3920))
|
||||
- Added plugin-defined timeline transformations and rendering ([#3940](https://github.com/getpaseo/paseo/pull/3940))
|
||||
- Added SSH connections to existing remote daemons from Desktop and CLI ([#3989](https://github.com/getpaseo/paseo/pull/3989) by [@reidlevesque](https://github.com/reidlevesque))
|
||||
- Added workspace management and layout actions to the Command Center ([#3013](https://github.com/getpaseo/paseo/pull/3013) by [@cleiter](https://github.com/cleiter))
|
||||
- Added contextual composer-pill contributions for plugins ([#3956](https://github.com/getpaseo/paseo/pull/3956))
|
||||
- Added host UI primitives for client plugins ([#3967](https://github.com/getpaseo/paseo/pull/3967))
|
||||
- Added readable prompts, details, and results for Paseo tool calls ([#4066](https://github.com/getpaseo/paseo/pull/4066))
|
||||
- Added zoom and pan to workspace images and assistant timeline previews ([#4032](https://github.com/getpaseo/paseo/pull/4032), [#4049](https://github.com/getpaseo/paseo/pull/4049), [#4064](https://github.com/getpaseo/paseo/pull/4064))
|
||||
- Added opening child folders directly in the preferred desktop editor ([#3615](https://github.com/getpaseo/paseo/pull/3615) by [@caikovsky](https://github.com/caikovsky))
|
||||
- Added host navigation from plugin surfaces to agents and workspaces ([#3901](https://github.com/getpaseo/paseo/pull/3901) by [@omercnet](https://github.com/omercnet))
|
||||
- Added host-rendered icons to plugin client surfaces ([#3903](https://github.com/getpaseo/paseo/pull/3903) by [@omercnet](https://github.com/omercnet))
|
||||
- Added semantic surface, border, success, and warning colors to plugin themes ([#3898](https://github.com/getpaseo/paseo/pull/3898) by [@omercnet](https://github.com/omercnet))
|
||||
- Added registered project listing to the public plugin SDK ([#3899](https://github.com/getpaseo/paseo/pull/3899) by [@omercnet](https://github.com/omercnet))
|
||||
- Added PR and MR number search to the Command Center ([#3008](https://github.com/getpaseo/paseo/pull/3008) by [@cleiter](https://github.com/cleiter))
|
||||
- Added session commands to agent SDK handles ([#3719](https://github.com/getpaseo/paseo/pull/3719) by [@gpambrozio](https://github.com/gpambrozio), [@marvin-ambrozio](https://github.com/marvin-ambrozio))
|
||||
- Added F-Droid release metadata and stable changelog generation ([#2501](https://github.com/getpaseo/paseo/pull/2501) by [@antonok-edm](https://github.com/antonok-edm))
|
||||
- Added Astro syntax highlighting to files, the editor, and diffs ([#3997](https://github.com/getpaseo/paseo/pull/3997))
|
||||
|
||||
### Improved
|
||||
|
||||
- Streamed assistant text at a steady visual rate instead of in arrival lumps ([#3612](https://github.com/getpaseo/paseo/pull/3612) by [@Tommypop2](https://github.com/Tommypop2))
|
||||
- Painted cached conversations before unrelated cache restoration and network loading ([#3907](https://github.com/getpaseo/paseo/pull/3907))
|
||||
- Reused one OpenCode helper across ordinary agent sessions instead of spawning one per agent ([#4009](https://github.com/getpaseo/paseo/pull/4009))
|
||||
- Batched GitHub pull request polling within the reserved API budget ([#3825](https://github.com/getpaseo/paseo/pull/3825) by [@dezchai](https://github.com/dezchai))
|
||||
- Added manual, action-required, and warning states to GitLab and Gitea checks ([#2337](https://github.com/getpaseo/paseo/pull/2337) by [@nllptrx](https://github.com/nllptrx))
|
||||
- Kept cold-start pull request status checks on the shared GitHub batch path ([#4025](https://github.com/getpaseo/paseo/pull/4025))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the daemon crashing when a Claude or OMP JSONL process closes during a write ([#4048](https://github.com/getpaseo/paseo/pull/4048))
|
||||
- Fixed Android first-message submissions crashing when the keyboard restarted during composer teardown ([#4044](https://github.com/getpaseo/paseo/pull/4044))
|
||||
- Fixed long mobile drafts hiding composer controls behind the keyboard ([#4051](https://github.com/getpaseo/paseo/pull/4051))
|
||||
- Fixed Android timelines shifting when tapping selectable text ([#4090](https://github.com/getpaseo/paseo/pull/4090))
|
||||
- Fixed web and Electron composers losing focus after submission ([#4067](https://github.com/getpaseo/paseo/pull/4067))
|
||||
- Fixed Android dictation leaving Bluetooth audio in call-quality routing after capture stopped ([#4069](https://github.com/getpaseo/paseo/pull/4069))
|
||||
- Fixed dictation retries timing out after an abandoned partial transcript ([#4065](https://github.com/getpaseo/paseo/pull/4065))
|
||||
- Fixed immediate dictation submissions omitting the newest speech segment ([#3968](https://github.com/getpaseo/paseo/pull/3968))
|
||||
- Fixed the model selector crashing in iPad desktop layouts ([#3992](https://github.com/getpaseo/paseo/pull/3992) by [@yzim](https://github.com/yzim))
|
||||
- Fixed OpenCode child-session prompts missing from provider subagent timelines ([#4055](https://github.com/getpaseo/paseo/pull/4055) by [@mcowger](https://github.com/mcowger))
|
||||
- Fixed archived agents disappearing when opened from History ([#4033](https://github.com/getpaseo/paseo/pull/4033))
|
||||
- Fixed restored workspace tabs entering reconciliation loops ([#3987](https://github.com/getpaseo/paseo/pull/3987))
|
||||
- Fixed archived workspaces returning from durable cache ([#3975](https://github.com/getpaseo/paseo/pull/3975))
|
||||
- Fixed Grok unified-billing accounts showing zero credits instead of weekly usage ([#4029](https://github.com/getpaseo/paseo/pull/4029) by [@Lite-G](https://github.com/Lite-G), [@claude](https://github.com/claude))
|
||||
- Fixed daemon reconnects stalling while Git processes were queued ([#3945](https://github.com/getpaseo/paseo/pull/3945))
|
||||
- Fixed slow Pi and OMP startup requests timing out after 30 seconds ([#4008](https://github.com/getpaseo/paseo/pull/4008))
|
||||
- Fixed commits and squash merges bypassing configured Git signing ([#3976](https://github.com/getpaseo/paseo/pull/3976))
|
||||
- Fixed Android system navigation covering the Commits controls ([#4005](https://github.com/getpaseo/paseo/pull/4005))
|
||||
- Fixed new agent work appearing as reopened tasks ([#4068](https://github.com/getpaseo/paseo/pull/4068))
|
||||
- Fixed Markdown literal characters being replaced by typographic symbols ([#3253](https://github.com/getpaseo/paseo/pull/3253) by [@cleiter](https://github.com/cleiter))
|
||||
- Fixed rewinding long conversations replaying the complete rebuilt timeline ([#3642](https://github.com/getpaseo/paseo/pull/3642))
|
||||
- Fixed rewound prompts remaining blank in the mounted iOS composer ([#3946](https://github.com/getpaseo/paseo/pull/3946))
|
||||
- Fixed native plugin setup and async client callbacks failing on iOS ([#3942](https://github.com/getpaseo/paseo/pull/3942))
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ Before adding a new component, read `components/ui/`. The primitive usually exis
|
||||
|
||||
Hierarchy is conveyed through weight and color, not size. Most interface text is `fontSize.base`; compact metadata and hints use `fontSize.sm`. The distinction between a row's primary line and its secondary line is `foreground` versus `foregroundMuted`.
|
||||
|
||||
The authored interface ramp uses a 14px base. New native installs default to 15px; web and desktop default to 14px. The Appearance **Interface size** setting is the rendered `fontSize.base` value and scales the rest of the UI ramp proportionally. Primary readable content has its own `fontSize.content`, which defaults to 15px on every platform. It owns message bodies, composer input, Markdown, and PR prose. Controls, navigation, metadata, tool chrome, code, diffs, editors, and terminals stay on their interface or code tokens. **Code size** remains independent.
|
||||
The authored interface ramp uses a 14px base. New native installs default to 15px; web and desktop default to 14px. The Appearance **Interface size** setting is the rendered `fontSize.base` value and scales the rest of the UI ramp proportionally. Primary readable content has its own `fontSize.content`, which defaults to 16px on native and 15px on web and desktop. It owns message bodies, composer input, Markdown, and PR prose. Controls, navigation, metadata, tool chrome, code, diffs, editors, and terminals stay on their interface or code tokens. **Code size** remains independent.
|
||||
|
||||
Weight has three tiers, applied by role:
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Changed
|
||||
- Changed the project license to Apache-2.0
|
||||
- Changed Cmd/Ctrl+E to toggle Explorer visibility without selecting a view
|
||||
- Changed the default mobile content text size from 15px to 16px
|
||||
|
||||
Added
|
||||
- Added plugin installation and updates directly from Git repositories
|
||||
- Added plugin-defined timeline transformations and rendering
|
||||
- Added SSH connections to existing remote daemons from Desktop and CLI
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
@@ -0,0 +1,11 @@
|
||||
Changed
|
||||
- Changed the project license to Apache-2.0
|
||||
- Changed Cmd/Ctrl+E to toggle Explorer visibility without selecting a view
|
||||
- Changed the default mobile content text size from 15px to 16px
|
||||
|
||||
Added
|
||||
- Added plugin installation and updates directly from Git repositories
|
||||
- Added plugin-defined timeline transformations and rendering
|
||||
- Added SSH connections to existing remote daemons from Desktop and CLI
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
@@ -0,0 +1,11 @@
|
||||
Changed
|
||||
- Changed the project license to Apache-2.0
|
||||
- Changed Cmd/Ctrl+E to toggle Explorer visibility without selecting a view
|
||||
- Changed the default mobile content text size from 15px to 16px
|
||||
|
||||
Added
|
||||
- Added plugin installation and updates directly from Git repositories
|
||||
- Added plugin-defined timeline transformations and rendering
|
||||
- Added SSH connections to existing remote daemons from Desktop and CLI
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
@@ -0,0 +1,11 @@
|
||||
Changed
|
||||
- Changed the project license to Apache-2.0
|
||||
- Changed Cmd/Ctrl+E to toggle Explorer visibility without selecting a view
|
||||
- Changed the default mobile content text size from 15px to 16px
|
||||
|
||||
Added
|
||||
- Added plugin installation and updates directly from Git repositories
|
||||
- Added plugin-defined timeline transformations and rendering
|
||||
- Added SSH connections to existing remote daemons from Desktop and CLI
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
@@ -0,0 +1,4 @@
|
||||
Added
|
||||
- Added Claude Fable 5.1 with a 1M context window
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
@@ -0,0 +1,4 @@
|
||||
Added
|
||||
- Added Claude Fable 5.1 with a 1M context window
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
@@ -0,0 +1,4 @@
|
||||
Added
|
||||
- Added Claude Fable 5.1 with a 1M context window
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
@@ -0,0 +1,4 @@
|
||||
Added
|
||||
- Added Claude Fable 5.1 with a 1M context window
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
+1
-1
@@ -1 +1 @@
|
||||
sha256-zn0UJm7RPR5mlCvKTddcq0bIugrp6VcRBPdwJX8kJIg=
|
||||
sha256-HFYBrCP62r6ZofazBIGSOnMhk8IIEmv+TFJMpqTIJQ8=
|
||||
|
||||
Generated
+27
-27
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "paseo",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "paseo",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
@@ -36298,7 +36298,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@getpaseo/app",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "6.10.4",
|
||||
"@codemirror/language": "6.12.4",
|
||||
@@ -37363,12 +37363,12 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@getpaseo/cli",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^1.0.0",
|
||||
"@getpaseo/client": "0.7.0-beta.3",
|
||||
"@getpaseo/protocol": "0.7.0-beta.3",
|
||||
"@getpaseo/server": "0.7.0-beta.3",
|
||||
"@getpaseo/client": "0.7.1",
|
||||
"@getpaseo/protocol": "0.7.1",
|
||||
"@getpaseo/server": "0.7.1",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
@@ -37615,10 +37615,10 @@
|
||||
},
|
||||
"packages/client": {
|
||||
"name": "@getpaseo/client",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"@getpaseo/protocol": "0.7.0-beta.3",
|
||||
"@getpaseo/relay": "0.7.0-beta.3",
|
||||
"@getpaseo/protocol": "0.7.1",
|
||||
"@getpaseo/relay": "0.7.1",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -37629,7 +37629,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@getpaseo/desktop",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@getpaseo/cli": "*",
|
||||
@@ -37872,7 +37872,7 @@
|
||||
},
|
||||
"packages/expo-two-way-audio": {
|
||||
"name": "@getpaseo/expo-two-way-audio",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
@@ -38768,7 +38768,7 @@
|
||||
},
|
||||
"packages/highlight": {
|
||||
"name": "@getpaseo/highlight",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "6.12.4",
|
||||
"@codemirror/legacy-modes": "^6.5.3",
|
||||
@@ -38797,13 +38797,13 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@getpaseo/plugin",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@getpaseo/client": "0.7.0-beta.3",
|
||||
"@getpaseo/protocol": "0.7.0-beta.3",
|
||||
"@getpaseo/client": "0.7.1",
|
||||
"@getpaseo/protocol": "0.7.1",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/react": "~19.2.0",
|
||||
"react": "19.1.0",
|
||||
@@ -38812,15 +38812,15 @@
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@getpaseo/client": "0.7.0-beta.3",
|
||||
"@getpaseo/protocol": "0.7.0-beta.3",
|
||||
"@getpaseo/client": "0.7.1",
|
||||
"@getpaseo/protocol": "0.7.1",
|
||||
"react": "19.1.0",
|
||||
"zod": "^4.4.3"
|
||||
}
|
||||
},
|
||||
"packages/protocol": {
|
||||
"name": "@getpaseo/protocol",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
@@ -38833,7 +38833,7 @@
|
||||
},
|
||||
"packages/relay": {
|
||||
"name": "@getpaseo/relay",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"tweetnacl": "^1.0.3",
|
||||
@@ -39051,17 +39051,17 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@getpaseo/server",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.17.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.246",
|
||||
"@anthropic-ai/sdk": "^0.104.2",
|
||||
"@babel/parser": "^7.29.8",
|
||||
"@getpaseo/client": "0.7.0-beta.3",
|
||||
"@getpaseo/highlight": "0.7.0-beta.3",
|
||||
"@getpaseo/plugin": "0.7.0-beta.3",
|
||||
"@getpaseo/protocol": "0.7.0-beta.3",
|
||||
"@getpaseo/relay": "0.7.0-beta.3",
|
||||
"@getpaseo/client": "0.7.1",
|
||||
"@getpaseo/highlight": "0.7.1",
|
||||
"@getpaseo/plugin": "0.7.1",
|
||||
"@getpaseo/protocol": "0.7.1",
|
||||
"@getpaseo/relay": "0.7.1",
|
||||
"@isaacs/ttlcache": "^2.1.4",
|
||||
"@modelcontextprotocol/sdk": "1.29.0",
|
||||
"@opencode-ai/sdk": "1.14.46",
|
||||
@@ -39985,7 +39985,7 @@
|
||||
},
|
||||
"packages/website": {
|
||||
"name": "@getpaseo/website",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "^1.29.1",
|
||||
"@cloudflare/workers-types": "^4.20260317.1",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "paseo",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"private": true,
|
||||
"description": "Paseo: voice-controlled development environment for local AI coding agents",
|
||||
"keywords": [
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
openPrPane,
|
||||
expectPrPaneTitle,
|
||||
expectPrPaneState,
|
||||
expectPrPaneCheckSummary,
|
||||
expectPrPaneActivityCount,
|
||||
expectPrPaneChecks,
|
||||
expectPrPaneActivity,
|
||||
} from "../support/helpers/pr-pane";
|
||||
import { gotoWorkspace } from "../support/helpers/launcher";
|
||||
import {
|
||||
@@ -50,7 +50,6 @@ test.describe("PR pane", () => {
|
||||
],
|
||||
},
|
||||
{ title: "PR with reviews", state: "open", commentCount: 3 },
|
||||
{ title: "PR with no checks", state: "open" },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -102,25 +101,21 @@ test.describe("PR pane", () => {
|
||||
await expectPrPaneTitle(page, "Work in progress");
|
||||
});
|
||||
|
||||
test("renders check pills with correct success/failure/pending counts", async ({ page }) => {
|
||||
test("renders seeded checks in their success, failure, and pending groups", async ({ page }) => {
|
||||
await gotoWorkspace(page, workspaceByTitle.get("PR with mixed checks")!);
|
||||
await openPrPane(page);
|
||||
|
||||
await expectPrPaneCheckSummary(page, { success: 2, failure: 1, pending: 1 });
|
||||
await expectPrPaneChecks(page, {
|
||||
success: ["build-1", "build-2"],
|
||||
failure: ["deploy"],
|
||||
pending: ["security"],
|
||||
});
|
||||
});
|
||||
|
||||
test("renders activity rows with correct count", async ({ page }) => {
|
||||
test("renders every seeded activity comment", async ({ page }) => {
|
||||
await gotoWorkspace(page, workspaceByTitle.get("PR with reviews")!);
|
||||
await openPrPane(page);
|
||||
|
||||
await expectPrPaneActivityCount(page, 3);
|
||||
});
|
||||
|
||||
test("renders gracefully with zero checks", async ({ page }) => {
|
||||
await gotoWorkspace(page, workspaceByTitle.get("PR with no checks")!);
|
||||
await openPrPane(page);
|
||||
|
||||
await expectPrPaneCheckSummary(page, { success: 0, failure: 0, pending: 0 });
|
||||
await expectPrPaneTitle(page, "PR with no checks");
|
||||
await expectPrPaneActivity(page, ["Test comment 1", "Test comment 2", "Test comment 3"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,7 +43,6 @@ const cases: ProviderSubagentCase[] = [
|
||||
sentinel: "OPENCODE_CHILD_SENTINEL",
|
||||
expectedName: "Verify OpenCode descriptor",
|
||||
expectedSubtitle: /explore · gpt-5\.4(?: · [^\n·]+)? · \d+(?:\.\d+)?k? tokens/i,
|
||||
expectsUserMessage: false,
|
||||
providerConfig: { model: "openai/gpt-5.4" },
|
||||
prompt:
|
||||
'Use the task tool exactly once with description "Verify OpenCode descriptor" and the explore subagent. Ask it to reply with exactly OPENCODE_CHILD_SENTINEL and do nothing else. Wait for it, then reply ROOT_DONE.',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
import type { CheckPresentationCounts } from "@/git/check-presentation";
|
||||
import { getStateLabel } from "@/git/pull-request-panel/data";
|
||||
import { openPullRequestPanel } from "./workspace-tabs";
|
||||
|
||||
@@ -20,23 +19,31 @@ export async function expectPrPaneState(
|
||||
});
|
||||
}
|
||||
|
||||
async function assertCheckPill(page: Page, testId: string, count: number): Promise<void> {
|
||||
const locator = page.getByTestId(testId);
|
||||
await expect(locator).toHaveCount(count > 0 ? 1 : 0, { timeout: 15_000 });
|
||||
if (count > 0) {
|
||||
await expect(locator).toContainText(String(count));
|
||||
export async function expectPrPaneChecks(
|
||||
page: Page,
|
||||
checks: { success: string[]; failure: string[]; pending: string[] },
|
||||
): Promise<void> {
|
||||
const section = page.getByTestId("pr-pane-checks");
|
||||
const groups = [
|
||||
{ status: "failure", label: /failing checks?$/, names: checks.failure },
|
||||
{ status: "pending", label: /in progress checks?$/, names: checks.pending },
|
||||
{ status: "success", label: /successful checks?$/, names: checks.success },
|
||||
];
|
||||
|
||||
for (const group of groups) {
|
||||
const groupSection = section.getByTestId(`pr-pane-check-group-${group.status}`);
|
||||
await expect(groupSection.getByRole("button", { name: group.label })).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
for (const name of group.names) {
|
||||
await expect(groupSection.getByText(name, { exact: true })).toHaveCount(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function expectPrPaneCheckSummary(
|
||||
page: Page,
|
||||
counts: Pick<CheckPresentationCounts, "success" | "failure" | "pending">,
|
||||
): Promise<void> {
|
||||
await assertCheckPill(page, "pr-pane-check-success", counts.success);
|
||||
await assertCheckPill(page, "pr-pane-check-failure", counts.failure);
|
||||
await assertCheckPill(page, "pr-pane-check-pending", counts.pending);
|
||||
}
|
||||
|
||||
export async function expectPrPaneActivityCount(page: Page, count: number): Promise<void> {
|
||||
await expect(page.getByTestId("pr-pane-activity-row")).toHaveCount(count, { timeout: 15_000 });
|
||||
export async function expectPrPaneActivity(page: Page, bodies: string[]): Promise<void> {
|
||||
const activity = page.getByTestId("pr-pane-activity-row");
|
||||
for (const body of bodies) {
|
||||
await expect(activity.getByText(body, { exact: true })).toBeVisible({ timeout: 15_000 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/app",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"private": true,
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -148,7 +148,7 @@ function CheckGroup({
|
||||
}) {
|
||||
const handlePress = useCallback(() => onToggle(group.status), [group.status, onToggle]);
|
||||
return (
|
||||
<View>
|
||||
<View testID={`pr-pane-check-group-${group.status}`}>
|
||||
<Pressable onPress={handlePress} style={styles.groupHeader} accessibilityRole="button">
|
||||
<Text style={styles.groupLabel}>{group.label}</Text>
|
||||
{collapsed ? (
|
||||
|
||||
@@ -20,6 +20,11 @@ function storedSendBehavior(storage: Storage): SendBehavior | undefined {
|
||||
return raw === undefined ? undefined : JSON.parse(raw).sendBehavior;
|
||||
}
|
||||
|
||||
function storedContentFontSize(storage: Storage): number | undefined {
|
||||
const raw = storage.entries.get(APP_SETTINGS_KEY);
|
||||
return raw === undefined ? undefined : JSON.parse(raw).contentFontSize;
|
||||
}
|
||||
|
||||
/** An in-memory storage whose write to `failingKey` always throws, as a full disk would. */
|
||||
function createFailingWriteStorage(failingKey: string): Storage {
|
||||
const storage = createInMemoryKeyValueStorage();
|
||||
@@ -81,6 +86,47 @@ describe("migrateAppSettings", () => {
|
||||
expect(appliedIds(storage)).toEqual(["some-later-migration", "steer-default"]);
|
||||
});
|
||||
|
||||
it("migrates every mobile 15px content preference to 16px", async () => {
|
||||
const storage = createInMemoryKeyValueStorage();
|
||||
const settings = { ...settingsWith("steer"), contentFontSize: 15 };
|
||||
|
||||
const result = await migrateAppSettings(settings, storage, undefined, { native: true });
|
||||
|
||||
expect(result.contentFontSize).toBe(16);
|
||||
expect(storedContentFontSize(storage)).toBe(16);
|
||||
expect(appliedIds(storage)).toEqual(["steer-default", "mobile-content-16"]);
|
||||
});
|
||||
|
||||
it("leaves a 15px web content preference unchanged", async () => {
|
||||
const storage = createInMemoryKeyValueStorage();
|
||||
const settings = { ...settingsWith("steer"), contentFontSize: 15 };
|
||||
|
||||
const result = await migrateAppSettings(settings, storage, undefined, { native: false });
|
||||
|
||||
expect(result.contentFontSize).toBe(15);
|
||||
expect(storedContentFontSize(storage)).toBeUndefined();
|
||||
expect(appliedIds(storage)).toEqual(["steer-default"]);
|
||||
});
|
||||
|
||||
it("lets a mobile user choose 15px after the default migration ran", async () => {
|
||||
const storage = createInMemoryKeyValueStorage();
|
||||
await migrateAppSettings(
|
||||
{ ...settingsWith("steer"), contentFontSize: 15 },
|
||||
storage,
|
||||
undefined,
|
||||
{ native: true },
|
||||
);
|
||||
|
||||
const result = await migrateAppSettings(
|
||||
{ ...settingsWith("steer"), contentFontSize: 15 },
|
||||
storage,
|
||||
undefined,
|
||||
{ native: true },
|
||||
);
|
||||
|
||||
expect(result.contentFontSize).toBe(15);
|
||||
});
|
||||
|
||||
it("stays unmarked when the settings write fails, so a later launch retries", async () => {
|
||||
const storage = createFailingWriteStorage(APP_SETTINGS_KEY);
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ const AppliedMigrationsSchema = z.strictObject({ applied: z.array(z.string()) })
|
||||
*/
|
||||
const STEER_DEFAULT_MIGRATION = "steer-default";
|
||||
|
||||
/** Existing mobile installs materialized the old 15px content default in storage. */
|
||||
const MOBILE_CONTENT_16_MIGRATION = "mobile-content-16";
|
||||
|
||||
/**
|
||||
* Brings stored settings up to date, returning what the caller should use. Owns both writes so
|
||||
* the marker can only ever be written after the settings it describes: a failed marker write
|
||||
@@ -23,6 +26,7 @@ export async function migrateAppSettings(
|
||||
settings: AppSettings,
|
||||
storage: KeyValueStorage,
|
||||
stored?: PersistedAppSettings,
|
||||
options: { native?: boolean } = {},
|
||||
): Promise<AppSettings> {
|
||||
const migrationMarker = await readValidatedJson(
|
||||
storage,
|
||||
@@ -30,12 +34,26 @@ export async function migrateAppSettings(
|
||||
AppliedMigrationsSchema,
|
||||
);
|
||||
const applied = new Set(migrationMarker?.applied ?? []);
|
||||
if (applied.has(STEER_DEFAULT_MIGRATION)) {
|
||||
let addedMigration = false;
|
||||
|
||||
let migrated = settings;
|
||||
if (!applied.has(STEER_DEFAULT_MIGRATION)) {
|
||||
migrated =
|
||||
migrated.sendBehavior === "interrupt" ? { ...migrated, sendBehavior: "steer" } : migrated;
|
||||
applied.add(STEER_DEFAULT_MIGRATION);
|
||||
addedMigration = true;
|
||||
}
|
||||
|
||||
if (options.native && !applied.has(MOBILE_CONTENT_16_MIGRATION)) {
|
||||
migrated = migrated.contentFontSize === 15 ? { ...migrated, contentFontSize: 16 } : migrated;
|
||||
applied.add(MOBILE_CONTENT_16_MIGRATION);
|
||||
addedMigration = true;
|
||||
}
|
||||
|
||||
if (!addedMigration) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
const migrated: AppSettings =
|
||||
settings.sendBehavior === "interrupt" ? { ...settings, sendBehavior: "steer" } : settings;
|
||||
if (migrated !== settings) {
|
||||
const storedSidebarRowItems = stored?.sidebarRowItems ?? {};
|
||||
await storage.setItem(
|
||||
@@ -48,7 +66,6 @@ export async function migrateAppSettings(
|
||||
);
|
||||
}
|
||||
|
||||
applied.add(STEER_DEFAULT_MIGRATION);
|
||||
await storage.setItem(SETTINGS_MIGRATIONS_KEY, JSON.stringify({ applied: [...applied] }));
|
||||
return migrated;
|
||||
}
|
||||
|
||||
@@ -659,8 +659,8 @@ describe("appearance settings", () => {
|
||||
expect(defaultUiBaseFontSize(false)).toBe(14);
|
||||
});
|
||||
|
||||
it("uses a 15px content default on mobile and web", () => {
|
||||
expect(defaultContentFontSize(true)).toBe(15);
|
||||
it("uses a 16px content default on mobile and a 15px default on web", () => {
|
||||
expect(defaultContentFontSize(true)).toBe(16);
|
||||
expect(defaultContentFontSize(false)).toBe(15);
|
||||
expect(DEFAULT_CONTENT_FONT_SIZE).toBe(defaultContentFontSize(false));
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ export const DEFAULT_UI_BASE_FONT_SIZE = defaultUiBaseFontSize(isNative);
|
||||
export const MIN_UI_BASE_FONT_SIZE = 10;
|
||||
export const MAX_UI_BASE_FONT_SIZE = 21;
|
||||
export function defaultContentFontSize(native: boolean): number {
|
||||
return native ? 15 : FONT_SIZE.content;
|
||||
return native ? 16 : FONT_SIZE.content;
|
||||
}
|
||||
|
||||
export const DEFAULT_CONTENT_FONT_SIZE = defaultContentFontSize(isNative);
|
||||
@@ -75,7 +75,7 @@ export interface AppSettings {
|
||||
uiFontFamily: string; // "" = platform default UI stack
|
||||
monoFontFamily: string; // "" = platform default mono stack
|
||||
uiBaseFontSize: number; // clamped px, platform default 14 or 15
|
||||
contentFontSize: number; // clamped px, default 15
|
||||
contentFontSize: number; // clamped px, platform default 15 or 16
|
||||
codeFontSize: number; // clamped px, default 12
|
||||
syntaxTheme: SyntaxThemeId; // default "one"
|
||||
workspaceTitleSource: WorkspaceTitleSource;
|
||||
@@ -343,7 +343,7 @@ export async function loadAppSettingsFromStorage(deps: SettingsDeps): Promise<Ap
|
||||
await writeAppSettings(deps.storage, read.stored, read.settings);
|
||||
}
|
||||
const { needsWrite: _needsWrite, ...stored } = read.stored;
|
||||
return await migrateAppSettings(read.settings, deps.storage, stored);
|
||||
return await migrateAppSettings(read.settings, deps.storage, stored, { native: isNative });
|
||||
} catch (error) {
|
||||
console.error("[AppSettings] Failed to load settings:", error);
|
||||
throw error;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/cli",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"description": "Paseo CLI - control your AI coding agents from the command line",
|
||||
"bin": {
|
||||
"paseo": "bin/paseo"
|
||||
@@ -28,9 +28,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^1.0.0",
|
||||
"@getpaseo/client": "0.7.0-beta.3",
|
||||
"@getpaseo/protocol": "0.7.0-beta.3",
|
||||
"@getpaseo/server": "0.7.0-beta.3",
|
||||
"@getpaseo/client": "0.7.1",
|
||||
"@getpaseo/protocol": "0.7.1",
|
||||
"@getpaseo/server": "0.7.1",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/client",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"description": "Paseo client SDK package",
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -39,8 +39,8 @@
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@getpaseo/protocol": "0.7.0-beta.3",
|
||||
"@getpaseo/relay": "0.7.0-beta.3",
|
||||
"@getpaseo/protocol": "0.7.1",
|
||||
"@getpaseo/relay": "0.7.1",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/desktop",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"private": true,
|
||||
"description": "Paseo desktop app (Electron wrapper)",
|
||||
"homepage": "https://paseo.sh",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/expo-two-way-audio",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"description": "Native module for two way audio streaming",
|
||||
"keywords": [
|
||||
"ExpoTwoWayAudio",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/highlight",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"files": [
|
||||
"dist",
|
||||
"!dist/**/*.map",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/plugin",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"description": "Paseo plugin SDK package",
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -44,8 +44,8 @@
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@getpaseo/client": "0.7.0-beta.3",
|
||||
"@getpaseo/protocol": "0.7.0-beta.3",
|
||||
"@getpaseo/client": "0.7.1",
|
||||
"@getpaseo/protocol": "0.7.1",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/react": "~19.2.0",
|
||||
"react": "19.1.0",
|
||||
@@ -54,8 +54,8 @@
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@getpaseo/client": "0.7.0-beta.3",
|
||||
"@getpaseo/protocol": "0.7.0-beta.3",
|
||||
"@getpaseo/client": "0.7.1",
|
||||
"@getpaseo/protocol": "0.7.1",
|
||||
"react": "19.1.0",
|
||||
"zod": "^4.4.3"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/protocol",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"description": "Paseo shared protocol schemas and wire types",
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/relay",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"description": "Paseo relay for bridging daemon and client connections",
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/server",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"description": "Paseo backend server",
|
||||
"files": [
|
||||
"dist/server",
|
||||
@@ -72,11 +72,11 @@
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.246",
|
||||
"@anthropic-ai/sdk": "^0.104.2",
|
||||
"@babel/parser": "^7.29.8",
|
||||
"@getpaseo/client": "0.7.0-beta.3",
|
||||
"@getpaseo/highlight": "0.7.0-beta.3",
|
||||
"@getpaseo/plugin": "0.7.0-beta.3",
|
||||
"@getpaseo/protocol": "0.7.0-beta.3",
|
||||
"@getpaseo/relay": "0.7.0-beta.3",
|
||||
"@getpaseo/client": "0.7.1",
|
||||
"@getpaseo/highlight": "0.7.1",
|
||||
"@getpaseo/plugin": "0.7.1",
|
||||
"@getpaseo/protocol": "0.7.1",
|
||||
"@getpaseo/relay": "0.7.1",
|
||||
"@isaacs/ttlcache": "^2.1.4",
|
||||
"@modelcontextprotocol/sdk": "1.29.0",
|
||||
"@opencode-ai/sdk": "1.14.46",
|
||||
|
||||
@@ -5543,6 +5543,88 @@ test("waitForAgentRunStart resolves while a foreground run is still only pending
|
||||
expect(manager.getAgent(snapshot.id)?.lifecycle).toBe("idle");
|
||||
});
|
||||
|
||||
test("waitForAgentRunStart ignores a prior turn error while the next run starts", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-error-resume-"));
|
||||
const storage = new AgentStorage(join(workdir, "agents"), logger);
|
||||
const retryStartEntered = deferred<void>();
|
||||
const releaseRetryStart = deferred<void>();
|
||||
|
||||
class ErrorThenResumeSession extends TestAgentSession {
|
||||
override async startTurn(): Promise<{ turnId: string }> {
|
||||
const turnId = `turn-${++this.turnIdCounter}`;
|
||||
if (this.turnIdCounter === 1) {
|
||||
void (async () => {
|
||||
this.pushEvent({ type: "turn_started", provider: this.provider, turnId });
|
||||
this.pushEvent({
|
||||
type: "turn_failed",
|
||||
provider: this.provider,
|
||||
turnId,
|
||||
error: "model at capacity",
|
||||
});
|
||||
})();
|
||||
return { turnId };
|
||||
}
|
||||
|
||||
retryStartEntered.resolve();
|
||||
await releaseRetryStart.promise;
|
||||
void (async () => {
|
||||
this.pushEvent({ type: "turn_started", provider: this.provider, turnId });
|
||||
this.pushEvent({ type: "turn_completed", provider: this.provider, turnId });
|
||||
})();
|
||||
return { turnId };
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorThenResumeClient extends TestAgentClient {
|
||||
override async createSession(config: AgentSessionConfig): Promise<AgentSession> {
|
||||
return new ErrorThenResumeSession(config);
|
||||
}
|
||||
}
|
||||
|
||||
const manager = new AgentManager({
|
||||
clients: { codex: new ErrorThenResumeClient() },
|
||||
registry: storage,
|
||||
logger,
|
||||
});
|
||||
let agentId: string | null = null;
|
||||
|
||||
try {
|
||||
const snapshot = await manager.createAgent({ provider: "codex", cwd: workdir }, undefined, {
|
||||
workspaceId: undefined,
|
||||
});
|
||||
agentId = snapshot.id;
|
||||
|
||||
await manager.runAgent(agentId, "fail").catch(() => undefined);
|
||||
expect(manager.getAgent(agentId)?.lifecycle).toBe("error");
|
||||
|
||||
const dispatch = await startAgentRun(manager, agentId, "resume", logger);
|
||||
expect(dispatch.disposition).toBe("turn_started");
|
||||
const wait = manager.waitForAgentRunStart(agentId);
|
||||
let earlyResult: "pending" | "resolved" | "rejected" = "pending";
|
||||
void wait.then(
|
||||
() => {
|
||||
earlyResult = "resolved";
|
||||
return earlyResult;
|
||||
},
|
||||
() => {
|
||||
earlyResult = "rejected";
|
||||
return earlyResult;
|
||||
},
|
||||
);
|
||||
await retryStartEntered.promise;
|
||||
await Promise.resolve();
|
||||
|
||||
expect(earlyResult).toBe("pending");
|
||||
releaseRetryStart.resolve();
|
||||
await expect(wait).resolves.toBeUndefined();
|
||||
await manager.waitForAgentEvent(agentId);
|
||||
} finally {
|
||||
releaseRetryStart.resolve();
|
||||
if (agentId) await manager.closeAgent(agentId).catch(() => undefined);
|
||||
rmSync(workdir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("replaceAgentRun does not emit idle or resolve waiters between interrupted and replacement runs", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-replace-run-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
|
||||
@@ -2165,6 +2165,7 @@ export class AgentManager {
|
||||
}
|
||||
agent.pendingReplacement = false;
|
||||
const errorMsg = error instanceof Error ? error.message : "Failed to start turn";
|
||||
pendingRun.start = { status: "failed", error: errorMsg };
|
||||
await this.handleStreamEvent(agent, {
|
||||
type: "turn_failed",
|
||||
provider: agent.provider,
|
||||
@@ -2232,8 +2233,7 @@ export class AgentManager {
|
||||
agent.pendingReplacement = false;
|
||||
}
|
||||
const turnStartedAt = new Date();
|
||||
pendingRun.started = true;
|
||||
pendingRun.turnId = turnId;
|
||||
pendingRun.start = { status: "started", turnId };
|
||||
agent.activeForegroundTurnId = turnId;
|
||||
this.openActiveTurn(agent, turnId, turnStartedAt);
|
||||
agent.lifecycle = "running";
|
||||
@@ -2575,7 +2575,10 @@ export class AgentManager {
|
||||
}
|
||||
|
||||
const pendingRun = this.runs.getPendingRun(agentId);
|
||||
if ((snapshot.lifecycle === "running" || pendingRun?.started) && !snapshot.pendingReplacement) {
|
||||
if (
|
||||
(snapshot.lifecycle === "running" || pendingRun?.start.status === "started") &&
|
||||
!snapshot.pendingReplacement
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2640,14 +2643,19 @@ export class AgentManager {
|
||||
|
||||
const currentPendingRun = this.runs.getPendingRun(agentId);
|
||||
if (
|
||||
(current.lifecycle === "running" || currentPendingRun?.started) &&
|
||||
(current.lifecycle === "running" || currentPendingRun?.start.status === "started") &&
|
||||
!current.pendingReplacement
|
||||
) {
|
||||
finishOk();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (current.lifecycle === "error" && !currentPendingRun?.started) {
|
||||
if (currentPendingRun?.start.status === "failed") {
|
||||
finishErr(new Error(currentPendingRun.start.error));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (current.lifecycle === "error" && !currentPendingRun) {
|
||||
finishErr(new Error(current.lastError ?? `Agent ${agentId} failed to start`));
|
||||
return true;
|
||||
}
|
||||
@@ -2734,16 +2742,17 @@ export class AgentManager {
|
||||
return { status: settlement === "completed" ? "settled" : "refused" };
|
||||
}
|
||||
|
||||
if (settlement === "timed_out" && run.turnId) {
|
||||
const runTurnId = this.runs.getTurnId(agentId);
|
||||
if (settlement === "timed_out" && runTurnId) {
|
||||
this.logger.warn(
|
||||
{ agentId, turnId: run.turnId, kind: run.kind },
|
||||
{ agentId, turnId: runTurnId, kind: run.kind },
|
||||
"cancelAgentRun: acknowledged turn still active after timeout, force-canceling",
|
||||
);
|
||||
await this.dispatchSessionEvent(agent, {
|
||||
type: "turn_canceled",
|
||||
provider: agent.provider,
|
||||
reason: "interrupted",
|
||||
turnId: run.turnId,
|
||||
turnId: runTurnId,
|
||||
});
|
||||
await run.settledPromise;
|
||||
} else if (settlement === "timed_out" && run.kind === "foreground") {
|
||||
@@ -3023,7 +3032,7 @@ export class AgentManager {
|
||||
let hasStarted =
|
||||
isAgentBusy(initialStatus) ||
|
||||
Boolean(snapshot.activeForegroundTurnId) ||
|
||||
Boolean(pendingForegroundRun?.started);
|
||||
pendingForegroundRun?.start.status === "started";
|
||||
let terminalStatusOverride: AgentLifecycleStatus | null = null;
|
||||
let finished = false;
|
||||
|
||||
@@ -3429,7 +3438,7 @@ export class AgentManager {
|
||||
return;
|
||||
}
|
||||
const pendingRun = this.runs.getPendingRun(agentId);
|
||||
if (pendingRun && !pendingRun.started) {
|
||||
if (pendingRun?.start.status === "pending") {
|
||||
pendingRun.stagedEvents.push(event);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ export interface PendingForegroundRun {
|
||||
token: string;
|
||||
kind: "foreground";
|
||||
stagedEvents: AgentStreamEvent[];
|
||||
turnId: string | null;
|
||||
started: boolean;
|
||||
start:
|
||||
| { status: "pending" }
|
||||
| { status: "started"; turnId: string }
|
||||
| { status: "failed"; error: string };
|
||||
settled: boolean;
|
||||
settledPromise: Promise<void>;
|
||||
resolveSettled: () => void;
|
||||
@@ -64,6 +66,13 @@ export class AgentRunState {
|
||||
return this.runs.has(agentId);
|
||||
}
|
||||
|
||||
getTurnId(agentId: string): string | null {
|
||||
const run = this.runs.get(agentId);
|
||||
if (!run) return null;
|
||||
if (run.kind === "autonomous") return run.turnId;
|
||||
return run.start.status === "started" ? run.start.turnId : null;
|
||||
}
|
||||
|
||||
trackAutonomousRun(agentId: string, turnId: string | null): TrackedAgentRun {
|
||||
const current = this.runs.get(agentId);
|
||||
if (current) {
|
||||
@@ -85,7 +94,10 @@ export class AgentRunState {
|
||||
if (!run) {
|
||||
return;
|
||||
}
|
||||
if (run.kind === "foreground" && (run.turnId === null || run.turnId !== turnId)) {
|
||||
if (
|
||||
run.kind === "foreground" &&
|
||||
(run.start.status !== "started" || run.start.turnId !== turnId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
@@ -268,8 +280,7 @@ function createPendingForegroundRun(): PendingForegroundRun {
|
||||
return {
|
||||
...createTrackedRunState(),
|
||||
kind: "foreground",
|
||||
turnId: null,
|
||||
started: false,
|
||||
start: { status: "pending" },
|
||||
stagedEvents: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -425,6 +425,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
|
||||
expect(models.map((m) => m.id)).toEqual([
|
||||
"claude-opus-5",
|
||||
"claude-fable-5-1",
|
||||
"claude-fable-5",
|
||||
"claude-fable-5[1m]",
|
||||
"claude-opus-4-8[1m]",
|
||||
@@ -495,6 +496,7 @@ describe("ClaudeAgentClient.fetchCatalog", () => {
|
||||
};
|
||||
|
||||
expect(getThinkingIds("claude-opus-5")).toContain("ultracode");
|
||||
expect(getThinkingIds("claude-fable-5-1")).toContain("ultracode");
|
||||
expect(getThinkingIds("claude-fable-5")).toContain("ultracode");
|
||||
expect(getThinkingIds("claude-opus-4-8[1m]")).toContain("ultracode");
|
||||
expect(getThinkingIds("claude-opus-4-8")).toContain("ultracode");
|
||||
|
||||
@@ -45,12 +45,19 @@ export const CLAUDE_MODEL_MANIFEST = [
|
||||
supportsThinkingDisabled: true,
|
||||
supportsFastMode: true,
|
||||
},
|
||||
{
|
||||
id: "claude-fable-5-1",
|
||||
label: "Fable 5.1",
|
||||
description: "Fable 5.1 · Most powerful model",
|
||||
contextWindowMaxTokens: 1_000_000,
|
||||
effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
|
||||
},
|
||||
{
|
||||
id: "claude-fable-5",
|
||||
// COMPAT(claudeFable5OneMillionId): added in v0.3.0, remove after 2027-02-06 once pre-v0.3.0 app preferences are outside support.
|
||||
aliases: ["claude-fable-5[1m]"],
|
||||
label: "Fable 5",
|
||||
description: "Fable 5 · Most powerful model",
|
||||
description: "Fable 5 · Previous release",
|
||||
minimumClaudeCodeVersion: "2.1.169",
|
||||
contextWindowMaxTokens: 1_000_000,
|
||||
effortLevels: CLAUDE_EFFORT_LEVELS.xhigh,
|
||||
@@ -326,7 +333,7 @@ export function normalizeClaudeManifestModelId(value: string | null | undefined)
|
||||
}
|
||||
|
||||
const runtimeMatch = trimmed.match(
|
||||
/^(?:claude[-_ ])?(opus|sonnet|haiku)[-_ ]+(\d+)[-.](\d+)(?:\[1m\])?(?:[-_ ]+\d{8})?(?:\[1m\])?$/i,
|
||||
/^(?:claude[-_ ])?(fable|opus|sonnet|haiku)[-_ ]+(\d+)[-.](\d+)(?:\[1m\])?(?:[-_ ]+\d{8})?(?:\[1m\])?$/i,
|
||||
);
|
||||
if (!runtimeMatch) {
|
||||
return null;
|
||||
@@ -371,7 +378,7 @@ export function normalizeClaudeRuntimeModelId(value: string | null | undefined):
|
||||
}
|
||||
|
||||
const runtimeMatch = trimmed.match(
|
||||
/claude[-_ ](opus|sonnet|haiku)[-_ ]+(\d+)[-.](\d+)(\[1m\])?/i,
|
||||
/claude[-_ ](fable|opus|sonnet|haiku)[-_ ]+(\d+)[-.](\d+)(\[1m\])?/i,
|
||||
);
|
||||
if (!runtimeMatch) {
|
||||
return null;
|
||||
@@ -417,6 +424,14 @@ function normalizeMajorMinorClaudeModelId(
|
||||
): string | null {
|
||||
const family = familyValue.toLowerCase();
|
||||
const suffix = hasOneMillionContext ? "[1m]" : "";
|
||||
const candidate = `claude-${family}-${major}-${minor}${suffix}`;
|
||||
return isClaudeManifestModelId(candidate) ? candidate : null;
|
||||
const candidates = [
|
||||
`claude-${family}-${major}-${minor}${suffix}`,
|
||||
`claude-${family}-${major}-${minor}`,
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
if (isClaudeManifestModelId(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ describe("getClaudeModels", () => {
|
||||
const models = getClaudeModels();
|
||||
expect(models.map((m) => m.id)).toEqual([
|
||||
"claude-opus-5",
|
||||
"claude-fable-5-1",
|
||||
"claude-fable-5",
|
||||
"claude-fable-5[1m]",
|
||||
"claude-opus-4-8[1m]",
|
||||
@@ -82,6 +83,7 @@ describe("getClaudeModels", () => {
|
||||
expect(contextWindows).toEqual(
|
||||
new Map([
|
||||
["claude-opus-5", 1_000_000],
|
||||
["claude-fable-5-1", 1_000_000],
|
||||
["claude-fable-5", 1_000_000],
|
||||
["claude-fable-5[1m]", 1_000_000],
|
||||
["claude-opus-4-8[1m]", 1_000_000],
|
||||
@@ -160,6 +162,9 @@ describe("getClaudeModels", () => {
|
||||
expect(models.get("claude-fable-5")?.thinkingOptions?.map((option) => option.id)).not.toContain(
|
||||
CLAUDE_DISABLED_THINKING_OPTION_ID,
|
||||
);
|
||||
expect(
|
||||
models.get("claude-fable-5-1")?.thinkingOptions?.map((option) => option.id),
|
||||
).not.toContain(CLAUDE_DISABLED_THINKING_OPTION_ID);
|
||||
expect(models.get("claude-haiku-4-5")?.thinkingOptions).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -170,6 +175,7 @@ describe("getClaudeModels", () => {
|
||||
["claude-sonnet-5[1m]", true, "high"],
|
||||
["claude-sonnet-5-20260101", true, "high"],
|
||||
["claude-fable-5", false, "high"],
|
||||
["claude-fable-5-1", false, "high"],
|
||||
["claude-haiku-4-5", false, undefined],
|
||||
["openrouter/anthropic/claude-opus-4-8", false, undefined],
|
||||
[null, false, undefined],
|
||||
@@ -458,7 +464,7 @@ describe("Claude Opus 5 catalog", () => {
|
||||
describe("Claude Fable 5 catalog", () => {
|
||||
it("offers one selectable Fable 5 entry and a compatibility entry for old apps", () => {
|
||||
const fable5Models = getClaudeModels()
|
||||
.filter((model) => model.id.startsWith("claude-fable-5"))
|
||||
.filter((model) => model.id === "claude-fable-5" || model.id === "claude-fable-5[1m]")
|
||||
.map(({ id, aliases, isSelectable, label, contextWindowMaxTokens }) => ({
|
||||
id,
|
||||
aliases,
|
||||
@@ -491,6 +497,24 @@ describe("Claude Fable 5 catalog", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Claude Fable 5.1 catalog", () => {
|
||||
it("offers one Fable 5.1 entry with a 1M context window", () => {
|
||||
const fable51Models = getClaudeModels()
|
||||
.filter((model) => model.id.startsWith("claude-fable-5-1"))
|
||||
.map(({ id, label, contextWindowMaxTokens }) => ({ id, label, contextWindowMaxTokens }));
|
||||
|
||||
expect(fable51Models).toEqual([
|
||||
{ id: "claude-fable-5-1", label: "Fable 5.1", contextWindowMaxTokens: 1_000_000 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves suffixed and dated Fable 5.1 IDs to the catalog entry", () => {
|
||||
expect(findClaudeModel("claude-fable-5-1[1m]")?.id).toBe("claude-fable-5-1");
|
||||
expect(findClaudeModel("claude-fable-5-1-20260901")?.id).toBe("claude-fable-5-1");
|
||||
expect(findClaudeModel("claude-fable-5-1-20260901[1m]")?.id).toBe("claude-fable-5-1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("claudeManifestModelSupportsFastMode", () => {
|
||||
it("keeps fast mode strict to first-party manifest model IDs", () => {
|
||||
expect(normalizeClaudeManifestModelId("openrouter/anthropic/claude-opus-4-8")).toBeNull();
|
||||
@@ -502,5 +526,6 @@ describe("claudeManifestModelSupportsFastMode", () => {
|
||||
expect(claudeManifestModelSupportsFastMode("claude-opus-5")).toBe(true);
|
||||
expect(claudeManifestModelSupportsFastMode("claude-sonnet-5")).toBe(false);
|
||||
expect(claudeManifestModelSupportsFastMode("claude-fable-5")).toBe(false);
|
||||
expect(claudeManifestModelSupportsFastMode("claude-fable-5-1")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,6 +54,14 @@ readline.createInterface({ input: process.stdin, crlfDelay: Infinity }).on("line
|
||||
});
|
||||
`;
|
||||
|
||||
const CLOSED_STDIN_CHILD_SOURCE = String.raw`
|
||||
const fs = require("node:fs");
|
||||
|
||||
fs.closeSync(0);
|
||||
process.stdout.write(JSON.stringify({ type: "stdin_closed" }) + "\n");
|
||||
setInterval(() => {}, 1_000);
|
||||
`;
|
||||
|
||||
interface InMemoryChildProcess extends ChildProcessWithoutNullStreams {
|
||||
stdin: PassThrough;
|
||||
stdout: PassThrough;
|
||||
@@ -63,6 +71,7 @@ interface InMemoryChildProcess extends ChildProcessWithoutNullStreams {
|
||||
interface StartProcessOptions {
|
||||
child?: ChildProcessWithoutNullStreams;
|
||||
defaultRequestTimeoutMs?: number;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
function createInMemoryChildProcess(): InMemoryChildProcess {
|
||||
@@ -85,7 +94,7 @@ function startProcess(options: StartProcessOptions = {}): JsonlRpcProcess {
|
||||
return new JsonlRpcProcess({
|
||||
launch: {
|
||||
command: process.execPath,
|
||||
args: ["-e", CHILD_SOURCE, "--", "resolved-arg"],
|
||||
args: ["-e", options.source ?? CHILD_SOURCE, "--", "resolved-arg"],
|
||||
cwd: process.cwd(),
|
||||
env: { JSONL_RPC_TEST_VALUE: "resolved-env" },
|
||||
},
|
||||
@@ -222,6 +231,70 @@ describe("JsonlRpcProcess", () => {
|
||||
await rejection;
|
||||
});
|
||||
|
||||
test("closes the transport when a direct send synchronously throws EPIPE", async () => {
|
||||
const child = createInMemoryChildProcess();
|
||||
const transport = startProcess({ child });
|
||||
|
||||
child.stdin.write = () => {
|
||||
throw Object.assign(new Error("write EPIPE"), { code: "EPIPE" });
|
||||
};
|
||||
|
||||
expect(() => transport.send({ type: "notice" })).not.toThrow();
|
||||
await expect(transport.request({ type: "echo", value: "after" })).rejects.toThrow(
|
||||
"JSONL RPC process is closed",
|
||||
);
|
||||
});
|
||||
|
||||
// Closing fd 0 does not sever the inherited pipe on Windows, so this fixture cannot produce EPIPE.
|
||||
test.skipIf(process.platform === "win32")(
|
||||
"keeps the parent alive when a real child closes its stdin pipe",
|
||||
async () => {
|
||||
const transport = startProcess({ source: CLOSED_STDIN_CHILD_SOURCE });
|
||||
const stdinClosed = new Promise<void>((resolve) => {
|
||||
const unsubscribe = transport.onMessage((message) => {
|
||||
if (message.type !== "stdin_closed") return;
|
||||
unsubscribe();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
await stdinClosed;
|
||||
await expect(transport.request({ type: "echo", value: "after" })).rejects.toThrow(
|
||||
/EPIPE|stdin is not writable/,
|
||||
);
|
||||
await expect(transport.request({ type: "echo", value: "later" })).rejects.toThrow(
|
||||
"JSONL RPC process is closed",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("stdin error events close the transport instead of becoming uncaught exceptions", async () => {
|
||||
const child = createInMemoryChildProcess();
|
||||
const transport = startProcess({ child });
|
||||
const request = transport.request({ type: "hang" });
|
||||
const err = Object.assign(new Error("write EPIPE"), { code: "EPIPE" });
|
||||
|
||||
child.stdin.emit("error", err);
|
||||
|
||||
await expect(request).rejects.toThrow("write EPIPE");
|
||||
await expect(transport.request({ type: "echo", value: "after" })).rejects.toThrow(
|
||||
"JSONL RPC process is closed",
|
||||
);
|
||||
});
|
||||
|
||||
test("a non-writable stdin closes the transport instead of hanging the request", async () => {
|
||||
const child = createInMemoryChildProcess();
|
||||
const transport = startProcess({ child });
|
||||
child.stdin.end();
|
||||
|
||||
await expect(transport.request({ type: "hang" })).rejects.toThrow(
|
||||
"JSONL RPC stdin is not writable",
|
||||
);
|
||||
await expect(transport.request({ type: "echo", value: "after" })).rejects.toThrow(
|
||||
"JSONL RPC process is closed",
|
||||
);
|
||||
});
|
||||
|
||||
test("reassembles protocol v2 chunked responses into the logical frame", async () => {
|
||||
const child = createInMemoryChildProcess();
|
||||
const transport = startProcess({ child });
|
||||
|
||||
@@ -104,6 +104,9 @@ export class JsonlRpcProcess {
|
||||
this.stderrBuffer = this.stderrBuffer.slice(-STDERR_BUFFER_LIMIT);
|
||||
}
|
||||
});
|
||||
this.child.stdin.on("error", (error) => {
|
||||
this.handleStdinError(error);
|
||||
});
|
||||
this.child.on("error", (error) => {
|
||||
this.failAll(error instanceof Error ? error : new Error(String(error)));
|
||||
});
|
||||
@@ -173,10 +176,18 @@ export class JsonlRpcProcess {
|
||||
}
|
||||
|
||||
send(message: Record<string, unknown>): void {
|
||||
if (this.disposed || this.child.stdin.destroyed || !this.child.stdin.writable) {
|
||||
if (this.disposed) {
|
||||
return;
|
||||
}
|
||||
this.child.stdin.write(`${JSON.stringify(message)}\n`);
|
||||
if (this.child.stdin.destroyed || !this.child.stdin.writable) {
|
||||
this.handleStdinError(new Error(`${this.diagnosticName} stdin is not writable`));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.child.stdin.write(`${JSON.stringify(message)}\n`);
|
||||
} catch (error) {
|
||||
this.handleStdinError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async close(error = new Error(`${this.diagnosticName} process is closed`)): Promise<void> {
|
||||
@@ -243,6 +254,15 @@ export class JsonlRpcProcess {
|
||||
pending.resolve(response.data);
|
||||
}
|
||||
|
||||
private handleStdinError(error: unknown): void {
|
||||
if (this.disposed) {
|
||||
return;
|
||||
}
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
this.options.logger.warn({ err }, `${this.diagnosticName} stdin write failed`);
|
||||
void this.close(err);
|
||||
}
|
||||
|
||||
private failAll(error: Error): void {
|
||||
if (this.disposed) {
|
||||
return;
|
||||
|
||||
@@ -6361,7 +6361,7 @@ describe("OpenCode provider subagent contract", () => {
|
||||
messageID: "msg_child_prompt",
|
||||
type: "text",
|
||||
text: "Inspect the auth flow.",
|
||||
time: { start: 1, end: 2 },
|
||||
time: { start: 1 },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2811,7 +2811,7 @@ function appendOpenCodeTextPart(
|
||||
events: AgentStreamEvent[],
|
||||
): void {
|
||||
if (messageRole === "user") {
|
||||
if (!part.time?.end || !part.text || state.emittedUserMessageIds?.has(part.messageID)) {
|
||||
if (!part.text || state.emittedUserMessageIds?.has(part.messageID)) {
|
||||
return;
|
||||
}
|
||||
state.emittedUserMessageIds?.add(part.messageID);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/website",
|
||||
"version": "0.7.0-beta.3",
|
||||
"version": "0.7.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java
|
||||
index f708543..26df5e0 100644
|
||||
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java
|
||||
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java
|
||||
@@ -29,6 +29,7 @@ import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.OverScroller;
|
||||
import android.widget.ScrollView;
|
||||
+import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@@ -425,12 +426,23 @@ public class ReactScrollView extends ScrollView
|
||||
*/
|
||||
@Override
|
||||
public void requestChildFocus(View child, View focused) {
|
||||
- if (focused != null) {
|
||||
+ if (focused != null && shouldScrollToFocusedChild(focused)) {
|
||||
scrollToChild(focused);
|
||||
}
|
||||
super.requestChildFocus(child, focused);
|
||||
}
|
||||
|
||||
+ private boolean shouldScrollToFocusedChild(View focused) {
|
||||
+ if (getScaleY() >= 0 || !focused.isInTouchMode() || !(focused instanceof TextView)) {
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ // Selectable text takes focus when tapped. In inverted lists, scrolling to that focus uses
|
||||
+ // untransformed coordinates and unexpectedly moves the timeline.
|
||||
+ TextView textView = (TextView) focused;
|
||||
+ return !textView.isTextSelectable() || textView.onCheckIsTextEditor();
|
||||
+ }
|
||||
+
|
||||
private int getScrollDelta(View descendent) {
|
||||
descendent.getDrawingRect(mTempRect);
|
||||
offsetDescendantRectToMyCoords(descendent, mTempRect);
|
||||
@@ -12,6 +12,10 @@ const patchedPackages = [
|
||||
nodeModulesPath: "node_modules/react-native-markdown-display",
|
||||
patchPrefix: "react-native-markdown-display+",
|
||||
},
|
||||
{
|
||||
nodeModulesPath: "node_modules/react-native",
|
||||
patchPrefix: "react-native+",
|
||||
},
|
||||
{
|
||||
nodeModulesPath: "node_modules/react-native-draggable-flatlist",
|
||||
patchPrefix: "react-native-draggable-flatlist+",
|
||||
|
||||
Reference in New Issue
Block a user