@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.
- 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 +288 -132
- 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 +3 -3
- 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.js +15 -15
- 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 +25 -23
- 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 +39 -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 +114 -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 +1 -1
- package/dist/query/context-manager.js +5 -5
- package/dist/query/errors.js +1 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +42 -24
- package/dist/query/tools.js +15 -12
- package/dist/query/types.d.ts +3 -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 +311 -198
- package/dist/sdk/index.d.ts +5 -5
- package/dist/sdk/index.js +32 -26
- 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 +34 -32
- 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 +163 -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 +25 -39
- 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.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 +5 -5
- package/dist/tools/MemoryTool/index.js +28 -14
- 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.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
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Optional: POST to configurable endpoint on session end.
|
|
13
13
|
*/
|
|
14
14
|
export type TelemetryEvent = {
|
|
15
|
-
type:
|
|
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,
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { readOhConfig } from
|
|
18
|
-
const TELEMETRY_DIR = join(homedir(),
|
|
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)
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
90
|
-
.split(
|
|
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(
|
|
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),
|
|
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 ===
|
|
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 ===
|
|
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 {
|
|
122
|
+
catch {
|
|
123
|
+
/* skip malformed files */
|
|
124
|
+
}
|
|
121
125
|
}
|
|
122
126
|
return { totalSessions: files.length, totalEvents, toolUsage, errorCategories };
|
|
123
127
|
}
|
package/dist/harness/traces.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type TraceSpan = {
|
|
|
15
15
|
endTime: number;
|
|
16
16
|
durationMs: number;
|
|
17
17
|
attributes: Record<string, unknown>;
|
|
18
|
-
status:
|
|
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?:
|
|
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 */
|
package/dist/harness/traces.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Compatible with OpenTelemetry export format.
|
|
9
9
|
*/
|
|
10
|
-
import { appendFileSync, mkdirSync,
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
const TRACE_DIR = join(homedir(),
|
|
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 =
|
|
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 ===
|
|
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)
|
|
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,
|
|
102
|
-
.split(
|
|
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(
|
|
116
|
-
.map(f => f.replace(
|
|
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
|
|
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 ===
|
|
135
|
-
const pad =
|
|
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 ===
|
|
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(
|
|
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:
|
|
161
|
-
{ key:
|
|
163
|
+
{ key: "service.name", value: { stringValue: "openharness" } },
|
|
164
|
+
{ key: "session.id", value: { stringValue: sessionId } },
|
|
162
165
|
],
|
|
163
166
|
},
|
|
164
|
-
scopeSpans: [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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 ===
|
|
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
|
|
@@ -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
|
|
12
|
-
import { existsSync } from
|
|
13
|
-
import { extname, join } from
|
|
14
|
-
import { readOhConfig } from
|
|
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,
|
|
23
|
+
if (existsSync(join(root, "tsconfig.json"))) {
|
|
24
24
|
rules.push({
|
|
25
|
-
extensions: [
|
|
26
|
-
lint:
|
|
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 = [
|
|
32
|
-
|
|
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: [
|
|
35
|
-
lint:
|
|
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,
|
|
47
|
+
if (existsSync(join(root, "pyproject.toml")) || existsSync(join(root, "setup.py"))) {
|
|
41
48
|
rules.push({
|
|
42
|
-
extensions: [
|
|
43
|
-
lint:
|
|
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,
|
|
55
|
+
if (existsSync(join(root, "go.mod"))) {
|
|
49
56
|
rules.push({
|
|
50
|
-
extensions: [
|
|
51
|
-
lint:
|
|
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,
|
|
63
|
+
if (existsSync(join(root, "Cargo.toml"))) {
|
|
57
64
|
rules.push({
|
|
58
|
-
extensions: [
|
|
59
|
-
lint:
|
|
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 ??
|
|
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:
|
|
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
|
|
104
|
-
case
|
|
110
|
+
case "Write":
|
|
111
|
+
case "Edit":
|
|
105
112
|
return toolInput.file_path ? [String(toolInput.file_path)] : [];
|
|
106
|
-
case
|
|
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 ===
|
|
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 ===
|
|
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(
|
|
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
|
|
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:
|
|
179
|
-
stdio: [
|
|
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 ===
|
|
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 ??
|
|
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
|
}
|