@omercnet/paseo-pr-radar 0.3.4 → 0.4.0-next.89.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,29 +30,58 @@ Paseo supplies normalized workspace pull request status. A daemon-side plugin ha
30
30
 
31
31
  ## Install
32
32
 
33
- Install from the plugin's monorepo directory on the Paseo daemon host:
33
+ ### Paseo 0.9 beta
34
+
35
+ Paseo 0.9 supports npm plugin sources. Install the published package on the daemon host:
36
+
37
+ ```bash
38
+ paseo plugin install npm:@omercnet/paseo-pr-radar
39
+ ```
40
+
41
+ Update an npm installation to the latest published release:
42
+
43
+ ```bash
44
+ paseo plugin update pr-radar
45
+ ```
46
+
47
+ The npm install and update flow requires Paseo 0.9. Paseo 0.8 does not accept npm plugin sources.
48
+
49
+ ### Paseo 0.8
50
+
51
+ Install from the Git monorepo:
34
52
 
35
53
  ```bash
36
54
  paseo plugin add omercnet/paseo-plugins:pr-radar
37
55
  ```
38
56
 
57
+ Or install a local checkout by absolute path on the daemon host:
58
+
59
+ ```bash
60
+ paseo plugin install /absolute/path/to/paseo-plugins/pr-radar
61
+ ```
62
+
63
+ On Paseo 0.8, `plugin update` updates Git-managed installations only. Local directory
64
+ installations continue to use their checked-out source and require `plugin reload` after edits.
65
+
39
66
  The daemon must have plugins enabled and `gh` authenticated for GitHub viewer-aware triage.
40
67
 
41
68
  ## Develop
42
69
 
43
70
  ```bash
44
- bun install
45
- bun run check
46
- bun test
47
- bun run test:coverage
48
- bun run typecheck
49
- bunx paseo plugin install "$PWD"
50
- bunx paseo plugin reload pr-radar
71
+ npm ci
72
+ npm run check
73
+ npm test
74
+ npm run test:coverage
75
+ npm run typecheck
76
+ npx paseo plugin install "$PWD"
77
+ npx paseo plugin reload pr-radar
51
78
  ```
52
79
 
53
80
  Release Please maintains the version, changelog, component tag, and GitHub release from
54
81
  Conventional Commits in the monorepo.
55
82
 
56
- The project targets Paseo `0.8.x`, including compatible `0.8` prereleases. Host-owned navigation opens linked agents and
57
- workspaces without private routes or page reloads on web, desktop, iOS, and Android. React `19.1`
58
- and React Native `0.81` match the versions supplied by the plugin host.
83
+ The manifest supports Paseo `0.8.x`, including compatible `0.8` prereleases, and Paseo
84
+ `0.9.0-beta.1`. Development uses the `0.9.0-beta.1` CLI, client, plugin SDK, and protocol packages
85
+ together while preserving runtime compatibility with 0.8 hosts. Host-owned navigation opens
86
+ linked agents and workspaces without private routes or page reloads on web, desktop, iOS, and
87
+ Android. React `19.1` and React Native `0.81` match the versions supplied by the plugin host.
@@ -1,3 +1,4 @@
1
+ import * as PaseoClient from "@getpaseo/plugin/client";
1
2
  import { type PluginSurfaceProps, usePaseo, useRpc } from "@getpaseo/plugin/client";
2
3
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
4
  import { useCallback, useEffect, useMemo, useState } from "react";
@@ -24,6 +25,7 @@ import {
24
25
  hasActiveAgent,
25
26
  matchesRow,
26
27
  mergeInboxRows,
28
+ openPullRequestUrl,
27
29
  type PaseoApi,
28
30
  type PaseoWorkspace,
29
31
  type RadarBucket,
@@ -38,6 +40,11 @@ const CLOCK_TICK_MS = 30_000;
38
40
 
39
41
  type SavedView = "security" | "updated" | "stale" | "automation";
40
42
 
43
+ const openExternalUrlCandidate: unknown = Reflect.get(PaseoClient, "openExternalUrl");
44
+ const guardedOpenExternalUrl =
45
+ typeof openExternalUrlCandidate === "function"
46
+ ? (openExternalUrlCandidate as (url: string) => Promise<void>)
47
+ : undefined;
41
48
  const SAVED_VIEW_TITLES: Record<SavedView, string> = {
42
49
  security: "Security",
43
50
  updated: "Updated",
@@ -235,7 +242,10 @@ export function PrRadar({ theme, layout, host, navigation }: PluginSurfaceProps)
235
242
  })
236
243
  : paseo.workspaces.ref(action.workspaceId);
237
244
  const created = await targetWorkspace.agents.create(agentOptions);
238
- navigation?.openAgent({ agentId: created.id });
245
+ if (navigation) {
246
+ const target = { agentId: created.id, serverId: host.id };
247
+ navigation.openAgent(target);
248
+ }
239
249
  return `Started ${profile.name} for ${row.repository}#${row.number ?? "PR"}.`;
240
250
  },
241
251
  onMutate: () => {
@@ -513,7 +523,7 @@ export function PrRadar({ theme, layout, host, navigation }: PluginSurfaceProps)
513
523
  const openPr = useCallback(async (row: RadarRow) => {
514
524
  setOpenError(null);
515
525
  try {
516
- await Linking.openURL(row.url);
526
+ await openPullRequestUrl(row.url, guardedOpenExternalUrl, Linking);
517
527
  } catch {
518
528
  setOpenError(`Could not open ${row.repository}#${row.number ?? "PR"}.`);
519
529
  }
@@ -566,7 +576,10 @@ export function PrRadar({ theme, layout, host, navigation }: PluginSurfaceProps)
566
576
  <Pressable
567
577
  accessibilityRole="button"
568
578
  accessibilityLabel={`Open agent ${primaryAgent.title}`}
569
- onPress={() => navigation.openAgent({ agentId: primaryAgent.id })}
579
+ onPress={() => {
580
+ const target = { agentId: primaryAgent.id, serverId: host.id };
581
+ navigation.openAgent(target);
582
+ }}
570
583
  style={({ pressed }) => [styles.action, pressed && styles.refreshPressed]}
571
584
  >
572
585
  <Text style={styles.actionText}>Open agent</Text>
@@ -576,7 +589,10 @@ export function PrRadar({ theme, layout, host, navigation }: PluginSurfaceProps)
576
589
  <Pressable
577
590
  accessibilityRole="button"
578
591
  accessibilityLabel={`Open workspace for ${item.repository} ${item.number ?? ""}`}
579
- onPress={() => navigation.openWorkspace({ workspaceId: item.workspaceIds[0] })}
592
+ onPress={() => {
593
+ const target = { workspaceId: item.workspaceIds[0], serverId: host.id };
594
+ navigation.openWorkspace(target);
595
+ }}
580
596
  style={({ pressed }) => [styles.action, pressed && styles.refreshPressed]}
581
597
  >
582
598
  <Text style={styles.actionText}>Open workspace</Text>
package/client/radar.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { usePaseo } from "@getpaseo/plugin/client";
2
2
  import { z } from "zod";
3
- import type { GitHubInboxItem } from "../shared/viewer-scope";
3
+ import { type GitHubInboxItem, HttpsUrlSchema } from "../shared/viewer-scope";
4
4
 
5
5
  export type PaseoApi = ReturnType<typeof usePaseo>;
6
6
  export type PaseoWorkspace = Awaited<ReturnType<PaseoApi["workspaces"]["list"]>>["entries"][number];
@@ -312,6 +312,23 @@ export function buildAgentPrompt(row: RadarRow): string {
312
312
  return `Continue work on ${row.url}. Current state: ${row.reason}. Inspect the pull request and workspace, resolve the actionable blocker, run relevant validation, push the fix, and report the result. Do not merge the pull request.`;
313
313
  }
314
314
 
315
+ type UrlOpener = (url: string) => Promise<unknown>;
316
+
317
+ export async function openPullRequestUrl(
318
+ url: string,
319
+ guardedOpen: UrlOpener | undefined,
320
+ fallback: { openURL: UrlOpener },
321
+ ): Promise<void> {
322
+ const parsed = HttpsUrlSchema.safeParse(url);
323
+ if (!parsed.success) throw new Error("Only HTTPS pull request URLs are supported.");
324
+
325
+ if (guardedOpen) {
326
+ await guardedOpen(parsed.data);
327
+ return;
328
+ }
329
+ await fallback.openURL(parsed.data);
330
+ }
331
+
315
332
  export function buildRadarSnapshot(
316
333
  workspaces: readonly PaseoWorkspace[],
317
334
  entries: readonly AgentEntry[],
@@ -349,14 +366,15 @@ export function buildRadarSnapshot(
349
366
 
350
367
  const pullRequest = runtime?.pullRequest;
351
368
  if (!pullRequest || !isOpenPullRequest(pullRequest.state, pullRequest.isMerged)) continue;
369
+ const parsedUrl = HttpsUrlSchema.safeParse(pullRequest.url);
370
+ if (!parsedUrl.success) continue;
371
+ const url = parsedUrl.data;
352
372
 
353
373
  const repository =
354
374
  pullRequest.repoOwner && pullRequest.repoName
355
375
  ? `${pullRequest.repoOwner}/${pullRequest.repoName}`
356
- : parseRepository(pullRequest.url);
357
- const id = pullRequest.number
358
- ? `${repository.toLowerCase()}#${pullRequest.number}`
359
- : pullRequest.url;
376
+ : parseRepository(url);
377
+ const id = pullRequest.number ? `${repository.toLowerCase()}#${pullRequest.number}` : url;
360
378
  const parsedFacts = PullRequestFactsSchema.safeParse(pullRequest);
361
379
  const facts = parsedFacts.success
362
380
  ? (parsedFacts.data.forgeSpecific ?? parsedFacts.data.github ?? null)
@@ -386,7 +404,7 @@ export function buildRadarSnapshot(
386
404
  const row: RadarRow = {
387
405
  id,
388
406
  number: pullRequest.number ?? null,
389
- url: pullRequest.url,
407
+ url,
390
408
  title: pullRequest.title,
391
409
  repository,
392
410
  baseRefName: pullRequest.baseRefName,
package/package.json CHANGED
@@ -1,16 +1,25 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-pr-radar",
3
- "version": "0.3.4",
3
+ "version": "0.4.0-next.89.1",
4
4
  "type": "module",
5
5
  "description": "A viewer-aware delivery queue for pull requests linked to Paseo workspaces.",
6
6
  "license": "MIT",
7
7
  "author": "Omer Cohen",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/omercnet/paseo-plugins.git",
11
+ "directory": "pr-radar"
12
+ },
13
+ "homepage": "https://github.com/omercnet/paseo-plugins/tree/main/pr-radar#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/omercnet/paseo-plugins/issues"
16
+ },
8
17
  "publishConfig": {
9
18
  "access": "public"
10
19
  },
11
- "packageManager": "bun@1.4.0",
20
+ "packageManager": "npm@11.19.1",
12
21
  "engines": {
13
- "bun": ">=1.4.0"
22
+ "node": ">=24"
14
23
  },
15
24
  "keywords": [
16
25
  "paseo",
@@ -19,7 +28,6 @@
19
28
  "coding-agents"
20
29
  ],
21
30
  "files": [
22
- "bun.lock",
23
31
  "index.client.tsx",
24
32
  "index.server.ts",
25
33
  "client",
@@ -33,24 +41,26 @@
33
41
  "scripts": {
34
42
  "check": "biome check .",
35
43
  "check:write": "biome check --write .",
36
- "test": "bun test",
37
- "test:coverage": "bun test --coverage",
44
+ "test": "vitest run",
45
+ "test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=lcov --coverage.thresholds.lines=90 --coverage.thresholds.functions=85",
38
46
  "typecheck": "tsc --noEmit",
39
- "package:release": "bun scripts/package-release.ts"
47
+ "package:release": "node scripts/package-release.ts"
40
48
  },
41
49
  "devDependencies": {
42
50
  "@biomejs/biome": "^2.5.10",
43
- "@getpaseo/cli": "0.8.0",
44
- "@getpaseo/client": "0.8.0",
45
- "@getpaseo/plugin": "0.8.0",
46
- "@getpaseo/protocol": "0.8.0",
51
+ "@getpaseo/cli": "0.9.0-beta.1",
52
+ "@getpaseo/client": "0.9.0-beta.1",
53
+ "@getpaseo/plugin": "0.9.0-beta.1",
54
+ "@getpaseo/protocol": "0.9.0-beta.1",
47
55
  "@tanstack/react-query": "^5.102.3",
48
- "@types/bun": "^1.4.0",
56
+ "@types/node": "^24.5.2",
49
57
  "@types/react": "~19.2.0",
58
+ "@vitest/coverage-v8": "^5.0.0",
50
59
  "fflate": "^0.8.3",
51
60
  "react": "19.1.0",
52
61
  "react-native": "0.81.5",
53
- "typescript": "^5.9.3",
62
+ "typescript": "^7.0.0",
63
+ "vitest": "^5.0.0",
54
64
  "zod": "^4.4.3"
55
65
  }
56
66
  }
package/paseo-plugin.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "id": "pr-radar",
3
- "requirements": { "paseo": "^0.8.0" }
3
+ "requirements": { "paseo": "^0.8.0 || 0.9.0-beta.1" }
4
4
  }
@@ -1,10 +1,12 @@
1
1
  import { defineRpc } from "@getpaseo/plugin";
2
2
  import { z } from "zod";
3
3
 
4
+ export const HttpsUrlSchema = z.url({ protocol: /^https$/ });
5
+
4
6
  export const GitHubInboxItemSchema = z.object({
5
7
  id: z.string(),
6
8
  number: z.number().int().positive(),
7
- url: z.url(),
9
+ url: HttpsUrlSchema,
8
10
  title: z.string(),
9
11
  repository: z.string(),
10
12
  author: z.string().nullable(),
@@ -30,13 +32,13 @@ export type GitHubInboxItem = z.infer<typeof GitHubInboxItemSchema>;
30
32
  export const viewerScope = defineRpc({
31
33
  name: "pr-radar.viewer-scope",
32
34
  input: z.object({
33
- urls: z.array(z.url()).max(200),
35
+ urls: z.array(HttpsUrlSchema).max(200),
34
36
  windowDays: z.number().int().min(1).max(365).default(30),
35
37
  }),
36
38
  output: z.object({
37
39
  viewer: z.string().nullable(),
38
- authoredUrls: z.array(z.url()),
39
- reviewRequestedUrls: z.array(z.url()),
40
+ authoredUrls: z.array(HttpsUrlSchema),
41
+ reviewRequestedUrls: z.array(HttpsUrlSchema),
40
42
  inboxItems: z.array(GitHubInboxItemSchema),
41
43
  truncated: z.boolean(),
42
44
  coverageNote: z.string(),