@zhijiewang/openharness 2.26.0 → 2.28.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/dist/Tool.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import type { z } from "zod";
6
6
  import type { Provider } from "./providers/base.js";
7
+ import type { ToolCallComplete, ToolCallEnd, ToolCallStart, ToolOutputDelta } from "./types/events.js";
7
8
  import type { PermissionMode, RiskLevel } from "./types/permissions.js";
8
9
  export type ToolResult = {
9
10
  output: string;
@@ -26,6 +27,8 @@ export type ToolContext = {
26
27
  askUserQuestion?: (question: string, options?: string[]) => Promise<string>;
27
28
  /** Auto-commit after file-modifying tools */
28
29
  gitCommitPerTool?: boolean;
30
+ /** Forward an inner-query tool event to the outer event stream, stamped with the parent's callId. Used by AgentTool and AgentDispatcher to surface nested tool calls. */
31
+ emitChildEvent?: (event: ToolCallStart | ToolCallComplete | ToolCallEnd | ToolOutputDelta) => void;
29
32
  };
30
33
  export type Tool<Input extends z.ZodType = z.ZodType> = {
31
34
  readonly name: string;
@@ -311,15 +311,18 @@ export async function executeSingleTool(toolCall, tools, context, permissionMode
311
311
  }
312
312
  export async function* executeToolCalls(toolCalls, tools, context, permissionMode, askUser, state, permissionPromptTool) {
313
313
  const batches = partitionToolCalls(toolCalls, tools);
314
- const outputChunks = [];
314
+ const childEvents = [];
315
315
  const onOutputChunk = (callId, chunk) => {
316
- outputChunks.push({ type: "tool_output_delta", callId, chunk });
316
+ childEvents.push({ type: "tool_output_delta", callId, chunk });
317
+ };
318
+ const emitChildEvent = (event) => {
319
+ childEvents.push(event);
317
320
  };
318
321
  const allToolNames = toolCalls.map((tc) => tc.toolName);
319
322
  for (const batch of batches) {
320
323
  if (batch.concurrent) {
321
- const results = await Promise.all(batch.calls.map((tc) => executeSingleTool(tc, tools, { ...context, callId: tc.id, onOutputChunk }, permissionMode, askUser, permissionPromptTool)));
322
- for (const chunk of outputChunks.splice(0))
324
+ const results = await Promise.all(batch.calls.map((tc) => executeSingleTool(tc, tools, { ...context, callId: tc.id, onOutputChunk, emitChildEvent }, permissionMode, askUser, permissionPromptTool)));
325
+ for (const chunk of childEvents.splice(0))
323
326
  yield chunk;
324
327
  for (let i = 0; i < batch.calls.length; i++) {
325
328
  const tc = batch.calls[i];
@@ -330,8 +333,8 @@ export async function* executeToolCalls(toolCalls, tools, context, permissionMod
330
333
  }
331
334
  else {
332
335
  for (const tc of batch.calls) {
333
- const result = await executeSingleTool(tc, tools, { ...context, callId: tc.id, onOutputChunk }, permissionMode, askUser, permissionPromptTool);
334
- for (const chunk of outputChunks.splice(0))
336
+ const result = await executeSingleTool(tc, tools, { ...context, callId: tc.id, onOutputChunk, emitChildEvent }, permissionMode, askUser, permissionPromptTool);
337
+ for (const chunk of childEvents.splice(0))
335
338
  yield chunk;
336
339
  yield { type: "tool_call_end", callId: tc.id, output: result.output, isError: result.isError };
337
340
  state?.messages.push(createToolResultMessage({ callId: tc.id, output: result.output, isError: result.isError }));
@@ -7,6 +7,7 @@ import { renderDiff } from "./diff.js";
7
7
  import { renderToolOutput } from "./output-renderer.js";
8
8
  import { deriveSpinnerLabel } from "./spinner-label.js";
9
9
  import { toolColor } from "./tool-color.js";
10
+ import { buildToolCallTree } from "./tool-tree.js";
10
11
  // ── Style constants ──
11
12
  const s = (fg, bold = false, dim = false) => ({ fg, bg: null, bold, dim, underline: false });
12
13
  export const S_TEXT = s(null);
@@ -128,10 +129,11 @@ export function renderErrorSection(state, grid, r, limit) {
128
129
  }
129
130
  export function renderToolCallsSection(state, grid, r, limit, opts) {
130
131
  const w = grid.width;
131
- for (const [callId, tc] of state.toolCalls) {
132
- if (r >= limit)
133
- break;
134
- const isAgent = tc.isAgent || tc.toolName === "Agent" || tc.toolName === "ParallelAgents";
132
+ const tree = buildToolCallTree(state.toolCalls);
133
+ const MAX_DEPTH = 3;
134
+ const renderSingleCall = (callId, tc, depth) => {
135
+ const colOffset = depth * 4;
136
+ const isAgent = tc.isAgent || tc.toolName === "Agent" || tc.toolName === "ParallelAgents" || tc.toolName === "Task";
135
137
  const icon = isAgent
136
138
  ? tc.status === "running"
137
139
  ? "⊕"
@@ -149,11 +151,11 @@ export function renderToolCallsSection(state, grid, r, limit, opts) {
149
151
  const isExpanded = state.expandedToolCalls.has(callId);
150
152
  const canExpand = tc.status !== "running" && tc.output;
151
153
  if (canExpand) {
152
- grid.writeText(r, 0, isExpanded ? "▼" : "▶", S_DIM);
154
+ grid.writeText(r, 0 + colOffset, isExpanded ? "▼" : "▶", S_DIM);
153
155
  }
154
- grid.writeText(r, 2, `${icon} `, statusStyle);
155
- grid.writeText(r, 4, tc.toolName, nameStyle);
156
- let afterName = 4 + tc.toolName.length + 1;
156
+ grid.writeText(r, 2 + colOffset, `${icon} `, statusStyle);
157
+ grid.writeText(r, 4 + colOffset, tc.toolName, nameStyle);
158
+ let afterName = 4 + colOffset + tc.toolName.length + 1;
157
159
  if (tc.args) {
158
160
  const maxArgs = w - afterName - 15;
159
161
  if (maxArgs > 5) {
@@ -177,34 +179,57 @@ export function renderToolCallsSection(state, grid, r, limit, opts) {
177
179
  }
178
180
  r++;
179
181
  if (isAgent && tc.agentDescription && r < limit) {
180
- grid.writeText(r, 6, tc.agentDescription.slice(0, w - 8), S_DIM);
182
+ grid.writeText(r, 6 + colOffset, tc.agentDescription.slice(0, w - 8 - colOffset), S_DIM);
181
183
  r++;
182
184
  }
183
185
  if (tc.status === "running" && tc.liveOutput && tc.liveOutput.length > 0) {
184
186
  const overflow = tc.liveOutput.length > opts.maxLiveLines ? tc.liveOutput.length - opts.maxLiveLines : 0;
185
187
  if (opts.showOverflow && overflow > 0 && r < limit) {
186
- grid.writeText(r, 6, `… (${overflow} earlier lines)`, S_DIM);
188
+ grid.writeText(r, 6 + colOffset, `… (${overflow} earlier lines)`, S_DIM);
187
189
  r++;
188
190
  }
189
191
  const visible = overflow > 0 ? tc.liveOutput.slice(-opts.maxLiveLines) : tc.liveOutput;
190
192
  for (const line of visible) {
191
193
  if (r >= limit)
192
194
  break;
193
- grid.writeTextWithLinks(r, 6, line.slice(0, w - 8), S_DIM, w - 2);
195
+ grid.writeTextWithLinks(r, 6 + colOffset, line.slice(0, w - 8 - colOffset), S_DIM, w - 2);
194
196
  r++;
195
197
  }
196
198
  }
197
199
  if (tc.output && tc.status !== "running" && isExpanded && r < limit) {
198
- const consumed = renderToolOutput(grid, r, 6, tc.output, tc.outputType, w - 8, {
200
+ const consumed = renderToolOutput(grid, r, 6 + colOffset, tc.output, tc.outputType, w - 8 - colOffset, {
199
201
  status: tc.status,
200
202
  maxLines: 20,
201
203
  limit,
202
204
  });
203
205
  r += consumed;
204
206
  }
205
- }
207
+ };
208
+ const renderNode = (node) => {
209
+ if (r >= limit)
210
+ return;
211
+ if (node.depth > MAX_DEPTH) {
212
+ const descendants = countDescendants(node) + 1;
213
+ const colOffset = MAX_DEPTH * 4;
214
+ const noun = descendants === 1 ? "level" : "levels";
215
+ grid.writeText(r, colOffset, `… (${descendants} more ${noun})`, S_DIM);
216
+ r++;
217
+ return;
218
+ }
219
+ renderSingleCall(node.callId, node.call, node.depth);
220
+ for (const child of node.children)
221
+ renderNode(child);
222
+ };
223
+ for (const root of tree)
224
+ renderNode(root);
206
225
  return r;
207
226
  }
227
+ function countDescendants(node) {
228
+ let n = node.children.length;
229
+ for (const c of node.children)
230
+ n += countDescendants(c);
231
+ return n;
232
+ }
208
233
  export function renderContextWarningSection(state, grid, r, limit) {
209
234
  if (!state.contextWarning || r >= limit)
210
235
  return r;
@@ -11,6 +11,7 @@ export { resetStyleCache } from "./layout-sections.js";
11
11
  export type ToolCallInfo = {
12
12
  toolName: string;
13
13
  status: "running" | "done" | "error";
14
+ parentCallId?: string;
14
15
  output?: string;
15
16
  outputType?: "json" | "markdown" | "image" | "plain";
16
17
  args?: string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tree builder for tool calls — walks the flat callId map and produces a
3
+ * depth-first parent-child tree for rendering.
4
+ */
5
+ import type { ToolCallInfo } from "./layout.js";
6
+ export type TreeNode = {
7
+ callId: string;
8
+ call: ToolCallInfo;
9
+ children: TreeNode[];
10
+ depth: number;
11
+ };
12
+ export declare function buildToolCallTree(toolCalls: Map<string, ToolCallInfo>): TreeNode[];
13
+ //# sourceMappingURL=tool-tree.d.ts.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Tree builder for tool calls — walks the flat callId map and produces a
3
+ * depth-first parent-child tree for rendering.
4
+ */
5
+ export function buildToolCallTree(toolCalls) {
6
+ const childrenOf = new Map();
7
+ const allIds = new Set();
8
+ for (const [callId, info] of toolCalls) {
9
+ allIds.add(callId);
10
+ const parent = info.parentCallId;
11
+ if (parent === undefined)
12
+ continue;
13
+ const list = childrenOf.get(parent) ?? [];
14
+ list.push(callId);
15
+ childrenOf.set(parent, list);
16
+ }
17
+ const roots = [];
18
+ for (const [callId, info] of toolCalls) {
19
+ const parent = info.parentCallId;
20
+ if (parent === undefined || !allIds.has(parent)) {
21
+ roots.push(callId);
22
+ }
23
+ }
24
+ const seen = new Set();
25
+ const build = (callId, depth) => {
26
+ if (seen.has(callId))
27
+ return null;
28
+ seen.add(callId);
29
+ const info = toolCalls.get(callId);
30
+ if (!info)
31
+ return null;
32
+ const childIds = childrenOf.get(callId) ?? [];
33
+ const children = [];
34
+ for (const cid of childIds) {
35
+ const child = build(cid, depth + 1);
36
+ if (child)
37
+ children.push(child);
38
+ }
39
+ return { callId, call: info, children, depth };
40
+ };
41
+ const result = [];
42
+ for (const rootId of roots) {
43
+ const node = build(rootId, 0);
44
+ if (node)
45
+ result.push(node);
46
+ }
47
+ return result;
48
+ }
49
+ //# sourceMappingURL=tool-tree.js.map
package/dist/repl.js CHANGED
@@ -934,19 +934,20 @@ export async function startREPL(config) {
934
934
  break;
935
935
  case "tool_call_start": {
936
936
  callIdToToolName.set(event.callId, event.toolName);
937
- const isAgentTool = event.toolName === "Agent" || event.toolName === "ParallelAgents";
937
+ const isAgentTool = event.toolName === "Agent" || event.toolName === "ParallelAgents" || event.toolName === "Task";
938
938
  renderer.setToolCall(event.callId, {
939
939
  toolName: event.toolName,
940
940
  status: "running",
941
941
  startedAt: Date.now(),
942
942
  isAgent: isAgentTool,
943
+ parentCallId: event.parentCallId,
943
944
  });
944
945
  break;
945
946
  }
946
947
  case "tool_call_complete": {
947
948
  const tcToolName = callIdToToolName.get(event.callId) ?? "";
948
949
  const existingTc = renderer.getToolCall(event.callId);
949
- const isAgentCall = tcToolName === "Agent" || tcToolName === "ParallelAgents";
950
+ const isAgentCall = tcToolName === "Agent" || tcToolName === "ParallelAgents" || tcToolName === "Task";
950
951
  const agentDesc = isAgentCall
951
952
  ? event.arguments.description
952
953
  : undefined;
@@ -956,6 +957,7 @@ export async function startREPL(config) {
956
957
  status: "running",
957
958
  args: formatToolArgs(tcToolName, event.arguments),
958
959
  agentDescription: agentDesc ?? existingTc?.agentDescription,
960
+ parentCallId: event.parentCallId ?? existingTc?.parentCallId,
959
961
  });
960
962
  break;
961
963
  }
@@ -986,6 +988,7 @@ export async function startREPL(config) {
986
988
  status: event.isError ? "error" : "done",
987
989
  output: event.output?.slice(0, TOOL_OUTPUT_RENDER_CAP),
988
990
  outputType: event.outputType,
991
+ parentCallId: event.parentCallId ?? prevTc?.parentCallId,
989
992
  args: prevTc?.args,
990
993
  resultSummary: event.output ? summarizeToolOutput(event.output) : undefined,
991
994
  startedAt: prevTc?.startedAt,
@@ -7,7 +7,13 @@
7
7
  */
8
8
  import type { Provider } from "../providers/base.js";
9
9
  import type { Tools } from "../Tool.js";
10
+ import type { StreamEvent, ToolCallComplete, ToolCallEnd, ToolCallStart, ToolOutputDelta } from "../types/events.js";
10
11
  import type { PermissionMode } from "../types/permissions.js";
12
+ /**
13
+ * Forward inner-loop tool events to the outer stream, stamping parentCallId.
14
+ * Exported for direct unit testing.
15
+ */
16
+ export declare function forwardChildEvent(event: StreamEvent, parentCallId: string | undefined, emit: ((e: ToolCallStart | ToolCallComplete | ToolCallEnd | ToolOutputDelta) => void) | undefined): boolean;
11
17
  export type AgentTask = {
12
18
  id: string;
13
19
  prompt: string;
@@ -29,10 +35,12 @@ export declare class AgentDispatcher {
29
35
  private model?;
30
36
  private workingDir?;
31
37
  private abortSignal?;
38
+ private parentCallId?;
39
+ private emitChildEvent?;
32
40
  private tasks;
33
41
  private results;
34
42
  private maxConcurrency;
35
- constructor(provider: Provider, tools: Tools, systemPrompt: string, permissionMode: PermissionMode, model?: string | undefined, workingDir?: string | undefined, abortSignal?: AbortSignal | undefined, maxConcurrency?: number);
43
+ constructor(provider: Provider, tools: Tools, systemPrompt: string, permissionMode: PermissionMode, model?: string | undefined, workingDir?: string | undefined, abortSignal?: AbortSignal | undefined, maxConcurrency?: number, parentCallId?: string | undefined, emitChildEvent?: ((event: ToolCallStart | ToolCallComplete | ToolCallEnd | ToolOutputDelta) => void) | undefined);
36
44
  addTask(task: AgentTask): void;
37
45
  addTasks(tasks: AgentTask[]): void;
38
46
  /** Execute all tasks respecting dependencies. Returns results in completion order. */
@@ -6,6 +6,22 @@
6
6
  * and triggers dependent tasks when their blockers complete.
7
7
  */
8
8
  import { createWorktree, isGitRepo, removeWorktree } from "../git/index.js";
9
+ /**
10
+ * Forward inner-loop tool events to the outer stream, stamping parentCallId.
11
+ * Exported for direct unit testing.
12
+ */
13
+ export function forwardChildEvent(event, parentCallId, emit) {
14
+ if (!emit || !parentCallId)
15
+ return false;
16
+ if (event.type === "tool_call_start" ||
17
+ event.type === "tool_call_complete" ||
18
+ event.type === "tool_call_end" ||
19
+ event.type === "tool_output_delta") {
20
+ emit({ ...event, parentCallId });
21
+ return true;
22
+ }
23
+ return false;
24
+ }
9
25
  export class AgentDispatcher {
10
26
  provider;
11
27
  tools;
@@ -14,10 +30,12 @@ export class AgentDispatcher {
14
30
  model;
15
31
  workingDir;
16
32
  abortSignal;
33
+ parentCallId;
34
+ emitChildEvent;
17
35
  tasks;
18
36
  results = new Map();
19
37
  maxConcurrency;
20
- constructor(provider, tools, systemPrompt, permissionMode, model, workingDir, abortSignal, maxConcurrency = 4) {
38
+ constructor(provider, tools, systemPrompt, permissionMode, model, workingDir, abortSignal, maxConcurrency = 4, parentCallId, emitChildEvent) {
21
39
  this.provider = provider;
22
40
  this.tools = tools;
23
41
  this.systemPrompt = systemPrompt;
@@ -25,6 +43,8 @@ export class AgentDispatcher {
25
43
  this.model = model;
26
44
  this.workingDir = workingDir;
27
45
  this.abortSignal = abortSignal;
46
+ this.parentCallId = parentCallId;
47
+ this.emitChildEvent = emitChildEvent;
28
48
  this.tasks = new Map();
29
49
  this.maxConcurrency = maxConcurrency;
30
50
  }
@@ -108,6 +128,25 @@ export class AgentDispatcher {
108
128
  const cwd = this.workingDir ?? process.cwd();
109
129
  const useWorktree = isGitRepo(cwd);
110
130
  let worktreePath = null;
131
+ let result;
132
+ const taskCallId = `task-${task.id}-${Date.now().toString(36)}`;
133
+ const taskDescription = task.description ?? task.id;
134
+ const synthEnabled = !!this.emitChildEvent && !!this.parentCallId;
135
+ if (synthEnabled) {
136
+ this.emitChildEvent({
137
+ type: "tool_call_start",
138
+ toolName: "Task",
139
+ callId: taskCallId,
140
+ parentCallId: this.parentCallId,
141
+ });
142
+ this.emitChildEvent({
143
+ type: "tool_call_complete",
144
+ toolName: "Task",
145
+ callId: taskCallId,
146
+ arguments: { description: taskDescription },
147
+ parentCallId: this.parentCallId,
148
+ });
149
+ }
111
150
  if (useWorktree) {
112
151
  worktreePath = createWorktree(cwd);
113
152
  }
@@ -155,13 +194,16 @@ export class AgentDispatcher {
155
194
  }
156
195
  }
157
196
  let output = "";
197
+ let errorMessage = null;
158
198
  try {
159
199
  for await (const event of query(promptWithContext, config)) {
160
200
  if (event.type === "text_delta")
161
201
  output += event.content;
162
202
  if (event.type === "error") {
163
- return { id: task.id, output: `Error: ${event.message}`, isError: true, durationMs: Date.now() - start };
203
+ errorMessage = event.message;
204
+ break;
164
205
  }
206
+ forwardChildEvent(event, taskCallId, this.emitChildEvent);
165
207
  }
166
208
  }
167
209
  finally {
@@ -174,10 +216,15 @@ export class AgentDispatcher {
174
216
  }
175
217
  }
176
218
  }
177
- return { id: task.id, output: output || "(no output)", isError: false, durationMs: Date.now() - start };
219
+ if (errorMessage !== null) {
220
+ result = { id: task.id, output: `Error: ${errorMessage}`, isError: true, durationMs: Date.now() - start };
221
+ }
222
+ else {
223
+ result = { id: task.id, output: output || "(no output)", isError: false, durationMs: Date.now() - start };
224
+ }
178
225
  }
179
226
  catch (err) {
180
- return {
227
+ result = {
181
228
  id: task.id,
182
229
  output: `Failed: ${err instanceof Error ? err.message : String(err)}`,
183
230
  isError: true,
@@ -188,7 +235,17 @@ export class AgentDispatcher {
188
235
  if (worktreePath) {
189
236
  removeWorktree(worktreePath, cwd);
190
237
  }
238
+ if (synthEnabled) {
239
+ this.emitChildEvent({
240
+ type: "tool_call_end",
241
+ callId: taskCallId,
242
+ output: result.output,
243
+ isError: result.isError,
244
+ parentCallId: this.parentCallId,
245
+ });
246
+ }
191
247
  }
248
+ return result;
192
249
  }
193
250
  }
194
251
  //# sourceMappingURL=AgentDispatcher.js.map
@@ -1,5 +1,17 @@
1
1
  import { z } from "zod";
2
- import type { Tool } from "../../Tool.js";
2
+ import type { Tool, ToolContext } from "../../Tool.js";
3
+ import type { StreamEvent } from "../../types/events.js";
4
+ /**
5
+ * Forward a single inner-query event to the outer stream via `context.emitChildEvent`,
6
+ * stamping it with `parentCallId = context.callId`.
7
+ *
8
+ * Handles: tool_call_start, tool_call_complete, tool_call_end, tool_output_delta.
9
+ * Returns true when the event was forwarded, false otherwise.
10
+ *
11
+ * Exported for unit testing — the hot path in AgentTool.call() calls this inline
12
+ * to keep the forwarding logic isolated and testable without stubbing query().
13
+ */
14
+ export declare function forwardInnerEvent(event: StreamEvent, context: ToolContext): boolean;
3
15
  declare const inputSchema: z.ZodObject<{
4
16
  prompt: z.ZodString;
5
17
  description: z.ZodOptional<z.ZodString>;
@@ -2,6 +2,28 @@ import { z } from "zod";
2
2
  import { createWorktree, hasWorktreeChanges, isGitRepo, removeWorktree } from "../../git/index.js";
3
3
  import { emitHook } from "../../harness/hooks.js";
4
4
  import { getMessageBus } from "../../services/agent-messaging.js";
5
+ /**
6
+ * Forward a single inner-query event to the outer stream via `context.emitChildEvent`,
7
+ * stamping it with `parentCallId = context.callId`.
8
+ *
9
+ * Handles: tool_call_start, tool_call_complete, tool_call_end, tool_output_delta.
10
+ * Returns true when the event was forwarded, false otherwise.
11
+ *
12
+ * Exported for unit testing — the hot path in AgentTool.call() calls this inline
13
+ * to keep the forwarding logic isolated and testable without stubbing query().
14
+ */
15
+ export function forwardInnerEvent(event, context) {
16
+ if (!context.emitChildEvent || !context.callId)
17
+ return false;
18
+ if (event.type === "tool_call_start" ||
19
+ event.type === "tool_call_complete" ||
20
+ event.type === "tool_call_end" ||
21
+ event.type === "tool_output_delta") {
22
+ context.emitChildEvent({ ...event, parentCallId: event.parentCallId ?? context.callId });
23
+ return true;
24
+ }
25
+ return false;
26
+ }
5
27
  const inputSchema = z.object({
6
28
  prompt: z.string(),
7
29
  description: z.string().optional(),
@@ -146,6 +168,12 @@ export const AgentTool = {
146
168
  if (context.onOutputChunk && context.callId) {
147
169
  context.onOutputChunk(context.callId, event.chunk);
148
170
  }
171
+ forwardInnerEvent(event, context);
172
+ }
173
+ else if (event.type === "tool_call_start" ||
174
+ event.type === "tool_call_complete" ||
175
+ event.type === "tool_call_end") {
176
+ forwardInnerEvent(event, context);
149
177
  }
150
178
  else if (event.type === "error") {
151
179
  return { output: `Sub-agent error: ${event.message}`, isError: true };
@@ -25,7 +25,8 @@ export const ParallelAgentTool = {
25
25
  return { output: "Parallel agents unavailable: provider not in context.", isError: true };
26
26
  }
27
27
  const systemPrompt = context.systemPrompt ?? "You are a sub-agent. Complete the delegated task concisely.";
28
- const dispatcher = new AgentDispatcher(context.provider, context.tools, systemPrompt, context.permissionMode ?? "trust", context.model, context.workingDir, context.abortSignal);
28
+ const dispatcher = new AgentDispatcher(context.provider, context.tools, systemPrompt, context.permissionMode ?? "trust", context.model, context.workingDir, context.abortSignal, 4, // maxConcurrency default
29
+ context.callId, context.emitChildEvent);
29
30
  dispatcher.addTasks(input.tasks);
30
31
  const results = await dispatcher.execute();
31
32
  const output = results
@@ -9,12 +9,14 @@ export type ToolCallStart = {
9
9
  readonly type: "tool_call_start";
10
10
  readonly toolName: string;
11
11
  readonly callId: string;
12
+ readonly parentCallId?: string;
12
13
  };
13
14
  export type ToolCallComplete = {
14
15
  readonly type: "tool_call_complete";
15
16
  readonly callId: string;
16
17
  readonly toolName: string;
17
18
  readonly arguments: Record<string, unknown>;
19
+ readonly parentCallId?: string;
18
20
  };
19
21
  export type ToolCallEnd = {
20
22
  readonly type: "tool_call_end";
@@ -22,6 +24,7 @@ export type ToolCallEnd = {
22
24
  readonly output: string;
23
25
  readonly outputType?: "json" | "markdown" | "image" | "plain";
24
26
  readonly isError: boolean;
27
+ readonly parentCallId?: string;
25
28
  };
26
29
  export type PermissionRequest = {
27
30
  readonly type: "permission_request";
@@ -59,6 +62,7 @@ export type ToolOutputDelta = {
59
62
  readonly type: "tool_output_delta";
60
63
  readonly callId: string;
61
64
  readonly chunk: string;
65
+ readonly parentCallId?: string;
62
66
  };
63
67
  export type AskUserRequest = {
64
68
  readonly type: "ask_user";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhijiewang/openharness",
3
- "version": "2.26.0",
3
+ "version": "2.28.0",
4
4
  "description": "Open-source terminal coding agent. Works with any LLM.",
5
5
  "type": "module",
6
6
  "bin": {