mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5842482b0 | ||
|
|
9b9535aa78 | ||
|
|
515cb0a777 | ||
|
|
0dc944df3a | ||
|
|
16ea92aec5 |
119
.github/workflows/android-apk-release.yml
vendored
Normal file
119
.github/workflows/android-apk-release.yml
vendored
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
name: Android APK Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "Existing tag to build (e.g. v0.1.0)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: android-apk-release-${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref_name }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-android-apk:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: read
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || github.ref }}
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "22"
|
||||||
|
cache: "npm"
|
||||||
|
registry-url: "https://npm.pkg.github.com"
|
||||||
|
scope: "@boudra"
|
||||||
|
|
||||||
|
- name: Install JS dependencies
|
||||||
|
run: npm ci
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Setup Expo and EAS
|
||||||
|
uses: expo/expo-github-action@v8
|
||||||
|
with:
|
||||||
|
eas-version: latest
|
||||||
|
token: ${{ secrets.EXPO_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build Android APK on EAS
|
||||||
|
id: eas_build
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd packages/app
|
||||||
|
|
||||||
|
build_json="$(npx eas build --platform android --profile production-apk --non-interactive --wait --json)"
|
||||||
|
echo "$build_json" > "$RUNNER_TEMP/eas-build.json"
|
||||||
|
|
||||||
|
build_id="$(jq -r 'if type == "array" then .[0].id // empty else .id // empty end' "$RUNNER_TEMP/eas-build.json")"
|
||||||
|
if [ -z "$build_id" ]; then
|
||||||
|
echo "Failed to determine EAS build ID."
|
||||||
|
cat "$RUNNER_TEMP/eas-build.json"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "build_id=$build_id" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Resolve APK artifact URL
|
||||||
|
id: artifact
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd packages/app
|
||||||
|
|
||||||
|
build_view_json="$(npx eas build:view '${{ steps.eas_build.outputs.build_id }}' --json)"
|
||||||
|
echo "$build_view_json" > "$RUNNER_TEMP/eas-build-view.json"
|
||||||
|
|
||||||
|
artifact_url="$(jq -r '.artifacts.buildUrl // .artifacts.applicationArchiveUrl // empty' "$RUNNER_TEMP/eas-build-view.json")"
|
||||||
|
if [ -z "$artifact_url" ]; then
|
||||||
|
echo "Failed to determine APK artifact URL."
|
||||||
|
cat "$RUNNER_TEMP/eas-build-view.json"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
asset_name="paseo-${RELEASE_TAG}-android.apk"
|
||||||
|
asset_path="$RUNNER_TEMP/$asset_name"
|
||||||
|
|
||||||
|
curl --fail --location --output "$asset_path" "$artifact_url"
|
||||||
|
|
||||||
|
echo "asset_name=$asset_name" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "asset_path=$asset_path" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Wait for GitHub release tag
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for attempt in $(seq 1 90); do
|
||||||
|
if gh release view "$RELEASE_TAG" --repo "${{ github.repository }}" >/dev/null 2>&1; then
|
||||||
|
echo "Found release for tag $RELEASE_TAG"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Release for $RELEASE_TAG is not available yet (attempt $attempt/90)."
|
||||||
|
sleep 20
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Timed out waiting for GitHub release tag $RELEASE_TAG."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Upload APK to GitHub Release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh release upload "$RELEASE_TAG" "${{ steps.artifact.outputs.asset_path }}" --clobber --repo "${{ github.repository }}"
|
||||||
@@ -139,6 +139,7 @@ npm run android:production
|
|||||||
### Cloud build + submit (EAS Workflows)
|
### Cloud build + submit (EAS Workflows)
|
||||||
|
|
||||||
Tag pushes like `v0.1.0` trigger `packages/app/.eas/workflows/release-mobile.yml` on Expo servers.
|
Tag pushes like `v0.1.0` trigger `packages/app/.eas/workflows/release-mobile.yml` on Expo servers.
|
||||||
|
Tag pushes like `v0.1.0` also trigger `.github/workflows/android-apk-release.yml` on GitHub Actions to publish an APK asset on the matching GitHub Release.
|
||||||
|
|
||||||
That workflow does:
|
That workflow does:
|
||||||
- Build iOS with the `production` profile
|
- Build iOS with the `production` profile
|
||||||
@@ -182,7 +183,7 @@ npm run release:patch
|
|||||||
npm run version:all:patch # npm version across all workspaces (creates commit + local tag)
|
npm run version:all:patch # npm version across all workspaces (creates commit + local tag)
|
||||||
npm run release:check
|
npm run release:check
|
||||||
npm run release:publish
|
npm run release:publish
|
||||||
npm run release:push # pushes HEAD and current version tag (triggers desktop + EAS mobile workflows)
|
npm run release:push # pushes HEAD and current version tag (triggers desktop + Android APK + EAS mobile workflows)
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
@@ -196,6 +197,7 @@ Notes:
|
|||||||
Release completion checklist:
|
Release completion checklist:
|
||||||
- `npm run release:patch` completes successfully.
|
- `npm run release:patch` completes successfully.
|
||||||
- GitHub `Desktop Release` workflow for the new `v*` tag is green.
|
- GitHub `Desktop Release` workflow for the new `v*` tag is green.
|
||||||
|
- GitHub `Android APK Release` workflow for the same tag is green.
|
||||||
- EAS `release-mobile.yml` workflow for the same tag is green (Expo queues can take longer on the free plan).
|
- EAS `release-mobile.yml` workflow for the same tag is green (Expo queues can take longer on the free plan).
|
||||||
|
|
||||||
## Orchestrator Mode
|
## Orchestrator Mode
|
||||||
|
|||||||
54
package-lock.json
generated
54
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "paseo",
|
"name": "paseo",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "paseo",
|
"name": "paseo",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
@@ -14714,7 +14714,9 @@
|
|||||||
},
|
},
|
||||||
"node_modules/nan": {
|
"node_modules/nan": {
|
||||||
"version": "2.23.0",
|
"version": "2.23.0",
|
||||||
"license": "MIT"
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
@@ -14779,6 +14781,12 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-addon-api": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/node-domexception": {
|
"node_modules/node-domexception": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"funding": [
|
"funding": [
|
||||||
@@ -14825,6 +14833,16 @@
|
|||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-pty": {
|
||||||
|
"version": "1.2.0-beta.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.11.tgz",
|
||||||
|
"integrity": "sha512-THcUyu1WwdgoIyUvgXOZ70EOMXzheGa0q3tbEb5kUIfKgcpBJ+AJ9Q1kq0bKtYmQzr77usXiTORZTLmAUQlnoQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"node-addon-api": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.26",
|
"version": "2.0.26",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
@@ -20344,7 +20362,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@getpaseo/app",
|
"name": "@getpaseo/app",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@boudra/expo-two-way-audio": "^0.1.3",
|
"@boudra/expo-two-way-audio": "^0.1.3",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
@@ -20352,7 +20370,7 @@
|
|||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@expo/vector-icons": "^15.0.2",
|
"@expo/vector-icons": "^15.0.2",
|
||||||
"@floating-ui/react-native": "^0.10.7",
|
"@floating-ui/react-native": "^0.10.7",
|
||||||
"@getpaseo/server": "0.1.12",
|
"@getpaseo/server": "0.1.13",
|
||||||
"@gorhom/bottom-sheet": "^5.2.6",
|
"@gorhom/bottom-sheet": "^5.2.6",
|
||||||
"@gorhom/portal": "^1.0.14",
|
"@gorhom/portal": "^1.0.14",
|
||||||
"@lezer/common": "^1.5.0",
|
"@lezer/common": "^1.5.0",
|
||||||
@@ -20456,11 +20474,11 @@
|
|||||||
},
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "@getpaseo/cli",
|
"name": "@getpaseo/cli",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^1.0.0",
|
"@clack/prompts": "^1.0.0",
|
||||||
"@getpaseo/relay": "0.1.12",
|
"@getpaseo/relay": "0.1.13",
|
||||||
"@getpaseo/server": "0.1.12",
|
"@getpaseo/server": "0.1.13",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
@@ -20510,14 +20528,14 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@getpaseo/desktop",
|
"name": "@getpaseo/desktop",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.9.6"
|
"@tauri-apps/cli": "^2.9.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/relay": {
|
"packages/relay": {
|
||||||
"name": "@getpaseo/relay",
|
"name": "@getpaseo/relay",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
@@ -20533,12 +20551,12 @@
|
|||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@getpaseo/server",
|
"name": "@getpaseo/server",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "2.0.52",
|
"@ai-sdk/openai": "2.0.52",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
||||||
"@deepgram/sdk": "^3.4.0",
|
"@deepgram/sdk": "^3.4.0",
|
||||||
"@getpaseo/relay": "0.1.12",
|
"@getpaseo/relay": "0.1.13",
|
||||||
"@lezer/common": "^1.5.0",
|
"@lezer/common": "^1.5.0",
|
||||||
"@lezer/css": "^1.3.0",
|
"@lezer/css": "^1.3.0",
|
||||||
"@lezer/highlight": "^1.2.3",
|
"@lezer/highlight": "^1.2.3",
|
||||||
@@ -20558,7 +20576,7 @@
|
|||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"lezer-elixir": "^1.1.2",
|
"lezer-elixir": "^1.1.2",
|
||||||
"mnemonic-id": "^3.2.7",
|
"mnemonic-id": "^3.2.7",
|
||||||
"node-pty": "^1.0.0",
|
"node-pty": "1.2.0-beta.11",
|
||||||
"onnxruntime-node": "^1.23.0",
|
"onnxruntime-node": "^1.23.0",
|
||||||
"openai": "^4.20.0",
|
"openai": "^4.20.0",
|
||||||
"pino": "^10.2.0",
|
"pino": "^10.2.0",
|
||||||
@@ -20802,14 +20820,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/server/node_modules/node-pty": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"nan": "^2.17.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/server/node_modules/qs": {
|
"packages/server/node_modules/qs": {
|
||||||
"version": "6.14.0",
|
"version": "6.14.0",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
@@ -20890,7 +20900,7 @@
|
|||||||
},
|
},
|
||||||
"packages/website": {
|
"packages/website": {
|
||||||
"name": "@getpaseo/website",
|
"name": "@getpaseo/website",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "^1.20.3",
|
"@cloudflare/vite-plugin": "^1.20.3",
|
||||||
"@cloudflare/workers-types": "^4.20260114.0",
|
"@cloudflare/workers-types": "^4.20260114.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "paseo",
|
"name": "paseo",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/server",
|
"packages/server",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/app",
|
"name": "@getpaseo/app",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@expo/vector-icons": "^15.0.2",
|
"@expo/vector-icons": "^15.0.2",
|
||||||
"@floating-ui/react-native": "^0.10.7",
|
"@floating-ui/react-native": "^0.10.7",
|
||||||
"@getpaseo/server": "0.1.12",
|
"@getpaseo/server": "0.1.13",
|
||||||
"@gorhom/bottom-sheet": "^5.2.6",
|
"@gorhom/bottom-sheet": "^5.2.6",
|
||||||
"@gorhom/portal": "^1.0.14",
|
"@gorhom/portal": "^1.0.14",
|
||||||
"@lezer/common": "^1.5.0",
|
"@lezer/common": "^1.5.0",
|
||||||
|
|||||||
@@ -240,8 +240,9 @@ export function ExplorerSidebar({ serverId, agentId, cwd, isGit }: ExplorerSideb
|
|||||||
width: resizeWidth.value,
|
width: resizeWidth.value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mobile: full-screen overlay with gesture
|
// Mobile: full-screen overlay with gesture.
|
||||||
const overlayPointerEvents = Platform.OS === "web" ? "auto" : "box-none";
|
// On web, keep it interactive only while open so closed sidebars don't eat taps.
|
||||||
|
const overlayPointerEvents = Platform.OS === "web" ? (isOpen ? "auto" : "none") : "box-none";
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -325,8 +325,9 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
|||||||
|
|
||||||
|
|
||||||
// Render mobile sidebar
|
// Render mobile sidebar
|
||||||
// On web, use "auto" instead of "box-none" because web's pointer-events: none blocks scroll
|
// On web, keep the overlay interactive only while the sidebar is open.
|
||||||
const overlayPointerEvents = Platform.OS === "web" ? "auto" : "box-none";
|
// This preserves swipe/scroll behavior without blocking taps when closed.
|
||||||
|
const overlayPointerEvents = Platform.OS === "web" ? (isOpen ? "auto" : "none") : "box-none";
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
<View style={StyleSheet.absoluteFillObject} pointerEvents={overlayPointerEvents}>
|
<View style={StyleSheet.absoluteFillObject} pointerEvents={overlayPointerEvents}>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { useDaemonConnections } from "./daemon-connections-context";
|
|||||||
import type { ActiveConnection } from "./daemon-connections-context";
|
import type { ActiveConnection } from "./daemon-connections-context";
|
||||||
import {
|
import {
|
||||||
useSessionStore,
|
useSessionStore,
|
||||||
|
type Agent,
|
||||||
type SessionState,
|
type SessionState,
|
||||||
type DaemonConnectionSnapshot,
|
type DaemonConnectionSnapshot,
|
||||||
} from "@/stores/session-store";
|
} from "@/stores/session-store";
|
||||||
@@ -850,14 +851,7 @@ export function SessionProvider({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!connectionSnapshot.isConnected) {
|
if (!connectionSnapshot.isConnected) {
|
||||||
hasBootstrappedAgentUpdatesRef.current = false;
|
hasBootstrappedAgentUpdatesRef.current = false;
|
||||||
const subscriptionId = agentUpdatesSubscriptionIdRef.current;
|
pendingAgentUpdatesRef.current.clear();
|
||||||
if (subscriptionId && client) {
|
|
||||||
try {
|
|
||||||
client.unsubscribeAgentUpdates(subscriptionId);
|
|
||||||
} catch {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
agentUpdatesSubscriptionIdRef.current = null;
|
agentUpdatesSubscriptionIdRef.current = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -866,26 +860,84 @@ export function SessionProvider({
|
|||||||
}
|
}
|
||||||
hasBootstrappedAgentUpdatesRef.current = true;
|
hasBootstrappedAgentUpdatesRef.current = true;
|
||||||
|
|
||||||
try {
|
let cancelled = false;
|
||||||
if (!agentUpdatesSubscriptionIdRef.current) {
|
const requestedSubscriptionId = `app:${serverId}`;
|
||||||
agentUpdatesSubscriptionIdRef.current = client.subscribeAgentUpdates({
|
|
||||||
subscriptionId: `app:${serverId}`,
|
|
||||||
filter: { labels: { ui: "true" } },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[Session] subscribeAgentUpdates failed", { serverId, err });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session bootstrap is now fully event-driven for agent lists.
|
const bootstrapAgentDirectory = async () => {
|
||||||
setInitializingAgents(serverId, new Map());
|
try {
|
||||||
setHasHydratedAgents(serverId, true);
|
const payload = await client.fetchAgents({
|
||||||
updateConnectionStatus(serverId, {
|
filter: { labels: { ui: "true" } },
|
||||||
status: "online",
|
subscribe: { subscriptionId: requestedSubscriptionId },
|
||||||
lastOnlineAt: new Date().toISOString(),
|
});
|
||||||
agentListReady: true,
|
if (cancelled) {
|
||||||
});
|
return;
|
||||||
}, [connectionSnapshot.isConnected, client, serverId, setHasHydratedAgents, updateConnectionStatus]);
|
}
|
||||||
|
|
||||||
|
agentUpdatesSubscriptionIdRef.current =
|
||||||
|
payload.subscriptionId ?? requestedSubscriptionId;
|
||||||
|
|
||||||
|
const nextAgents = new Map<string, Agent>();
|
||||||
|
const nextPendingPermissions = new Map<
|
||||||
|
string,
|
||||||
|
{ key: string; agentId: string; request: AgentPermissionRequest }
|
||||||
|
>();
|
||||||
|
const nextStatuses = new Map<string, AgentLifecycleStatus>();
|
||||||
|
|
||||||
|
for (const entry of payload.entries) {
|
||||||
|
const agent = {
|
||||||
|
...normalizeAgentSnapshot(entry.agent, serverId),
|
||||||
|
projectPlacement: entry.project,
|
||||||
|
};
|
||||||
|
nextAgents.set(agent.id, agent);
|
||||||
|
nextStatuses.set(agent.id, agent.status);
|
||||||
|
|
||||||
|
for (const request of agent.pendingPermissions) {
|
||||||
|
const key = derivePendingPermissionKey(agent.id, request);
|
||||||
|
nextPendingPermissions.set(key, { key, agentId: agent.id, request });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousAgentStatusRef.current = nextStatuses;
|
||||||
|
pendingAgentUpdatesRef.current.clear();
|
||||||
|
setAgents(serverId, nextAgents);
|
||||||
|
for (const agent of nextAgents.values()) {
|
||||||
|
setAgentLastActivity(agent.id, agent.lastActivityAt);
|
||||||
|
}
|
||||||
|
setPendingPermissions(serverId, nextPendingPermissions);
|
||||||
|
setInitializingAgents(serverId, new Map());
|
||||||
|
setHasHydratedAgents(serverId, true);
|
||||||
|
updateConnectionStatus(serverId, {
|
||||||
|
status: "online",
|
||||||
|
lastOnlineAt: new Date().toISOString(),
|
||||||
|
agentListReady: true,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hasBootstrappedAgentUpdatesRef.current = false;
|
||||||
|
pendingAgentUpdatesRef.current.clear();
|
||||||
|
agentUpdatesSubscriptionIdRef.current = null;
|
||||||
|
console.error("[Session] fetchAgents bootstrap failed", { serverId, err });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void bootstrapAgentDirectory();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
connectionSnapshot.isConnected,
|
||||||
|
client,
|
||||||
|
serverId,
|
||||||
|
setAgentLastActivity,
|
||||||
|
setAgents,
|
||||||
|
setHasHydratedAgents,
|
||||||
|
setInitializingAgents,
|
||||||
|
setPendingPermissions,
|
||||||
|
updateConnectionStatus,
|
||||||
|
]);
|
||||||
|
|
||||||
// Daemon message handlers - directly update Zustand store
|
// Daemon message handlers - directly update Zustand store
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/cli",
|
"name": "@getpaseo/cli",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"description": "Paseo CLI - control your AI coding agents from the command line",
|
"description": "Paseo CLI - control your AI coding agents from the command line",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^1.0.0",
|
"@clack/prompts": "^1.0.0",
|
||||||
"@getpaseo/relay": "0.1.12",
|
"@getpaseo/relay": "0.1.13",
|
||||||
"@getpaseo/server": "0.1.12",
|
"@getpaseo/server": "0.1.13",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ export async function connectToDaemon(options?: ConnectOptions): Promise<DaemonC
|
|||||||
if (timeoutHandle) {
|
if (timeoutHandle) {
|
||||||
clearTimeout(timeoutHandle)
|
clearTimeout(timeoutHandle)
|
||||||
}
|
}
|
||||||
client.subscribeAgentUpdates({ subscriptionId: `cli:${process.pid}` })
|
|
||||||
return client
|
return client
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Clear the timeout on error too
|
// Clear the timeout on error too
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/desktop",
|
"name": "@getpaseo/desktop",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Paseo desktop app (Tauri wrapper)",
|
"description": "Paseo desktop app (Tauri wrapper)",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/relay",
|
"name": "@getpaseo/relay",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"description": "Paseo relay for bridging daemon and client connections",
|
"description": "Paseo relay for bridging daemon and client connections",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/server",
|
"name": "@getpaseo/server",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"description": "Paseo backend server",
|
"description": "Paseo backend server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
@@ -53,10 +53,10 @@
|
|||||||
"test:e2e:mobile": "playwright test --project='Mobile Chrome'"
|
"test:e2e:mobile": "playwright test --project='Mobile Chrome'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
|
||||||
"@getpaseo/relay": "0.1.12",
|
|
||||||
"@ai-sdk/openai": "2.0.52",
|
"@ai-sdk/openai": "2.0.52",
|
||||||
|
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
||||||
"@deepgram/sdk": "^3.4.0",
|
"@deepgram/sdk": "^3.4.0",
|
||||||
|
"@getpaseo/relay": "0.1.13",
|
||||||
"@lezer/common": "^1.5.0",
|
"@lezer/common": "^1.5.0",
|
||||||
"@lezer/css": "^1.3.0",
|
"@lezer/css": "^1.3.0",
|
||||||
"@lezer/highlight": "^1.2.3",
|
"@lezer/highlight": "^1.2.3",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"lezer-elixir": "^1.1.2",
|
"lezer-elixir": "^1.1.2",
|
||||||
"mnemonic-id": "^3.2.7",
|
"mnemonic-id": "^3.2.7",
|
||||||
"node-pty": "^1.0.0",
|
"node-pty": "1.2.0-beta.11",
|
||||||
"onnxruntime-node": "^1.23.0",
|
"onnxruntime-node": "^1.23.0",
|
||||||
"openai": "^4.20.0",
|
"openai": "^4.20.0",
|
||||||
"pino": "^10.2.0",
|
"pino": "^10.2.0",
|
||||||
@@ -93,13 +93,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.56.1",
|
"@playwright/test": "^1.56.1",
|
||||||
"playwright": "^1.56.1",
|
|
||||||
"@types/express": "^4.17.20",
|
"@types/express": "^4.17.20",
|
||||||
"@types/node": "^20.9.0",
|
"@types/node": "^20.9.0",
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"@types/ws": "^8.5.8",
|
"@types/ws": "^8.5.8",
|
||||||
"@vitest/ui": "^3.2.4",
|
"@vitest/ui": "^3.2.4",
|
||||||
|
"playwright": "^1.56.1",
|
||||||
"tsx": "^4.6.0",
|
"tsx": "^4.6.0",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ async function runVoiceRoundTrip(params: {
|
|||||||
}): Promise<RoundTripResult> {
|
}): Promise<RoundTripResult> {
|
||||||
const client = new DaemonClient({ url: `${params.daemonUrl}/ws` });
|
const client = new DaemonClient({ url: `${params.daemonUrl}/ws` });
|
||||||
await client.connect();
|
await client.connect();
|
||||||
client.subscribeAgentUpdates({ subscriptionId: `voice-e2e-${randomUUID()}` });
|
await client.fetchAgents({
|
||||||
|
subscribe: { subscriptionId: `voice-e2e-${randomUUID()}` },
|
||||||
|
});
|
||||||
|
|
||||||
const mode = await client.setVoiceMode(true, params.voiceAgentId);
|
const mode = await client.setVoiceMode(true, params.voiceAgentId);
|
||||||
if (!mode.accepted) {
|
if (!mode.accepted) {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ async function main(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await client.connect();
|
await client.connect();
|
||||||
client.subscribeAgentUpdates({ subscriptionId: "voice-debug" });
|
await client.fetchAgents({ subscribe: { subscriptionId: "voice-debug" } });
|
||||||
|
|
||||||
const voiceCwd = mkdtempSync(path.join(tmpdir(), "voice-roundtrip-debug-"));
|
const voiceCwd = mkdtempSync(path.join(tmpdir(), "voice-roundtrip-debug-"));
|
||||||
const voiceAgent = await client.createAgent({
|
const voiceAgent = await client.createAgent({
|
||||||
|
|||||||
@@ -535,6 +535,7 @@ describe("DaemonClient", () => {
|
|||||||
{ key: "created_at", direction: "desc" },
|
{ key: "created_at", direction: "desc" },
|
||||||
],
|
],
|
||||||
page: { limit: 25, cursor: "cursor-1" },
|
page: { limit: 25, cursor: "cursor-1" },
|
||||||
|
subscribe: { subscriptionId: "sub-1" },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
@@ -549,6 +550,7 @@ describe("DaemonClient", () => {
|
|||||||
direction: "asc" | "desc";
|
direction: "asc" | "desc";
|
||||||
}>;
|
}>;
|
||||||
page?: { limit: number; cursor?: string };
|
page?: { limit: number; cursor?: string };
|
||||||
|
subscribe?: { subscriptionId?: string };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
expect(request.message.type).toBe("fetch_agents_request");
|
expect(request.message.type).toBe("fetch_agents_request");
|
||||||
@@ -557,6 +559,7 @@ describe("DaemonClient", () => {
|
|||||||
{ key: "created_at", direction: "desc" },
|
{ key: "created_at", direction: "desc" },
|
||||||
]);
|
]);
|
||||||
expect(request.message.page).toEqual({ limit: 25, cursor: "cursor-1" });
|
expect(request.message.page).toEqual({ limit: 25, cursor: "cursor-1" });
|
||||||
|
expect(request.message.subscribe).toEqual({ subscriptionId: "sub-1" });
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -565,6 +568,7 @@ describe("DaemonClient", () => {
|
|||||||
type: "fetch_agents_response",
|
type: "fetch_agents_response",
|
||||||
payload: {
|
payload: {
|
||||||
requestId: request.message.requestId,
|
requestId: request.message.requestId,
|
||||||
|
subscriptionId: "sub-1",
|
||||||
entries: [],
|
entries: [],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
nextCursor: null,
|
nextCursor: null,
|
||||||
@@ -578,6 +582,7 @@ describe("DaemonClient", () => {
|
|||||||
|
|
||||||
await expect(promise).resolves.toEqual({
|
await expect(promise).resolves.toEqual({
|
||||||
requestId: request.message.requestId,
|
requestId: request.message.requestId,
|
||||||
|
subscriptionId: "sub-1",
|
||||||
entries: [],
|
entries: [],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
nextCursor: null,
|
nextCursor: null,
|
||||||
|
|||||||
@@ -350,10 +350,6 @@ export class DaemonClient {
|
|||||||
private connectReject: ((error: Error) => void) | null = null;
|
private connectReject: ((error: Error) => void) | null = null;
|
||||||
private lastErrorValue: string | null = null;
|
private lastErrorValue: string | null = null;
|
||||||
private connectionState: ConnectionState = { status: "idle" };
|
private connectionState: ConnectionState = { status: "idle" };
|
||||||
private agentUpdateSubscriptions = new Map<
|
|
||||||
string,
|
|
||||||
{ labels?: Record<string, string>; agentId?: string } | undefined
|
|
||||||
>();
|
|
||||||
private checkoutDiffSubscriptions = new Map<
|
private checkoutDiffSubscriptions = new Map<
|
||||||
string,
|
string,
|
||||||
{ cwd: string; compare: { mode: "uncommitted" | "base"; baseRef?: string } }
|
{ cwd: string; compare: { mode: "uncommitted" | "base"; baseRef?: string } }
|
||||||
@@ -475,7 +471,6 @@ export class DaemonClient {
|
|||||||
this.lastErrorValue = null;
|
this.lastErrorValue = null;
|
||||||
this.reconnectAttempt = 0;
|
this.reconnectAttempt = 0;
|
||||||
this.updateConnectionState({ status: "connected" });
|
this.updateConnectionState({ status: "connected" });
|
||||||
this.resubscribeAgentUpdates();
|
|
||||||
this.resubscribeCheckoutDiffSubscriptions();
|
this.resubscribeCheckoutDiffSubscriptions();
|
||||||
this.resubscribeTerminalDirectorySubscriptions();
|
this.resubscribeTerminalDirectorySubscriptions();
|
||||||
this.flushPendingSendQueue();
|
this.flushPendingSendQueue();
|
||||||
@@ -997,6 +992,7 @@ export class DaemonClient {
|
|||||||
...(options?.filter ? { filter: options.filter } : {}),
|
...(options?.filter ? { filter: options.filter } : {}),
|
||||||
...(options?.sort ? { sort: options.sort } : {}),
|
...(options?.sort ? { sort: options.sort } : {}),
|
||||||
...(options?.page ? { page: options.page } : {}),
|
...(options?.page ? { page: options.page } : {}),
|
||||||
|
...(options?.subscribe ? { subscribe: options.subscribe } : {}),
|
||||||
});
|
});
|
||||||
return this.sendRequest({
|
return this.sendRequest({
|
||||||
requestId: resolvedRequestId,
|
requestId: resolvedRequestId,
|
||||||
@@ -1043,44 +1039,6 @@ export class DaemonClient {
|
|||||||
return payload.agent;
|
return payload.agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeAgentUpdates(options?: {
|
|
||||||
subscriptionId?: string;
|
|
||||||
filter?: { labels?: Record<string, string>; agentId?: string };
|
|
||||||
}): string {
|
|
||||||
const subscriptionId = options?.subscriptionId ?? crypto.randomUUID();
|
|
||||||
this.agentUpdateSubscriptions.set(subscriptionId, options?.filter);
|
|
||||||
const message = SessionInboundMessageSchema.parse({
|
|
||||||
type: "subscribe_agent_updates",
|
|
||||||
subscriptionId,
|
|
||||||
...(options?.filter ? { filter: options.filter } : {}),
|
|
||||||
});
|
|
||||||
this.sendSessionMessage(message);
|
|
||||||
return subscriptionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribeAgentUpdates(subscriptionId: string): void {
|
|
||||||
this.agentUpdateSubscriptions.delete(subscriptionId);
|
|
||||||
const message = SessionInboundMessageSchema.parse({
|
|
||||||
type: "unsubscribe_agent_updates",
|
|
||||||
subscriptionId,
|
|
||||||
});
|
|
||||||
this.sendSessionMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private resubscribeAgentUpdates(): void {
|
|
||||||
if (this.agentUpdateSubscriptions.size === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const [subscriptionId, filter] of this.agentUpdateSubscriptions) {
|
|
||||||
const message = SessionInboundMessageSchema.parse({
|
|
||||||
type: "subscribe_agent_updates",
|
|
||||||
subscriptionId,
|
|
||||||
...(filter ? { filter } : {}),
|
|
||||||
});
|
|
||||||
this.sendSessionMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private resubscribeCheckoutDiffSubscriptions(): void {
|
private resubscribeCheckoutDiffSubscriptions(): void {
|
||||||
if (this.checkoutDiffSubscriptions.size === 0) {
|
if (this.checkoutDiffSubscriptions.size === 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -291,7 +291,9 @@ describe("daemon client E2E", () => {
|
|||||||
async () => {
|
async () => {
|
||||||
const cwd = tmpCwd();
|
const cwd = tmpCwd();
|
||||||
|
|
||||||
ctx.client.subscribeAgentUpdates();
|
await ctx.client.fetchAgents({
|
||||||
|
subscribe: { subscriptionId: "daemon-client-lifecycle" },
|
||||||
|
});
|
||||||
|
|
||||||
const agentUpdatePromise = waitForSignal(15000, (resolve) => {
|
const agentUpdatePromise = waitForSignal(15000, (resolve) => {
|
||||||
const unsubscribe = ctx.client.on("agent_update", (message) => {
|
const unsubscribe = ctx.client.on("agent_update", (message) => {
|
||||||
|
|||||||
@@ -143,7 +143,9 @@ describe("daemon E2E", () => {
|
|||||||
async () => {
|
async () => {
|
||||||
const cwd = tmpCwd();
|
const cwd = tmpCwd();
|
||||||
|
|
||||||
ctx.client.subscribeAgentUpdates();
|
await ctx.client.fetchAgents({
|
||||||
|
subscribe: { subscriptionId: "agent-operations-cancel" },
|
||||||
|
});
|
||||||
|
|
||||||
// Create Codex agent
|
// Create Codex agent
|
||||||
const agent = await ctx.client.createAgent({
|
const agent = await ctx.client.createAgent({
|
||||||
@@ -217,7 +219,9 @@ describe("daemon E2E", () => {
|
|||||||
async () => {
|
async () => {
|
||||||
const cwd = tmpCwd();
|
const cwd = tmpCwd();
|
||||||
|
|
||||||
ctx.client.subscribeAgentUpdates();
|
await ctx.client.fetchAgents({
|
||||||
|
subscribe: { subscriptionId: "agent-operations-mode" },
|
||||||
|
});
|
||||||
|
|
||||||
// Create a Codex agent with default mode ("auto")
|
// Create a Codex agent with default mode ("auto")
|
||||||
const agent = await ctx.client.createAgent({
|
const agent = await ctx.client.createAgent({
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ describe("daemon E2E", () => {
|
|||||||
unsubscribe = ctx.client.subscribeRawMessages((message) => {
|
unsubscribe = ctx.client.subscribeRawMessages((message) => {
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
});
|
});
|
||||||
ctx.client.subscribeAgentUpdates();
|
await ctx.client.fetchAgents({ subscribe: { subscriptionId: "live-preferences" } });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ describe("daemon E2E (real claude) - send while running recovery", () => {
|
|||||||
try {
|
try {
|
||||||
await primary.connect();
|
await primary.connect();
|
||||||
await secondary.connect();
|
await secondary.connect();
|
||||||
primary.subscribeAgentUpdates({ subscriptionId: "primary" });
|
await primary.fetchAgents({ subscribe: { subscriptionId: "primary" } });
|
||||||
secondary.subscribeAgentUpdates({ subscriptionId: "secondary" });
|
await secondary.fetchAgents({ subscribe: { subscriptionId: "secondary" } });
|
||||||
|
|
||||||
const agent = await primary.createAgent({
|
const agent = await primary.createAgent({
|
||||||
cwd,
|
cwd,
|
||||||
@@ -67,7 +67,9 @@ describe("daemon E2E (real claude) - send while running recovery", () => {
|
|||||||
const reconnected = new DaemonClient({ url: `ws://127.0.0.1:${daemon.port}/ws` });
|
const reconnected = new DaemonClient({ url: `ws://127.0.0.1:${daemon.port}/ws` });
|
||||||
try {
|
try {
|
||||||
await reconnected.connect();
|
await reconnected.connect();
|
||||||
reconnected.subscribeAgentUpdates({ subscriptionId: "reconnected" });
|
await reconnected.fetchAgents({
|
||||||
|
subscribe: { subscriptionId: "reconnected" },
|
||||||
|
});
|
||||||
|
|
||||||
reconnected.on("agent_update", (message) => {
|
reconnected.on("agent_update", (message) => {
|
||||||
if (message.type !== "agent_update" || message.payload.kind !== "upsert") {
|
if (message.type !== "agent_update" || message.payload.kind !== "upsert") {
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ describe("daemon E2E (real codex) - send while running recovery", () => {
|
|||||||
try {
|
try {
|
||||||
await primary.connect();
|
await primary.connect();
|
||||||
await secondary.connect();
|
await secondary.connect();
|
||||||
primary.subscribeAgentUpdates({ subscriptionId: "primary" });
|
await primary.fetchAgents({ subscribe: { subscriptionId: "primary" } });
|
||||||
secondary.subscribeAgentUpdates({ subscriptionId: "secondary" });
|
await secondary.fetchAgents({ subscribe: { subscriptionId: "secondary" } });
|
||||||
|
|
||||||
const agent = await primary.createAgent({
|
const agent = await primary.createAgent({
|
||||||
cwd,
|
cwd,
|
||||||
@@ -64,7 +64,9 @@ describe("daemon E2E (real codex) - send while running recovery", () => {
|
|||||||
const reconnected = new DaemonClient({ url: `ws://127.0.0.1:${daemon.port}/ws` });
|
const reconnected = new DaemonClient({ url: `ws://127.0.0.1:${daemon.port}/ws` });
|
||||||
try {
|
try {
|
||||||
await reconnected.connect();
|
await reconnected.connect();
|
||||||
reconnected.subscribeAgentUpdates({ subscriptionId: "reconnected" });
|
await reconnected.fetchAgents({
|
||||||
|
subscribe: { subscriptionId: "reconnected" },
|
||||||
|
});
|
||||||
|
|
||||||
reconnected.on("agent_update", (message) => {
|
reconnected.on("agent_update", (message) => {
|
||||||
if (message.type !== "agent_update" || message.payload.kind !== "upsert") {
|
if (message.type !== "agent_update" || message.payload.kind !== "upsert") {
|
||||||
|
|||||||
@@ -293,6 +293,7 @@ type FetchAgentsRequestMessage = Extract<
|
|||||||
SessionInboundMessage,
|
SessionInboundMessage,
|
||||||
{ type: "fetch_agents_request" }
|
{ type: "fetch_agents_request" }
|
||||||
>;
|
>;
|
||||||
|
type FetchAgentsRequestFilter = NonNullable<FetchAgentsRequestMessage["filter"]>;
|
||||||
type FetchAgentsRequestSort = NonNullable<FetchAgentsRequestMessage["sort"]>[number];
|
type FetchAgentsRequestSort = NonNullable<FetchAgentsRequestMessage["sort"]>[number];
|
||||||
type FetchAgentsResponsePayload = Extract<
|
type FetchAgentsResponsePayload = Extract<
|
||||||
SessionOutboundMessage,
|
SessionOutboundMessage,
|
||||||
@@ -300,6 +301,17 @@ type FetchAgentsResponsePayload = Extract<
|
|||||||
>["payload"];
|
>["payload"];
|
||||||
type FetchAgentsResponseEntry = FetchAgentsResponsePayload["entries"][number];
|
type FetchAgentsResponseEntry = FetchAgentsResponsePayload["entries"][number];
|
||||||
type FetchAgentsResponsePageInfo = FetchAgentsResponsePayload["pageInfo"];
|
type FetchAgentsResponsePageInfo = FetchAgentsResponsePayload["pageInfo"];
|
||||||
|
type AgentUpdatePayload = Extract<
|
||||||
|
SessionOutboundMessage,
|
||||||
|
{ type: "agent_update" }
|
||||||
|
>["payload"];
|
||||||
|
type AgentUpdatesFilter = FetchAgentsRequestFilter;
|
||||||
|
type AgentUpdatesSubscriptionState = {
|
||||||
|
subscriptionId: string;
|
||||||
|
filter?: AgentUpdatesFilter;
|
||||||
|
isBootstrapping: boolean;
|
||||||
|
pendingUpdatesByAgentId: Map<string, AgentUpdatePayload>;
|
||||||
|
};
|
||||||
type FetchAgentsCursor = {
|
type FetchAgentsCursor = {
|
||||||
sort: FetchAgentsRequestSort[];
|
sort: FetchAgentsRequestSort[];
|
||||||
values: Record<string, string | number | null>;
|
values: Record<string, string | number | null>;
|
||||||
@@ -551,12 +563,7 @@ export class Session {
|
|||||||
private readonly pushTokenStore: PushTokenStore;
|
private readonly pushTokenStore: PushTokenStore;
|
||||||
private readonly providerRegistry: ReturnType<typeof buildProviderRegistry>;
|
private readonly providerRegistry: ReturnType<typeof buildProviderRegistry>;
|
||||||
private unsubscribeAgentEvents: (() => void) | null = null;
|
private unsubscribeAgentEvents: (() => void) | null = null;
|
||||||
private agentUpdatesSubscription:
|
private agentUpdatesSubscription: AgentUpdatesSubscriptionState | null = null;
|
||||||
| {
|
|
||||||
subscriptionId: string;
|
|
||||||
filter?: { labels?: Record<string, string>; agentId?: string };
|
|
||||||
}
|
|
||||||
| null = null;
|
|
||||||
private clientActivity: {
|
private clientActivity: {
|
||||||
deviceType: "web" | "mobile";
|
deviceType: "web" | "mobile";
|
||||||
focusedAgentId: string | null;
|
focusedAgentId: string | null;
|
||||||
@@ -1081,19 +1088,105 @@ export class Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private matchesAgentFilter(
|
private matchesAgentFilter(options: {
|
||||||
agent: AgentSnapshotPayload,
|
agent: AgentSnapshotPayload;
|
||||||
filter?: { labels?: Record<string, string>; agentId?: string }
|
project: ProjectPlacementPayload;
|
||||||
): boolean {
|
filter?: AgentUpdatesFilter;
|
||||||
if (filter?.agentId && agent.id !== filter.agentId) {
|
}): boolean {
|
||||||
|
const { agent, project, filter } = options;
|
||||||
|
|
||||||
|
if (filter?.labels) {
|
||||||
|
const matchesLabels = Object.entries(filter.labels).every(
|
||||||
|
([key, value]) => agent.labels[key] === value
|
||||||
|
);
|
||||||
|
if (!matchesLabels) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const includeArchived = filter?.includeArchived ?? false;
|
||||||
|
if (!includeArchived && agent.archivedAt) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!filter?.labels) {
|
|
||||||
return true;
|
if (filter?.statuses && filter.statuses.length > 0) {
|
||||||
|
const statuses = new Set(filter.statuses);
|
||||||
|
if (!statuses.has(agent.status)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof filter?.requiresAttention === "boolean") {
|
||||||
|
const requiresAttention = agent.requiresAttention ?? false;
|
||||||
|
if (requiresAttention !== filter.requiresAttention) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter?.projectKeys && filter.projectKeys.length > 0) {
|
||||||
|
const projectKeys = new Set(
|
||||||
|
filter.projectKeys.filter((item) => item.trim().length > 0)
|
||||||
|
);
|
||||||
|
if (projectKeys.size > 0 && !projectKeys.has(project.projectKey)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAgentUpdateTargetId(update: AgentUpdatePayload): string {
|
||||||
|
return update.kind === "remove" ? update.agentId : update.agent.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bufferOrEmitAgentUpdate(
|
||||||
|
subscription: AgentUpdatesSubscriptionState,
|
||||||
|
payload: AgentUpdatePayload
|
||||||
|
): void {
|
||||||
|
if (subscription.isBootstrapping) {
|
||||||
|
subscription.pendingUpdatesByAgentId.set(
|
||||||
|
this.getAgentUpdateTargetId(payload),
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit({
|
||||||
|
type: "agent_update",
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private flushBootstrappedAgentUpdates(options?: {
|
||||||
|
snapshotUpdatedAtByAgentId?: Map<string, number>;
|
||||||
|
}): void {
|
||||||
|
const subscription = this.agentUpdatesSubscription;
|
||||||
|
if (!subscription || !subscription.isBootstrapping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription.isBootstrapping = false;
|
||||||
|
const pending = Array.from(subscription.pendingUpdatesByAgentId.values());
|
||||||
|
subscription.pendingUpdatesByAgentId.clear();
|
||||||
|
|
||||||
|
for (const payload of pending) {
|
||||||
|
if (payload.kind === "upsert") {
|
||||||
|
const snapshotUpdatedAt = options?.snapshotUpdatedAtByAgentId?.get(
|
||||||
|
payload.agent.id
|
||||||
|
);
|
||||||
|
if (typeof snapshotUpdatedAt === "number") {
|
||||||
|
const updateUpdatedAt = Date.parse(payload.agent.updatedAt);
|
||||||
|
if (!Number.isNaN(updateUpdatedAt) && updateUpdatedAt <= snapshotUpdatedAt) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit({
|
||||||
|
type: "agent_update",
|
||||||
|
payload,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return Object.entries(filter.labels).every(
|
|
||||||
([key, value]) => agent.labels[key] === value
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildFallbackProjectCheckout(cwd: string): ProjectCheckoutLitePayload {
|
private buildFallbackProjectCheckout(cwd: string): ProjectCheckoutLitePayload {
|
||||||
@@ -1159,51 +1252,31 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const payload = await this.buildAgentPayload(agent);
|
const payload = await this.buildAgentPayload(agent);
|
||||||
const matches = this.matchesAgentFilter(payload, subscription.filter);
|
const project = await this.buildProjectPlacement(payload.cwd);
|
||||||
|
const matches = this.matchesAgentFilter({
|
||||||
|
agent: payload,
|
||||||
|
project,
|
||||||
|
filter: subscription.filter,
|
||||||
|
});
|
||||||
|
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const project = await this.buildProjectPlacement(payload.cwd);
|
this.bufferOrEmitAgentUpdate(subscription, {
|
||||||
this.emit({
|
kind: "upsert",
|
||||||
type: "agent_update",
|
agent: payload,
|
||||||
payload: { kind: "upsert", agent: payload, project },
|
project,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit({
|
this.bufferOrEmitAgentUpdate(subscription, {
|
||||||
type: "agent_update",
|
kind: "remove",
|
||||||
payload: { kind: "remove", agentId: payload.id },
|
agentId: payload.id,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.sessionLogger.error({ err: error }, "Failed to emit agent update");
|
this.sessionLogger.error({ err: error }, "Failed to emit agent update");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async emitCurrentAgentUpdatesForSubscription(): Promise<void> {
|
|
||||||
const subscription = this.agentUpdatesSubscription;
|
|
||||||
if (!subscription) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const agents = await this.listAgentPayloads({
|
|
||||||
labels: subscription.filter?.labels,
|
|
||||||
});
|
|
||||||
for (const agent of agents) {
|
|
||||||
const project = await this.buildProjectPlacement(agent.cwd);
|
|
||||||
this.emit({
|
|
||||||
type: "agent_update",
|
|
||||||
payload: { kind: "upsert", agent, project },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.sessionLogger.error(
|
|
||||||
{ err: error },
|
|
||||||
"Failed to emit current agent updates for subscription bootstrap"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main entry point for processing session messages
|
* Main entry point for processing session messages
|
||||||
*/
|
*/
|
||||||
@@ -1230,22 +1303,6 @@ export class Session {
|
|||||||
await this.handleFetchAgent(msg.agentId, msg.requestId);
|
await this.handleFetchAgent(msg.agentId, msg.requestId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "subscribe_agent_updates":
|
|
||||||
this.agentUpdatesSubscription = {
|
|
||||||
subscriptionId: msg.subscriptionId,
|
|
||||||
filter: msg.filter,
|
|
||||||
};
|
|
||||||
await this.emitCurrentAgentUpdatesForSubscription();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "unsubscribe_agent_updates":
|
|
||||||
if (
|
|
||||||
this.agentUpdatesSubscription?.subscriptionId === msg.subscriptionId
|
|
||||||
) {
|
|
||||||
this.agentUpdatesSubscription = null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "delete_agent_request":
|
case "delete_agent_request":
|
||||||
await this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
|
await this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
|
||||||
break;
|
break;
|
||||||
@@ -1702,9 +1759,9 @@ export class Session {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this.agentUpdatesSubscription) {
|
if (this.agentUpdatesSubscription) {
|
||||||
this.emit({
|
this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, {
|
||||||
type: "agent_update",
|
kind: "remove",
|
||||||
payload: { kind: "remove", agentId },
|
agentId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5097,28 +5154,11 @@ export class Session {
|
|||||||
}> {
|
}> {
|
||||||
const filter = request.filter;
|
const filter = request.filter;
|
||||||
const sort = this.normalizeFetchAgentsSort(request.sort);
|
const sort = this.normalizeFetchAgentsSort(request.sort);
|
||||||
const includeArchived = filter?.includeArchived ?? false;
|
|
||||||
|
|
||||||
let agents = await this.listAgentPayloads({
|
const agents = await this.listAgentPayloads({
|
||||||
labels: filter?.labels,
|
labels: filter?.labels,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!includeArchived) {
|
|
||||||
agents = agents.filter((agent) => !agent.archivedAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter?.statuses && filter.statuses.length > 0) {
|
|
||||||
const statuses = new Set(filter.statuses);
|
|
||||||
agents = agents.filter((agent) => statuses.has(agent.status));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof filter?.requiresAttention === "boolean") {
|
|
||||||
agents = agents.filter(
|
|
||||||
(agent) =>
|
|
||||||
(agent.requiresAttention ?? false) === filter.requiresAttention
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const placementByCwd = new Map<string, Promise<ProjectPlacementPayload>>();
|
const placementByCwd = new Map<string, Promise<ProjectPlacementPayload>>();
|
||||||
const getPlacement = (cwd: string): Promise<ProjectPlacementPayload> => {
|
const getPlacement = (cwd: string): Promise<ProjectPlacementPayload> => {
|
||||||
const existing = placementByCwd.get(cwd);
|
const existing = placementByCwd.get(cwd);
|
||||||
@@ -5136,11 +5176,13 @@ export class Session {
|
|||||||
project: await getPlacement(agent.cwd),
|
project: await getPlacement(agent.cwd),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
entries = entries.filter((entry) =>
|
||||||
if (filter?.projectKeys && filter.projectKeys.length > 0) {
|
this.matchesAgentFilter({
|
||||||
const projectKeys = new Set(filter.projectKeys.filter((item) => item.trim().length > 0));
|
agent: entry.agent,
|
||||||
entries = entries.filter((entry) => projectKeys.has(entry.project.projectKey));
|
project: entry.project,
|
||||||
}
|
filter,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
entries.sort((left, right) =>
|
entries.sort((left, right) =>
|
||||||
this.compareFetchAgentsEntries(left, right, sort)
|
this.compareFetchAgentsEntries(left, right, sort)
|
||||||
@@ -5178,16 +5220,55 @@ export class Session {
|
|||||||
private async handleFetchAgents(
|
private async handleFetchAgents(
|
||||||
request: Extract<SessionInboundMessage, { type: "fetch_agents_request" }>
|
request: Extract<SessionInboundMessage, { type: "fetch_agents_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
|
||||||
|
const subscriptionId =
|
||||||
|
request.subscribe
|
||||||
|
? requestedSubscriptionId && requestedSubscriptionId.length > 0
|
||||||
|
? requestedSubscriptionId
|
||||||
|
: uuidv4()
|
||||||
|
: null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (subscriptionId) {
|
||||||
|
this.agentUpdatesSubscription = {
|
||||||
|
subscriptionId,
|
||||||
|
filter: request.filter,
|
||||||
|
isBootstrapping: true,
|
||||||
|
pendingUpdatesByAgentId: new Map(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const payload = await this.listFetchAgentsEntries(request);
|
const payload = await this.listFetchAgentsEntries(request);
|
||||||
|
const snapshotUpdatedAtByAgentId = new Map<string, number>();
|
||||||
|
for (const entry of payload.entries) {
|
||||||
|
const parsedUpdatedAt = Date.parse(entry.agent.updatedAt);
|
||||||
|
if (!Number.isNaN(parsedUpdatedAt)) {
|
||||||
|
snapshotUpdatedAtByAgentId.set(entry.agent.id, parsedUpdatedAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.emit({
|
this.emit({
|
||||||
type: "fetch_agents_response",
|
type: "fetch_agents_response",
|
||||||
payload: {
|
payload: {
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
|
...(subscriptionId ? { subscriptionId } : {}),
|
||||||
...payload,
|
...payload,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
subscriptionId &&
|
||||||
|
this.agentUpdatesSubscription?.subscriptionId === subscriptionId
|
||||||
|
) {
|
||||||
|
this.flushBootstrappedAgentUpdates({ snapshotUpdatedAtByAgentId });
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (
|
||||||
|
subscriptionId &&
|
||||||
|
this.agentUpdatesSubscription?.subscriptionId === subscriptionId
|
||||||
|
) {
|
||||||
|
this.agentUpdatesSubscription = null;
|
||||||
|
}
|
||||||
const code =
|
const code =
|
||||||
error instanceof SessionRequestError ? error.code : "fetch_agents_failed";
|
error instanceof SessionRequestError ? error.code : "fetch_agents_failed";
|
||||||
const message =
|
const message =
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export async function createDaemonTestContext(
|
|||||||
url: `ws://127.0.0.1:${daemon.port}/ws`,
|
url: `ws://127.0.0.1:${daemon.port}/ws`,
|
||||||
});
|
});
|
||||||
await client.connect();
|
await client.connect();
|
||||||
client.subscribeAgentUpdates({ subscriptionId: "test" });
|
await client.fetchAgents({ subscribe: { subscriptionId: "test" } });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
daemon,
|
daemon,
|
||||||
|
|||||||
@@ -405,26 +405,12 @@ export const AudioPlayedMessageSchema = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RequestAgentListMessageSchema = z.object({
|
const AgentDirectoryFilterSchema = z.object({
|
||||||
type: z.literal("request_agent_list"),
|
labels: z.record(z.string()).optional(),
|
||||||
requestId: z.string(),
|
projectKeys: z.array(z.string()).optional(),
|
||||||
filter: z.object({
|
statuses: z.array(AgentStatusSchema).optional(),
|
||||||
labels: z.record(z.string()).optional(),
|
includeArchived: z.boolean().optional(),
|
||||||
}).optional(),
|
requiresAttention: z.boolean().optional(),
|
||||||
});
|
|
||||||
|
|
||||||
export const SubscribeAgentUpdatesMessageSchema = z.object({
|
|
||||||
type: z.literal("subscribe_agent_updates"),
|
|
||||||
subscriptionId: z.string(),
|
|
||||||
filter: z.object({
|
|
||||||
labels: z.record(z.string()).optional(),
|
|
||||||
agentId: z.string().optional(),
|
|
||||||
}).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const UnsubscribeAgentUpdatesMessageSchema = z.object({
|
|
||||||
type: z.literal("unsubscribe_agent_updates"),
|
|
||||||
subscriptionId: z.string(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DeleteAgentRequestMessageSchema = z.object({
|
export const DeleteAgentRequestMessageSchema = z.object({
|
||||||
@@ -472,15 +458,7 @@ export const SendAgentMessageSchema = z.object({
|
|||||||
export const FetchAgentsRequestMessageSchema = z.object({
|
export const FetchAgentsRequestMessageSchema = z.object({
|
||||||
type: z.literal("fetch_agents_request"),
|
type: z.literal("fetch_agents_request"),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
filter: z
|
filter: AgentDirectoryFilterSchema.optional(),
|
||||||
.object({
|
|
||||||
labels: z.record(z.string()).optional(),
|
|
||||||
projectKeys: z.array(z.string()).optional(),
|
|
||||||
statuses: z.array(AgentStatusSchema).optional(),
|
|
||||||
includeArchived: z.boolean().optional(),
|
|
||||||
requiresAttention: z.boolean().optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
sort: z
|
sort: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -495,6 +473,11 @@ export const FetchAgentsRequestMessageSchema = z.object({
|
|||||||
cursor: z.string().min(1).optional(),
|
cursor: z.string().min(1).optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
subscribe: z
|
||||||
|
.object({
|
||||||
|
subscriptionId: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const FetchAgentRequestMessageSchema = z.object({
|
export const FetchAgentRequestMessageSchema = z.object({
|
||||||
@@ -1045,8 +1028,6 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
|||||||
AudioPlayedMessageSchema,
|
AudioPlayedMessageSchema,
|
||||||
FetchAgentsRequestMessageSchema,
|
FetchAgentsRequestMessageSchema,
|
||||||
FetchAgentRequestMessageSchema,
|
FetchAgentRequestMessageSchema,
|
||||||
SubscribeAgentUpdatesMessageSchema,
|
|
||||||
UnsubscribeAgentUpdatesMessageSchema,
|
|
||||||
DeleteAgentRequestMessageSchema,
|
DeleteAgentRequestMessageSchema,
|
||||||
ArchiveAgentRequestMessageSchema,
|
ArchiveAgentRequestMessageSchema,
|
||||||
UpdateAgentRequestMessageSchema,
|
UpdateAgentRequestMessageSchema,
|
||||||
@@ -1443,6 +1424,7 @@ export const FetchAgentsResponseMessageSchema = z.object({
|
|||||||
type: z.literal("fetch_agents_response"),
|
type: z.literal("fetch_agents_response"),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
|
subscriptionId: z.string().nullable().optional(),
|
||||||
entries: z.array(
|
entries: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
agent: AgentSnapshotPayloadSchema,
|
agent: AgentSnapshotPayloadSchema,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/website",
|
"name": "@getpaseo/website",
|
||||||
"version": "0.1.12",
|
"version": "0.1.13",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user