feat(cli): add real-time streaming support to run command with tool execution visibility
- Added message.part.updated event handling for incremental text streaming - Added tool.execute event to display tool calls with input previews - Added tool.result event to show truncated tool result outputs - Enhanced EventState with lastPartText and currentTool tracking - Defined MessagePartUpdatedProps, ToolExecuteProps, ToolResultProps types - Updated event tests to cover new state fields This enables the CLI run command to display real-time agent output similar to the native opencode run command, improving user experience with immediate feedback on tool execution. 🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -717,8 +717,8 @@ Oh My OpenCode は以下の場所からフックを読み込んで実行しま
|
|||||||
有効時(デフォルト)、Sisyphus はオプションの特殊エージェントを備えた強力なオーケストレーターを提供します:
|
有効時(デフォルト)、Sisyphus はオプションの特殊エージェントを備えた強力なオーケストレーターを提供します:
|
||||||
|
|
||||||
- **Sisyphus**: プライマリオーケストレーターエージェント (Claude Opus 4.5)
|
- **Sisyphus**: プライマリオーケストレーターエージェント (Claude Opus 4.5)
|
||||||
- **Builder-Sisyphus**: OhMyOpenCode 強化版のビルドエージェント(デフォルトで無効)
|
- **Builder-Sisyphus**: OpenCode のデフォルトビルドエージェント(SDK 制限により名前変更、デフォルトで無効)
|
||||||
- **Planner-Sisyphus**: OhMyOpenCode 強化版のプランエージェント(デフォルトで有効)
|
- **Planner-Sisyphus**: OpenCode のデフォルトプランエージェント(SDK 制限により名前変更、デフォルトで有効)
|
||||||
|
|
||||||
**設定オプション:**
|
**設定オプション:**
|
||||||
|
|
||||||
@@ -779,8 +779,8 @@ Oh My OpenCode は以下の場所からフックを読み込んで実行しま
|
|||||||
| オプション | デフォルト | 説明 |
|
| オプション | デフォルト | 説明 |
|
||||||
| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `disabled` | `false` | `true` の場合、すべての Sisyphus オーケストレーションを無効化し、元の build/plan をプライマリとして復元します。 |
|
| `disabled` | `false` | `true` の場合、すべての Sisyphus オーケストレーションを無効化し、元の build/plan をプライマリとして復元します。 |
|
||||||
| `builder_enabled` | `false` | `true` の場合、Builder-Sisyphus エージェント(OhMyOpenCode 強化版ビルドモード)を有効化します。デフォルトの OpenCode ビルド体験を維持するため、デフォルトでは無効です。 |
|
| `builder_enabled` | `false` | `true` の場合、Builder-Sisyphus エージェントを有効化します(OpenCode build と同じ、SDK 制限により名前変更)。デフォルトでは無効です。 |
|
||||||
| `planner_enabled` | `true` | `true` の場合、Planner-Sisyphus エージェント(OhMyOpenCode 強化版プランモード)を有効化します。デフォルトで有効です。 |
|
| `planner_enabled` | `true` | `true` の場合、Planner-Sisyphus エージェントを有効化します(OpenCode plan と同じ、SDK 制限により名前変更)。デフォルトで有効です。 |
|
||||||
| `replace_build` | `true` | `true` の場合、デフォルトのビルドエージェントをサブエージェントモードに降格させます。`false` に設定すると、Builder-Sisyphus とデフォルトのビルドの両方を利用できます。 |
|
| `replace_build` | `true` | `true` の場合、デフォルトのビルドエージェントをサブエージェントモードに降格させます。`false` に設定すると、Builder-Sisyphus とデフォルトのビルドの両方を利用できます。 |
|
||||||
| `replace_plan` | `true` | `true` の場合、デフォルトのプランエージェントをサブエージェントモードに降格させます。`false` に設定すると、Planner-Sisyphus とデフォルトのプランの両方を利用できます。 |
|
| `replace_plan` | `true` | `true` の場合、デフォルトのプランエージェントをサブエージェントモードに降格させます。`false` に設定すると、Planner-Sisyphus とデフォルトのプランの両方を利用できます。 |
|
||||||
|
|
||||||
|
|||||||
@@ -711,8 +711,8 @@ Schema 자동 완성이 지원됩니다:
|
|||||||
활성화 시 (기본값), Sisyphus는 옵션으로 선택 가능한 특화 에이전트들과 함께 강력한 오케스트레이터를 제공합니다:
|
활성화 시 (기본값), Sisyphus는 옵션으로 선택 가능한 특화 에이전트들과 함께 강력한 오케스트레이터를 제공합니다:
|
||||||
|
|
||||||
- **Sisyphus**: Primary 오케스트레이터 에이전트 (Claude Opus 4.5)
|
- **Sisyphus**: Primary 오케스트레이터 에이전트 (Claude Opus 4.5)
|
||||||
- **Builder-Sisyphus**: OhMyOpenCode 강화 버전 빌드 에이전트 (기본적으로 비활성화)
|
- **Builder-Sisyphus**: OpenCode 기본 빌드 에이전트 (SDK 제한으로 이름만 변경, 기본적으로 비활성화)
|
||||||
- **Planner-Sisyphus**: OhMyOpenCode 강화 버전 플랜 에이전트 (기본적으로 활성화)
|
- **Planner-Sisyphus**: OpenCode 기본 플랜 에이전트 (SDK 제한으로 이름만 변경, 기본적으로 활성화)
|
||||||
|
|
||||||
**설정 옵션:**
|
**설정 옵션:**
|
||||||
|
|
||||||
@@ -773,8 +773,8 @@ Schema 자동 완성이 지원됩니다:
|
|||||||
| 옵션 | 기본값 | 설명 |
|
| 옵션 | 기본값 | 설명 |
|
||||||
| ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `disabled` | `false` | `true`면 모든 Sisyphus 오케스트레이션을 비활성화하고 원래 build/plan을 primary로 복원합니다. |
|
| `disabled` | `false` | `true`면 모든 Sisyphus 오케스트레이션을 비활성화하고 원래 build/plan을 primary로 복원합니다. |
|
||||||
| `builder_enabled` | `false` | `true`면 Builder-Sisyphus 에이전트 (OhMyOpenCode 강화 빌드 모드)를 활성화합니다. 기본 OpenCode 빌드 경험을 보존하기 위해 기본적으로 비활성화되어 있습니다. |
|
| `builder_enabled` | `false` | `true`면 Builder-Sisyphus 에이전트를 활성화합니다 (OpenCode build와 동일, SDK 제한으로 이름만 변경). 기본적으로 비활성화되어 있습니다. |
|
||||||
| `planner_enabled` | `true` | `true`면 Planner-Sisyphus 에이전트 (OhMyOpenCode 강화 플랜 모드)를 활성화합니다. 기본적으로 활성화되어 있습니다. |
|
| `planner_enabled` | `true` | `true`면 Planner-Sisyphus 에이전트를 활성화합니다 (OpenCode plan과 동일, SDK 제한으로 이름만 변경). 기본적으로 활성화되어 있습니다. |
|
||||||
| `replace_build` | `true` | `true`면 기본 빌드 에이전트를 subagent 모드로 강등시킵니다. `false`로 설정하면 Builder-Sisyphus와 기본 빌드를 모두 사용할 수 있습니다. |
|
| `replace_build` | `true` | `true`면 기본 빌드 에이전트를 subagent 모드로 강등시킵니다. `false`로 설정하면 Builder-Sisyphus와 기본 빌드를 모두 사용할 수 있습니다. |
|
||||||
| `replace_plan` | `true` | `true`면 기본 플랜 에이전트를 subagent 모드로 강등시킵니다. `false`로 설정하면 Planner-Sisyphus와 기본 플랜을 모두 사용할 수 있습니다. |
|
| `replace_plan` | `true` | `true`면 기본 플랜 에이전트를 subagent 모드로 강등시킵니다. `false`로 설정하면 Planner-Sisyphus와 기본 플랜을 모두 사용할 수 있습니다. |
|
||||||
|
|
||||||
|
|||||||
@@ -783,8 +783,8 @@ Available agents: `oracle`, `librarian`, `explore`, `frontend-ui-ux-engineer`, `
|
|||||||
When enabled (default), Sisyphus provides a powerful orchestrator with optional specialized agents:
|
When enabled (default), Sisyphus provides a powerful orchestrator with optional specialized agents:
|
||||||
|
|
||||||
- **Sisyphus**: Primary orchestrator agent (Claude Opus 4.5)
|
- **Sisyphus**: Primary orchestrator agent (Claude Opus 4.5)
|
||||||
- **Builder-Sisyphus**: Optional build agent with OhMyOpenCode enhancements (disabled by default)
|
- **Builder-Sisyphus**: OpenCode's default build agent, renamed due to SDK limitations (disabled by default)
|
||||||
- **Planner-Sisyphus**: Plan agent with OhMyOpenCode enhancements (enabled by default)
|
- **Planner-Sisyphus**: OpenCode's default plan agent, renamed due to SDK limitations (enabled by default)
|
||||||
|
|
||||||
**Configuration Options:**
|
**Configuration Options:**
|
||||||
|
|
||||||
@@ -845,8 +845,8 @@ You can also customize Sisyphus agents like other agents:
|
|||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
| ------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `disabled` | `false` | When `true`, disables all Sisyphus orchestration and restores original build/plan as primary. |
|
| `disabled` | `false` | When `true`, disables all Sisyphus orchestration and restores original build/plan as primary. |
|
||||||
| `builder_enabled` | `false` | When `true`, enables Builder-Sisyphus agent (OhMyOpenCode enhanced build mode). Disabled by default to preserve default OpenCode build experience. |
|
| `builder_enabled` | `false` | When `true`, enables Builder-Sisyphus agent (same as OpenCode build, renamed due to SDK limitations). Disabled by default. |
|
||||||
| `planner_enabled` | `true` | When `true`, enables Planner-Sisyphus agent (OhMyOpenCode enhanced plan mode). Enabled by default. |
|
| `planner_enabled` | `true` | When `true`, enables Planner-Sisyphus agent (same as OpenCode plan, renamed due to SDK limitations). Enabled by default. |
|
||||||
| `replace_build` | `true` | When `true`, demotes default build agent to subagent mode. Set to `false` to keep both Builder-Sisyphus and default build available. |
|
| `replace_build` | `true` | When `true`, demotes default build agent to subagent mode. Set to `false` to keep both Builder-Sisyphus and default build available. |
|
||||||
| `replace_plan` | `true` | When `true`, demotes default plan agent to subagent mode. Set to `false` to keep both Planner-Sisyphus and default plan available. |
|
| `replace_plan` | `true` | When `true`, demotes default plan agent to subagent mode. Set to `false` to keep both Planner-Sisyphus and default plan available. |
|
||||||
|
|
||||||
|
|||||||
@@ -717,8 +717,8 @@ Agent 爽了,你自然也爽。但我还想直接让你爽。
|
|||||||
默认开启。Sisyphus 提供一个强力的编排器,带可选的专门 Agent:
|
默认开启。Sisyphus 提供一个强力的编排器,带可选的专门 Agent:
|
||||||
|
|
||||||
- **Sisyphus**:主编排 Agent(Claude Opus 4.5)
|
- **Sisyphus**:主编排 Agent(Claude Opus 4.5)
|
||||||
- **Builder-Sisyphus**:OhMyOpenCode 增强版构建 Agent(默认禁用)
|
- **Builder-Sisyphus**:OpenCode 默认构建 Agent(因 SDK 限制仅改名,默认禁用)
|
||||||
- **Planner-Sisyphus**:OhMyOpenCode 增强版计划 Agent(默认启用)
|
- **Planner-Sisyphus**:OpenCode 默认计划 Agent(因 SDK 限制仅改名,默认启用)
|
||||||
|
|
||||||
**配置选项:**
|
**配置选项:**
|
||||||
|
|
||||||
@@ -779,8 +779,8 @@ Sisyphus Agent 也能自定义:
|
|||||||
| 选项 | 默认值 | 说明 |
|
| 选项 | 默认值 | 说明 |
|
||||||
| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `disabled` | `false` | 设为 `true` 就禁用所有 Sisyphus 编排,恢复原来的 build/plan。 |
|
| `disabled` | `false` | 设为 `true` 就禁用所有 Sisyphus 编排,恢复原来的 build/plan。 |
|
||||||
| `builder_enabled` | `false` | 设为 `true` 就启用 Builder-Sisyphus Agent(OhMyOpenCode 增强构建模式)。为了保留默认 OpenCode 构建体验,默认禁用。 |
|
| `builder_enabled` | `false` | 设为 `true` 就启用 Builder-Sisyphus Agent(与 OpenCode build 相同,因 SDK 限制仅改名)。默认禁用。 |
|
||||||
| `planner_enabled` | `true` | 设为 `true` 就启用 Planner-Sisyphus Agent(OhMyOpenCode 增强计划模式)。默认启用。 |
|
| `planner_enabled` | `true` | 设为 `true` 就启用 Planner-Sisyphus Agent(与 OpenCode plan 相同,因 SDK 限制仅改名)。默认启用。 |
|
||||||
| `replace_build` | `true` | 设为 `true` 就把默认构建 Agent 降级为子 Agent 模式。设为 `false` 可以同时保留 Builder-Sisyphus 和默认构建。 |
|
| `replace_build` | `true` | 设为 `true` 就把默认构建 Agent 降级为子 Agent 模式。设为 `false` 可以同时保留 Builder-Sisyphus 和默认构建。 |
|
||||||
| `replace_plan` | `true` | 设为 `true` 就把默认计划 Agent 降级为子 Agent 模式。设为 `false` 可以同时保留 Planner-Sisyphus 和默认计划。 |
|
| `replace_plan` | `true` | 设为 `true` 就把默认计划 Agent 降级为子 Agent 模式。设为 `false` 可以同时保留 Planner-Sisyphus 和默认计划。 |
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,15 @@ async function* toAsyncIterable<T>(items: T[]): AsyncIterable<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("createEventState", () => {
|
describe("createEventState", () => {
|
||||||
it("creates initial state with mainSessionIdle false and empty lastOutput", () => {
|
it("creates initial state with correct defaults", () => {
|
||||||
// #given / #when
|
// #given / #when
|
||||||
const state = createEventState()
|
const state = createEventState()
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(state.mainSessionIdle).toBe(false)
|
expect(state.mainSessionIdle).toBe(false)
|
||||||
expect(state.lastOutput).toBe("")
|
expect(state.lastOutput).toBe("")
|
||||||
|
expect(state.lastPartText).toBe("")
|
||||||
|
expect(state.currentTool).toBe(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -73,6 +75,8 @@ describe("event handling", () => {
|
|||||||
const state: EventState = {
|
const state: EventState = {
|
||||||
mainSessionIdle: true,
|
mainSessionIdle: true,
|
||||||
lastOutput: "",
|
lastOutput: "",
|
||||||
|
lastPartText: "",
|
||||||
|
currentTool: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload: EventPayload = {
|
const payload: EventPayload = {
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
|
import pc from "picocolors"
|
||||||
import type {
|
import type {
|
||||||
RunContext,
|
RunContext,
|
||||||
EventPayload,
|
EventPayload,
|
||||||
SessionIdleProps,
|
SessionIdleProps,
|
||||||
SessionStatusProps,
|
SessionStatusProps,
|
||||||
MessageUpdatedProps,
|
MessageUpdatedProps,
|
||||||
|
MessagePartUpdatedProps,
|
||||||
|
ToolExecuteProps,
|
||||||
|
ToolResultProps,
|
||||||
} from "./types"
|
} from "./types"
|
||||||
|
|
||||||
export interface EventState {
|
export interface EventState {
|
||||||
mainSessionIdle: boolean
|
mainSessionIdle: boolean
|
||||||
lastOutput: string
|
lastOutput: string
|
||||||
|
lastPartText: string
|
||||||
|
currentTool: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEventState(): EventState {
|
export function createEventState(): EventState {
|
||||||
return {
|
return {
|
||||||
mainSessionIdle: false,
|
mainSessionIdle: false,
|
||||||
lastOutput: "",
|
lastOutput: "",
|
||||||
|
lastPartText: "",
|
||||||
|
currentTool: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +40,10 @@ export async function processEvents(
|
|||||||
|
|
||||||
handleSessionIdle(ctx, payload, state)
|
handleSessionIdle(ctx, payload, state)
|
||||||
handleSessionStatus(ctx, payload, state)
|
handleSessionStatus(ctx, payload, state)
|
||||||
|
handleMessagePartUpdated(ctx, payload, state)
|
||||||
handleMessageUpdated(ctx, payload, state)
|
handleMessageUpdated(ctx, payload, state)
|
||||||
|
handleToolExecute(ctx, payload, state)
|
||||||
|
handleToolResult(ctx, payload, state)
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,6 +74,29 @@ function handleSessionStatus(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleMessagePartUpdated(
|
||||||
|
ctx: RunContext,
|
||||||
|
payload: EventPayload,
|
||||||
|
state: EventState
|
||||||
|
): void {
|
||||||
|
if (payload.type !== "message.part.updated") return
|
||||||
|
|
||||||
|
const props = payload.properties as MessagePartUpdatedProps | undefined
|
||||||
|
if (props?.info?.sessionID !== ctx.sessionID) return
|
||||||
|
if (props?.info?.role !== "assistant") return
|
||||||
|
|
||||||
|
const part = props.part
|
||||||
|
if (!part) return
|
||||||
|
|
||||||
|
if (part.type === "text" && part.text) {
|
||||||
|
const newText = part.text.slice(state.lastPartText.length)
|
||||||
|
if (newText) {
|
||||||
|
process.stdout.write(newText)
|
||||||
|
}
|
||||||
|
state.lastPartText = part.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleMessageUpdated(
|
function handleMessageUpdated(
|
||||||
ctx: RunContext,
|
ctx: RunContext,
|
||||||
payload: EventPayload,
|
payload: EventPayload,
|
||||||
@@ -77,9 +111,66 @@ function handleMessageUpdated(
|
|||||||
const content = props.content
|
const content = props.content
|
||||||
if (!content || content === state.lastOutput) return
|
if (!content || content === state.lastOutput) return
|
||||||
|
|
||||||
const newContent = content.slice(state.lastOutput.length)
|
if (state.lastPartText.length === 0) {
|
||||||
if (newContent) {
|
const newContent = content.slice(state.lastOutput.length)
|
||||||
process.stdout.write(newContent)
|
if (newContent) {
|
||||||
|
process.stdout.write(newContent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
state.lastOutput = content
|
state.lastOutput = content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleToolExecute(
|
||||||
|
ctx: RunContext,
|
||||||
|
payload: EventPayload,
|
||||||
|
state: EventState
|
||||||
|
): void {
|
||||||
|
if (payload.type !== "tool.execute") return
|
||||||
|
|
||||||
|
const props = payload.properties as ToolExecuteProps | undefined
|
||||||
|
if (props?.sessionID !== ctx.sessionID) return
|
||||||
|
|
||||||
|
const toolName = props?.name || "unknown"
|
||||||
|
state.currentTool = toolName
|
||||||
|
|
||||||
|
let inputPreview = ""
|
||||||
|
if (props?.input) {
|
||||||
|
const input = props.input
|
||||||
|
if (input.command) {
|
||||||
|
inputPreview = ` ${pc.dim(String(input.command).slice(0, 60))}`
|
||||||
|
} else if (input.pattern) {
|
||||||
|
inputPreview = ` ${pc.dim(String(input.pattern).slice(0, 40))}`
|
||||||
|
} else if (input.filePath) {
|
||||||
|
inputPreview = ` ${pc.dim(String(input.filePath))}`
|
||||||
|
} else if (input.query) {
|
||||||
|
inputPreview = ` ${pc.dim(String(input.query).slice(0, 40))}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(`\n${pc.cyan("⚡")} ${pc.bold(toolName)}${inputPreview}\n`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToolResult(
|
||||||
|
ctx: RunContext,
|
||||||
|
payload: EventPayload,
|
||||||
|
state: EventState
|
||||||
|
): void {
|
||||||
|
if (payload.type !== "tool.result") return
|
||||||
|
|
||||||
|
const props = payload.properties as ToolResultProps | undefined
|
||||||
|
if (props?.sessionID !== ctx.sessionID) return
|
||||||
|
|
||||||
|
const output = props?.output || ""
|
||||||
|
const maxLen = 200
|
||||||
|
const preview = output.length > maxLen
|
||||||
|
? output.slice(0, maxLen) + "..."
|
||||||
|
: output
|
||||||
|
|
||||||
|
if (preview.trim()) {
|
||||||
|
const lines = preview.split("\n").slice(0, 3)
|
||||||
|
process.stdout.write(pc.dim(` └─ ${lines.join("\n ")}\n`))
|
||||||
|
}
|
||||||
|
|
||||||
|
state.currentTool = null
|
||||||
|
state.lastPartText = ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,3 +47,25 @@ export interface MessageUpdatedProps {
|
|||||||
info?: { sessionID?: string; role?: string }
|
info?: { sessionID?: string; role?: string }
|
||||||
content?: string
|
content?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MessagePartUpdatedProps {
|
||||||
|
info?: { sessionID?: string; role?: string }
|
||||||
|
part?: {
|
||||||
|
type?: string
|
||||||
|
text?: string
|
||||||
|
name?: string
|
||||||
|
input?: unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolExecuteProps {
|
||||||
|
sessionID?: string
|
||||||
|
name?: string
|
||||||
|
input?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolResultProps {
|
||||||
|
sessionID?: string
|
||||||
|
name?: string
|
||||||
|
output?: string
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user