@a5c-ai/babysitter-omp 0.1.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 ADDED
@@ -0,0 +1,80 @@
1
+ # @a5c-ai/babysitter-omp
2
+
3
+ Babysitter integration plugin for `oh-my-pi`. This package owns the
4
+ oh-my-pi-specific install surface, command docs, and skill wiring while sharing
5
+ compatible runtime internals with the upstream Pi-family.
6
+
7
+ ## Integration Model
8
+
9
+ The oh-my-pi plugin keeps Babysitter as the orchestration layer for omp
10
+ sessions:
11
+
12
+ - session lifecycle hooks prepare and bind run state
13
+ - omp commands start or resume runs
14
+ - the harness advances one orchestration phase at a time
15
+ - the plugin runtime records effect outcomes and updates the run state
16
+ - completion requires the emitted `completionProof`
17
+
18
+ ## Installation
19
+
20
+ Install the published oh-my-pi plugin globally:
21
+
22
+ ```bash
23
+ npx @a5c-ai/babysitter-omp install
24
+ ```
25
+
26
+ Install into a specific workspace instead of the user profile:
27
+
28
+ ```bash
29
+ npx @a5c-ai/babysitter-omp install --workspace /path/to/repo
30
+ ```
31
+
32
+ Or use the Babysitter SDK helper:
33
+
34
+ ```bash
35
+ babysitter harness:install-plugin oh-my-pi
36
+ babysitter harness:install-plugin oh-my-pi --workspace /path/to/repo
37
+ ```
38
+
39
+ This package installs under:
40
+
41
+ ```text
42
+ ~/.omp/plugins/babysitter
43
+ ```
44
+
45
+ If the workspace does not already have an active process-library binding, the
46
+ installer bootstraps the shared global SDK process library automatically:
47
+
48
+ ```bash
49
+ babysitter process-library:active --json
50
+ ```
51
+
52
+ ## Commands
53
+
54
+ The plugin exposes oh-my-pi-facing Babysitter commands such as:
55
+
56
+ - `/babysitter:call`
57
+ - `/babysitter:status`
58
+ - `/babysitter:resume`
59
+ - `/babysitter:doctor`
60
+
61
+ ## Troubleshooting
62
+
63
+ - `babysitter harness:discover --json` is the supported way to verify whether
64
+ `oh-my-pi` is installed from the current environment.
65
+ - If discovery reports `oh-my-pi` as installed but a direct invocation fails,
66
+ validate the current shell `PATH` first with `where omp` on Windows.
67
+
68
+ ## Tests
69
+
70
+ ```bash
71
+ cd plugins/babysitter-omp
72
+ npm test
73
+ npm run test:integration
74
+ npm run test:harness
75
+ npm run test:tui
76
+ ```
77
+
78
+ ## License
79
+
80
+ MIT
package/bin/cli.cjs ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+
7
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
8
+
9
+ function printUsage() {
10
+ console.error([
11
+ 'Usage:',
12
+ ' babysitter-omp install [--global]',
13
+ ' babysitter-omp install --workspace [path]',
14
+ ' babysitter-omp uninstall [--global]',
15
+ ' babysitter-omp uninstall --workspace [path]',
16
+ ].join('\n'));
17
+ }
18
+
19
+ function parseArgs(argv) {
20
+ let workspace = null;
21
+
22
+ for (let i = 0; i < argv.length; i += 1) {
23
+ const arg = argv[i];
24
+ if (arg === '--workspace') {
25
+ const next = argv[i + 1];
26
+ if (next && !next.startsWith('-')) {
27
+ workspace = path.resolve(next);
28
+ i += 1;
29
+ } else {
30
+ workspace = process.cwd();
31
+ }
32
+ continue;
33
+ }
34
+ if (arg === '--global') {
35
+ workspace = null;
36
+ continue;
37
+ }
38
+ throw new Error(`unknown argument: ${arg}`);
39
+ }
40
+
41
+ return { workspace };
42
+ }
43
+
44
+ function runNodeScript(scriptPath, args) {
45
+ const result = spawnSync(process.execPath, [scriptPath, ...args], {
46
+ cwd: process.cwd(),
47
+ stdio: 'inherit',
48
+ env: process.env,
49
+ });
50
+ process.exitCode = result.status ?? 1;
51
+ }
52
+
53
+ function main() {
54
+ const [command, ...rest] = process.argv.slice(2);
55
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
56
+ printUsage();
57
+ process.exitCode = command ? 0 : 1;
58
+ return;
59
+ }
60
+
61
+ if (command !== 'install' && command !== 'uninstall') {
62
+ printUsage();
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+
67
+ const parsed = parseArgs(rest);
68
+ const args = [];
69
+ if (parsed.workspace) {
70
+ args.push('--workspace', parsed.workspace);
71
+ } else {
72
+ args.push('--global');
73
+ }
74
+
75
+ runNodeScript(path.join(PACKAGE_ROOT, 'bin', `${command}.cjs`), args);
76
+ }
77
+
78
+ main();
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const { spawnSync } = require('child_process');
8
+
9
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
10
+ const INSTALL_ENTRIES = [
11
+ { source: 'package.json', required: true },
12
+ { source: 'extensions', required: true },
13
+ { source: 'skills', required: true },
14
+ { source: 'commands', required: true },
15
+ { source: 'scripts', required: true },
16
+ ];
17
+
18
+ function parseArgs(argv) {
19
+ const args = {
20
+ workspace: null,
21
+ };
22
+ for (let i = 2; i < argv.length; i += 1) {
23
+ if (argv[i] === '--workspace' && argv[i + 1]) {
24
+ args.workspace = path.resolve(argv[++i]);
25
+ } else if (argv[i] === '--global') {
26
+ args.workspace = null;
27
+ } else {
28
+ throw new Error(`unknown argument: ${argv[i]}`);
29
+ }
30
+ }
31
+ return args;
32
+ }
33
+
34
+ function getGlobalStateDir() {
35
+ if (process.env.BABYSITTER_GLOBAL_STATE_DIR) {
36
+ return path.resolve(process.env.BABYSITTER_GLOBAL_STATE_DIR);
37
+ }
38
+ return path.join(os.homedir(), '.a5c');
39
+ }
40
+
41
+ function getPluginRoot(args) {
42
+ const base = args.workspace ? path.resolve(args.workspace) : os.homedir();
43
+ return path.join(base, '.omp', 'plugins', 'babysitter');
44
+ }
45
+
46
+ function copyRecursive(src, dest) {
47
+ const stat = fs.statSync(src);
48
+ if (stat.isDirectory()) {
49
+ fs.mkdirSync(dest, { recursive: true });
50
+ for (const entry of fs.readdirSync(src)) {
51
+ if (['node_modules', '.git', 'test', '.gitignore', 'state'].includes(entry)) continue;
52
+ copyRecursive(path.join(src, entry), path.join(dest, entry));
53
+ }
54
+ return;
55
+ }
56
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
57
+ fs.copyFileSync(src, dest);
58
+ }
59
+
60
+ function resolveBabysitterCommand(packageRoot) {
61
+ if (process.env.BABYSITTER_SDK_CLI) {
62
+ return {
63
+ command: process.execPath,
64
+ argsPrefix: [path.resolve(process.env.BABYSITTER_SDK_CLI)],
65
+ };
66
+ }
67
+ try {
68
+ return {
69
+ command: process.execPath,
70
+ argsPrefix: [
71
+ require.resolve('@a5c-ai/babysitter-sdk/dist/cli/main.js', {
72
+ paths: [packageRoot],
73
+ }),
74
+ ],
75
+ };
76
+ } catch {
77
+ return {
78
+ command: 'babysitter',
79
+ argsPrefix: [],
80
+ };
81
+ }
82
+ }
83
+
84
+ function runBabysitterCli(packageRoot, cliArgs) {
85
+ const resolved = resolveBabysitterCommand(packageRoot);
86
+ const result = spawnSync(resolved.command, [...resolved.argsPrefix, ...cliArgs], {
87
+ cwd: packageRoot,
88
+ stdio: ['ignore', 'pipe', 'pipe'],
89
+ encoding: 'utf8',
90
+ env: process.env,
91
+ });
92
+ if (result.status !== 0) {
93
+ const stderr = (result.stderr || '').trim();
94
+ const stdout = (result.stdout || '').trim();
95
+ throw new Error(
96
+ `babysitter ${cliArgs.join(' ')} failed` +
97
+ (stderr ? `: ${stderr}` : stdout ? `: ${stdout}` : ''),
98
+ );
99
+ }
100
+ return result.stdout;
101
+ }
102
+
103
+ function ensureGlobalProcessLibrary() {
104
+ const active = JSON.parse(
105
+ runBabysitterCli(
106
+ PACKAGE_ROOT,
107
+ ['process-library:active', '--state-dir', getGlobalStateDir(), '--json'],
108
+ ),
109
+ );
110
+ console.log(`[babysitter] process library: ${active.binding?.dir}`);
111
+ }
112
+
113
+ function installEntry(pluginRoot, entry) {
114
+ const src = path.join(PACKAGE_ROOT, entry.source);
115
+ const dest = path.join(pluginRoot, entry.source);
116
+ if (!fs.existsSync(src)) {
117
+ if (entry.required) throw new Error(`required install payload is missing: ${src}`);
118
+ return;
119
+ }
120
+ copyRecursive(src, dest);
121
+ console.log(`[babysitter] ${entry.source}${fs.statSync(src).isDirectory() ? '/' : ''}`);
122
+ }
123
+
124
+ function main() {
125
+ const args = parseArgs(process.argv);
126
+ const pluginRoot = getPluginRoot(args);
127
+ console.log(`[babysitter] Installing oh-my-pi plugin to ${pluginRoot}`);
128
+
129
+ try {
130
+ fs.rmSync(pluginRoot, { recursive: true, force: true });
131
+ fs.mkdirSync(pluginRoot, { recursive: true });
132
+ for (const entry of INSTALL_ENTRIES) {
133
+ installEntry(pluginRoot, entry);
134
+ }
135
+ fs.mkdirSync(path.join(pluginRoot, 'state'), { recursive: true });
136
+ ensureGlobalProcessLibrary();
137
+ console.log('[babysitter] Installation complete!');
138
+ } catch (err) {
139
+ console.error(`[babysitter] Failed to install plugin files: ${err.message}`);
140
+ process.exitCode = 1;
141
+ }
142
+ }
143
+
144
+ main();
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+
8
+ function parseArgs(argv) {
9
+ const args = {
10
+ workspace: null,
11
+ };
12
+ for (let i = 2; i < argv.length; i += 1) {
13
+ if (argv[i] === '--workspace' && argv[i + 1]) {
14
+ args.workspace = path.resolve(argv[++i]);
15
+ } else if (argv[i] === '--global') {
16
+ args.workspace = null;
17
+ } else {
18
+ throw new Error(`unknown argument: ${argv[i]}`);
19
+ }
20
+ }
21
+ return args;
22
+ }
23
+
24
+ function getPluginRoot(args) {
25
+ const base = args.workspace ? path.resolve(args.workspace) : os.homedir();
26
+ return path.join(base, '.omp', 'plugins', 'babysitter');
27
+ }
28
+
29
+ function main() {
30
+ const args = parseArgs(process.argv);
31
+ const pluginRoot = getPluginRoot(args);
32
+ if (!fs.existsSync(pluginRoot)) {
33
+ console.log('[babysitter] Nothing to uninstall.');
34
+ return;
35
+ }
36
+ fs.rmSync(pluginRoot, { recursive: true, force: true });
37
+ console.log(`[babysitter] Removed ${pluginRoot}`);
38
+ }
39
+
40
+ main();
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: babysitter:call
3
+ description: Start a babysitter orchestration run
4
+ arguments:
5
+ - name: prompt
6
+ description: The task to orchestrate
7
+ required: true
8
+ ---
9
+
10
+ Start a babysitter orchestration run. Creates a new run using the SDK, binds it to the current session, and begins iteration.
11
+
12
+ This command initialises a fresh babysitter run with the given prompt, associates it with the active oh-my-pi session, and kicks off the first orchestration iteration. The loop driver will continue iterating automatically on subsequent `agent_end` events until the run completes, fails, or a guard trips.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: babysitter:doctor
3
+ description: Diagnose the health of a babysitter run
4
+ arguments:
5
+ - name: runId
6
+ description: Optional run ID to diagnose (defaults to the active run)
7
+ required: false
8
+ ---
9
+
10
+ Run diagnostic checks against a babysitter run to identify potential issues. Inspects run metadata, journal integrity, state cache, lock files, and effect health.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: babysitter:resume
3
+ description: Resume a previously stopped or interrupted babysitter run
4
+ arguments:
5
+ - name: runId
6
+ description: The run ID to resume
7
+ required: true
8
+ ---
9
+
10
+ Resume an existing babysitter orchestration run that was previously stopped, interrupted, or is in a waiting state. Re-binds the run to the current session and continues iteration from where it left off.
11
+
12
+ ## Behaviour
13
+
14
+ 1. Locates the run directory for the given run ID.
15
+ 2. Reads run metadata and journal to determine current state.
16
+ 3. Re-binds the run to the active oh-my-pi session.
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: babysitter:status
3
+ description: Check the status of the active babysitter run
4
+ arguments:
5
+ - name: runId
6
+ description: Optional run ID to check (defaults to the active run)
7
+ required: false
8
+ ---
9
+
10
+ Check the current status of a babysitter orchestration run.
11
+
12
+ ## Notes
13
+
14
+ - When called without arguments, reports on the run bound to the current session.
15
+ - If discovery reports `oh-my-pi` as installed but direct invocation fails, validate `where omp`.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Babysitter CLI invocation helper.
3
+ *
4
+ * Spawns the `babysitter` CLI as a child process, captures stdout/stderr,
5
+ * parses JSON output, and surfaces structured errors. Every other module
6
+ * that needs to talk to babysitter goes through here so there is exactly
7
+ * one place to handle timeouts, env vars, and error mapping.
8
+ *
9
+ * @deprecated Use `sdk-bridge.ts` instead. This module spawns a child
10
+ * process to communicate with babysitter. The SDK bridge imports the
11
+ * runtime directly, avoiding subprocess overhead and JSON parsing.
12
+ * This file is retained only for backward compatibility and will be
13
+ * removed in a future release.
14
+ *
15
+ * @module cli-wrapper
16
+ */
17
+
18
+ import { execFile } from 'node:child_process';
19
+ import { CLI_COMMAND, CLI_TIMEOUT_MS } from './constants';
20
+
21
+ /** Structured result from a CLI invocation. */
22
+ export interface CliResult<T = unknown> {
23
+ /** Whether the process exited with code 0. */
24
+ success: boolean;
25
+ /** Parsed JSON from stdout (when `--json` flag is used). */
26
+ data?: T;
27
+ /** Raw stdout string. */
28
+ stdout: string;
29
+ /** Raw stderr string. */
30
+ stderr: string;
31
+ /** Process exit code (null when killed by signal). */
32
+ exitCode: number | null;
33
+ }
34
+
35
+ /**
36
+ * Invoke the babysitter CLI with the given command and arguments.
37
+ *
38
+ * The `--json` flag is appended automatically so callers always get
39
+ * machine-readable output. Errors are captured rather than thrown --
40
+ * inspect `CliResult.success` to decide how to proceed.
41
+ *
42
+ * @param command - The babysitter sub-command (e.g. `"run:iterate"`).
43
+ * @param args - Additional positional / flag arguments.
44
+ * @param options - Optional overrides for timeout and env.
45
+ * @returns A promise that resolves to the structured CLI result.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const res = await runCli('run:iterate', ['--run-id', runId]);
50
+ * if (res.success && res.data) {
51
+ * console.log(res.data);
52
+ * }
53
+ * ```
54
+ */
55
+ export async function runCli<T = unknown>(
56
+ command: string,
57
+ args: string[] = [],
58
+ options: { timeoutMs?: number; env?: Record<string, string> } = {},
59
+ ): Promise<CliResult<T>> {
60
+ const timeout = options.timeoutMs ?? CLI_TIMEOUT_MS;
61
+ const fullArgs = [command, '--json', ...args];
62
+
63
+ return new Promise<CliResult<T>>((resolve) => {
64
+ const child = execFile(
65
+ CLI_COMMAND,
66
+ fullArgs,
67
+ {
68
+ timeout,
69
+ maxBuffer: 10 * 1024 * 1024, // 10 MiB
70
+ env: { ...process.env, ...options.env },
71
+ },
72
+ (error, stdout, stderr) => {
73
+ const exitCode =
74
+ error && 'code' in error ? (error.code as number | null) : child.exitCode;
75
+
76
+ let data: T | undefined;
77
+ try {
78
+ if (stdout.trim()) {
79
+ data = JSON.parse(stdout) as T;
80
+ }
81
+ } catch {
82
+ // stdout was not valid JSON -- leave data undefined
83
+ }
84
+
85
+ resolve({
86
+ success: exitCode === 0,
87
+ data,
88
+ stdout: String(stdout),
89
+ stderr: String(stderr),
90
+ exitCode: typeof exitCode === 'number' ? exitCode : null,
91
+ });
92
+ },
93
+ );
94
+ });
95
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Configuration constants for the babysitter oh-my-pi extension.
3
+ *
4
+ * Centralises magic strings, default timeouts, environment variable names,
5
+ * and widget keys so the rest of the extension can remain blissfully ignorant
6
+ * of where these values come from.
7
+ *
8
+ * @module constants
9
+ */
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // CLI
13
+ // ---------------------------------------------------------------------------
14
+
15
+ /** Path (or bare command name) used to invoke the babysitter CLI. */
16
+ export const CLI_COMMAND = process.env['BABYSITTER_CLI_PATH'] ?? 'babysitter';
17
+
18
+ /** Default timeout (ms) applied to every CLI invocation. */
19
+ export const CLI_TIMEOUT_MS = 120_000;
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Orchestration limits (mirrors BABYSITTER_MAX_ITERATIONS etc.)
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /** Default maximum orchestration iterations per run. */
26
+ export const DEFAULT_MAX_ITERATIONS = 256;
27
+
28
+ /** Default maximum wall-clock time (ms) for a single run. */
29
+ export const DEFAULT_MAX_DURATION_MS = 30 * 60 * 1_000; // 30 minutes
30
+
31
+ /** Consecutive errors before the guard trips. */
32
+ export const DEFAULT_ERROR_THRESHOLD = 5;
33
+
34
+ /** Number of identical iteration digests that signal a doom loop. */
35
+ export const DEFAULT_DOOM_LOOP_WINDOW = 4;
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Timeouts
39
+ // ---------------------------------------------------------------------------
40
+
41
+ /** Timeout (ms) for a single effect execution. */
42
+ export const EFFECT_TIMEOUT_MS = 900_000; // 15 minutes, matches NODE_TASK_TIMEOUT
43
+
44
+ /** Timeout (ms) for posting a task result back. */
45
+ export const POST_RESULT_TIMEOUT_MS = 30_000;
46
+
47
+ /** Delay (ms) for a sleep effect (placeholder; real value comes from the effect). */
48
+ export const DEFAULT_SLEEP_MS = 5_000;
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Environment variable names
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export const ENV_RUNS_DIR = 'BABYSITTER_RUNS_DIR';
55
+ export const ENV_MAX_ITERATIONS = 'BABYSITTER_MAX_ITERATIONS';
56
+ export const ENV_QUALITY_THRESHOLD = 'BABYSITTER_QUALITY_THRESHOLD';
57
+ export const ENV_TIMEOUT = 'BABYSITTER_TIMEOUT';
58
+ export const ENV_LOG_LEVEL = 'BABYSITTER_LOG_LEVEL';
59
+ export const ENV_HOOK_TIMEOUT = 'BABYSITTER_HOOK_TIMEOUT';
60
+ export const ENV_NODE_TASK_TIMEOUT = 'BABYSITTER_NODE_TASK_TIMEOUT';
61
+ export const ENV_CLI_PATH = 'BABYSITTER_CLI_PATH';
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Widget keys (for registerMessageRenderer / TUI identification)
65
+ // ---------------------------------------------------------------------------
66
+
67
+ export const WIDGET_RUN_PROGRESS = 'babysitter:run-progress';
68
+ export const WIDGET_TASK_STATUS = 'babysitter:task-status';
69
+ export const WIDGET_QUALITY_SCORE = 'babysitter:quality-score';
70
+ export const WIDGET_PHASE_INDICATOR = 'babysitter:phase-indicator';
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Extension metadata
74
+ // ---------------------------------------------------------------------------
75
+
76
+ export const EXTENSION_NAME = 'babysitter';
77
+ export const EXTENSION_VERSION = '0.1.0';