mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-21 16:52:40 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3ba6a586c | ||
|
|
0c1447db91 | ||
|
|
1ec55a7e29 | ||
|
|
33eb36c021 | ||
|
|
0fc6bf0c94 | ||
|
|
2d55870490 | ||
|
|
3d857f90d2 | ||
|
|
fed4d0cddd |
@@ -1,11 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.11.2 - 2026-10-09
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed intermittent text clipping in iOS chats and Markdown previews ([#6445](https://github.com/getpaseo/paseo/pull/6445))
|
||||
|
||||
## 0.11.1 - 2026-10-07
|
||||
|
||||
### Added
|
||||
|
||||
@@ -39,11 +39,11 @@
|
||||
<img src="https://paseo.sh/mobile-mockup.png" alt="Paseo mobile app" width="100%">
|
||||
</p>
|
||||
|
||||
Paseo is a desktop, mobile, web, and CLI app for coding agents. Open the desktop app and work: agents, editor, terminals, diffs, pull requests, and a browser in one window. Run many agents at once, each in its own worktree, on one machine or several. The mobile app is the full app, native on iOS and Android.
|
||||
Paseo is an open source agentic development environment for desktop, mobile, web, and CLI. Open the desktop app and work: agents, editor, terminals, diffs, pull requests, and a browser in one window. Run many agents at once, each in its own worktree, on one machine or several. The mobile app is the full app, native on iOS and Android.
|
||||
|
||||
- **Parallel agents:** Run many agents at once, each in its own worktree.
|
||||
- **Built-in orchestration:** Agents in Paseo can create worktrees, launch other agents, and talk to them, across providers.
|
||||
- **Full IDE:** Edit files, review diffs, open pull requests, and run terminals, in split panes you arrange how you want.
|
||||
- **Complete development workflow:** Edit files, review diffs, open pull requests, and run terminals, in split panes you arrange how you want.
|
||||
- **Self-hosted:** Agents run on your machine with your full dev environment. Use your tools, your configs, and your skills.
|
||||
- **Multi-provider:** Claude Code, Codex, Copilot, OpenCode, Pi, Antigravity, and Muse Code through the same interface. Pick the right model for each job.
|
||||
- **Voice control:** Dictate tasks or talk through problems in voice mode. Hands-free when you need it.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
Fixed
|
||||
- Fixed intermittent text clipping in iOS chats and Markdown previews
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
@@ -1,4 +0,0 @@
|
||||
Fixed
|
||||
- Fixed intermittent text clipping in iOS chats and Markdown previews
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
@@ -1,4 +0,0 @@
|
||||
Fixed
|
||||
- Fixed intermittent text clipping in iOS chats and Markdown previews
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
@@ -1,4 +0,0 @@
|
||||
Fixed
|
||||
- Fixed intermittent text clipping in iOS chats and Markdown previews
|
||||
|
||||
Full notes: https://paseo.sh/changelog
|
||||
+1
-1
@@ -1 +1 @@
|
||||
sha256-AKY6OMZgHeyn/To/2zsmtfb5C4fKniUp8gvpEYaR7/o=
|
||||
sha256-aoYgGgEkqeeZJR7OOcSHuZEhBELkpyOGqHo4oEx7W88=
|
||||
|
||||
Generated
+28
-28
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "paseo",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "paseo",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
@@ -36413,7 +36413,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@getpaseo/app",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "6.10.4",
|
||||
"@codemirror/language": "6.12.4",
|
||||
@@ -37496,12 +37496,12 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@getpaseo/cli",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^1.0.0",
|
||||
"@getpaseo/client": "0.11.2",
|
||||
"@getpaseo/protocol": "0.11.2",
|
||||
"@getpaseo/server": "0.11.2",
|
||||
"@getpaseo/client": "0.11.1",
|
||||
"@getpaseo/protocol": "0.11.1",
|
||||
"@getpaseo/server": "0.11.1",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
@@ -37748,10 +37748,10 @@
|
||||
},
|
||||
"packages/client": {
|
||||
"name": "@getpaseo/client",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"dependencies": {
|
||||
"@getpaseo/protocol": "0.11.2",
|
||||
"@getpaseo/relay": "0.11.2",
|
||||
"@getpaseo/protocol": "0.11.1",
|
||||
"@getpaseo/relay": "0.11.1",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -37762,7 +37762,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@getpaseo/desktop",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@getpaseo/cli": "*",
|
||||
@@ -38006,7 +38006,7 @@
|
||||
},
|
||||
"packages/expo-two-way-audio": {
|
||||
"name": "@getpaseo/expo-two-way-audio",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
@@ -38902,7 +38902,7 @@
|
||||
},
|
||||
"packages/highlight": {
|
||||
"name": "@getpaseo/highlight",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "6.12.4",
|
||||
"@codemirror/legacy-modes": "^6.5.3",
|
||||
@@ -38931,14 +38931,14 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@getpaseo/plugin",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^1.4.0",
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@getpaseo/client": "0.11.2",
|
||||
"@getpaseo/protocol": "0.11.2",
|
||||
"@getpaseo/client": "0.11.1",
|
||||
"@getpaseo/protocol": "0.11.1",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/react": "~19.2.0",
|
||||
"react": "19.1.0",
|
||||
@@ -38948,8 +38948,8 @@
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@getpaseo/client": "0.11.2",
|
||||
"@getpaseo/protocol": "0.11.2",
|
||||
"@getpaseo/client": "0.11.1",
|
||||
"@getpaseo/protocol": "0.11.1",
|
||||
"react": "~19.1.0",
|
||||
"react-native": ">=0.81.5",
|
||||
"zod": "^4.4.3"
|
||||
@@ -38971,7 +38971,7 @@
|
||||
},
|
||||
"packages/protocol": {
|
||||
"name": "@getpaseo/protocol",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"dependencies": {
|
||||
"semver": "^7.8.5",
|
||||
"zod": "^4.4.3"
|
||||
@@ -38998,7 +38998,7 @@
|
||||
},
|
||||
"packages/relay": {
|
||||
"name": "@getpaseo/relay",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"tweetnacl": "^1.0.3",
|
||||
@@ -39216,16 +39216,16 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@getpaseo/server",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "^0.17.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.246",
|
||||
"@anthropic-ai/sdk": "^0.104.2",
|
||||
"@getpaseo/client": "0.11.2",
|
||||
"@getpaseo/highlight": "0.11.2",
|
||||
"@getpaseo/plugin": "0.11.2",
|
||||
"@getpaseo/protocol": "0.11.2",
|
||||
"@getpaseo/relay": "0.11.2",
|
||||
"@getpaseo/client": "0.11.1",
|
||||
"@getpaseo/highlight": "0.11.1",
|
||||
"@getpaseo/plugin": "0.11.1",
|
||||
"@getpaseo/protocol": "0.11.1",
|
||||
"@getpaseo/relay": "0.11.1",
|
||||
"@isaacs/ttlcache": "^2.1.4",
|
||||
"@modelcontextprotocol/sdk": "1.29.0",
|
||||
"@opencode-ai/sdk": "1.14.46",
|
||||
@@ -40146,7 +40146,7 @@
|
||||
},
|
||||
"packages/website": {
|
||||
"name": "@getpaseo/website",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "^1.29.1",
|
||||
"@cloudflare/workers-types": "^4.20260317.1",
|
||||
@@ -41754,7 +41754,7 @@
|
||||
},
|
||||
"plugins": {
|
||||
"name": "@getpaseo/builtin-plugins",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"dependencies": {
|
||||
"@getpaseo/plugin": "*",
|
||||
"zod": "^4.4.3"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "paseo",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"private": true,
|
||||
"description": "Paseo: voice-controlled development environment for local AI coding agents",
|
||||
"keywords": [
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFileSync, spawn } from "node:child_process";
|
||||
import { setTimeout } from "node:timers/promises";
|
||||
|
||||
// A primary mouse click on a sidebar workspace row selects it (#3996).
|
||||
// Run with the app on a wide Android layout, sidebar open, against a real daemon
|
||||
// with at least two workspaces. The app must already run the checkout being verified.
|
||||
// The click comes from a virtual USB mouse, so Android sends the same DOWN,
|
||||
// BUTTON_PRESS, BUTTON_RELEASE, UP sequence as a physical touchpad click.
|
||||
const serial = process.env.ANDROID_SERIAL ?? "emulator-5554";
|
||||
const CLICK_HOLD_MS = 110;
|
||||
|
||||
function adb(...args) {
|
||||
return execFileSync("adb", ["-s", serial, ...args], {
|
||||
encoding: "utf8",
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
function workspaceRows() {
|
||||
adb("shell", "uiautomator", "dump", "/sdcard/paseo-mouse-click.xml");
|
||||
const xml = adb("shell", "cat", "/sdcard/paseo-mouse-click.xml");
|
||||
return [...xml.matchAll(/<node [^>]*resource-id="sidebar-workspace-row-[^"]*"[^>]*>/g)].map(
|
||||
([node]) => {
|
||||
const [, left, top, right, bottom] = node.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
|
||||
return {
|
||||
id: node.match(/resource-id="([^"]*)"/)[1],
|
||||
selected: node.includes('selected="true"'),
|
||||
// The title line, clear of the PR and check badges on the second line.
|
||||
title: { x: +left + (right - left) / 4, y: +top + (bottom - top) / 3 },
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function cursorPosition() {
|
||||
const events = [
|
||||
...adb("shell", "dumpsys", "input").matchAll(
|
||||
/source=MOUSE.*xCursorPosition=([\d.]+), yCursorPosition=([\d.]+).*age=(\d+)ms/g,
|
||||
),
|
||||
];
|
||||
assert.ok(events.length > 0, "Android reported no mouse events");
|
||||
const [, x, y] = events.reduce((latest, event) => (+event[3] < +latest[3] ? event : latest));
|
||||
return { x: +x, y: +y };
|
||||
}
|
||||
|
||||
const MOUSE_NAME = "Paseo E2E Mouse";
|
||||
|
||||
async function openVirtualMouse() {
|
||||
const uinput = spawn("adb", ["-s", serial, "shell", "uinput", "-"], {
|
||||
stdio: ["pipe", "ignore", "inherit"],
|
||||
});
|
||||
// A mouse that never registered must fail as setup, not as a missed click.
|
||||
let failure = null;
|
||||
uinput.on("exit", (code, signal) => {
|
||||
failure ??= `uinput exited (code ${code}, signal ${signal})`;
|
||||
});
|
||||
uinput.on("error", (error) => {
|
||||
failure ??= `uinput failed to start: ${error.message}`;
|
||||
});
|
||||
uinput.stdin.on("error", (error) => {
|
||||
failure ??= `uinput stdin closed: ${error.message}`;
|
||||
});
|
||||
const send = (command) => {
|
||||
if (failure) throw new Error(failure);
|
||||
uinput.stdin.write(`${JSON.stringify({ id: 1, ...command })}\n`);
|
||||
};
|
||||
const inject = (...events) =>
|
||||
send({ command: "inject", events: [...events, "EV_SYN", "SYN_REPORT", 0] });
|
||||
send({
|
||||
command: "register",
|
||||
name: MOUSE_NAME,
|
||||
vid: 0x18d1,
|
||||
pid: 0x0001,
|
||||
bus: "usb",
|
||||
configuration: [
|
||||
{ type: "UI_SET_EVBIT", data: ["EV_KEY", "EV_REL"] },
|
||||
{ type: "UI_SET_KEYBIT", data: ["BTN_LEFT", "BTN_RIGHT", "BTN_MIDDLE"] },
|
||||
{ type: "UI_SET_RELBIT", data: ["REL_X", "REL_Y"] },
|
||||
],
|
||||
});
|
||||
const deadline = Date.now() + 5_000;
|
||||
while (!adb("shell", "dumpsys", "input").includes(MOUSE_NAME)) {
|
||||
if (failure) throw new Error(failure);
|
||||
if (Date.now() > deadline) throw new Error(`Android did not add ${MOUSE_NAME}`);
|
||||
await setTimeout(250);
|
||||
}
|
||||
return {
|
||||
// Pointer speed and acceleration scale relative motion, so steer by the
|
||||
// cursor position Android reports until it lands on the target.
|
||||
async moveTo(target) {
|
||||
inject("EV_REL", "REL_X", 1, "EV_REL", "REL_Y", 1);
|
||||
await setTimeout(150);
|
||||
for (let step = 0; step < 20; step += 1) {
|
||||
const cursor = cursorPosition();
|
||||
const dx = target.x - cursor.x;
|
||||
const dy = target.y - cursor.y;
|
||||
if (Math.abs(dx) <= 4 && Math.abs(dy) <= 4) return;
|
||||
const clamp = (delta) => Math.max(-60, Math.min(60, Math.round(delta / 3)));
|
||||
inject("EV_REL", "REL_X", clamp(dx), "EV_REL", "REL_Y", clamp(dy));
|
||||
await setTimeout(150);
|
||||
}
|
||||
throw new Error(`Cursor did not reach ${JSON.stringify(target)}`);
|
||||
},
|
||||
async click() {
|
||||
inject("EV_KEY", "BTN_LEFT", 1);
|
||||
await setTimeout(CLICK_HOLD_MS);
|
||||
inject("EV_KEY", "BTN_LEFT", 0);
|
||||
},
|
||||
close() {
|
||||
uinput.stdin.end();
|
||||
uinput.kill();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function waitForSelected(id) {
|
||||
const deadline = Date.now() + 5_000;
|
||||
while (Date.now() < deadline) {
|
||||
if (workspaceRows().find((row) => row.id === id)?.selected) return true;
|
||||
await setTimeout(250);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = workspaceRows().find((row) => !row.selected);
|
||||
assert.ok(target, "Open the sidebar with at least one unselected workspace row first");
|
||||
|
||||
const mouse = await openVirtualMouse();
|
||||
try {
|
||||
await mouse.moveTo(target.title);
|
||||
await mouse.click();
|
||||
assert.ok(await waitForSelected(target.id), `Primary mouse click did not select ${target.id}`);
|
||||
console.log(`PASS: primary mouse click selected ${target.id}`);
|
||||
} finally {
|
||||
mouse.close();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# Native iOS text geometry regression
|
||||
|
||||
Run on the Namespace Mac with a fresh simulator development build. Use the PID
|
||||
returned by `xcrun simctl launch <your-task-simulator-UDID> sh.paseo.debug`.
|
||||
Attach only to your task's app, while no device replay is running:
|
||||
|
||||
```sh
|
||||
PASEO_NATIVE_TEXT_REFLOW_PID=12345 \
|
||||
PASEO_NATIVE_TEXT_REFLOW_LOG=/Volumes/devbox/workspace/native-text-reflow.log \
|
||||
bash packages/app/e2e/mobile/native-text-reflow/ios.sh
|
||||
```
|
||||
|
||||
The probe constructs the installed `RNUITextView`, fills its real UIKit text view,
|
||||
and resizes its content container from 500 to 240 and back to 500 points. It
|
||||
asserts the inner frame follows both widths and the same selection survives.
|
||||
It uses public UIView/UITextView operations, without mocking React Native.
|
||||
LLDB detaches after the probe. An assertion failure or evaluation error fails the
|
||||
shell command; the log distinguishes them.
|
||||
|
||||
This covers native container resizing and selection retention. It does not cover
|
||||
Fabric event delivery, chat row height, reader anchoring, or the on-screen copy
|
||||
menu. Those require the real chat journey: read an older multiline response,
|
||||
open/close Explorer, resize/rotate, inspect complete paragraphs and bullets,
|
||||
retain expanded/collapsed details, and check a stationary reader's position.
|
||||
Accessibility exposes full text even when glyphs are clipped, so a text wait
|
||||
alone does not verify that journey.
|
||||
@@ -0,0 +1,55 @@
|
||||
import lldb
|
||||
|
||||
|
||||
def __lldb_init_module(debugger, _internal_dict):
|
||||
debugger.HandleCommand("expression -l objc++ -- @import UIKit")
|
||||
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
|
||||
options = lldb.SBExpressionOptions()
|
||||
options.SetLanguage(lldb.eLanguageTypeObjC_plus_plus)
|
||||
result = frame.EvaluateExpression(
|
||||
r'''({
|
||||
UIView *host = (UIView *)[[NSClassFromString(@"RNUITextView") alloc] init];
|
||||
auto hostFrame = host.frame;
|
||||
hostFrame.size.width = 500;
|
||||
hostFrame.size.height = 200;
|
||||
host.frame = hostFrame;
|
||||
UIView *container = (UIView *)[host performSelector:@selector(contentView)];
|
||||
container.frame = host.bounds;
|
||||
UITextView *text = nil;
|
||||
for (UIView *child in host.subviews) {
|
||||
if ([child isKindOfClass:UITextView.class]) text = (UITextView *)child;
|
||||
for (UIView *nested in child.subviews) {
|
||||
if ([nested isKindOfClass:UITextView.class]) text = (UITextView *)nested;
|
||||
}
|
||||
}
|
||||
text.frame = container.bounds;
|
||||
text.font = [UIFont systemFontOfSize:16];
|
||||
text.text = @"A realistic multiline paragraph must reflow when Explorer narrows the available chat width, then fill the restored width without replacing the text view or discarding a selection.";
|
||||
auto range = text.selectedRange;
|
||||
range.location = 12;
|
||||
range.length = 9;
|
||||
text.selectedRange = range;
|
||||
auto frame = container.frame;
|
||||
frame.size.width = 240;
|
||||
container.frame = frame;
|
||||
[container layoutIfNeeded];
|
||||
CGFloat narrow = text.frame.size.width;
|
||||
auto selection = text.selectedRange;
|
||||
frame.size.width = 500;
|
||||
container.frame = frame;
|
||||
[container layoutIfNeeded];
|
||||
CGFloat wide = text.frame.size.width;
|
||||
BOOL ok = text != nil && narrow == 240 && wide == 500 &&
|
||||
selection.location == text.selectedRange.location &&
|
||||
selection.length == text.selectedRange.length &&
|
||||
selection.location == 12 && selection.length == 9;
|
||||
[NSString stringWithFormat:@"NATIVE_REFLOW_%@ narrow=%.0f wide=%.0f selection=%lu:%lu",
|
||||
ok ? @"PASS" : @"FAIL", narrow, wide,
|
||||
(unsigned long)text.selectedRange.location,
|
||||
(unsigned long)text.selectedRange.length];
|
||||
})''',
|
||||
options,
|
||||
)
|
||||
if result.GetError().Fail():
|
||||
raise RuntimeError(result.GetError().GetCString())
|
||||
print(result.GetObjectDescription())
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
: "${PASEO_NATIVE_TEXT_REFLOW_PID:?Set the PID of your task-owned Paseo iOS simulator app}"
|
||||
: "${PASEO_NATIVE_TEXT_REFLOW_LOG:?Set the test output log path}"
|
||||
|
||||
if [[ "$(ps -p "$PASEO_NATIVE_TEXT_REFLOW_PID" -o comm=)" != *PaseoDebug.app/PaseoDebug ]]; then
|
||||
echo "Expected a running PaseoDebug simulator app" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
probe_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
xcrun lldb --batch -p "$PASEO_NATIVE_TEXT_REFLOW_PID" \
|
||||
-o "command script import $probe_dir/ios.py" \
|
||||
-o "process detach" -o quit > "$PASEO_NATIVE_TEXT_REFLOW_LOG" 2>&1
|
||||
|
||||
grep '^NATIVE_REFLOW_' "$PASEO_NATIVE_TEXT_REFLOW_LOG"
|
||||
grep -q '^NATIVE_REFLOW_PASS narrow=240 wide=500 selection=12:9$' "$PASEO_NATIVE_TEXT_REFLOW_LOG"
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/app",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"private": true,
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { forwardRef, useMemo } from "react";
|
||||
import { Pressable, StyleSheet, View } from "react-native";
|
||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||
import { Gesture, GestureDetector, PointerType } from "react-native-gesture-handler";
|
||||
import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
||||
import { resolveHighlightCorners } from "./press-highlight.shape";
|
||||
import type { PressHighlightProps } from "./press-highlight.types";
|
||||
@@ -20,6 +20,15 @@ export const PressHighlight = forwardRef<View, PressHighlightProps>(function Pre
|
||||
.enabled(disabled !== true && highlightStyle != null)
|
||||
.maxDistance(8)
|
||||
.shouldCancelWhenOutside(true)
|
||||
// A mouse never gets the touch glow. Left unchecked, this Tap activates on the primary
|
||||
// button release and cancels the Pressable's press, so the click never reaches onPress.
|
||||
// Android delivers the first touch-down before the Tap begins, and on the UI thread the
|
||||
// failure lands synchronously, before ACTION_BUTTON_PRESS or ACTION_BUTTON_RELEASE.
|
||||
.onTouchesDown((event, stateManager) => {
|
||||
if (event.pointerType === PointerType.MOUSE) {
|
||||
stateManager.fail();
|
||||
}
|
||||
})
|
||||
.onBegin(() => {
|
||||
highlighted.value = 1;
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/cli",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.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.11.2",
|
||||
"@getpaseo/protocol": "0.11.2",
|
||||
"@getpaseo/server": "0.11.2",
|
||||
"@getpaseo/client": "0.11.1",
|
||||
"@getpaseo/protocol": "0.11.1",
|
||||
"@getpaseo/server": "0.11.1",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/client",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"description": "Paseo client SDK package",
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -39,8 +39,8 @@
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@getpaseo/protocol": "0.11.2",
|
||||
"@getpaseo/relay": "0.11.2",
|
||||
"@getpaseo/protocol": "0.11.1",
|
||||
"@getpaseo/relay": "0.11.1",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/desktop",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.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.11.2",
|
||||
"version": "0.11.1",
|
||||
"description": "Native module for two way audio streaming",
|
||||
"keywords": [
|
||||
"ExpoTwoWayAudio",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/highlight",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"files": [
|
||||
"dist",
|
||||
"!dist/**/*.map",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/plugin",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"description": "Paseo plugin SDK package",
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -70,8 +70,8 @@
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@getpaseo/client": "0.11.2",
|
||||
"@getpaseo/protocol": "0.11.2",
|
||||
"@getpaseo/client": "0.11.1",
|
||||
"@getpaseo/protocol": "0.11.1",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/react": "~19.2.0",
|
||||
"react": "19.1.0",
|
||||
@@ -81,8 +81,8 @@
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@getpaseo/client": "0.11.2",
|
||||
"@getpaseo/protocol": "0.11.2",
|
||||
"@getpaseo/client": "0.11.1",
|
||||
"@getpaseo/protocol": "0.11.1",
|
||||
"react": "~19.1.0",
|
||||
"react-native": ">=0.81.5",
|
||||
"zod": "^4.4.3"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/protocol",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"description": "Paseo shared protocol schemas and wire types",
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/relay",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"description": "Paseo relay for bridging daemon and client connections",
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/server",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"description": "Paseo backend server",
|
||||
"files": [
|
||||
"dist/server",
|
||||
@@ -107,11 +107,11 @@
|
||||
"@agentclientprotocol/sdk": "^0.17.1",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.246",
|
||||
"@anthropic-ai/sdk": "^0.104.2",
|
||||
"@getpaseo/client": "0.11.2",
|
||||
"@getpaseo/highlight": "0.11.2",
|
||||
"@getpaseo/plugin": "0.11.2",
|
||||
"@getpaseo/protocol": "0.11.2",
|
||||
"@getpaseo/relay": "0.11.2",
|
||||
"@getpaseo/client": "0.11.1",
|
||||
"@getpaseo/highlight": "0.11.1",
|
||||
"@getpaseo/plugin": "0.11.1",
|
||||
"@getpaseo/protocol": "0.11.1",
|
||||
"@getpaseo/relay": "0.11.1",
|
||||
"@isaacs/ttlcache": "^2.1.4",
|
||||
"@modelcontextprotocol/sdk": "1.29.0",
|
||||
"@opencode-ai/sdk": "1.14.46",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
@@ -2867,6 +2867,44 @@ describe("ScheduleService", () => {
|
||||
expect(inspected.runs[0]?.error).toBe("network blip");
|
||||
});
|
||||
|
||||
// chmod cannot make a directory unwritable on Windows or for root.
|
||||
test.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
|
||||
"fires again after a tick whose run could not be recorded",
|
||||
async () => {
|
||||
const service = createScheduleService({
|
||||
paseoHome: tempDir,
|
||||
logger: createTestLogger(),
|
||||
agentManager: new AgentManager({ logger: createTestLogger() }),
|
||||
agentStorage,
|
||||
providerSnapshotManager: NO_UNATTENDED_SCHEDULE_POLICY,
|
||||
now: () => now,
|
||||
runner: async () => ({ agentId: null, output: "ran" }),
|
||||
});
|
||||
|
||||
const created = await service.create({
|
||||
prompt: "ping target",
|
||||
cadence: { type: "every", everyMs: 60_000 },
|
||||
target: { type: "agent", agentId: "88888888-8888-4888-8888-888888888888" },
|
||||
});
|
||||
|
||||
const schedulesDir = join(tempDir, "schedules");
|
||||
now = new Date("2026-01-01T00:01:00.000Z");
|
||||
await chmod(schedulesDir, 0o555);
|
||||
try {
|
||||
await expect(service.tick()).rejects.toThrow();
|
||||
} finally {
|
||||
await chmod(schedulesDir, 0o755);
|
||||
}
|
||||
|
||||
await service.tick();
|
||||
|
||||
const inspected = await service.inspect(created.id);
|
||||
expect(inspected.runs).toHaveLength(1);
|
||||
expect(inspected.runs[0]?.status).toBe("succeeded");
|
||||
expect(inspected.nextRunAt).toBe("2026-01-01T00:02:00.000Z");
|
||||
},
|
||||
);
|
||||
|
||||
test("completes the schedule when a scheduled run targets an archived agent", async () => {
|
||||
const service = createScheduleService({
|
||||
paseoHome: tempDir,
|
||||
|
||||
@@ -694,42 +694,44 @@ export class ScheduleService {
|
||||
): Promise<void> {
|
||||
const manual = options?.manual === true;
|
||||
this.runningScheduleIds.add(schedule.id);
|
||||
const runId = randomUUID();
|
||||
const runningRun: ScheduleRun = {
|
||||
id: runId,
|
||||
scheduledFor: manual ? now.toISOString() : (schedule.nextRunAt ?? now.toISOString()),
|
||||
startedAt: now.toISOString(),
|
||||
endedAt: null,
|
||||
status: "running",
|
||||
agentId: null,
|
||||
output: null,
|
||||
error: null,
|
||||
};
|
||||
const scheduleWithRun = await this.appendRunningRun(schedule.id, runningRun);
|
||||
|
||||
try {
|
||||
const result = await this.runner(scheduleWithRun, runId);
|
||||
await this.finishRun({
|
||||
scheduleId: schedule.id,
|
||||
runId,
|
||||
status: "succeeded",
|
||||
agentId: result.agentId,
|
||||
output: result.output,
|
||||
error: null,
|
||||
targetGone: false,
|
||||
manual,
|
||||
});
|
||||
} catch (error) {
|
||||
await this.finishRun({
|
||||
scheduleId: schedule.id,
|
||||
runId,
|
||||
status: "failed",
|
||||
const runId = randomUUID();
|
||||
const runningRun: ScheduleRun = {
|
||||
id: runId,
|
||||
scheduledFor: manual ? now.toISOString() : (schedule.nextRunAt ?? now.toISOString()),
|
||||
startedAt: now.toISOString(),
|
||||
endedAt: null,
|
||||
status: "running",
|
||||
agentId: null,
|
||||
output: null,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
targetGone: error instanceof ScheduleTargetGoneError,
|
||||
manual,
|
||||
});
|
||||
error: null,
|
||||
};
|
||||
const scheduleWithRun = await this.appendRunningRun(schedule.id, runningRun);
|
||||
|
||||
try {
|
||||
const result = await this.runner(scheduleWithRun, runId);
|
||||
await this.finishRun({
|
||||
scheduleId: schedule.id,
|
||||
runId,
|
||||
status: "succeeded",
|
||||
agentId: result.agentId,
|
||||
output: result.output,
|
||||
error: null,
|
||||
targetGone: false,
|
||||
manual,
|
||||
});
|
||||
} catch (error) {
|
||||
await this.finishRun({
|
||||
scheduleId: schedule.id,
|
||||
runId,
|
||||
status: "failed",
|
||||
agentId: null,
|
||||
output: null,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
targetGone: error instanceof ScheduleTargetGoneError,
|
||||
manual,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
this.runningScheduleIds.delete(schedule.id);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/website",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -9,7 +9,7 @@ order: 55
|
||||
|
||||
Claude Desktop is Anthropic's app for Claude Chat, Cowork, and Claude Code on macOS, Windows, and Linux.
|
||||
|
||||
Paseo is an app for orchestrating coding agents, with native clients on desktop, mobile, web, and the CLI. Open source (Apache-2.0).
|
||||
Paseo is an open source agentic development environment. Run parallel agents in separate worktrees, edit files, review diffs and pull requests, and test your app with terminals and a built-in desktop browser. Available on desktop, mobile, web, and CLI under Apache-2.0.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ order: 54
|
||||
|
||||
OpenAI provides Codex through the ChatGPT desktop app on macOS, Windows, and Linux, with local, worktree, cloud, and remote workflows.
|
||||
|
||||
Paseo is an app for orchestrating coding agents, with native clients on desktop, mobile, web, and the CLI. Open source (Apache-2.0).
|
||||
Paseo is an open source agentic development environment. Run parallel agents in separate worktrees, edit files, review diffs and pull requests, and test your app with terminals and a built-in desktop browser. Available on desktop, mobile, web, and CLI under Apache-2.0.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ order: 50
|
||||
|
||||
Conductor is a proprietary macOS app for running Claude Code, Codex, Cursor, and OpenCode in parallel Git worktrees and managed cloud workspaces.
|
||||
|
||||
Paseo is an app for orchestrating coding agents, with native clients on desktop, mobile, web, and the CLI. Open source (Apache-2.0).
|
||||
Paseo is an open source agentic development environment. Run parallel agents in separate worktrees, edit files, review diffs and pull requests, and test your app with terminals and a built-in desktop browser. Available on desktop, mobile, web, and CLI under Apache-2.0.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ order: 53
|
||||
|
||||
Happy Coder connects Claude Code and Codex sessions across its macOS, web, iOS, and Android apps. It wraps the agent CLI on your laptop and syncs sessions over an end-to-end encrypted relay. Open source under MIT.
|
||||
|
||||
Paseo is an app for orchestrating coding agents, with native clients on desktop, mobile, web, and the CLI. Open source (Apache-2.0).
|
||||
Paseo is an open source agentic development environment. Run parallel agents in separate worktrees, edit files, review diffs and pull requests, and test your app with terminals and a built-in desktop browser. Available on desktop, mobile, web, and CLI under Apache-2.0.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ order: 56
|
||||
|
||||
OpenCode Desktop is the desktop app for OpenCode. It is available in beta for macOS, Windows, and Linux.
|
||||
|
||||
Paseo is an app for orchestrating coding agents, with native clients on desktop, mobile, web, and the CLI. Open source (Apache-2.0).
|
||||
Paseo is an open source agentic development environment. Run parallel agents in separate worktrees, edit files, review diffs and pull requests, and test your app with terminals and a built-in desktop browser. Available on desktop, mobile, web, and CLI under Apache-2.0.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Orca ADE Alternative With Agent Chat, a No-Account Relay, and Plugins
|
||||
description: Paseo is an Orca alternative for developers who want a chat-first interface, a daemon they can reach from any client, and plugins that run on the server and every client.
|
||||
title: Orca Alternative for Agentic Development
|
||||
description: Paseo is an open source agentic development environment with parallel worktrees, an editor, terminals, diffs, pull requests, and a built-in browser.
|
||||
nav: Orca
|
||||
order: 57
|
||||
---
|
||||
@@ -9,7 +9,7 @@ order: 57
|
||||
|
||||
Orca is an MIT-licensed desktop app for running terminal coding agents in parallel Git worktrees. It includes a mobile companion app, SSH worktrees, remote Orca servers, an embedded browser, and a CLI.
|
||||
|
||||
Paseo is an app for orchestrating coding agents, with native clients on desktop, mobile, web, and the CLI. Open source (Apache-2.0).
|
||||
Paseo is an open source agentic development environment. Run parallel agents in separate worktrees, edit files, review diffs and pull requests, and test your app with terminals and a built-in desktop browser. Available on desktop, mobile, web, and CLI under Apache-2.0.
|
||||
|
||||

|
||||
|
||||
@@ -23,7 +23,7 @@ Orca is made by Stably AI, a venture-funded company (Y Combinator). Paseo is ind
|
||||
|
||||
## Architecture and remote machines
|
||||
|
||||
The Paseo daemon runs as its own process. Desktop, web, mobile, and CLI clients connect to it directly, over SSH, or through the optional end-to-end encrypted relay. Run the daemon on your laptop, a VM, a home server, or in Docker, and connect to any of them from any client. See [connectivity](/docs/connectivity).
|
||||
The Paseo desktop app starts and manages a local daemon automatically. That daemon owns the agents and workspaces, so other clients can connect to the same development environment. Desktop, web, mobile, and CLI clients connect to it directly, over SSH, or through the optional end-to-end encrypted relay. Run the daemon on your laptop, a VM, a home server, or in Docker, and connect to any of them from any client. See [connectivity](/docs/connectivity).
|
||||
|
||||
Orca runs inside its desktop app by default and reaches other machines in two ways. SSH worktrees keep the Orca runtime on your laptop and run selected worktrees and terminals on a remote host. Remote Orca Servers, in beta, run Orca desktop or `orca serve` on another machine and let clients pair to it. Orca's documentation recommends Tailscale or another private network for Remote Orca Servers.
|
||||
|
||||
|
||||
@@ -6,13 +6,15 @@ const SITE_URL = "https://paseo.sh";
|
||||
|
||||
const PRODUCT_PREAMBLE = `# Paseo
|
||||
|
||||
> Mobile and desktop app for monitoring and controlling your local AI coding agents from anywhere. Your dev environment, in your pocket.
|
||||
> Open source agentic development environment for desktop, mobile, web, and CLI.
|
||||
|
||||
Paseo is an open source application that lets you run AI coding agents on your own machine and drive them from your phone, desktop, browser, or terminal. Your code stays local — Paseo connects directly to your real development environment instead of running agents in someone else's cloud.
|
||||
Paseo is a full agentic development environment. For agent-driven development, it replaces your IDE: run coding agents, edit files, review diffs and pull requests, run tests in terminals, and check your app in the built-in desktop browser. Arrange agents, files, terminals, and browser tabs in split panes within a workspace.
|
||||
|
||||
A self-hosted daemon manages agent lifecycle, exposes a WebSocket API, and ships with an MCP server so other agents can talk to it. Native apps for iOS, Android, macOS, Windows, Linux, and the web let you launch sessions, watch them work, review diffs, and ship from anywhere. A Docker-style CLI ("paseo run", "paseo ls", "paseo logs", "paseo wait") gives you scripting access. An end-to-end encrypted relay lets the mobile app reach your daemon over the public internet without exposing it.
|
||||
Run many agents in parallel, each in its own git worktree and branch. Choose a provider and model for each task, inspect the results, and decide what to merge. Agents can also create worktrees, launch other agents, and communicate with them through Paseo's orchestration tools.
|
||||
|
||||
Paseo supports every major coding agent: Claude Code, Codex, GitHub Copilot, OpenCode, Antigravity, Muse Code, Cursor, Gemini, Cline, Goose, Amp, Aider, and 30+ others. Each agent runs as its own process; Paseo handles I/O, persistence, git worktree isolation, schedules, and skills.
|
||||
Agents run on your own machines with your existing tools, configuration, and credentials. Work from desktop, the full native iOS and Android app, a web browser, or the CLI. The desktop app starts its local daemon automatically. For remote work, run the daemon on another machine and connect directly or through the optional end-to-end encrypted relay.
|
||||
|
||||
Paseo supports Claude Code, Codex, GitHub Copilot, OpenCode, Pi, Antigravity, Muse Code, and additional ACP-compatible agents. The CLI, TypeScript SDK, and MCP tools expose agent and workspace operations for automation. Plugins extend providers, workflows, and the app's interface.
|
||||
|
||||
Distribution: native apps for Mac, Windows, Linux, iOS, and Android; web app; Homebrew; npm. Source: Apache-2.0 at https://github.com/getpaseo/paseo. Marketing site: https://paseo.sh.
|
||||
`;
|
||||
|
||||
@@ -17,16 +17,19 @@ function Home() {
|
||||
<LandingPage
|
||||
title={
|
||||
<>
|
||||
The control plane
|
||||
The agentic development
|
||||
<br />
|
||||
for coding agents
|
||||
environment
|
||||
</>
|
||||
}
|
||||
subtitle={
|
||||
<>
|
||||
Run many coding agents at once, on your machines.
|
||||
Run many coding agents at once,
|
||||
<br className="sm:hidden" />
|
||||
<span className="hidden sm:inline"> </span>
|
||||
on any machine.
|
||||
<br />
|
||||
Desktop and mobile. Open source.
|
||||
From your desk and from your phone.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
diff --git a/node_modules/react-native-uitextview/ios/RNUITextView.mm b/node_modules/react-native-uitextview/ios/RNUITextView.mm
|
||||
index d0c31ed..df8a233 100644
|
||||
--- a/node_modules/react-native-uitextview/ios/RNUITextView.mm
|
||||
+++ b/node_modules/react-native-uitextview/ios/RNUITextView.mm
|
||||
@@ -22,6 +22,7 @@ using namespace facebook::react;
|
||||
RNUITextViewShadowNode::ConcreteState::Shared _state;
|
||||
UITapGestureRecognizer * _outsideTapRecognizer;
|
||||
BOOL _suppressSelectionChange;
|
||||
+ CGRect _lastTextLayoutFrame;
|
||||
}
|
||||
|
||||
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
||||
@@ -47,7 +48,8 @@ using namespace facebook::react;
|
||||
_textView.delegate = self;
|
||||
// Must match RCTTextLayoutManager, which measures with usesFontLeading = NO.
|
||||
_textView.layoutManager.usesFontLeading = NO;
|
||||
- [self addSubview:_textView];
|
||||
+ _textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
+ [_view addSubview:_textView];
|
||||
|
||||
const auto longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handleLongPressIfNecessary:)];
|
||||
@@ -94,16 +96,15 @@ using namespace facebook::react;
|
||||
// Reset the frame to zero so that when it properly lays out on the next use
|
||||
_textView.frame = CGRectZero;
|
||||
_textView.attributedText = nil;
|
||||
+ _lastTextLayoutFrame = CGRectZero;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
- // _textView's frame is assigned inside drawRect, which only fires when
|
||||
- // state changes. Trigger a redraw whenever the host frame moves out from
|
||||
- // under it (rotation, parent relayout) so the text view resizes and
|
||||
- // onTextLayout re-fires with the new line wrapping.
|
||||
- if (!CGRectEqualToRect(_textView.frame, _view.frame)) {
|
||||
+ // Autoresizing updates UIKit immediately; report the new line layout when
|
||||
+ // the content container's bounds change.
|
||||
+ if (!CGRectEqualToRect(_lastTextLayoutFrame, _view.bounds)) {
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
@@ -124,8 +125,9 @@ using namespace facebook::react;
|
||||
// entirely when nothing actually changed so a JS-side state update made in
|
||||
// response to onSelectionChange doesn't deselect what the user is selecting.
|
||||
const BOOL textChanged = ![_textView.attributedText isEqualToAttributedString:convertedAttrString];
|
||||
- const BOOL frameChanged = !CGRectEqualToRect(_textView.frame, _view.frame);
|
||||
- if (!textChanged && !frameChanged) {
|
||||
+ const BOOL frameChanged = !CGRectEqualToRect(_textView.frame, _view.bounds);
|
||||
+ const BOOL layoutChanged = !CGRectEqualToRect(_lastTextLayoutFrame, _view.bounds);
|
||||
+ if (!textChanged && !frameChanged && !layoutChanged) {
|
||||
return;
|
||||
}
|
||||
if (textChanged) {
|
||||
@@ -142,7 +144,7 @@ using namespace facebook::react;
|
||||
_suppressSelectionChange = NO;
|
||||
}
|
||||
if (frameChanged) {
|
||||
- _textView.frame = _view.frame;
|
||||
+ _textView.frame = _view.bounds;
|
||||
}
|
||||
|
||||
__block std::vector<std::string> lines;
|
||||
@@ -162,6 +164,7 @@ using namespace facebook::react;
|
||||
}
|
||||
}];
|
||||
|
||||
+ _lastTextLayoutFrame = _view.bounds;
|
||||
if (_eventEmitter != nullptr) {
|
||||
std::dynamic_pointer_cast<const facebook::react::RNUITextViewEventEmitter>(_eventEmitter)
|
||||
->onTextLayout(facebook::react::RNUITextViewEventEmitter::OnTextLayout{static_cast<int>(self.tag), lines});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/builtin-plugins",
|
||||
"version": "0.11.2",
|
||||
"version": "0.11.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -8,7 +8,7 @@ category: Getting started
|
||||
|
||||
# Getting started
|
||||
|
||||
Paseo runs your coding agents on your machine and gives you a mobile, desktop, web, and CLI client to drive them from anywhere. Three common ways to install.
|
||||
Paseo is an agentic development environment for running coding agents, editing files, reviewing changes, and testing your work. Agents run on your machines; desktop, mobile, web, and CLI clients let you work from anywhere. Choose how to install it below.
|
||||
|
||||
## Desktop app (recommended)
|
||||
|
||||
|
||||
+22
-24
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Why Paseo?
|
||||
description: What Paseo is, what it isn't, and how it fits into your workflow.
|
||||
description: A full agentic development environment for running coding agents, editing files, reviewing changes, and testing your work.
|
||||
nav: Why Paseo?
|
||||
order: 2
|
||||
category: Getting started
|
||||
@@ -8,38 +8,36 @@ category: Getting started
|
||||
|
||||
# Why Paseo?
|
||||
|
||||
Paseo is a self-hostable platform for running and orchestrating coding agents. It runs the agent CLIs you already use, on the hardware you already have, and gives you a UI, CLI, and API to drive them from anywhere.
|
||||
Paseo is a full agentic development environment. Run coding agents, edit files, review diffs and pull requests, and test your app with terminals and a built-in desktop browser. For agent-driven development, Paseo replaces your IDE: take a task from the first prompt through review and testing in one app.
|
||||
|
||||
## Architecture
|
||||
## Develop in one workspace
|
||||
|
||||
- Daemon-client architecture. The daemon manages agents; clients (mobile, desktop, web, CLI) connect locally or over a relay. Remote access isn't an add-on.
|
||||
- macOS, Windows, and Linux are all primary targets. None of them are a port or an afterthought.
|
||||
- Mobile, desktop, and web are separate native clients. The mobile app is built in React Native, not a webview.
|
||||
Open the desktop app, choose a project and an agent, and start working. Keep the conversation, editor, terminal, diffs, and browser together in split panes. Run your app, inspect the result, and ask the agent for changes without leaving the workspace.
|
||||
|
||||
## Providers
|
||||
The desktop app is available on macOS, Windows, and Linux. It starts and manages its local daemon automatically.
|
||||
|
||||
- Bring your own. Use your Claude subscription, your OpenAI account, your own API keys, a self-hosted endpoint. Paseo doesn't proxy model calls.
|
||||
- Local voice stack. Speech-to-text and text-to-speech run on-device by default. OpenAI providers are configurable if you want cloud quality.
|
||||
- Open source. No telemetry on your code.
|
||||
## Run tasks in parallel
|
||||
|
||||
## Where agents run
|
||||
Give independent tasks their own worktrees and branches. Run several agents at once, inspect each diff, test each implementation, and choose what to merge. Use the same provider for every task or choose a different provider and model for each.
|
||||
|
||||
- Your laptop, a homelab, a company server. Same daemon, same client surface.
|
||||
- Any directory, git or not. Launch agents, merge locally, review the diff in the app.
|
||||
- GitHub PRs, checks, and reviews surface in the app when you want them. Not required.
|
||||
Configure [workspace scripts](/docs/worktrees#scripts-and-services) to start development servers with separate ports for each worktree. [Try the parallel development workflow](/docs/parallel-development).
|
||||
|
||||
## Parallel work
|
||||
## Use your agents and tools
|
||||
|
||||
- Splits and panes. Agents, terminals, and browsers side by side in one workspace.
|
||||
- Per-worktree services. Each worktree gets allocated ports for dev servers and databases, reachable through proxy URLs like `web.fix-auth.my-app.localhost` so they don't collide.
|
||||
- Multiple agents on the same repo via worktrees.
|
||||
Paseo runs the coding agents you already use, with your subscriptions, credentials, configuration, skills, and MCP servers. Your agents work with the files and tools on your machine. See [supported providers](/docs/supported-providers).
|
||||
|
||||
## Automation
|
||||
Dictate tasks or use [voice mode](/docs/voice). Speech-to-text and text-to-speech run locally by default, with cloud providers available in settings.
|
||||
|
||||
- The CLI exposes the same surface as the app. Anything in the UI is scriptable.
|
||||
- [Paseo tools](/docs/orchestration). Agents can drive Paseo themselves: create isolated workspaces, spawn subagents, open terminals, and send prompts.
|
||||
- [Hub](/docs/hub). A service you host that starts agents on your daemon when someone mentions you on GitHub, Slack, or Discord.
|
||||
## Continue from another device
|
||||
|
||||
## What it isn't
|
||||
The daemon owns the running agents and workspaces. The desktop app connects to it locally; mobile, web, and CLI clients connect to the same environment. That separation lets you continue working from another device or run your environment on a server.
|
||||
|
||||
Not a hosted agent, not an IDE, not a model provider. Paseo runs the CLIs you already use and stays out of the way.
|
||||
Use Paseo entirely on your laptop, or connect to another machine when you need it. The native iOS and Android app gives you agents, files, terminals, and diffs on your phone. The built-in browser runs in the desktop app. [Connect your devices](/docs/connectivity) directly or through the optional end-to-end encrypted relay.
|
||||
|
||||
## Automate and extend your workflow
|
||||
|
||||
Give agents [Paseo tools](/docs/orchestration) to create worktrees, launch other agents, send prompts, and collect results. Use the [CLI](/docs/cli) or [TypeScript SDK](/docs/sdk) to automate agent and workspace operations.
|
||||
|
||||
[Plugins](/docs/plugins) add providers, workspace panels, commands, and workflows. [Hub](/docs/hub) connects external events to agents running on your machines.
|
||||
|
||||
Paseo is open source under Apache-2.0, with no telemetry, tracking, or required Paseo account.
|
||||
|
||||
@@ -12,6 +12,10 @@ const patchedPackages = [
|
||||
nodeModulesPath: "node_modules/react-native-markdown-display",
|
||||
patchPrefix: "react-native-markdown-display+",
|
||||
},
|
||||
{
|
||||
nodeModulesPath: "node_modules/react-native-uitextview",
|
||||
patchPrefix: "react-native-uitextview+",
|
||||
},
|
||||
// Remove after react-native-unistyles ships
|
||||
// https://github.com/jpudysz/react-native-unistyles/pull/1203.
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user