@a5c-ai/babysitter-paperclip 0.0.2-staging.02a0ee21

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.
@@ -0,0 +1,313 @@
1
+ /**
2
+ * CLI bridge to the babysitter SDK.
3
+ *
4
+ * Wraps babysitter CLI commands as typed async functions for use by the
5
+ * Paperclip plugin worker. All operations shell out to the `babysitter` CLI
6
+ * to maintain a clean process boundary.
7
+ */
8
+
9
+ import { execFile } from "node:child_process";
10
+ import { promisify } from "node:util";
11
+ import type {
12
+ RunDetail,
13
+ RunEvent,
14
+ PendingEffect,
15
+ PendingBreakpoint,
16
+ } from "./types";
17
+
18
+ const exec = promisify(execFile);
19
+
20
+ const CLI = "babysitter";
21
+
22
+ /** Execute a babysitter CLI command and return parsed JSON. */
23
+ async function runCli<T>(
24
+ args: string[],
25
+ options?: { cwd?: string }
26
+ ): Promise<T> {
27
+ const { stdout } = await exec(CLI, [...args, "--json"], {
28
+ cwd: options?.cwd,
29
+ timeout: 60_000,
30
+ });
31
+ return JSON.parse(stdout) as T;
32
+ }
33
+
34
+ /** Create a new babysitter run. */
35
+ export async function createRun(opts: {
36
+ processId: string;
37
+ entry: string;
38
+ inputsFile: string;
39
+ runsDir?: string;
40
+ cwd?: string;
41
+ }): Promise<{ runId: string; runDir: string }> {
42
+ const args = [
43
+ "run:create",
44
+ "--process-id",
45
+ opts.processId,
46
+ "--entry",
47
+ opts.entry,
48
+ "--inputs",
49
+ opts.inputsFile,
50
+ ];
51
+ if (opts.runsDir) args.push("--runs-dir", opts.runsDir);
52
+ return runCli(args, { cwd: opts.cwd });
53
+ }
54
+
55
+ /** Iterate a run until pending effects or completion. */
56
+ export async function iterateRun(
57
+ runDir: string,
58
+ options?: { cwd?: string }
59
+ ): Promise<{
60
+ status: string;
61
+ nextActions: Array<{
62
+ effectId: string;
63
+ kind: string;
64
+ label: string;
65
+ taskId: string;
66
+ taskDef?: Record<string, unknown>;
67
+ }>;
68
+ metadata: Record<string, unknown>;
69
+ }> {
70
+ return runCli(["run:iterate", runDir], options);
71
+ }
72
+
73
+ /** Get run status. */
74
+ export async function getRunStatus(
75
+ runDir: string,
76
+ options?: { cwd?: string }
77
+ ): Promise<{
78
+ state: string;
79
+ pendingByKind: Record<string, number>;
80
+ completionProof: string | null;
81
+ }> {
82
+ return runCli(["run:status", runDir], options);
83
+ }
84
+
85
+ /** Get run events. */
86
+ export async function getRunEvents(
87
+ runDir: string,
88
+ limit?: number,
89
+ options?: { cwd?: string }
90
+ ): Promise<RunEvent[]> {
91
+ const args = ["run:events", runDir];
92
+ if (limit) args.push("--limit", String(limit));
93
+ return runCli(args, options);
94
+ }
95
+
96
+ /** List pending tasks. */
97
+ export async function listPendingTasks(
98
+ runDir: string,
99
+ options?: { cwd?: string }
100
+ ): Promise<PendingEffect[]> {
101
+ return runCli(["task:list", runDir, "--pending"], options);
102
+ }
103
+
104
+ /** Show a specific task. */
105
+ export async function showTask(
106
+ runDir: string,
107
+ effectId: string,
108
+ options?: { cwd?: string }
109
+ ): Promise<{ effect: PendingEffect; task: Record<string, unknown> | null }> {
110
+ return runCli(["task:show", runDir, effectId], options);
111
+ }
112
+
113
+ /** Post a task result (approve/reject breakpoint or post effect result). */
114
+ export async function postTaskResult(
115
+ runDir: string,
116
+ effectId: string,
117
+ result: {
118
+ status: "ok" | "error";
119
+ value: Record<string, unknown>;
120
+ },
121
+ options?: { cwd?: string }
122
+ ): Promise<{ status: string }> {
123
+ return runCli(
124
+ [
125
+ "task:post",
126
+ runDir,
127
+ effectId,
128
+ "--status",
129
+ result.status,
130
+ "--value-inline",
131
+ JSON.stringify(result.value),
132
+ ],
133
+ options
134
+ );
135
+ }
136
+
137
+ /** Approve a breakpoint. */
138
+ export async function approveBreakpoint(
139
+ runDir: string,
140
+ effectId: string,
141
+ response?: string,
142
+ options?: { cwd?: string }
143
+ ): Promise<{ status: string }> {
144
+ return postTaskResult(
145
+ runDir,
146
+ effectId,
147
+ {
148
+ status: "ok",
149
+ value: { approved: true, response: response ?? "Approved via Paperclip UI" },
150
+ },
151
+ options
152
+ );
153
+ }
154
+
155
+ /** Reject a breakpoint. Note: uses --status ok with approved: false. */
156
+ export async function rejectBreakpoint(
157
+ runDir: string,
158
+ effectId: string,
159
+ feedback: string,
160
+ options?: { cwd?: string }
161
+ ): Promise<{ status: string }> {
162
+ return postTaskResult(
163
+ runDir,
164
+ effectId,
165
+ {
166
+ status: "ok",
167
+ value: { approved: false, feedback },
168
+ },
169
+ options
170
+ );
171
+ }
172
+
173
+ /**
174
+ * Extract pending breakpoints from a run with full metadata.
175
+ *
176
+ * This reads the task.json for each breakpoint effect to get the full payload
177
+ * including question, options, expert routing, tags, and strategy. This is the
178
+ * same metadata that the underlying harness (Claude Code, OpenClaw) uses to
179
+ * present breakpoints to users.
180
+ *
181
+ * The breakpoint lifecycle:
182
+ * 1. Process calls ctx.breakpoint(payload) in SDK
183
+ * 2. SDK writes task.json with kind:"breakpoint" + metadata.payload
184
+ * 3. run:iterate returns "waiting" with the breakpoint as a pending action
185
+ * 4. Underlying harness stop hook detects only-breakpoints-pending → allows exit
186
+ * 5. Paperclip polls run:status, sees pending breakpoint
187
+ * 6. Paperclip reads task.json metadata, surfaces in UI
188
+ * 7. User approves/rejects in Paperclip UI
189
+ * 8. Paperclip posts via task:post --status ok (ALWAYS ok, even for rejection)
190
+ * 9. Next run:iterate resolves the cached breakpoint result
191
+ */
192
+ export async function getPendingBreakpoints(
193
+ runDir: string,
194
+ options?: { cwd?: string }
195
+ ): Promise<PendingBreakpoint[]> {
196
+ const tasks = await listPendingTasks(runDir, options);
197
+ const breakpoints: PendingBreakpoint[] = [];
198
+
199
+ for (const t of tasks) {
200
+ if (t.kind !== "breakpoint") continue;
201
+
202
+ // Try to get full task metadata including question/options
203
+ let question: string | undefined;
204
+ let taskOptions: string[] | undefined;
205
+ let expert: string | string[] | undefined;
206
+ let tags: string[] | undefined;
207
+ let strategy: string | undefined;
208
+ let previousFeedback: string | undefined;
209
+ let attempt: number | undefined;
210
+
211
+ try {
212
+ const detail = await showTask(runDir, t.effectId, options);
213
+ const task = detail.task as Record<string, unknown> | null;
214
+ if (task) {
215
+ const metadata = task.metadata as Record<string, unknown> | undefined;
216
+ const payload = metadata?.payload as Record<string, unknown> | undefined;
217
+ if (payload) {
218
+ question = (payload.question ?? payload.title) as string | undefined;
219
+ taskOptions = payload.options as string[] | undefined;
220
+ expert = payload.expert as string | string[] | undefined;
221
+ tags = payload.tags as string[] | undefined;
222
+ strategy = payload.strategy as string | undefined;
223
+ previousFeedback = payload.previousFeedback as string | undefined;
224
+ attempt = payload.attempt as number | undefined;
225
+ }
226
+ }
227
+ } catch {
228
+ // Task metadata unavailable - use basic info
229
+ }
230
+
231
+ breakpoints.push({
232
+ effectId: t.effectId,
233
+ title: question ?? t.label,
234
+ question,
235
+ options: taskOptions,
236
+ expert,
237
+ tags,
238
+ strategy: strategy as PendingBreakpoint["strategy"],
239
+ previousFeedback,
240
+ attempt,
241
+ requestedAt: t.requestedAt,
242
+ });
243
+ }
244
+
245
+ return breakpoints;
246
+ }
247
+
248
+ /**
249
+ * Check if a run has ONLY breakpoints pending (no other effect types).
250
+ *
251
+ * This mirrors the check in the Claude Code stop hook (claudeCode.ts:578-598):
252
+ * when only breakpoints are pending, the harness allows exit because human
253
+ * action is required. This is the signal that Paperclip should surface
254
+ * breakpoints in the UI.
255
+ */
256
+ export async function hasOnlyBreakpointsPending(
257
+ runDir: string,
258
+ options?: { cwd?: string }
259
+ ): Promise<{ onlyBreakpoints: boolean; breakpointCount: number; otherCount: number }> {
260
+ const status = await getRunStatus(runDir, options);
261
+ const pending = status.pendingByKind;
262
+ const breakpointCount = pending.breakpoint ?? 0;
263
+ const otherCount = Object.entries(pending)
264
+ .filter(([k]) => k !== "breakpoint")
265
+ .reduce((sum, [, v]) => sum + v, 0);
266
+
267
+ return {
268
+ onlyBreakpoints: breakpointCount > 0 && otherCount === 0,
269
+ breakpointCount,
270
+ otherCount,
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Install the babysitter plugin for a specific harness.
276
+ * Delegates to `babysitter harness:install-plugin <name>`.
277
+ */
278
+ export async function installHarnessPlugin(
279
+ harnessName: string,
280
+ options?: { cwd?: string }
281
+ ): Promise<{ success: boolean; output: string }> {
282
+ try {
283
+ const { stdout } = await exec(
284
+ CLI,
285
+ ["harness:install-plugin", harnessName, "--json"],
286
+ { cwd: options?.cwd, timeout: 120_000 }
287
+ );
288
+ return { success: true, output: stdout };
289
+ } catch (err) {
290
+ return {
291
+ success: false,
292
+ output: err instanceof Error ? err.message : String(err),
293
+ };
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Discover available harnesses and their plugin status.
299
+ */
300
+ export async function discoverHarnesses(
301
+ options?: { cwd?: string }
302
+ ): Promise<Array<{ name: string; available: boolean; pluginInstalled?: boolean }>> {
303
+ try {
304
+ return await runCli(["harness:discover"], options);
305
+ } catch {
306
+ return [];
307
+ }
308
+ }
309
+
310
+ /** Build the run directory path from a runs dir and run ID. */
311
+ export function buildRunDir(runsDir: string, runId: string): string {
312
+ return `${runsDir}/${runId}`;
313
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Delegating adapter for Paperclip harness detection.
3
+ *
4
+ * Detects which underlying AI harness a Paperclip agent uses and delegates
5
+ * babysitter adapter operations to the appropriate harness adapter.
6
+ *
7
+ * Detection tiers (in priority order):
8
+ * 1. Agent metadata — read adapterType from agent config (e.g., claude_local -> claude-code)
9
+ * 2. Environment probing — check env vars for known harness signatures
10
+ * 3. Explicit config — fall back to plugin settings
11
+ */
12
+
13
+ import { ADAPTER_TYPE_MAP, type HarnessDetectionResult } from "./types";
14
+
15
+ /**
16
+ * Detect the underlying harness from a Paperclip agent's adapter type.
17
+ *
18
+ * @param adapterType - The agent's adapterType field (e.g., "claude_local")
19
+ * @param pluginConfig - Plugin settings for fallback detection
20
+ * @returns Detection result with harness name and confidence
21
+ */
22
+ export function detectHarness(
23
+ adapterType?: string,
24
+ pluginConfig?: { defaultHarness?: string }
25
+ ): HarnessDetectionResult {
26
+ // Tier 1: Agent metadata inspection (highest confidence)
27
+ if (adapterType && adapterType in ADAPTER_TYPE_MAP) {
28
+ return {
29
+ harnessName: ADAPTER_TYPE_MAP[adapterType],
30
+ detectionTier: "agent-metadata",
31
+ confidence: "high",
32
+ };
33
+ }
34
+
35
+ // Tier 2: Environment variable probing
36
+ const envHarness = detectFromEnvironment();
37
+ if (envHarness) {
38
+ return {
39
+ harnessName: envHarness,
40
+ detectionTier: "env-probe",
41
+ confidence: "medium",
42
+ };
43
+ }
44
+
45
+ // Tier 3: Explicit plugin configuration
46
+ if (pluginConfig?.defaultHarness) {
47
+ return {
48
+ harnessName: pluginConfig.defaultHarness,
49
+ detectionTier: "config",
50
+ confidence: "medium",
51
+ };
52
+ }
53
+
54
+ // Fallback: default to claude-code
55
+ return {
56
+ harnessName: "claude-code",
57
+ detectionTier: "fallback",
58
+ confidence: "low",
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Probe environment variables for known harness signatures.
64
+ */
65
+ function detectFromEnvironment(): string | undefined {
66
+ const env = process.env;
67
+
68
+ // Claude Code sets CLAUDE_CODE_* env vars
69
+ if (env.CLAUDE_CODE_SESSION || env.CLAUDE_CODE_ENTRYPOINT) {
70
+ return "claude-code";
71
+ }
72
+
73
+ // Codex uses CODEX_* vars
74
+ if (env.CODEX_SESSION || env.CODEX_HOME) {
75
+ return "codex";
76
+ }
77
+
78
+ // Gemini CLI
79
+ if (env.GEMINI_CLI_SESSION || env.GOOGLE_GENAI_API_KEY) {
80
+ return "gemini-cli";
81
+ }
82
+
83
+ // Cursor
84
+ if (env.CURSOR_SESSION) {
85
+ return "cursor";
86
+ }
87
+
88
+ return undefined;
89
+ }
90
+
91
+ /**
92
+ * Map a Paperclip adapter type string to a babysitter harness name.
93
+ * Returns undefined if no mapping exists.
94
+ */
95
+ export function mapAdapterType(adapterType: string): string | undefined {
96
+ return ADAPTER_TYPE_MAP[adapterType];
97
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Harness plugin installer for the Paperclip babysitter integration.
3
+ *
4
+ * When the Paperclip plugin detects an underlying harness (e.g., claude_local),
5
+ * it needs the corresponding babysitter harness plugin installed for that
6
+ * harness to handle the stop-hook iteration loop and breakpoint presentation.
7
+ *
8
+ * This module checks whether the babysitter plugin is installed for a given
9
+ * harness and provides installation commands.
10
+ *
11
+ * The underlying harness plugin is what actually drives the orchestration loop:
12
+ * - Claude Code: stop-hook pauses between iterations, allows exit when only
13
+ * breakpoints are pending (user must approve externally)
14
+ * - OpenClaw: agent_end hook fires async iteration, before_prompt_build
15
+ * injects context
16
+ *
17
+ * The Paperclip plugin SUPPLEMENTS this by:
18
+ * - Monitoring run state for pending breakpoints via run:status / task:list
19
+ * - Surfacing breakpoints in the Paperclip dashboard UI
20
+ * - Allowing approve/reject through Paperclip action handlers
21
+ * - Posting results via task:post, which the underlying harness picks up
22
+ * on next iteration
23
+ */
24
+
25
+ import { execFile } from "node:child_process";
26
+ import { promisify } from "node:util";
27
+
28
+ const exec = promisify(execFile);
29
+
30
+ /** Maps babysitter harness names to their plugin install commands. */
31
+ const HARNESS_INSTALL_COMMANDS: Record<string, { check: string[]; install: string[] }> = {
32
+ "claude-code": {
33
+ check: ["babysitter", "harness:discover", "--json"],
34
+ install: ["babysitter", "harness:install-plugin", "claude-code"],
35
+ },
36
+ codex: {
37
+ check: ["babysitter", "harness:discover", "--json"],
38
+ install: ["babysitter", "harness:install-plugin", "codex"],
39
+ },
40
+ openclaw: {
41
+ check: ["babysitter", "harness:discover", "--json"],
42
+ install: ["babysitter", "harness:install-plugin", "openclaw"],
43
+ },
44
+ "gemini-cli": {
45
+ check: ["babysitter", "harness:discover", "--json"],
46
+ install: ["babysitter", "harness:install-plugin", "gemini-cli"],
47
+ },
48
+ cursor: {
49
+ check: ["babysitter", "harness:discover", "--json"],
50
+ install: ["babysitter", "harness:install-plugin", "cursor"],
51
+ },
52
+ "github-copilot": {
53
+ check: ["babysitter", "harness:discover", "--json"],
54
+ install: ["babysitter", "harness:install-plugin", "github-copilot"],
55
+ },
56
+ };
57
+
58
+ /** Marketplace name for the a5c.ai babysitter plugins. */
59
+ const MARKETPLACE_NAME = "a5c.ai";
60
+ const MARKETPLACE_URL = "https://github.com/a5c-ai/babysitter.git";
61
+
62
+ export interface HarnessPluginStatus {
63
+ harnessName: string;
64
+ cliAvailable: boolean;
65
+ pluginInstalled: boolean;
66
+ installCommand?: string;
67
+ }
68
+
69
+ /**
70
+ * Check if the babysitter CLI is available.
71
+ */
72
+ export async function isBabysitterCliAvailable(): Promise<boolean> {
73
+ try {
74
+ await exec("babysitter", ["--version"], { timeout: 10_000 });
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Check if a harness CLI is available and the babysitter plugin is installed.
83
+ */
84
+ export async function checkHarnessPluginStatus(
85
+ harnessName: string
86
+ ): Promise<HarnessPluginStatus> {
87
+ const result: HarnessPluginStatus = {
88
+ harnessName,
89
+ cliAvailable: false,
90
+ pluginInstalled: false,
91
+ };
92
+
93
+ // Check if babysitter CLI is available
94
+ if (!(await isBabysitterCliAvailable())) {
95
+ result.installCommand = "npm install -g @a5c-ai/babysitter-sdk";
96
+ return result;
97
+ }
98
+
99
+ result.cliAvailable = true;
100
+
101
+ // Check harness discovery to see if the harness CLI is available
102
+ try {
103
+ const { stdout } = await exec("babysitter", ["harness:discover", "--json"], {
104
+ timeout: 15_000,
105
+ });
106
+ const discovery = JSON.parse(stdout) as Array<{
107
+ name: string;
108
+ available: boolean;
109
+ pluginInstalled?: boolean;
110
+ }>;
111
+
112
+ const harness = discovery.find(
113
+ (h) => h.name === harnessName || h.name === harnessName.replace("-", "_")
114
+ );
115
+
116
+ if (harness) {
117
+ result.cliAvailable = harness.available;
118
+ result.pluginInstalled = harness.pluginInstalled ?? false;
119
+ }
120
+ } catch {
121
+ // Discovery failed - assume not installed
122
+ }
123
+
124
+ if (!result.pluginInstalled) {
125
+ const cmd = HARNESS_INSTALL_COMMANDS[harnessName];
126
+ result.installCommand = cmd
127
+ ? cmd.install.join(" ")
128
+ : `babysitter harness:install-plugin ${harnessName}`;
129
+ }
130
+
131
+ return result;
132
+ }
133
+
134
+ /**
135
+ * Attempt to install the babysitter plugin for a given harness.
136
+ * Returns success/failure and any output.
137
+ */
138
+ export async function installHarnessPlugin(
139
+ harnessName: string
140
+ ): Promise<{ success: boolean; output: string }> {
141
+ const cmd = HARNESS_INSTALL_COMMANDS[harnessName];
142
+ if (!cmd) {
143
+ return {
144
+ success: false,
145
+ output: `No install command known for harness: ${harnessName}`,
146
+ };
147
+ }
148
+
149
+ try {
150
+ // First ensure marketplace is added
151
+ try {
152
+ await exec("babysitter", [
153
+ "plugin:add-marketplace",
154
+ "--marketplace-url", MARKETPLACE_URL,
155
+ "--global",
156
+ ], { timeout: 30_000 });
157
+ } catch {
158
+ // Marketplace may already exist - continue
159
+ }
160
+
161
+ // Install the plugin
162
+ const [binary, ...args] = cmd.install;
163
+ const { stdout, stderr } = await exec(binary, args, { timeout: 60_000 });
164
+ return { success: true, output: stdout || stderr || "Installed successfully" };
165
+ } catch (err) {
166
+ return {
167
+ success: false,
168
+ output: err instanceof Error ? err.message : String(err),
169
+ };
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Ensure the babysitter SDK CLI is installed.
175
+ * Attempts npm global install, falls back to providing npx instructions.
176
+ */
177
+ export async function ensureBabysitterCli(): Promise<{
178
+ available: boolean;
179
+ method: "global" | "npx" | "missing";
180
+ }> {
181
+ if (await isBabysitterCliAvailable()) {
182
+ return { available: true, method: "global" };
183
+ }
184
+
185
+ // Try installing globally
186
+ try {
187
+ await exec("npm", ["install", "-g", "@a5c-ai/babysitter-sdk"], {
188
+ timeout: 60_000,
189
+ });
190
+ return { available: true, method: "global" };
191
+ } catch {
192
+ // Check npx fallback
193
+ try {
194
+ await exec("npx", ["-y", "@a5c-ai/babysitter-sdk", "--version"], {
195
+ timeout: 30_000,
196
+ });
197
+ return { available: true, method: "npx" };
198
+ } catch {
199
+ return { available: false, method: "missing" };
200
+ }
201
+ }
202
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Babysitter Paperclip plugin manifest.
3
+ *
4
+ * Declares capabilities, event subscriptions, UI slots, and settings
5
+ * for the Babysitter orchestration integration with Paperclip.
6
+ */
7
+
8
+ export const manifest = {
9
+ id: "babysitter",
10
+ displayName: "Babysitter Orchestrator",
11
+ description:
12
+ "Deterministic, event-sourced orchestration for Paperclip agents via Babysitter.",
13
+ version: "0.0.1",
14
+
15
+ entrypoints: {
16
+ worker: "dist/worker.js",
17
+ ui: "dist/ui",
18
+ },
19
+
20
+ capabilities: [
21
+ "events.subscribe",
22
+ "events.emit",
23
+ "plugin.state.read",
24
+ "plugin.state.write",
25
+ "agents.read",
26
+ "agent.tools.register",
27
+ "ui.dashboardWidget.register",
28
+ "ui.detailTab.register",
29
+ "ui.action.register",
30
+ "ui.sidebar.register",
31
+ ],
32
+
33
+ events: {
34
+ subscribe: [
35
+ "agent.run.started",
36
+ "agent.run.finished",
37
+ "agent.run.failed",
38
+ "agent.run.cancelled",
39
+ ],
40
+ emit: [
41
+ "plugin.babysitter.run.created",
42
+ "plugin.babysitter.breakpoint.requested",
43
+ "plugin.babysitter.breakpoint.resolved",
44
+ ],
45
+ },
46
+
47
+ settings: {
48
+ runsDir: {
49
+ type: "string" as const,
50
+ default: ".a5c/runs",
51
+ displayName: "Runs Directory",
52
+ description: "Directory where babysitter run data is stored.",
53
+ },
54
+ autoIterate: {
55
+ type: "boolean" as const,
56
+ default: true,
57
+ displayName: "Auto-Iterate",
58
+ description:
59
+ "Automatically iterate runs when effects are resolved.",
60
+ },
61
+ maxIterations: {
62
+ type: "number" as const,
63
+ default: 256,
64
+ displayName: "Max Iterations",
65
+ description: "Maximum orchestration iterations per run.",
66
+ },
67
+ breakpointTimeout: {
68
+ type: "number" as const,
69
+ default: 3600000,
70
+ displayName: "Breakpoint Timeout (ms)",
71
+ description:
72
+ "Time to wait for breakpoint approval before timing out (default 1 hour).",
73
+ },
74
+ },
75
+
76
+ ui: {
77
+ slots: [
78
+ {
79
+ type: "dashboardWidget",
80
+ id: "babysitter-dashboard",
81
+ displayName: "Babysitter Runs",
82
+ exportName: "BabysitterDashboard",
83
+ },
84
+ {
85
+ type: "detailTab",
86
+ id: "babysitter-run-detail",
87
+ displayName: "Babysitter Run",
88
+ exportName: "RunDetailTab",
89
+ entityTypes: ["agent"],
90
+ },
91
+ {
92
+ type: "sidebarPanel",
93
+ id: "babysitter-sidebar",
94
+ displayName: "Babysitter",
95
+ exportName: "BabysitterSidebar",
96
+ },
97
+ ],
98
+ },
99
+ } as const;
100
+
101
+ export default manifest;