@omercnet/paseo-agent-crew 0.3.0-next.133.1 → 0.3.0-next.137.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 +10 -0
- package/client/auto-open.ts +211 -0
- package/client/settings-screen.tsx +78 -0
- package/index.client.tsx +25 -4
- package/index.server.ts +10 -0
- package/package.json +4 -1
- package/server/auto-open.ts +86 -0
- package/shared/auto-open.ts +24 -0
- package/shared/settings.ts +17 -0
package/README.md
CHANGED
|
@@ -20,6 +20,16 @@ that no private organization names remained in the rendered page.
|
|
|
20
20
|
|
|
21
21
|

|
|
22
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.
|
|
32
|
+
|
|
23
33
|
## What it shows
|
|
24
34
|
|
|
25
35
|
- Every non-archived managed agent in the current workspace, organized into orchestration trees.
|
|
@@ -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
|
+
}
|
|
@@ -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.0-next.
|
|
3
|
+
"version": "0.3.0-next.137.1",
|
|
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",
|
|
@@ -32,7 +32,10 @@
|
|
|
32
32
|
"LICENSE",
|
|
33
33
|
"README.md",
|
|
34
34
|
"client",
|
|
35
|
+
"shared",
|
|
36
|
+
"server",
|
|
35
37
|
"index.client.tsx",
|
|
38
|
+
"index.server.ts",
|
|
36
39
|
"paseo-plugin.json"
|
|
37
40
|
],
|
|
38
41
|
"scripts": {
|
|
@@ -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);
|