@omercnet/paseo-agent-crew 0.3.0 → 1.0.0
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 +23 -31
- package/client/auto-open.ts +211 -0
- package/client/crew.ts +39 -4
- package/client/settings-screen.tsx +78 -0
- package/index.client.tsx +25 -4
- package/index.server.ts +10 -0
- package/package.json +15 -17
- package/paseo-plugin.json +2 -3
- package/server/auto-open.ts +86 -0
- package/shared/auto-open.ts +24 -0
- package/shared/settings.ts +17 -0
- package/CHANGELOG.md +0 -60
- package/docs/images/agent-crew-action.png +0 -0
- package/docs/images/agent-crew-overview.png +0 -0
- package/tsconfig.json +0 -16
package/README.md
CHANGED
|
@@ -8,16 +8,27 @@ parent-child relationships across workspace boundaries.
|
|
|
8
8
|
|
|
9
9
|
## Screenshots
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
Agent, workspace, and task details come from an isolated synthetic workspace. Both PNGs were
|
|
12
|
+
captured at 2× pixel density after browser DevTools rewrote local paths and host labels and verified
|
|
13
|
+
that no private organization names remained in the rendered page.
|
|
13
14
|
|
|
14
15
|
### Crew overview
|
|
15
16
|
|
|
16
|
-

|
|
17
|
+

|
|
17
18
|
|
|
18
19
|
### Safe action confirmation
|
|
19
20
|
|
|
20
|
-

|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
## Auto open
|
|
24
|
+
|
|
25
|
+
When enabled in the plugin settings screen, Agent Crew opens the Explorer tab once for each new workspace observed on that host.
|
|
26
|
+
|
|
27
|
+
- Default: off.
|
|
28
|
+
- Existing workspaces are not opened when the setting is enabled.
|
|
29
|
+
- Disabling stops queued and future opens. Re-enabling resumes observation for workspaces created or updated afterward.
|
|
30
|
+
- Workspace claims are remembered on the daemon so reconnects and reloads do not reopen a workspace.
|
|
31
|
+
- A failed claim batch is retried once after two seconds, then dropped with a logged error.
|
|
21
32
|
|
|
22
33
|
## What it shows
|
|
23
34
|
|
|
@@ -99,46 +110,27 @@ From npm:
|
|
|
99
110
|
paseo plugin install npm:@omercnet/paseo-agent-crew
|
|
100
111
|
```
|
|
101
112
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
paseo plugin add omercnet/paseo-plugins:agent-crew
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Update a Git installation on either version, or an npm installation on Paseo 0.9, after reviewing
|
|
109
|
-
the proposed revision:
|
|
113
|
+
Update the installed npm package after reviewing the proposed revision:
|
|
110
114
|
|
|
111
115
|
```bash
|
|
112
116
|
paseo plugin update agent-crew
|
|
113
117
|
```
|
|
114
118
|
|
|
115
|
-
From a local checkout on the Paseo daemon host, on Paseo 0.8 or 0.9:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
git clone https://github.com/omercnet/paseo-plugins.git
|
|
119
|
-
cd paseo-plugins/agent-crew
|
|
120
|
-
npm ci
|
|
121
|
-
paseo plugin install "$PWD"
|
|
122
|
-
```
|
|
123
|
-
|
|
124
119
|
Open a workspace, choose **New tab** in Explorer, then select **Agent Crew**. The **Open Agent Crew**
|
|
125
120
|
Command Center action opens it directly.
|
|
126
121
|
|
|
127
122
|
## Develop
|
|
128
123
|
|
|
129
124
|
```bash
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
paseo plugin install /absolute/path/to/paseo-agent-crew
|
|
136
|
-
paseo plugin reload agent-crew
|
|
125
|
+
bun install
|
|
126
|
+
bun run check
|
|
127
|
+
bun run test
|
|
128
|
+
bun run test:coverage
|
|
129
|
+
bun run typecheck
|
|
137
130
|
```
|
|
138
131
|
|
|
139
|
-
The manifest
|
|
140
|
-
|
|
141
|
-
`@getpaseo/plugin`, and `@getpaseo/protocol` to `0.9.0-beta.1` for development. React 19.1 and
|
|
132
|
+
The manifest requires Paseo `^0.9.0`. The project pins `@getpaseo/cli`, `@getpaseo/client`,
|
|
133
|
+
`@getpaseo/plugin`, and `@getpaseo/protocol` to `0.9.0` for development. React 19.1 and
|
|
142
134
|
React Native 0.81 match the host.
|
|
143
135
|
|
|
144
136
|
Release Please maintains versions, changelog entries, component tags, and GitHub releases from
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { PluginClientContext } from "@getpaseo/plugin/client";
|
|
2
|
+
import { claimOpenedWorkspaces, MAX_AUTO_OPEN_CLAIM_BATCH } from "../shared/auto-open";
|
|
3
|
+
import {
|
|
4
|
+
agentCrewSettingsRpc,
|
|
5
|
+
agentCrewSettingsSchema,
|
|
6
|
+
DEFAULT_AUTO_OPEN_EXPLORER,
|
|
7
|
+
} from "../shared/settings";
|
|
8
|
+
|
|
9
|
+
const FLUSH_DELAY_MS = 400;
|
|
10
|
+
const RETRY_DELAY_MS = 2000;
|
|
11
|
+
const PAGE_LIMIT = 200;
|
|
12
|
+
const SETTINGS_POLL_MS = 15_000;
|
|
13
|
+
|
|
14
|
+
type ClaimJob = {
|
|
15
|
+
workspaceIds: string[];
|
|
16
|
+
retried: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type AutoOpenManager = {
|
|
20
|
+
setEnabled(enabled: boolean): void;
|
|
21
|
+
dispose(): void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
async function listWorkspaceIds(paseo: PluginClientContext["paseo"]): Promise<Set<string>> {
|
|
25
|
+
const workspaceIds = new Set<string>();
|
|
26
|
+
const seenCursors = new Set<string>();
|
|
27
|
+
let cursor: string | undefined;
|
|
28
|
+
while (true) {
|
|
29
|
+
const result = await paseo.workspaces.list({
|
|
30
|
+
page: { limit: PAGE_LIMIT, ...(cursor ? { cursor } : {}) },
|
|
31
|
+
});
|
|
32
|
+
for (const workspace of result.entries) workspaceIds.add(workspace.id);
|
|
33
|
+
if (!result.pageInfo.hasMore) return workspaceIds;
|
|
34
|
+
const nextCursor = result.pageInfo.nextCursor ?? undefined;
|
|
35
|
+
if (!nextCursor || seenCursors.has(nextCursor)) return workspaceIds;
|
|
36
|
+
seenCursors.add(nextCursor);
|
|
37
|
+
cursor = nextCursor;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function startAutoOpen(client: PluginClientContext): () => void {
|
|
42
|
+
const pending = new Set<string>();
|
|
43
|
+
const buffered = new Set<string>();
|
|
44
|
+
const jobs: ClaimJob[] = [];
|
|
45
|
+
let known: Set<string> | undefined;
|
|
46
|
+
let flushTimer: NodeJS.Timeout | undefined;
|
|
47
|
+
let removeObserver: (() => void) | undefined;
|
|
48
|
+
let releaseSubscription: (() => Promise<void>) | undefined;
|
|
49
|
+
let pumping = false;
|
|
50
|
+
let closed = false;
|
|
51
|
+
|
|
52
|
+
function schedule(delayMs: number) {
|
|
53
|
+
if (closed || pumping || flushTimer) return;
|
|
54
|
+
flushTimer = setTimeout(() => {
|
|
55
|
+
flushTimer = undefined;
|
|
56
|
+
void pump();
|
|
57
|
+
}, delayMs);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function enqueue(workspaceId: string) {
|
|
61
|
+
if (closed) return;
|
|
62
|
+
pending.add(workspaceId);
|
|
63
|
+
schedule(FLUSH_DELAY_MS);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function observe(workspaceId: string) {
|
|
67
|
+
if (!known) {
|
|
68
|
+
buffered.add(workspaceId);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (known.has(workspaceId)) return;
|
|
72
|
+
known.add(workspaceId);
|
|
73
|
+
enqueue(workspaceId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function drainPending() {
|
|
77
|
+
const workspaceIds = [...pending];
|
|
78
|
+
pending.clear();
|
|
79
|
+
for (let index = 0; index < workspaceIds.length; index += MAX_AUTO_OPEN_CLAIM_BATCH) {
|
|
80
|
+
jobs.push({
|
|
81
|
+
workspaceIds: workspaceIds.slice(index, index + MAX_AUTO_OPEN_CLAIM_BATCH),
|
|
82
|
+
retried: false,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function pump() {
|
|
88
|
+
if (closed || pumping) return;
|
|
89
|
+
pumping = true;
|
|
90
|
+
drainPending();
|
|
91
|
+
while (!closed && jobs.length > 0) {
|
|
92
|
+
const job = jobs.shift();
|
|
93
|
+
if (!job) break;
|
|
94
|
+
try {
|
|
95
|
+
const { claimed } = await client.rpc(claimOpenedWorkspaces, {
|
|
96
|
+
workspaceIds: job.workspaceIds,
|
|
97
|
+
});
|
|
98
|
+
for (const workspaceId of claimed) {
|
|
99
|
+
try {
|
|
100
|
+
client.openPanel("crew", { workspaceId, location: "explorer" });
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(
|
|
103
|
+
`Agent Crew auto-open could not open the panel for workspace ${workspaceId}`,
|
|
104
|
+
error,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error("Agent Crew auto-open claim failed", error);
|
|
110
|
+
if (!job.retried) {
|
|
111
|
+
jobs.unshift({ ...job, retried: true });
|
|
112
|
+
pumping = false;
|
|
113
|
+
schedule(RETRY_DELAY_MS);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
pumping = false;
|
|
119
|
+
if (!closed && pending.size > 0) schedule(FLUSH_DELAY_MS);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
void client.paseo.workspaces
|
|
123
|
+
.list({ subscribe: {} })
|
|
124
|
+
.then(({ subscription }) => {
|
|
125
|
+
if (closed) return subscription.release();
|
|
126
|
+
releaseSubscription = () => subscription.release();
|
|
127
|
+
removeObserver = subscription.subscribe({
|
|
128
|
+
snapshot() {},
|
|
129
|
+
update(message) {
|
|
130
|
+
if (message.type !== "workspace_update" || message.payload.kind !== "upsert") return;
|
|
131
|
+
observe(message.payload.workspace.id);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
return listWorkspaceIds(client.paseo).then((workspaceIds) => {
|
|
135
|
+
if (closed) return;
|
|
136
|
+
known = workspaceIds;
|
|
137
|
+
for (const workspaceId of buffered) observe(workspaceId);
|
|
138
|
+
buffered.clear();
|
|
139
|
+
});
|
|
140
|
+
})
|
|
141
|
+
.catch((error: unknown) => {
|
|
142
|
+
if (!closed) console.error("Agent Crew auto-open observation failed", error);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return () => {
|
|
146
|
+
closed = true;
|
|
147
|
+
pending.clear();
|
|
148
|
+
buffered.clear();
|
|
149
|
+
jobs.length = 0;
|
|
150
|
+
clearTimeout(flushTimer);
|
|
151
|
+
removeObserver?.();
|
|
152
|
+
void releaseSubscription?.();
|
|
153
|
+
releaseSubscription = undefined;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function createAutoOpenManager(client: PluginClientContext): AutoOpenManager {
|
|
158
|
+
let enabled = DEFAULT_AUTO_OPEN_EXPLORER;
|
|
159
|
+
let activeCleanup: (() => void) | undefined;
|
|
160
|
+
let disposed = false;
|
|
161
|
+
let refreshRunning = false;
|
|
162
|
+
let generation = 0;
|
|
163
|
+
const pollTimer = setInterval(() => {
|
|
164
|
+
void refreshEnabled();
|
|
165
|
+
}, SETTINGS_POLL_MS);
|
|
166
|
+
|
|
167
|
+
function syncEnabled(nextEnabled: boolean) {
|
|
168
|
+
if (disposed || nextEnabled === enabled) return;
|
|
169
|
+
enabled = nextEnabled;
|
|
170
|
+
activeCleanup?.();
|
|
171
|
+
activeCleanup = enabled ? startAutoOpen(client) : undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function refreshEnabled() {
|
|
175
|
+
if (disposed || refreshRunning) return;
|
|
176
|
+
refreshRunning = true;
|
|
177
|
+
const requestGeneration = generation;
|
|
178
|
+
try {
|
|
179
|
+
const result = await client.rpc(agentCrewSettingsRpc.read, {});
|
|
180
|
+
if (disposed || requestGeneration !== generation) return;
|
|
181
|
+
if (result.status !== "ready") {
|
|
182
|
+
syncEnabled(DEFAULT_AUTO_OPEN_EXPLORER);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const parsed = agentCrewSettingsSchema.safeParse(result.values);
|
|
186
|
+
syncEnabled(parsed.success ? parsed.data.autoOpenExplorer : DEFAULT_AUTO_OPEN_EXPLORER);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (disposed || requestGeneration !== generation) return;
|
|
189
|
+
console.error("Agent Crew auto-open settings read failed", error);
|
|
190
|
+
syncEnabled(DEFAULT_AUTO_OPEN_EXPLORER);
|
|
191
|
+
} finally {
|
|
192
|
+
refreshRunning = false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
void refreshEnabled();
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
setEnabled(nextEnabled) {
|
|
200
|
+
generation += 1;
|
|
201
|
+
syncEnabled(nextEnabled);
|
|
202
|
+
},
|
|
203
|
+
dispose() {
|
|
204
|
+
disposed = true;
|
|
205
|
+
generation += 1;
|
|
206
|
+
clearInterval(pollTimer);
|
|
207
|
+
activeCleanup?.();
|
|
208
|
+
activeCleanup = undefined;
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
package/client/crew.ts
CHANGED
|
@@ -6,11 +6,46 @@ export type AgentEntry = Awaited<ReturnType<PaseoApi["agents"]["list"]>>["entrie
|
|
|
6
6
|
type AgentSnapshot = AgentEntry["agent"];
|
|
7
7
|
|
|
8
8
|
export function listenToCrewDirectory(paseo: PaseoApi, invalidate: () => void): () => void {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
let closed = false;
|
|
10
|
+
let removeAgentObserver: (() => void) | undefined;
|
|
11
|
+
let removeWorkspaceObserver: (() => void) | undefined;
|
|
12
|
+
let releaseAgentSubscription: (() => Promise<void>) | undefined;
|
|
13
|
+
let releaseWorkspaceSubscription: (() => Promise<void>) | undefined;
|
|
14
|
+
|
|
15
|
+
void paseo.agents
|
|
16
|
+
.list({ subscribe: {} })
|
|
17
|
+
.then(({ subscription }) => {
|
|
18
|
+
if (closed) return subscription.release();
|
|
19
|
+
releaseAgentSubscription = () => subscription.release();
|
|
20
|
+
removeAgentObserver = subscription.subscribe({
|
|
21
|
+
snapshot: invalidate,
|
|
22
|
+
update: invalidate,
|
|
23
|
+
});
|
|
24
|
+
})
|
|
25
|
+
.catch((error: unknown) => {
|
|
26
|
+
if (!closed) console.error("Agent Crew agent observation failed", error);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
void paseo.workspaces
|
|
30
|
+
.list({ subscribe: {} })
|
|
31
|
+
.then(({ subscription }) => {
|
|
32
|
+
if (closed) return subscription.release();
|
|
33
|
+
releaseWorkspaceSubscription = () => subscription.release();
|
|
34
|
+
removeWorkspaceObserver = subscription.subscribe({
|
|
35
|
+
snapshot: invalidate,
|
|
36
|
+
update: invalidate,
|
|
37
|
+
});
|
|
38
|
+
})
|
|
39
|
+
.catch((error: unknown) => {
|
|
40
|
+
if (!closed) console.error("Agent Crew workspace observation failed", error);
|
|
41
|
+
});
|
|
42
|
+
|
|
11
43
|
return () => {
|
|
12
|
-
|
|
13
|
-
|
|
44
|
+
closed = true;
|
|
45
|
+
removeAgentObserver?.();
|
|
46
|
+
removeWorkspaceObserver?.();
|
|
47
|
+
void releaseAgentSubscription?.();
|
|
48
|
+
void releaseWorkspaceSubscription?.();
|
|
14
49
|
};
|
|
15
50
|
}
|
|
16
51
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type PluginSurfaceProps, useSettings } from "@getpaseo/plugin/client";
|
|
2
|
+
import {
|
|
3
|
+
SettingsAction,
|
|
4
|
+
SettingsCard,
|
|
5
|
+
SettingsSection,
|
|
6
|
+
SettingsSwitch,
|
|
7
|
+
} from "@getpaseo/plugin/client/ui";
|
|
8
|
+
import { useMemo } from "react";
|
|
9
|
+
import { Text } from "react-native";
|
|
10
|
+
import { agentCrewSettings } from "../shared/settings";
|
|
11
|
+
|
|
12
|
+
export interface AgentCrewSettingsScreenProps extends PluginSurfaceProps {
|
|
13
|
+
onAutoOpenChange(enabled: boolean): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function AgentCrewSettingsScreen({ onAutoOpenChange, theme }: AgentCrewSettingsScreenProps) {
|
|
17
|
+
const settings = useSettings(agentCrewSettings);
|
|
18
|
+
const styles = useMemo(
|
|
19
|
+
() => ({
|
|
20
|
+
text: { color: theme.colors.foreground },
|
|
21
|
+
error: { color: theme.colors.statusDanger },
|
|
22
|
+
}),
|
|
23
|
+
[theme],
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (settings.status === "loading") return <Text style={styles.text}>Loading settings…</Text>;
|
|
27
|
+
if (settings.status !== "ready") {
|
|
28
|
+
return (
|
|
29
|
+
<SettingsSection title="Agent Crew settings">
|
|
30
|
+
<Text accessibilityRole="alert" style={styles.error}>
|
|
31
|
+
{settings.error}
|
|
32
|
+
</Text>
|
|
33
|
+
<SettingsCard>
|
|
34
|
+
<SettingsAction
|
|
35
|
+
label="Read settings again"
|
|
36
|
+
actionLabel="Reload"
|
|
37
|
+
onPress={settings.reload}
|
|
38
|
+
/>
|
|
39
|
+
{settings.status === "invalid" ? (
|
|
40
|
+
<SettingsAction
|
|
41
|
+
label="Replace invalid data with defaults"
|
|
42
|
+
actionLabel="Reset"
|
|
43
|
+
disabled={settings.saving}
|
|
44
|
+
onPress={async () => {
|
|
45
|
+
if (await settings.reset()) onAutoOpenChange(false);
|
|
46
|
+
}}
|
|
47
|
+
/>
|
|
48
|
+
) : null}
|
|
49
|
+
</SettingsCard>
|
|
50
|
+
</SettingsSection>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<SettingsSection title="Explorer">
|
|
56
|
+
<SettingsCard>
|
|
57
|
+
<SettingsSwitch
|
|
58
|
+
label="Open Agent Crew automatically"
|
|
59
|
+
hint="Open the Explorer panel once for each new workspace observed while enabled"
|
|
60
|
+
value={settings.values.autoOpenExplorer}
|
|
61
|
+
disabled={settings.saving}
|
|
62
|
+
onValueChange={async (autoOpenExplorer) => {
|
|
63
|
+
const saved = await settings.save(
|
|
64
|
+
{ ...settings.values, autoOpenExplorer },
|
|
65
|
+
settings.revision,
|
|
66
|
+
);
|
|
67
|
+
if (saved) onAutoOpenChange(autoOpenExplorer);
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
</SettingsCard>
|
|
71
|
+
{settings.saveError ? (
|
|
72
|
+
<Text accessibilityRole="alert" style={styles.error}>
|
|
73
|
+
{settings.saveError}
|
|
74
|
+
</Text>
|
|
75
|
+
) : null}
|
|
76
|
+
</SettingsSection>
|
|
77
|
+
);
|
|
78
|
+
}
|
package/index.client.tsx
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import type { PluginClientContext } from "@getpaseo/plugin/client";
|
|
1
|
+
import type { PluginClientContext, PluginSurfaceProps } from "@getpaseo/plugin/client";
|
|
2
|
+
import { createAutoOpenManager } from "./client/auto-open";
|
|
2
3
|
import { AgentCrew } from "./client/main";
|
|
4
|
+
import { AgentCrewSettingsScreen } from "./client/settings-screen";
|
|
5
|
+
import { agentCrewSettings } from "./shared/settings";
|
|
3
6
|
|
|
4
7
|
export default function contribute(client: PluginClientContext) {
|
|
5
|
-
client
|
|
8
|
+
const autoOpen = createAutoOpenManager(client);
|
|
9
|
+
|
|
10
|
+
function SettingsSurface(props: PluginSurfaceProps) {
|
|
11
|
+
return <AgentCrewSettingsScreen {...props} onAutoOpenChange={autoOpen.setEnabled} />;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const removeSettings = client.addSettingsScreen({
|
|
15
|
+
id: agentCrewSettings.id,
|
|
16
|
+
title: "Agent Crew settings",
|
|
17
|
+
icon: "Settings",
|
|
18
|
+
Component: SettingsSurface,
|
|
19
|
+
});
|
|
20
|
+
const removeWorkspacePanel = client.addWorkspacePanel({
|
|
6
21
|
id: "crew",
|
|
7
22
|
title: "Agent Crew",
|
|
8
23
|
icon: "Network",
|
|
@@ -10,7 +25,7 @@ export default function contribute(client: PluginClientContext) {
|
|
|
10
25
|
locations: ["explorer"],
|
|
11
26
|
Component: AgentCrew,
|
|
12
27
|
});
|
|
13
|
-
client.addCommandCenterItem({
|
|
28
|
+
const removeOpenCrew = client.addCommandCenterItem({
|
|
14
29
|
id: "open-crew",
|
|
15
30
|
title: "Open Agent Crew",
|
|
16
31
|
icon: "Network",
|
|
@@ -20,5 +35,11 @@ export default function contribute(client: PluginClientContext) {
|
|
|
20
35
|
openPanel("crew", { location: "explorer" });
|
|
21
36
|
},
|
|
22
37
|
});
|
|
23
|
-
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
removeOpenCrew();
|
|
41
|
+
removeWorkspacePanel();
|
|
42
|
+
removeSettings();
|
|
43
|
+
autoOpen.dispose();
|
|
44
|
+
};
|
|
24
45
|
}
|
package/index.server.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
2
|
+
import { createAutoOpenClaimHandler } from "./server/auto-open";
|
|
3
|
+
import { claimOpenedWorkspaces } from "./shared/auto-open";
|
|
4
|
+
import { agentCrewSettings } from "./shared/settings";
|
|
5
|
+
|
|
6
|
+
export default function contribute(server: PluginServerContext) {
|
|
7
|
+
server.registerSettings(agentCrewSettings);
|
|
8
|
+
server.handle(claimOpenedWorkspaces, createAutoOpenClaimHandler());
|
|
9
|
+
return () => {};
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-agent-crew",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A Paseo Explorer panel for visualizing and controlling every managed agent crew in a workspace.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
-
"packageManager": "
|
|
19
|
+
"packageManager": "bun@1.4.2",
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=24"
|
|
22
22
|
},
|
|
@@ -29,39 +29,37 @@
|
|
|
29
29
|
"orchestration"
|
|
30
30
|
],
|
|
31
31
|
"files": [
|
|
32
|
-
"CHANGELOG.md",
|
|
33
32
|
"LICENSE",
|
|
34
33
|
"README.md",
|
|
35
34
|
"client",
|
|
36
|
-
"
|
|
35
|
+
"shared",
|
|
36
|
+
"server",
|
|
37
37
|
"index.client.tsx",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"tsconfig.json"
|
|
38
|
+
"index.server.ts",
|
|
39
|
+
"paseo-plugin.json"
|
|
41
40
|
],
|
|
42
41
|
"scripts": {
|
|
43
42
|
"check": "biome check .",
|
|
44
43
|
"check:write": "biome check --write .",
|
|
45
44
|
"test": "vitest run",
|
|
46
|
-
"package:release": "node scripts/package-release.ts",
|
|
47
45
|
"test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=lcov --coverage.thresholds.lines=90 --coverage.thresholds.functions=85",
|
|
48
46
|
"typecheck": "tsc --noEmit"
|
|
49
47
|
},
|
|
50
48
|
"devDependencies": {
|
|
51
49
|
"@biomejs/biome": "^2.5.10",
|
|
52
|
-
"@getpaseo/cli": "0.9.0
|
|
53
|
-
"@getpaseo/client": "0.9.0
|
|
54
|
-
"@getpaseo/plugin": "0.9.0
|
|
55
|
-
"@getpaseo/protocol": "0.9.0
|
|
50
|
+
"@getpaseo/cli": "0.9.0",
|
|
51
|
+
"@getpaseo/client": "0.9.0",
|
|
52
|
+
"@getpaseo/plugin": "0.9.0",
|
|
53
|
+
"@getpaseo/protocol": "0.9.0",
|
|
56
54
|
"@tanstack/react-query": "^5.102.3",
|
|
57
|
-
"@types/node": "^24.
|
|
58
|
-
"@types/react": "
|
|
55
|
+
"@types/node": "^24.10.1",
|
|
56
|
+
"@types/react": "^19.2.18",
|
|
57
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
59
58
|
"fflate": "^0.8.3",
|
|
60
59
|
"react": "19.1.0",
|
|
61
60
|
"react-native": "0.81.5",
|
|
62
|
-
"typescript": "^7.0.
|
|
63
|
-
"@vitest/coverage-v8": "^5.0.0",
|
|
61
|
+
"typescript": "^7.0.2",
|
|
64
62
|
"vitest": "^5.0.0",
|
|
65
|
-
"zod": "
|
|
63
|
+
"zod": "4.4.3"
|
|
66
64
|
}
|
|
67
65
|
}
|
package/paseo-plugin.json
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import type { RpcInput } from "@getpaseo/plugin";
|
|
5
|
+
import { type claimOpenedWorkspaces, claimUnclaimedWorkspaces } from "../shared/auto-open";
|
|
6
|
+
|
|
7
|
+
export interface AutoOpenStore {
|
|
8
|
+
load(): Promise<ReadonlySet<string>>;
|
|
9
|
+
persist(next: ReadonlySet<string>): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function autoOpenDataFilePath(): string {
|
|
13
|
+
const home = process.env.PASEO_HOME ?? join(homedir(), ".paseo");
|
|
14
|
+
return join(home, "plugin-data", "agent-crew", "auto-open.json");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseWorkspaceIds(raw: string): Set<string> {
|
|
18
|
+
const parsed: unknown = JSON.parse(raw);
|
|
19
|
+
if (!Array.isArray(parsed)) {
|
|
20
|
+
throw new Error("auto-open store must contain an array of workspace IDs");
|
|
21
|
+
}
|
|
22
|
+
const values = new Set<string>();
|
|
23
|
+
for (const value of parsed) {
|
|
24
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
25
|
+
throw new Error("auto-open store must contain only non-empty string workspace IDs");
|
|
26
|
+
}
|
|
27
|
+
values.add(value);
|
|
28
|
+
}
|
|
29
|
+
return values;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createFileAutoOpenStore(filePath: string = autoOpenDataFilePath()): AutoOpenStore {
|
|
33
|
+
return {
|
|
34
|
+
async load() {
|
|
35
|
+
let raw: string;
|
|
36
|
+
try {
|
|
37
|
+
raw = await readFile(filePath, "utf8");
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return new Set();
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return parseWorkspaceIds(raw);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("Agent Crew auto-open store load failed", { filePath, error });
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
async persist(next) {
|
|
50
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
51
|
+
const temporary = `${filePath}.${process.pid}.tmp`;
|
|
52
|
+
await writeFile(temporary, `${JSON.stringify([...next], null, 2)}\n`, "utf8");
|
|
53
|
+
await rename(temporary, filePath);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createMemoryAutoOpenStore(initial: Iterable<string> = []): AutoOpenStore {
|
|
59
|
+
let values = new Set(initial);
|
|
60
|
+
return {
|
|
61
|
+
async load() {
|
|
62
|
+
return new Set(values);
|
|
63
|
+
},
|
|
64
|
+
async persist(next) {
|
|
65
|
+
values = new Set(next);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createAutoOpenClaimHandler(store: AutoOpenStore = createFileAutoOpenStore()) {
|
|
71
|
+
let chain: Promise<unknown> = Promise.resolve();
|
|
72
|
+
return async function handleClaim({ workspaceIds }: RpcInput<typeof claimOpenedWorkspaces>) {
|
|
73
|
+
const run = async () => {
|
|
74
|
+
const opened = await store.load();
|
|
75
|
+
const claimed = claimUnclaimedWorkspaces(opened, workspaceIds);
|
|
76
|
+
if (claimed.length === 0) return { claimed };
|
|
77
|
+
const next = new Set(opened);
|
|
78
|
+
for (const workspaceId of claimed) next.add(workspaceId);
|
|
79
|
+
await store.persist(next);
|
|
80
|
+
return { claimed };
|
|
81
|
+
};
|
|
82
|
+
const result = chain.then(run, run);
|
|
83
|
+
chain = result.catch(() => {});
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const MAX_AUTO_OPEN_CLAIM_BATCH = 1000;
|
|
5
|
+
|
|
6
|
+
export const claimOpenedWorkspaces = defineRpc({
|
|
7
|
+
name: "agent-crew.auto-open.claim",
|
|
8
|
+
input: z.object({
|
|
9
|
+
workspaceIds: z.array(z.string().min(1)).min(1).max(MAX_AUTO_OPEN_CLAIM_BATCH),
|
|
10
|
+
}),
|
|
11
|
+
output: z.object({ claimed: z.array(z.string()) }),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export function claimUnclaimedWorkspaces(
|
|
15
|
+
opened: ReadonlySet<string>,
|
|
16
|
+
candidates: readonly string[],
|
|
17
|
+
): string[] {
|
|
18
|
+
const claimed: string[] = [];
|
|
19
|
+
for (const workspaceId of candidates) {
|
|
20
|
+
if (opened.has(workspaceId) || claimed.includes(workspaceId)) continue;
|
|
21
|
+
claimed.push(workspaceId);
|
|
22
|
+
}
|
|
23
|
+
return claimed;
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineSettings, settingsRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_AUTO_OPEN_EXPLORER = false;
|
|
5
|
+
|
|
6
|
+
export const agentCrewSettingsSchema = z.object({
|
|
7
|
+
autoOpenExplorer: z.boolean().default(DEFAULT_AUTO_OPEN_EXPLORER),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const agentCrewSettings = defineSettings({
|
|
11
|
+
id: "agent-crew-preferences",
|
|
12
|
+
scope: "host",
|
|
13
|
+
version: 1,
|
|
14
|
+
schema: agentCrewSettingsSchema,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const agentCrewSettingsRpc = settingsRpc(agentCrewSettings.id);
|
package/CHANGELOG.md
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## Unreleased
|
|
4
|
-
|
|
5
|
-
### Migration
|
|
6
|
-
|
|
7
|
-
- migrate the plugin to the Paseo 0.8 runtime-entry layout
|
|
8
|
-
- move client modules under `client/` and shared code under `shared/`
|
|
9
|
-
- switch to first-class `parentAgentId` handling with bounded cycles
|
|
10
|
-
- add an explicit pending-permission modal with Allow and Deny actions
|
|
11
|
-
- pin Paseo plugin packages to `0.8.0-beta.1`
|
|
12
|
-
|
|
13
|
-
## [0.3.0](https://github.com/omercnet/paseo-plugins/compare/agent-crew-v0.2.3...agent-crew-v0.3.0) (2026-09-18)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
### Features
|
|
17
|
-
|
|
18
|
-
* **release:** publish plugins to npm ([#80](https://github.com/omercnet/paseo-plugins/issues/80)) ([3c93048](https://github.com/omercnet/paseo-plugins/commit/3c93048cfefda97d8c2bc1631e3428fb64bdad09))
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
### Bug Fixes
|
|
22
|
-
|
|
23
|
-
* **agent-crew:** support Paseo 0.9 beta ([#90](https://github.com/omercnet/paseo-plugins/issues/90)) ([d2d3114](https://github.com/omercnet/paseo-plugins/commit/d2d3114bfcca63e0c184e9be4843a941a1fb56b6))
|
|
24
|
-
|
|
25
|
-
## [0.2.3](https://github.com/omercnet/paseo-plugins/compare/agent-crew-v0.2.2...agent-crew-v0.2.3) (2026-09-10)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
### Bug Fixes
|
|
29
|
-
|
|
30
|
-
* upgrade plugins to Paseo 0.8.0 ([c6b4e40](https://github.com/omercnet/paseo-plugins/commit/c6b4e4081fc9527de0ca6183a3903ff5ec4f93c4))
|
|
31
|
-
* upgrade plugins to Paseo 0.8.0 ([d7ad454](https://github.com/omercnet/paseo-plugins/commit/d7ad4540d2e1875bdf4a4d21f76ded25cac2f3dc))
|
|
32
|
-
|
|
33
|
-
## [0.2.2](https://github.com/omercnet/paseo-agent-crew/compare/v0.2.1...v0.2.2) (2026-09-08)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
### Bug Fixes
|
|
37
|
-
|
|
38
|
-
* derive client API types from host SDK ([#7](https://github.com/omercnet/paseo-agent-crew/issues/7)) ([ffdfacf](https://github.com/omercnet/paseo-agent-crew/commit/ffdfacf9e4f0bd43f84e973176b2b2ffd7fe6350))
|
|
39
|
-
|
|
40
|
-
## [0.2.1](https://github.com/omercnet/paseo-agent-crew/compare/v0.2.0...v0.2.1) (2026-09-08)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
### Bug Fixes
|
|
44
|
-
|
|
45
|
-
* clean up Paseo 0.8 migration ([#5](https://github.com/omercnet/paseo-agent-crew/issues/5)) ([02912fb](https://github.com/omercnet/paseo-agent-crew/commit/02912fbb73956420ae8315bceea7025e71dd7a71))
|
|
46
|
-
|
|
47
|
-
## [0.2.0](https://github.com/omercnet/paseo-agent-crew/compare/v0.1.0...v0.2.0) (2026-09-08)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
### Features
|
|
51
|
-
|
|
52
|
-
* migrate agent crew to paseo 0.8 ([#3](https://github.com/omercnet/paseo-agent-crew/issues/3)) ([3014f1c](https://github.com/omercnet/paseo-agent-crew/commit/3014f1c2f1afb8187cb2ac0e86f2b442bb805911))
|
|
53
|
-
|
|
54
|
-
## 0.1.0 (2026-09-02)
|
|
55
|
-
|
|
56
|
-
### Features
|
|
57
|
-
|
|
58
|
-
- add the workspace-wide Agent Crew Explorer panel
|
|
59
|
-
- organize managed agents into collapsible cross-workspace delegation trees
|
|
60
|
-
- add status filters, search, navigation, and guarded agent controls
|
|
Binary file
|
|
Binary file
|
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2024",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"lib": ["ES2024"],
|
|
7
|
-
"jsx": "react-jsx",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"noEmit": true,
|
|
11
|
-
"esModuleInterop": true,
|
|
12
|
-
"allowSyntheticDefaultImports": true,
|
|
13
|
-
"types": ["node"]
|
|
14
|
-
},
|
|
15
|
-
"include": ["**/*.ts", "**/*.tsx"]
|
|
16
|
-
}
|