From ec7ea4b55a64da9e6b3ac3e48590d04ac4a59411 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 10 Feb 2026 16:26:51 +0700 Subject: [PATCH] Update files --- package-lock.json | 338 ++---------- package.json | 9 +- packages/app/package.json | 4 +- .../app/src/components/agent-stream-view.tsx | 7 +- .../src/components/draggable-list.native.tsx | 18 +- packages/app/src/components/message.tsx | 508 ++++++++++++++---- .../app/src/components/sliding-sidebar.tsx | 2 +- packages/app/src/styles/markdown-styles.ts | 2 +- packages/app/src/utils/markdown-list.test.ts | 52 ++ packages/app/src/utils/markdown-list.ts | 87 +++ packages/cli/package.json | 15 +- packages/cli/scripts/prepare-bundled-deps.sh | 32 -- packages/relay/package.json | 4 +- packages/server/package.json | 7 +- scripts/sync-workspace-versions.mjs | 73 +++ 15 files changed, 712 insertions(+), 446 deletions(-) create mode 100644 packages/app/src/utils/markdown-list.test.ts create mode 100644 packages/app/src/utils/markdown-list.ts delete mode 100755 packages/cli/scripts/prepare-bundled-deps.sh create mode 100644 scripts/sync-workspace-versions.mjs diff --git a/package-lock.json b/package-lock.json index 3c6ed89a7..156155db7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,20 +56,6 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/openai": { - "version": "2.0.53", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.12" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/@ai-sdk/provider": { "version": "2.0.0", "license": "Apache-2.0", @@ -4175,280 +4161,6 @@ "@lezer/lr": "^1.0.0" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.20.2", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ajv": "^6.12.6", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { - "version": "2.0.0", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { - "version": "2.2.0", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { - "version": "1.0.0", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { - "version": "1.2.2", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/express": { - "version": "5.1.0", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { - "version": "2.1.0", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { - "version": "2.0.0", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { - "version": "0.6.3", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { - "version": "1.1.0", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { - "version": "2.0.0", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { - "version": "3.0.1", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { - "version": "1.0.0", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { - "version": "6.14.0", - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/send": { - "version": "1.2.0", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { - "version": "2.2.0", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { - "version": "2.0.1", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/zod": { - "version": "3.25.76", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -5224,6 +4936,16 @@ "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, + "node_modules/@react-native-masked-view/masked-view": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.3.2.tgz", + "integrity": "sha512-XwuQoW7/GEgWRMovOQtX3A4PrXhyaZm0lVUiY8qJDvdngjLms9Cpdck6SmGAUNqQwcj2EadHC1HwL0bEyoa/SQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16", + "react-native": ">=0.57" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.81.5", "license": "MIT", @@ -5458,11 +5180,6 @@ "version": "2.1.0", "license": "MIT" }, - "node_modules/@react-native/normalize-colors": { - "version": "0.82.1", - "license": "MIT", - "peer": true - }, "node_modules/@react-native/virtualized-lists": { "version": "0.81.5", "license": "MIT", @@ -7039,7 +6756,7 @@ }, "node_modules/ajv": { "version": "6.12.6", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -15446,6 +15163,7 @@ }, "node_modules/playwright": { "version": "1.56.1", + "dev": true, "license": "Apache-2.0", "dependencies": { "playwright-core": "1.56.1" @@ -15462,6 +15180,7 @@ }, "node_modules/playwright-core": { "version": "1.56.1", + "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -20087,7 +19806,7 @@ "@dnd-kit/utilities": "^3.2.2", "@expo/vector-icons": "^15.0.2", "@floating-ui/react-native": "^0.10.7", - "@getpaseo/server": "*", + "@getpaseo/server": "0.1.0", "@gorhom/bottom-sheet": "^5.2.6", "@gorhom/portal": "^1.0.14", "@lezer/common": "^1.5.0", @@ -20099,6 +19818,7 @@ "@lezer/markdown": "^1.6.2", "@lezer/python": "^1.1.18", "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-masked-view/masked-view": "^0.3.2", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", @@ -20188,7 +19908,8 @@ "name": "@getpaseo/cli", "version": "0.1.0", "dependencies": { - "@getpaseo/server": "*", + "@getpaseo/relay": "0.1.0", + "@getpaseo/server": "0.1.0", "chalk": "^5.3.0", "commander": "^12.0.0", "mime-types": "^2.1.35", @@ -20263,8 +19984,10 @@ "name": "@getpaseo/server", "version": "0.1.0", "dependencies": { - "@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", + "@getpaseo/relay": "0.1.0", "@lezer/common": "^1.5.0", "@lezer/css": "^1.3.0", "@lezer/highlight": "^1.2.3", @@ -20277,7 +20000,7 @@ "@opencode-ai/sdk": "^1.1.12", "@sctg/sentencepiece-js": "^1.1.0", "@xterm/headless": "^6.0.0", - "ai": "^5.0.76", + "ai": "5.0.78", "ajv": "^8.17.1", "dotenv": "^17.2.3", "express": "^4.18.2", @@ -20288,7 +20011,6 @@ "openai": "^4.20.0", "pino": "^10.2.0", "pino-pretty": "^13.1.3", - "playwright": "^1.56.1", "qrcode": "^1.5.4", "sherpa-onnx": "^1.12.23", "sherpa-onnx-node": "^1.12.23", @@ -20300,7 +20022,6 @@ "zod-to-json-schema": "^3.25.1" }, "devDependencies": { - "@getpaseo/relay": "*", "@playwright/test": "^1.56.1", "@types/express": "^4.17.20", "@types/node": "^20.9.0", @@ -20308,11 +20029,28 @@ "@types/uuid": "^9.0.7", "@types/ws": "^8.5.8", "@vitest/ui": "^3.2.4", + "playwright": "^1.56.1", "tsx": "^4.6.0", "typescript": "^5.2.2", "vitest": "^3.2.4" } }, + "packages/server/node_modules/@ai-sdk/openai": { + "version": "2.0.52", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.52.tgz", + "integrity": "sha512-n1arAo4+63e6/FFE6z/1ZsZbiOl4cfsoZ3F4i2X7LPIEea786Y2yd7Qdr7AdB4HTLVo3OSb1PHVIcQmvYIhmEA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "packages/server/node_modules/@modelcontextprotocol/sdk": { "version": "1.20.1", "license": "MIT", diff --git a/package.json b/package.json index 5e4088c08..bd19be943 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,14 @@ "web": "npm run web --workspace=@getpaseo/app", "dev:desktop": "npm run dev --workspace=@getpaseo/desktop", "build:desktop": "npm run build --workspace=@getpaseo/desktop", - "cli": "npx tsx packages/cli/src/index.js" + "cli": "npx tsx packages/cli/src/index.js", + "version:sync-internal": "node scripts/sync-workspace-versions.mjs", + "version:all:patch": "npm version patch --workspaces --include-workspace-root --no-git-tag-version && npm run version:sync-internal && npm install --package-lock-only", + "version:all:minor": "npm version minor --workspaces --include-workspace-root --no-git-tag-version && npm run version:sync-internal && npm install --package-lock-only", + "version:all:major": "npm version major --workspaces --include-workspace-root --no-git-tag-version && npm run version:sync-internal && npm install --package-lock-only", + "release:check": "npm run typecheck --workspace=@getpaseo/relay && npm run typecheck --workspace=@getpaseo/server && npm run typecheck --workspace=@getpaseo/cli && npm run build --workspace=@getpaseo/relay && npm run build --workspace=@getpaseo/server && npm run build --workspace=@getpaseo/cli && npm pack --dry-run --workspace=@getpaseo/relay && npm pack --dry-run --workspace=@getpaseo/server && npm pack --dry-run --workspace=@getpaseo/cli", + "release:publish:dry-run": "npm publish --dry-run --workspace=@getpaseo/relay --access public && npm publish --dry-run --workspace=@getpaseo/server --access public && npm publish --dry-run --workspace=@getpaseo/cli --access public", + "release:publish": "npm publish --workspace=@getpaseo/relay --access public && npm publish --workspace=@getpaseo/server --access public && npm publish --workspace=@getpaseo/cli --access public" }, "devDependencies": { "concurrently": "^9.2.1", diff --git a/packages/app/package.json b/packages/app/package.json index 126e9d884..a109921fe 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -2,6 +2,7 @@ "name": "@getpaseo/app", "main": "index.ts", "version": "0.1.0", + "private": true, "scripts": { "start": "expo start", "reset-project": "node ./scripts/reset-project.js", @@ -29,6 +30,7 @@ "@dnd-kit/utilities": "^3.2.2", "@expo/vector-icons": "^15.0.2", "@floating-ui/react-native": "^0.10.7", + "@getpaseo/server": "0.1.0", "@gorhom/bottom-sheet": "^5.2.6", "@gorhom/portal": "^1.0.14", "@lezer/common": "^1.5.0", @@ -39,8 +41,8 @@ "@lezer/json": "^1.0.3", "@lezer/markdown": "^1.6.2", "@lezer/python": "^1.1.18", - "@getpaseo/server": "*", "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-masked-view/masked-view": "^0.3.2", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index d5093ac72..d1775739c 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -53,6 +53,7 @@ import { ToolCallSheetProvider } from "./tool-call-sheet"; import { createMarkdownStyles } from "@/styles/markdown-styles"; import { MAX_CONTENT_WIDTH } from "@/constants/layout"; import { isPerfLoggingEnabled, measurePayload, perfLog } from "@/utils/perf"; +import { getMarkdownListMarker } from "@/utils/markdown-list"; const isUserMessageItem = (item?: StreamItem) => item?.kind === "user_message"; const isToolSequenceItem = (item?: StreamItem) => @@ -973,9 +974,7 @@ function PermissionRequestCard({ parent: any, styles: any ) => { - const isOrdered = parent?.type === "ordered_list"; - const index = parent?.children?.indexOf(node) ?? 0; - const bullet = isOrdered ? `${index + 1}.` : "•"; + const { isOrdered, marker } = getMarkdownListMarker(node, parent); const iconStyle = isOrdered ? styles.ordered_list_icon : styles.bullet_list_icon; @@ -985,7 +984,7 @@ function PermissionRequestCard({ return ( - {bullet} + {marker} diff --git a/packages/app/src/components/draggable-list.native.tsx b/packages/app/src/components/draggable-list.native.tsx index 3bf1b6a30..99671635c 100644 --- a/packages/app/src/components/draggable-list.native.tsx +++ b/packages/app/src/components/draggable-list.native.tsx @@ -1,5 +1,5 @@ import { RefreshControl } from "react-native"; -import { useCallback } from "react"; +import { useCallback, useState } from "react"; import DraggableFlatList, { type RenderItemParams, } from "react-native-draggable-flatlist"; @@ -28,6 +28,7 @@ export function DraggableList({ waitFor, }: DraggableListProps) { const { theme } = useUnistyles(); + const [isDragging, setIsDragging] = useState(false); // Pass the ref directly to DraggableFlatList - it handles the gesture coordination // The ref may not have .current set yet, but that's okay - DraggableFlatList will @@ -50,11 +51,22 @@ export function DraggableList({ const handleDragEnd = useCallback( ({ data: newData }: { data: T[] }) => { + setIsDragging(false); onDragEnd(newData); }, [onDragEnd] ); + const handleDragBegin = useCallback(() => { + setIsDragging(true); + }, []); + + const handleRelease = useCallback(() => { + setIsDragging(false); + }, []); + + const showRefreshControl = Boolean(onRefresh) && (!isDragging || Boolean(refreshing)); + return ( ({ simultaneousHandlers={simultaneousHandlers} // Higher activationDistance prevents drag from interfering with nested onLongPress handlers activationDistance={20} + onDragBegin={handleDragBegin} + onRelease={handleRelease} // @ts-expect-error - waitFor is supported by RNGH FlatList but not typed in DraggableFlatList waitFor={waitFor} refreshControl={ - onRefresh ? ( + showRefreshControl ? ( ({ container: { flexDirection: "row", @@ -381,6 +422,10 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({ fontWeight: theme.fontWeight.normal, flexShrink: 0, }, + labelLoading: { + color: theme.colors.foreground, + opacity: 0.72, + }, secondaryLabel: { flex: 1, color: theme.colors.foregroundMuted, @@ -388,6 +433,12 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({ fontWeight: theme.fontWeight.normal, marginLeft: theme.spacing[2], }, + shimmerText: { + color: "transparent", + fontSize: theme.fontSize.base, + fontWeight: theme.fontWeight.normal, + flexShrink: 1, + }, spacer: { flex: 1, }, @@ -414,7 +465,32 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({ shimmerOverlay: { position: "absolute", top: 0, + right: 0, bottom: 0, + left: 0, + flexDirection: "row", + alignItems: "center", + overflow: "hidden", + }, + shimmerMaskRow: { + flexDirection: "row", + alignItems: "center", + width: "100%", + height: "100%", + }, + nativeShimmerTrack: { + position: "absolute", + top: 0, + right: 0, + bottom: 0, + left: 0, + overflow: "hidden", + }, + nativeShimmerPeak: { + position: "absolute", + top: 0, + bottom: 0, + left: 0, }, })); @@ -562,9 +638,7 @@ export const AssistantMessage = memo(function AssistantMessage({ parent: any, styles: any ) => { - const isOrdered = parent?.type === "ordered_list"; - const index = parent?.children?.indexOf(node) ?? 0; - const bullet = isOrdered ? `${index + 1}.` : "•"; + const { isOrdered, marker } = getMarkdownListMarker(node, parent); const iconStyle = isOrdered ? styles.ordered_list_icon : styles.bullet_list_icon; @@ -574,7 +648,7 @@ export const AssistantMessage = memo(function AssistantMessage({ return ( - {bullet} + {marker} @@ -997,41 +1071,167 @@ const ExpandableBadge = memo(function ExpandableBadge({ const { theme } = useUnistyles(); const resolvedDisableOuterSpacing = useDisableOuterSpacing(disableOuterSpacing); - const hasDetails = Boolean(renderDetails); - const detailContent = hasDetails && isExpanded ? renderDetails?.() : null; + const isInteractive = Boolean(onToggle); + const hasDetailContent = Boolean(renderDetails); + const detailContent = + hasDetailContent && isExpanded ? renderDetails?.() : null; - const [badgeWidth, setBadgeWidth] = useState(0); - const handleLayout = useCallback((e: LayoutChangeEvent) => { - setBadgeWidth(e.nativeEvent.layout.width); - }, []); + const nativeGradientIdRef = useRef( + `shimmer-gradient-${Math.random().toString(36).substring(2, 9)}` + ); + const [labelRowWidth, setLabelRowWidth] = useState(0); + const [labelRowHeight, setLabelRowHeight] = useState(0); + const [labelOffsetX, setLabelOffsetX] = useState(0); + const [labelWidth, setLabelWidth] = useState(0); + const [secondaryOffsetX, setSecondaryOffsetX] = useState(0); + const [secondaryWidth, setSecondaryWidth] = useState(0); + const shimmerTranslateX = useSharedValue(0); - const shimmer = useSharedValue(-1); + const totalShimmerChars = + label.trim().length + (secondaryLabel?.trim().length ?? 0); + const shortTextDurationAdjustment = totalShimmerChars <= 12 ? 0.25 : 0; + const shimmerDuration = Math.max( + 1, + Math.min(2.3, 1.25 + totalShimmerChars * 0.008 - shortTextDurationAdjustment) + ); + const nativeShimmerPeakWidth = Math.max( + 32, + Math.min(120, labelRowWidth > 0 ? labelRowWidth * 0.28 : 0) + ); + const isWebShimmer = isLoading && Platform.OS === "web"; + const shouldMeasureWebShimmer = isWebShimmer; + const shouldMeasureNativeShimmer = isLoading && Platform.OS !== "web"; + const isNativeShimmer = + shouldMeasureNativeShimmer && + labelRowWidth > 0 && + labelRowHeight > 0; + const webShimmerSpanStartX = labelOffsetX; + const webShimmerSpanEndX = secondaryLabel + ? secondaryOffsetX + secondaryWidth + : labelOffsetX + labelWidth; + const webShimmerSpanWidth = Math.max( + 1, + webShimmerSpanEndX - webShimmerSpanStartX + ); + const webShimmerPeakWidth = Math.max( + 42, + Math.min(120, webShimmerSpanWidth * 0.22) + ); + const webShimmerTrackStart = webShimmerSpanStartX - webShimmerPeakWidth; + const webShimmerTrackEnd = webShimmerSpanEndX; + + const handleLabelRowLayout = useCallback((event: LayoutChangeEvent) => { + if (!shouldMeasureNativeShimmer) { + return; + } + const { width, height } = event.nativeEvent.layout; + setLabelRowWidth((previous) => + Math.abs(previous - width) > 0.5 ? width : previous + ); + setLabelRowHeight((previous) => + Math.abs(previous - height) > 0.5 ? height : previous + ); + }, [shouldMeasureNativeShimmer]); + + const handleLabelLayout = useCallback((event: LayoutChangeEvent) => { + if (!shouldMeasureWebShimmer) { + return; + } + const { x, width } = event.nativeEvent.layout; + setLabelOffsetX((previous) => + Math.abs(previous - x) > 0.5 ? x : previous + ); + setLabelWidth((previous) => + Math.abs(previous - width) > 0.5 ? width : previous + ); + }, [shouldMeasureWebShimmer]); + + const handleSecondaryLayout = useCallback((event: LayoutChangeEvent) => { + if (!shouldMeasureWebShimmer || !secondaryLabel) { + return; + } + const { x, width } = event.nativeEvent.layout; + setSecondaryOffsetX((previous) => + Math.abs(previous - x) > 0.5 ? x : previous + ); + setSecondaryWidth((previous) => + Math.abs(previous - width) > 0.5 ? width : previous + ); + }, [shouldMeasureWebShimmer, secondaryLabel]); useEffect(() => { - if (isLoading) { - shimmer.value = -1; - shimmer.value = withRepeat( - withTiming(1, { duration: 2400, easing: Easing.bezier(0.4, 0, 0.6, 1) }), - -1 - ); - } else { - cancelAnimation(shimmer); - shimmer.value = -1; + if (!isWebShimmer) { + return; } - }, [isLoading]); + ensureWebToolCallShimmerKeyframes(); + }, [isWebShimmer]); - const shimmerBandWidth = 18; - const shimmerStyle = useAnimatedStyle(() => { - const travel = badgeWidth + shimmerBandWidth; - return { - transform: [ - { - translateX: - -shimmerBandWidth + ((shimmer.value + 1) / 2) * travel, - }, - ], + useEffect(() => { + if (!isNativeShimmer) { + cancelAnimation(shimmerTranslateX); + shimmerTranslateX.value = -nativeShimmerPeakWidth; + return; + } + const startPosition = -nativeShimmerPeakWidth; + const endPosition = labelRowWidth + nativeShimmerPeakWidth; + shimmerTranslateX.value = startPosition; + shimmerTranslateX.value = withRepeat( + withTiming(endPosition, { + duration: shimmerDuration * 1000, + easing: Easing.linear, + }), + -1, + false + ); + return () => { + cancelAnimation(shimmerTranslateX); }; - }); + }, [ + isNativeShimmer, + labelRowWidth, + nativeShimmerPeakWidth, + shimmerDuration, + shimmerTranslateX, + ]); + + const nativeShimmerPeakStyle = useAnimatedStyle(() => ({ + transform: [{ translateX: shimmerTranslateX.value }], + })); + + const shimmerGradient = + "linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.45) 24%, #ffffff 40%, #ffffff 60%, rgba(255, 255, 255, 0.45) 76%, rgba(255, 255, 255, 0) 100%)"; + + const shimmerLabelStyle = isWebShimmer + ? ({ + opacity: 1, + color: "transparent", + backgroundImage: shimmerGradient, + backgroundSize: `${webShimmerPeakWidth}px 100%`, + backgroundRepeat: "no-repeat", + backgroundClip: "text", + WebkitBackgroundClip: "text", + WebkitTextFillColor: "transparent", + animation: `${WEB_TOOLCALL_SHIMMER_ANIMATION_NAME} ${shimmerDuration}s linear infinite`, + "--paseo-shimmer-start": `${webShimmerTrackStart - labelOffsetX}px`, + "--paseo-shimmer-end": `${webShimmerTrackEnd - labelOffsetX}px`, + } as never) + : null; + + const shimmerSecondaryStyle = isWebShimmer + ? ({ + opacity: 1, + color: "transparent", + backgroundImage: shimmerGradient, + backgroundSize: `${webShimmerPeakWidth}px 100%`, + backgroundRepeat: "no-repeat", + backgroundClip: "text", + WebkitBackgroundClip: "text", + WebkitTextFillColor: "transparent", + animation: `${WEB_TOOLCALL_SHIMMER_ANIMATION_NAME} ${shimmerDuration}s linear infinite`, + "--paseo-shimmer-start": `${webShimmerTrackStart - secondaryOffsetX}px`, + "--paseo-shimmer-end": `${webShimmerTrackEnd - secondaryOffsetX}px`, + } as never) + : null; const IconComponent = icon; const iconColor = isError @@ -1058,14 +1258,13 @@ const ExpandableBadge = memo(function ExpandableBadge({ testID={testID} > [ expandableBadgeStylesheet.pressable, - pressed && hasDetails + pressed && isInteractive ? expandableBadgeStylesheet.pressablePressed : null, isExpanded && expandableBadgeStylesheet.pressableExpanded, @@ -1075,53 +1274,147 @@ const ExpandableBadge = memo(function ExpandableBadge({ <> {iconNode} - - + + {label} {secondaryLabel ? ( {secondaryLabel} ) : ( )} - {isLoading && badgeWidth > 0 ? ( - - - - + {label} + + {secondaryLabel ? ( + + {secondaryLabel} + + ) : ( + + )} + + ) : null} + {isNativeShimmer ? ( + + - - - - - - - - - - - - + + {label} + + {secondaryLabel ? ( + + {secondaryLabel} + + ) : ( + + )} + + } + > + + + + + + + + + + + + + + + + ) : null} - {hasDetails && hovered ? ( + {isInteractive && hovered ? ( ); -}); +}, areExpandableBadgePropsEqual); + +function areExpandableBadgePropsEqual( + previous: ExpandableBadgeProps, + next: ExpandableBadgeProps +) { + if (previous.label !== next.label) return false; + if (previous.secondaryLabel !== next.secondaryLabel) return false; + if (previous.icon !== next.icon) return false; + if (previous.isExpanded !== next.isExpanded) return false; + if (previous.style !== next.style) return false; + if (previous.isLoading !== next.isLoading) return false; + if (previous.isError !== next.isError) return false; + if (previous.isLastInSequence !== next.isLastInSequence) return false; + if (previous.disableOuterSpacing !== next.disableOuterSpacing) return false; + if (previous.testID !== next.testID) return false; + if (Boolean(previous.onToggle) !== Boolean(next.onToggle)) return false; + if (previous.isExpanded && previous.renderDetails !== next.renderDetails) { + return false; + } + return true; +} interface ToolCallProps { toolName: string; @@ -1329,18 +1643,26 @@ export const ToolCall = memo(function ToolCall({ icon={IconComponent} isExpanded={!isMobile && isExpanded} onToggle={hasDetails ? handleToggle : undefined} - renderDetails={ - hasDetails && !isMobile - ? renderDetails - : hasDetails - ? () => null - : undefined - } - isLoading={status === "executing" || status === "running"} + renderDetails={hasDetails && !isMobile ? renderDetails : undefined} + isLoading={status === "running" || status === "executing"} isError={status === "failed"} isLastInSequence={isLastInSequence} disableOuterSpacing={disableOuterSpacing} onDetailHoverChange={onInlineDetailsHoverChange} /> ); -}); +}, areToolCallPropsEqual); + +function areToolCallPropsEqual(previous: ToolCallProps, next: ToolCallProps) { + if (previous.toolName !== next.toolName) return false; + if (previous.args !== next.args) return false; + if (previous.result !== next.result) return false; + if (previous.error !== next.error) return false; + if (previous.status !== next.status) return false; + if (previous.detail !== next.detail) return false; + if (previous.cwd !== next.cwd) return false; + if (previous.metadata !== next.metadata) return false; + if (previous.isLastInSequence !== next.isLastInSequence) return false; + if (previous.disableOuterSpacing !== next.disableOuterSpacing) return false; + return true; +} diff --git a/packages/app/src/components/sliding-sidebar.tsx b/packages/app/src/components/sliding-sidebar.tsx index 8163c708c..f060a6179 100644 --- a/packages/app/src/components/sliding-sidebar.tsx +++ b/packages/app/src/components/sliding-sidebar.tsx @@ -125,8 +125,8 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) { if (isMobile) { translateX.value = -windowWidth; backdropOpacity.value = 0; + closeToAgent(); } - closeToAgent(); router.push("/agents"); }, [backdropOpacity, closeToAgent, isMobile, translateX, windowWidth]); diff --git a/packages/app/src/styles/markdown-styles.ts b/packages/app/src/styles/markdown-styles.ts index 1aa304ed9..d3e1ac3d5 100644 --- a/packages/app/src/styles/markdown-styles.ts +++ b/packages/app/src/styles/markdown-styles.ts @@ -254,7 +254,7 @@ export function createMarkdownStyles(theme: Theme) { color: theme.colors.foregroundMuted, marginRight: 4, fontSize: theme.fontSize.base, - fontWeight: theme.fontWeight.semibold, + fontWeight: theme.fontWeight.normal, lineHeight: 22, minWidth: 12, }, diff --git a/packages/app/src/utils/markdown-list.test.ts b/packages/app/src/utils/markdown-list.test.ts new file mode 100644 index 000000000..a750165da --- /dev/null +++ b/packages/app/src/utils/markdown-list.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from "vitest"; +import { getMarkdownListMarker } from "./markdown-list"; + +describe("getMarkdownListMarker", () => { + it("returns a bullet marker for unordered list items", () => { + expect( + getMarkdownListMarker( + { index: 0 }, + [{ type: "bullet_list" }] + ) + ).toEqual({ + isOrdered: false, + marker: "•", + }); + }); + + it("returns numbered markers for ordered list items", () => { + expect( + getMarkdownListMarker( + { index: 1, markup: "." }, + [{ type: "ordered_list" }] + ) + ).toEqual({ + isOrdered: true, + marker: "2.", + }); + }); + + it("respects ordered list start attribute", () => { + expect( + getMarkdownListMarker( + { index: 2, markup: ")" }, + [{ type: "ordered_list", attributes: { start: "5" } }] + ) + ).toEqual({ + isOrdered: true, + marker: "7)", + }); + }); + + it("prefers the nearest list ancestor in nested lists", () => { + expect( + getMarkdownListMarker( + { index: 0, markup: "." }, + [{ type: "ordered_list" }, { type: "bullet_list" }] + ) + ).toEqual({ + isOrdered: true, + marker: "1.", + }); + }); +}); diff --git a/packages/app/src/utils/markdown-list.ts b/packages/app/src/utils/markdown-list.ts new file mode 100644 index 000000000..79cf7a34a --- /dev/null +++ b/packages/app/src/utils/markdown-list.ts @@ -0,0 +1,87 @@ +type MarkdownNode = { + type?: string; + index?: number; + markup?: string; + attributes?: { + start?: number | string; + }; + children?: MarkdownNode[]; +}; + +const LIST_BULLET = "•"; +const DEFAULT_ORDERED_LIST_MARKUP = "."; + +function toParentNodes(parent: unknown): MarkdownNode[] { + if (Array.isArray(parent)) { + return parent; + } + + if (parent && typeof parent === "object") { + return [parent as MarkdownNode]; + } + + return []; +} + +function getNearestListParent(parent: unknown): MarkdownNode | undefined { + return toParentNodes(parent).find( + (ancestor) => + ancestor?.type === "ordered_list" || ancestor?.type === "bullet_list" + ); +} + +function getOrderedListItemIndex(node: MarkdownNode, listParent: MarkdownNode): number { + if (typeof node.index === "number" && Number.isFinite(node.index) && node.index >= 0) { + return node.index; + } + + if (Array.isArray(listParent.children)) { + const fallbackIndex = listParent.children.indexOf(node); + if (fallbackIndex >= 0) { + return fallbackIndex; + } + } + + return 0; +} + +function parseOrderedListStart(node: MarkdownNode): number { + const rawStart = node.attributes?.start; + if (typeof rawStart === "number" && Number.isFinite(rawStart)) { + return rawStart; + } + + if (typeof rawStart === "string") { + const parsed = Number.parseInt(rawStart, 10); + if (Number.isFinite(parsed)) { + return parsed; + } + } + + return 1; +} + +export function getMarkdownListMarker(node: MarkdownNode, parent: unknown): { + isOrdered: boolean; + marker: string; +} { + const listParent = getNearestListParent(parent); + if (!listParent || listParent.type !== "ordered_list") { + return { + isOrdered: false, + marker: LIST_BULLET, + }; + } + + const orderedIndex = getOrderedListItemIndex(node, listParent); + const orderedStart = parseOrderedListStart(listParent); + const orderedMarkup = + typeof node.markup === "string" && node.markup.length > 0 + ? node.markup + : DEFAULT_ORDERED_LIST_MARKUP; + + return { + isOrdered: true, + marker: `${orderedStart + orderedIndex}${orderedMarkup}`, + }; +} diff --git a/packages/cli/package.json b/packages/cli/package.json index dda52960f..b9f8d8039 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -7,23 +7,22 @@ "bin", "dist" ], - "bundleDependencies": [ - "@getpaseo/server", - "@getpaseo/relay" - ], + "publishConfig": { + "access": "public" + }, "bin": { - "paseo": "./bin/paseo" + "paseo": "bin/paseo" }, "scripts": { "build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false", - "prepack": "npm --prefix ../.. run build --workspace=@getpaseo/relay && npm --prefix ../.. run build --workspace=@getpaseo/server && npm run build && bash ./scripts/prepare-bundled-deps.sh", + "prepack": "npm run build", "typecheck": "tsc --noEmit", "test:e2e": "npx zx tests/run-all.ts", "test:e2e:lifecycle": "npx tsx tests/e2e/agent-lifecycle.test.ts" }, "dependencies": { - "@getpaseo/relay": "*", - "@getpaseo/server": "*", + "@getpaseo/relay": "0.1.0", + "@getpaseo/server": "0.1.0", "chalk": "^5.3.0", "commander": "^12.0.0", "mime-types": "^2.1.35", diff --git a/packages/cli/scripts/prepare-bundled-deps.sh b/packages/cli/scripts/prepare-bundled-deps.sh deleted file mode 100755 index b2280fb1c..000000000 --- a/packages/cli/scripts/prepare-bundled-deps.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CLI_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -REPO_ROOT="$(cd "$CLI_DIR/../.." && pwd)" -TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/paseo-bundle-XXXXXX")" - -cleanup() { - rm -rf "$TMP_DIR" -} -trap cleanup EXIT - -# Nested npm calls can inherit dry-run mode from a parent `npm pack --dry-run`. -# Force real pack/install here so bundled deps are materialized. -export npm_config_dry_run=false - -npm --prefix "$REPO_ROOT" pack --silent --workspace=@getpaseo/relay --pack-destination "$TMP_DIR" -npm --prefix "$REPO_ROOT" pack --silent --workspace=@getpaseo/server --pack-destination "$TMP_DIR" - -RELAY_TGZ="$(ls "$TMP_DIR"/getpaseo-relay-*.tgz | head -n 1)" -SERVER_TGZ="$(ls "$TMP_DIR"/getpaseo-server-*.tgz | head -n 1)" - -mkdir -p "$CLI_DIR/node_modules/@getpaseo" -rm -rf "$CLI_DIR/node_modules/@getpaseo/relay" "$CLI_DIR/node_modules/@getpaseo/server" - -npm --prefix "$CLI_DIR" install --no-save --no-package-lock --workspaces=false --silent "$RELAY_TGZ" "$SERVER_TGZ" - -if [ -L "$CLI_DIR/node_modules/@getpaseo/relay" ] || [ -L "$CLI_DIR/node_modules/@getpaseo/server" ]; then - echo "Expected bundled @getpaseo deps to be real directories, not symlinks." >&2 - exit 1 -fi diff --git a/packages/relay/package.json b/packages/relay/package.json index 3d772292a..5dd632abc 100644 --- a/packages/relay/package.json +++ b/packages/relay/package.json @@ -1,9 +1,11 @@ { "name": "@getpaseo/relay", "version": "0.1.0", - "private": true, "description": "Paseo relay for bridging daemon and client connections", "type": "module", + "publishConfig": { + "access": "public" + }, "types": "./dist/index.d.ts", "files": [ "dist" diff --git a/packages/server/package.json b/packages/server/package.json index f578dcb8f..811ae6eb5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,9 +1,11 @@ { "name": "@getpaseo/server", "version": "0.1.0", - "private": true, "description": "Paseo backend server", "type": "module", + "publishConfig": { + "access": "public" + }, "types": "./dist/server/server/exports.d.ts", "files": [ "dist/server", @@ -49,7 +51,8 @@ "test:e2e:mobile": "playwright test --project='Mobile Chrome'" }, "dependencies": { - "@getpaseo/relay": "*", + "@anthropic-ai/claude-agent-sdk": "^0.2.11", + "@getpaseo/relay": "0.1.0", "@ai-sdk/openai": "2.0.52", "@deepgram/sdk": "^3.4.0", "@lezer/common": "^1.5.0", diff --git a/scripts/sync-workspace-versions.mjs b/scripts/sync-workspace-versions.mjs new file mode 100644 index 000000000..cecb16a34 --- /dev/null +++ b/scripts/sync-workspace-versions.mjs @@ -0,0 +1,73 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const rootDir = path.resolve(__dirname, '..') +const rootPackagePath = path.join(rootDir, 'package.json') + +const rootPackage = JSON.parse(readFileSync(rootPackagePath, 'utf8')) +const rootVersion = rootPackage.version +const workspacePaths = Array.isArray(rootPackage.workspaces) ? rootPackage.workspaces : [] + +if (typeof rootVersion !== 'string' || rootVersion.length === 0) { + throw new Error('Root package.json must contain a valid "version"') +} + +const dependencySections = [ + 'dependencies', + 'devDependencies', + 'peerDependencies', + 'optionalDependencies', +] + +const touched = [] + +for (const workspacePath of workspacePaths) { + const packagePath = path.join(rootDir, workspacePath, 'package.json') + if (!existsSync(packagePath)) { + continue + } + + const pkg = JSON.parse(readFileSync(packagePath, 'utf8')) + let changed = false + + if (pkg.version !== rootVersion) { + pkg.version = rootVersion + changed = true + } + + for (const section of dependencySections) { + const deps = pkg[section] + if (!deps || typeof deps !== 'object') { + continue + } + + for (const name of Object.keys(deps)) { + if (!name.startsWith('@getpaseo/')) { + continue + } + if (name === pkg.name) { + continue + } + if (deps[name] !== rootVersion) { + deps[name] = rootVersion + changed = true + } + } + } + + if (changed) { + writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`) + touched.push(path.relative(rootDir, packagePath)) + } +} + +if (touched.length === 0) { + console.log(`Workspace versions and internal deps already synced to ${rootVersion}`) +} else { + console.log(`Synced to ${rootVersion}:`) + for (const file of touched) { + console.log(`- ${file}`) + } +}