@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 +80 -0
- package/bin/cli.cjs +78 -0
- package/bin/install.cjs +144 -0
- package/bin/uninstall.cjs +40 -0
- package/commands/babysitter-call.md +12 -0
- package/commands/babysitter-doctor.md +10 -0
- package/commands/babysitter-resume.md +16 -0
- package/commands/babysitter-status.md +15 -0
- package/extensions/babysitter/cli-wrapper.ts +95 -0
- package/extensions/babysitter/constants.ts +77 -0
- package/extensions/babysitter/custom-tools.ts +208 -0
- package/extensions/babysitter/effect-executor.ts +362 -0
- package/extensions/babysitter/guards.ts +257 -0
- package/extensions/babysitter/index.ts +554 -0
- package/extensions/babysitter/loop-driver.ts +256 -0
- package/extensions/babysitter/result-poster.ts +115 -0
- package/extensions/babysitter/sdk-bridge.ts +243 -0
- package/extensions/babysitter/session-binder.ts +284 -0
- package/extensions/babysitter/status-line.ts +54 -0
- package/extensions/babysitter/task-interceptor.ts +82 -0
- package/extensions/babysitter/todo-replacement.ts +125 -0
- package/extensions/babysitter/tool-renderer.ts +263 -0
- package/extensions/babysitter/tui-widgets.ts +164 -0
- package/extensions/babysitter/types.ts +222 -0
- package/package.json +57 -0
- package/scripts/setup.sh +74 -0
- package/scripts/sync-command-docs.cjs +115 -0
- package/skills/babysitter/SKILL.md +45 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom tool registrations for the babysitter oh-my-pi extension.
|
|
3
|
+
*
|
|
4
|
+
* Registers babysitter-specific tools with oh-my-pi so that agents (and
|
|
5
|
+
* users, if they are feeling adventurous) can inspect run status, post
|
|
6
|
+
* results for pending effects, and manually trigger iterations — all
|
|
7
|
+
* without leaving the oh-my-pi session.
|
|
8
|
+
*
|
|
9
|
+
* @module custom-tools
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { getActiveRun } from './session-binder';
|
|
13
|
+
import { getRunStatus, postResult, iterate } from './sdk-bridge';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Tool: babysitter_run_status
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds and returns the run-status tool definition.
|
|
21
|
+
*
|
|
22
|
+
* Retrieves the current babysitter run state including status, iteration
|
|
23
|
+
* count, and pending effects. Requires an active run bound via the
|
|
24
|
+
* session binder; returns a helpful message when no run is active.
|
|
25
|
+
*/
|
|
26
|
+
function buildRunStatusTool() {
|
|
27
|
+
return {
|
|
28
|
+
name: 'babysitter_run_status',
|
|
29
|
+
label: 'Babysitter Run Status',
|
|
30
|
+
description:
|
|
31
|
+
'Get the current babysitter run status including run state, iteration count, and pending effects.',
|
|
32
|
+
parameters: {},
|
|
33
|
+
execute: async (
|
|
34
|
+
_toolCallId: string,
|
|
35
|
+
_params: Record<string, unknown>,
|
|
36
|
+
_signal: unknown,
|
|
37
|
+
_onUpdate: unknown,
|
|
38
|
+
_ctx: unknown,
|
|
39
|
+
) => {
|
|
40
|
+
const activeRun = getActiveRun();
|
|
41
|
+
|
|
42
|
+
if (!activeRun) {
|
|
43
|
+
return {
|
|
44
|
+
content: 'No active babysitter run found.',
|
|
45
|
+
details: { active: false },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const status = await getRunStatus(activeRun.runDir);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
`Run: ${status.runId}`,
|
|
54
|
+
`Process: ${status.processId}`,
|
|
55
|
+
`Status: ${status.status}`,
|
|
56
|
+
`Iteration: ${activeRun.iteration}`,
|
|
57
|
+
`Pending effects: ${status.pendingEffects.length}`,
|
|
58
|
+
].join('\n'),
|
|
59
|
+
details: {
|
|
60
|
+
active: true,
|
|
61
|
+
runId: status.runId,
|
|
62
|
+
processId: status.processId,
|
|
63
|
+
status: status.status,
|
|
64
|
+
iteration: activeRun.iteration,
|
|
65
|
+
pendingEffects: status.pendingEffects,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Tool: babysitter_post_result
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Builds and returns the post-result tool definition.
|
|
78
|
+
*
|
|
79
|
+
* Posts a result (ok or error) for a pending effect back into the run
|
|
80
|
+
* journal. Delegates directly to {@link postResult} from the SDK bridge.
|
|
81
|
+
*/
|
|
82
|
+
function buildPostResultTool() {
|
|
83
|
+
return {
|
|
84
|
+
name: 'babysitter_post_result',
|
|
85
|
+
label: 'Babysitter Post Result',
|
|
86
|
+
description:
|
|
87
|
+
'Post a result for a pending babysitter effect. Requires an effectId and status ("ok" or "error"). Optionally accepts a value string.',
|
|
88
|
+
parameters: {
|
|
89
|
+
type: 'object',
|
|
90
|
+
properties: {
|
|
91
|
+
effectId: { type: 'string', description: 'The effect identifier to resolve.' },
|
|
92
|
+
status: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
enum: ['ok', 'error'],
|
|
95
|
+
description: 'Whether the effect succeeded or failed.',
|
|
96
|
+
},
|
|
97
|
+
value: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description: 'Optional result value to attach to the resolution.',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
required: ['effectId', 'status'],
|
|
103
|
+
},
|
|
104
|
+
execute: async (
|
|
105
|
+
_toolCallId: string,
|
|
106
|
+
params: Record<string, unknown>,
|
|
107
|
+
_signal: unknown,
|
|
108
|
+
_onUpdate: unknown,
|
|
109
|
+
_ctx: unknown,
|
|
110
|
+
) => {
|
|
111
|
+
const activeRun = getActiveRun();
|
|
112
|
+
|
|
113
|
+
if (!activeRun) {
|
|
114
|
+
return {
|
|
115
|
+
content: 'No active babysitter run. Cannot post result.',
|
|
116
|
+
details: { posted: false },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const effectId = params.effectId as string;
|
|
121
|
+
const status = params.status as 'ok' | 'error';
|
|
122
|
+
const value = params.value as string | undefined;
|
|
123
|
+
|
|
124
|
+
const artifacts = await postResult({
|
|
125
|
+
runDir: activeRun.runDir,
|
|
126
|
+
effectId,
|
|
127
|
+
status,
|
|
128
|
+
value,
|
|
129
|
+
error: status === 'error' ? value : undefined,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
content: `Result posted for effect ${effectId} (status: ${status}).`,
|
|
134
|
+
details: {
|
|
135
|
+
posted: true,
|
|
136
|
+
effectId,
|
|
137
|
+
status,
|
|
138
|
+
artifacts,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Tool: babysitter_iterate
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Builds and returns the iterate tool definition.
|
|
151
|
+
*
|
|
152
|
+
* Manually triggers the next orchestration iteration for the active run.
|
|
153
|
+
* Useful when the automatic loop is paused or when an agent wants
|
|
154
|
+
* explicit control over iteration timing.
|
|
155
|
+
*/
|
|
156
|
+
function buildIterateTool() {
|
|
157
|
+
return {
|
|
158
|
+
name: 'babysitter_iterate',
|
|
159
|
+
label: 'Babysitter Iterate',
|
|
160
|
+
description:
|
|
161
|
+
'Manually trigger the next babysitter orchestration iteration for the active run.',
|
|
162
|
+
parameters: {},
|
|
163
|
+
execute: async (
|
|
164
|
+
_toolCallId: string,
|
|
165
|
+
_params: Record<string, unknown>,
|
|
166
|
+
_signal: unknown,
|
|
167
|
+
_onUpdate: unknown,
|
|
168
|
+
_ctx: unknown,
|
|
169
|
+
) => {
|
|
170
|
+
const activeRun = getActiveRun();
|
|
171
|
+
|
|
172
|
+
if (!activeRun) {
|
|
173
|
+
return {
|
|
174
|
+
content: 'No active babysitter run. Cannot iterate.',
|
|
175
|
+
details: { iterated: false },
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const result = await iterate(activeRun.runDir);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
content: `Iteration completed. Status: ${(result as Record<string, unknown>).status ?? 'unknown'}.`,
|
|
183
|
+
details: {
|
|
184
|
+
iterated: true,
|
|
185
|
+
result,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Public registration function
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Register all babysitter custom tools with the oh-my-pi host.
|
|
198
|
+
*
|
|
199
|
+
* @param pi - The oh-my-pi extension API handle (typed as `any` to
|
|
200
|
+
* accommodate the `registerTool` overload that accepts
|
|
201
|
+
* the extended tool shape with `label` and `execute`).
|
|
202
|
+
*/
|
|
203
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
204
|
+
export function registerCustomTools(pi: any): void {
|
|
205
|
+
pi.registerTool(buildRunStatusTool());
|
|
206
|
+
pi.registerTool(buildPostResultTool());
|
|
207
|
+
pi.registerTool(buildIterateTool());
|
|
208
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps babysitter effect kinds to oh-my-pi execution capabilities.
|
|
3
|
+
*
|
|
4
|
+
* Each effect kind (agent, node, shell, breakpoint, sleep, skill,
|
|
5
|
+
* orchestrator_task) is dispatched to the appropriate oh-my-pi primitive:
|
|
6
|
+
* sub-agent tasks, child_process execution, user prompts, timers, etc.
|
|
7
|
+
*
|
|
8
|
+
* Results are committed back to the run journal via the SDK's
|
|
9
|
+
* `commitEffectResult` -- no CLI subprocess is spawned.
|
|
10
|
+
*
|
|
11
|
+
* @module effect-executor
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { execSync } from 'child_process';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
commitEffectResult,
|
|
18
|
+
type EffectAction,
|
|
19
|
+
type CommitEffectResultOptions,
|
|
20
|
+
} from '@a5c-ai/babysitter-sdk';
|
|
21
|
+
|
|
22
|
+
import { EFFECT_TIMEOUT_MS, DEFAULT_SLEEP_MS } from './constants';
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Public result shape
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/** Outcome of executing a single effect. */
|
|
29
|
+
export interface EffectResult {
|
|
30
|
+
status: 'ok' | 'error';
|
|
31
|
+
value?: unknown;
|
|
32
|
+
error?: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// executeEffect
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Execute a single babysitter effect using oh-my-pi capabilities.
|
|
41
|
+
*
|
|
42
|
+
* The mapping from effect kind to execution strategy is:
|
|
43
|
+
*
|
|
44
|
+
* | Effect kind | oh-my-pi capability |
|
|
45
|
+
* |----------------------|-------------------------------------------|
|
|
46
|
+
* | `agent` | Sub-agent via `pi.sendUserMessage()` |
|
|
47
|
+
* | `node` | `execSync('node ...')` |
|
|
48
|
+
* | `shell` | `execSync('<command>')` |
|
|
49
|
+
* | `breakpoint` | Ask tool (user approval gate) |
|
|
50
|
+
* | `sleep` | `setTimeout` with target timestamp check |
|
|
51
|
+
* | `skill` | Command system / skill expansion |
|
|
52
|
+
* | `orchestrator_task` | Sub-agent delegation with orchestrator |
|
|
53
|
+
*
|
|
54
|
+
* @param action - The effect action descriptor from the babysitter runtime.
|
|
55
|
+
* @param pi - The oh-my-pi extension API handle.
|
|
56
|
+
* @returns An {@link EffectResult} representing the outcome.
|
|
57
|
+
*/
|
|
58
|
+
export async function executeEffect(
|
|
59
|
+
action: EffectAction,
|
|
60
|
+
pi: unknown,
|
|
61
|
+
): Promise<EffectResult> {
|
|
62
|
+
const kind = action.kind;
|
|
63
|
+
|
|
64
|
+
switch (kind) {
|
|
65
|
+
case 'agent':
|
|
66
|
+
return executeAgentEffect(action, pi);
|
|
67
|
+
|
|
68
|
+
case 'node':
|
|
69
|
+
return executeNodeEffect(action);
|
|
70
|
+
|
|
71
|
+
case 'shell':
|
|
72
|
+
return executeShellEffect(action);
|
|
73
|
+
|
|
74
|
+
case 'breakpoint':
|
|
75
|
+
return executeBreakpointEffect(action, pi);
|
|
76
|
+
|
|
77
|
+
case 'sleep':
|
|
78
|
+
return executeSleepEffect(action);
|
|
79
|
+
|
|
80
|
+
case 'skill':
|
|
81
|
+
return executeSkillEffect(action, pi);
|
|
82
|
+
|
|
83
|
+
case 'orchestrator_task':
|
|
84
|
+
return executeOrchestratorEffect(action, pi);
|
|
85
|
+
|
|
86
|
+
default:
|
|
87
|
+
return {
|
|
88
|
+
status: 'error',
|
|
89
|
+
error: `Unknown effect kind: ${String(kind)}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// postEffectResult
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Post an effect result back into a run's journal using the SDK directly.
|
|
100
|
+
*
|
|
101
|
+
* This replaces the former CLI-based `task:post` approach with an in-process
|
|
102
|
+
* call to `commitEffectResult`.
|
|
103
|
+
*
|
|
104
|
+
* @param runDir - Absolute path to the run directory.
|
|
105
|
+
* @param effectId - The effect identifier to resolve.
|
|
106
|
+
* @param result - The structured result payload.
|
|
107
|
+
*/
|
|
108
|
+
export async function postEffectResult(
|
|
109
|
+
runDir: string,
|
|
110
|
+
effectId: string,
|
|
111
|
+
result: EffectResult,
|
|
112
|
+
): Promise<void> {
|
|
113
|
+
const opts: CommitEffectResultOptions = {
|
|
114
|
+
runDir,
|
|
115
|
+
effectId,
|
|
116
|
+
result: {
|
|
117
|
+
status: result.status,
|
|
118
|
+
value: result.status === 'ok' ? result.value : undefined,
|
|
119
|
+
error: result.status === 'error' ? result.error : undefined,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
await commitEffectResult(opts);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Kind-specific executors
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Dispatch an agent effect as a sub-agent task via oh-my-pi messaging.
|
|
132
|
+
*
|
|
133
|
+
* Uses `pi.sendUserMessage()` to inject an agent prompt into the
|
|
134
|
+
* conversation, delegating execution to the host's sub-agent system.
|
|
135
|
+
*/
|
|
136
|
+
async function executeAgentEffect(
|
|
137
|
+
action: EffectAction,
|
|
138
|
+
pi: unknown,
|
|
139
|
+
): Promise<EffectResult> {
|
|
140
|
+
try {
|
|
141
|
+
const taskDef = action.taskDef ?? {};
|
|
142
|
+
const args = (taskDef as Record<string, unknown>)['args'] as Record<string, unknown> | undefined;
|
|
143
|
+
const prompt = (args?.['prompt'] as string)
|
|
144
|
+
?? (taskDef as Record<string, unknown>)['title'] as string
|
|
145
|
+
?? `Execute agent task ${action.effectId}`;
|
|
146
|
+
|
|
147
|
+
const piApi = pi as { sendUserMessage: (msg: { role: string; content: string }) => void };
|
|
148
|
+
piApi.sendUserMessage({
|
|
149
|
+
role: 'user',
|
|
150
|
+
content: `[babysitter:agent] ${prompt}`,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
status: 'ok',
|
|
155
|
+
value: { dispatched: true, effectId: action.effectId },
|
|
156
|
+
};
|
|
157
|
+
} catch (err: unknown) {
|
|
158
|
+
return { status: 'error', error: String(err) };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Execute a node script via `child_process.execSync`.
|
|
164
|
+
*
|
|
165
|
+
* The script content is pulled from `action.taskDef.args.script` and
|
|
166
|
+
* executed with `node -e`.
|
|
167
|
+
*/
|
|
168
|
+
async function executeNodeEffect(action: EffectAction): Promise<EffectResult> {
|
|
169
|
+
try {
|
|
170
|
+
const taskDef = action.taskDef ?? {};
|
|
171
|
+
const args = (taskDef as Record<string, unknown>)['args'] as Record<string, unknown> | undefined;
|
|
172
|
+
const script = (args?.['script'] as string) ?? '';
|
|
173
|
+
const timeout = (args?.['timeout'] as number) ?? EFFECT_TIMEOUT_MS;
|
|
174
|
+
|
|
175
|
+
const output = execSync(`node -e ${JSON.stringify(script)}`, {
|
|
176
|
+
encoding: 'utf-8',
|
|
177
|
+
timeout,
|
|
178
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return { status: 'ok', value: output };
|
|
182
|
+
} catch (err: unknown) {
|
|
183
|
+
const execError = err as { stderr?: string; message?: string };
|
|
184
|
+
return {
|
|
185
|
+
status: 'error',
|
|
186
|
+
error: execError.stderr ?? execError.message ?? String(err),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Execute a shell command via `child_process.execSync`.
|
|
193
|
+
*
|
|
194
|
+
* The command string is pulled from `action.taskDef.args.command`.
|
|
195
|
+
*/
|
|
196
|
+
async function executeShellEffect(action: EffectAction): Promise<EffectResult> {
|
|
197
|
+
try {
|
|
198
|
+
const taskDef = action.taskDef ?? {};
|
|
199
|
+
const args = (taskDef as Record<string, unknown>)['args'] as Record<string, unknown> | undefined;
|
|
200
|
+
const command = (args?.['command'] as string) ?? '';
|
|
201
|
+
const timeout = (args?.['timeout'] as number) ?? EFFECT_TIMEOUT_MS;
|
|
202
|
+
|
|
203
|
+
const output = execSync(command, {
|
|
204
|
+
encoding: 'utf-8',
|
|
205
|
+
timeout,
|
|
206
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return { status: 'ok', value: output };
|
|
210
|
+
} catch (err: unknown) {
|
|
211
|
+
const execError = err as { stderr?: string; message?: string };
|
|
212
|
+
return {
|
|
213
|
+
status: 'error',
|
|
214
|
+
error: execError.stderr ?? execError.message ?? String(err),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Present a breakpoint to the user for approval via oh-my-pi's ask tool.
|
|
221
|
+
*
|
|
222
|
+
* Sends a message with type `'ask'` and options `['Approve', 'Reject']`.
|
|
223
|
+
* The user's response determines the result status.
|
|
224
|
+
*/
|
|
225
|
+
async function executeBreakpointEffect(
|
|
226
|
+
action: EffectAction,
|
|
227
|
+
pi: unknown,
|
|
228
|
+
): Promise<EffectResult> {
|
|
229
|
+
try {
|
|
230
|
+
const taskDef = action.taskDef ?? {};
|
|
231
|
+
const args = (taskDef as Record<string, unknown>)['args'] as Record<string, unknown> | undefined;
|
|
232
|
+
const message = (args?.['message'] as string)
|
|
233
|
+
?? (taskDef as Record<string, unknown>)['title'] as string
|
|
234
|
+
?? `Breakpoint: ${action.effectId}`;
|
|
235
|
+
|
|
236
|
+
const piApi = pi as {
|
|
237
|
+
sendMessage: (msg: {
|
|
238
|
+
type: string;
|
|
239
|
+
question: string;
|
|
240
|
+
options: string[];
|
|
241
|
+
}) => Promise<{ response?: string }> | void;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const response = await Promise.resolve(
|
|
245
|
+
piApi.sendMessage({
|
|
246
|
+
type: 'ask',
|
|
247
|
+
question: `[babysitter:breakpoint] ${message}`,
|
|
248
|
+
options: ['Approve', 'Reject'],
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const approved = !response || response.response !== 'Reject';
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
status: approved ? 'ok' : 'error',
|
|
256
|
+
value: approved ? { approved: true, message } : undefined,
|
|
257
|
+
error: approved ? undefined : 'Breakpoint rejected by user',
|
|
258
|
+
};
|
|
259
|
+
} catch (err: unknown) {
|
|
260
|
+
return { status: 'error', error: String(err) };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Pause execution until a target timestamp is reached.
|
|
266
|
+
*
|
|
267
|
+
* If `schedulerHints.sleepUntilEpochMs` is present the sleep lasts until
|
|
268
|
+
* that absolute time. Otherwise falls back to `args.durationMs` or
|
|
269
|
+
* the default sleep duration.
|
|
270
|
+
*/
|
|
271
|
+
async function executeSleepEffect(action: EffectAction): Promise<EffectResult> {
|
|
272
|
+
try {
|
|
273
|
+
const taskDef = action.taskDef ?? {};
|
|
274
|
+
const args = (taskDef as Record<string, unknown>)['args'] as Record<string, unknown> | undefined;
|
|
275
|
+
|
|
276
|
+
let durationMs: number;
|
|
277
|
+
|
|
278
|
+
// Prefer the scheduler hint for an absolute target timestamp.
|
|
279
|
+
const targetEpochMs = action.schedulerHints?.sleepUntilEpochMs;
|
|
280
|
+
if (targetEpochMs != null) {
|
|
281
|
+
const remaining = targetEpochMs - Date.now();
|
|
282
|
+
durationMs = remaining > 0 ? remaining : 0;
|
|
283
|
+
} else {
|
|
284
|
+
durationMs = (args?.['durationMs'] as number) ?? DEFAULT_SLEEP_MS;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await new Promise<void>((resolve) => setTimeout(resolve, durationMs));
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
status: 'ok',
|
|
291
|
+
value: { sleptMs: durationMs, wokeAt: new Date().toISOString() },
|
|
292
|
+
};
|
|
293
|
+
} catch (err: unknown) {
|
|
294
|
+
return { status: 'error', error: String(err) };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Expand a skill via oh-my-pi's command system.
|
|
300
|
+
*
|
|
301
|
+
* Delegates to `pi.registerCommand` handler lookup or dispatches
|
|
302
|
+
* through the command system.
|
|
303
|
+
*/
|
|
304
|
+
async function executeSkillEffect(
|
|
305
|
+
action: EffectAction,
|
|
306
|
+
pi: unknown,
|
|
307
|
+
): Promise<EffectResult> {
|
|
308
|
+
try {
|
|
309
|
+
const taskDef = action.taskDef ?? {};
|
|
310
|
+
const args = (taskDef as Record<string, unknown>)['args'] as Record<string, unknown> | undefined;
|
|
311
|
+
const skillName = (args?.['skill'] as string) ?? '';
|
|
312
|
+
|
|
313
|
+
const piApi = pi as {
|
|
314
|
+
sendMessage: (msg: { role: string; content: string }) => void;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
piApi.sendMessage({
|
|
318
|
+
role: 'system',
|
|
319
|
+
content: `[babysitter:skill] Executing skill: ${skillName}`,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
status: 'ok',
|
|
324
|
+
value: { skill: skillName, dispatched: true },
|
|
325
|
+
};
|
|
326
|
+
} catch (err: unknown) {
|
|
327
|
+
return { status: 'error', error: String(err) };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Delegate to a sub-agent for orchestrator tasks.
|
|
333
|
+
*
|
|
334
|
+
* Injects an orchestrator prompt into the conversation via
|
|
335
|
+
* `pi.sendUserMessage()`, letting the host's agent system handle
|
|
336
|
+
* the delegation.
|
|
337
|
+
*/
|
|
338
|
+
async function executeOrchestratorEffect(
|
|
339
|
+
action: EffectAction,
|
|
340
|
+
pi: unknown,
|
|
341
|
+
): Promise<EffectResult> {
|
|
342
|
+
try {
|
|
343
|
+
const taskDef = action.taskDef ?? {};
|
|
344
|
+
const args = (taskDef as Record<string, unknown>)['args'] as Record<string, unknown> | undefined;
|
|
345
|
+
const prompt = (args?.['prompt'] as string)
|
|
346
|
+
?? (taskDef as Record<string, unknown>)['title'] as string
|
|
347
|
+
?? `Orchestrator task ${action.effectId}`;
|
|
348
|
+
|
|
349
|
+
const piApi = pi as { sendUserMessage: (msg: { role: string; content: string }) => void };
|
|
350
|
+
piApi.sendUserMessage({
|
|
351
|
+
role: 'user',
|
|
352
|
+
content: `[babysitter:orchestrator] ${prompt}`,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
status: 'ok',
|
|
357
|
+
value: { dispatched: true, effectId: action.effectId },
|
|
358
|
+
};
|
|
359
|
+
} catch (err: unknown) {
|
|
360
|
+
return { status: 'error', error: String(err) };
|
|
361
|
+
}
|
|
362
|
+
}
|