@zhijiewang/openharness 2.0.0 → 2.3.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/README.md +4 -4
- package/dist/DeferredTool.js +3 -1
- package/dist/Tool.d.ts +1 -1
- package/dist/agents/roles.js +58 -62
- package/dist/commands/cybergotchi.d.ts +1 -1
- package/dist/commands/cybergotchi.js +30 -30
- package/dist/commands/index.js +360 -122
- package/dist/components/App.d.ts +1 -1
- package/dist/components/App.js +6 -6
- package/dist/components/CompanionFooter.d.ts +1 -1
- package/dist/components/CompanionFooter.js +6 -8
- package/dist/components/CybergotchiBubble.js +5 -5
- package/dist/components/CybergotchiPanel.d.ts +1 -1
- package/dist/components/CybergotchiPanel.js +7 -7
- package/dist/components/CybergotchiPanelConnected.js +2 -2
- package/dist/components/CybergotchiSetup.js +26 -24
- package/dist/components/CybergotchiSprite.d.ts +1 -1
- package/dist/components/CybergotchiSprite.js +8 -12
- package/dist/components/DiffView.d.ts +1 -1
- package/dist/components/DiffView.js +10 -10
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/InitWizard.js +65 -33
- package/dist/components/Markdown.js +2 -4
- package/dist/components/Messages.js +4 -4
- package/dist/components/PermissionPrompt.d.ts +1 -1
- package/dist/components/PermissionPrompt.js +15 -17
- package/dist/components/REPL.d.ts +1 -1
- package/dist/components/REPL.js +74 -49
- package/dist/components/Spinner.js +2 -2
- package/dist/components/TextInput.js +35 -29
- package/dist/components/ToolCallDisplay.js +3 -5
- package/dist/cybergotchi/bones.d.ts +1 -1
- package/dist/cybergotchi/bones.js +8 -8
- package/dist/cybergotchi/config.d.ts +2 -2
- package/dist/cybergotchi/config.js +13 -13
- package/dist/cybergotchi/events.d.ts +5 -5
- package/dist/cybergotchi/events.js +7 -7
- package/dist/cybergotchi/needs.d.ts +2 -2
- package/dist/cybergotchi/needs.js +7 -9
- package/dist/cybergotchi/personality.d.ts +2 -2
- package/dist/cybergotchi/personality.js +2 -2
- package/dist/cybergotchi/species.d.ts +1 -1
- package/dist/cybergotchi/species.js +145 -217
- package/dist/cybergotchi/speech.d.ts +2 -2
- package/dist/cybergotchi/speech.js +43 -43
- package/dist/cybergotchi/types.d.ts +4 -4
- package/dist/cybergotchi/types.js +26 -26
- package/dist/cybergotchi/useCybergotchi.d.ts +1 -1
- package/dist/cybergotchi/useCybergotchi.js +29 -25
- package/dist/git/index.js +11 -9
- package/dist/harness/checkpoints.js +29 -21
- package/dist/harness/config.d.ts +12 -2
- package/dist/harness/config.js +15 -9
- package/dist/harness/context-warning.d.ts +1 -1
- package/dist/harness/context-warning.js +1 -1
- package/dist/harness/cost.js +1 -1
- package/dist/harness/credentials.js +13 -13
- package/dist/harness/hooks.js +7 -5
- package/dist/harness/keybindings.js +20 -18
- package/dist/harness/marketplace.d.ts +3 -3
- package/dist/harness/marketplace.js +55 -42
- package/dist/harness/memory.d.ts +23 -5
- package/dist/harness/memory.js +142 -41
- package/dist/harness/onboarding.js +30 -10
- package/dist/harness/plugins.d.ts +9 -1
- package/dist/harness/plugins.js +54 -30
- package/dist/harness/rules.js +12 -7
- package/dist/harness/sandbox.d.ts +34 -0
- package/dist/harness/sandbox.js +104 -0
- package/dist/harness/session-db.d.ts +55 -0
- package/dist/harness/session-db.js +165 -0
- package/dist/harness/session.d.ts +1 -1
- package/dist/harness/session.js +34 -15
- package/dist/harness/store.d.ts +3 -3
- package/dist/harness/store.js +6 -4
- package/dist/harness/submit-handler.d.ts +4 -4
- package/dist/harness/submit-handler.js +57 -21
- package/dist/harness/telemetry.d.ts +1 -1
- package/dist/harness/telemetry.js +23 -19
- package/dist/harness/traces.d.ts +2 -2
- package/dist/harness/traces.js +44 -33
- package/dist/harness/verification.d.ts +1 -1
- package/dist/harness/verification.js +50 -44
- package/dist/lsp/client.js +44 -40
- package/dist/main.js +100 -59
- package/dist/mcp/DeferredMcpTool.d.ts +4 -4
- package/dist/mcp/DeferredMcpTool.js +9 -5
- package/dist/mcp/McpTool.d.ts +4 -4
- package/dist/mcp/McpTool.js +8 -4
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/client.js +21 -21
- package/dist/mcp/loader.d.ts +1 -1
- package/dist/mcp/loader.js +17 -12
- package/dist/mcp/registry.d.ts +3 -3
- package/dist/mcp/registry.js +97 -97
- package/dist/mcp/schema.d.ts +1 -1
- package/dist/mcp/schema.js +16 -16
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +21 -21
- package/dist/mcp/types.d.ts +3 -3
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +10 -9
- package/dist/providers/base.d.ts +1 -1
- package/dist/providers/index.js +10 -3
- package/dist/providers/llamacpp.d.ts +2 -2
- package/dist/providers/llamacpp.js +1 -3
- package/dist/providers/ollama.d.ts +2 -2
- package/dist/providers/ollama.js +3 -4
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.js +3 -5
- package/dist/providers/openrouter.d.ts +2 -2
- package/dist/providers/router.d.ts +1 -1
- package/dist/providers/router.js +7 -7
- package/dist/query/compress.d.ts +2 -2
- package/dist/query/compress.js +22 -21
- package/dist/query/context-manager.d.ts +2 -2
- package/dist/query/context-manager.js +8 -11
- package/dist/query/errors.js +1 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +30 -22
- package/dist/query/tools.js +15 -12
- package/dist/query/types.d.ts +1 -1
- package/dist/query.d.ts +1 -1
- package/dist/query.js +1 -1
- package/dist/remote/auth.d.ts +2 -2
- package/dist/remote/auth.js +8 -8
- package/dist/remote/server.d.ts +3 -3
- package/dist/remote/server.js +60 -60
- package/dist/renderer/cells.js +9 -9
- package/dist/renderer/colors.js +24 -6
- package/dist/renderer/diff.d.ts +2 -2
- package/dist/renderer/diff.js +27 -19
- package/dist/renderer/differ.d.ts +1 -1
- package/dist/renderer/differ.js +9 -9
- package/dist/renderer/image.js +19 -19
- package/dist/renderer/index.d.ts +6 -6
- package/dist/renderer/index.js +163 -93
- package/dist/renderer/input.js +66 -48
- package/dist/renderer/layout.d.ts +6 -6
- package/dist/renderer/layout.js +163 -124
- package/dist/renderer/markdown.d.ts +2 -2
- package/dist/renderer/markdown.js +173 -54
- package/dist/renderer/session-browser.d.ts +2 -2
- package/dist/renderer/session-browser.js +19 -21
- package/dist/repl.d.ts +5 -5
- package/dist/repl.js +300 -198
- package/dist/sdk/index.d.ts +8 -7
- package/dist/sdk/index.js +59 -42
- package/dist/services/AgentDispatcher.d.ts +3 -3
- package/dist/services/AgentDispatcher.js +33 -29
- package/dist/services/CronExecutor.d.ts +4 -4
- package/dist/services/CronExecutor.js +12 -8
- package/dist/services/EvaluatorLoop.d.ts +3 -3
- package/dist/services/EvaluatorLoop.js +29 -21
- package/dist/services/MetaHarness.d.ts +1 -1
- package/dist/services/MetaHarness.js +41 -33
- package/dist/services/PipelineExecutor.d.ts +1 -1
- package/dist/services/PipelineExecutor.js +23 -25
- package/dist/services/SkillExtractor.d.ts +43 -0
- package/dist/services/SkillExtractor.js +143 -0
- package/dist/services/StreamingToolExecutor.d.ts +2 -2
- package/dist/services/StreamingToolExecutor.js +11 -7
- package/dist/services/a2a.d.ts +8 -8
- package/dist/services/a2a.js +44 -34
- package/dist/services/agent-messaging.d.ts +33 -15
- package/dist/services/agent-messaging.js +65 -13
- package/dist/services/cron.js +16 -16
- package/dist/tools/AgentTool/index.d.ts +5 -2
- package/dist/tools/AgentTool/index.js +35 -15
- package/dist/tools/AskUserTool/index.js +1 -1
- package/dist/tools/BashTool/index.d.ts +2 -2
- package/dist/tools/BashTool/index.js +18 -10
- package/dist/tools/CronTool/index.d.ts +2 -2
- package/dist/tools/CronTool/index.js +30 -12
- package/dist/tools/DiagnosticsTool/index.js +28 -22
- package/dist/tools/EnterPlanModeTool/index.js +93 -14
- package/dist/tools/EnterWorktreeTool/index.js +7 -3
- package/dist/tools/ExitPlanModeTool/index.d.ts +22 -1
- package/dist/tools/ExitPlanModeTool/index.js +20 -5
- package/dist/tools/ExitWorktreeTool/index.js +11 -4
- package/dist/tools/FileEditTool/index.js +3 -5
- package/dist/tools/FileReadTool/index.js +16 -10
- package/dist/tools/FileWriteTool/index.js +2 -2
- package/dist/tools/GlobTool/index.js +5 -9
- package/dist/tools/GrepTool/index.d.ts +2 -2
- package/dist/tools/GrepTool/index.js +14 -9
- package/dist/tools/ImageReadTool/index.js +2 -2
- package/dist/tools/KillProcessTool/index.js +11 -7
- package/dist/tools/LSTool/index.js +3 -3
- package/dist/tools/MemoryTool/index.d.ts +11 -11
- package/dist/tools/MemoryTool/index.js +28 -14
- package/dist/tools/MonitorTool/index.d.ts +2 -2
- package/dist/tools/MonitorTool/index.js +24 -19
- package/dist/tools/MultiEditTool/index.js +9 -5
- package/dist/tools/NotebookEditTool/index.js +3 -3
- package/dist/tools/ParallelAgentTool/index.d.ts +4 -4
- package/dist/tools/ParallelAgentTool/index.js +12 -6
- package/dist/tools/PipelineTool/index.d.ts +4 -4
- package/dist/tools/PipelineTool/index.js +3 -3
- package/dist/tools/PowerShellTool/index.js +10 -6
- package/dist/tools/RemoteTriggerTool/index.js +8 -4
- package/dist/tools/ScheduleWakeupTool/index.d.ts +42 -0
- package/dist/tools/ScheduleWakeupTool/index.js +115 -0
- package/dist/tools/SendMessageTool/index.js +25 -7
- package/dist/tools/SessionSearchTool/index.d.ts +15 -0
- package/dist/tools/SessionSearchTool/index.js +36 -0
- package/dist/tools/SkillTool/index.d.ts +3 -0
- package/dist/tools/SkillTool/index.js +39 -9
- package/dist/tools/TaskCreateTool/index.d.ts +2 -2
- package/dist/tools/TaskCreateTool/index.js +2 -2
- package/dist/tools/TaskGetTool/index.js +2 -2
- package/dist/tools/TaskListTool/index.js +3 -5
- package/dist/tools/TaskOutputTool/index.js +2 -2
- package/dist/tools/TaskStopTool/index.js +3 -3
- package/dist/tools/TaskUpdateTool/index.d.ts +4 -4
- package/dist/tools/TaskUpdateTool/index.js +2 -2
- package/dist/tools/ToolSearchTool/index.js +9 -6
- package/dist/tools/WebFetchTool/index.js +1 -1
- package/dist/tools/WebSearchTool/index.js +2 -6
- package/dist/tools.js +31 -30
- package/dist/types/permissions.js +15 -9
- package/dist/utils/bash-safety.d.ts +1 -1
- package/dist/utils/bash-safety.js +64 -54
- package/dist/utils/diff-algorithm.d.ts +3 -3
- package/dist/utils/diff-algorithm.js +7 -7
- package/dist/utils/fs.js +3 -3
- package/dist/utils/safe-env.js +1 -1
- package/dist/utils/theme-data.d.ts +1 -1
- package/dist/utils/theme-data.js +1 -1
- package/dist/utils/theme.d.ts +1 -1
- package/dist/utils/theme.js +1 -1
- package/dist/utils/tool-summary.d.ts +1 -1
- package/dist/utils/tool-summary.js +27 -9
- package/package.json +10 -3
package/dist/services/a2a.js
CHANGED
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Based on the emerging A2A (Agent-to-Agent) protocol standard.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
const AGENT_REGISTRY_DIR = join(homedir(),
|
|
15
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
const AGENT_REGISTRY_DIR = join(homedir(), ".oh", "agents");
|
|
19
19
|
// ── Registry Operations ──
|
|
20
20
|
/** Publish an agent card to the shared registry */
|
|
21
21
|
export function publishCard(card) {
|
|
@@ -29,16 +29,18 @@ export function unpublishCard(agentId) {
|
|
|
29
29
|
try {
|
|
30
30
|
unlinkSync(filePath);
|
|
31
31
|
}
|
|
32
|
-
catch {
|
|
32
|
+
catch {
|
|
33
|
+
/* ignore */
|
|
34
|
+
}
|
|
33
35
|
}
|
|
34
36
|
/** Discover all registered agents */
|
|
35
37
|
export function discoverAgents() {
|
|
36
38
|
if (!existsSync(AGENT_REGISTRY_DIR))
|
|
37
39
|
return [];
|
|
38
40
|
const cards = [];
|
|
39
|
-
for (const file of readdirSync(AGENT_REGISTRY_DIR).filter(f => f.endsWith(
|
|
41
|
+
for (const file of readdirSync(AGENT_REGISTRY_DIR).filter((f) => f.endsWith(".json"))) {
|
|
40
42
|
try {
|
|
41
|
-
const raw = readFileSync(join(AGENT_REGISTRY_DIR, file),
|
|
43
|
+
const raw = readFileSync(join(AGENT_REGISTRY_DIR, file), "utf-8");
|
|
42
44
|
const card = JSON.parse(raw);
|
|
43
45
|
// Check if the agent process is still alive
|
|
44
46
|
if (isProcessAlive(card.pid)) {
|
|
@@ -49,20 +51,24 @@ export function discoverAgents() {
|
|
|
49
51
|
try {
|
|
50
52
|
unlinkSync(join(AGENT_REGISTRY_DIR, file));
|
|
51
53
|
}
|
|
52
|
-
catch {
|
|
54
|
+
catch {
|
|
55
|
+
/* ignore */
|
|
56
|
+
}
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
|
-
catch {
|
|
59
|
+
catch {
|
|
60
|
+
/* skip malformed cards */
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
return cards;
|
|
58
64
|
}
|
|
59
65
|
/** Find agents by capability name */
|
|
60
66
|
export function findAgentsByCapability(capabilityName) {
|
|
61
|
-
return discoverAgents().filter(card => card.capabilities.some(c => c.name.toLowerCase() === capabilityName.toLowerCase()));
|
|
67
|
+
return discoverAgents().filter((card) => card.capabilities.some((c) => c.name.toLowerCase() === capabilityName.toLowerCase()));
|
|
62
68
|
}
|
|
63
69
|
/** Find an agent by name */
|
|
64
70
|
export function findAgentByName(name) {
|
|
65
|
-
return discoverAgents().find(c => c.name.toLowerCase() === name.toLowerCase()) ?? null;
|
|
71
|
+
return discoverAgents().find((c) => c.name.toLowerCase() === name.toLowerCase()) ?? null;
|
|
66
72
|
}
|
|
67
73
|
// ── Message Routing ──
|
|
68
74
|
/**
|
|
@@ -75,13 +81,13 @@ export async function routeMessage(message) {
|
|
|
75
81
|
let targetCard = null;
|
|
76
82
|
// Try by agent ID first
|
|
77
83
|
const agents = discoverAgents();
|
|
78
|
-
targetCard = agents.find(a => a.id === message.to) ?? null;
|
|
84
|
+
targetCard = agents.find((a) => a.id === message.to) ?? null;
|
|
79
85
|
// Try by name
|
|
80
86
|
if (!targetCard) {
|
|
81
|
-
targetCard = agents.find(a => a.name.toLowerCase() === message.to.toLowerCase()) ?? null;
|
|
87
|
+
targetCard = agents.find((a) => a.name.toLowerCase() === message.to.toLowerCase()) ?? null;
|
|
82
88
|
}
|
|
83
89
|
// Try by capability
|
|
84
|
-
if (!targetCard && message.type ===
|
|
90
|
+
if (!targetCard && message.type === "task" && message.payload.kind === "task") {
|
|
85
91
|
const capable = findAgentsByCapability(message.payload.capability);
|
|
86
92
|
if (capable.length > 0)
|
|
87
93
|
targetCard = capable[0];
|
|
@@ -90,25 +96,27 @@ export async function routeMessage(message) {
|
|
|
90
96
|
return null;
|
|
91
97
|
// Route based on endpoint type
|
|
92
98
|
switch (targetCard.endpoint.type) {
|
|
93
|
-
case
|
|
99
|
+
case "http": {
|
|
94
100
|
try {
|
|
95
|
-
const url = `${targetCard.endpoint.address}${targetCard.endpoint.port ?
|
|
101
|
+
const url = `${targetCard.endpoint.address}${targetCard.endpoint.port ? `:${targetCard.endpoint.port}` : ""}/a2a`;
|
|
96
102
|
const res = await fetch(url, {
|
|
97
|
-
method:
|
|
98
|
-
headers: {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "Content-Type": "application/json" },
|
|
99
105
|
body: JSON.stringify(message),
|
|
100
106
|
signal: AbortSignal.timeout(30_000),
|
|
101
107
|
});
|
|
102
108
|
if (res.ok) {
|
|
103
|
-
return await res.json();
|
|
109
|
+
return (await res.json());
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
|
-
catch {
|
|
112
|
+
catch {
|
|
113
|
+
/* delivery failed */
|
|
114
|
+
}
|
|
107
115
|
return null;
|
|
108
116
|
}
|
|
109
|
-
case
|
|
117
|
+
case "ipc": {
|
|
110
118
|
// File-based inbox for local IPC
|
|
111
|
-
const inboxDir = join(AGENT_REGISTRY_DIR,
|
|
119
|
+
const inboxDir = join(AGENT_REGISTRY_DIR, "inboxes", targetCard.id);
|
|
112
120
|
mkdirSync(inboxDir, { recursive: true });
|
|
113
121
|
const msgFile = join(inboxDir, `${message.id}.json`);
|
|
114
122
|
writeFileSync(msgFile, JSON.stringify(message, null, 2));
|
|
@@ -120,18 +128,20 @@ export async function routeMessage(message) {
|
|
|
120
128
|
}
|
|
121
129
|
/** Read pending messages from an agent's inbox */
|
|
122
130
|
export function readInbox(agentId) {
|
|
123
|
-
const inboxDir = join(AGENT_REGISTRY_DIR,
|
|
131
|
+
const inboxDir = join(AGENT_REGISTRY_DIR, "inboxes", agentId);
|
|
124
132
|
if (!existsSync(inboxDir))
|
|
125
133
|
return [];
|
|
126
134
|
const messages = [];
|
|
127
|
-
for (const file of readdirSync(inboxDir).filter(f => f.endsWith(
|
|
135
|
+
for (const file of readdirSync(inboxDir).filter((f) => f.endsWith(".json"))) {
|
|
128
136
|
try {
|
|
129
|
-
const raw = readFileSync(join(inboxDir, file),
|
|
137
|
+
const raw = readFileSync(join(inboxDir, file), "utf-8");
|
|
130
138
|
messages.push(JSON.parse(raw));
|
|
131
139
|
// Remove after reading
|
|
132
140
|
unlinkSync(join(inboxDir, file));
|
|
133
141
|
}
|
|
134
|
-
catch {
|
|
142
|
+
catch {
|
|
143
|
+
/* skip */
|
|
144
|
+
}
|
|
135
145
|
}
|
|
136
146
|
return messages.sort((a, b) => a.timestamp - b.timestamp);
|
|
137
147
|
}
|
|
@@ -155,17 +165,17 @@ export function createSessionCard(sessionId, opts = {}) {
|
|
|
155
165
|
return {
|
|
156
166
|
id: `oh-${sessionId}`,
|
|
157
167
|
name: `openharness-${sessionId.slice(0, 6)}`,
|
|
158
|
-
version:
|
|
168
|
+
version: "1.0.0",
|
|
159
169
|
capabilities: [
|
|
160
|
-
{ name:
|
|
161
|
-
{ name:
|
|
162
|
-
{ name:
|
|
163
|
-
{ name:
|
|
164
|
-
{ name:
|
|
170
|
+
{ name: "code-generation", description: "Generate, edit, and review code" },
|
|
171
|
+
{ name: "code-review", description: "Review code for bugs and quality" },
|
|
172
|
+
{ name: "test-generation", description: "Write tests for existing code" },
|
|
173
|
+
{ name: "file-operations", description: "Read, write, search files" },
|
|
174
|
+
{ name: "bash-execution", description: "Run shell commands" },
|
|
165
175
|
],
|
|
166
176
|
endpoint: opts.port
|
|
167
|
-
? { type:
|
|
168
|
-
: { type:
|
|
177
|
+
? { type: "http", address: "http://localhost", port: opts.port }
|
|
178
|
+
: { type: "ipc", address: join(AGENT_REGISTRY_DIR, "inboxes", `oh-${sessionId}`) },
|
|
169
179
|
registeredAt: Date.now(),
|
|
170
180
|
pid: process.pid,
|
|
171
181
|
provider: opts.provider,
|
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent messaging — peer-to-peer communication between sub-agents.
|
|
3
|
-
*
|
|
4
|
-
* Provides a message bus for agents to send/receive messages, share state,
|
|
5
|
-
* and coordinate work. Uses file-based locking for concurrent file access.
|
|
6
|
-
*
|
|
7
|
-
* Architecture (inspired by Claude Code Agent Teams):
|
|
8
|
-
* - Each agent has a unique ID
|
|
9
|
-
* - Messages are stored in a shared inbox (in-memory for same-process agents)
|
|
10
|
-
* - File locking prevents concurrent edits to the same file
|
|
11
|
-
*/
|
|
12
1
|
export type AgentMessage = {
|
|
13
2
|
from: string;
|
|
14
3
|
to: string;
|
|
15
|
-
type:
|
|
4
|
+
type: "request" | "response" | "status" | "error";
|
|
16
5
|
content: string;
|
|
17
6
|
timestamp: number;
|
|
18
7
|
metadata?: Record<string, unknown>;
|
|
@@ -20,9 +9,23 @@ export type AgentMessage = {
|
|
|
20
9
|
export type AgentInfo = {
|
|
21
10
|
id: string;
|
|
22
11
|
role: string;
|
|
23
|
-
status:
|
|
12
|
+
status: "idle" | "working" | "done" | "error";
|
|
24
13
|
currentTask?: string;
|
|
25
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Background agent entry — tracks running/completed background agents
|
|
17
|
+
* so they can be continued via SendMessage.
|
|
18
|
+
*/
|
|
19
|
+
export type BackgroundAgent = {
|
|
20
|
+
id: string;
|
|
21
|
+
role: string;
|
|
22
|
+
status: "running" | "completed" | "error";
|
|
23
|
+
startedAt: number;
|
|
24
|
+
completedAt?: number;
|
|
25
|
+
result?: string;
|
|
26
|
+
/** Queue of messages sent to this agent while it was running */
|
|
27
|
+
pendingMessages: string[];
|
|
28
|
+
};
|
|
26
29
|
/**
|
|
27
30
|
* Message bus for agent-to-agent communication.
|
|
28
31
|
*/
|
|
@@ -30,22 +33,37 @@ export declare class AgentMessageBus {
|
|
|
30
33
|
private inboxes;
|
|
31
34
|
private agents;
|
|
32
35
|
private fileLocks;
|
|
36
|
+
private backgroundAgents;
|
|
33
37
|
/** Register an agent with the bus */
|
|
34
38
|
registerAgent(id: string, role: string): void;
|
|
35
39
|
/** Unregister an agent */
|
|
36
40
|
unregisterAgent(id: string): void;
|
|
37
41
|
/** Send a message to a specific agent or broadcast */
|
|
38
|
-
send(message: Omit<AgentMessage,
|
|
42
|
+
send(message: Omit<AgentMessage, "timestamp">): void;
|
|
39
43
|
/** Receive pending messages for an agent (drains inbox) */
|
|
40
44
|
receive(agentId: string): AgentMessage[];
|
|
41
45
|
/** Peek at inbox without draining */
|
|
42
46
|
peek(agentId: string): AgentMessage[];
|
|
43
47
|
/** Update agent status */
|
|
44
|
-
updateStatus(agentId: string, status: AgentInfo[
|
|
48
|
+
updateStatus(agentId: string, status: AgentInfo["status"], currentTask?: string): void;
|
|
45
49
|
/** Get all registered agents */
|
|
46
50
|
getAgents(): AgentInfo[];
|
|
47
51
|
/** Get a specific agent's info */
|
|
48
52
|
getAgent(id: string): AgentInfo | undefined;
|
|
53
|
+
/** Register a background agent for later continuation */
|
|
54
|
+
registerBackgroundAgent(id: string, role: string): void;
|
|
55
|
+
/** Mark a background agent as completed with its result */
|
|
56
|
+
completeBackgroundAgent(id: string, result: string): void;
|
|
57
|
+
/** Mark a background agent as errored */
|
|
58
|
+
errorBackgroundAgent(id: string, error: string): void;
|
|
59
|
+
/** Queue a message for a background agent */
|
|
60
|
+
sendToBackgroundAgent(id: string, content: string): boolean;
|
|
61
|
+
/** Get a background agent's info */
|
|
62
|
+
getBackgroundAgent(id: string): BackgroundAgent | undefined;
|
|
63
|
+
/** List all background agents */
|
|
64
|
+
getBackgroundAgents(): BackgroundAgent[];
|
|
65
|
+
/** Drain pending messages for a background agent */
|
|
66
|
+
drainBackgroundMessages(id: string): string[];
|
|
49
67
|
/** Acquire a lock on a file path. Returns true if acquired, false if already locked. */
|
|
50
68
|
acquireLock(agentId: string, filePath: string): boolean;
|
|
51
69
|
/** Release a lock on a file path */
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent messaging — peer-to-peer communication between sub-agents.
|
|
3
|
-
*
|
|
4
|
-
* Provides a message bus for agents to send/receive messages, share state,
|
|
5
|
-
* and coordinate work. Uses file-based locking for concurrent file access.
|
|
6
|
-
*
|
|
7
|
-
* Architecture (inspired by Claude Code Agent Teams):
|
|
8
|
-
* - Each agent has a unique ID
|
|
9
|
-
* - Messages are stored in a shared inbox (in-memory for same-process agents)
|
|
10
|
-
* - File locking prevents concurrent edits to the same file
|
|
11
|
-
*/
|
|
12
1
|
/**
|
|
13
2
|
* Message bus for agent-to-agent communication.
|
|
14
3
|
*/
|
|
@@ -16,9 +5,10 @@ export class AgentMessageBus {
|
|
|
16
5
|
inboxes = new Map();
|
|
17
6
|
agents = new Map();
|
|
18
7
|
fileLocks = new Map(); // filePath → agentId
|
|
8
|
+
backgroundAgents = new Map();
|
|
19
9
|
/** Register an agent with the bus */
|
|
20
10
|
registerAgent(id, role) {
|
|
21
|
-
this.agents.set(id, { id, role, status:
|
|
11
|
+
this.agents.set(id, { id, role, status: "idle" });
|
|
22
12
|
this.inboxes.set(id, []);
|
|
23
13
|
}
|
|
24
14
|
/** Unregister an agent */
|
|
@@ -34,7 +24,7 @@ export class AgentMessageBus {
|
|
|
34
24
|
/** Send a message to a specific agent or broadcast */
|
|
35
25
|
send(message) {
|
|
36
26
|
const msg = { ...message, timestamp: Date.now() };
|
|
37
|
-
if (msg.to ===
|
|
27
|
+
if (msg.to === "*") {
|
|
38
28
|
// Broadcast to all agents except sender
|
|
39
29
|
for (const [id, inbox] of this.inboxes) {
|
|
40
30
|
if (id !== msg.from)
|
|
@@ -76,6 +66,68 @@ export class AgentMessageBus {
|
|
|
76
66
|
getAgent(id) {
|
|
77
67
|
return this.agents.get(id);
|
|
78
68
|
}
|
|
69
|
+
// ── Background Agent Registry ──
|
|
70
|
+
/** Register a background agent for later continuation */
|
|
71
|
+
registerBackgroundAgent(id, role) {
|
|
72
|
+
this.backgroundAgents.set(id, {
|
|
73
|
+
id,
|
|
74
|
+
role,
|
|
75
|
+
status: "running",
|
|
76
|
+
startedAt: Date.now(),
|
|
77
|
+
pendingMessages: [],
|
|
78
|
+
});
|
|
79
|
+
// Evict completed/errored agents older than 30 minutes to prevent unbounded growth
|
|
80
|
+
const EVICT_AGE_MS = 30 * 60 * 1000;
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
for (const [agentId, agent] of this.backgroundAgents) {
|
|
83
|
+
if (agent.status !== "running" && agent.completedAt && now - agent.completedAt > EVICT_AGE_MS) {
|
|
84
|
+
this.backgroundAgents.delete(agentId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Mark a background agent as completed with its result */
|
|
89
|
+
completeBackgroundAgent(id, result) {
|
|
90
|
+
const agent = this.backgroundAgents.get(id);
|
|
91
|
+
if (agent) {
|
|
92
|
+
agent.status = "completed";
|
|
93
|
+
agent.completedAt = Date.now();
|
|
94
|
+
agent.result = result;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Mark a background agent as errored */
|
|
98
|
+
errorBackgroundAgent(id, error) {
|
|
99
|
+
const agent = this.backgroundAgents.get(id);
|
|
100
|
+
if (agent) {
|
|
101
|
+
agent.status = "error";
|
|
102
|
+
agent.completedAt = Date.now();
|
|
103
|
+
agent.result = error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Queue a message for a background agent */
|
|
107
|
+
sendToBackgroundAgent(id, content) {
|
|
108
|
+
const agent = this.backgroundAgents.get(id);
|
|
109
|
+
if (!agent)
|
|
110
|
+
return false;
|
|
111
|
+
agent.pendingMessages.push(content);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
/** Get a background agent's info */
|
|
115
|
+
getBackgroundAgent(id) {
|
|
116
|
+
return this.backgroundAgents.get(id);
|
|
117
|
+
}
|
|
118
|
+
/** List all background agents */
|
|
119
|
+
getBackgroundAgents() {
|
|
120
|
+
return [...this.backgroundAgents.values()];
|
|
121
|
+
}
|
|
122
|
+
/** Drain pending messages for a background agent */
|
|
123
|
+
drainBackgroundMessages(id) {
|
|
124
|
+
const agent = this.backgroundAgents.get(id);
|
|
125
|
+
if (!agent || agent.pendingMessages.length === 0)
|
|
126
|
+
return [];
|
|
127
|
+
const msgs = [...agent.pendingMessages];
|
|
128
|
+
agent.pendingMessages.length = 0;
|
|
129
|
+
return msgs;
|
|
130
|
+
}
|
|
79
131
|
// ── File Locking ──
|
|
80
132
|
/** Acquire a lock on a file path. Returns true if acquired, false if already locked. */
|
|
81
133
|
acquireLock(agentId, filePath) {
|
package/dist/services/cron.js
CHANGED
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
* This is a simple implementation using setInterval for minute-level granularity.
|
|
8
8
|
* For production use, consider node-cron or similar.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
const CRON_DIR = join(homedir(),
|
|
10
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
const CRON_DIR = join(homedir(), ".oh", "crons");
|
|
14
14
|
/** List all cron definitions */
|
|
15
15
|
export function listCrons() {
|
|
16
16
|
if (!existsSync(CRON_DIR))
|
|
17
17
|
return [];
|
|
18
18
|
return readdirSync(CRON_DIR)
|
|
19
|
-
.filter(f => f.endsWith(
|
|
20
|
-
.map(f => {
|
|
19
|
+
.filter((f) => f.endsWith(".json"))
|
|
20
|
+
.map((f) => {
|
|
21
21
|
try {
|
|
22
|
-
return JSON.parse(readFileSync(join(CRON_DIR, f),
|
|
22
|
+
return JSON.parse(readFileSync(join(CRON_DIR, f), "utf-8"));
|
|
23
23
|
}
|
|
24
24
|
catch {
|
|
25
25
|
return null;
|
|
@@ -61,19 +61,19 @@ export function parseScheduleMs(schedule) {
|
|
|
61
61
|
// "every 5m" → 5 minutes
|
|
62
62
|
const minMatch = schedule.match(/^every\s+(\d+)\s*m(?:in(?:ute)?s?)?$/i);
|
|
63
63
|
if (minMatch)
|
|
64
|
-
return parseInt(minMatch[1]) * 60 * 1000;
|
|
64
|
+
return parseInt(minMatch[1], 10) * 60 * 1000;
|
|
65
65
|
// "every 2h" → 2 hours
|
|
66
66
|
const hourMatch = schedule.match(/^every\s+(\d+)\s*h(?:ours?)?$/i);
|
|
67
67
|
if (hourMatch)
|
|
68
|
-
return parseInt(hourMatch[1]) * 60 * 60 * 1000;
|
|
68
|
+
return parseInt(hourMatch[1], 10) * 60 * 60 * 1000;
|
|
69
69
|
// "every 1d" → 1 day
|
|
70
70
|
const dayMatch = schedule.match(/^every\s+(\d+)\s*d(?:ays?)?$/i);
|
|
71
71
|
if (dayMatch)
|
|
72
|
-
return parseInt(dayMatch[1]) * 24 * 60 * 60 * 1000;
|
|
72
|
+
return parseInt(dayMatch[1], 10) * 24 * 60 * 60 * 1000;
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
75
|
// ── Result Persistence ──
|
|
76
|
-
const HISTORY_DIR = join(CRON_DIR,
|
|
76
|
+
const HISTORY_DIR = join(CRON_DIR, "history");
|
|
77
77
|
/** Save a cron execution result to history */
|
|
78
78
|
export function saveCronResult(result) {
|
|
79
79
|
mkdirSync(HISTORY_DIR, { recursive: true });
|
|
@@ -85,13 +85,13 @@ export function getCronHistory(cronId, limit = 10) {
|
|
|
85
85
|
if (!existsSync(HISTORY_DIR))
|
|
86
86
|
return [];
|
|
87
87
|
return readdirSync(HISTORY_DIR)
|
|
88
|
-
.filter(f => f.startsWith(`${cronId}-`) && f.endsWith(
|
|
88
|
+
.filter((f) => f.startsWith(`${cronId}-`) && f.endsWith(".json"))
|
|
89
89
|
.sort()
|
|
90
90
|
.reverse()
|
|
91
91
|
.slice(0, limit)
|
|
92
|
-
.map(f => {
|
|
92
|
+
.map((f) => {
|
|
93
93
|
try {
|
|
94
|
-
return JSON.parse(readFileSync(join(HISTORY_DIR, f),
|
|
94
|
+
return JSON.parse(readFileSync(join(HISTORY_DIR, f), "utf-8"));
|
|
95
95
|
}
|
|
96
96
|
catch {
|
|
97
97
|
return null;
|
|
@@ -104,14 +104,14 @@ export function getCronHistory(cronId, limit = 10) {
|
|
|
104
104
|
*/
|
|
105
105
|
export function getDueCrons(crons) {
|
|
106
106
|
const now = Date.now();
|
|
107
|
-
return crons.filter(c => {
|
|
107
|
+
return crons.filter((c) => {
|
|
108
108
|
if (!c.enabled)
|
|
109
109
|
return false;
|
|
110
110
|
const intervalMs = parseScheduleMs(c.schedule);
|
|
111
111
|
if (!intervalMs)
|
|
112
112
|
return false;
|
|
113
113
|
const lastRun = c.lastRun ?? c.createdAt;
|
|
114
|
-
return
|
|
114
|
+
return now - lastRun >= intervalMs;
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
//# sourceMappingURL=cron.js.map
|
|
@@ -4,6 +4,7 @@ declare const inputSchema: z.ZodObject<{
|
|
|
4
4
|
prompt: z.ZodString;
|
|
5
5
|
description: z.ZodOptional<z.ZodString>;
|
|
6
6
|
isolated: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
isolation: z.ZodOptional<z.ZodEnum<["worktree"]>>;
|
|
7
8
|
run_in_background: z.ZodOptional<z.ZodBoolean>;
|
|
8
9
|
model: z.ZodOptional<z.ZodString>;
|
|
9
10
|
subagent_type: z.ZodOptional<z.ZodString>;
|
|
@@ -12,16 +13,18 @@ declare const inputSchema: z.ZodObject<{
|
|
|
12
13
|
prompt: string;
|
|
13
14
|
model?: string | undefined;
|
|
14
15
|
description?: string | undefined;
|
|
15
|
-
run_in_background?: boolean | undefined;
|
|
16
16
|
isolated?: boolean | undefined;
|
|
17
|
+
isolation?: "worktree" | undefined;
|
|
18
|
+
run_in_background?: boolean | undefined;
|
|
17
19
|
subagent_type?: string | undefined;
|
|
18
20
|
allowed_tools?: string[] | undefined;
|
|
19
21
|
}, {
|
|
20
22
|
prompt: string;
|
|
21
23
|
model?: string | undefined;
|
|
22
24
|
description?: string | undefined;
|
|
23
|
-
run_in_background?: boolean | undefined;
|
|
24
25
|
isolated?: boolean | undefined;
|
|
26
|
+
isolation?: "worktree" | undefined;
|
|
27
|
+
run_in_background?: boolean | undefined;
|
|
25
28
|
subagent_type?: string | undefined;
|
|
26
29
|
allowed_tools?: string[] | undefined;
|
|
27
30
|
}>;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { createWorktree,
|
|
2
|
+
import { createWorktree, hasWorktreeChanges, isGitRepo, removeWorktree } from "../../git/index.js";
|
|
3
3
|
import { emitHook } from "../../harness/hooks.js";
|
|
4
|
+
import { getMessageBus } from "../../services/agent-messaging.js";
|
|
4
5
|
const inputSchema = z.object({
|
|
5
6
|
prompt: z.string(),
|
|
6
7
|
description: z.string().optional(),
|
|
7
|
-
isolated: z.boolean().optional(),
|
|
8
|
+
isolated: z.boolean().optional().describe("Whether to run in an isolated git worktree (default: true in git repos)"),
|
|
9
|
+
isolation: z
|
|
10
|
+
.enum(["worktree"])
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Isolation mode — 'worktree' creates a temporary git worktree (Claude Code compatible)"),
|
|
8
13
|
run_in_background: z.boolean().optional(),
|
|
9
14
|
model: z.string().optional(),
|
|
10
15
|
subagent_type: z.string().optional(),
|
|
@@ -30,7 +35,9 @@ export const AgentTool = {
|
|
|
30
35
|
}
|
|
31
36
|
const { query } = await import("../../query.js");
|
|
32
37
|
// Worktree isolation: create isolated copy of repo if requested or if in git repo
|
|
33
|
-
|
|
38
|
+
// Supports both `isolation: "worktree"` (Claude Code) and `isolated: boolean` (legacy)
|
|
39
|
+
const explicitWorktree = input.isolation === "worktree";
|
|
40
|
+
const useWorktree = (explicitWorktree || input.isolated !== false) && isGitRepo(context.workingDir);
|
|
34
41
|
let worktreePath = null;
|
|
35
42
|
let agentWorkingDir = context.workingDir;
|
|
36
43
|
if (useWorktree) {
|
|
@@ -49,14 +56,14 @@ export const AgentTool = {
|
|
|
49
56
|
};
|
|
50
57
|
const hint = builtinHints[input.subagent_type.toLowerCase()];
|
|
51
58
|
if (hint) {
|
|
52
|
-
systemPrompt = hint
|
|
59
|
+
systemPrompt = `${hint}\n\n${systemPrompt}`;
|
|
53
60
|
}
|
|
54
61
|
else {
|
|
55
62
|
// Check agent roles (code-reviewer, test-writer, debugger, evaluator, etc.)
|
|
56
63
|
const { getRole } = await import("../../agents/roles.js");
|
|
57
64
|
role = getRole(input.subagent_type.toLowerCase());
|
|
58
65
|
if (role) {
|
|
59
|
-
systemPrompt = role.systemPromptSupplement
|
|
66
|
+
systemPrompt = `${role.systemPromptSupplement}\n\n${systemPrompt}`;
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
69
|
}
|
|
@@ -64,9 +71,9 @@ export const AgentTool = {
|
|
|
64
71
|
let agentTools = context.tools;
|
|
65
72
|
const allowList = input.allowed_tools ?? (role?.suggestedTools?.length ? role.suggestedTools : null);
|
|
66
73
|
if (allowList) {
|
|
67
|
-
const allowSet = new Set(allowList.map(n => n.toLowerCase()));
|
|
68
|
-
allowSet.add(
|
|
69
|
-
const filtered = context.tools.filter(t => allowSet.has(t.name.toLowerCase()));
|
|
74
|
+
const allowSet = new Set(allowList.map((n) => n.toLowerCase()));
|
|
75
|
+
allowSet.add("askuser"); // Always allow user communication
|
|
76
|
+
const filtered = context.tools.filter((t) => allowSet.has(t.name.toLowerCase()));
|
|
70
77
|
if (filtered.length > 0)
|
|
71
78
|
agentTools = filtered; // Fallback to all tools if filter produces empty set
|
|
72
79
|
}
|
|
@@ -82,10 +89,12 @@ export const AgentTool = {
|
|
|
82
89
|
abortSignal: context.abortSignal,
|
|
83
90
|
};
|
|
84
91
|
const agentId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
85
|
-
emitHook("subagentStart", { agentId, toolName: input.subagent_type ??
|
|
92
|
+
emitHook("subagentStart", { agentId, toolName: input.subagent_type ?? "general" });
|
|
86
93
|
// Background execution: start agent and return immediately
|
|
87
94
|
if (input.run_in_background) {
|
|
88
95
|
const bgId = agentId;
|
|
96
|
+
const bus = getMessageBus();
|
|
97
|
+
bus.registerBackgroundAgent(bgId, input.subagent_type ?? "general");
|
|
89
98
|
const runAgent = async () => {
|
|
90
99
|
let finalText = "";
|
|
91
100
|
const originalCwd = process.cwd();
|
|
@@ -94,7 +103,9 @@ export const AgentTool = {
|
|
|
94
103
|
try {
|
|
95
104
|
process.chdir(agentWorkingDir);
|
|
96
105
|
}
|
|
97
|
-
catch {
|
|
106
|
+
catch {
|
|
107
|
+
/* ignore */
|
|
108
|
+
}
|
|
98
109
|
}
|
|
99
110
|
for await (const event of query(input.prompt, config)) {
|
|
100
111
|
if (event.type === "text_delta")
|
|
@@ -106,7 +117,9 @@ export const AgentTool = {
|
|
|
106
117
|
try {
|
|
107
118
|
process.chdir(originalCwd);
|
|
108
119
|
}
|
|
109
|
-
catch {
|
|
120
|
+
catch {
|
|
121
|
+
/* ignore */
|
|
122
|
+
}
|
|
110
123
|
}
|
|
111
124
|
// Clean up worktree only if no changes were made
|
|
112
125
|
if (worktreePath) {
|
|
@@ -119,17 +132,20 @@ export const AgentTool = {
|
|
|
119
132
|
}
|
|
120
133
|
}
|
|
121
134
|
}
|
|
135
|
+
bus.completeBackgroundAgent(bgId, finalText);
|
|
122
136
|
if (context.onOutputChunk && context.callId) {
|
|
123
137
|
context.onOutputChunk(context.callId, `\n[background:${bgId} completed]\n${finalText}`);
|
|
124
138
|
}
|
|
125
139
|
};
|
|
126
140
|
runAgent().catch((err) => {
|
|
141
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
142
|
+
bus.errorBackgroundAgent(bgId, errMsg);
|
|
127
143
|
if (context.onOutputChunk && context.callId) {
|
|
128
|
-
context.onOutputChunk(context.callId, `\n[background:${bgId} failed: ${
|
|
144
|
+
context.onOutputChunk(context.callId, `\n[background:${bgId} failed: ${errMsg}]`);
|
|
129
145
|
}
|
|
130
146
|
});
|
|
131
147
|
return {
|
|
132
|
-
output: `Background agent started (id: ${bgId}). You will be notified when it completes.`,
|
|
148
|
+
output: `Background agent started (id: ${bgId}). You will be notified when it completes. Use SendMessage with to:'${bgId}' to send it messages.`,
|
|
133
149
|
isError: false,
|
|
134
150
|
};
|
|
135
151
|
}
|
|
@@ -142,7 +158,9 @@ export const AgentTool = {
|
|
|
142
158
|
try {
|
|
143
159
|
process.chdir(agentWorkingDir);
|
|
144
160
|
}
|
|
145
|
-
catch {
|
|
161
|
+
catch {
|
|
162
|
+
/* ignore */
|
|
163
|
+
}
|
|
146
164
|
}
|
|
147
165
|
try {
|
|
148
166
|
for await (const event of query(input.prompt, config)) {
|
|
@@ -171,7 +189,9 @@ export const AgentTool = {
|
|
|
171
189
|
try {
|
|
172
190
|
process.chdir(originalCwd);
|
|
173
191
|
}
|
|
174
|
-
catch {
|
|
192
|
+
catch {
|
|
193
|
+
/* ignore */
|
|
194
|
+
}
|
|
175
195
|
}
|
|
176
196
|
}
|
|
177
197
|
}
|
|
@@ -22,7 +22,7 @@ export const AskUserTool = {
|
|
|
22
22
|
// Headless fallback — return question as text so LLM can see it
|
|
23
23
|
let output = `[AskUser] ${input.question}`;
|
|
24
24
|
if (input.options && input.options.length > 0) {
|
|
25
|
-
output +=
|
|
25
|
+
output += `\nOptions:\n${input.options.map((o, i) => ` ${i + 1}. ${o}`).join("\n")}`;
|
|
26
26
|
}
|
|
27
27
|
output += "\n(No interactive session available — please answer in your next message.)";
|
|
28
28
|
return { output, isError: false };
|
|
@@ -7,13 +7,13 @@ declare const inputSchema: z.ZodObject<{
|
|
|
7
7
|
run_in_background: z.ZodOptional<z.ZodBoolean>;
|
|
8
8
|
}, "strip", z.ZodTypeAny, {
|
|
9
9
|
command: string;
|
|
10
|
-
description?: string | undefined;
|
|
11
10
|
timeout?: number | undefined;
|
|
11
|
+
description?: string | undefined;
|
|
12
12
|
run_in_background?: boolean | undefined;
|
|
13
13
|
}, {
|
|
14
14
|
command: string;
|
|
15
|
-
description?: string | undefined;
|
|
16
15
|
timeout?: number | undefined;
|
|
16
|
+
description?: string | undefined;
|
|
17
17
|
run_in_background?: boolean | undefined;
|
|
18
18
|
}>;
|
|
19
19
|
export declare const BashTool: Tool<typeof inputSchema>;
|