@a5c-ai/tula 5.0.1-staging.daf8e165bc4a
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 +34 -0
- package/dist/cli/amuxEventsFormatter.d.ts +26 -0
- package/dist/cli/amuxEventsFormatter.d.ts.map +1 -0
- package/dist/cli/amuxEventsFormatter.js +86 -0
- package/dist/cli/args/argFlags.d.ts +6 -0
- package/dist/cli/args/argFlags.d.ts.map +1 -0
- package/dist/cli/args/argFlags.js +72 -0
- package/dist/cli/args/argPositionals.d.ts +3 -0
- package/dist/cli/args/argPositionals.d.ts.map +1 -0
- package/dist/cli/args/argPositionals.js +18 -0
- package/dist/cli/args/index.d.ts +4 -0
- package/dist/cli/args/index.d.ts.map +1 -0
- package/dist/cli/args/index.js +75 -0
- package/dist/cli/args/types.d.ts +13 -0
- package/dist/cli/args/types.d.ts.map +1 -0
- package/dist/cli/args/types.js +2 -0
- package/dist/cli/commands/daemon.d.ts +31 -0
- package/dist/cli/commands/daemon.d.ts.map +1 -0
- package/dist/cli/commands/daemon.js +156 -0
- package/dist/cli/commands/harness/createRun.d.ts +3 -0
- package/dist/cli/commands/harness/createRun.d.ts.map +1 -0
- package/dist/cli/commands/harness/createRun.js +8 -0
- package/dist/cli/commands/harness/resumeRun.d.ts +20 -0
- package/dist/cli/commands/harness/resumeRun.d.ts.map +1 -0
- package/dist/cli/commands/harness/resumeRun.js +341 -0
- package/dist/cli/commands/jsonlInteractive.d.ts +35 -0
- package/dist/cli/commands/jsonlInteractive.d.ts.map +1 -0
- package/dist/cli/commands/jsonlInteractive.js +302 -0
- package/dist/cli/commands/mcpServe.d.ts +18 -0
- package/dist/cli/commands/mcpServe.d.ts.map +1 -0
- package/dist/cli/commands/mcpServe.js +59 -0
- package/dist/cli/commands/session/history.d.ts +14 -0
- package/dist/cli/commands/session/history.d.ts.map +1 -0
- package/dist/cli/commands/session/history.js +100 -0
- package/dist/cli/commands/tui.d.ts +23 -0
- package/dist/cli/commands/tui.d.ts.map +1 -0
- package/dist/cli/commands/tui.js +183 -0
- package/dist/cli/dispatch.d.ts +4 -0
- package/dist/cli/dispatch.d.ts.map +1 -0
- package/dist/cli/dispatch.js +348 -0
- package/dist/cli/main.d.ts +7 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +41 -0
- package/dist/cli/program.d.ts +7 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +8 -0
- package/dist/cli/ui.d.ts +9 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +125 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/prompts/commandTemplates.d.ts +3 -0
- package/dist/prompts/commandTemplates.d.ts.map +1 -0
- package/dist/prompts/commandTemplates.js +238 -0
- package/package.json +76 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* resume-run command handler.
|
|
4
|
+
* Uses an agentic Pi session to discover existing runs, present them to the
|
|
5
|
+
* user, assess state, and resume orchestration via handleHarnessCreateRun.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.handleHarnessResumeRun = handleHarnessResumeRun;
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const typebox_1 = require("@sinclair/typebox");
|
|
44
|
+
const tula_core_1 = require("@a5c-ai/tula-core");
|
|
45
|
+
const tula_core_2 = require("@a5c-ai/tula-core");
|
|
46
|
+
const babysitter_sdk_1 = require("@a5c-ai/babysitter-sdk");
|
|
47
|
+
const createRun_1 = require("./createRun");
|
|
48
|
+
const harness_1 = require("@a5c-ai/agent-platform/harness");
|
|
49
|
+
const harness_2 = require("@a5c-ai/agent-platform/harness");
|
|
50
|
+
function errorResult(message) {
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// System prompt
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
function buildResumeSystemPrompt(runsDir, runIdHint) {
|
|
59
|
+
const parts = [
|
|
60
|
+
"You are an assistant that helps users find and resume babysitter orchestration runs.",
|
|
61
|
+
"",
|
|
62
|
+
"## Your workflow",
|
|
63
|
+
"",
|
|
64
|
+
"1. **Discover runs** -- call `babysitter_list_runs` to list recent runs.",
|
|
65
|
+
runIdHint
|
|
66
|
+
? `2. The user has requested run ID "${runIdHint}". Call \`babysitter_assess_run\` with that run ID to inspect it.`
|
|
67
|
+
: "2. Present the runs to the user. If there are multiple, use `AskUserQuestion` to let them pick one.",
|
|
68
|
+
"3. **Assess** the selected run by calling `babysitter_assess_run` with the run ID.",
|
|
69
|
+
"4. Show the user the run's state (status, pending effects, journal summary).",
|
|
70
|
+
"5. If the run is resumable (not completed), call `babysitter_resume_run` to resume it.",
|
|
71
|
+
"6. If the run is already completed, tell the user and stop.",
|
|
72
|
+
"",
|
|
73
|
+
"## Important rules",
|
|
74
|
+
"",
|
|
75
|
+
"- Always call `babysitter_list_runs` first to see what's available.",
|
|
76
|
+
"- Use `AskUserQuestion` for interactive selection when there are multiple runs.",
|
|
77
|
+
"- You may also use read, grep, bash and other tools to inspect run files in " + runsDir + " if more detail is needed.",
|
|
78
|
+
"- Call `babysitter_resume_run` exactly once when ready to resume.",
|
|
79
|
+
"- Do NOT attempt to modify run files yourself -- only use the provided resume tool.",
|
|
80
|
+
];
|
|
81
|
+
return parts.join("\n");
|
|
82
|
+
}
|
|
83
|
+
function buildResumeUserPrompt(runIdHint) {
|
|
84
|
+
if (runIdHint) {
|
|
85
|
+
return `Resume the run with ID "${runIdHint}". Assess it and proceed.`;
|
|
86
|
+
}
|
|
87
|
+
return "Help me find and resume a babysitter run. Start by listing the available runs.";
|
|
88
|
+
}
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Main handler
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
async function handleHarnessResumeRun(args) {
|
|
93
|
+
const { json, verbose, interactive = true, } = args;
|
|
94
|
+
const runsDir = args.runsDir ?? (0, babysitter_sdk_1.resolveRunsDir)({ cwd: args.workspace ?? process.cwd() });
|
|
95
|
+
const outputMode = args.outputMode;
|
|
96
|
+
const writeVerbose = (message) => {
|
|
97
|
+
(0, harness_1.writeVerboseLine)(verbose, json, message, outputMode);
|
|
98
|
+
};
|
|
99
|
+
const writeVerboseData = (label, value, maxChars) => {
|
|
100
|
+
(0, harness_1.writeVerboseBlock)(verbose, json, label, value, maxChars, outputMode);
|
|
101
|
+
};
|
|
102
|
+
// Track whether the agent has triggered a resume (set by the resume tool).
|
|
103
|
+
let resumeTriggered = false;
|
|
104
|
+
let resumeExitCode = 0;
|
|
105
|
+
// -----------------------------------------------------------------
|
|
106
|
+
// Define domain-specific tools
|
|
107
|
+
// -----------------------------------------------------------------
|
|
108
|
+
const customTools = [
|
|
109
|
+
{
|
|
110
|
+
name: "babysitter_list_runs",
|
|
111
|
+
label: "List Runs",
|
|
112
|
+
description: "List recent runs in the runs directory with status and metadata. " +
|
|
113
|
+
"Returns up to 20 runs sorted by most recent first.",
|
|
114
|
+
parameters: typebox_1.Type.Object({
|
|
115
|
+
statusFilter: typebox_1.Type.Optional(typebox_1.Type.String({ description: "Optional status filter: created, in-progress, waiting, failed, completed" })),
|
|
116
|
+
}),
|
|
117
|
+
execute: async (_toolCallId, params) => {
|
|
118
|
+
writeVerboseData("resume tool babysitter_list_runs", params);
|
|
119
|
+
try {
|
|
120
|
+
let runs = await (0, harness_2.discoverRuns)(runsDir);
|
|
121
|
+
if (params.statusFilter) {
|
|
122
|
+
runs = runs.filter((r) => r.status === params.statusFilter);
|
|
123
|
+
}
|
|
124
|
+
if (runs.length === 0) {
|
|
125
|
+
return (0, harness_1.formatToolResult)({ runs: [], runsDir }, "No runs found.");
|
|
126
|
+
}
|
|
127
|
+
const summary = runs.map((r) => ({
|
|
128
|
+
runId: r.runId,
|
|
129
|
+
processId: r.processId,
|
|
130
|
+
status: r.status,
|
|
131
|
+
createdAt: r.createdAt,
|
|
132
|
+
prompt: r.prompt
|
|
133
|
+
? r.prompt.length > 80 ? r.prompt.slice(0, 77) + "..." : r.prompt
|
|
134
|
+
: undefined,
|
|
135
|
+
effects: `${r.resolvedEffects}/${r.totalEffects} resolved`,
|
|
136
|
+
pendingEffects: r.pendingEffects,
|
|
137
|
+
}));
|
|
138
|
+
return (0, harness_1.formatToolResult)({ count: runs.length, runs: summary }, `Found ${runs.length} run(s).`);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
142
|
+
return errorResult(`Failed to list runs: ${msg}`);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "babysitter_assess_run",
|
|
148
|
+
label: "Assess Run",
|
|
149
|
+
description: "Get detailed state assessment for a specific run by ID. " +
|
|
150
|
+
"Returns status, effects, journal summary, and entrypoint info.",
|
|
151
|
+
parameters: typebox_1.Type.Object({
|
|
152
|
+
runId: typebox_1.Type.String({ description: "The run ID (or prefix) to assess" }),
|
|
153
|
+
}),
|
|
154
|
+
execute: async (_toolCallId, params) => {
|
|
155
|
+
writeVerboseData("resume tool babysitter_assess_run", params);
|
|
156
|
+
const runDir = (0, babysitter_sdk_1.resolveExistingRunDir)(params.runId, {
|
|
157
|
+
cwd: args.workspace ?? process.cwd(),
|
|
158
|
+
override: runsDir,
|
|
159
|
+
});
|
|
160
|
+
try {
|
|
161
|
+
const assessment = await (0, harness_2.assessRun)(runDir);
|
|
162
|
+
return (0, harness_1.formatToolResult)({
|
|
163
|
+
runId: assessment.run.runId,
|
|
164
|
+
processId: assessment.run.processId,
|
|
165
|
+
status: assessment.run.status,
|
|
166
|
+
createdAt: assessment.run.createdAt,
|
|
167
|
+
prompt: assessment.run.prompt,
|
|
168
|
+
totalEffects: assessment.run.totalEffects,
|
|
169
|
+
resolvedEffects: assessment.run.resolvedEffects,
|
|
170
|
+
pendingEffects: assessment.run.pendingEffects,
|
|
171
|
+
journalEvents: assessment.journalLength,
|
|
172
|
+
lastEvent: assessment.lastEvent,
|
|
173
|
+
entrypoint: assessment.run.entrypoint,
|
|
174
|
+
runDir: assessment.run.runDir,
|
|
175
|
+
resumable: assessment.run.status !== "completed",
|
|
176
|
+
}, `Run ${assessment.run.runId} assessed.`);
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
180
|
+
return errorResult(`Failed to assess run "${params.runId}": ${msg}`);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "babysitter_resume_run",
|
|
186
|
+
label: "Resume Run",
|
|
187
|
+
description: "Resume orchestration for a specific run. Call this exactly once " +
|
|
188
|
+
"after assessing the run and confirming it should be resumed.",
|
|
189
|
+
parameters: typebox_1.Type.Object({
|
|
190
|
+
runId: typebox_1.Type.String({ description: "The run ID to resume" }),
|
|
191
|
+
}),
|
|
192
|
+
execute: async (_toolCallId, params) => {
|
|
193
|
+
writeVerboseData("resume tool babysitter_resume_run", params);
|
|
194
|
+
if (resumeTriggered) {
|
|
195
|
+
return errorResult("Resume has already been triggered for this session.");
|
|
196
|
+
}
|
|
197
|
+
const runDir = (0, babysitter_sdk_1.resolveExistingRunDir)(params.runId, {
|
|
198
|
+
cwd: args.workspace ?? process.cwd(),
|
|
199
|
+
override: runsDir,
|
|
200
|
+
});
|
|
201
|
+
try {
|
|
202
|
+
const assessment = await (0, harness_2.assessRun)(runDir);
|
|
203
|
+
const selectedRun = assessment.run;
|
|
204
|
+
if (selectedRun.status === "completed") {
|
|
205
|
+
return (0, harness_1.formatToolResult)({ runId: selectedRun.runId, status: "completed" }, "Run is already completed. Nothing to resume.");
|
|
206
|
+
}
|
|
207
|
+
resumeTriggered = true;
|
|
208
|
+
if (!json && outputMode !== "tui") {
|
|
209
|
+
process.stderr.write(`${harness_1.MAGENTA}Resuming run ${harness_1.BOLD}${selectedRun.runId}${harness_1.RESET}${harness_1.MAGENTA}...${harness_1.RESET}\n\n`);
|
|
210
|
+
}
|
|
211
|
+
// Resolve the process entry path
|
|
212
|
+
const entryImportPath = selectedRun.entrypoint.importPath;
|
|
213
|
+
const processPath = path.isAbsolute(entryImportPath)
|
|
214
|
+
? entryImportPath
|
|
215
|
+
: path.resolve(entryImportPath);
|
|
216
|
+
resumeExitCode = await (0, createRun_1.handleHarnessCreateRun)({
|
|
217
|
+
processPath,
|
|
218
|
+
prompt: selectedRun.prompt,
|
|
219
|
+
harness: args.harness,
|
|
220
|
+
workspace: args.workspace,
|
|
221
|
+
model: args.model,
|
|
222
|
+
maxIterations: args.maxIterations,
|
|
223
|
+
runsDir: args.runsDir,
|
|
224
|
+
json: args.json,
|
|
225
|
+
verbose: args.verbose,
|
|
226
|
+
interactive: args.interactive,
|
|
227
|
+
existingRunId: selectedRun.runId,
|
|
228
|
+
existingRunDir: selectedRun.runDir,
|
|
229
|
+
outputMode,
|
|
230
|
+
});
|
|
231
|
+
return (0, harness_1.formatToolResult)({ runId: selectedRun.runId, exitCode: resumeExitCode }, resumeExitCode === 0
|
|
232
|
+
? "Run resumed successfully."
|
|
233
|
+
: `Run resume completed with exit code ${resumeExitCode}.`);
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
237
|
+
return errorResult(`Failed to resume run "${params.runId}": ${msg}`);
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
// -----------------------------------------------------------------
|
|
243
|
+
// Build agentic tools (read/write/edit/grep/bash/etc.)
|
|
244
|
+
// -----------------------------------------------------------------
|
|
245
|
+
const agenticTools = (0, tula_core_2.createAgentCoreToolDefinitions)({
|
|
246
|
+
workspace: args.workspace ?? process.cwd(),
|
|
247
|
+
interactive: interactive,
|
|
248
|
+
askUserQuestionHandler: interactive
|
|
249
|
+
? undefined // Let the default AskUserQuestion handler work
|
|
250
|
+
: undefined,
|
|
251
|
+
});
|
|
252
|
+
const mergedCustomTools = [...customTools, ...agenticTools];
|
|
253
|
+
writeVerbose(`[resume setup] runsDir=${runsDir} workspace=${path.resolve(args.workspace ?? process.cwd())} model=${args.model ?? "(default)"}`);
|
|
254
|
+
writeVerboseData("resume tools", mergedCustomTools.map((tool) => ({
|
|
255
|
+
name: tool.name,
|
|
256
|
+
label: tool.label,
|
|
257
|
+
})));
|
|
258
|
+
// -----------------------------------------------------------------
|
|
259
|
+
// Create Pi session
|
|
260
|
+
// -----------------------------------------------------------------
|
|
261
|
+
const systemPrompt = buildResumeSystemPrompt(runsDir, args.runId);
|
|
262
|
+
const userPrompt = buildResumeUserPrompt(args.runId);
|
|
263
|
+
writeVerboseData("resume system prompt", systemPrompt);
|
|
264
|
+
writeVerboseData("resume user prompt", userPrompt);
|
|
265
|
+
let session = null;
|
|
266
|
+
try {
|
|
267
|
+
session = (0, tula_core_1.createAgentCoreSession)({
|
|
268
|
+
workspace: args.workspace,
|
|
269
|
+
model: args.model,
|
|
270
|
+
thinkingLevel: "low",
|
|
271
|
+
toolsMode: "coding",
|
|
272
|
+
customTools: mergedCustomTools,
|
|
273
|
+
systemPrompt,
|
|
274
|
+
isolated: true,
|
|
275
|
+
ephemeral: true,
|
|
276
|
+
});
|
|
277
|
+
await session.initialize();
|
|
278
|
+
// Subscribe to stream text output to stderr in non-JSON mode
|
|
279
|
+
let unsubscribe = null;
|
|
280
|
+
if (!json && outputMode !== "tui") {
|
|
281
|
+
process.stderr.write(`\n${harness_1.BOLD}${harness_1.MAGENTA}Run Discovery${harness_1.RESET} ${harness_1.DIM}Agent is searching for runs...${harness_1.RESET}\n\n`);
|
|
282
|
+
unsubscribe = session.subscribe((event) => {
|
|
283
|
+
if (event.type === "text_delta") {
|
|
284
|
+
const text = event.text;
|
|
285
|
+
if (text)
|
|
286
|
+
process.stderr.write(text);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
// Prompt the agent
|
|
291
|
+
const result = await session.prompt(userPrompt, 300_000);
|
|
292
|
+
if (unsubscribe)
|
|
293
|
+
unsubscribe();
|
|
294
|
+
if (!json && outputMode !== "tui")
|
|
295
|
+
process.stderr.write("\n");
|
|
296
|
+
writeVerboseData("resume agent result", {
|
|
297
|
+
success: result.success,
|
|
298
|
+
outputPreview: result.output.length > 500
|
|
299
|
+
? result.output.slice(0, 497) + "..."
|
|
300
|
+
: result.output,
|
|
301
|
+
resumeTriggered,
|
|
302
|
+
});
|
|
303
|
+
if (!result.success && !resumeTriggered) {
|
|
304
|
+
if (json) {
|
|
305
|
+
process.stdout.write(JSON.stringify({ ok: false, error: "Resume agent failed", details: result.output }) + "\n");
|
|
306
|
+
}
|
|
307
|
+
else if (outputMode !== "tui") {
|
|
308
|
+
process.stderr.write(`${harness_1.RED}Resume agent failed:${harness_1.RESET} ${result.output}\n`);
|
|
309
|
+
}
|
|
310
|
+
return 1;
|
|
311
|
+
}
|
|
312
|
+
// If the agent never triggered resume, it decided not to (e.g., no runs or user quit)
|
|
313
|
+
if (!resumeTriggered) {
|
|
314
|
+
if (json) {
|
|
315
|
+
process.stdout.write(JSON.stringify({ ok: true, resumed: false, message: "No run was resumed" }) + "\n");
|
|
316
|
+
}
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
return resumeExitCode;
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
323
|
+
if (json) {
|
|
324
|
+
process.stdout.write(JSON.stringify({ ok: false, error: "Resume session failed", details: message }) + "\n");
|
|
325
|
+
}
|
|
326
|
+
else if (outputMode !== "tui") {
|
|
327
|
+
process.stderr.write(`${harness_1.RED}Error:${harness_1.RESET} ${message}\n`);
|
|
328
|
+
}
|
|
329
|
+
return 1;
|
|
330
|
+
}
|
|
331
|
+
finally {
|
|
332
|
+
if (session) {
|
|
333
|
+
try {
|
|
334
|
+
session.dispose();
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
// Ignore cleanup errors
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAP-JSON-004: Streaming JSONL CLI Mode (stdin/stdout).
|
|
3
|
+
*
|
|
4
|
+
* Reads JSONL requests from stdin, dispatches to API functions, writes
|
|
5
|
+
* JSONL responses to stdout. Enables programmatic control from any
|
|
6
|
+
* language via process pipes — no HTTP, no WebSocket.
|
|
7
|
+
*/
|
|
8
|
+
import type { ApiResult } from "@a5c-ai/agent-platform/api";
|
|
9
|
+
interface ParsedRequest {
|
|
10
|
+
id: string;
|
|
11
|
+
method: string;
|
|
12
|
+
params: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
interface ParseError {
|
|
15
|
+
id: null;
|
|
16
|
+
error: {
|
|
17
|
+
code: string;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
interface StreamOptions {
|
|
22
|
+
stdin: NodeJS.ReadableStream;
|
|
23
|
+
stdout: NodeJS.WritableStream;
|
|
24
|
+
}
|
|
25
|
+
export declare const SUPPORTED_METHODS: readonly ["run.create", "run.iterate", "run.status", "run.events", "effect.commit", "effect.list", "effect.show", "effect.cancel", "effect.batchCommit", "breakpoint.list", "breakpoint.show", "breakpoint.respond", "breakpoint.listRules", "breakpoint.addRule", "breakpoint.removeRule", "breakpoint.evaluateAutoApproval", "event.subscribe", "event.unsubscribe", "shutdown"];
|
|
26
|
+
export declare function parseJsonlRequest(line: string): ParsedRequest | ParseError;
|
|
27
|
+
export declare function formatJsonlResponse(id: string | null, result: ApiResult<unknown>): string;
|
|
28
|
+
export declare function dispatchJsonlMethod(method: string, params: Record<string, unknown>, defaults: {
|
|
29
|
+
runsDir: string;
|
|
30
|
+
}): Promise<ApiResult<unknown>>;
|
|
31
|
+
export declare function handleJsonlInteractive(args: {
|
|
32
|
+
runsDir: string;
|
|
33
|
+
}, streamOpts?: StreamOptions): Promise<number>;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=jsonlInteractive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonlInteractive.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/jsonlInteractive.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA8BH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAI5D,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,UAAU,UAAU;IAClB,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1C;AAED,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC,cAAc,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC/B;AAID,eAAO,MAAM,iBAAiB,oXAoBpB,CAAC;AAIX,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,UAAU,CAyC1E;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,MAAM,CAWzF;AAID,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAC5B,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAoJ7B;AAMD,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EACzB,UAAU,CAAC,EAAE,aAAa,GACzB,OAAO,CAAC,MAAM,CAAC,CAqFjB"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GAP-JSON-004: Streaming JSONL CLI Mode (stdin/stdout).
|
|
4
|
+
*
|
|
5
|
+
* Reads JSONL requests from stdin, dispatches to API functions, writes
|
|
6
|
+
* JSONL responses to stdout. Enables programmatic control from any
|
|
7
|
+
* language via process pipes — no HTTP, no WebSocket.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.SUPPORTED_METHODS = void 0;
|
|
11
|
+
exports.parseJsonlRequest = parseJsonlRequest;
|
|
12
|
+
exports.formatJsonlResponse = formatJsonlResponse;
|
|
13
|
+
exports.dispatchJsonlMethod = dispatchJsonlMethod;
|
|
14
|
+
exports.handleJsonlInteractive = handleJsonlInteractive;
|
|
15
|
+
const node_readline_1 = require("node:readline");
|
|
16
|
+
const api_1 = require("@a5c-ai/agent-platform/api");
|
|
17
|
+
const api_2 = require("@a5c-ai/agent-platform/api");
|
|
18
|
+
const api_3 = require("@a5c-ai/agent-platform/api");
|
|
19
|
+
const api_4 = require("@a5c-ai/agent-platform/api");
|
|
20
|
+
// ── Supported methods ──────────────────────────────────────────────────────
|
|
21
|
+
exports.SUPPORTED_METHODS = [
|
|
22
|
+
"run.create",
|
|
23
|
+
"run.iterate",
|
|
24
|
+
"run.status",
|
|
25
|
+
"run.events",
|
|
26
|
+
"effect.commit",
|
|
27
|
+
"effect.list",
|
|
28
|
+
"effect.show",
|
|
29
|
+
"effect.cancel",
|
|
30
|
+
"effect.batchCommit",
|
|
31
|
+
"breakpoint.list",
|
|
32
|
+
"breakpoint.show",
|
|
33
|
+
"breakpoint.respond",
|
|
34
|
+
"breakpoint.listRules",
|
|
35
|
+
"breakpoint.addRule",
|
|
36
|
+
"breakpoint.removeRule",
|
|
37
|
+
"breakpoint.evaluateAutoApproval",
|
|
38
|
+
"event.subscribe",
|
|
39
|
+
"event.unsubscribe",
|
|
40
|
+
"shutdown",
|
|
41
|
+
];
|
|
42
|
+
// ── Pure functions ─────────────────────────────────────────────────────────
|
|
43
|
+
function parseJsonlRequest(line) {
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(line);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return {
|
|
50
|
+
id: null,
|
|
51
|
+
error: { code: "INVALID_REQUEST", message: "Malformed JSON" },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
55
|
+
return {
|
|
56
|
+
id: null,
|
|
57
|
+
error: { code: "INVALID_REQUEST", message: "Request must be a JSON object" },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const obj = parsed;
|
|
61
|
+
if (obj.id === undefined || obj.id === null) {
|
|
62
|
+
return {
|
|
63
|
+
id: null,
|
|
64
|
+
error: { code: "INVALID_REQUEST", message: "Missing required field: id" },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (typeof obj.method !== "string" || !obj.method) {
|
|
68
|
+
return {
|
|
69
|
+
id: null,
|
|
70
|
+
error: { code: "INVALID_REQUEST", message: "Missing required field: method" },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
id: String(obj.id),
|
|
75
|
+
method: obj.method,
|
|
76
|
+
params: (typeof obj.params === "object" && obj.params !== null && !Array.isArray(obj.params))
|
|
77
|
+
? obj.params
|
|
78
|
+
: {},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function formatJsonlResponse(id, result) {
|
|
82
|
+
try {
|
|
83
|
+
if (result.ok) {
|
|
84
|
+
return JSON.stringify({ id, result: result.data });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
return JSON.stringify({ id, error: result.error });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
92
|
+
return JSON.stringify({ id, error: { code: "SERIALIZATION_ERROR", message: msg } });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// ── Dispatch table ─────────────────────────────────────────────────────────
|
|
96
|
+
async function dispatchJsonlMethod(method, params, defaults) {
|
|
97
|
+
try {
|
|
98
|
+
const p = params;
|
|
99
|
+
const runsDir = (typeof p.runsDir === "string" ? p.runsDir : defaults.runsDir);
|
|
100
|
+
switch (method) {
|
|
101
|
+
// ── Run lifecycle ──
|
|
102
|
+
case "run.create":
|
|
103
|
+
return (0, api_1.apiCreateRun)({
|
|
104
|
+
processId: p.processId,
|
|
105
|
+
entrypoint: p.entrypoint,
|
|
106
|
+
runsDir,
|
|
107
|
+
inputs: p.inputs,
|
|
108
|
+
prompt: p.prompt,
|
|
109
|
+
});
|
|
110
|
+
case "run.iterate":
|
|
111
|
+
return (0, api_1.apiIterate)({ runDir: p.runDir });
|
|
112
|
+
case "run.status":
|
|
113
|
+
return (0, api_1.apiRunStatus)({ runId: p.runId, runsDir });
|
|
114
|
+
case "run.events":
|
|
115
|
+
return (0, api_1.apiRunEvents)({
|
|
116
|
+
runId: p.runId,
|
|
117
|
+
runsDir,
|
|
118
|
+
limit: typeof p.limit === "number" ? p.limit : undefined,
|
|
119
|
+
filterType: typeof p.filterType === "string" ? p.filterType : undefined,
|
|
120
|
+
});
|
|
121
|
+
// ── Effect dispatch ──
|
|
122
|
+
case "effect.commit":
|
|
123
|
+
return (0, api_1.apiCommitEffect)({
|
|
124
|
+
runDir: p.runDir,
|
|
125
|
+
effectId: p.effectId,
|
|
126
|
+
result: p.result,
|
|
127
|
+
});
|
|
128
|
+
case "effect.list":
|
|
129
|
+
return (0, api_2.apiListEffects)({
|
|
130
|
+
runDir: p.runDir,
|
|
131
|
+
filter: p.filter,
|
|
132
|
+
});
|
|
133
|
+
case "effect.show":
|
|
134
|
+
return (0, api_2.apiShowEffect)({
|
|
135
|
+
runDir: p.runDir,
|
|
136
|
+
effectId: p.effectId,
|
|
137
|
+
});
|
|
138
|
+
case "effect.cancel":
|
|
139
|
+
return (0, api_2.apiCancelEffect)({
|
|
140
|
+
runDir: p.runDir,
|
|
141
|
+
effectId: p.effectId,
|
|
142
|
+
reason: p.reason,
|
|
143
|
+
});
|
|
144
|
+
case "effect.batchCommit":
|
|
145
|
+
return (0, api_2.apiBatchCommitEffects)({
|
|
146
|
+
runDir: p.runDir,
|
|
147
|
+
effects: p.effects,
|
|
148
|
+
});
|
|
149
|
+
// ── Breakpoint interaction ──
|
|
150
|
+
case "breakpoint.list":
|
|
151
|
+
return (0, api_3.apiListBreakpoints)({ runDir: p.runDir });
|
|
152
|
+
case "breakpoint.show":
|
|
153
|
+
return (0, api_3.apiShowBreakpoint)({
|
|
154
|
+
runDir: p.runDir,
|
|
155
|
+
effectId: p.effectId,
|
|
156
|
+
});
|
|
157
|
+
case "breakpoint.respond":
|
|
158
|
+
return (0, api_3.apiRespondToBreakpoint)({
|
|
159
|
+
runDir: p.runDir,
|
|
160
|
+
effectId: p.effectId,
|
|
161
|
+
approved: p.approved,
|
|
162
|
+
response: p.response,
|
|
163
|
+
feedback: p.feedback,
|
|
164
|
+
option: p.option,
|
|
165
|
+
respondedBy: p.respondedBy,
|
|
166
|
+
});
|
|
167
|
+
case "breakpoint.listRules":
|
|
168
|
+
return (0, api_3.apiListAutoApprovalRules)({ rulesPath: p.rulesPath });
|
|
169
|
+
case "breakpoint.addRule":
|
|
170
|
+
return (0, api_3.apiAddAutoApprovalRule)({
|
|
171
|
+
pattern: p.pattern,
|
|
172
|
+
action: p.action,
|
|
173
|
+
createdBy: p.createdBy,
|
|
174
|
+
id: p.id,
|
|
175
|
+
source: p.source,
|
|
176
|
+
note: p.note,
|
|
177
|
+
rulesPath: p.rulesPath,
|
|
178
|
+
});
|
|
179
|
+
case "breakpoint.removeRule":
|
|
180
|
+
return (0, api_3.apiRemoveAutoApprovalRule)({
|
|
181
|
+
ruleId: p.ruleId,
|
|
182
|
+
rulesPath: p.rulesPath,
|
|
183
|
+
});
|
|
184
|
+
case "breakpoint.evaluateAutoApproval":
|
|
185
|
+
return (0, api_3.apiEvaluateAutoApproval)({
|
|
186
|
+
breakpointId: p.breakpointId,
|
|
187
|
+
tags: p.tags,
|
|
188
|
+
expert: p.expert,
|
|
189
|
+
rulesPath: p.rulesPath,
|
|
190
|
+
autoApproveAfterN: p.autoApproveAfterN,
|
|
191
|
+
consecutiveApprovals: p.consecutiveApprovals,
|
|
192
|
+
});
|
|
193
|
+
// ── Event streaming ──
|
|
194
|
+
case "event.subscribe": {
|
|
195
|
+
const subResult = await (0, api_4.apiSubscribeRunEvents)({
|
|
196
|
+
runId: p.runId,
|
|
197
|
+
runsDir,
|
|
198
|
+
afterSeq: typeof p.afterSeq === "number" ? p.afterSeq : undefined,
|
|
199
|
+
pollIntervalMs: typeof p.pollIntervalMs === "number" ? p.pollIntervalMs : undefined,
|
|
200
|
+
onEvent: () => {
|
|
201
|
+
// Events are consumed via polling; JSONL doesn't push events
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
return subResult;
|
|
205
|
+
}
|
|
206
|
+
case "event.unsubscribe":
|
|
207
|
+
return (0, api_4.apiUnsubscribeRunEvents)({
|
|
208
|
+
subscriptionId: p.subscriptionId,
|
|
209
|
+
});
|
|
210
|
+
// ── Control ──
|
|
211
|
+
case "shutdown":
|
|
212
|
+
(0, api_4.closeAllSubscriptions)();
|
|
213
|
+
return { ok: true, data: { ok: true } };
|
|
214
|
+
default:
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
error: { code: "UNKNOWN_METHOD", message: `Unknown method: ${method}` },
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
223
|
+
return { ok: false, error: { code: "INTERNAL_ERROR", message: msg } };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// ── Main handler ───────────────────────────────────────────────────────────
|
|
227
|
+
const MAX_PENDING_QUEUE = 100;
|
|
228
|
+
async function handleJsonlInteractive(args, streamOpts) {
|
|
229
|
+
const stdin = streamOpts?.stdin ?? process.stdin;
|
|
230
|
+
const stdout = streamOpts?.stdout ?? process.stdout;
|
|
231
|
+
const writeLine = (line) => {
|
|
232
|
+
return stdout.write(line + "\n");
|
|
233
|
+
};
|
|
234
|
+
// Emit ready notification
|
|
235
|
+
writeLine(JSON.stringify({
|
|
236
|
+
jsonl: "ready",
|
|
237
|
+
version: 1,
|
|
238
|
+
methods: [...exports.SUPPORTED_METHODS],
|
|
239
|
+
}));
|
|
240
|
+
const rl = (0, node_readline_1.createInterface)({
|
|
241
|
+
input: stdin,
|
|
242
|
+
crlfDelay: Infinity,
|
|
243
|
+
});
|
|
244
|
+
let shutdownRequested = false;
|
|
245
|
+
let activeCount = 0;
|
|
246
|
+
const pending = new Set();
|
|
247
|
+
const processLine = async (line) => {
|
|
248
|
+
const trimmed = line.trim();
|
|
249
|
+
if (!trimmed)
|
|
250
|
+
return;
|
|
251
|
+
const parsed = parseJsonlRequest(trimmed);
|
|
252
|
+
if ("error" in parsed) {
|
|
253
|
+
writeLine(formatJsonlResponse(parsed.id, {
|
|
254
|
+
ok: false,
|
|
255
|
+
error: parsed.error,
|
|
256
|
+
}));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const result = await dispatchJsonlMethod(parsed.method, parsed.params, {
|
|
261
|
+
runsDir: args.runsDir,
|
|
262
|
+
});
|
|
263
|
+
writeLine(formatJsonlResponse(parsed.id, result));
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
267
|
+
writeLine(formatJsonlResponse(parsed.id, {
|
|
268
|
+
ok: false,
|
|
269
|
+
error: { code: "INTERNAL_ERROR", message: msg },
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
if (parsed.method === "shutdown") {
|
|
273
|
+
shutdownRequested = true;
|
|
274
|
+
rl.close();
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
return new Promise((resolve) => {
|
|
278
|
+
rl.on("line", (line) => {
|
|
279
|
+
if (shutdownRequested)
|
|
280
|
+
return;
|
|
281
|
+
activeCount++;
|
|
282
|
+
const promise = processLine(line).finally(() => {
|
|
283
|
+
activeCount--;
|
|
284
|
+
pending.delete(promise);
|
|
285
|
+
if (activeCount < MAX_PENDING_QUEUE) {
|
|
286
|
+
rl.resume();
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
pending.add(promise);
|
|
290
|
+
// Backpressure: pause stdin when too many pending
|
|
291
|
+
if (activeCount >= MAX_PENDING_QUEUE) {
|
|
292
|
+
rl.pause();
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
rl.on("close", () => {
|
|
296
|
+
// Wait for all pending requests to complete
|
|
297
|
+
void Promise.allSettled([...pending]).then(() => {
|
|
298
|
+
resolve(0);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|