pi-harness-runtime 1.1.49 → 1.1.71-3
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/CHANGELOG.md +30 -9
- package/README.md +119 -0
- package/harness/langchain/agents.d.ts +143 -0
- package/harness/langchain/agents.test.d.ts +2 -0
- package/harness/langchain/agents.test.ts +0 -1
- package/harness/langchain/agents.ts +196 -13
- package/harness/langchain/checkpointer.d.ts +48 -0
- package/harness/langchain/checkpointer.test.d.ts +8 -0
- package/harness/langchain/cron-cli.d.ts +12 -0
- package/harness/langchain/cron-cli.ts +169 -0
- package/harness/langchain/daemon.d.ts +161 -0
- package/harness/langchain/daemon.test.d.ts +11 -0
- package/harness/langchain/daemon.test.ts +62 -5
- package/harness/langchain/daemon.ts +231 -12
- package/harness/langchain/file-tools.d.ts +97 -0
- package/harness/langchain/file-tools.ts +29 -11
- package/harness/langchain/graph.d.ts +706 -0
- package/harness/langchain/graph.ts +184 -26
- package/harness/langchain/model-router.d.ts +63 -0
- package/harness/langchain/model-router.test.d.ts +2 -0
- package/harness/langchain/model-router.test.ts +191 -0
- package/harness/langchain/model-router.ts +188 -0
- package/harness/langchain/models.d.ts +72 -0
- package/harness/langchain/models.ts +111 -49
- package/harness/langchain/ping-pong-middleware.d.ts +28 -0
- package/harness/langchain/ping-pong-middleware.test.d.ts +2 -0
- package/harness/langchain/ping-pong-middleware.test.ts +69 -0
- package/harness/langchain/ping-pong-middleware.ts +204 -0
- package/harness/langchain/run.d.ts +15 -0
- package/harness/langchain/run.ts +15 -3
- package/harness/langchain/scoreboard.test.d.ts +13 -0
- package/harness/langchain/scoreboard.test.ts +28 -5
- package/harness/langchain/status-line.d.ts +91 -0
- package/harness/langchain/status-line.test.d.ts +8 -0
- package/harness/langchain/surge.d.ts +103 -0
- package/harness/langchain/surge.test.d.ts +10 -0
- package/harness/langchain/surge.test.ts +179 -0
- package/harness/langchain/surge.ts +188 -19
- package/harness/langchain/widget.d.ts +105 -0
- package/harness/langchain/widget.test.d.ts +8 -0
- package/harness/ssh-detach-interceptor.test.ts +263 -0
- package/harness/ssh-detach-interceptor.ts +438 -0
- package/index.ts +212 -47
- package/package.json +4 -2
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/dist/src/index.d.ts +2 -0
- package/packages/intent-analyzer/dist/src/index.d.ts.map +1 -1
- package/packages/intent-analyzer/dist/src/index.js +1 -0
- package/packages/intent-analyzer/dist/src/index.js.map +1 -1
- package/packages/intent-analyzer/dist/src/ping-pong.d.ts +56 -0
- package/packages/intent-analyzer/dist/src/ping-pong.d.ts.map +1 -0
- package/packages/intent-analyzer/dist/src/ping-pong.js +286 -0
- package/packages/intent-analyzer/dist/src/ping-pong.js.map +1 -0
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/intent-analyzer/src/index.ts +13 -0
- package/packages/intent-analyzer/src/ping-pong.ts +387 -0
- package/packages/intent-analyzer/test/ping-pong.test.ts +96 -0
- package/packages/jev-judge/README.md +234 -0
- package/packages/jev-judge/dist/decision-engine.d.ts +100 -0
- package/packages/jev-judge/dist/decision-engine.d.ts.map +1 -0
- package/packages/jev-judge/dist/decision-engine.js +151 -0
- package/packages/jev-judge/dist/decision-engine.js.map +1 -0
- package/packages/jev-judge/dist/e2e-judge.d.ts +61 -0
- package/packages/jev-judge/dist/e2e-judge.d.ts.map +1 -0
- package/packages/jev-judge/dist/e2e-judge.js +268 -0
- package/packages/jev-judge/dist/e2e-judge.js.map +1 -0
- package/packages/jev-judge/dist/flaky-detector.d.ts +34 -0
- package/packages/jev-judge/dist/flaky-detector.d.ts.map +1 -0
- package/packages/jev-judge/dist/flaky-detector.js +154 -0
- package/packages/jev-judge/dist/flaky-detector.js.map +1 -0
- package/packages/jev-judge/dist/index.d.ts +77 -0
- package/packages/jev-judge/dist/index.d.ts.map +1 -0
- package/packages/jev-judge/dist/index.js +271 -0
- package/packages/jev-judge/dist/index.js.map +1 -0
- package/packages/jev-judge/dist/types.d.ts +97 -0
- package/packages/jev-judge/dist/types.d.ts.map +1 -0
- package/packages/jev-judge/dist/types.js +8 -0
- package/packages/jev-judge/dist/types.js.map +1 -0
- package/packages/jev-judge/package.json +46 -0
- package/packages/jev-judge/src/decision-engine.ts +231 -0
- package/packages/jev-judge/src/e2e-judge.ts +364 -0
- package/packages/jev-judge/src/flaky-detector.ts +190 -0
- package/packages/jev-judge/src/index.ts +345 -0
- package/packages/jev-judge/src/types.ts +131 -0
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/README.md +71 -0
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/todo-bd-sync/src/extension.ts +16 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/dist/src/approved-patterns.d.ts +51 -0
- package/packages/trajectory/dist/src/approved-patterns.d.ts.map +1 -0
- package/packages/trajectory/dist/src/approved-patterns.js +134 -0
- package/packages/trajectory/dist/src/approved-patterns.js.map +1 -0
- package/packages/trajectory/dist/src/index.d.ts +13 -0
- package/packages/trajectory/dist/src/index.d.ts.map +1 -0
- package/packages/trajectory/dist/src/index.js +12 -0
- package/packages/trajectory/dist/src/index.js.map +1 -0
- package/packages/trajectory/dist/src/store.d.ts +69 -0
- package/packages/trajectory/dist/src/store.d.ts.map +1 -0
- package/packages/trajectory/dist/src/store.js +253 -0
- package/packages/trajectory/dist/src/store.js.map +1 -0
- package/packages/trajectory/dist/src/types.d.ts +73 -0
- package/packages/trajectory/dist/src/types.d.ts.map +1 -0
- package/packages/trajectory/dist/src/types.js +9 -0
- package/packages/trajectory/dist/src/types.js.map +1 -0
- package/packages/trajectory/dist/test/store.test.d.ts +2 -0
- package/packages/trajectory/dist/test/store.test.d.ts.map +1 -0
- package/packages/trajectory/dist/test/store.test.js +228 -0
- package/packages/trajectory/dist/test/store.test.js.map +1 -0
- package/packages/trajectory/package.json +29 -0
- package/packages/trajectory/src/approved-patterns.ts +180 -0
- package/packages/trajectory/src/index.ts +26 -0
- package/packages/trajectory/src/store.ts +285 -0
- package/packages/trajectory/src/types.ts +84 -0
- package/packages/trajectory/test/store.test.d.ts +2 -0
- package/packages/trajectory/test/store.test.ts +250 -0
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/packages/write-review/src/blackboard.ts +38 -5
- package/packages/write-review/src/types.ts +13 -0
- package/skills/harness-runtime/SKILL.md +67 -4
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flaky Detector - Specialized module for detecting flaky tests
|
|
3
|
+
*
|
|
4
|
+
* Uses Jev to analyze test failures and determine if they're flaky.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { JevJudge } from "./index.js";
|
|
8
|
+
import type { JevJudgeConfig, E2ETestResult, E2EFlakyAnalysis } from "./types.js";
|
|
9
|
+
|
|
10
|
+
// Known flaky patterns
|
|
11
|
+
const FLAKY_PATTERNS = {
|
|
12
|
+
timeout: ["timeout", "timed out", "ETIMEDOUT", "TimeoutError"],
|
|
13
|
+
network: ["ECONNREFUSED", "ENOTFOUND", "network", "fetch failed"],
|
|
14
|
+
race_condition: [
|
|
15
|
+
"race condition",
|
|
16
|
+
"concurrent",
|
|
17
|
+
"simultaneous",
|
|
18
|
+
"out of order",
|
|
19
|
+
],
|
|
20
|
+
ui_state: [
|
|
21
|
+
"element not visible",
|
|
22
|
+
"element not interactable",
|
|
23
|
+
"stale element",
|
|
24
|
+
"not attached",
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Flaky Detector - Specialized for detecting flaky test failures
|
|
30
|
+
*/
|
|
31
|
+
export class FlakyDetector {
|
|
32
|
+
private judge: JevJudge;
|
|
33
|
+
private threshold: number;
|
|
34
|
+
|
|
35
|
+
constructor(config: JevJudgeConfig, threshold = 0.7) {
|
|
36
|
+
this.judge = new JevJudge(config);
|
|
37
|
+
this.threshold = threshold;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Quick pattern-based flaky detection (without Jev)
|
|
42
|
+
* Used for fast, synchronous checks
|
|
43
|
+
*/
|
|
44
|
+
detectPattern(errorMessage?: string): {
|
|
45
|
+
isFlaky: boolean;
|
|
46
|
+
pattern: string | null;
|
|
47
|
+
confidence: "high" | "medium" | "low";
|
|
48
|
+
} {
|
|
49
|
+
if (!errorMessage) {
|
|
50
|
+
return {
|
|
51
|
+
isFlaky: false,
|
|
52
|
+
pattern: null,
|
|
53
|
+
confidence: "low",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const lowerError = errorMessage.toLowerCase();
|
|
58
|
+
|
|
59
|
+
for (const [pattern, keywords] of Object.entries(FLAKY_PATTERNS)) {
|
|
60
|
+
const matches = keywords.filter((kw) => lowerError.includes(kw.toLowerCase()));
|
|
61
|
+
|
|
62
|
+
if (matches.length > 0) {
|
|
63
|
+
return {
|
|
64
|
+
isFlaky: true,
|
|
65
|
+
pattern,
|
|
66
|
+
confidence: matches.length >= 2 ? "high" : "medium",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
isFlaky: false,
|
|
73
|
+
pattern: null,
|
|
74
|
+
confidence: "low",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* AI-powered flaky detection with Jev
|
|
80
|
+
*/
|
|
81
|
+
async detect(testResult: E2ETestResult): Promise<E2EFlakyAnalysis> {
|
|
82
|
+
// First, quick pattern check
|
|
83
|
+
const patternResult = this.detectPattern(testResult.error);
|
|
84
|
+
|
|
85
|
+
// If pattern detection is confident, return early
|
|
86
|
+
if (patternResult.confidence === "high") {
|
|
87
|
+
return {
|
|
88
|
+
isFlaky: patternResult.isFlaky,
|
|
89
|
+
confidence: patternResult.confidence,
|
|
90
|
+
probability: patternResult.isFlaky ? 0.95 : 0.05,
|
|
91
|
+
pattern: patternResult.pattern ?? undefined,
|
|
92
|
+
recommendation: patternResult.isFlaky ? "skip" : "investigate",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Use Jev for nuanced analysis
|
|
97
|
+
const state = this.buildState(testResult);
|
|
98
|
+
|
|
99
|
+
const result = await this.judge.evaluate(state, {
|
|
100
|
+
isFlaky: {
|
|
101
|
+
type: "noul",
|
|
102
|
+
instructions:
|
|
103
|
+
"Does this failure match a known flaky pattern? Consider: timeout, network issue, race condition, or intermittent UI state.",
|
|
104
|
+
},
|
|
105
|
+
patternType: {
|
|
106
|
+
type: "choice",
|
|
107
|
+
instructions: "What is the most likely cause?",
|
|
108
|
+
options: [
|
|
109
|
+
"timeout",
|
|
110
|
+
"network_issue",
|
|
111
|
+
"race_condition",
|
|
112
|
+
"ui_intermittent",
|
|
113
|
+
"resource_contention",
|
|
114
|
+
"real_bug",
|
|
115
|
+
"unknown",
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
confidence: {
|
|
119
|
+
type: "noul",
|
|
120
|
+
instructions:
|
|
121
|
+
"Is this assessment based on clear evidence and likely to be correct?",
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const isFlakyDecision = result.decisions.isFlaky;
|
|
126
|
+
const probability = isFlakyDecision.probability ?? 0.5;
|
|
127
|
+
|
|
128
|
+
const patternDecision = result.decisions.patternType as {
|
|
129
|
+
response: { choice: string };
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
isFlaky: probability >= this.threshold,
|
|
134
|
+
confidence: isFlakyDecision.confidence,
|
|
135
|
+
probability,
|
|
136
|
+
pattern: patternDecision?.response?.choice,
|
|
137
|
+
recommendation: this.getRecommendation(probability),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Batch detect flaky tests
|
|
143
|
+
*/
|
|
144
|
+
async batchDetect(
|
|
145
|
+
testResults: E2ETestResult[]
|
|
146
|
+
): Promise<Map<string, E2EFlakyAnalysis>> {
|
|
147
|
+
const results = new Map<string, E2EFlakyAnalysis>();
|
|
148
|
+
|
|
149
|
+
// Process in batches
|
|
150
|
+
const BATCH_SIZE = 5;
|
|
151
|
+
for (let i = 0; i < testResults.length; i += BATCH_SIZE) {
|
|
152
|
+
const batch = testResults.slice(i, i + BATCH_SIZE);
|
|
153
|
+
|
|
154
|
+
const batchResults = await Promise.all(
|
|
155
|
+
batch.map((test) => this.detect(test))
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
for (let j = 0; j < batch.length; j++) {
|
|
159
|
+
results.set(batch[j].name, batchResults[j]);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private buildState(test: E2ETestResult): string {
|
|
167
|
+
const parts: string[] = [`Test: ${test.name}`, `Status: ${test.status}`];
|
|
168
|
+
|
|
169
|
+
if (test.error) {
|
|
170
|
+
parts.push(`Error: ${test.error}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (test.duration !== undefined) {
|
|
174
|
+
parts.push(`Duration: ${test.duration}ms`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (test.retries !== undefined) {
|
|
178
|
+
parts.push(`Previous retries: ${test.retries}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return parts.join("\n");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private getRecommendation(probability: number): "retry" | "skip" | "block" | "investigate" {
|
|
185
|
+
if (probability >= 0.9) return "skip";
|
|
186
|
+
if (probability >= 0.7) return "retry";
|
|
187
|
+
if (probability >= 0.4) return "investigate";
|
|
188
|
+
return "block";
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jev Judge - Main API
|
|
3
|
+
*
|
|
4
|
+
* Structured decision model for agent routing, E2E testing, and automated decisions.
|
|
5
|
+
* Uses TypeSafe Jev via OpenRouter for fast, calibrated, hallucination-free decisions.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { JevJudge } from "@pi-harness/jev-judge";
|
|
10
|
+
*
|
|
11
|
+
* const judge = new JevJudge({ apiKey: process.env.OPENROUTER_API_KEY! });
|
|
12
|
+
*
|
|
13
|
+
* // Ask multiple questions at once
|
|
14
|
+
* const result = await judge.evaluate(
|
|
15
|
+
* "The deploy failed with exit code 1",
|
|
16
|
+
* {
|
|
17
|
+
* isUrgent: { type: "noul", instructions: "Does this require immediate attention?" },
|
|
18
|
+
* severity: {
|
|
19
|
+
* type: "choice",
|
|
20
|
+
* instructions: "What is the severity level?",
|
|
21
|
+
* options: ["critical", "high", "medium", "low"]
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* );
|
|
25
|
+
*
|
|
26
|
+
* console.log(result.decisions.isUrgent.noul); // 0.92
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type {
|
|
31
|
+
JevJudgeConfig,
|
|
32
|
+
JevQuestion,
|
|
33
|
+
JevQuestions,
|
|
34
|
+
JevResponse,
|
|
35
|
+
JevDecision,
|
|
36
|
+
JevEvaluationResult,
|
|
37
|
+
JevNoulResponse,
|
|
38
|
+
JevChoiceResponse,
|
|
39
|
+
JevScoreResponse,
|
|
40
|
+
ThresholdDecision,
|
|
41
|
+
} from "./types.js";
|
|
42
|
+
|
|
43
|
+
// OpenAI SDK (OpenRouter is OpenAI-compatible)
|
|
44
|
+
// @ts-ignore - openai is installed at workspace root
|
|
45
|
+
import OpenAI from "openai";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default configuration
|
|
49
|
+
*/
|
|
50
|
+
const DEFAULT_CONFIG = {
|
|
51
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
52
|
+
model: "typesafe/jev-1.13",
|
|
53
|
+
defaultThreshold: 0.7,
|
|
54
|
+
timeoutMs: 30000,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* JevJudge - Main class for making structured decisions with Jev
|
|
59
|
+
*/
|
|
60
|
+
export class JevJudge {
|
|
61
|
+
private client: OpenAI;
|
|
62
|
+
private config: Required<JevJudgeConfig>;
|
|
63
|
+
|
|
64
|
+
constructor(config: JevJudgeConfig) {
|
|
65
|
+
this.config = {
|
|
66
|
+
...DEFAULT_CONFIG,
|
|
67
|
+
...config,
|
|
68
|
+
} as Required<JevJudgeConfig>;
|
|
69
|
+
|
|
70
|
+
this.client = new OpenAI({
|
|
71
|
+
apiKey: this.config.apiKey,
|
|
72
|
+
baseURL: this.config.baseUrl,
|
|
73
|
+
timeout: this.config.timeoutMs,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Evaluate state against questions
|
|
79
|
+
*
|
|
80
|
+
* @param state - The context/situation to evaluate (text or structured)
|
|
81
|
+
* @param questions - Map of question_id -> question definition
|
|
82
|
+
* @returns Evaluation result with decisions
|
|
83
|
+
*/
|
|
84
|
+
async evaluate<T extends string>(
|
|
85
|
+
state: string | object,
|
|
86
|
+
questions: JevQuestions<T>
|
|
87
|
+
): Promise<JevEvaluationResult<T>> {
|
|
88
|
+
const startTime = Date.now();
|
|
89
|
+
|
|
90
|
+
// Convert state to string if object
|
|
91
|
+
const stateStr = typeof state === "string" ? state : JSON.stringify(state);
|
|
92
|
+
|
|
93
|
+
// Build questions array for API
|
|
94
|
+
const questionsArray = Object.entries(questions).map(([id, q]) => {
|
|
95
|
+
const questionObj = q as JevQuestion;
|
|
96
|
+
const entry: Record<string, unknown> = { id };
|
|
97
|
+
if (questionObj.type === "noul") {
|
|
98
|
+
entry.type = "noul";
|
|
99
|
+
entry.instructions = questionObj.instructions;
|
|
100
|
+
} else if (questionObj.type === "choice") {
|
|
101
|
+
entry.type = "choice";
|
|
102
|
+
entry.instructions = questionObj.instructions;
|
|
103
|
+
entry.options = questionObj.options;
|
|
104
|
+
} else if (questionObj.type === "score") {
|
|
105
|
+
entry.type = "score";
|
|
106
|
+
entry.instructions = questionObj.instructions;
|
|
107
|
+
if (questionObj.min !== undefined) entry.min = questionObj.min;
|
|
108
|
+
if (questionObj.max !== undefined) entry.max = questionObj.max;
|
|
109
|
+
}
|
|
110
|
+
return entry;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Call Jev via OpenRouter
|
|
114
|
+
const response = await this.client.chat.completions.create({
|
|
115
|
+
model: this.config.model,
|
|
116
|
+
messages: [
|
|
117
|
+
{
|
|
118
|
+
role: "system",
|
|
119
|
+
content: `You are a decision engine. Evaluate the following state and answer the questions.
|
|
120
|
+
Return your answers in the exact JSON format specified.`,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
role: "user",
|
|
124
|
+
content: `State: ${stateStr}
|
|
125
|
+
|
|
126
|
+
Questions:
|
|
127
|
+
${JSON.stringify(questionsArray, null, 2)}
|
|
128
|
+
|
|
129
|
+
Respond with a JSON object mapping each question ID to its answer.`,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
response_format: {
|
|
133
|
+
type: "json_object",
|
|
134
|
+
schema: this.buildResponseSchema(questions),
|
|
135
|
+
},
|
|
136
|
+
temperature: 0.1, // Low temperature for consistent decisions
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const latencyMs = Date.now() - startTime;
|
|
140
|
+
|
|
141
|
+
// Parse response
|
|
142
|
+
const content = response.choices[0]?.message?.content;
|
|
143
|
+
if (!content) {
|
|
144
|
+
throw new Error("No response from Jev");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let parsed: Record<string, unknown>;
|
|
148
|
+
try {
|
|
149
|
+
parsed = JSON.parse(content);
|
|
150
|
+
} catch {
|
|
151
|
+
throw new Error("Invalid JSON response from Jev");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Build decisions map
|
|
155
|
+
const decisions = {} as Record<T, JevDecision<T>>;
|
|
156
|
+
|
|
157
|
+
for (const [questionId, question] of Object.entries(questions)) {
|
|
158
|
+
const questionData = question as JevQuestion;
|
|
159
|
+
const responseData = (parsed as Record<string, unknown>)[questionId] as
|
|
160
|
+
| JevResponse
|
|
161
|
+
| undefined;
|
|
162
|
+
|
|
163
|
+
if (!responseData) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const decision = this.buildDecision(
|
|
168
|
+
questionId,
|
|
169
|
+
questionData,
|
|
170
|
+
responseData
|
|
171
|
+
);
|
|
172
|
+
(decisions as Record<string, JevDecision>)[questionId] = decision;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
decisions,
|
|
177
|
+
state: stateStr,
|
|
178
|
+
latencyMs,
|
|
179
|
+
model: this.config.model,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Quick noul (yes/no) evaluation
|
|
185
|
+
*/
|
|
186
|
+
async noul(
|
|
187
|
+
state: string | object,
|
|
188
|
+
question: string
|
|
189
|
+
): Promise<{ noul: number; confidence: "high" | "medium" | "low" }> {
|
|
190
|
+
const result = await this.evaluate(state, {
|
|
191
|
+
answer: { type: "noul", instructions: question },
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const decision = result.decisions.answer as JevDecision<"answer">;
|
|
195
|
+
return {
|
|
196
|
+
noul: (decision.response as JevNoulResponse).noul,
|
|
197
|
+
confidence: decision.confidence,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Quick choice evaluation
|
|
203
|
+
*/
|
|
204
|
+
async choice<T extends string>(
|
|
205
|
+
state: string | object,
|
|
206
|
+
question: string,
|
|
207
|
+
options: T[]
|
|
208
|
+
): Promise<{ choice: T; confidence: number }> {
|
|
209
|
+
const result = await this.evaluate(state, {
|
|
210
|
+
answer: { type: "choice", instructions: question, options },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const decision = result.decisions.answer as JevDecision<"answer">;
|
|
214
|
+
return {
|
|
215
|
+
choice: (decision.response as JevChoiceResponse<T>).choice,
|
|
216
|
+
confidence: (decision.response as JevChoiceResponse).confidence,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Make threshold-based decision
|
|
222
|
+
*/
|
|
223
|
+
thresholdDecision(
|
|
224
|
+
probability: number,
|
|
225
|
+
threshold: number = this.config.defaultThreshold
|
|
226
|
+
): ThresholdDecision {
|
|
227
|
+
const confidence = this.getConfidence(probability, threshold);
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
shouldAct: probability >= threshold,
|
|
231
|
+
probability,
|
|
232
|
+
confidence,
|
|
233
|
+
action: probability >= 0.9 ? "proceed" : probability >= 0.5 ? "review" : "block",
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Batch evaluate multiple states
|
|
239
|
+
*/
|
|
240
|
+
async batchEvaluate<T extends string>(
|
|
241
|
+
states: (string | object)[],
|
|
242
|
+
questions: JevQuestions<T>,
|
|
243
|
+
options: { concurrency?: number } = {}
|
|
244
|
+
): Promise<JevEvaluationResult<T>[]> {
|
|
245
|
+
const { concurrency = 3 } = options;
|
|
246
|
+
|
|
247
|
+
const results: JevEvaluationResult<T>[] = [];
|
|
248
|
+
const batches: (string | object)[] = [];
|
|
249
|
+
|
|
250
|
+
// Split into batches
|
|
251
|
+
for (let i = 0; i < states.length; i += concurrency) {
|
|
252
|
+
batches.push(...states.slice(i, i + concurrency));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Process batches
|
|
256
|
+
for (const batch of batches) {
|
|
257
|
+
const result = await this.evaluate(batch, questions);
|
|
258
|
+
results.push(result);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return results;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Private helpers
|
|
265
|
+
|
|
266
|
+
private buildResponseSchema(questions: JevQuestions): Record<string, unknown> {
|
|
267
|
+
const schema: Record<string, unknown> = {};
|
|
268
|
+
|
|
269
|
+
for (const [id, question] of Object.entries(questions)) {
|
|
270
|
+
switch ((question as JevQuestion).type) {
|
|
271
|
+
case "noul":
|
|
272
|
+
schema[id] = { type: "object", properties: {
|
|
273
|
+
noul: { type: "number", minimum: 0, maximum: 1 }
|
|
274
|
+
}, required: ["noul"] };
|
|
275
|
+
break;
|
|
276
|
+
case "choice":
|
|
277
|
+
schema[id] = { type: "object", properties: {
|
|
278
|
+
choice: { type: "string", enum: (question as { options: string[] }).options },
|
|
279
|
+
confidence: { type: "number", minimum: 0, maximum: 1 }
|
|
280
|
+
}, required: ["choice"] };
|
|
281
|
+
break;
|
|
282
|
+
case "score":
|
|
283
|
+
schema[id] = { type: "object", properties: {
|
|
284
|
+
score: { type: "number" },
|
|
285
|
+
confidence: { type: "number", minimum: 0, maximum: 1 }
|
|
286
|
+
}, required: ["score"] };
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return { type: "object", properties: schema, additionalProperties: false };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private buildDecision<T extends string>(
|
|
295
|
+
questionId: T,
|
|
296
|
+
question: JevQuestion,
|
|
297
|
+
response: JevResponse
|
|
298
|
+
): JevDecision<T> {
|
|
299
|
+
let probability: number;
|
|
300
|
+
let confidence: "high" | "medium" | "low";
|
|
301
|
+
|
|
302
|
+
switch (response.type) {
|
|
303
|
+
case "noul":
|
|
304
|
+
probability = response.noul;
|
|
305
|
+
break;
|
|
306
|
+
case "choice":
|
|
307
|
+
probability = response.confidence;
|
|
308
|
+
break;
|
|
309
|
+
case "score":
|
|
310
|
+
// Normalize score to 0-1
|
|
311
|
+
const max = question.type === "score" ? (question.max ?? 100) : 100;
|
|
312
|
+
const min = question.type === "score" ? (question.min ?? 0) : 0;
|
|
313
|
+
probability = (response.score - min) / (max - min);
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
confidence = this.getConfidence(probability, this.config.defaultThreshold);
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
questionId,
|
|
321
|
+
question,
|
|
322
|
+
response,
|
|
323
|
+
probability,
|
|
324
|
+
confidence,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private getConfidence(
|
|
329
|
+
probability: number,
|
|
330
|
+
threshold: number
|
|
331
|
+
): "high" | "medium" | "low" {
|
|
332
|
+
const distance = Math.abs(probability - threshold);
|
|
333
|
+
if (distance > 0.3) return "high";
|
|
334
|
+
if (distance > 0.15) return "medium";
|
|
335
|
+
return "low";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Export types
|
|
340
|
+
export * from "./types.js";
|
|
341
|
+
|
|
342
|
+
// Export submodules
|
|
343
|
+
export { E2EJudge } from "./e2e-judge.js";
|
|
344
|
+
export { FlakyDetector } from "./flaky-detector.js";
|
|
345
|
+
export { DecisionEngine } from "./decision-engine.js";
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jev Judge - Type definitions
|
|
3
|
+
*
|
|
4
|
+
* Jev is a System One model from TypeSafe AI that makes fast, structured decisions
|
|
5
|
+
* with calibrated probabilities. Available via OpenRouter: typesafe/jev-1.13
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Question types supported by Jev
|
|
9
|
+
export type JevQuestionType = "noul" | "choice" | "score";
|
|
10
|
+
|
|
11
|
+
// Noul: Yes/No question (returns probability 0-1)
|
|
12
|
+
export interface JevNoulQuestion {
|
|
13
|
+
type: "noul";
|
|
14
|
+
instructions: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Choice: Select from options (returns selected option)
|
|
18
|
+
export interface JevChoiceQuestion<T extends string = string> {
|
|
19
|
+
type: "choice";
|
|
20
|
+
instructions: string;
|
|
21
|
+
options: T[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Score: Rate something 0-100
|
|
25
|
+
export interface JevScoreQuestion {
|
|
26
|
+
type: "score";
|
|
27
|
+
instructions: string;
|
|
28
|
+
min?: number;
|
|
29
|
+
max?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Any question type
|
|
33
|
+
export type JevQuestion = JevNoulQuestion | JevChoiceQuestion | JevScoreQuestion;
|
|
34
|
+
|
|
35
|
+
// Questions map (question_id -> question)
|
|
36
|
+
export type JevQuestions<T extends string = string> = Partial<
|
|
37
|
+
Record<T, JevQuestion>
|
|
38
|
+
>;
|
|
39
|
+
|
|
40
|
+
// Response types
|
|
41
|
+
export interface JevNoulResponse {
|
|
42
|
+
type: "noul";
|
|
43
|
+
noul: number; // 0-1 probability
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface JevChoiceResponse<T extends string = string> {
|
|
47
|
+
type: "choice";
|
|
48
|
+
choice: T;
|
|
49
|
+
confidence: number; // 0-1
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface JevScoreResponse {
|
|
53
|
+
type: "score";
|
|
54
|
+
score: number;
|
|
55
|
+
confidence: number; // 0-1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Any response type
|
|
59
|
+
export type JevResponse = JevNoulResponse | JevChoiceResponse | JevScoreResponse;
|
|
60
|
+
|
|
61
|
+
// Response map
|
|
62
|
+
export type JevResponses<T extends string = string> = Partial<Record<T, JevResponse>>;
|
|
63
|
+
|
|
64
|
+
// Decision result with metadata
|
|
65
|
+
export interface JevDecision<T extends string = string> {
|
|
66
|
+
questionId: T;
|
|
67
|
+
question: JevQuestion;
|
|
68
|
+
response: JevResponse;
|
|
69
|
+
probability?: number; // Extracted for easy access (noul = noul value, choice = confidence)
|
|
70
|
+
confidence: "high" | "medium" | "low";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Full evaluation result
|
|
74
|
+
export interface JevEvaluationResult<T extends string = string> {
|
|
75
|
+
decisions: Record<T, JevDecision<T>>;
|
|
76
|
+
state: string;
|
|
77
|
+
latencyMs: number;
|
|
78
|
+
model: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Threshold-based action decision
|
|
82
|
+
export interface ThresholdDecision {
|
|
83
|
+
shouldAct: boolean;
|
|
84
|
+
probability: number;
|
|
85
|
+
confidence: "high" | "medium" | "low";
|
|
86
|
+
action: "proceed" | "review" | "block";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Config
|
|
90
|
+
export interface JevJudgeConfig {
|
|
91
|
+
apiKey: string;
|
|
92
|
+
baseUrl?: string; // Defaults to OpenRouter
|
|
93
|
+
model?: string; // Defaults to typesafe/jev-1.13
|
|
94
|
+
defaultThreshold?: number; // Default 0.7
|
|
95
|
+
timeoutMs?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// E2E Test Judge types
|
|
99
|
+
export interface E2ETestResult {
|
|
100
|
+
name: string;
|
|
101
|
+
status: "passed" | "failed" | "skipped" | "pending";
|
|
102
|
+
duration?: number; // ms
|
|
103
|
+
error?: string;
|
|
104
|
+
screenshot?: string;
|
|
105
|
+
consoleLogs?: string[];
|
|
106
|
+
networkLogs?: string[];
|
|
107
|
+
retries?: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface E2EFlakyAnalysis {
|
|
111
|
+
isFlaky: boolean;
|
|
112
|
+
confidence: "high" | "medium" | "low";
|
|
113
|
+
probability: number;
|
|
114
|
+
pattern?: string;
|
|
115
|
+
recommendation: "retry" | "skip" | "block" | "investigate";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface E2EBuildDecision {
|
|
119
|
+
shouldBlock: boolean;
|
|
120
|
+
probability: number;
|
|
121
|
+
confidence: "high" | "medium" | "low";
|
|
122
|
+
reason: string;
|
|
123
|
+
affectedTests: string[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface E2ERetryDecision {
|
|
127
|
+
shouldRetry: boolean;
|
|
128
|
+
maxAttempts: number;
|
|
129
|
+
probability: number;
|
|
130
|
+
delayMs?: number;
|
|
131
|
+
}
|