feat(cli): thread step --background + thread running #457

Merged
xiaomo merged 1 commits from fix/456-thread-step-background into main 2026-05-24 05:33:56 +00:00
Owner

What

This PR implements issue #456, adding two related capabilities to the uwf CLI:

  1. Background execution mode for uwf thread step (via --background flag)

    • Spawns agent execution in a detached background process
    • Returns immediately with thread ID and background status
    • Maintains marker files to track running processes
    • Supports --count option to run multiple steps in background
    • Prevents concurrent execution of the same thread
  2. Running threads query command (uwf thread running)

    • Lists all threads currently executing in background
    • Returns thread ID, workflow, current role, PID, and start time
    • Automatically filters out stale markers (dead processes)
    • Empty list when no threads are running

Why

Enable long-running workflow executions without blocking the CLI. Users can:

  • Start a thread in background and continue working
  • Monitor running threads
  • Kill background threads when needed

Changes

Core Implementation

  • workflow-protocol: Added RunningThreadItem, RunningThreadsOutput types; updated StepOutput to include background: boolean | null field

  • cli-workflow/background: New module for process management

    • Marker file creation/deletion (atomic operations)
    • PID liveness checking
    • Stale marker cleanup
    • Running threads query
  • cli-workflow/commands/thread:

    • Updated cmdThreadStep to support --background and --_background-worker flags
    • Added cmdThreadStepBackground for spawning detached processes
    • Added cmdThreadRunning to list running threads
    • Updated cmdThreadKill to terminate background processes
  • cli-workflow/cli: Added CLI routing for new commands and flags

Integration

  • uwf thread kill now terminates background processes before archiving
  • Foreground execution checks for existing background process and fails if found
  • Background worker creates/cleans up marker files automatically
  • Marker files stored in ~/.uncaged/workflow/running/*.json

Testing

  • Added comprehensive test coverage for parseClaudeCodeStreamOutput edge cases from issue #439
  • Tests verify proper handling of multiple assistant messages, empty content arrays, tool use extraction, and malformed NDJSON

Ref

Fixes #456

## What This PR implements issue #456, adding two related capabilities to the `uwf` CLI: 1. **Background execution mode** for `uwf thread step` (via `--background` flag) - Spawns agent execution in a detached background process - Returns immediately with thread ID and background status - Maintains marker files to track running processes - Supports `--count` option to run multiple steps in background - Prevents concurrent execution of the same thread 2. **Running threads query** command (`uwf thread running`) - Lists all threads currently executing in background - Returns thread ID, workflow, current role, PID, and start time - Automatically filters out stale markers (dead processes) - Empty list when no threads are running ## Why Enable long-running workflow executions without blocking the CLI. Users can: - Start a thread in background and continue working - Monitor running threads - Kill background threads when needed ## Changes ### Core Implementation - **workflow-protocol**: Added `RunningThreadItem`, `RunningThreadsOutput` types; updated `StepOutput` to include `background: boolean | null` field - **cli-workflow/background**: New module for process management - Marker file creation/deletion (atomic operations) - PID liveness checking - Stale marker cleanup - Running threads query - **cli-workflow/commands/thread**: - Updated `cmdThreadStep` to support `--background` and `--_background-worker` flags - Added `cmdThreadStepBackground` for spawning detached processes - Added `cmdThreadRunning` to list running threads - Updated `cmdThreadKill` to terminate background processes - **cli-workflow/cli**: Added CLI routing for new commands and flags ### Integration - `uwf thread kill` now terminates background processes before archiving - Foreground execution checks for existing background process and fails if found - Background worker creates/cleans up marker files automatically - Marker files stored in `~/.uncaged/workflow/running/*.json` ### Testing - Added comprehensive test coverage for `parseClaudeCodeStreamOutput` edge cases from issue #439 - Tests verify proper handling of multiple assistant messages, empty content arrays, tool use extraction, and malformed NDJSON ## Ref Fixes #456
xiaoju added 2 commits 2026-05-24 05:05:16 +00:00
This commit implements issue #456, adding two related capabilities to the uwf CLI:

1. **Background execution mode** for `uwf thread step` (via `--background` flag)
   - Spawns agent execution in a detached background process
   - Returns immediately with thread ID and background status
   - Maintains marker files to track running processes
   - Supports `--count` option to run multiple steps in background
   - Prevents concurrent execution of the same thread

2. **Running threads query** command (`uwf thread running`)
   - Lists all threads currently executing in background
   - Returns thread ID, workflow, current role, PID, and start time
   - Automatically filters out stale markers (dead processes)
   - Empty list when no threads are running

**Key changes:**

- **workflow-protocol**: Added `RunningThreadItem`, `RunningThreadsOutput` types
  Updated `StepOutput` to include `background: boolean | null` field

- **cli-workflow/background**: New module for process management
  - Marker file creation/deletion (atomic operations)
  - PID liveness checking
  - Stale marker cleanup
  - Running threads query

- **cli-workflow/commands/thread**:
  - Updated `cmdThreadStep` to support `--background` and `--_background-worker` flags
  - Added `cmdThreadStepBackground` for spawning detached processes
  - Added `cmdThreadRunning` to list running threads
  - Updated `cmdThreadKill` to terminate background processes

- **cli-workflow/cli**: Added CLI routing for new commands and flags

**Integration:**
- `uwf thread kill` now terminates background processes before archiving
- Foreground execution checks for existing background process and fails if found
- Background worker creates/cleans up marker files automatically
- Marker files stored in `~/.uncaged/workflow/running/*.json`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit adds extensive test coverage for parseClaudeCodeStreamOutput edge cases
identified in issue #439. Tests verify proper handling of:

- Multiple assistant messages with tool use and text
- Empty content arrays (skipped)
- Text + tool use in same message
- Sequential turn indexing
- Single turn edge cases
- Malformed NDJSON handling
- Tool call extraction edge cases (string vs object input, multiple calls)
- Tool result extraction with multiple results
- Usage field parsing with defaults

These tests ensure the parser correctly handles all scenarios from the original issue
and prevent regression.

Fixes #456

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
xiaomo requested changes 2026-05-24 05:23:30 +00:00
Dismissed
xiaomo left a comment
Owner

整体实现方向正确,代码结构清晰。几个问题需要修:

必须修

1. createMarker 的"原子写入"并不原子

await rm(markerPath, { force: true });
await writeFile(markerPath, content, "utf8");
await rm(tempPath, { force: true });

写了 tempPath 但没用 rename,最终还是直接 writeFile 到 markerPath。这不是原子操作。应该用 rename(tempPath, markerPath)rename 在同一文件系统上是原子的。

import { rename } from "node:fs/promises";
await writeFile(tempPath, content, "utf8");
await rename(tempPath, markerPath);

注意 tmpdir() 可能跟 storageRoot 不在同一文件系统(比如 /tmp 是 tmpfs),rename 会 cross-device 失败。tempPath 应该放在 runningDir 下:

const tempPath = join(runningDir, `.${marker.thread}.tmp`);

2. background worker 的 marker role 信息不准确

当前通过 UWF_BACKGROUND_ROLE 环境变量传初始 role,但 -c > 1 时后续 step 的 role 会变,marker 里的 role 不会更新。

建议:要么每次 step 后更新 marker(在 cmdThreadStepOnce 里),要么 marker 里不存 role(用 currentRole: string | null,初始为 null,查询时从 thread head 动态读取)。

3. cmdThreadStepBackground 里做了一次完整的 moderator evaluate

只是为了拿 role 名写 marker,但这引入了额外的 CAS 读取和 evaluate 开销。而且 background worker 启动后会再 evaluate 一次(重复)。结合上面第 2 点,建议 marker 不存 role,省掉这次 evaluate。

建议

4. RunningThreadItem.workflowCasRef(bundle hash)

对用户来说 hash 不直观。issue spec 里写的是 workflowName。建议加一个 name 字段(从 registry 反查),或者至少在 marker 里记上 workflow name。

5. 测试

parseClaudeCodeStreamOutput 的测试跟 #456 无关(是 #439 的),放在这个 PR 里有点混。不阻塞合并,但建议以后分开。

整体实现方向正确,代码结构清晰。几个问题需要修: ## 必须修 ### 1. `createMarker` 的"原子写入"并不原子 ```ts await rm(markerPath, { force: true }); await writeFile(markerPath, content, "utf8"); await rm(tempPath, { force: true }); ``` 写了 tempPath 但没用 `rename`,最终还是直接 `writeFile` 到 markerPath。这不是原子操作。应该用 `rename(tempPath, markerPath)` — `rename` 在同一文件系统上是原子的。 ```ts import { rename } from "node:fs/promises"; await writeFile(tempPath, content, "utf8"); await rename(tempPath, markerPath); ``` 注意 `tmpdir()` 可能跟 `storageRoot` 不在同一文件系统(比如 /tmp 是 tmpfs),`rename` 会 cross-device 失败。tempPath 应该放在 `runningDir` 下: ```ts const tempPath = join(runningDir, `.${marker.thread}.tmp`); ``` ### 2. background worker 的 marker role 信息不准确 当前通过 `UWF_BACKGROUND_ROLE` 环境变量传初始 role,但 `-c > 1` 时后续 step 的 role 会变,marker 里的 role 不会更新。 建议:要么每次 step 后更新 marker(在 `cmdThreadStepOnce` 里),要么 marker 里不存 role(用 `currentRole: string | null`,初始为 null,查询时从 thread head 动态读取)。 ### 3. `cmdThreadStepBackground` 里做了一次完整的 moderator evaluate 只是为了拿 role 名写 marker,但这引入了额外的 CAS 读取和 evaluate 开销。而且 background worker 启动后会再 evaluate 一次(重复)。结合上面第 2 点,建议 marker 不存 role,省掉这次 evaluate。 ## 建议 ### 4. `RunningThreadItem.workflow` 是 `CasRef`(bundle hash) 对用户来说 hash 不直观。issue spec 里写的是 `workflowName`。建议加一个 `name` 字段(从 registry 反查),或者至少在 marker 里记上 workflow name。 ### 5. 测试 `parseClaudeCodeStreamOutput` 的测试跟 #456 无关(是 #439 的),放在这个 PR 里有点混。不阻塞合并,但建议以后分开。
xiaoju force-pushed fix/456-thread-step-background from 40b6abb4c3 to 521d908719 2026-05-24 05:29:00 +00:00 Compare
xiaomo approved these changes 2026-05-24 05:33:54 +00:00
xiaomo left a comment
Owner

LGTM 🍊 三个问题全部修干净了。

LGTM 🍊 三个问题全部修干净了。
xiaomo merged commit 95102941f1 into main 2026-05-24 05:33:56 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/workflow#457