@zhijiewang/openharness 0.12.1 → 1.2.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.
@@ -108,6 +108,10 @@ export class TerminalRenderer {
108
108
  clearInterval(this.animationTimer);
109
109
  this.animationTimer = null;
110
110
  }
111
+ if (this.renderTimer) {
112
+ clearTimeout(this.renderTimer);
113
+ this.renderTimer = null;
114
+ }
111
115
  if (this.resizeHandler) {
112
116
  process.stdout.off('resize', this.resizeHandler);
113
117
  this.resizeHandler = null;
@@ -363,15 +367,39 @@ export class TerminalRenderer {
363
367
  return true;
364
368
  }
365
369
  // ── Rendering ──
370
+ renderTimer = null;
371
+ lastRenderTime = 0;
372
+ static FRAME_MS = 16; // ~60fps
373
+ /**
374
+ * Schedule a render at ~60fps. Multiple calls within a frame are batched.
375
+ * This prevents excessive re-renders during fast token streaming.
376
+ */
366
377
  scheduleRender() {
367
378
  if (this.renderPending || !this.started)
368
379
  return;
369
380
  this.renderPending = true;
370
- queueMicrotask(() => {
371
- this.renderPending = false;
372
- if (this.started)
373
- this.render();
374
- });
381
+ const now = Date.now();
382
+ const elapsed = now - this.lastRenderTime;
383
+ if (elapsed >= TerminalRenderer.FRAME_MS) {
384
+ // Enough time has passed — render on next microtask (no delay)
385
+ queueMicrotask(() => {
386
+ this.renderPending = false;
387
+ this.lastRenderTime = Date.now();
388
+ if (this.started)
389
+ this.render();
390
+ });
391
+ }
392
+ else {
393
+ // Too soon — debounce to next frame boundary
394
+ const delay = TerminalRenderer.FRAME_MS - elapsed;
395
+ this.renderTimer = setTimeout(() => {
396
+ this.renderTimer = null;
397
+ this.renderPending = false;
398
+ this.lastRenderTime = Date.now();
399
+ if (this.started)
400
+ this.render();
401
+ }, delay);
402
+ }
375
403
  }
376
404
  /** Apply lightweight markdown styling to a line for scrollback output */
377
405
  styleMarkdownLine(line) {
package/dist/repl.js CHANGED
@@ -62,6 +62,13 @@ export async function startREPL(config) {
62
62
  const { CronExecutor } = await import('./services/CronExecutor.js');
63
63
  const cronExecutor = new CronExecutor(config.provider, config.tools, config.systemPrompt, config.permissionMode, config.model);
64
64
  cronExecutor.start();
65
+ // A2A: publish agent card for cross-process discovery
66
+ const { createSessionCard, publishCard, unpublishCard } = await import('./services/a2a.js');
67
+ const agentCard = createSessionCard(session.id, {
68
+ provider: config.provider.name,
69
+ model: config.model,
70
+ });
71
+ publishCard(agentCard);
65
72
  const cost = new CostTracker();
66
73
  let cachedConfig = readOhConfig();
67
74
  // Centralized state store — all REPL state lives here
@@ -923,6 +930,7 @@ export async function startREPL(config) {
923
930
  if (cleanedUp)
924
931
  return;
925
932
  cleanedUp = true;
933
+ unpublishCard(agentCard.id);
926
934
  cronExecutor.stop();
927
935
  renderer.stop();
928
936
  session.messages = messages;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * PipelineExecutor — declarative multi-step tool workflows.
3
+ *
4
+ * Executes a sequence of tool calls with dependency resolution and
5
+ * variable substitution. Steps can reference prior step outputs via $stepId.
6
+ *
7
+ * Unlike the LLM-mediated agent loop, pipelines are deterministic —
8
+ * faster, cheaper, and repeatable for known workflows.
9
+ *
10
+ * Reuses the dependency resolution pattern from AgentDispatcher.
11
+ */
12
+ import type { Tools, ToolContext } from '../Tool.js';
13
+ export type PipelineStep = {
14
+ id: string;
15
+ tool: string;
16
+ args: Record<string, unknown>;
17
+ dependsOn?: string[];
18
+ };
19
+ export type PipelineStepResult = {
20
+ stepId: string;
21
+ output: string;
22
+ isError: boolean;
23
+ durationMs: number;
24
+ };
25
+ export declare class PipelineExecutor {
26
+ private tools;
27
+ private context;
28
+ constructor(tools: Tools, context: ToolContext);
29
+ /**
30
+ * Execute a pipeline. Returns results for all steps.
31
+ * Steps with unmet dependencies (failed/skipped blockers) are skipped.
32
+ */
33
+ execute(steps: PipelineStep[]): Promise<PipelineStepResult[]>;
34
+ private isReady;
35
+ private hasFailedBlocker;
36
+ private executeStep;
37
+ /**
38
+ * Resolve $stepId references in args.
39
+ * If a string value starts with $, replace it with the output of that step.
40
+ * Supports nested objects and arrays.
41
+ */
42
+ private resolveArgs;
43
+ }
44
+ /**
45
+ * Format pipeline results as a readable summary.
46
+ */
47
+ export declare function formatPipelineResults(results: PipelineStepResult[]): string;
48
+ //# sourceMappingURL=PipelineExecutor.d.ts.map
@@ -0,0 +1,179 @@
1
+ /**
2
+ * PipelineExecutor — declarative multi-step tool workflows.
3
+ *
4
+ * Executes a sequence of tool calls with dependency resolution and
5
+ * variable substitution. Steps can reference prior step outputs via $stepId.
6
+ *
7
+ * Unlike the LLM-mediated agent loop, pipelines are deterministic —
8
+ * faster, cheaper, and repeatable for known workflows.
9
+ *
10
+ * Reuses the dependency resolution pattern from AgentDispatcher.
11
+ */
12
+ import { findToolByName } from '../Tool.js';
13
+ // ── Executor ──
14
+ export class PipelineExecutor {
15
+ tools;
16
+ context;
17
+ constructor(tools, context) {
18
+ this.tools = tools;
19
+ this.context = context;
20
+ }
21
+ /**
22
+ * Execute a pipeline. Returns results for all steps.
23
+ * Steps with unmet dependencies (failed/skipped blockers) are skipped.
24
+ */
25
+ async execute(steps) {
26
+ // Validate step IDs are unique
27
+ const ids = new Set(steps.map(s => s.id));
28
+ if (ids.size !== steps.length) {
29
+ return [{ stepId: 'pipeline', output: 'Error: duplicate step IDs', isError: true, durationMs: 0 }];
30
+ }
31
+ const internal = new Map();
32
+ for (const step of steps) {
33
+ internal.set(step.id, { ...step, status: 'pending' });
34
+ }
35
+ const results = [];
36
+ // Process steps in dependency order
37
+ while (true) {
38
+ const ready = [...internal.values()].filter(s => s.status === 'pending' && this.isReady(s, internal));
39
+ const running = [...internal.values()].filter(s => s.status === 'running');
40
+ if (ready.length === 0 && running.length === 0)
41
+ break;
42
+ // Execute ready steps (sequentially for safety — tools may have side effects)
43
+ for (const step of ready) {
44
+ step.status = 'running';
45
+ // Check if any blocker failed — skip this step
46
+ if (this.hasFailedBlocker(step, internal)) {
47
+ step.status = 'skipped';
48
+ const result = {
49
+ stepId: step.id,
50
+ output: 'Skipped: dependency failed',
51
+ isError: true,
52
+ durationMs: 0,
53
+ };
54
+ step.result = result;
55
+ results.push(result);
56
+ continue;
57
+ }
58
+ const result = await this.executeStep(step, internal);
59
+ step.result = result;
60
+ step.status = result.isError ? 'failed' : 'completed';
61
+ results.push(result);
62
+ }
63
+ }
64
+ return results;
65
+ }
66
+ isReady(step, all) {
67
+ if (!step.dependsOn || step.dependsOn.length === 0)
68
+ return true;
69
+ return step.dependsOn.every(id => {
70
+ const dep = all.get(id);
71
+ return dep && (dep.status === 'completed' || dep.status === 'failed' || dep.status === 'skipped');
72
+ });
73
+ }
74
+ hasFailedBlocker(step, all) {
75
+ if (!step.dependsOn)
76
+ return false;
77
+ return step.dependsOn.some(id => {
78
+ const dep = all.get(id);
79
+ return dep && (dep.status === 'failed' || dep.status === 'skipped');
80
+ });
81
+ }
82
+ async executeStep(step, all) {
83
+ const start = Date.now();
84
+ // Find the tool
85
+ const tool = findToolByName(this.tools, step.tool);
86
+ if (!tool) {
87
+ return {
88
+ stepId: step.id,
89
+ output: `Error: unknown tool '${step.tool}'`,
90
+ isError: true,
91
+ durationMs: Date.now() - start,
92
+ };
93
+ }
94
+ // Substitute $refs in args
95
+ const resolvedArgs = this.resolveArgs(step.args, all);
96
+ // Validate and execute
97
+ const parsed = tool.inputSchema.safeParse(resolvedArgs);
98
+ if (!parsed.success) {
99
+ return {
100
+ stepId: step.id,
101
+ output: `Validation error: ${parsed.error.message}`,
102
+ isError: true,
103
+ durationMs: Date.now() - start,
104
+ };
105
+ }
106
+ try {
107
+ const result = await tool.call(parsed.data, this.context);
108
+ return {
109
+ stepId: step.id,
110
+ output: result.output,
111
+ isError: result.isError,
112
+ durationMs: Date.now() - start,
113
+ };
114
+ }
115
+ catch (err) {
116
+ return {
117
+ stepId: step.id,
118
+ output: `Error: ${err instanceof Error ? err.message : String(err)}`,
119
+ isError: true,
120
+ durationMs: Date.now() - start,
121
+ };
122
+ }
123
+ }
124
+ /**
125
+ * Resolve $stepId references in args.
126
+ * If a string value starts with $, replace it with the output of that step.
127
+ * Supports nested objects and arrays.
128
+ */
129
+ resolveArgs(args, all) {
130
+ const resolve = (value) => {
131
+ if (typeof value === 'string' && value.startsWith('$')) {
132
+ const refId = value.slice(1);
133
+ const refStep = all.get(refId);
134
+ if (refStep?.result && !refStep.result.isError) {
135
+ return refStep.result.output;
136
+ }
137
+ return value; // Keep as-is if ref not found
138
+ }
139
+ if (Array.isArray(value))
140
+ return value.map(resolve);
141
+ if (value && typeof value === 'object') {
142
+ const resolved = {};
143
+ for (const [k, v] of Object.entries(value)) {
144
+ resolved[k] = resolve(v);
145
+ }
146
+ return resolved;
147
+ }
148
+ return value;
149
+ };
150
+ return resolve(args);
151
+ }
152
+ }
153
+ /**
154
+ * Format pipeline results as a readable summary.
155
+ */
156
+ export function formatPipelineResults(results) {
157
+ const lines = [];
158
+ let totalMs = 0;
159
+ for (const r of results) {
160
+ const status = r.isError ? '✗' : '✓';
161
+ const duration = r.durationMs > 0 ? ` (${r.durationMs}ms)` : '';
162
+ lines.push(`${status} Step "${r.stepId}"${duration}`);
163
+ // Show truncated output
164
+ const output = r.output.length > 200
165
+ ? r.output.slice(0, 200) + '...'
166
+ : r.output;
167
+ if (output) {
168
+ for (const line of output.split('\n').slice(0, 5)) {
169
+ lines.push(` ${line}`);
170
+ }
171
+ }
172
+ lines.push('');
173
+ totalMs += r.durationMs;
174
+ }
175
+ const passed = results.filter(r => !r.isError).length;
176
+ lines.push(`Pipeline: ${passed}/${results.length} steps passed (${totalMs}ms total)`);
177
+ return lines.join('\n');
178
+ }
179
+ //# sourceMappingURL=PipelineExecutor.js.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * A2A Protocol — Agent-to-Agent discovery and routing.
3
+ *
4
+ * Enables agents running in separate processes (or machines) to:
5
+ * - Advertise their capabilities via Agent Cards
6
+ * - Discover other agents via a shared registry
7
+ * - Route messages to agents by name or capability
8
+ * - Delegate tasks with typed request/response
9
+ *
10
+ * Registry is file-based (~/.oh/agents/) for same-machine agents.
11
+ * Each running agent writes a card file on startup and removes it on exit.
12
+ *
13
+ * Based on the emerging A2A (Agent-to-Agent) protocol standard.
14
+ */
15
+ export type AgentCard = {
16
+ /** Unique agent instance ID */
17
+ id: string;
18
+ /** Human-readable name */
19
+ name: string;
20
+ /** Agent version */
21
+ version: string;
22
+ /** What this agent can do */
23
+ capabilities: AgentCapability[];
24
+ /** How to reach this agent */
25
+ endpoint: AgentEndpoint;
26
+ /** When this card was published */
27
+ registeredAt: number;
28
+ /** PID of the agent process */
29
+ pid: number;
30
+ /** Provider and model info */
31
+ provider?: string;
32
+ model?: string;
33
+ /** Working directory */
34
+ workingDir?: string;
35
+ };
36
+ export type AgentCapability = {
37
+ /** Capability identifier (e.g., 'code-review', 'test-generation') */
38
+ name: string;
39
+ /** Human description */
40
+ description: string;
41
+ /** Input schema (JSON Schema format) */
42
+ inputSchema?: Record<string, unknown>;
43
+ /** Output schema */
44
+ outputSchema?: Record<string, unknown>;
45
+ };
46
+ export type AgentEndpoint = {
47
+ /** Transport type */
48
+ type: 'http' | 'ipc' | 'stdio';
49
+ /** Address (URL for http, socket path for ipc, pid for stdio) */
50
+ address: string;
51
+ /** Port for HTTP transport */
52
+ port?: number;
53
+ };
54
+ export type A2AMessage = {
55
+ /** Message ID */
56
+ id: string;
57
+ /** Source agent ID */
58
+ from: string;
59
+ /** Target agent ID or capability name */
60
+ to: string;
61
+ /** Message type */
62
+ type: 'task' | 'result' | 'status' | 'cancel' | 'discover';
63
+ /** Payload */
64
+ payload: A2APayload;
65
+ /** Timestamp */
66
+ timestamp: number;
67
+ };
68
+ export type A2APayload = {
69
+ kind: 'task';
70
+ capability: string;
71
+ input: unknown;
72
+ timeout?: number;
73
+ } | {
74
+ kind: 'result';
75
+ taskId: string;
76
+ output: unknown;
77
+ error?: string;
78
+ } | {
79
+ kind: 'status';
80
+ state: 'idle' | 'working' | 'done' | 'error';
81
+ progress?: string;
82
+ } | {
83
+ kind: 'cancel';
84
+ taskId: string;
85
+ reason?: string;
86
+ } | {
87
+ kind: 'discover';
88
+ filter?: {
89
+ capability?: string;
90
+ name?: string;
91
+ };
92
+ };
93
+ /** Publish an agent card to the shared registry */
94
+ export declare function publishCard(card: AgentCard): void;
95
+ /** Remove an agent card from the registry */
96
+ export declare function unpublishCard(agentId: string): void;
97
+ /** Discover all registered agents */
98
+ export declare function discoverAgents(): AgentCard[];
99
+ /** Find agents by capability name */
100
+ export declare function findAgentsByCapability(capabilityName: string): AgentCard[];
101
+ /** Find an agent by name */
102
+ export declare function findAgentByName(name: string): AgentCard | null;
103
+ /**
104
+ * Route a message to an agent.
105
+ * For HTTP endpoints: sends via fetch.
106
+ * For IPC/stdio: writes to the agent's inbox file.
107
+ */
108
+ export declare function routeMessage(message: A2AMessage): Promise<A2AMessage | null>;
109
+ /** Read pending messages from an agent's inbox */
110
+ export declare function readInbox(agentId: string): A2AMessage[];
111
+ /** Generate a unique message ID */
112
+ export declare function generateMessageId(): string;
113
+ /** Create a standard agent card for the current openHarness session */
114
+ export declare function createSessionCard(sessionId: string, opts?: {
115
+ provider?: string;
116
+ model?: string;
117
+ port?: number;
118
+ }): AgentCard;
119
+ //# sourceMappingURL=a2a.d.ts.map
@@ -0,0 +1,176 @@
1
+ /**
2
+ * A2A Protocol — Agent-to-Agent discovery and routing.
3
+ *
4
+ * Enables agents running in separate processes (or machines) to:
5
+ * - Advertise their capabilities via Agent Cards
6
+ * - Discover other agents via a shared registry
7
+ * - Route messages to agents by name or capability
8
+ * - Delegate tasks with typed request/response
9
+ *
10
+ * Registry is file-based (~/.oh/agents/) for same-machine agents.
11
+ * Each running agent writes a card file on startup and removes it on exit.
12
+ *
13
+ * Based on the emerging A2A (Agent-to-Agent) protocol standard.
14
+ */
15
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, unlinkSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ const AGENT_REGISTRY_DIR = join(homedir(), '.oh', 'agents');
19
+ // ── Registry Operations ──
20
+ /** Publish an agent card to the shared registry */
21
+ export function publishCard(card) {
22
+ mkdirSync(AGENT_REGISTRY_DIR, { recursive: true });
23
+ const filePath = join(AGENT_REGISTRY_DIR, `${card.id}.json`);
24
+ writeFileSync(filePath, JSON.stringify(card, null, 2));
25
+ }
26
+ /** Remove an agent card from the registry */
27
+ export function unpublishCard(agentId) {
28
+ const filePath = join(AGENT_REGISTRY_DIR, `${agentId}.json`);
29
+ try {
30
+ unlinkSync(filePath);
31
+ }
32
+ catch { /* ignore */ }
33
+ }
34
+ /** Discover all registered agents */
35
+ export function discoverAgents() {
36
+ if (!existsSync(AGENT_REGISTRY_DIR))
37
+ return [];
38
+ const cards = [];
39
+ for (const file of readdirSync(AGENT_REGISTRY_DIR).filter(f => f.endsWith('.json'))) {
40
+ try {
41
+ const raw = readFileSync(join(AGENT_REGISTRY_DIR, file), 'utf-8');
42
+ const card = JSON.parse(raw);
43
+ // Check if the agent process is still alive
44
+ if (isProcessAlive(card.pid)) {
45
+ cards.push(card);
46
+ }
47
+ else {
48
+ // Stale card — clean up
49
+ try {
50
+ unlinkSync(join(AGENT_REGISTRY_DIR, file));
51
+ }
52
+ catch { /* ignore */ }
53
+ }
54
+ }
55
+ catch { /* skip malformed cards */ }
56
+ }
57
+ return cards;
58
+ }
59
+ /** Find agents by capability name */
60
+ export function findAgentsByCapability(capabilityName) {
61
+ return discoverAgents().filter(card => card.capabilities.some(c => c.name.toLowerCase() === capabilityName.toLowerCase()));
62
+ }
63
+ /** Find an agent by name */
64
+ export function findAgentByName(name) {
65
+ return discoverAgents().find(c => c.name.toLowerCase() === name.toLowerCase()) ?? null;
66
+ }
67
+ // ── Message Routing ──
68
+ /**
69
+ * Route a message to an agent.
70
+ * For HTTP endpoints: sends via fetch.
71
+ * For IPC/stdio: writes to the agent's inbox file.
72
+ */
73
+ export async function routeMessage(message) {
74
+ // Find the target agent
75
+ let targetCard = null;
76
+ // Try by agent ID first
77
+ const agents = discoverAgents();
78
+ targetCard = agents.find(a => a.id === message.to) ?? null;
79
+ // Try by name
80
+ if (!targetCard) {
81
+ targetCard = agents.find(a => a.name.toLowerCase() === message.to.toLowerCase()) ?? null;
82
+ }
83
+ // Try by capability
84
+ if (!targetCard && message.type === 'task' && message.payload.kind === 'task') {
85
+ const capable = findAgentsByCapability(message.payload.capability);
86
+ if (capable.length > 0)
87
+ targetCard = capable[0];
88
+ }
89
+ if (!targetCard)
90
+ return null;
91
+ // Route based on endpoint type
92
+ switch (targetCard.endpoint.type) {
93
+ case 'http': {
94
+ try {
95
+ const url = `${targetCard.endpoint.address}${targetCard.endpoint.port ? ':' + targetCard.endpoint.port : ''}/a2a`;
96
+ const res = await fetch(url, {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/json' },
99
+ body: JSON.stringify(message),
100
+ signal: AbortSignal.timeout(30_000),
101
+ });
102
+ if (res.ok) {
103
+ return await res.json();
104
+ }
105
+ }
106
+ catch { /* delivery failed */ }
107
+ return null;
108
+ }
109
+ case 'ipc': {
110
+ // File-based inbox for local IPC
111
+ const inboxDir = join(AGENT_REGISTRY_DIR, 'inboxes', targetCard.id);
112
+ mkdirSync(inboxDir, { recursive: true });
113
+ const msgFile = join(inboxDir, `${message.id}.json`);
114
+ writeFileSync(msgFile, JSON.stringify(message, null, 2));
115
+ return null; // Async — no immediate response
116
+ }
117
+ default:
118
+ return null;
119
+ }
120
+ }
121
+ /** Read pending messages from an agent's inbox */
122
+ export function readInbox(agentId) {
123
+ const inboxDir = join(AGENT_REGISTRY_DIR, 'inboxes', agentId);
124
+ if (!existsSync(inboxDir))
125
+ return [];
126
+ const messages = [];
127
+ for (const file of readdirSync(inboxDir).filter(f => f.endsWith('.json'))) {
128
+ try {
129
+ const raw = readFileSync(join(inboxDir, file), 'utf-8');
130
+ messages.push(JSON.parse(raw));
131
+ // Remove after reading
132
+ unlinkSync(join(inboxDir, file));
133
+ }
134
+ catch { /* skip */ }
135
+ }
136
+ return messages.sort((a, b) => a.timestamp - b.timestamp);
137
+ }
138
+ // ── Helpers ──
139
+ /** Check if a process is still alive */
140
+ function isProcessAlive(pid) {
141
+ try {
142
+ process.kill(pid, 0); // Signal 0 = check existence only
143
+ return true;
144
+ }
145
+ catch {
146
+ return false;
147
+ }
148
+ }
149
+ /** Generate a unique message ID */
150
+ export function generateMessageId() {
151
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
152
+ }
153
+ /** Create a standard agent card for the current openHarness session */
154
+ export function createSessionCard(sessionId, opts = {}) {
155
+ return {
156
+ id: `oh-${sessionId}`,
157
+ name: `openharness-${sessionId.slice(0, 6)}`,
158
+ version: '1.0.0',
159
+ capabilities: [
160
+ { name: 'code-generation', description: 'Generate, edit, and review code' },
161
+ { name: 'code-review', description: 'Review code for bugs and quality' },
162
+ { name: 'test-generation', description: 'Write tests for existing code' },
163
+ { name: 'file-operations', description: 'Read, write, search files' },
164
+ { name: 'bash-execution', description: 'Run shell commands' },
165
+ ],
166
+ endpoint: opts.port
167
+ ? { type: 'http', address: 'http://localhost', port: opts.port }
168
+ : { type: 'ipc', address: join(AGENT_REGISTRY_DIR, 'inboxes', `oh-${sessionId}`) },
169
+ registeredAt: Date.now(),
170
+ pid: process.pid,
171
+ provider: opts.provider,
172
+ model: opts.model,
173
+ workingDir: process.cwd(),
174
+ };
175
+ }
176
+ //# sourceMappingURL=a2a.js.map
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ import type { Tool } from "../../Tool.js";
3
+ declare const inputSchema: z.ZodObject<{
4
+ steps: z.ZodArray<z.ZodObject<{
5
+ id: z.ZodString;
6
+ tool: z.ZodString;
7
+ args: z.ZodRecord<z.ZodString, z.ZodUnknown>;
8
+ dependsOn: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ tool: string;
11
+ args: Record<string, unknown>;
12
+ id: string;
13
+ dependsOn?: string[] | undefined;
14
+ }, {
15
+ tool: string;
16
+ args: Record<string, unknown>;
17
+ id: string;
18
+ dependsOn?: string[] | undefined;
19
+ }>, "many">;
20
+ description: z.ZodOptional<z.ZodString>;
21
+ }, "strip", z.ZodTypeAny, {
22
+ steps: {
23
+ tool: string;
24
+ args: Record<string, unknown>;
25
+ id: string;
26
+ dependsOn?: string[] | undefined;
27
+ }[];
28
+ description?: string | undefined;
29
+ }, {
30
+ steps: {
31
+ tool: string;
32
+ args: Record<string, unknown>;
33
+ id: string;
34
+ dependsOn?: string[] | undefined;
35
+ }[];
36
+ description?: string | undefined;
37
+ }>;
38
+ export declare const PipelineTool: Tool<typeof inputSchema>;
39
+ export {};
40
+ //# sourceMappingURL=index.d.ts.map