Skip to content

feat(workflows): implement interactive workflow run with approval loops, quiet connection, and automatic file uploading - #547

Open
yxvnyk wants to merge 3 commits into
codemie-ai:mainfrom
yxvnyk:feature/EPMCDME-12353_run-workflows-cli
Open

yxvnyk wants to merge 3 commits into
codemie-ai:mainfrom
yxvnyk:feature/EPMCDME-12353_run-workflows-cli

Conversation

@yxvnyk

@yxvnyk yxvnyk commented Sep 9, 2026

Copy link
Copy Markdown

Ticket

EPMCDME-12353

Summary

This MR extended CodeMie CLI to support executing custom or shared workflows by ID or Name under the codemie workflow run and codemie sdk workflows run commands.

Changes

Impact

Both commands are feature-complete and support:

  1. Interactive Interruption & Approval Handler:
    • Detects Interrupted status from any node (e.g. approval states) and presents an interactive CLI choice to the user: Approve & Continue, Edit current message, or Abort workflow.
    • Supports looping over successive interruptions seamlessly.
    • Leverages a stable, engine-level execution ID (execId) across resumes to prevent state loss after a generic success base response.
  2. Quiet Authentication Connection:
    • Suppresses the standard ✔ Connected to CodeMie and configuration-loading spinner when raw JSON output is requested (such as during --no-wait or --json execution), making the CLI commands fully pipeline-safe and compliant with Unix stdout standards.
  3. Clean Immediate Exit (--no-wait):
    • Correctly handles commander options negation. Triggering a run with --no-wait outputs ONLY clean, raw JSON starting metadata and exits instantly.
  4. Unix Pipeline-safe Completed Outputs:
    • Successful executions display ONLY the actual finalized markdown/text output (fetched from the graph's finalizer state), omitting extra metadata or checkmark wrappers.
  5. Automatic File Upload Assignment:
    • Re-adds the -f, --file <name> option. Specifying a local file automatically reads and uploads it to the CodeMie files API before launching the workflow with its remote file_url reference attached.

Verification & Testing Evidence

  1. Integration Tests
    Executed and passed 100% of the workflow test suite:

    npx vitest run tests/integration/cli-commands/workflow.test.ts

   1  RUN  v4.1.5 C:/epam/codemie-dev/codemie-code
   2
   3  Test Files  1 passed (1)
   4       Tests  6 passed (6)
   5    Duration  5.28s
  1. Manual Verification Scenarios

A. Standard Output Extraction (Successful Polled Run)

`node ./bin/codemie.js workflow run d362c364-ee7f-4716-a1b7-f6f706d09f0d --input "hello"`
  • Output: Displays only the markdown result from the graph finalizer:
   1 ✔ Connected to CodeMie
   2 ✓ Workflow completed successfully.
   3
   4 # Conversation Summary
   5
   6 ## Initial User Task
   7 The user greeted with "hello"...

B. Clean JSON Trigger (--no-wait Suppressing Spinner)

1 node ./bin/codemie.js workflow run d362c364-ee7f-4716-a1b7-f6f706d09f0d --input "hello" --no-wait

  • Output: Returns confirmation about starting the workflow:

✓ ✓ Workflow execution started successfully. (Status: In Progress)

C. File Assignment & Interactive Approval loop

node ./bin/codemie.js workflow run d362c364-ee7f-4716-a1b7-f6f706d09f0d --input "hello my friend" --file "package.json"

  • Output: Quietly uploads package.json, enters interrupted state, prompts user to Approve/Edit/Abort, accepts edited input, and resumes polling seamlessly:
    1 ✔ Connected to CodeMie
    2 ✔ ✓ File package.json uploaded successfully.
    3
    4 ⚠ Workflow execution is Interrupted and requires your decision.
    5 Interrupted Message:
    6 hello* my* friend* -*
    7
    8 ? How would you like to proceed? Edit current message
    9 ? Enter your edited message: hello* my* friend*
   10 ✔ ✓ Workflow resumed with edited message.
   11 # Conversation Summary
   12 ... (prints only final result)

handleSdkError(error, "run workflow");
}
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate run command logic across src/cli/commands/sdk/workflows.ts and src/cli/commands/workflow.ts

The run command handler (id/name resolution, file upload, execution polling, the Interrupted approve/edit/abort flow, and result output) is duplicated almost verbatim between sdk/workflows.ts (cmd.command("run <id>")) and workflow.ts (workflow.command('run [workflow-id-or-name]')) — ~230 lines each, differing only in quote style and local variable names.

Could we extract the shared orchestration logic (everything past argument resolution — polling, interrupt handling, output formatting) into a single helper, and keep both command files as thin wrappers that only bind/resolve CLI arguments (id-or-name, --input, --file, --wait, --json) and delegate to it?

Note: this shared logic shouldn't move into services/workflows.ts — that layer is a clean SDK-only wrapper today (no chalk/ora/inquirer/console.log). The polling/prompt/output code is CLI-presentation logic, so it belongs in a separate CLI-level helper (e.g. sdk/actions/run-workflow.ts) that calls the existing runWorkflow service function, rather than being folded into the service itself.

@yxvnyk yxvnyk Sep 21, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sdk workflows run logic is deprecated. I will remove it

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but lets clean up logic here as well. I added some comments, but in general: we need to leave here only thin orchestration and ui logic (spinners, user interactions, logs)


try {
let targetId = id;
if (!id.startsWith("wfl_")) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate what this constant actually mean?

if (match) {
targetId = match.id;
} else if (workflows.length === 1) {
targetId = workflows[0].id;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets separate parameters - id and name
For id we can use getWorkflow service, for name - create separate helper service to find exact match and throw error if there is no exact match.

} catch {
// Keep as string
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to separate helper

mimeType: "application/octet-stream",
});
uploadedFileName = uploadRes.file_url;
uploadSpinner.succeed(chalk.green(`✓ File ${path.basename(opts.file)} uploaded successfully.`));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to separate service

const resumeSpinner = ora("Resuming workflow with edited message...").start();
try {
await (client.workflows as any).api.put(
`/v1/workflows/${targetId}/executions/${execId}/resume`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add this method to sdk

handleSdkError(error, "run workflow");
}
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but lets clean up logic here as well. I added some comments, but in general: we need to leave here only thin orchestration and ui logic (spinners, user interactions, logs)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants