paseo-bm-plugin 0.0.0-placeholder.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/LICENSE +21 -0
- package/README.md +53 -0
- package/client/agent-tree.ts +308 -0
- package/client/answer-state.ts +62 -0
- package/client/bead-chips.tsx +147 -0
- package/client/beads-header-button.ts +108 -0
- package/client/beads-model.ts +581 -0
- package/client/beads-screen.tsx +516 -0
- package/client/beads-tab.tsx +58 -0
- package/client/chat-card.tsx +636 -0
- package/client/chat-cards.ts +1038 -0
- package/client/dashboard-actions.tsx +255 -0
- package/client/dashboard-model.ts +947 -0
- package/client/dashboard-view.ts +215 -0
- package/client/dashboard.tsx +318 -0
- package/client/launch-manager.ts +323 -0
- package/client/launcher.tsx +516 -0
- package/client/markdown-view.tsx +112 -0
- package/client/markdown.ts +145 -0
- package/client/settings.tsx +104 -0
- package/client/setup-model.ts +552 -0
- package/client/setup-screen.tsx +913 -0
- package/client/slot.ts +47 -0
- package/client/tree.tsx +204 -0
- package/client/ui.tsx +262 -0
- package/client/waiting-pills-model.ts +156 -0
- package/client/waiting-pills.tsx +201 -0
- package/index.client.tsx +232 -0
- package/index.server.ts +168 -0
- package/package.json +35 -0
- package/paseo-plugin.json +6 -0
- package/roles/manager.md +181 -0
- package/roles/reviewer.md +160 -0
- package/roles/worker.md +407 -0
- package/server/agent-labels.ts +194 -0
- package/server/agent-role.ts +102 -0
- package/server/answer-marks.ts +120 -0
- package/server/bead-actions.ts +88 -0
- package/server/bead-work.ts +80 -0
- package/server/beads-store.ts +342 -0
- package/server/bm-report.ts +433 -0
- package/server/chat-peers.ts +65 -0
- package/server/chat-rpc.ts +122 -0
- package/server/chat-waiting.ts +182 -0
- package/server/collector.ts +629 -0
- package/server/config-writer.ts +222 -0
- package/server/cost.ts +88 -0
- package/server/dashboard-rpc.ts +662 -0
- package/server/fallback-detect.ts +183 -0
- package/server/fallback-handover.ts +365 -0
- package/server/fallback-manager.ts +170 -0
- package/server/fallback-reviewer.ts +198 -0
- package/server/fallback-rpc.ts +306 -0
- package/server/fallback-settings.ts +322 -0
- package/server/fallback-state.ts +518 -0
- package/server/fallback-switch.ts +191 -0
- package/server/fallback-wait.ts +188 -0
- package/server/format-check.ts +352 -0
- package/server/install-home.ts +187 -0
- package/server/live-timeline.ts +129 -0
- package/server/manager-instructions.ts +9 -0
- package/server/manager.ts +647 -0
- package/server/model-costs.ts +238 -0
- package/server/notice-queue.ts +315 -0
- package/server/notices.ts +81 -0
- package/server/paseo-cli.ts +115 -0
- package/server/provider-id.ts +12 -0
- package/server/review-budget.ts +208 -0
- package/server/reviewer-instructions.ts +9 -0
- package/server/role-choices.ts +161 -0
- package/server/role-extras.ts +270 -0
- package/server/role-hook.ts +347 -0
- package/server/role-mode.ts +397 -0
- package/server/role-settings-rpc.ts +325 -0
- package/server/roles.ts +96 -0
- package/server/settings-notices.ts +112 -0
- package/server/setup-rpc.ts +70 -0
- package/server/setup-skills.ts +121 -0
- package/server/setup-tools.ts +162 -0
- package/server/shell.ts +68 -0
- package/server/stop-propagation.ts +365 -0
- package/server/tools-check.ts +118 -0
- package/server/trace-store.ts +1137 -0
- package/server/traces.ts +1356 -0
- package/server/worker-instructions.ts +9 -0
- package/server/workflow-steps.ts +422 -0
- package/shared/bead-ids.ts +25 -0
- package/shared/bm-fallback.ts +91 -0
- package/shared/bm-format.ts +424 -0
- package/shared/bm-questions.ts +213 -0
- package/shared/bm-report.ts +433 -0
- package/shared/contracts.ts +1371 -0
- package/shared/fallback-patterns.ts +201 -0
- package/shared/fallback.ts +46 -0
- package/shared/new-request.ts +20 -0
- package/shared/order.ts +22 -0
- package/shared/prices.ts +65 -0
- package/shared/settings.ts +57 -0
- package/shared/sole-worker.ts +20 -0
- package/shared/version.ts +6 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch logic behind the "Open Beads Manager" entry points (WP-113, design
|
|
3
|
+
* §2.2, §5, §9.3). Pure and injectable: no React, no React Native, no JSX, so
|
|
4
|
+
* it is testable without a renderer. `launcher.tsx` renders from it.
|
|
5
|
+
*
|
|
6
|
+
* The client never finds or creates a Manager itself: it calls `manager.ensure`
|
|
7
|
+
* for one workspace and opens the returned `agentId`. De-duplication (one
|
|
8
|
+
* Manager per workspace, REQ-020b) is the server's job.
|
|
9
|
+
*
|
|
10
|
+
* Paseo 0.8 facts this is built on (checked against @getpaseo/plugin 0.8.0
|
|
11
|
+
* `dist/client/contracts.d.ts`, not guessed):
|
|
12
|
+
* - A sidebar item can only point at a surface, and a surface receives no
|
|
13
|
+
* workspace id. It does receive the optional `navigation.openAgent`.
|
|
14
|
+
* - A workspace Command Center item receives the `workspace` snapshot, `rpc`
|
|
15
|
+
* and `openSurface(id)`, but no way to open an agent.
|
|
16
|
+
* So the Command Center item queues its workspace with `selectFromCommandCenter`
|
|
17
|
+
* and opens the surface, which runs the launch; opened from the sidebar the
|
|
18
|
+
* surface shows Setup, and its Workspaces list opens a workspace's Manager.
|
|
19
|
+
*/
|
|
20
|
+
import type { PluginTheme } from "@getpaseo/plugin";
|
|
21
|
+
import type { z } from "zod";
|
|
22
|
+
import type { managerEnsureRpc } from "../shared/contracts";
|
|
23
|
+
import type { Tone } from "./dashboard-model";
|
|
24
|
+
import { createSlot, type Slot } from "./slot";
|
|
25
|
+
|
|
26
|
+
/** Surface id shared by the sidebar item and the Command Center item. */
|
|
27
|
+
export const LAUNCHER_SURFACE_ID = "beads-manager";
|
|
28
|
+
|
|
29
|
+
/** Lucide icon name (lucide.dev/icons/bot). */
|
|
30
|
+
export const LAUNCHER_ICON = "Bot";
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Launch controller.
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/** What `manager.ensure` answers, as its contract says (delta 20260918f S6). */
|
|
37
|
+
export type EnsureManagerOutput = z.infer<typeof managerEnsureRpc.output>;
|
|
38
|
+
|
|
39
|
+
export interface LaunchDeps {
|
|
40
|
+
/** Calls the `manager.ensure` RPC. */
|
|
41
|
+
ensure: (input: { workspaceId: string }) => Promise<EnsureManagerOutput>;
|
|
42
|
+
/** Opens an agent in the Paseo client. */
|
|
43
|
+
openAgent: (input: { agentId: string }) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type LauncherState =
|
|
47
|
+
| { status: "idle" }
|
|
48
|
+
| { status: "pending"; workspaceId: string }
|
|
49
|
+
| {
|
|
50
|
+
status: "opened";
|
|
51
|
+
workspaceId: string;
|
|
52
|
+
agentId: string;
|
|
53
|
+
created: boolean;
|
|
54
|
+
otherManagerIds: string[];
|
|
55
|
+
modeNotice: string | null;
|
|
56
|
+
/** Added by delta 20260921 §4.2.4; absent in a state built before it. */
|
|
57
|
+
toolsNotice?: string | null;
|
|
58
|
+
}
|
|
59
|
+
| { status: "error"; workspaceId: string; code: string | null; message: string };
|
|
60
|
+
|
|
61
|
+
export type LaunchOutcome = "opened" | "error" | "busy";
|
|
62
|
+
|
|
63
|
+
export interface ManagerLauncher {
|
|
64
|
+
getState(): LauncherState;
|
|
65
|
+
subscribe(listener: () => void): () => void;
|
|
66
|
+
/**
|
|
67
|
+
* Ensures and opens the Manager of `workspaceId`. While a launch is pending
|
|
68
|
+
* every further call returns "busy" without calling the RPC again.
|
|
69
|
+
*/
|
|
70
|
+
launch(workspaceId: string, deps: LaunchDeps): Promise<LaunchOutcome>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const ERROR_CODE_PATTERN = /^\s*(E_[A-Z0-9_]+)\b/;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Registry code at the start of an RPC error message (`E_PROVIDER_UNAVAILABLE: ...`),
|
|
77
|
+
* or null when the server sent none.
|
|
78
|
+
*/
|
|
79
|
+
export function errorCodeOf(error: unknown): string | null {
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
return ERROR_CODE_PATTERN.exec(message)?.[1] ?? null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function errorMessageOf(error: unknown): string {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
return message.trim() || "Unknown error";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createManagerLauncher(): ManagerLauncher {
|
|
90
|
+
let state: LauncherState = { status: "idle" };
|
|
91
|
+
const listeners = new Set<() => void>();
|
|
92
|
+
|
|
93
|
+
const set = (next: LauncherState) => {
|
|
94
|
+
state = next;
|
|
95
|
+
for (const listener of listeners) listener();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
getState: () => state,
|
|
100
|
+
subscribe(listener) {
|
|
101
|
+
listeners.add(listener);
|
|
102
|
+
return () => {
|
|
103
|
+
listeners.delete(listener);
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
async launch(workspaceId, deps) {
|
|
107
|
+
if (state.status === "pending") return "busy";
|
|
108
|
+
set({ status: "pending", workspaceId });
|
|
109
|
+
try {
|
|
110
|
+
const result = await deps.ensure({ workspaceId });
|
|
111
|
+
deps.openAgent({ agentId: result.agentId });
|
|
112
|
+
set({
|
|
113
|
+
status: "opened",
|
|
114
|
+
workspaceId,
|
|
115
|
+
agentId: result.agentId,
|
|
116
|
+
created: result.created,
|
|
117
|
+
otherManagerIds: [...result.otherManagerIds],
|
|
118
|
+
modeNotice: result.modeNotice ?? null,
|
|
119
|
+
toolsNotice: result.toolsNotice ?? null,
|
|
120
|
+
});
|
|
121
|
+
return "opened";
|
|
122
|
+
} catch (error) {
|
|
123
|
+
set({
|
|
124
|
+
status: "error",
|
|
125
|
+
workspaceId,
|
|
126
|
+
code: errorCodeOf(error),
|
|
127
|
+
message: errorMessageOf(error),
|
|
128
|
+
});
|
|
129
|
+
return "error";
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Hand-off from the Command Center item to the surface.
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
/** One launcher and one request queue per loaded client bundle. */
|
|
140
|
+
export const managerLauncher = createManagerLauncher();
|
|
141
|
+
/** The workspace a Command Center selection asked for; the surface takes it. */
|
|
142
|
+
export const launchRequests: Slot<string> = createSlot<string>();
|
|
143
|
+
|
|
144
|
+
/** Body of the workspace Command Center item. */
|
|
145
|
+
export function selectFromCommandCenter(
|
|
146
|
+
context: { workspace: { id: string }; openSurface(id: string): void },
|
|
147
|
+
requests: Slot<string> = launchRequests,
|
|
148
|
+
): void {
|
|
149
|
+
requests.put(context.workspace.id);
|
|
150
|
+
context.openSurface(LAUNCHER_SURFACE_ID);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Runs the pending Command Center request, if any. Without `openAgent` (older
|
|
155
|
+
* Paseo hosts) the request stays queued and nothing is called. While another
|
|
156
|
+
* launch is pending it stays queued too: taking it now would only get "busy"
|
|
157
|
+
* back and lose it. The surface runs this again when the launcher's state
|
|
158
|
+
* changes.
|
|
159
|
+
*/
|
|
160
|
+
export async function runPendingRequest(
|
|
161
|
+
requests: Slot<string>,
|
|
162
|
+
launcher: ManagerLauncher,
|
|
163
|
+
deps: Pick<LaunchDeps, "ensure"> & { openAgent?: LaunchDeps["openAgent"] },
|
|
164
|
+
): Promise<LaunchOutcome | null> {
|
|
165
|
+
if (!deps.openAgent) return null;
|
|
166
|
+
if (launcher.getState().status === "pending") return null;
|
|
167
|
+
const workspaceId = requests.take();
|
|
168
|
+
if (workspaceId === null) return null;
|
|
169
|
+
return launcher.launch(workspaceId, { ensure: deps.ensure, openAgent: deps.openAgent });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Presentation: user-facing text (English) and styles from theme.colors.
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
/** The tones a launcher notice uses; colours come from `toneColor` in `dashboard-model.ts`. */
|
|
177
|
+
export type NoticeTone = Extract<Tone, "muted" | "warning" | "danger">;
|
|
178
|
+
|
|
179
|
+
export interface LauncherNotice {
|
|
180
|
+
tone: NoticeTone;
|
|
181
|
+
text: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function describeLauncherState(state: LauncherState): LauncherNotice[] {
|
|
185
|
+
switch (state.status) {
|
|
186
|
+
case "idle":
|
|
187
|
+
return [];
|
|
188
|
+
case "pending":
|
|
189
|
+
return [{ tone: "muted", text: "Opening Beads Manager…" }];
|
|
190
|
+
case "opened": {
|
|
191
|
+
const notices: LauncherNotice[] = [
|
|
192
|
+
{
|
|
193
|
+
tone: "muted",
|
|
194
|
+
text: state.created
|
|
195
|
+
? "Started a new Beads Manager for this workspace."
|
|
196
|
+
: "Reopened the existing Beads Manager for this workspace.",
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
const others = state.otherManagerIds.length;
|
|
200
|
+
if (others > 0) {
|
|
201
|
+
notices.push({
|
|
202
|
+
tone: "warning",
|
|
203
|
+
text: `This workspace has ${others} other live Beads Manager${others === 1 ? "" : "s"} (${state.otherManagerIds.join(", ")}). The newest one was opened; nothing was archived or deleted.`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// The chat is already open: this only explains why the Manager may still ask for permission.
|
|
207
|
+
if (state.modeNotice !== null) notices.push({ tone: "warning", text: state.modeNotice });
|
|
208
|
+
// Delta 20260921 §4.2.4: without Paseo tools this Manager cannot run a request.
|
|
209
|
+
const toolsNotice = state.toolsNotice ?? null;
|
|
210
|
+
if (toolsNotice !== null) notices.push({ tone: "warning", text: toolsNotice });
|
|
211
|
+
return notices;
|
|
212
|
+
}
|
|
213
|
+
case "error":
|
|
214
|
+
return [
|
|
215
|
+
{
|
|
216
|
+
tone: "danger",
|
|
217
|
+
text: state.code
|
|
218
|
+
? `Could not open Beads Manager (${state.code}). ${state.message}`
|
|
219
|
+
: `Could not open Beads Manager. ${state.message}`,
|
|
220
|
+
},
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Said when the host cannot open an agent from a plugin (no `navigation.openAgent`). */
|
|
226
|
+
export const OLD_HOST_WARNING = "This Paseo version cannot open agents from plugins. Update Paseo to use this launcher.";
|
|
227
|
+
|
|
228
|
+
/** One line of the status strip at the top of the Beads Manager surface. */
|
|
229
|
+
export interface StatusLine {
|
|
230
|
+
key: string;
|
|
231
|
+
text: string;
|
|
232
|
+
tone: NoticeTone;
|
|
233
|
+
/** Only a slash command's notice can be dismissed; the others follow the state. */
|
|
234
|
+
dismissable: boolean;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Everything the Beads Manager surface has to say before its content, in order:
|
|
239
|
+
* what a slash command reported, a host too old to open agents, then the state
|
|
240
|
+
* of the last Manager launch. The surface draws these on BOTH its main screen
|
|
241
|
+
* (Setup) and the workspace list (delta 20260918e §4.2): `/bm-worker-stop-all`
|
|
242
|
+
* opens the surface on the main screen, and a Command Center launch can fail
|
|
243
|
+
* while any view is showing.
|
|
244
|
+
*/
|
|
245
|
+
export function launcherStatusLines(input: {
|
|
246
|
+
commandNotice: string | null;
|
|
247
|
+
canOpenAgents: boolean;
|
|
248
|
+
state: LauncherState;
|
|
249
|
+
}): StatusLine[] {
|
|
250
|
+
const lines: StatusLine[] = [];
|
|
251
|
+
if (input.commandNotice !== null) {
|
|
252
|
+
lines.push({ key: "command-notice", text: input.commandNotice, tone: "muted", dismissable: true });
|
|
253
|
+
}
|
|
254
|
+
if (!input.canOpenAgents) {
|
|
255
|
+
lines.push({ key: "old-host", text: OLD_HOST_WARNING, tone: "warning", dismissable: false });
|
|
256
|
+
}
|
|
257
|
+
describeLauncherState(input.state).forEach((notice, index) => {
|
|
258
|
+
lines.push({ key: `launch-${index}`, text: notice.text, tone: notice.tone, dismissable: false });
|
|
259
|
+
});
|
|
260
|
+
return lines;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function launcherStyles(theme: PluginTheme, compact: boolean) {
|
|
264
|
+
return {
|
|
265
|
+
screen: { flex: 1, backgroundColor: theme.colors.surface0 },
|
|
266
|
+
content: { padding: compact ? 16 : 24, gap: compact ? 8 : 12 },
|
|
267
|
+
title: { color: theme.colors.foreground, fontSize: compact ? 20 : 24, fontWeight: "600" as const },
|
|
268
|
+
body: { color: theme.colors.foregroundMuted, fontSize: 14 },
|
|
269
|
+
row: {
|
|
270
|
+
flexDirection: compact ? ("column" as const) : ("row" as const),
|
|
271
|
+
alignItems: compact ? ("stretch" as const) : ("center" as const),
|
|
272
|
+
justifyContent: "space-between" as const,
|
|
273
|
+
gap: compact ? 8 : 12,
|
|
274
|
+
padding: compact ? 12 : 14,
|
|
275
|
+
borderRadius: 10,
|
|
276
|
+
borderWidth: 1,
|
|
277
|
+
borderColor: theme.colors.border,
|
|
278
|
+
backgroundColor: theme.colors.surface1,
|
|
279
|
+
},
|
|
280
|
+
rowTitle: { color: theme.colors.foreground, fontSize: 15, fontWeight: "600" as const },
|
|
281
|
+
rowSubtitle: { color: theme.colors.foregroundMuted, fontSize: 12 },
|
|
282
|
+
stats: { flexDirection: "row" as const, flexWrap: "wrap" as const, gap: 12, marginTop: 2 },
|
|
283
|
+
stat: { flexDirection: "row" as const, alignItems: "center" as const, gap: 4 },
|
|
284
|
+
statText: { fontSize: 12, fontWeight: "600" as const },
|
|
285
|
+
/** The three actions stay on one line, on a phone too. */
|
|
286
|
+
actions: { flexDirection: "row" as const, gap: 6 },
|
|
287
|
+
action: {
|
|
288
|
+
flexGrow: compact ? 1 : 0,
|
|
289
|
+
flexDirection: "row" as const,
|
|
290
|
+
alignItems: "center" as const,
|
|
291
|
+
justifyContent: "center" as const,
|
|
292
|
+
gap: 6,
|
|
293
|
+
paddingVertical: compact ? 7 : 6,
|
|
294
|
+
paddingHorizontal: 10,
|
|
295
|
+
borderRadius: 8,
|
|
296
|
+
borderWidth: 1,
|
|
297
|
+
borderColor: theme.colors.border,
|
|
298
|
+
backgroundColor: theme.colors.surface2,
|
|
299
|
+
},
|
|
300
|
+
iconButton: {
|
|
301
|
+
padding: 8,
|
|
302
|
+
borderRadius: 8,
|
|
303
|
+
borderWidth: 1,
|
|
304
|
+
borderColor: theme.colors.border,
|
|
305
|
+
backgroundColor: theme.colors.surface1,
|
|
306
|
+
},
|
|
307
|
+
actionPrimary: { backgroundColor: theme.colors.accent, borderColor: theme.colors.accent },
|
|
308
|
+
actionText: { color: theme.colors.foreground, fontSize: 13 },
|
|
309
|
+
actionPrimaryText: { color: theme.colors.accentForeground, fontSize: 13 },
|
|
310
|
+
button: {
|
|
311
|
+
paddingVertical: 10,
|
|
312
|
+
paddingHorizontal: 14,
|
|
313
|
+
borderRadius: 8,
|
|
314
|
+
alignItems: "center" as const,
|
|
315
|
+
backgroundColor: theme.colors.accent,
|
|
316
|
+
},
|
|
317
|
+
buttonDisabled: { opacity: 0.5 },
|
|
318
|
+
buttonText: { color: theme.colors.accentForeground, fontSize: 14 },
|
|
319
|
+
notice: { fontSize: 13 },
|
|
320
|
+
footer: { color: theme.colors.foregroundMuted, fontSize: 12 },
|
|
321
|
+
spinner: { color: theme.colors.foregroundMuted },
|
|
322
|
+
};
|
|
323
|
+
}
|