@loopstack/cli 0.17.0 → 0.18.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 +14 -10
- package/dist/commands/answer.d.ts +3 -0
- package/dist/commands/answer.d.ts.map +1 -0
- package/dist/commands/answer.js +82 -0
- package/dist/commands/answer.js.map +1 -0
- package/dist/commands/attach.d.ts +3 -0
- package/dist/commands/attach.d.ts.map +1 -0
- package/dist/commands/attach.js +103 -0
- package/dist/commands/attach.js.map +1 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +38 -13
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/runs.d.ts.map +1 -1
- package/dist/commands/runs.js +67 -69
- package/dist/commands/runs.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +3 -1
- package/dist/commands/watch.js.map +1 -1
- package/dist/hitl/discovery.d.ts +15 -7
- package/dist/hitl/discovery.d.ts.map +1 -1
- package/dist/hitl/discovery.js +85 -28
- package/dist/hitl/discovery.js.map +1 -1
- package/dist/hitl/idle-handler.d.ts +0 -1
- package/dist/hitl/idle-handler.d.ts.map +1 -1
- package/dist/hitl/idle-handler.js +49 -17
- package/dist/hitl/idle-handler.js.map +1 -1
- package/dist/hitl/pending.d.ts +16 -0
- package/dist/hitl/pending.d.ts.map +1 -0
- package/dist/hitl/pending.js +45 -0
- package/dist/hitl/pending.js.map +1 -0
- package/dist/hitl/prompt.d.ts +5 -8
- package/dist/hitl/prompt.d.ts.map +1 -1
- package/dist/hitl/prompt.js +47 -105
- package/dist/hitl/prompt.js.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/output/format.d.ts +1 -1
- package/dist/output/format.d.ts.map +1 -1
- package/dist/output/format.js +16 -5
- package/dist/output/format.js.map +1 -1
- package/dist/run/documents.d.ts +13 -1
- package/dist/run/documents.d.ts.map +1 -1
- package/dist/run/documents.js +82 -22
- package/dist/run/documents.js.map +1 -1
- package/dist/run/follow.d.ts +6 -2
- package/dist/run/follow.d.ts.map +1 -1
- package/dist/run/follow.js +61 -9
- package/dist/run/follow.js.map +1 -1
- package/dist/run/retry.d.ts +5 -0
- package/dist/run/retry.d.ts.map +1 -0
- package/dist/run/retry.js +59 -0
- package/dist/run/retry.js.map +1 -0
- package/dist/run/trail.d.ts +17 -0
- package/dist/run/trail.d.ts.map +1 -0
- package/dist/run/trail.js +42 -0
- package/dist/run/trail.js.map +1 -0
- package/dist/widgets/collect/form.collect.d.ts +7 -0
- package/dist/widgets/collect/form.collect.d.ts.map +1 -0
- package/dist/widgets/collect/form.collect.js +71 -0
- package/dist/widgets/collect/form.collect.js.map +1 -0
- package/dist/widgets/collect/prompts.collect.d.ts +5 -0
- package/dist/widgets/collect/prompts.collect.d.ts.map +1 -0
- package/dist/widgets/collect/prompts.collect.js +38 -0
- package/dist/widgets/collect/prompts.collect.js.map +1 -0
- package/dist/widgets/collect/secret-input.collect.d.ts +3 -0
- package/dist/widgets/collect/secret-input.collect.d.ts.map +1 -0
- package/dist/widgets/collect/secret-input.collect.js +47 -0
- package/dist/widgets/collect/secret-input.collect.js.map +1 -0
- package/dist/widgets/collect/transition.d.ts +3 -0
- package/dist/widgets/collect/transition.d.ts.map +1 -0
- package/dist/widgets/collect/transition.js +5 -0
- package/dist/widgets/collect/transition.js.map +1 -0
- package/dist/widgets/collect/workflow.collect.d.ts +4 -0
- package/dist/widgets/collect/workflow.collect.d.ts.map +1 -0
- package/dist/widgets/collect/workflow.collect.js +24 -0
- package/dist/widgets/collect/workflow.collect.js.map +1 -0
- package/dist/widgets/document/form.widget.d.ts +3 -0
- package/dist/widgets/document/form.widget.d.ts.map +1 -0
- package/dist/widgets/document/form.widget.js +34 -0
- package/dist/widgets/document/form.widget.js.map +1 -0
- package/dist/widgets/document/json.widget.d.ts +3 -0
- package/dist/widgets/document/json.widget.d.ts.map +1 -0
- package/dist/widgets/document/json.widget.js +17 -0
- package/dist/widgets/document/json.widget.js.map +1 -0
- package/dist/widgets/document/link.widget.d.ts +3 -0
- package/dist/widgets/document/link.widget.d.ts.map +1 -0
- package/dist/widgets/document/link.widget.js +9 -0
- package/dist/widgets/document/link.widget.js.map +1 -0
- package/dist/widgets/document/llm-message.widget.d.ts +3 -0
- package/dist/widgets/document/llm-message.widget.d.ts.map +1 -0
- package/dist/widgets/document/llm-message.widget.js +67 -0
- package/dist/widgets/document/llm-message.widget.js.map +1 -0
- package/dist/widgets/document/message.widget.d.ts +5 -0
- package/dist/widgets/document/message.widget.d.ts.map +1 -0
- package/dist/widgets/document/message.widget.js +35 -0
- package/dist/widgets/document/message.widget.js.map +1 -0
- package/dist/widgets/document/oauth-prompt.widget.d.ts +4 -0
- package/dist/widgets/document/oauth-prompt.widget.d.ts.map +1 -0
- package/dist/widgets/document/oauth-prompt.widget.js +22 -0
- package/dist/widgets/document/oauth-prompt.widget.js.map +1 -0
- package/dist/widgets/document/secret-request.widget.d.ts +3 -0
- package/dist/widgets/document/secret-request.widget.d.ts.map +1 -0
- package/dist/widgets/document/secret-request.widget.js +9 -0
- package/dist/widgets/document/secret-request.widget.js.map +1 -0
- package/dist/{hitl → widgets}/editor.d.ts +0 -1
- package/dist/widgets/editor.d.ts.map +1 -0
- package/dist/{hitl → widgets}/editor.js +0 -13
- package/dist/widgets/editor.js.map +1 -0
- package/dist/widgets/field/field-widgets.d.ts +6 -0
- package/dist/widgets/field/field-widgets.d.ts.map +1 -0
- package/dist/widgets/field/field-widgets.js +64 -0
- package/dist/widgets/field/field-widgets.js.map +1 -0
- package/dist/widgets/registry.d.ts +6 -0
- package/dist/widgets/registry.d.ts.map +1 -0
- package/dist/widgets/registry.js +45 -0
- package/dist/widgets/registry.js.map +1 -0
- package/dist/widgets/types.d.ts +44 -0
- package/dist/widgets/types.d.ts.map +1 -0
- package/dist/widgets/types.js +2 -0
- package/dist/widgets/types.js.map +1 -0
- package/fixtures/app/CLAUDE.md +15 -1
- package/fixtures/app/src/main.ts +3 -0
- package/package.json +3 -3
- package/dist/hitl/editor.d.ts.map +0 -1
- package/dist/hitl/editor.js.map +0 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ result: {"greeting":"Hello, Maya! 👋"}
|
|
|
24
24
|
■ run completed in 96ms
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
When a run asks a question, answer it right there — or in Studio; whichever answers first wins, and the CLI keeps following either way.
|
|
27
|
+
When a run asks a question, answer it right there — or in Studio; whichever answers first wins, and the CLI keeps following either way. Choices, confirms, chat inputs, and secret entry render as native prompts; forms submit as-is or open the complete content in `$EDITOR` first:
|
|
28
28
|
|
|
29
29
|
```
|
|
30
30
|
⏸ waiting_for_answer
|
|
@@ -36,17 +36,21 @@ answer: 2
|
|
|
36
36
|
■ run completed
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
Input only Studio can collect is named explicitly (with a deep link) instead of hanging, and browser-based waits — like an OAuth sign-in link — resume the attached session automatically once completed.
|
|
40
|
+
|
|
39
41
|
## Commands
|
|
40
42
|
|
|
41
|
-
| Command | What it does
|
|
42
|
-
| --------------------------- |
|
|
43
|
-
| `loopstack create <dir>` | Scaffold a new Loopstack app (NestJS + LoopstackModule + hello workflow + docker-compose)
|
|
44
|
-
| `loopstack list [workflow]` | Workflows you can run; with a name: its arguments and a ready-to-run example
|
|
45
|
-
| `loopstack run <workflow>` | Start a run and follow it live; `--arg k=v`, `--arg k=@file.json`, `--arg k=@-` (stdin), `--detach`, `--quiet`, `--open`, `--json`
|
|
46
|
-
| `loopstack runs [run-id]` | Recent runs (waiting-for-input first) or one run’s
|
|
47
|
-
| `loopstack
|
|
48
|
-
| `loopstack
|
|
49
|
-
| `loopstack
|
|
43
|
+
| Command | What it does |
|
|
44
|
+
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| `loopstack create <dir>` | Scaffold a new Loopstack app (NestJS + LoopstackModule + hello workflow + docker-compose) |
|
|
46
|
+
| `loopstack list [workflow]` | Workflows you can run; with a name: its arguments and a ready-to-run example |
|
|
47
|
+
| `loopstack run <workflow>` | Start a run and follow it live; `--arg k=v`, `--arg k=@file.json`, `--arg k=@-` (stdin), `--detach`, `--quiet`, `--open`, `--json` |
|
|
48
|
+
| `loopstack runs [run-id]` | Recent runs (waiting-for-input first) or one run’s full transcript; `--json` includes the pending prompt (`pendingPrompt`), `--record <file>` derives a replay fixture from the run’s recorded tool calls |
|
|
49
|
+
| `loopstack answer <run-id>` | Answer a waiting run’s prompt non-interactively — `--arg k=v` fields or `--payload <json>`; for agents and scripts |
|
|
50
|
+
| `loopstack attach <run-id>` | Rejoin a run live — transcript so far, then streaming output and interactive prompts (docker/tmux semantics) |
|
|
51
|
+
| `loopstack watch` | Live event stream; `--workflow`, `--type`, NDJSON with `--json` |
|
|
52
|
+
| `loopstack login` | Save a backend environment to `~/.loopstack/config.json` |
|
|
53
|
+
| `loopstack env list\|use` | Manage saved environments |
|
|
50
54
|
|
|
51
55
|
## CI
|
|
52
56
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/commands/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBzC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyD5D"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { createClientFor, resolveConnection } from '../config/resolve.js';
|
|
4
|
+
import { CliError, ExitCode } from '../errors.js';
|
|
5
|
+
import { inspectPendingPrompt } from '../hitl/pending.js';
|
|
6
|
+
import { printData, printStatus } from '../output/format.js';
|
|
7
|
+
import { parseRunArgs } from '../run/args.js';
|
|
8
|
+
const collect = (value, previous) => [...previous, value];
|
|
9
|
+
export function registerAnswerCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command('answer <runId>')
|
|
12
|
+
.description('Answer the prompt a waiting run is parked on — non-interactive (for agents and scripts)')
|
|
13
|
+
.option('--arg <key=value>', 'answer field (repeatable); key=@file reads a file, key=@- reads stdin', collect, [])
|
|
14
|
+
.option('--payload <json>', 'full answer payload as JSON; @file reads a file, @- reads stdin')
|
|
15
|
+
.option('--transition <id>', 'wait transition to answer (required when several are available)')
|
|
16
|
+
.action(async (runId, options, cmd) => {
|
|
17
|
+
const globals = cmd.optsWithGlobals();
|
|
18
|
+
const connection = resolveConnection(globals);
|
|
19
|
+
const client = createClientFor(connection);
|
|
20
|
+
const json = !!globals.json;
|
|
21
|
+
const pending = await inspectPendingPrompt(client, runId);
|
|
22
|
+
if (!pending) {
|
|
23
|
+
throw new CliError(`Run ${runId} has no pending prompt — nothing to answer.`);
|
|
24
|
+
}
|
|
25
|
+
if (pending.studioOnly) {
|
|
26
|
+
throw new CliError(`The pending input (${pending.widget}) can only be collected in Studio — the CLI cannot answer it.`);
|
|
27
|
+
}
|
|
28
|
+
const transition = options.transition ?? pending.transition;
|
|
29
|
+
if (!transition) {
|
|
30
|
+
throw new CliError(`Several transitions are available (${pending.transitions.join(', ')}) — pick one with --transition <id>.`);
|
|
31
|
+
}
|
|
32
|
+
if (!pending.transitions.includes(transition)) {
|
|
33
|
+
throw new CliError(`Transition '${transition}' is not available — currently available: ${pending.transitions.join(', ')}.`);
|
|
34
|
+
}
|
|
35
|
+
const payload = buildPayload(options);
|
|
36
|
+
await client.processor.run(pending.workflowId, {
|
|
37
|
+
transition: { id: transition, workflowId: pending.workflowId, payload },
|
|
38
|
+
});
|
|
39
|
+
if (json) {
|
|
40
|
+
printData(JSON.stringify({ runId, promptWorkflowId: pending.workflowId, transition, payload, submitted: true }, null, 2));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
printStatus(`${pc.green('✓')} answered '${transition}' on ${pending.workflowName}`);
|
|
44
|
+
printStatus(pc.dim(` follow with: loopstack attach ${runId}`));
|
|
45
|
+
}
|
|
46
|
+
process.exit(ExitCode.Success);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function buildPayload(options) {
|
|
50
|
+
if (options.payload !== undefined && options.arg.length > 0) {
|
|
51
|
+
throw new CliError('Use either --payload or --arg, not both.');
|
|
52
|
+
}
|
|
53
|
+
if (options.payload !== undefined) {
|
|
54
|
+
const raw = options.payload === '@-'
|
|
55
|
+
? readFileSync(0, 'utf8')
|
|
56
|
+
: options.payload.startsWith('@')
|
|
57
|
+
? readPayloadFile(options.payload.slice(1))
|
|
58
|
+
: options.payload;
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
62
|
+
throw new CliError('--payload must be a JSON object.');
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error instanceof CliError)
|
|
68
|
+
throw error;
|
|
69
|
+
throw new CliError(`--payload is not valid JSON: ${raw.slice(0, 120)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return parseRunArgs(options.arg);
|
|
73
|
+
}
|
|
74
|
+
function readPayloadFile(path) {
|
|
75
|
+
try {
|
|
76
|
+
return readFileSync(path, 'utf8');
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new CliError(`Cannot read --payload file "${path}".`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=answer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answer.js","sourceRoot":"","sources":["../../src/commands/answer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../../src/commands/attach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+BzC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyG5D"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { WorkflowState } from '@loopstack/contracts/enums';
|
|
3
|
+
import { createClientFor, resolveConnection } from '../config/resolve.js';
|
|
4
|
+
import { ExitCode } from '../errors.js';
|
|
5
|
+
import { createIdleHandler } from '../hitl/idle-handler.js';
|
|
6
|
+
import { printData, printStatus, renderResult } from '../output/format.js';
|
|
7
|
+
import { openInBrowser, studioRunUrl } from '../output/studio-link.js';
|
|
8
|
+
import { followRun } from '../run/follow.js';
|
|
9
|
+
import { offerRetry } from '../run/retry.js';
|
|
10
|
+
import { renderRunTrail } from '../run/trail.js';
|
|
11
|
+
const TERMINAL_STATES = [
|
|
12
|
+
WorkflowState.Completed,
|
|
13
|
+
WorkflowState.Failed,
|
|
14
|
+
WorkflowState.Canceled,
|
|
15
|
+
];
|
|
16
|
+
const IDLE_STATES = [WorkflowState.Waiting, WorkflowState.Paused];
|
|
17
|
+
function exitCodeForStatus(status) {
|
|
18
|
+
if (status === WorkflowState.Completed)
|
|
19
|
+
return ExitCode.Success;
|
|
20
|
+
if (IDLE_STATES.includes(status))
|
|
21
|
+
return ExitCode.NeedsInput;
|
|
22
|
+
return ExitCode.RunFailed;
|
|
23
|
+
}
|
|
24
|
+
export function registerAttachCommand(program) {
|
|
25
|
+
program
|
|
26
|
+
.command('attach <runId>')
|
|
27
|
+
.description('Attach to a run: transcript so far, then live progress and prompts (exit 0 completed / 1 failed / 3 waiting)')
|
|
28
|
+
.option('--open', 'open the run in Studio')
|
|
29
|
+
.action(async (runId, options, cmd) => {
|
|
30
|
+
const globals = cmd.optsWithGlobals();
|
|
31
|
+
const connection = resolveConnection(globals);
|
|
32
|
+
const client = createClientFor(connection);
|
|
33
|
+
const json = !!globals.json;
|
|
34
|
+
const out = json ? process.stderr : process.stdout;
|
|
35
|
+
const link = studioRunUrl(connection, runId);
|
|
36
|
+
if (options.open) {
|
|
37
|
+
if (link)
|
|
38
|
+
openInBrowser(link);
|
|
39
|
+
else
|
|
40
|
+
printStatus('No Studio URL configured for this environment — set one with `loopstack login`.');
|
|
41
|
+
}
|
|
42
|
+
const events = client.stream.events();
|
|
43
|
+
await client.stream.waitForOpen();
|
|
44
|
+
const workflow = await client.workflows.get(runId);
|
|
45
|
+
const checkpoints = await client.workflows.checkpoints(runId);
|
|
46
|
+
const { renderer, streamedMessageIds, streamedToolCallIds, visibleWorkflowIds } = await renderRunTrail(client, connection, out, workflow, checkpoints);
|
|
47
|
+
if (TERMINAL_STATES.includes(workflow.status)) {
|
|
48
|
+
if (json) {
|
|
49
|
+
printData(JSON.stringify({
|
|
50
|
+
workflowId: runId,
|
|
51
|
+
status: workflow.status,
|
|
52
|
+
result: workflow.result,
|
|
53
|
+
errorMessage: workflow.errorMessage,
|
|
54
|
+
}, null, 2));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
out.write('\n');
|
|
58
|
+
if (renderResult(out, workflow.result))
|
|
59
|
+
out.write('\n');
|
|
60
|
+
}
|
|
61
|
+
out.write(pc.dim(`run already ${workflow.status} — nothing to attach to\n`));
|
|
62
|
+
if (workflow.status !== WorkflowState.Completed && workflow.errorMessage) {
|
|
63
|
+
out.write(`${pc.red('■')} ${workflow.errorMessage}\n`);
|
|
64
|
+
}
|
|
65
|
+
client.stream.close();
|
|
66
|
+
process.exit(exitCodeForStatus(workflow.status));
|
|
67
|
+
}
|
|
68
|
+
out.write(pc.dim('\n— attached, following live —\n'));
|
|
69
|
+
const followOptions = {
|
|
70
|
+
onIdle: createIdleHandler(client, runId, out, { studioUrl: link }),
|
|
71
|
+
initiallyIdle: IDLE_STATES.includes(workflow.status),
|
|
72
|
+
onDocument: renderer.onDocument,
|
|
73
|
+
streamedMessageIds,
|
|
74
|
+
streamedToolCallIds,
|
|
75
|
+
visibleWorkflowIds,
|
|
76
|
+
onStreamReset: async () => (await client.workflows.get(runId)).status,
|
|
77
|
+
};
|
|
78
|
+
let outcome = await followRun(events, runId, out, followOptions);
|
|
79
|
+
if (!json)
|
|
80
|
+
outcome = await offerRetry(client, runId, events, out, out, followOptions, outcome);
|
|
81
|
+
client.stream.close();
|
|
82
|
+
const full = await client.workflows.get(runId);
|
|
83
|
+
if (json) {
|
|
84
|
+
printData(JSON.stringify({ workflowId: runId, status: outcome.status, result: full.result, errorMessage: full.errorMessage }, null, 2));
|
|
85
|
+
}
|
|
86
|
+
if (outcome.status === WorkflowState.Completed) {
|
|
87
|
+
if (!json) {
|
|
88
|
+
out.write('\n');
|
|
89
|
+
if (renderResult(out, full.result))
|
|
90
|
+
out.write('\n');
|
|
91
|
+
}
|
|
92
|
+
out.write(`${pc.green('■')} run completed\n`);
|
|
93
|
+
}
|
|
94
|
+
else if (IDLE_STATES.includes(outcome.status)) {
|
|
95
|
+
out.write(`${pc.yellow('⏸')} run is still waiting for input\n`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
out.write(`${pc.red('■')} run ${outcome.status}${full.errorMessage ? `: ${full.errorMessage}` : ''}\n`);
|
|
99
|
+
}
|
|
100
|
+
process.exit(exitCodeForStatus(outcome.status));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=attach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach.js","sourceRoot":"","sources":["../../src/commands/attach.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,eAAe,GAA6B;IAChD,aAAa,CAAC,SAAS;IACvB,aAAa,CAAC,MAAM;IACpB,aAAa,CAAC,QAAQ;CACvB,CAAC;AAEF,MAAM,WAAW,GAA6B,CAAC,aAAa,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;AAG5F,SAAS,iBAAiB,CAAC,MAAqB;IAC9C,IAAI,MAAM,KAAK,aAAa,CAAC,SAAS;QAAE,OAAO,QAAQ,CAAC,OAAO,CAAC;IAChE,IAAI,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,QAAQ,CAAC,UAAU,CAAC;IAC7D,OAAO,QAAQ,CAAC,SAAS,CAAC;AAC5B,CAAC;AAMD,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,gBAAgB,CAAC;SACzB,WAAW,CACV,8GAA8G,CAC/G;SACA,MAAM,CAAC,QAAQ,EAAE,wBAAwB,CAAC;SAC1C,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,OAAsB,EAAE,GAAG,EAAE,EAAE;QAC3D,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,EAAoE,CAAC;QACxG,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QAEnD,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,IAAI,IAAI;gBAAE,aAAa,CAAC,IAAI,CAAC,CAAC;;gBACzB,WAAW,CAAC,iFAAiF,CAAC,CAAC;QACtG,CAAC;QAID,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACtC,MAAM,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAElC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,GAAG,MAAM,cAAc,CACpG,MAAM,EACN,UAAU,EACV,GAAG,EACH,QAAQ,EACR,WAAW,CACZ,CAAC;QAEF,IAAI,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAE9C,IAAI,IAAI,EAAE,CAAC;gBACT,SAAS,CACP,IAAI,CAAC,SAAS,CACZ;oBACE,UAAU,EAAE,KAAK;oBACjB,MAAM,EAAE,QAAQ,CAAC,MAAM;oBACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;oBACvB,YAAY,EAAE,QAAQ,CAAC,YAAY;iBACpC,EACD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAChB,IAAI,YAAY,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC;oBAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC1D,CAAC;YACD,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,eAAe,QAAQ,CAAC,MAAM,2BAA2B,CAAC,CAAC,CAAC;YAC7E,IAAI,QAAQ,CAAC,MAAM,KAAK,aAAa,CAAC,SAAS,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;gBACzE,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,YAAY,IAAI,CAAC,CAAC;YACzD,CAAC;YACD,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAGtB,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACnD,CAAC;QAED,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC,CAAC;QAKtD,MAAM,aAAa,GAAG;YACpB,MAAM,EAAE,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;YAClE,aAAa,EAAE,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;YACpD,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,kBAAkB;YAClB,mBAAmB;YACnB,kBAAkB;YAClB,aAAa,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;SACtE,CAAC;QACF,IAAI,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI;YAAE,OAAO,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;QAC/F,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAEtB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC/C,IAAI,IAAI,EAAE,CAAC;YACT,SAAS,CACP,IAAI,CAAC,SAAS,CACZ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,EACnG,IAAI,EACJ,CAAC,CACF,CACF,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,KAAK,aAAa,CAAC,SAAS,EAAE,CAAC;YAC/C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAChB,IAAI,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC;oBAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACtD,CAAC;YACD,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAChD,CAAC;aAAM,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QAClE,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAC1G,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4BzC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4HzD"}
|
package/dist/commands/run.js
CHANGED
|
@@ -9,6 +9,7 @@ import { openInBrowser, studioRunUrl } from '../output/studio-link.js';
|
|
|
9
9
|
import { parseRunArgs } from '../run/args.js';
|
|
10
10
|
import { createDocumentRenderer } from '../run/documents.js';
|
|
11
11
|
import { followRun } from '../run/follow.js';
|
|
12
|
+
import { offerRetry } from '../run/retry.js';
|
|
12
13
|
import { resolveWorkspaceId } from '../run/workspace.js';
|
|
13
14
|
const collect = (value, previous) => [...previous, value];
|
|
14
15
|
const discard = () => new Writable({ write: (_chunk, _encoding, callback) => callback() });
|
|
@@ -19,22 +20,29 @@ export function registerRunCommand(program) {
|
|
|
19
20
|
.option('--arg <key=value>', 'workflow argument, repeatable; key=@file.json reads a file, key=@- reads stdin', collect, [])
|
|
20
21
|
.option('--workspace <id>', 'workspace to run in (default: newest workspace of the workflow’s app)')
|
|
21
22
|
.option('--detach', 'start the run, print its id, exit immediately')
|
|
23
|
+
.option('--trace', 'persist the run trace (tool calls, transitions) — enables `runs <id> --record`')
|
|
22
24
|
.option('--quiet', 'no progress — print only the final result')
|
|
23
25
|
.option('--open', 'open the run in Studio')
|
|
24
|
-
.option('--editor', 'answer field forms in $EDITOR instead of handing off to Studio')
|
|
25
26
|
.action(async (workflowName, options, cmd) => {
|
|
26
27
|
const globals = cmd.optsWithGlobals();
|
|
27
28
|
const connection = resolveConnection(globals);
|
|
28
29
|
const client = createClientFor(connection);
|
|
29
30
|
const json = !!globals.json;
|
|
30
31
|
const quiet = !!options.quiet;
|
|
31
|
-
const out = json ? process.stderr : quiet ? discard() : process.stdout;
|
|
32
|
+
const out = json ? process.stderr : quiet ? discard() : options.detach ? process.stderr : process.stdout;
|
|
32
33
|
const statusOut = json || quiet ? process.stderr : process.stdout;
|
|
33
34
|
const args = parseRunArgs(options.arg);
|
|
34
35
|
const workspaceId = await resolveWorkspaceId(client, workflowName, options.workspace, connection.workspaceId);
|
|
35
36
|
const events = options.detach ? undefined : client.stream.events();
|
|
37
|
+
if (events)
|
|
38
|
+
await client.stream.waitForOpen();
|
|
36
39
|
const startedAt = Date.now();
|
|
37
|
-
const run = await client.processor.start({
|
|
40
|
+
const run = await client.processor.start({
|
|
41
|
+
workflowName,
|
|
42
|
+
workspaceId,
|
|
43
|
+
args,
|
|
44
|
+
...(options.trace ? { trace: true } : {}),
|
|
45
|
+
});
|
|
38
46
|
out.write(pc.dim(`▸ run ${run.workflowId} started\n`));
|
|
39
47
|
const link = studioRunUrl(connection, run.workflowId);
|
|
40
48
|
if (link && events)
|
|
@@ -48,14 +56,30 @@ export function registerRunCommand(program) {
|
|
|
48
56
|
if (!events) {
|
|
49
57
|
if (link)
|
|
50
58
|
printStatus(pc.dim(`⧉ ${link}`));
|
|
51
|
-
printStatus(pc.dim(`
|
|
59
|
+
printStatus(pc.dim(`attach with: loopstack attach ${run.workflowId}`));
|
|
52
60
|
printData(json ? JSON.stringify({ workflowId: run.workflowId, ...(link && { studioUrl: link }) }) : run.workflowId);
|
|
53
61
|
process.exit(ExitCode.Success);
|
|
54
62
|
}
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
const streamedMessageIds = new Set();
|
|
64
|
+
const streamedToolCallIds = new Set();
|
|
65
|
+
const visibleWorkflowIds = new Set();
|
|
66
|
+
const renderer = createDocumentRenderer(client, out, {
|
|
67
|
+
studioUrl: (id) => studioRunUrl(connection, id),
|
|
68
|
+
streamedMessageIds,
|
|
69
|
+
streamedToolCallIds,
|
|
70
|
+
visibleWorkflowIds,
|
|
58
71
|
});
|
|
72
|
+
const followOptions = {
|
|
73
|
+
onIdle: createIdleHandler(client, run.workflowId, statusOut, { studioUrl: link }),
|
|
74
|
+
onDocument: renderer.onDocument,
|
|
75
|
+
streamedMessageIds,
|
|
76
|
+
streamedToolCallIds,
|
|
77
|
+
visibleWorkflowIds,
|
|
78
|
+
onStreamReset: async () => (await client.workflows.get(run.workflowId)).status,
|
|
79
|
+
};
|
|
80
|
+
let outcome = await followRun(events, run.workflowId, out, followOptions);
|
|
81
|
+
if (!json)
|
|
82
|
+
outcome = await offerRetry(client, run.workflowId, events, out, statusOut, followOptions, outcome);
|
|
59
83
|
client.stream.close();
|
|
60
84
|
const durationMs = Date.now() - startedAt;
|
|
61
85
|
const full = await client.workflows.get(run.workflowId);
|
|
@@ -70,17 +94,18 @@ export function registerRunCommand(program) {
|
|
|
70
94
|
}, null, 2));
|
|
71
95
|
}
|
|
72
96
|
if (outcome.status === WorkflowState.Completed) {
|
|
73
|
-
if (!json
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
if (!json) {
|
|
98
|
+
if (!quiet)
|
|
99
|
+
out.write('\n');
|
|
100
|
+
const wrote = renderResult(quiet ? process.stdout : out, full.result);
|
|
101
|
+
if (!quiet && wrote)
|
|
102
|
+
out.write('\n');
|
|
76
103
|
}
|
|
77
|
-
if (!json && !quiet)
|
|
78
|
-
renderResult(out, full.result);
|
|
79
104
|
statusOut.write(`${pc.green('■')} run completed in ${formatDuration(durationMs)}\n`);
|
|
80
105
|
process.exit(ExitCode.Success);
|
|
81
106
|
}
|
|
82
107
|
if (outcome.status === WorkflowState.Waiting || outcome.status === WorkflowState.Paused) {
|
|
83
|
-
statusOut.write(`${pc.yellow('⏸')} run is waiting for input — resume with \`loopstack
|
|
108
|
+
statusOut.write(`${pc.yellow('⏸')} run is waiting for input — resume with \`loopstack attach ${run.workflowId}\`\n`);
|
|
84
109
|
if (link)
|
|
85
110
|
statusOut.write(pc.dim(`⧉ answer in Studio: ${link}\n`));
|
|
86
111
|
process.exit(ExitCode.NeedsInput);
|
package/dist/commands/run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,QAAkB,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,CAAC;AAE5E,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAW3F,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,OAAO;SACJ,OAAO,CAAC,gBAAgB,CAAC;SACzB,WAAW,CAAC,6FAA6F,CAAC;SAC1G,MAAM,CACL,mBAAmB,EACnB,gFAAgF,EAChF,OAAO,EACP,EAAc,CACf;SACA,MAAM,CAAC,kBAAkB,EAAE,uEAAuE,CAAC;SACnG,MAAM,CAAC,UAAU,EAAE,+CAA+C,CAAC;SACnE,MAAM,CAAC,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runs.d.ts","sourceRoot":"","sources":["../../src/commands/runs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"runs.d.ts","sourceRoot":"","sources":["../../src/commands/runs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiCzC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4B1D"}
|
package/dist/commands/runs.js
CHANGED
|
@@ -1,44 +1,78 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
1
3
|
import pc from 'picocolors';
|
|
2
4
|
import { SortOrder, WorkflowState } from '@loopstack/contracts/enums';
|
|
3
5
|
import { createClientFor, resolveConnection } from '../config/resolve.js';
|
|
4
6
|
import { CliError, ExitCode } from '../errors.js';
|
|
5
|
-
import {
|
|
6
|
-
import { colorStatus,
|
|
7
|
+
import { inspectPendingPrompt } from '../hitl/pending.js';
|
|
8
|
+
import { colorStatus, printData, printStatus, renderResult, renderTable } from '../output/format.js';
|
|
7
9
|
import { openInBrowser, studioRunUrl } from '../output/studio-link.js';
|
|
8
|
-
import {
|
|
9
|
-
import { followRun } from '../run/follow.js';
|
|
10
|
-
const TERMINAL_STATES = [
|
|
11
|
-
WorkflowState.Completed,
|
|
12
|
-
WorkflowState.Failed,
|
|
13
|
-
WorkflowState.Canceled,
|
|
14
|
-
];
|
|
10
|
+
import { renderRunTrail } from '../run/trail.js';
|
|
15
11
|
const IDLE_STATES = [WorkflowState.Waiting, WorkflowState.Paused];
|
|
16
12
|
export function registerRunsCommand(program) {
|
|
17
13
|
program
|
|
18
14
|
.command('runs [runId]')
|
|
19
|
-
.description('Recent runs (runs waiting for input first), or one run’s
|
|
20
|
-
.option('--follow', 'attach to the live stream after printing the trail (requires a run id)')
|
|
15
|
+
.description('Recent runs (runs waiting for input first), or one run’s full transcript; `attach` joins it live')
|
|
21
16
|
.option('--limit <n>', 'maximum number of runs to list', '20')
|
|
22
17
|
.option('--workspace <id>', 'filter by workspace id')
|
|
23
18
|
.option('--search <text>', 'search runs')
|
|
24
19
|
.option('--status <status>', 'filter by status (e.g. waiting, completed, failed)')
|
|
25
20
|
.option('--open', 'open the run in Studio (requires a run id)')
|
|
26
|
-
.option('--
|
|
21
|
+
.option('--record <file>', 'write the run’s recorded tool calls as a replay fixture (JSON)')
|
|
22
|
+
.option('--tools <names>', 'comma-separated tool names to include in the fixture (default: all)')
|
|
27
23
|
.action(async (runId, options, cmd) => {
|
|
28
24
|
const globals = cmd.optsWithGlobals();
|
|
29
25
|
const connection = resolveConnection(globals);
|
|
30
26
|
const client = createClientFor(connection);
|
|
31
27
|
if (!runId) {
|
|
32
|
-
if (options.follow)
|
|
33
|
-
throw new CliError('--follow requires a run id: loopstack runs <run-id> --follow');
|
|
34
28
|
if (options.open)
|
|
35
29
|
throw new CliError('--open requires a run id: loopstack runs <run-id> --open');
|
|
30
|
+
if (options.record)
|
|
31
|
+
throw new CliError('--record requires a run id: loopstack runs <run-id> --record <file>');
|
|
36
32
|
await listRuns(client, connection, options, !!globals.json);
|
|
37
33
|
return;
|
|
38
34
|
}
|
|
35
|
+
if (options.record) {
|
|
36
|
+
await recordFixture(client, runId, options.record, options.tools, !!globals.json);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
39
|
await showRun(client, connection, runId, options, !!globals.json);
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
+
async function recordFixture(client, runId, file, tools, json) {
|
|
43
|
+
const allRecords = await client.workflows.toolCalls(runId);
|
|
44
|
+
if (allRecords.length === 0) {
|
|
45
|
+
throw new CliError('No recorded tool calls for this run. Start the run with `loopstack run <workflow> --trace` ' +
|
|
46
|
+
'(or the backend with LOOPSTACK_TRACE=true) and run the workflow again.');
|
|
47
|
+
}
|
|
48
|
+
const boundary = tools ? new Set(tools.split(',').map((name) => name.trim())) : undefined;
|
|
49
|
+
const inBoundary = boundary ? allRecords.filter((record) => boundary.has(record.toolName)) : allRecords;
|
|
50
|
+
if (boundary && inBoundary.length === 0) {
|
|
51
|
+
throw new CliError(`No recorded calls match --tools ${tools}. Recorded tools: ` +
|
|
52
|
+
`${[...new Set(allRecords.map((record) => record.toolName))].join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
const records = inBoundary.filter((record) => !record.envelope.pending);
|
|
55
|
+
const recordings = records.map((record) => ({
|
|
56
|
+
tool: record.toolName,
|
|
57
|
+
workflow: record.workflowName,
|
|
58
|
+
transition: record.transitionId ?? '',
|
|
59
|
+
...(record.args !== null ? { args: record.args } : {}),
|
|
60
|
+
...(record.config !== null ? { config: record.config } : {}),
|
|
61
|
+
envelope: record.envelope,
|
|
62
|
+
}));
|
|
63
|
+
const target = resolve(file);
|
|
64
|
+
writeFileSync(target, `${JSON.stringify({ version: 3, recordings }, null, 2)}\n`);
|
|
65
|
+
const skipped = inBoundary.length - records.length;
|
|
66
|
+
if (json) {
|
|
67
|
+
printData(JSON.stringify({ file: target, recordings: recordings.length, skippedPending: skipped }, null, 2));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
printStatus(`${pc.green('✓')} wrote ${recordings.length} recording(s) to ${target}`);
|
|
71
|
+
if (skipped > 0) {
|
|
72
|
+
printStatus(` ${skipped} pending envelope(s) skipped — async tools always run live in replayed tests`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
42
76
|
async function listRuns(client, connection, options, json) {
|
|
43
77
|
const filter = {
|
|
44
78
|
...(options.workspace && { workspaceId: options.workspace }),
|
|
@@ -77,12 +111,11 @@ async function listRuns(client, connection, options, json) {
|
|
|
77
111
|
printData(renderTable(['ID', 'WORKFLOW', 'RUN', 'STATUS', 'TITLE', 'CREATED'], rows));
|
|
78
112
|
printStatus('');
|
|
79
113
|
if (needsInput.length > 0) {
|
|
80
|
-
printStatus(`${pc.yellow('⏸')} ${needsInput.length} waiting for input — answer with \`loopstack
|
|
114
|
+
printStatus(`${pc.yellow('⏸')} ${needsInput.length} waiting for input — answer with \`loopstack attach <run-id>\``);
|
|
81
115
|
}
|
|
82
116
|
printStatus(`${ordered.length} of ${page.total} runs (${connection.name})`);
|
|
83
117
|
}
|
|
84
118
|
async function showRun(client, connection, runId, options, json) {
|
|
85
|
-
const out = json ? process.stderr : process.stdout;
|
|
86
119
|
const link = studioRunUrl(connection, runId);
|
|
87
120
|
if (options.open) {
|
|
88
121
|
if (link)
|
|
@@ -90,64 +123,29 @@ async function showRun(client, connection, runId, options, json) {
|
|
|
90
123
|
else
|
|
91
124
|
printStatus('No Studio URL configured for this environment — set one with `loopstack login`.');
|
|
92
125
|
}
|
|
93
|
-
const events = options.follow ? client.stream.events() : undefined;
|
|
94
126
|
const workflow = await client.workflows.get(runId);
|
|
95
127
|
const checkpoints = await client.workflows.checkpoints(runId);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
const title = workflow.title ? ` (${workflow.title})` : '';
|
|
101
|
-
out.write(`${pc.bold(workflow.workflowName)} #${workflow.run}${title} — ${colorStatus(workflow.status)}\n`);
|
|
102
|
-
out.write(pc.dim(`started ${new Date(workflow.createdAt).toLocaleString()} ${workflow.id}\n`));
|
|
103
|
-
if (link)
|
|
104
|
-
out.write(pc.dim(`⧉ ${link}\n`));
|
|
105
|
-
const steps = [];
|
|
106
|
-
for (const checkpoint of checkpoints) {
|
|
107
|
-
const at = new Date(checkpoint.createdAt).getTime();
|
|
108
|
-
const last = steps[steps.length - 1];
|
|
109
|
-
if (last && last.place === checkpoint.place)
|
|
110
|
-
continue;
|
|
111
|
-
if (last)
|
|
112
|
-
last.leftAt = at;
|
|
113
|
-
steps.push({ place: checkpoint.place, enteredAt: at });
|
|
114
|
-
}
|
|
115
|
-
for (const step of steps) {
|
|
116
|
-
if (step.place === 'end')
|
|
117
|
-
continue;
|
|
118
|
-
const duration = step.leftAt !== undefined ? pc.dim(` (${formatDuration(step.leftAt - step.enteredAt)})`) : '';
|
|
119
|
-
out.write(`${pc.green('✓')} ${step.place}${duration}\n`);
|
|
120
|
-
}
|
|
121
|
-
if (workflow.errorMessage)
|
|
122
|
-
out.write(`${pc.red('✖')} ${workflow.errorMessage}\n`);
|
|
123
|
-
if (!options.follow || TERMINAL_STATES.includes(workflow.status)) {
|
|
124
|
-
renderResult(out, workflow.result);
|
|
125
|
-
client.stream.close();
|
|
126
|
-
process.exit(ExitCode.Success);
|
|
127
|
-
}
|
|
128
|
-
out.write(pc.dim('— attached, following live —\n'));
|
|
129
|
-
const onIdle = createIdleHandler(client, runId, out, { studioUrl: link, editor: options.editor });
|
|
130
|
-
const outcome = await followRun(events, runId, out, {
|
|
131
|
-
onIdle,
|
|
132
|
-
initiallyIdle: IDLE_STATES.includes(workflow.status),
|
|
133
|
-
onDocument: createDocumentRenderer(client, out),
|
|
134
|
-
});
|
|
135
|
-
client.stream.close();
|
|
136
|
-
const full = await client.workflows.get(runId);
|
|
128
|
+
const pendingPrompt = IDLE_STATES.includes(workflow.status)
|
|
129
|
+
? await inspectPendingPrompt(client, runId).catch(() => undefined)
|
|
130
|
+
: undefined;
|
|
137
131
|
if (json) {
|
|
138
|
-
printData(JSON.stringify({
|
|
139
|
-
}
|
|
140
|
-
if (outcome.status === WorkflowState.Completed) {
|
|
141
|
-
if (!json)
|
|
142
|
-
renderResult(out, full.result);
|
|
143
|
-
out.write(`${pc.green('■')} run completed\n`);
|
|
132
|
+
printData(JSON.stringify({ workflow, checkpoints, ...(pendingPrompt && { pendingPrompt }), ...(link && { studioUrl: link }) }, null, 2));
|
|
144
133
|
process.exit(ExitCode.Success);
|
|
145
134
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
135
|
+
const out = process.stdout;
|
|
136
|
+
await renderRunTrail(client, connection, out, workflow, checkpoints);
|
|
137
|
+
out.write('\n');
|
|
138
|
+
if (renderResult(out, workflow.result))
|
|
139
|
+
out.write('\n');
|
|
140
|
+
if (IDLE_STATES.includes(workflow.status)) {
|
|
141
|
+
if (pendingPrompt && !pendingPrompt.studioOnly) {
|
|
142
|
+
out.write(`${pc.yellow('⏸')} waiting for input: ${pendingPrompt.description}\n`);
|
|
143
|
+
out.write(pc.dim(` answer with \`loopstack answer ${runId} --arg k=v\` or interactively: \`loopstack attach ${runId}\`\n`));
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
out.write(`${pc.yellow('⏸')} run is waiting for input — answer with \`loopstack attach ${runId}\`\n`);
|
|
147
|
+
}
|
|
149
148
|
}
|
|
150
|
-
|
|
151
|
-
process.exit(ExitCode.RunFailed);
|
|
149
|
+
process.exit(ExitCode.Success);
|
|
152
150
|
}
|
|
153
151
|
//# sourceMappingURL=runs.js.map
|