@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.
Files changed (235) hide show
  1. package/README.md +4 -4
  2. package/dist/DeferredTool.js +3 -1
  3. package/dist/Tool.d.ts +1 -1
  4. package/dist/agents/roles.js +58 -62
  5. package/dist/commands/cybergotchi.d.ts +1 -1
  6. package/dist/commands/cybergotchi.js +30 -30
  7. package/dist/commands/index.js +360 -122
  8. package/dist/components/App.d.ts +1 -1
  9. package/dist/components/App.js +6 -6
  10. package/dist/components/CompanionFooter.d.ts +1 -1
  11. package/dist/components/CompanionFooter.js +6 -8
  12. package/dist/components/CybergotchiBubble.js +5 -5
  13. package/dist/components/CybergotchiPanel.d.ts +1 -1
  14. package/dist/components/CybergotchiPanel.js +7 -7
  15. package/dist/components/CybergotchiPanelConnected.js +2 -2
  16. package/dist/components/CybergotchiSetup.js +26 -24
  17. package/dist/components/CybergotchiSprite.d.ts +1 -1
  18. package/dist/components/CybergotchiSprite.js +8 -12
  19. package/dist/components/DiffView.d.ts +1 -1
  20. package/dist/components/DiffView.js +10 -10
  21. package/dist/components/ErrorBoundary.d.ts +1 -1
  22. package/dist/components/ErrorBoundary.js +1 -1
  23. package/dist/components/InitWizard.js +65 -33
  24. package/dist/components/Markdown.js +2 -4
  25. package/dist/components/Messages.js +4 -4
  26. package/dist/components/PermissionPrompt.d.ts +1 -1
  27. package/dist/components/PermissionPrompt.js +15 -17
  28. package/dist/components/REPL.d.ts +1 -1
  29. package/dist/components/REPL.js +74 -49
  30. package/dist/components/Spinner.js +2 -2
  31. package/dist/components/TextInput.js +35 -29
  32. package/dist/components/ToolCallDisplay.js +3 -5
  33. package/dist/cybergotchi/bones.d.ts +1 -1
  34. package/dist/cybergotchi/bones.js +8 -8
  35. package/dist/cybergotchi/config.d.ts +2 -2
  36. package/dist/cybergotchi/config.js +13 -13
  37. package/dist/cybergotchi/events.d.ts +5 -5
  38. package/dist/cybergotchi/events.js +7 -7
  39. package/dist/cybergotchi/needs.d.ts +2 -2
  40. package/dist/cybergotchi/needs.js +7 -9
  41. package/dist/cybergotchi/personality.d.ts +2 -2
  42. package/dist/cybergotchi/personality.js +2 -2
  43. package/dist/cybergotchi/species.d.ts +1 -1
  44. package/dist/cybergotchi/species.js +145 -217
  45. package/dist/cybergotchi/speech.d.ts +2 -2
  46. package/dist/cybergotchi/speech.js +43 -43
  47. package/dist/cybergotchi/types.d.ts +4 -4
  48. package/dist/cybergotchi/types.js +26 -26
  49. package/dist/cybergotchi/useCybergotchi.d.ts +1 -1
  50. package/dist/cybergotchi/useCybergotchi.js +29 -25
  51. package/dist/git/index.js +11 -9
  52. package/dist/harness/checkpoints.js +29 -21
  53. package/dist/harness/config.d.ts +12 -2
  54. package/dist/harness/config.js +15 -9
  55. package/dist/harness/context-warning.d.ts +1 -1
  56. package/dist/harness/context-warning.js +1 -1
  57. package/dist/harness/cost.js +1 -1
  58. package/dist/harness/credentials.js +13 -13
  59. package/dist/harness/hooks.js +7 -5
  60. package/dist/harness/keybindings.js +20 -18
  61. package/dist/harness/marketplace.d.ts +3 -3
  62. package/dist/harness/marketplace.js +55 -42
  63. package/dist/harness/memory.d.ts +23 -5
  64. package/dist/harness/memory.js +142 -41
  65. package/dist/harness/onboarding.js +30 -10
  66. package/dist/harness/plugins.d.ts +9 -1
  67. package/dist/harness/plugins.js +54 -30
  68. package/dist/harness/rules.js +12 -7
  69. package/dist/harness/sandbox.d.ts +34 -0
  70. package/dist/harness/sandbox.js +104 -0
  71. package/dist/harness/session-db.d.ts +55 -0
  72. package/dist/harness/session-db.js +165 -0
  73. package/dist/harness/session.d.ts +1 -1
  74. package/dist/harness/session.js +34 -15
  75. package/dist/harness/store.d.ts +3 -3
  76. package/dist/harness/store.js +6 -4
  77. package/dist/harness/submit-handler.d.ts +4 -4
  78. package/dist/harness/submit-handler.js +57 -21
  79. package/dist/harness/telemetry.d.ts +1 -1
  80. package/dist/harness/telemetry.js +23 -19
  81. package/dist/harness/traces.d.ts +2 -2
  82. package/dist/harness/traces.js +44 -33
  83. package/dist/harness/verification.d.ts +1 -1
  84. package/dist/harness/verification.js +50 -44
  85. package/dist/lsp/client.js +44 -40
  86. package/dist/main.js +100 -59
  87. package/dist/mcp/DeferredMcpTool.d.ts +4 -4
  88. package/dist/mcp/DeferredMcpTool.js +9 -5
  89. package/dist/mcp/McpTool.d.ts +4 -4
  90. package/dist/mcp/McpTool.js +8 -4
  91. package/dist/mcp/client.d.ts +2 -2
  92. package/dist/mcp/client.js +21 -21
  93. package/dist/mcp/loader.d.ts +1 -1
  94. package/dist/mcp/loader.js +17 -12
  95. package/dist/mcp/registry.d.ts +3 -3
  96. package/dist/mcp/registry.js +97 -97
  97. package/dist/mcp/schema.d.ts +1 -1
  98. package/dist/mcp/schema.js +16 -16
  99. package/dist/mcp/server.d.ts +1 -1
  100. package/dist/mcp/server.js +21 -21
  101. package/dist/mcp/types.d.ts +3 -3
  102. package/dist/providers/anthropic.d.ts +2 -2
  103. package/dist/providers/anthropic.js +10 -9
  104. package/dist/providers/base.d.ts +1 -1
  105. package/dist/providers/index.js +10 -3
  106. package/dist/providers/llamacpp.d.ts +2 -2
  107. package/dist/providers/llamacpp.js +1 -3
  108. package/dist/providers/ollama.d.ts +2 -2
  109. package/dist/providers/ollama.js +3 -4
  110. package/dist/providers/openai.d.ts +2 -2
  111. package/dist/providers/openai.js +3 -5
  112. package/dist/providers/openrouter.d.ts +2 -2
  113. package/dist/providers/router.d.ts +1 -1
  114. package/dist/providers/router.js +7 -7
  115. package/dist/query/compress.d.ts +2 -2
  116. package/dist/query/compress.js +22 -21
  117. package/dist/query/context-manager.d.ts +2 -2
  118. package/dist/query/context-manager.js +8 -11
  119. package/dist/query/errors.js +1 -1
  120. package/dist/query/index.d.ts +1 -1
  121. package/dist/query/index.js +30 -22
  122. package/dist/query/tools.js +15 -12
  123. package/dist/query/types.d.ts +1 -1
  124. package/dist/query.d.ts +1 -1
  125. package/dist/query.js +1 -1
  126. package/dist/remote/auth.d.ts +2 -2
  127. package/dist/remote/auth.js +8 -8
  128. package/dist/remote/server.d.ts +3 -3
  129. package/dist/remote/server.js +60 -60
  130. package/dist/renderer/cells.js +9 -9
  131. package/dist/renderer/colors.js +24 -6
  132. package/dist/renderer/diff.d.ts +2 -2
  133. package/dist/renderer/diff.js +27 -19
  134. package/dist/renderer/differ.d.ts +1 -1
  135. package/dist/renderer/differ.js +9 -9
  136. package/dist/renderer/image.js +19 -19
  137. package/dist/renderer/index.d.ts +6 -6
  138. package/dist/renderer/index.js +163 -93
  139. package/dist/renderer/input.js +66 -48
  140. package/dist/renderer/layout.d.ts +6 -6
  141. package/dist/renderer/layout.js +163 -124
  142. package/dist/renderer/markdown.d.ts +2 -2
  143. package/dist/renderer/markdown.js +173 -54
  144. package/dist/renderer/session-browser.d.ts +2 -2
  145. package/dist/renderer/session-browser.js +19 -21
  146. package/dist/repl.d.ts +5 -5
  147. package/dist/repl.js +300 -198
  148. package/dist/sdk/index.d.ts +8 -7
  149. package/dist/sdk/index.js +59 -42
  150. package/dist/services/AgentDispatcher.d.ts +3 -3
  151. package/dist/services/AgentDispatcher.js +33 -29
  152. package/dist/services/CronExecutor.d.ts +4 -4
  153. package/dist/services/CronExecutor.js +12 -8
  154. package/dist/services/EvaluatorLoop.d.ts +3 -3
  155. package/dist/services/EvaluatorLoop.js +29 -21
  156. package/dist/services/MetaHarness.d.ts +1 -1
  157. package/dist/services/MetaHarness.js +41 -33
  158. package/dist/services/PipelineExecutor.d.ts +1 -1
  159. package/dist/services/PipelineExecutor.js +23 -25
  160. package/dist/services/SkillExtractor.d.ts +43 -0
  161. package/dist/services/SkillExtractor.js +143 -0
  162. package/dist/services/StreamingToolExecutor.d.ts +2 -2
  163. package/dist/services/StreamingToolExecutor.js +11 -7
  164. package/dist/services/a2a.d.ts +8 -8
  165. package/dist/services/a2a.js +44 -34
  166. package/dist/services/agent-messaging.d.ts +33 -15
  167. package/dist/services/agent-messaging.js +65 -13
  168. package/dist/services/cron.js +16 -16
  169. package/dist/tools/AgentTool/index.d.ts +5 -2
  170. package/dist/tools/AgentTool/index.js +35 -15
  171. package/dist/tools/AskUserTool/index.js +1 -1
  172. package/dist/tools/BashTool/index.d.ts +2 -2
  173. package/dist/tools/BashTool/index.js +18 -10
  174. package/dist/tools/CronTool/index.d.ts +2 -2
  175. package/dist/tools/CronTool/index.js +30 -12
  176. package/dist/tools/DiagnosticsTool/index.js +28 -22
  177. package/dist/tools/EnterPlanModeTool/index.js +93 -14
  178. package/dist/tools/EnterWorktreeTool/index.js +7 -3
  179. package/dist/tools/ExitPlanModeTool/index.d.ts +22 -1
  180. package/dist/tools/ExitPlanModeTool/index.js +20 -5
  181. package/dist/tools/ExitWorktreeTool/index.js +11 -4
  182. package/dist/tools/FileEditTool/index.js +3 -5
  183. package/dist/tools/FileReadTool/index.js +16 -10
  184. package/dist/tools/FileWriteTool/index.js +2 -2
  185. package/dist/tools/GlobTool/index.js +5 -9
  186. package/dist/tools/GrepTool/index.d.ts +2 -2
  187. package/dist/tools/GrepTool/index.js +14 -9
  188. package/dist/tools/ImageReadTool/index.js +2 -2
  189. package/dist/tools/KillProcessTool/index.js +11 -7
  190. package/dist/tools/LSTool/index.js +3 -3
  191. package/dist/tools/MemoryTool/index.d.ts +11 -11
  192. package/dist/tools/MemoryTool/index.js +28 -14
  193. package/dist/tools/MonitorTool/index.d.ts +2 -2
  194. package/dist/tools/MonitorTool/index.js +24 -19
  195. package/dist/tools/MultiEditTool/index.js +9 -5
  196. package/dist/tools/NotebookEditTool/index.js +3 -3
  197. package/dist/tools/ParallelAgentTool/index.d.ts +4 -4
  198. package/dist/tools/ParallelAgentTool/index.js +12 -6
  199. package/dist/tools/PipelineTool/index.d.ts +4 -4
  200. package/dist/tools/PipelineTool/index.js +3 -3
  201. package/dist/tools/PowerShellTool/index.js +10 -6
  202. package/dist/tools/RemoteTriggerTool/index.js +8 -4
  203. package/dist/tools/ScheduleWakeupTool/index.d.ts +42 -0
  204. package/dist/tools/ScheduleWakeupTool/index.js +115 -0
  205. package/dist/tools/SendMessageTool/index.js +25 -7
  206. package/dist/tools/SessionSearchTool/index.d.ts +15 -0
  207. package/dist/tools/SessionSearchTool/index.js +36 -0
  208. package/dist/tools/SkillTool/index.d.ts +3 -0
  209. package/dist/tools/SkillTool/index.js +39 -9
  210. package/dist/tools/TaskCreateTool/index.d.ts +2 -2
  211. package/dist/tools/TaskCreateTool/index.js +2 -2
  212. package/dist/tools/TaskGetTool/index.js +2 -2
  213. package/dist/tools/TaskListTool/index.js +3 -5
  214. package/dist/tools/TaskOutputTool/index.js +2 -2
  215. package/dist/tools/TaskStopTool/index.js +3 -3
  216. package/dist/tools/TaskUpdateTool/index.d.ts +4 -4
  217. package/dist/tools/TaskUpdateTool/index.js +2 -2
  218. package/dist/tools/ToolSearchTool/index.js +9 -6
  219. package/dist/tools/WebFetchTool/index.js +1 -1
  220. package/dist/tools/WebSearchTool/index.js +2 -6
  221. package/dist/tools.js +31 -30
  222. package/dist/types/permissions.js +15 -9
  223. package/dist/utils/bash-safety.d.ts +1 -1
  224. package/dist/utils/bash-safety.js +64 -54
  225. package/dist/utils/diff-algorithm.d.ts +3 -3
  226. package/dist/utils/diff-algorithm.js +7 -7
  227. package/dist/utils/fs.js +3 -3
  228. package/dist/utils/safe-env.js +1 -1
  229. package/dist/utils/theme-data.d.ts +1 -1
  230. package/dist/utils/theme-data.js +1 -1
  231. package/dist/utils/theme.d.ts +1 -1
  232. package/dist/utils/theme.js +1 -1
  233. package/dist/utils/tool-summary.d.ts +1 -1
  234. package/dist/utils/tool-summary.js +27 -9
  235. package/package.json +10 -3
@@ -7,16 +7,15 @@
7
7
  * - errors.ts — error classification and recovery
8
8
  * - types.ts — shared types
9
9
  */
10
- import { toolToAPIFormat } from "../Tool.js";
11
10
  import { DeferredTool } from "../DeferredTool.js";
12
- import { ContextManager } from "./context-manager.js";
13
- import { createAssistantMessage, createUserMessage } from "../types/message.js";
14
- import { StreamingToolExecutor } from "../services/StreamingToolExecutor.js";
15
11
  import { getContextWindow } from "../harness/cost.js";
16
- import { createToolResultMessage } from "../types/message.js";
17
- import { makeTokenEstimator, estimateMessagesTokens, compressMessages, summarizeConversation } from "./compress.js";
12
+ import { StreamingToolExecutor } from "../services/StreamingToolExecutor.js";
13
+ import { toolToAPIFormat } from "../Tool.js";
14
+ import { createAssistantMessage, createToolResultMessage, createUserMessage } from "../types/message.js";
15
+ import { compressMessages, estimateMessagesTokens, makeTokenEstimator, summarizeConversation } from "./compress.js";
16
+ import { ContextManager } from "./context-manager.js";
17
+ import { isNetworkError, isOverloadError, isPromptTooLongError, isRateLimitError, MAX_CONSECUTIVE_ERRORS, MAX_RATE_LIMIT_RETRIES, } from "./errors.js";
18
18
  import { executeToolCalls } from "./tools.js";
19
- import { isRateLimitError, isOverloadError, isPromptTooLongError, isNetworkError, MAX_CONSECUTIVE_ERRORS, MAX_RATE_LIMIT_RETRIES } from "./errors.js";
20
19
  export { compressMessages } from "./compress.js";
21
20
  const DEFAULT_MAX_TURNS = 50;
22
21
  export async function* query(userMessage, config, existingMessages = []) {
@@ -34,21 +33,19 @@ export async function* query(userMessage, config, existingMessages = []) {
34
33
  const estimateTokens = makeTokenEstimator(config.provider);
35
34
  const contextManager = new ContextManager(undefined, config.model);
36
35
  // Check provider capabilities
37
- const modelInfo = config.provider.getModelInfo?.(config.model ?? '');
36
+ const modelInfo = config.provider.getModelInfo?.(config.model ?? "");
38
37
  const toolsSupported = !modelInfo || modelInfo.supportsTools;
39
38
  const apiTools = toolsSupported ? config.tools.map(toolToAPIFormat) : undefined;
40
- let toolPrompts = toolsSupported
41
- ? config.tools.map((t) => t.prompt()).join("\n\n")
42
- : "";
39
+ let toolPrompts = toolsSupported ? config.tools.map((t) => t.prompt()).join("\n\n") : "";
43
40
  // Hint about deferred tools available via ToolSearch
44
41
  if (toolsSupported) {
45
- const deferredCount = config.tools.filter(t => t instanceof DeferredTool && !t.activated).length;
42
+ const deferredCount = config.tools.filter((t) => t instanceof DeferredTool && !t.activated).length;
46
43
  if (deferredCount > 0) {
47
44
  toolPrompts += `\n\n[${deferredCount} additional tools available — use ToolSearch to discover them]`;
48
45
  }
49
46
  }
50
47
  const fullSystemPrompt = toolPrompts
51
- ? config.systemPrompt + "\n\n# Available Tools\n\n" + toolPrompts
48
+ ? `${config.systemPrompt}\n\n# Available Tools\n\n${toolPrompts}`
52
49
  : config.systemPrompt;
53
50
  const state = {
54
51
  messages: [...existingMessages, createUserMessage(userMessage)],
@@ -81,7 +78,9 @@ export async function* query(userMessage, config, existingMessages = []) {
81
78
  state.messages = await summarizeConversation(config.provider, state.messages, config.model, Math.floor(contextWindow * 0.5));
82
79
  yield { type: "error", message: "Context compressed with LLM summarization." };
83
80
  }
84
- catch { /* continue with basic compression */ }
81
+ catch {
82
+ /* continue with basic compression */
83
+ }
85
84
  }
86
85
  }
87
86
  // ── LLM call with streaming ──
@@ -131,7 +130,10 @@ export async function* query(userMessage, config, existingMessages = []) {
131
130
  state.consecutiveErrors++;
132
131
  // Circuit breaker
133
132
  if (state.consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
134
- yield { type: "error", message: `Too many consecutive errors (${state.consecutiveErrors}): ${streamError.message}` };
133
+ yield {
134
+ type: "error",
135
+ message: `Too many consecutive errors (${state.consecutiveErrors}): ${streamError.message}`,
136
+ };
135
137
  yield { type: "turn_complete", reason: "error" };
136
138
  return;
137
139
  }
@@ -140,13 +142,16 @@ export async function* query(userMessage, config, existingMessages = []) {
140
142
  const attempt = state.consecutiveErrors;
141
143
  const isOverload = isOverloadError(streamError);
142
144
  if (attempt <= MAX_RATE_LIMIT_RETRIES) {
143
- const baseRetry = Math.pow(2, attempt) * (isOverload ? 2 : 1);
145
+ const baseRetry = 2 ** attempt * (isOverload ? 2 : 1);
144
146
  const retryIn = baseRetry * (0.5 + Math.random());
145
147
  yield { type: "rate_limited", retryIn: Math.round(retryIn), attempt };
146
148
  await new Promise((r) => setTimeout(r, retryIn * 1000));
147
149
  continue;
148
150
  }
149
- yield { type: "error", message: `${isOverload ? "Server overloaded" : "Rate limit exceeded"} after ${MAX_RATE_LIMIT_RETRIES} retries.` };
151
+ yield {
152
+ type: "error",
153
+ message: `${isOverload ? "Server overloaded" : "Rate limit exceeded"} after ${MAX_RATE_LIMIT_RETRIES} retries.`,
154
+ };
150
155
  yield { type: "turn_complete", reason: "error" };
151
156
  return;
152
157
  }
@@ -164,7 +169,7 @@ export async function* query(userMessage, config, existingMessages = []) {
164
169
  }
165
170
  if (isNetworkError(streamError)) {
166
171
  state.transition = "retry_network";
167
- const delay = 1000 * Math.pow(2, state.consecutiveErrors - 1);
172
+ const delay = 1000 * 2 ** (state.consecutiveErrors - 1);
168
173
  yield { type: "error", message: `Network error, retrying in ${delay / 1000}s...` };
169
174
  await new Promise((r) => setTimeout(r, delay));
170
175
  continue;
@@ -178,7 +183,10 @@ export async function* query(userMessage, config, existingMessages = []) {
178
183
  return;
179
184
  }
180
185
  if (assistantContent === "" && toolCalls.length === 0) {
181
- yield { type: "error", message: "No response received. Check that your model server is running and the model name is correct." };
186
+ yield {
187
+ type: "error",
188
+ message: "No response received. Check that your model server is running and the model name is correct.",
189
+ };
182
190
  return;
183
191
  }
184
192
  state.messages.push(createAssistantMessage(assistantContent, toolCalls.length > 0 ? toolCalls : undefined));
@@ -189,9 +197,9 @@ export async function* query(userMessage, config, existingMessages = []) {
189
197
  // Collect streaming tool results
190
198
  await streamingExecutor.waitForAll();
191
199
  const completedResults = [...streamingExecutor.getCompletedResults()];
192
- const executedIds = new Set(completedResults.map(r => r.toolCall.id));
200
+ const executedIds = new Set(completedResults.map((r) => r.toolCall.id));
193
201
  for (const { callId, chunk } of streamingExecutor.outputChunks) {
194
- yield { type: 'tool_output_delta', callId, chunk };
202
+ yield { type: "tool_output_delta", callId, chunk };
195
203
  }
196
204
  for (const { toolCall: tc, result } of completedResults) {
197
205
  yield { type: "tool_call_end", callId: tc.id, output: result.output, isError: result.isError };
@@ -200,7 +208,7 @@ export async function* query(userMessage, config, existingMessages = []) {
200
208
  state.messages.push(createToolResultMessage({ callId: tc.id, output: budgetedOutput, isError: result.isError }));
201
209
  }
202
210
  // Execute remaining tools not started during streaming
203
- const remaining = toolCalls.filter(tc => !executedIds.has(tc.id));
211
+ const remaining = toolCalls.filter((tc) => !executedIds.has(tc.id));
204
212
  if (remaining.length > 0) {
205
213
  yield* executeToolCalls(remaining, config.tools, toolContext, config.permissionMode, config.askUser, state);
206
214
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Tool execution — permission checking, batching, output capping.
3
3
  */
4
+ import { createCheckpoint, getAffectedFiles } from "../harness/checkpoints.js";
5
+ import { emitHook } from "../harness/hooks.js";
4
6
  import { findToolByName } from "../Tool.js";
5
7
  import { createToolResultMessage } from "../types/message.js";
6
8
  import { checkPermission } from "../types/permissions.js";
7
- import { emitHook } from "../harness/hooks.js";
8
- import { createCheckpoint, getAffectedFiles } from "../harness/checkpoints.js";
9
9
  const MAX_TOOL_RESULT_CHARS = 100_000;
10
10
  const TOOL_TIMEOUT_MS = 120_000;
11
11
  export function partitionToolCalls(toolCalls, tools) {
@@ -86,17 +86,17 @@ export async function executeSingleTool(toolCall, tools, context, permissionMode
86
86
  toolOutput: result.output.slice(0, 1000),
87
87
  });
88
88
  // Emit fileChanged hook for file-modifying tools
89
- if (!result.isError && ['Edit', 'Write', 'MultiEdit'].includes(tool.name)) {
89
+ if (!result.isError && ["Edit", "Write", "MultiEdit"].includes(tool.name)) {
90
90
  const filePaths = getAffectedFiles(tool.name, parsed.data);
91
91
  for (const fp of filePaths) {
92
92
  emitHook("fileChanged", { filePath: fp, toolName: tool.name });
93
93
  }
94
94
  }
95
95
  // Verification loop: auto-run lint/typecheck after file-modifying tools
96
- let verificationSuffix = '';
97
- if (!result.isError && ['Edit', 'Write', 'MultiEdit'].includes(tool.name)) {
96
+ let verificationSuffix = "";
97
+ if (!result.isError && ["Edit", "Write", "MultiEdit"].includes(tool.name)) {
98
98
  try {
99
- const { runVerificationForFiles, getVerificationConfig, extractFilePaths } = await import('../harness/verification.js');
99
+ const { runVerificationForFiles, getVerificationConfig, extractFilePaths } = await import("../harness/verification.js");
100
100
  const vConfig = getVerificationConfig();
101
101
  if (vConfig?.enabled) {
102
102
  const filePaths = extractFilePaths(tool.name, parsed.data);
@@ -105,24 +105,27 @@ export async function executeSingleTool(toolCall, tools, context, permissionMode
105
105
  if (vResult.ran) {
106
106
  if (!vResult.passed) {
107
107
  verificationSuffix = `\n\n[Verification FAILED]\n${vResult.summary}`;
108
- if (vConfig.mode === 'block') {
108
+ if (vConfig.mode === "block") {
109
109
  result = { output: result.output, isError: true };
110
110
  }
111
111
  }
112
112
  else {
113
- verificationSuffix = '\n\n[Verification passed]';
113
+ verificationSuffix = "\n\n[Verification passed]";
114
114
  }
115
115
  }
116
116
  }
117
117
  }
118
118
  }
119
- catch { /* verification should never break tool execution */ }
119
+ catch {
120
+ /* verification should never break tool execution */
121
+ }
120
122
  }
121
123
  // Strip ANSI and cap output, then append verification suffix
122
124
  let output = result.output.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "") + verificationSuffix;
123
125
  if (output.length > MAX_TOOL_RESULT_CHARS) {
124
- output = output.slice(0, MAX_TOOL_RESULT_CHARS)
125
- + `\n\n[TRUNCATED: output was ${output.length.toLocaleString()} chars, showing first ${MAX_TOOL_RESULT_CHARS.toLocaleString()}]`;
126
+ output =
127
+ output.slice(0, MAX_TOOL_RESULT_CHARS) +
128
+ `\n\n[TRUNCATED: output was ${output.length.toLocaleString()} chars, showing first ${MAX_TOOL_RESULT_CHARS.toLocaleString()}]`;
126
129
  }
127
130
  return { output, isError: result.isError };
128
131
  }
@@ -134,7 +137,7 @@ export async function* executeToolCalls(toolCalls, tools, context, permissionMod
134
137
  const batches = partitionToolCalls(toolCalls, tools);
135
138
  const outputChunks = [];
136
139
  const onOutputChunk = (callId, chunk) => {
137
- outputChunks.push({ type: 'tool_output_delta', callId, chunk });
140
+ outputChunks.push({ type: "tool_output_delta", callId, chunk });
138
141
  };
139
142
  for (const batch of batches) {
140
143
  if (batch.concurrent) {
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Shared types for the query loop sub-modules.
3
3
  */
4
- import type { Message } from "../types/message.js";
5
4
  import type { Provider } from "../providers/base.js";
6
5
  import type { Tools } from "../Tool.js";
6
+ import type { Message } from "../types/message.js";
7
7
  import type { AskUserFn, PermissionMode } from "../types/permissions.js";
8
8
  export type QueryConfig = {
9
9
  provider: Provider;
package/dist/query.d.ts CHANGED
@@ -6,6 +6,6 @@
6
6
  * - query/errors.ts — error classification and recovery
7
7
  * - query/types.ts — shared types
8
8
  */
9
- export { query, compressMessages } from "./query/index.js";
10
9
  export type { QueryConfig, QueryLoopState } from "./query/index.js";
10
+ export { compressMessages, query } from "./query/index.js";
11
11
  //# sourceMappingURL=query.d.ts.map
package/dist/query.js CHANGED
@@ -6,5 +6,5 @@
6
6
  * - query/errors.ts — error classification and recovery
7
7
  * - query/types.ts — shared types
8
8
  */
9
- export { query, compressMessages } from "./query/index.js";
9
+ export { compressMessages, query } from "./query/index.js";
10
10
  //# sourceMappingURL=query.js.map
@@ -2,8 +2,8 @@
2
2
  * API security layer — auth, rate limiting, and tool access control
3
3
  * for the remote server.
4
4
  */
5
- import type { IncomingMessage, ServerResponse } from 'node:http';
6
- import type { Tools } from '../Tool.js';
5
+ import type { IncomingMessage, ServerResponse } from "node:http";
6
+ import type { Tools } from "../Tool.js";
7
7
  /** Check if a request is within rate limits. Returns true if allowed. */
8
8
  export declare function checkRateLimit(ip: string, maxPerMinute: number): boolean;
9
9
  /** Validate a bearer token against configured tokens. Returns true if valid. */
@@ -2,7 +2,7 @@
2
2
  * API security layer — auth, rate limiting, and tool access control
3
3
  * for the remote server.
4
4
  */
5
- import { readOhConfig } from '../harness/config.js';
5
+ import { readOhConfig } from "../harness/config.js";
6
6
  const rateLimitMap = new Map();
7
7
  const WINDOW_MS = 60_000; // 1 minute sliding window
8
8
  /** Check if a request is within rate limits. Returns true if allowed. */
@@ -28,7 +28,7 @@ export function validateToken(authHeader) {
28
28
  return true;
29
29
  if (!authHeader)
30
30
  return false;
31
- const token = authHeader.replace(/^Bearer\s+/i, '').trim();
31
+ const token = authHeader.replace(/^Bearer\s+/i, "").trim();
32
32
  if (!token)
33
33
  return false;
34
34
  return tokens.includes(token);
@@ -40,8 +40,8 @@ export function filterRemoteTools(tools) {
40
40
  const allowed = config?.remote?.allowedTools;
41
41
  if (!allowed || allowed.length === 0)
42
42
  return tools;
43
- const allowSet = new Set(allowed.map(n => n.toLowerCase()));
44
- const filtered = tools.filter(t => allowSet.has(t.name.toLowerCase()));
43
+ const allowSet = new Set(allowed.map((n) => n.toLowerCase()));
44
+ const filtered = tools.filter((t) => allowSet.has(t.name.toLowerCase()));
45
45
  return filtered.length > 0 ? filtered : tools; // fallback to all if filter empties
46
46
  }
47
47
  // ── Request ID ──
@@ -56,17 +56,17 @@ export function generateRequestId() {
56
56
  */
57
57
  export function authenticateRequest(req, res) {
58
58
  const requestId = generateRequestId();
59
- res.setHeader('X-Request-ID', requestId);
59
+ res.setHeader("X-Request-ID", requestId);
60
60
  // Token auth
61
61
  if (!validateToken(req.headers.authorization)) {
62
- return { allowed: false, reason: 'Invalid or missing bearer token', requestId };
62
+ return { allowed: false, reason: "Invalid or missing bearer token", requestId };
63
63
  }
64
64
  // Rate limiting
65
65
  const config = readOhConfig();
66
66
  const rateLimit = config?.remote?.rateLimit ?? 60; // default 60/min
67
- const ip = req.socket.remoteAddress ?? 'unknown';
67
+ const ip = req.socket.remoteAddress ?? "unknown";
68
68
  if (!checkRateLimit(ip, rateLimit)) {
69
- return { allowed: false, reason: 'Rate limit exceeded', requestId };
69
+ return { allowed: false, reason: "Rate limit exceeded", requestId };
70
70
  }
71
71
  return { allowed: true, requestId };
72
72
  }
@@ -10,9 +10,9 @@
10
10
  *
11
11
  * Security: bearer token auth, per-IP rate limiting, tool allowlists.
12
12
  */
13
- import type { Provider } from '../providers/base.js';
14
- import type { Tools } from '../Tool.js';
15
- import type { PermissionMode } from '../types/permissions.js';
13
+ import type { Provider } from "../providers/base.js";
14
+ import type { Tools } from "../Tool.js";
15
+ import type { PermissionMode } from "../types/permissions.js";
16
16
  export type RemoteServerConfig = {
17
17
  port: number;
18
18
  provider: Provider;
@@ -10,10 +10,10 @@
10
10
  *
11
11
  * Security: bearer token auth, per-IP rate limiting, tool allowlists.
12
12
  */
13
- import { createServer } from 'node:http';
14
- import { WebSocketServer, WebSocket } from 'ws';
15
- import { authenticateRequest, filterRemoteTools } from './auth.js';
16
- import { createSessionCard, publishCard, unpublishCard, discoverAgents, generateMessageId, } from '../services/a2a.js';
13
+ import { createServer } from "node:http";
14
+ import { WebSocket, WebSocketServer } from "ws";
15
+ import { createSessionCard, discoverAgents, generateMessageId, publishCard, unpublishCard, } from "../services/a2a.js";
16
+ import { authenticateRequest, filterRemoteTools } from "./auth.js";
17
17
  export class RemoteServer {
18
18
  config;
19
19
  channels = new Map();
@@ -27,8 +27,8 @@ export class RemoteServer {
27
27
  this.server = createServer((req, res) => this.handleHttp(req, res));
28
28
  // WebSocket upgrade
29
29
  const wss = new WebSocketServer({ noServer: true });
30
- this.server.on('upgrade', (request, socket, head) => {
31
- if (request.url === '/channel') {
30
+ this.server.on("upgrade", (request, socket, head) => {
31
+ if (request.url === "/channel") {
32
32
  wss.handleUpgrade(request, socket, head, (ws) => {
33
33
  this.handleChannel(ws);
34
34
  });
@@ -69,29 +69,29 @@ export class RemoteServer {
69
69
  }
70
70
  async handleHttp(req, res) {
71
71
  // CORS headers
72
- res.setHeader('Access-Control-Allow-Origin', '*');
73
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
74
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
75
- if (req.method === 'OPTIONS') {
72
+ res.setHeader("Access-Control-Allow-Origin", "*");
73
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
74
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
75
+ if (req.method === "OPTIONS") {
76
76
  res.writeHead(204);
77
77
  res.end();
78
78
  return;
79
79
  }
80
80
  // Auth check (skip for /status which is a health check)
81
- if (req.url !== '/status') {
81
+ if (req.url !== "/status") {
82
82
  const auth = authenticateRequest(req, res);
83
83
  if (!auth.allowed) {
84
- const status = auth.reason?.includes('Rate limit') ? 429 : 401;
85
- res.writeHead(status, { 'Content-Type': 'application/json' });
84
+ const status = auth.reason?.includes("Rate limit") ? 429 : 401;
85
+ res.writeHead(status, { "Content-Type": "application/json" });
86
86
  res.end(JSON.stringify({ error: auth.reason, requestId: auth.requestId }));
87
87
  return;
88
88
  }
89
89
  }
90
90
  // ── GET /status ──
91
- if (req.url === '/status' && req.method === 'GET') {
92
- res.writeHead(200, { 'Content-Type': 'application/json' });
91
+ if (req.url === "/status" && req.method === "GET") {
92
+ res.writeHead(200, { "Content-Type": "application/json" });
93
93
  res.end(JSON.stringify({
94
- status: 'ok',
94
+ status: "ok",
95
95
  provider: this.config.provider.name,
96
96
  model: this.config.model,
97
97
  channels: this.channels.size,
@@ -100,24 +100,24 @@ export class RemoteServer {
100
100
  return;
101
101
  }
102
102
  // ── POST /dispatch ──
103
- if (req.url === '/dispatch' && req.method === 'POST') {
103
+ if (req.url === "/dispatch" && req.method === "POST") {
104
104
  await this.handleDispatch(req, res);
105
105
  return;
106
106
  }
107
107
  // ── POST /a2a ──
108
- if (req.url === '/a2a' && req.method === 'POST') {
108
+ if (req.url === "/a2a" && req.method === "POST") {
109
109
  await this.handleA2A(req, res);
110
110
  return;
111
111
  }
112
- res.writeHead(404, { 'Content-Type': 'application/json' });
113
- res.end(JSON.stringify({ error: 'Not found' }));
112
+ res.writeHead(404, { "Content-Type": "application/json" });
113
+ res.end(JSON.stringify({ error: "Not found" }));
114
114
  }
115
115
  async handleDispatch(req, res) {
116
116
  const body = await readBody(req);
117
117
  try {
118
118
  const { prompt, maxTurns } = JSON.parse(body);
119
119
  if (!prompt) {
120
- res.writeHead(400, { 'Content-Type': 'application/json' });
120
+ res.writeHead(400, { "Content-Type": "application/json" });
121
121
  res.end(JSON.stringify({ error: 'Missing "prompt" field' }));
122
122
  return;
123
123
  }
@@ -125,11 +125,11 @@ export class RemoteServer {
125
125
  const tools = filterRemoteTools(this.config.tools);
126
126
  // Stream response as Server-Sent Events
127
127
  res.writeHead(200, {
128
- 'Content-Type': 'text/event-stream',
129
- 'Cache-Control': 'no-cache',
130
- 'Connection': 'keep-alive',
128
+ "Content-Type": "text/event-stream",
129
+ "Cache-Control": "no-cache",
130
+ Connection: "keep-alive",
131
131
  });
132
- const { query } = await import('../query.js');
132
+ const { query } = await import("../query.js");
133
133
  const config = {
134
134
  provider: this.config.provider,
135
135
  tools,
@@ -142,12 +142,12 @@ export class RemoteServer {
142
142
  const data = JSON.stringify(event);
143
143
  res.write(`data: ${data}\n\n`);
144
144
  }
145
- res.write('data: [DONE]\n\n');
145
+ res.write("data: [DONE]\n\n");
146
146
  res.end();
147
147
  }
148
148
  catch (err) {
149
149
  if (!res.headersSent) {
150
- res.writeHead(500, { 'Content-Type': 'application/json' });
150
+ res.writeHead(500, { "Content-Type": "application/json" });
151
151
  }
152
152
  res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
153
153
  }
@@ -166,26 +166,26 @@ export class RemoteServer {
166
166
  try {
167
167
  const message = JSON.parse(body);
168
168
  switch (message.payload.kind) {
169
- case 'discover': {
169
+ case "discover": {
170
170
  // Return our agent card
171
171
  const agents = discoverAgents();
172
- const self = agents.find(a => a.id === this.agentCardId);
172
+ const self = agents.find((a) => a.id === this.agentCardId);
173
173
  const response = {
174
174
  id: generateMessageId(),
175
- from: this.agentCardId ?? 'unknown',
175
+ from: this.agentCardId ?? "unknown",
176
176
  to: message.from,
177
- type: 'result',
178
- payload: { kind: 'result', taskId: message.id, output: self ?? { error: 'agent not found' } },
177
+ type: "result",
178
+ payload: { kind: "result", taskId: message.id, output: self ?? { error: "agent not found" } },
179
179
  timestamp: Date.now(),
180
180
  };
181
- res.writeHead(200, { 'Content-Type': 'application/json' });
181
+ res.writeHead(200, { "Content-Type": "application/json" });
182
182
  res.end(JSON.stringify(response));
183
183
  return;
184
184
  }
185
- case 'task': {
185
+ case "task": {
186
186
  // Execute the task via query loop
187
187
  const tools = filterRemoteTools(this.config.tools);
188
- const { query } = await import('../query.js');
188
+ const { query } = await import("../query.js");
189
189
  const config = {
190
190
  provider: this.config.provider,
191
191
  tools,
@@ -194,45 +194,45 @@ export class RemoteServer {
194
194
  model: this.config.model,
195
195
  maxTurns: 10,
196
196
  };
197
- let output = '';
197
+ let output = "";
198
198
  for await (const event of query(String(message.payload.input), config)) {
199
- if (event.type === 'text_delta')
199
+ if (event.type === "text_delta")
200
200
  output += event.content;
201
201
  }
202
202
  const response = {
203
203
  id: generateMessageId(),
204
- from: this.agentCardId ?? 'unknown',
204
+ from: this.agentCardId ?? "unknown",
205
205
  to: message.from,
206
- type: 'result',
207
- payload: { kind: 'result', taskId: message.id, output },
206
+ type: "result",
207
+ payload: { kind: "result", taskId: message.id, output },
208
208
  timestamp: Date.now(),
209
209
  };
210
- res.writeHead(200, { 'Content-Type': 'application/json' });
210
+ res.writeHead(200, { "Content-Type": "application/json" });
211
211
  res.end(JSON.stringify(response));
212
212
  return;
213
213
  }
214
- case 'status': {
214
+ case "status": {
215
215
  const response = {
216
216
  id: generateMessageId(),
217
- from: this.agentCardId ?? 'unknown',
217
+ from: this.agentCardId ?? "unknown",
218
218
  to: message.from,
219
- type: 'status',
220
- payload: { kind: 'status', state: 'idle' },
219
+ type: "status",
220
+ payload: { kind: "status", state: "idle" },
221
221
  timestamp: Date.now(),
222
222
  };
223
- res.writeHead(200, { 'Content-Type': 'application/json' });
223
+ res.writeHead(200, { "Content-Type": "application/json" });
224
224
  res.end(JSON.stringify(response));
225
225
  return;
226
226
  }
227
227
  default: {
228
- res.writeHead(400, { 'Content-Type': 'application/json' });
228
+ res.writeHead(400, { "Content-Type": "application/json" });
229
229
  res.end(JSON.stringify({ error: `Unknown A2A message kind: ${message.payload.kind}` }));
230
230
  return;
231
231
  }
232
232
  }
233
233
  }
234
234
  catch (err) {
235
- res.writeHead(400, { 'Content-Type': 'application/json' });
235
+ res.writeHead(400, { "Content-Type": "application/json" });
236
236
  res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
237
237
  }
238
238
  }
@@ -242,13 +242,13 @@ export class RemoteServer {
242
242
  const channel = { id, ws, abortController };
243
243
  this.channels.set(id, channel);
244
244
  process.stderr.write(`[remote] Channel ${id} connected\n`);
245
- ws.send(JSON.stringify({ type: 'connected', channelId: id }));
246
- ws.on('message', async (data) => {
245
+ ws.send(JSON.stringify({ type: "connected", channelId: id }));
246
+ ws.on("message", async (data) => {
247
247
  try {
248
248
  const msg = JSON.parse(data.toString());
249
- if (msg.type === 'dispatch') {
249
+ if (msg.type === "dispatch") {
250
250
  const tools = filterRemoteTools(this.config.tools);
251
- const { query } = await import('../query.js');
251
+ const { query } = await import("../query.js");
252
252
  const config = {
253
253
  provider: this.config.provider,
254
254
  tools,
@@ -263,18 +263,18 @@ export class RemoteServer {
263
263
  break;
264
264
  ws.send(JSON.stringify(event));
265
265
  }
266
- ws.send(JSON.stringify({ type: 'dispatch_complete' }));
266
+ ws.send(JSON.stringify({ type: "dispatch_complete" }));
267
267
  }
268
- if (msg.type === 'abort') {
268
+ if (msg.type === "abort") {
269
269
  abortController.abort();
270
- ws.send(JSON.stringify({ type: 'aborted' }));
270
+ ws.send(JSON.stringify({ type: "aborted" }));
271
271
  }
272
272
  }
273
273
  catch (err) {
274
- ws.send(JSON.stringify({ type: 'error', message: err instanceof Error ? err.message : String(err) }));
274
+ ws.send(JSON.stringify({ type: "error", message: err instanceof Error ? err.message : String(err) }));
275
275
  }
276
276
  });
277
- ws.on('close', () => {
277
+ ws.on("close", () => {
278
278
  abortController.abort();
279
279
  this.channels.delete(id);
280
280
  process.stderr.write(`[remote] Channel ${id} disconnected\n`);
@@ -284,9 +284,9 @@ export class RemoteServer {
284
284
  function readBody(req) {
285
285
  return new Promise((resolve, reject) => {
286
286
  const chunks = [];
287
- req.on('data', (chunk) => chunks.push(chunk));
288
- req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
289
- req.on('error', reject);
287
+ req.on("data", (chunk) => chunks.push(chunk));
288
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
289
+ req.on("error", reject);
290
290
  });
291
291
  }
292
292
  //# sourceMappingURL=server.js.map