@zhijiewang/openharness 2.1.0 → 2.3.1

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 (231) 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 +288 -132
  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 +3 -3
  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.js +15 -15
  70. package/dist/harness/session-db.d.ts +55 -0
  71. package/dist/harness/session-db.js +165 -0
  72. package/dist/harness/session.d.ts +1 -1
  73. package/dist/harness/session.js +34 -15
  74. package/dist/harness/store.d.ts +3 -3
  75. package/dist/harness/store.js +6 -4
  76. package/dist/harness/submit-handler.d.ts +4 -4
  77. package/dist/harness/submit-handler.js +25 -23
  78. package/dist/harness/telemetry.d.ts +1 -1
  79. package/dist/harness/telemetry.js +23 -19
  80. package/dist/harness/traces.d.ts +2 -2
  81. package/dist/harness/traces.js +39 -33
  82. package/dist/harness/verification.d.ts +1 -1
  83. package/dist/harness/verification.js +50 -44
  84. package/dist/lsp/client.js +44 -40
  85. package/dist/main.js +114 -59
  86. package/dist/mcp/DeferredMcpTool.d.ts +4 -4
  87. package/dist/mcp/DeferredMcpTool.js +9 -5
  88. package/dist/mcp/McpTool.d.ts +4 -4
  89. package/dist/mcp/McpTool.js +8 -4
  90. package/dist/mcp/client.d.ts +2 -2
  91. package/dist/mcp/client.js +21 -21
  92. package/dist/mcp/loader.d.ts +1 -1
  93. package/dist/mcp/loader.js +17 -12
  94. package/dist/mcp/registry.d.ts +3 -3
  95. package/dist/mcp/registry.js +97 -97
  96. package/dist/mcp/schema.d.ts +1 -1
  97. package/dist/mcp/schema.js +16 -16
  98. package/dist/mcp/server.d.ts +1 -1
  99. package/dist/mcp/server.js +21 -21
  100. package/dist/mcp/types.d.ts +3 -3
  101. package/dist/providers/anthropic.d.ts +2 -2
  102. package/dist/providers/anthropic.js +10 -9
  103. package/dist/providers/base.d.ts +1 -1
  104. package/dist/providers/index.js +10 -3
  105. package/dist/providers/llamacpp.d.ts +2 -2
  106. package/dist/providers/llamacpp.js +1 -3
  107. package/dist/providers/ollama.d.ts +2 -2
  108. package/dist/providers/ollama.js +3 -4
  109. package/dist/providers/openai.d.ts +2 -2
  110. package/dist/providers/openai.js +3 -5
  111. package/dist/providers/openrouter.d.ts +2 -2
  112. package/dist/providers/router.d.ts +1 -1
  113. package/dist/providers/router.js +7 -7
  114. package/dist/query/compress.d.ts +2 -2
  115. package/dist/query/compress.js +22 -21
  116. package/dist/query/context-manager.d.ts +1 -1
  117. package/dist/query/context-manager.js +5 -5
  118. package/dist/query/errors.js +1 -1
  119. package/dist/query/index.d.ts +1 -1
  120. package/dist/query/index.js +42 -24
  121. package/dist/query/tools.js +15 -12
  122. package/dist/query/types.d.ts +3 -1
  123. package/dist/query.d.ts +1 -1
  124. package/dist/query.js +1 -1
  125. package/dist/remote/auth.d.ts +2 -2
  126. package/dist/remote/auth.js +8 -8
  127. package/dist/remote/server.d.ts +3 -3
  128. package/dist/remote/server.js +60 -60
  129. package/dist/renderer/cells.js +9 -9
  130. package/dist/renderer/colors.js +24 -6
  131. package/dist/renderer/diff.d.ts +2 -2
  132. package/dist/renderer/diff.js +27 -19
  133. package/dist/renderer/differ.d.ts +1 -1
  134. package/dist/renderer/differ.js +9 -9
  135. package/dist/renderer/image.js +19 -19
  136. package/dist/renderer/index.d.ts +6 -6
  137. package/dist/renderer/index.js +163 -93
  138. package/dist/renderer/input.js +66 -48
  139. package/dist/renderer/layout.d.ts +6 -6
  140. package/dist/renderer/layout.js +163 -124
  141. package/dist/renderer/markdown.d.ts +2 -2
  142. package/dist/renderer/markdown.js +173 -54
  143. package/dist/renderer/session-browser.d.ts +2 -2
  144. package/dist/renderer/session-browser.js +19 -21
  145. package/dist/repl.d.ts +5 -5
  146. package/dist/repl.js +311 -198
  147. package/dist/sdk/index.d.ts +5 -5
  148. package/dist/sdk/index.js +32 -26
  149. package/dist/services/AgentDispatcher.d.ts +3 -3
  150. package/dist/services/AgentDispatcher.js +33 -29
  151. package/dist/services/CronExecutor.d.ts +4 -4
  152. package/dist/services/CronExecutor.js +12 -8
  153. package/dist/services/EvaluatorLoop.d.ts +3 -3
  154. package/dist/services/EvaluatorLoop.js +29 -21
  155. package/dist/services/MetaHarness.d.ts +1 -1
  156. package/dist/services/MetaHarness.js +34 -32
  157. package/dist/services/PipelineExecutor.d.ts +1 -1
  158. package/dist/services/PipelineExecutor.js +23 -25
  159. package/dist/services/SkillExtractor.d.ts +43 -0
  160. package/dist/services/SkillExtractor.js +163 -0
  161. package/dist/services/StreamingToolExecutor.d.ts +2 -2
  162. package/dist/services/StreamingToolExecutor.js +11 -7
  163. package/dist/services/a2a.d.ts +8 -8
  164. package/dist/services/a2a.js +44 -34
  165. package/dist/services/agent-messaging.d.ts +33 -15
  166. package/dist/services/agent-messaging.js +65 -13
  167. package/dist/services/cron.js +16 -16
  168. package/dist/tools/AgentTool/index.d.ts +5 -2
  169. package/dist/tools/AgentTool/index.js +25 -39
  170. package/dist/tools/AskUserTool/index.js +1 -1
  171. package/dist/tools/BashTool/index.d.ts +2 -2
  172. package/dist/tools/BashTool/index.js +18 -10
  173. package/dist/tools/CronTool/index.js +30 -12
  174. package/dist/tools/DiagnosticsTool/index.js +28 -22
  175. package/dist/tools/EnterPlanModeTool/index.js +93 -14
  176. package/dist/tools/EnterWorktreeTool/index.js +7 -3
  177. package/dist/tools/ExitPlanModeTool/index.d.ts +22 -1
  178. package/dist/tools/ExitPlanModeTool/index.js +20 -5
  179. package/dist/tools/ExitWorktreeTool/index.js +11 -4
  180. package/dist/tools/FileEditTool/index.js +3 -5
  181. package/dist/tools/FileReadTool/index.js +16 -10
  182. package/dist/tools/FileWriteTool/index.js +2 -2
  183. package/dist/tools/GlobTool/index.js +5 -9
  184. package/dist/tools/GrepTool/index.d.ts +2 -2
  185. package/dist/tools/GrepTool/index.js +14 -9
  186. package/dist/tools/ImageReadTool/index.js +2 -2
  187. package/dist/tools/KillProcessTool/index.js +11 -7
  188. package/dist/tools/LSTool/index.js +3 -3
  189. package/dist/tools/MemoryTool/index.d.ts +5 -5
  190. package/dist/tools/MemoryTool/index.js +28 -14
  191. package/dist/tools/MonitorTool/index.js +24 -19
  192. package/dist/tools/MultiEditTool/index.js +9 -5
  193. package/dist/tools/NotebookEditTool/index.js +3 -3
  194. package/dist/tools/ParallelAgentTool/index.d.ts +4 -4
  195. package/dist/tools/ParallelAgentTool/index.js +12 -6
  196. package/dist/tools/PipelineTool/index.js +3 -3
  197. package/dist/tools/PowerShellTool/index.js +10 -6
  198. package/dist/tools/RemoteTriggerTool/index.js +8 -4
  199. package/dist/tools/ScheduleWakeupTool/index.d.ts +42 -0
  200. package/dist/tools/ScheduleWakeupTool/index.js +115 -0
  201. package/dist/tools/SendMessageTool/index.js +25 -7
  202. package/dist/tools/SessionSearchTool/index.d.ts +15 -0
  203. package/dist/tools/SessionSearchTool/index.js +36 -0
  204. package/dist/tools/SkillTool/index.d.ts +3 -0
  205. package/dist/tools/SkillTool/index.js +39 -9
  206. package/dist/tools/TaskCreateTool/index.d.ts +2 -2
  207. package/dist/tools/TaskCreateTool/index.js +2 -2
  208. package/dist/tools/TaskGetTool/index.js +2 -2
  209. package/dist/tools/TaskListTool/index.js +3 -5
  210. package/dist/tools/TaskOutputTool/index.js +2 -2
  211. package/dist/tools/TaskStopTool/index.js +3 -3
  212. package/dist/tools/TaskUpdateTool/index.d.ts +4 -4
  213. package/dist/tools/TaskUpdateTool/index.js +2 -2
  214. package/dist/tools/ToolSearchTool/index.js +9 -6
  215. package/dist/tools/WebFetchTool/index.js +1 -1
  216. package/dist/tools/WebSearchTool/index.js +2 -6
  217. package/dist/tools.js +31 -30
  218. package/dist/types/permissions.js +15 -9
  219. package/dist/utils/bash-safety.d.ts +1 -1
  220. package/dist/utils/bash-safety.js +64 -54
  221. package/dist/utils/diff-algorithm.d.ts +3 -3
  222. package/dist/utils/diff-algorithm.js +7 -7
  223. package/dist/utils/fs.js +3 -3
  224. package/dist/utils/safe-env.js +1 -1
  225. package/dist/utils/theme-data.d.ts +1 -1
  226. package/dist/utils/theme-data.js +1 -1
  227. package/dist/utils/theme.d.ts +1 -1
  228. package/dist/utils/theme.js +1 -1
  229. package/dist/utils/tool-summary.d.ts +1 -1
  230. package/dist/utils/tool-summary.js +27 -9
  231. package/package.json +10 -3
@@ -12,7 +12,7 @@
12
12
  * Optional: POST to configurable endpoint on session end.
13
13
  */
14
14
  export type TelemetryEvent = {
15
- type: 'session_start' | 'tool_call' | 'error' | 'session_end';
15
+ type: "session_start" | "tool_call" | "error" | "session_end";
16
16
  timestamp: number;
17
17
  sessionId: string;
18
18
  payload: TelemetryPayload;
@@ -11,11 +11,11 @@
11
11
  * Events are batched locally as JSONL in ~/.oh/telemetry/.
12
12
  * Optional: POST to configurable endpoint on session end.
13
13
  */
14
- import { appendFileSync, mkdirSync, existsSync, readdirSync, readFileSync } from 'node:fs';
15
- import { join } from 'node:path';
16
- import { homedir } from 'node:os';
17
- import { readOhConfig } from './config.js';
18
- const TELEMETRY_DIR = join(homedir(), '.oh', 'telemetry');
14
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
15
+ import { homedir } from "node:os";
16
+ import { join } from "node:path";
17
+ import { readOhConfig } from "./config.js";
18
+ const TELEMETRY_DIR = join(homedir(), ".oh", "telemetry");
19
19
  // ── State ──
20
20
  let _enabled;
21
21
  let _sessionFile = null;
@@ -40,14 +40,16 @@ export function recordEvent(event) {
40
40
  return;
41
41
  try {
42
42
  const file = getSessionFile(event.sessionId);
43
- appendFileSync(file, JSON.stringify(event) + '\n');
43
+ appendFileSync(file, `${JSON.stringify(event)}\n`);
44
+ }
45
+ catch {
46
+ /* never crash on telemetry failure */
44
47
  }
45
- catch { /* never crash on telemetry failure */ }
46
48
  }
47
49
  /** Convenience: record a tool call event */
48
50
  export function recordToolCall(sessionId, toolName, durationMs, isError) {
49
51
  recordEvent({
50
- type: 'tool_call',
52
+ type: "tool_call",
51
53
  timestamp: Date.now(),
52
54
  sessionId,
53
55
  payload: { toolName, durationMs, isError },
@@ -56,7 +58,7 @@ export function recordToolCall(sessionId, toolName, durationMs, isError) {
56
58
  /** Convenience: record session start */
57
59
  export function recordSessionStart(sessionId, provider, model) {
58
60
  recordEvent({
59
- type: 'session_start',
61
+ type: "session_start",
60
62
  timestamp: Date.now(),
61
63
  sessionId,
62
64
  payload: { provider, model, platform: process.platform },
@@ -65,7 +67,7 @@ export function recordSessionStart(sessionId, provider, model) {
65
67
  /** Convenience: record session end with stats */
66
68
  export function recordSessionEnd(sessionId, stats) {
67
69
  recordEvent({
68
- type: 'session_end',
70
+ type: "session_end",
69
71
  timestamp: Date.now(),
70
72
  sessionId,
71
73
  payload: stats,
@@ -74,7 +76,7 @@ export function recordSessionEnd(sessionId, stats) {
74
76
  /** Convenience: record an error */
75
77
  export function recordError(sessionId, category) {
76
78
  recordEvent({
77
- type: 'error',
79
+ type: "error",
78
80
  timestamp: Date.now(),
79
81
  sessionId,
80
82
  payload: { errorCategory: category },
@@ -86,10 +88,10 @@ export function readSessionEvents(sessionId) {
86
88
  if (!existsSync(file))
87
89
  return [];
88
90
  try {
89
- return readFileSync(file, 'utf-8')
90
- .split('\n')
91
+ return readFileSync(file, "utf-8")
92
+ .split("\n")
91
93
  .filter(Boolean)
92
- .map(line => JSON.parse(line));
94
+ .map((line) => JSON.parse(line));
93
95
  }
94
96
  catch {
95
97
  return [];
@@ -99,25 +101,27 @@ export function readSessionEvents(sessionId) {
99
101
  export function getAggregateStats() {
100
102
  if (!existsSync(TELEMETRY_DIR))
101
103
  return { totalSessions: 0, totalEvents: 0, toolUsage: {}, errorCategories: {} };
102
- const files = readdirSync(TELEMETRY_DIR).filter(f => f.endsWith('.jsonl'));
104
+ const files = readdirSync(TELEMETRY_DIR).filter((f) => f.endsWith(".jsonl"));
103
105
  const toolUsage = {};
104
106
  const errorCategories = {};
105
107
  let totalEvents = 0;
106
108
  for (const file of files) {
107
109
  try {
108
- const lines = readFileSync(join(TELEMETRY_DIR, file), 'utf-8').split('\n').filter(Boolean);
110
+ const lines = readFileSync(join(TELEMETRY_DIR, file), "utf-8").split("\n").filter(Boolean);
109
111
  totalEvents += lines.length;
110
112
  for (const line of lines) {
111
113
  const event = JSON.parse(line);
112
- if (event.type === 'tool_call' && event.payload.toolName) {
114
+ if (event.type === "tool_call" && event.payload.toolName) {
113
115
  toolUsage[event.payload.toolName] = (toolUsage[event.payload.toolName] ?? 0) + 1;
114
116
  }
115
- if (event.type === 'error' && event.payload.errorCategory) {
117
+ if (event.type === "error" && event.payload.errorCategory) {
116
118
  errorCategories[event.payload.errorCategory] = (errorCategories[event.payload.errorCategory] ?? 0) + 1;
117
119
  }
118
120
  }
119
121
  }
120
- catch { /* skip malformed files */ }
122
+ catch {
123
+ /* skip malformed files */
124
+ }
121
125
  }
122
126
  return { totalSessions: files.length, totalEvents, toolUsage, errorCategories };
123
127
  }
@@ -15,7 +15,7 @@ export type TraceSpan = {
15
15
  endTime: number;
16
16
  durationMs: number;
17
17
  attributes: Record<string, unknown>;
18
- status: 'ok' | 'error';
18
+ status: "ok" | "error";
19
19
  };
20
20
  export type TraceEvent = {
21
21
  name: string;
@@ -31,7 +31,7 @@ export declare class SessionTracer {
31
31
  /** Start a new span. Returns the span ID. */
32
32
  startSpan(name: string, attributes?: Record<string, unknown>, parentSpanId?: string): string;
33
33
  /** End a span and record it. */
34
- endSpan(spanId: string, status?: 'ok' | 'error', extraAttributes?: Record<string, unknown>): TraceSpan | null;
34
+ endSpan(spanId: string, status?: "ok" | "error", extraAttributes?: Record<string, unknown>): TraceSpan | null;
35
35
  /** Get all completed spans */
36
36
  getSpans(): TraceSpan[];
37
37
  /** Get a summary of the trace */
@@ -7,10 +7,10 @@
7
7
  *
8
8
  * Compatible with OpenTelemetry export format.
9
9
  */
10
- import { appendFileSync, mkdirSync, existsSync, readFileSync, readdirSync } from 'node:fs';
11
- import { join } from 'node:path';
12
- import { homedir } from 'node:os';
13
- const TRACE_DIR = join(homedir(), '.oh', 'traces');
10
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
11
+ import { homedir } from "node:os";
12
+ import { join } from "node:path";
13
+ const TRACE_DIR = join(homedir(), ".oh", "traces");
14
14
  // ── Tracer ──
15
15
  const MAX_IN_MEMORY_SPANS = 1000;
16
16
  export class SessionTracer {
@@ -28,7 +28,7 @@ export class SessionTracer {
28
28
  return spanId;
29
29
  }
30
30
  /** End a span and record it. */
31
- endSpan(spanId, status = 'ok', extraAttributes) {
31
+ endSpan(spanId, status = "ok", extraAttributes) {
32
32
  const active = this.activeSpans.get(spanId);
33
33
  if (!active)
34
34
  return null;
@@ -67,7 +67,7 @@ export class SessionTracer {
67
67
  entry.count++;
68
68
  entry.totalMs += span.durationMs;
69
69
  spansByName[span.name] = entry;
70
- if (span.status === 'error')
70
+ if (span.status === "error")
71
71
  errors++;
72
72
  if (span.startTime < minStart)
73
73
  minStart = span.startTime;
@@ -86,9 +86,11 @@ export class SessionTracer {
86
86
  try {
87
87
  mkdirSync(TRACE_DIR, { recursive: true });
88
88
  const file = join(TRACE_DIR, `${this.sessionId}.jsonl`);
89
- appendFileSync(file, JSON.stringify(span) + '\n');
89
+ appendFileSync(file, `${JSON.stringify(span)}\n`);
90
+ }
91
+ catch {
92
+ /* never crash on tracing failure */
90
93
  }
91
- catch { /* never crash on tracing failure */ }
92
94
  }
93
95
  }
94
96
  // ── Trace Loading ──
@@ -98,10 +100,10 @@ export function loadTrace(sessionId) {
98
100
  if (!existsSync(file))
99
101
  return [];
100
102
  try {
101
- return readFileSync(file, 'utf-8')
102
- .split('\n')
103
+ return readFileSync(file, "utf-8")
104
+ .split("\n")
103
105
  .filter(Boolean)
104
- .map(line => JSON.parse(line));
106
+ .map((line) => JSON.parse(line));
105
107
  }
106
108
  catch {
107
109
  return [];
@@ -112,16 +114,16 @@ export function listTracedSessions() {
112
114
  if (!existsSync(TRACE_DIR))
113
115
  return [];
114
116
  return readdirSync(TRACE_DIR)
115
- .filter(f => f.endsWith('.jsonl'))
116
- .map(f => f.replace('.jsonl', ''));
117
+ .filter((f) => f.endsWith(".jsonl"))
118
+ .map((f) => f.replace(".jsonl", ""));
117
119
  }
118
120
  /** Format trace for display */
119
121
  export function formatTrace(spans) {
120
122
  if (spans.length === 0)
121
- return 'No trace spans recorded.';
123
+ return "No trace spans recorded.";
122
124
  const lines = [`Trace (${spans.length} spans):\n`];
123
125
  // Group by parent for tree display
124
- const roots = spans.filter(s => !s.parentSpanId);
126
+ const roots = spans.filter((s) => !s.parentSpanId);
125
127
  const children = new Map();
126
128
  for (const s of spans) {
127
129
  if (s.parentSpanId) {
@@ -131,12 +133,12 @@ export function formatTrace(spans) {
131
133
  }
132
134
  }
133
135
  function renderSpan(span, indent) {
134
- const status = span.status === 'error' ? '' : '';
135
- const pad = ' '.repeat(indent);
136
+ const status = span.status === "error" ? "" : "";
137
+ const pad = " ".repeat(indent);
136
138
  const attrs = Object.entries(span.attributes)
137
139
  .filter(([, v]) => v !== undefined)
138
140
  .map(([k, v]) => `${k}=${String(v).slice(0, 30)}`)
139
- .join(' ');
141
+ .join(" ");
140
142
  lines.push(`${pad}${status} ${span.name} (${span.durationMs}ms) ${attrs}`);
141
143
  const kids = children.get(span.spanId) ?? [];
142
144
  for (const kid of kids)
@@ -146,27 +148,29 @@ export function formatTrace(spans) {
146
148
  renderSpan(root, 0);
147
149
  // Summary
148
150
  const totalMs = spans.reduce((sum, s) => sum + s.durationMs, 0);
149
- const errors = spans.filter(s => s.status === 'error').length;
150
- lines.push('');
151
+ const errors = spans.filter((s) => s.status === "error").length;
152
+ lines.push("");
151
153
  lines.push(`Total: ${spans.length} spans, ${totalMs}ms, ${errors} errors`);
152
- return lines.join('\n');
154
+ return lines.join("\n");
153
155
  }
154
156
  /** Export trace in OpenTelemetry-compatible format */
155
157
  export function exportTraceOTLP(sessionId, spans) {
156
158
  return {
157
- resourceSpans: [{
159
+ resourceSpans: [
160
+ {
158
161
  resource: {
159
162
  attributes: [
160
- { key: 'service.name', value: { stringValue: 'openharness' } },
161
- { key: 'session.id', value: { stringValue: sessionId } },
163
+ { key: "service.name", value: { stringValue: "openharness" } },
164
+ { key: "session.id", value: { stringValue: sessionId } },
162
165
  ],
163
166
  },
164
- scopeSpans: [{
165
- scope: { name: 'openharness.agent' },
166
- spans: spans.map(s => ({
167
- traceId: sessionId.padEnd(32, '0').slice(0, 32),
168
- spanId: s.spanId.padEnd(16, '0').slice(0, 16),
169
- parentSpanId: s.parentSpanId?.padEnd(16, '0').slice(0, 16),
167
+ scopeSpans: [
168
+ {
169
+ scope: { name: "openharness.agent" },
170
+ spans: spans.map((s) => ({
171
+ traceId: sessionId.padEnd(32, "0").slice(0, 32),
172
+ spanId: s.spanId.padEnd(16, "0").slice(0, 16),
173
+ parentSpanId: s.parentSpanId?.padEnd(16, "0").slice(0, 16),
170
174
  name: s.name,
171
175
  startTimeUnixNano: s.startTime * 1_000_000,
172
176
  endTimeUnixNano: s.endTime * 1_000_000,
@@ -174,10 +178,12 @@ export function exportTraceOTLP(sessionId, spans) {
174
178
  key: k,
175
179
  value: { stringValue: String(v) },
176
180
  })),
177
- status: { code: s.status === 'ok' ? 1 : 2 },
181
+ status: { code: s.status === "ok" ? 1 : 2 },
178
182
  })),
179
- }],
180
- }],
183
+ },
184
+ ],
185
+ },
186
+ ],
181
187
  };
182
188
  }
183
189
  //# sourceMappingURL=traces.js.map
@@ -15,7 +15,7 @@ export type VerificationRule = {
15
15
  };
16
16
  export type VerificationConfig = {
17
17
  enabled: boolean;
18
- mode: 'warn' | 'block';
18
+ mode: "warn" | "block";
19
19
  rules: VerificationRule[];
20
20
  };
21
21
  export type VerificationResult = {
@@ -8,10 +8,10 @@
8
8
  * This is the single highest-impact harness engineering pattern —
9
9
  * research shows 2-3x quality improvement from automated feedback.
10
10
  */
11
- import { execSync } from 'node:child_process';
12
- import { existsSync } from 'node:fs';
13
- import { extname, join } from 'node:path';
14
- import { readOhConfig } from './config.js';
11
+ import { execSync } from "node:child_process";
12
+ import { existsSync } from "node:fs";
13
+ import { extname, join } from "node:path";
14
+ import { readOhConfig } from "./config.js";
15
15
  const MAX_SUMMARY_CHARS = 500;
16
16
  const DEFAULT_TIMEOUT_MS = 10_000;
17
17
  // ── Auto-detection ──
@@ -20,43 +20,50 @@ export function autoDetectRules(projectRoot) {
20
20
  const root = projectRoot ?? process.cwd();
21
21
  const rules = [];
22
22
  // TypeScript
23
- if (existsSync(join(root, 'tsconfig.json'))) {
23
+ if (existsSync(join(root, "tsconfig.json"))) {
24
24
  rules.push({
25
- extensions: ['.ts', '.tsx'],
26
- lint: 'npx tsc --noEmit 2>&1 | head -20',
25
+ extensions: [".ts", ".tsx"],
26
+ lint: "npx tsc --noEmit 2>&1 | head -20",
27
27
  timeout: 15_000,
28
28
  });
29
29
  }
30
30
  // ESLint (JS/TS)
31
- const eslintConfigs = ['.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml', 'eslint.config.js', 'eslint.config.mjs'];
32
- if (eslintConfigs.some(f => existsSync(join(root, f)))) {
31
+ const eslintConfigs = [
32
+ ".eslintrc",
33
+ ".eslintrc.js",
34
+ ".eslintrc.json",
35
+ ".eslintrc.yml",
36
+ "eslint.config.js",
37
+ "eslint.config.mjs",
38
+ ];
39
+ if (eslintConfigs.some((f) => existsSync(join(root, f)))) {
33
40
  rules.push({
34
- extensions: ['.js', '.jsx', '.ts', '.tsx'],
35
- lint: 'npx eslint {file} --no-color 2>&1 | head -15',
41
+ extensions: [".js", ".jsx", ".ts", ".tsx"],
42
+ lint: "npx eslint {file} --no-color 2>&1 | head -15",
36
43
  timeout: 10_000,
37
44
  });
38
45
  }
39
46
  // Python — ruff (fast) or pylint
40
- if (existsSync(join(root, 'pyproject.toml')) || existsSync(join(root, 'setup.py'))) {
47
+ if (existsSync(join(root, "pyproject.toml")) || existsSync(join(root, "setup.py"))) {
41
48
  rules.push({
42
- extensions: ['.py'],
43
- lint: 'ruff check {file} 2>&1 | head -10',
49
+ extensions: [".py"],
50
+ lint: "ruff check {file} 2>&1 | head -10",
44
51
  timeout: 10_000,
45
52
  });
46
53
  }
47
54
  // Go
48
- if (existsSync(join(root, 'go.mod'))) {
55
+ if (existsSync(join(root, "go.mod"))) {
49
56
  rules.push({
50
- extensions: ['.go'],
51
- lint: 'go vet ./... 2>&1 | head -10',
57
+ extensions: [".go"],
58
+ lint: "go vet ./... 2>&1 | head -10",
52
59
  timeout: 15_000,
53
60
  });
54
61
  }
55
62
  // Rust
56
- if (existsSync(join(root, 'Cargo.toml'))) {
63
+ if (existsSync(join(root, "Cargo.toml"))) {
57
64
  rules.push({
58
- extensions: ['.rs'],
59
- lint: 'cargo check 2>&1 | tail -10',
65
+ extensions: [".rs"],
66
+ lint: "cargo check 2>&1 | tail -10",
60
67
  timeout: 30_000,
61
68
  });
62
69
  }
@@ -78,7 +85,7 @@ export function getVerificationConfig() {
78
85
  }
79
86
  _cachedConfig = {
80
87
  enabled: true,
81
- mode: v.mode ?? 'warn',
88
+ mode: v.mode ?? "warn",
82
89
  rules: v.rules ?? autoDetectRules(),
83
90
  };
84
91
  return _cachedConfig;
@@ -89,7 +96,7 @@ export function getVerificationConfig() {
89
96
  _cachedConfig = null;
90
97
  return null;
91
98
  }
92
- _cachedConfig = { enabled: true, mode: 'warn', rules: autoRules };
99
+ _cachedConfig = { enabled: true, mode: "warn", rules: autoRules };
93
100
  return _cachedConfig;
94
101
  }
95
102
  /** Clear cached config (for testing or after config changes) */
@@ -100,15 +107,15 @@ export function invalidateVerificationCache() {
100
107
  /** Extract file paths from tool input that were modified */
101
108
  export function extractFilePaths(toolName, toolInput) {
102
109
  switch (toolName) {
103
- case 'Write':
104
- case 'Edit':
110
+ case "Write":
111
+ case "Edit":
105
112
  return toolInput.file_path ? [String(toolInput.file_path)] : [];
106
- case 'MultiEdit':
113
+ case "MultiEdit":
107
114
  // MultiEdit has an array of edits, each with file_path
108
115
  if (Array.isArray(toolInput.edits)) {
109
116
  const paths = new Set();
110
117
  for (const edit of toolInput.edits) {
111
- if (edit && typeof edit === 'object' && 'file_path' in edit) {
118
+ if (edit && typeof edit === "object" && "file_path" in edit) {
112
119
  paths.add(String(edit.file_path));
113
120
  }
114
121
  }
@@ -123,7 +130,7 @@ export function extractFilePaths(toolName, toolInput) {
123
130
  /** Find the matching rule for a file extension */
124
131
  function findRule(filePath, rules) {
125
132
  const ext = extname(filePath).toLowerCase();
126
- return rules.find(r => r.extensions.includes(ext)) ?? null;
133
+ return rules.find((r) => r.extensions.includes(ext)) ?? null;
127
134
  }
128
135
  /**
129
136
  * Shell-escape a file path to prevent command injection.
@@ -131,7 +138,7 @@ function findRule(filePath, rules) {
131
138
  */
132
139
  function shellEscape(s) {
133
140
  // On Windows, use double quotes; on POSIX, use single quotes
134
- if (process.platform === 'win32') {
141
+ if (process.platform === "win32") {
135
142
  // Double-quote and escape internal double quotes and special chars
136
143
  return `"${s.replace(/"/g, '\\"')}"`;
137
144
  }
@@ -144,30 +151,30 @@ function shellEscape(s) {
144
151
  */
145
152
  export async function runVerificationForFiles(filePaths, config) {
146
153
  if (filePaths.length === 0)
147
- return { ran: false, passed: true, summary: '' };
154
+ return { ran: false, passed: true, summary: "" };
148
155
  if (filePaths.length === 1)
149
156
  return runVerification(filePaths[0], config);
150
157
  const results = [];
151
158
  for (const fp of filePaths) {
152
159
  results.push(await runVerification(fp, config));
153
160
  }
154
- const ran = results.some(r => r.ran);
155
- const passed = results.every(r => r.passed);
156
- const failures = results.filter(r => r.ran && !r.passed);
161
+ const ran = results.some((r) => r.ran);
162
+ const passed = results.every((r) => r.passed);
163
+ const failures = results.filter((r) => r.ran && !r.passed);
157
164
  if (!ran)
158
- return { ran: false, passed: true, summary: '' };
165
+ return { ran: false, passed: true, summary: "" };
159
166
  if (passed)
160
- return { ran: true, passed: true, summary: '' };
167
+ return { ran: true, passed: true, summary: "" };
161
168
  // Aggregate failure summaries (cap total to MAX_SUMMARY_CHARS)
162
- const summaryParts = failures.map(r => r.summary).filter(Boolean);
163
- const summary = summaryParts.join('\n---\n').slice(0, MAX_SUMMARY_CHARS);
169
+ const summaryParts = failures.map((r) => r.summary).filter(Boolean);
170
+ const summary = summaryParts.join("\n---\n").slice(0, MAX_SUMMARY_CHARS);
164
171
  return { ran: true, passed: false, summary };
165
172
  }
166
173
  /** Run verification for a single file. Returns result with concise summary. */
167
174
  export async function runVerification(filePath, config) {
168
175
  const rule = findRule(filePath, config.rules);
169
- if (!rule || !rule.lint) {
170
- return { ran: false, passed: true, summary: '' };
176
+ if (!rule?.lint) {
177
+ return { ran: false, passed: true, summary: "" };
171
178
  }
172
179
  const command = rule.lint.replace(/\{file\}/g, shellEscape(filePath));
173
180
  const timeout = rule.timeout ?? DEFAULT_TIMEOUT_MS;
@@ -175,21 +182,20 @@ export async function runVerification(filePath, config) {
175
182
  execSync(command, {
176
183
  timeout,
177
184
  cwd: process.cwd(),
178
- encoding: 'utf-8',
179
- stdio: ['pipe', 'pipe', 'pipe'],
185
+ encoding: "utf-8",
186
+ stdio: ["pipe", "pipe", "pipe"],
180
187
  windowsHide: true,
181
188
  });
182
189
  // Exit code 0 = passed
183
- return { ran: true, passed: true, summary: '' };
190
+ return { ran: true, passed: true, summary: "" };
184
191
  }
185
192
  catch (err) {
186
193
  // Timeout detection — check killed flag, signal, or error code
187
- const isTimeout = err.killed || err.signal === 'SIGTERM' || err.code === 'ETIMEDOUT'
188
- || (err.status === null && err.signal);
194
+ const isTimeout = err.killed || err.signal === "SIGTERM" || err.code === "ETIMEDOUT" || (err.status === null && err.signal);
189
195
  if (isTimeout) {
190
196
  return { ran: true, passed: false, summary: `Verification timed out after ${timeout / 1000}s` };
191
197
  }
192
- const output = String(err.stdout ?? err.stderr ?? err.message ?? 'Unknown error');
198
+ const output = String(err.stdout ?? err.stderr ?? err.message ?? "Unknown error");
193
199
  const summary = output.slice(0, MAX_SUMMARY_CHARS).trim();
194
200
  return { ran: true, passed: false, summary };
195
201
  }