@zibby/core 0.1.21 → 0.1.22
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/dist/agents/base.js +17 -0
- package/dist/backend-client.js +1 -0
- package/dist/constants/tool-names.js +1 -0
- package/dist/constants/zibby-scratch.js +1 -0
- package/dist/constants.js +1 -0
- package/dist/enrichment/base.js +1 -0
- package/dist/enrichment/enrichers/accessibility-enricher.js +1 -0
- package/dist/enrichment/enrichers/dom-enricher.js +1 -0
- package/dist/enrichment/enrichers/page-state-enricher.js +1 -0
- package/dist/enrichment/enrichers/position-enricher.js +1 -0
- package/dist/enrichment/index.js +1 -0
- package/dist/enrichment/mcp-integration.js +1 -0
- package/dist/enrichment/mcp-ref-enricher.js +1 -0
- package/dist/enrichment/pipeline.js +3 -0
- package/dist/enrichment/trace-text-enricher.js +1 -0
- package/dist/framework/agents/assistant-strategy.js +5 -0
- package/dist/framework/agents/base.js +1 -0
- package/dist/framework/agents/claude-strategy.js +4 -0
- package/dist/framework/agents/codex-strategy.js +4 -0
- package/dist/framework/agents/cursor-strategy.js +32 -0
- package/dist/framework/agents/gemini-strategy.js +11 -0
- package/dist/framework/agents/index.js +13 -0
- package/dist/framework/agents/middleware/assistant-round-pipeline.js +3 -0
- package/dist/framework/agents/providers/base.js +1 -0
- package/dist/framework/agents/providers/index.js +1 -0
- package/dist/framework/agents/providers/openai-transport.js +2 -0
- package/dist/framework/agents/providers/openai.js +1 -0
- package/dist/framework/agents/providers/transport-base.js +1 -0
- package/dist/framework/agents/utils/auth-resolver.js +1 -0
- package/dist/framework/agents/utils/cursor-output-formatter.js +1 -0
- package/dist/framework/agents/utils/openai-proxy-formatter.js +9 -0
- package/dist/framework/agents/utils/payload-budget.js +3 -0
- package/dist/framework/agents/utils/structured-output-formatter.js +21 -0
- package/dist/framework/code-generator.js +10 -0
- package/dist/framework/constants.js +1 -0
- package/dist/framework/context-loader.js +5 -0
- package/dist/framework/function-bridge.js +2 -0
- package/dist/framework/function-skill-registry.js +1 -0
- package/dist/framework/graph-compiler.js +1 -0
- package/dist/framework/graph.js +5 -0
- package/dist/framework/index.js +1 -0
- package/dist/framework/mcp-client.js +2 -0
- package/dist/framework/node-registry.js +9 -0
- package/dist/framework/node.js +5 -0
- package/dist/framework/output-parser.js +3 -0
- package/dist/framework/skill-registry.js +1 -0
- package/dist/framework/state-utils.js +1 -0
- package/dist/framework/state.js +1 -0
- package/dist/framework/tool-resolver.js +1 -0
- package/dist/index.js +8 -0
- package/dist/runtime/generation/base.js +1 -0
- package/dist/runtime/generation/index.js +3 -0
- package/dist/runtime/generation/mcp-ref-strategy.js +41 -0
- package/dist/runtime/generation/stable-id-strategy.js +16 -0
- package/dist/runtime/stable-id-runtime.js +1 -0
- package/dist/runtime/verification/base.js +1 -0
- package/dist/runtime/verification/index.js +3 -0
- package/dist/runtime/verification/playwright-json-strategy.js +1 -0
- package/dist/runtime/zibby-runtime.js +1 -0
- package/dist/sync/index.js +1 -0
- package/dist/sync/uploader.js +1 -0
- package/dist/tools/run-playwright-test.js +5 -0
- package/dist/utils/adf-converter.js +7 -0
- package/dist/utils/ast-utils.js +1 -0
- package/dist/utils/ci-setup.js +5 -0
- package/dist/utils/cursor-mcp-isolated-home.js +1 -0
- package/dist/utils/cursor-utils.js +18 -0
- package/dist/utils/live-frame-discovery.js +1 -0
- package/dist/utils/logger.js +1 -0
- package/dist/utils/mcp-config-writer.js +10 -0
- package/dist/utils/mission-control-from-run-states.js +1 -0
- package/dist/utils/node-schema-parser.js +1 -0
- package/dist/utils/parallel-config.js +1 -0
- package/dist/utils/post-process-events.js +1 -0
- package/dist/utils/result-handler.js +1 -0
- package/{src → dist}/utils/ripple-effect.js +3 -12
- package/dist/utils/run-capacity-coordinator.js +1 -0
- package/dist/utils/run-capacity-queue.js +2 -0
- package/dist/utils/run-index-merge.js +1 -0
- package/dist/utils/run-index-post-cli.js +1 -0
- package/dist/utils/run-registry.js +3 -0
- package/dist/utils/run-state-session.js +2 -0
- package/dist/utils/selector-generator.js +4 -0
- package/dist/utils/session-state-constants.js +1 -0
- package/dist/utils/session-state-live-runs.js +1 -0
- package/dist/utils/streaming-parser.js +4 -0
- package/dist/utils/test-post-processor.js +18 -0
- package/dist/utils/timeline.js +14 -0
- package/dist/utils/trace-parser.js +2 -0
- package/dist/utils/video-organizer.js +3 -0
- package/package.json +49 -35
- package/templates/browser-test-automation/README.md +29 -7
- package/templates/browser-test-automation/chat.mjs +36 -0
- package/templates/browser-test-automation/graph.mjs +5 -9
- package/templates/browser-test-automation/nodes/execute-live.mjs +30 -58
- package/templates/browser-test-automation/nodes/generate-script.mjs +32 -12
- package/templates/browser-test-automation/nodes/utils.mjs +153 -10
- package/templates/browser-test-automation/pipeline-ids.js +12 -0
- package/templates/browser-test-automation/result-handler.mjs +78 -2
- package/templates/browser-test-automation/run-index.mjs +418 -0
- package/scripts/export-default-workflows.js +0 -51
- package/scripts/patch-cursor-mcp.js +0 -174
- package/scripts/setup-ci.sh +0 -115
- package/scripts/setup-official-playwright-mcp.sh +0 -226
- package/scripts/test-with-video.sh +0 -49
- package/src/agents/base.js +0 -361
- package/src/constants.js +0 -47
- package/src/enrichment/base.js +0 -49
- package/src/enrichment/enrichers/accessibility-enricher.js +0 -197
- package/src/enrichment/enrichers/dom-enricher.js +0 -171
- package/src/enrichment/enrichers/page-state-enricher.js +0 -129
- package/src/enrichment/enrichers/position-enricher.js +0 -67
- package/src/enrichment/index.js +0 -96
- package/src/enrichment/mcp-integration.js +0 -149
- package/src/enrichment/mcp-ref-enricher.js +0 -78
- package/src/enrichment/pipeline.js +0 -192
- package/src/enrichment/trace-text-enricher.js +0 -115
- package/src/framework/AGENTS.md +0 -98
- package/src/framework/agents/base.js +0 -72
- package/src/framework/agents/claude-strategy.js +0 -278
- package/src/framework/agents/cursor-strategy.js +0 -544
- package/src/framework/agents/index.js +0 -105
- package/src/framework/agents/utils/cursor-output-formatter.js +0 -67
- package/src/framework/agents/utils/openai-proxy-formatter.js +0 -249
- package/src/framework/code-generator.js +0 -301
- package/src/framework/constants.js +0 -33
- package/src/framework/context-loader.js +0 -101
- package/src/framework/function-bridge.js +0 -78
- package/src/framework/function-skill-registry.js +0 -20
- package/src/framework/graph-compiler.js +0 -342
- package/src/framework/graph.js +0 -610
- package/src/framework/index.js +0 -28
- package/src/framework/node-registry.js +0 -163
- package/src/framework/node.js +0 -259
- package/src/framework/output-parser.js +0 -71
- package/src/framework/skill-registry.js +0 -55
- package/src/framework/state-utils.js +0 -52
- package/src/framework/state.js +0 -67
- package/src/framework/tool-resolver.js +0 -65
- package/src/index.js +0 -345
- package/src/runtime/generation/base.js +0 -46
- package/src/runtime/generation/index.js +0 -70
- package/src/runtime/generation/mcp-ref-strategy.js +0 -197
- package/src/runtime/generation/stable-id-strategy.js +0 -170
- package/src/runtime/stable-id-runtime.js +0 -248
- package/src/runtime/verification/base.js +0 -44
- package/src/runtime/verification/index.js +0 -67
- package/src/runtime/verification/playwright-json-strategy.js +0 -119
- package/src/runtime/zibby-runtime.js +0 -299
- package/src/sync/index.js +0 -2
- package/src/sync/uploader.js +0 -29
- package/src/tools/run-playwright-test.js +0 -158
- package/src/utils/adf-converter.js +0 -68
- package/src/utils/ast-utils.js +0 -37
- package/src/utils/ci-setup.js +0 -124
- package/src/utils/cursor-utils.js +0 -71
- package/src/utils/logger.js +0 -144
- package/src/utils/mcp-config-writer.js +0 -115
- package/src/utils/node-schema-parser.js +0 -522
- package/src/utils/post-process-events.js +0 -55
- package/src/utils/result-handler.js +0 -102
- package/src/utils/selector-generator.js +0 -239
- package/src/utils/streaming-parser.js +0 -387
- package/src/utils/test-post-processor.js +0 -211
- package/src/utils/timeline.js +0 -217
- package/src/utils/trace-parser.js +0 -325
- package/src/utils/video-organizer.js +0 -91
package/src/agents/base.js
DELETED
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import { mkdirSync, existsSync, writeFileSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import { ContextLoader } from '../framework/context-loader.js';
|
|
5
|
-
import { StreamingParser } from '../utils/streaming-parser.js';
|
|
6
|
-
import { findCursorAgentPath } from '../utils/cursor-utils.js';
|
|
7
|
-
import { DEFAULT_OUTPUT_BASE, SESSIONS_DIR, SESSION_INFO_FILE } from '../framework/constants.js';
|
|
8
|
-
import { WorkflowGraph } from '../framework/graph.js';
|
|
9
|
-
|
|
10
|
-
export class WorkflowAgent {
|
|
11
|
-
constructor(config = {}) {
|
|
12
|
-
this.config = config;
|
|
13
|
-
this.adapter = null;
|
|
14
|
-
this.paths = config.paths || { specs: 'test-specs', generated: 'tests' };
|
|
15
|
-
this.agentCommand = config.agentCommand || 'cursor-agent';
|
|
16
|
-
this.buildArgs = config.buildArgs || ((prompt, useStreaming = true) => {
|
|
17
|
-
const args = [
|
|
18
|
-
'-p',
|
|
19
|
-
prompt,
|
|
20
|
-
'--approve-mcps',
|
|
21
|
-
'--force'
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
// Only use streaming for non-structured output
|
|
25
|
-
if (useStreaming) {
|
|
26
|
-
args.push('--output-format', 'stream-json');
|
|
27
|
-
args.push('--stream-partial-output');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return args;
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Extract JSON result from NDJSON streaming output
|
|
36
|
-
* Uses StreamingParser for clean, reliable extraction
|
|
37
|
-
*/
|
|
38
|
-
static extractJsonFromStream(rawOutput) {
|
|
39
|
-
return StreamingParser.extractResult(rawOutput);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async initialize(adapter) {
|
|
43
|
-
this.adapter = adapter;
|
|
44
|
-
if (adapter && !adapter.isConnected()) {
|
|
45
|
-
await adapter.connect();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
buildGraph() {
|
|
50
|
-
throw new Error('buildGraph() must be implemented by subclass');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async onComplete(_result) {
|
|
54
|
-
// Override in subclass for post-execution processing
|
|
55
|
-
// (e.g., save artifacts, enrich events, rename videos)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async run(input, options = {}) {
|
|
59
|
-
const graph = this.buildGraph();
|
|
60
|
-
const initialState = typeof input === 'object' && !Array.isArray(input)
|
|
61
|
-
? { input, ...input, ...options }
|
|
62
|
-
: { input, ...options };
|
|
63
|
-
const result = await graph.run(this, initialState);
|
|
64
|
-
return result;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async executeNode(nodeConfig, state) {
|
|
68
|
-
const { prompt, outputSchema, model } = nodeConfig;
|
|
69
|
-
const promptText = typeof prompt === 'function' ? prompt(state) : prompt;
|
|
70
|
-
|
|
71
|
-
console.log(`\n📝 Prompt:\n${promptText}\n`);
|
|
72
|
-
|
|
73
|
-
const output = await this.executePrompt(promptText, state.cwd, 300000, model);
|
|
74
|
-
|
|
75
|
-
console.log(`\n📤 Raw Output:\n${output}\n`);
|
|
76
|
-
|
|
77
|
-
let parsedOutput = null;
|
|
78
|
-
if (outputSchema) {
|
|
79
|
-
try {
|
|
80
|
-
// Use the static helper to extract JSON from streaming output
|
|
81
|
-
parsedOutput = WorkflowAgent.extractJsonFromStream(output);
|
|
82
|
-
|
|
83
|
-
if (!parsedOutput) {
|
|
84
|
-
throw new Error('No valid result JSON found in output');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
console.log(`\n✅ Parsed Output:\n${JSON.stringify(parsedOutput, null, 2)}\n`);
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.warn(`⚠️ Failed to parse output as JSON: ${err.message}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { success: true, output: parsedOutput, raw: output };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async cleanup() {
|
|
97
|
-
if (this.adapter && this.adapter.isConnected()) {
|
|
98
|
-
await this.adapter.disconnect();
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async executePrompt(prompt, cwd = process.cwd(), timeoutMs = 300000, model = null, useStreaming = true) {
|
|
103
|
-
// Resolve cursor-agent path if needed (handles cases where PATH isn't set)
|
|
104
|
-
let agentCommand = this.agentCommand;
|
|
105
|
-
if (agentCommand === 'cursor-agent') {
|
|
106
|
-
const resolvedPath = await findCursorAgentPath();
|
|
107
|
-
if (resolvedPath) {
|
|
108
|
-
agentCommand = resolvedPath;
|
|
109
|
-
}
|
|
110
|
-
// If not found, keep original command - error will be clearer from spawn
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return new Promise((resolve, reject) => {
|
|
114
|
-
let output = '';
|
|
115
|
-
const args = this.buildArgs(prompt, useStreaming);
|
|
116
|
-
|
|
117
|
-
if (model) {
|
|
118
|
-
args.push('--model', model);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const env = { ...process.env };
|
|
122
|
-
|
|
123
|
-
const agent = spawn(agentCommand, args, {
|
|
124
|
-
cwd,
|
|
125
|
-
env,
|
|
126
|
-
stdio: ['inherit', 'pipe', 'inherit'], // Let stderr pass through for typewriter effect
|
|
127
|
-
shell: false,
|
|
128
|
-
detached: false,
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const timeout = setTimeout(() => {
|
|
132
|
-
console.log(`\n⏱️ Timeout reached (${timeoutMs / 1000}s) - killing agent...`);
|
|
133
|
-
agent.kill('SIGTERM');
|
|
134
|
-
// Force kill if still running after 2 seconds
|
|
135
|
-
setTimeout(() => {
|
|
136
|
-
if (!agent.killed) {
|
|
137
|
-
console.log('⚠️ Forcing kill (SIGKILL)...');
|
|
138
|
-
agent.kill('SIGKILL');
|
|
139
|
-
}
|
|
140
|
-
}, 2000);
|
|
141
|
-
reject(new Error(`Agent timed out after ${timeoutMs / 1000}s. The agent may be stuck or waiting for user input.`));
|
|
142
|
-
}, timeoutMs);
|
|
143
|
-
|
|
144
|
-
let lastOutputLength = 0;
|
|
145
|
-
let lastOutputTime = Date.now();
|
|
146
|
-
const heartbeat = setInterval(() => {
|
|
147
|
-
if (output.length > lastOutputLength) {
|
|
148
|
-
// Output is coming in, no need to show heartbeat
|
|
149
|
-
lastOutputLength = output.length;
|
|
150
|
-
lastOutputTime = Date.now();
|
|
151
|
-
} else {
|
|
152
|
-
// Only show "thinking" message if no output for 10 seconds
|
|
153
|
-
const timeSinceLastOutput = Date.now() - lastOutputTime;
|
|
154
|
-
if (timeSinceLastOutput > 10000) {
|
|
155
|
-
console.log(`⏳ AI agent is thinking... (${Math.floor(timeSinceLastOutput / 1000)}s, ${output.length} bytes so far)`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}, 10000);
|
|
159
|
-
|
|
160
|
-
// Handle Ctrl+C gracefully
|
|
161
|
-
const cleanup = () => {
|
|
162
|
-
clearTimeout(timeout);
|
|
163
|
-
clearInterval(heartbeat);
|
|
164
|
-
if (agent && !agent.killed) {
|
|
165
|
-
agent.kill('SIGTERM');
|
|
166
|
-
// Force kill if not dead after 2 seconds
|
|
167
|
-
setTimeout(() => {
|
|
168
|
-
if (!agent.killed) {
|
|
169
|
-
agent.kill('SIGKILL');
|
|
170
|
-
}
|
|
171
|
-
}, 2000);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const sigintHandler = () => {
|
|
176
|
-
console.log('\n\n🛑 Interrupted by user (Ctrl+C)');
|
|
177
|
-
cleanup();
|
|
178
|
-
// Don't reset terminal - it's too aggressive
|
|
179
|
-
reject(new Error('Interrupted by user'));
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
process.on('SIGINT', sigintHandler);
|
|
183
|
-
|
|
184
|
-
// Use StreamingParser for clean, reliable parsing
|
|
185
|
-
const parser = new StreamingParser();
|
|
186
|
-
|
|
187
|
-
// Handle stdout - cursor-agent outputs NDJSON protocol messages
|
|
188
|
-
agent.stdout.on('data', (data) => {
|
|
189
|
-
const chunk = data.toString();
|
|
190
|
-
output += chunk; // Accumulate raw output
|
|
191
|
-
|
|
192
|
-
const displayText = parser.processChunk(chunk);
|
|
193
|
-
|
|
194
|
-
if (displayText) {
|
|
195
|
-
// Write the extracted text immediately (no buffering)
|
|
196
|
-
// This creates the typewriter effect as cursor-agent streams
|
|
197
|
-
process.stdout.write(displayText, 'utf8', () => {
|
|
198
|
-
// Flush immediately after write
|
|
199
|
-
if (process.stdout.isTTY) {
|
|
200
|
-
process.stdout._flush && process.stdout._flush();
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
lastOutputTime = Date.now();
|
|
204
|
-
lastOutputLength += displayText.length;
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
agent.on('close', (code) => {
|
|
209
|
-
process.off('SIGINT', sigintHandler);
|
|
210
|
-
clearTimeout(timeout);
|
|
211
|
-
clearInterval(heartbeat);
|
|
212
|
-
|
|
213
|
-
// Flush any remaining buffer
|
|
214
|
-
const remainingText = parser.flush();
|
|
215
|
-
if (remainingText) {
|
|
216
|
-
process.stdout.write(remainingText);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Get the complete output (for JSON extraction, includes protocol)
|
|
220
|
-
output = parser.getRawText();
|
|
221
|
-
|
|
222
|
-
// Also store the extracted result for direct access
|
|
223
|
-
const extractedResult = parser.getResult();
|
|
224
|
-
|
|
225
|
-
if (code === 0) {
|
|
226
|
-
// Return both raw output and extracted result
|
|
227
|
-
resolve({ raw: output, extracted: extractedResult });
|
|
228
|
-
} else {
|
|
229
|
-
reject(new Error(`Agent exited with code ${code}`));
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
agent.on('error', (error) => {
|
|
234
|
-
process.off('SIGINT', sigintHandler);
|
|
235
|
-
clearTimeout(timeout);
|
|
236
|
-
clearInterval(heartbeat);
|
|
237
|
-
reject(new Error(`Failed to spawn agent: ${error.message}`));
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Run a single node (for testing/debugging)
|
|
244
|
-
* This is a framework feature, not part of workflow definition
|
|
245
|
-
*/
|
|
246
|
-
async runSingleNode(nodeName, nodeMap, initialState) {
|
|
247
|
-
const nodeConfig = nodeMap[nodeName];
|
|
248
|
-
if (!nodeConfig) {
|
|
249
|
-
throw new Error(`Unknown node: ${nodeName}. Available nodes: ${Object.keys(nodeMap).join(', ')}`);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const { cwd } = initialState;
|
|
253
|
-
if (!cwd) {
|
|
254
|
-
throw new Error('cwd is required for single node execution');
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Reuse existing session if provided (--session flag), otherwise create new
|
|
258
|
-
let sessionPath = initialState.sessionPath;
|
|
259
|
-
let sessionTimestamp = initialState.sessionTimestamp;
|
|
260
|
-
const config = initialState.config || {};
|
|
261
|
-
|
|
262
|
-
if (!sessionPath) {
|
|
263
|
-
// Generate CI-aware session ID
|
|
264
|
-
const ciSessionId = process.env.CI_JOB_ID ||
|
|
265
|
-
process.env.GITHUB_RUN_ID ||
|
|
266
|
-
process.env.CIRCLE_WORKFLOW_ID ||
|
|
267
|
-
process.env.BUILD_ID;
|
|
268
|
-
|
|
269
|
-
const baseId = ciSessionId || Date.now().toString();
|
|
270
|
-
const prefix = config.paths?.sessionPrefix;
|
|
271
|
-
const sessionId = prefix ? `${prefix}_${baseId}` : baseId;
|
|
272
|
-
|
|
273
|
-
sessionTimestamp = sessionTimestamp || Date.now();
|
|
274
|
-
|
|
275
|
-
// Use configurable output path
|
|
276
|
-
const outputBase = config.paths?.output || DEFAULT_OUTPUT_BASE;
|
|
277
|
-
sessionPath = join(cwd, outputBase, SESSIONS_DIR, sessionId);
|
|
278
|
-
|
|
279
|
-
if (!existsSync(sessionPath)) {
|
|
280
|
-
mkdirSync(sessionPath, { recursive: true });
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Write session info to output directory
|
|
285
|
-
const outputBase = config.paths?.output || DEFAULT_OUTPUT_BASE;
|
|
286
|
-
const sessionInfoPath = join(cwd, outputBase, SESSION_INFO_FILE);
|
|
287
|
-
mkdirSync(join(cwd, outputBase), { recursive: true });
|
|
288
|
-
writeFileSync(sessionInfoPath, JSON.stringify({
|
|
289
|
-
sessionPath,
|
|
290
|
-
sessionTimestamp: sessionTimestamp || sessionPath.split('/').pop(),
|
|
291
|
-
currentNode: nodeName,
|
|
292
|
-
createdAt: new Date().toISOString()
|
|
293
|
-
}), 'utf-8');
|
|
294
|
-
|
|
295
|
-
console.log(`\n${'='.repeat(80)}`);
|
|
296
|
-
console.log(`🎯 SINGLE NODE EXECUTION: ${nodeName}`);
|
|
297
|
-
console.log(`📁 Session: ${sessionPath.split('/').pop()}${initialState.sessionPath ? ' (reusing)' : ''}`);
|
|
298
|
-
console.log(`${'='.repeat(80)}\n`);
|
|
299
|
-
|
|
300
|
-
const context = await ContextLoader.loadContext(
|
|
301
|
-
initialState.specPath || '',
|
|
302
|
-
cwd,
|
|
303
|
-
initialState.contextConfig || {}
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
// Use Node class for consistent behavior with workflow execution
|
|
307
|
-
const { Node } = await import('../framework/node.js');
|
|
308
|
-
const { WorkflowState } = await import('../framework/state.js');
|
|
309
|
-
|
|
310
|
-
const state = new WorkflowState({
|
|
311
|
-
...initialState,
|
|
312
|
-
sessionPath,
|
|
313
|
-
sessionTimestamp,
|
|
314
|
-
context
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
const node = new Node(nodeConfig);
|
|
318
|
-
const result = await node.execute(this, state);
|
|
319
|
-
|
|
320
|
-
console.log(`\n${'='.repeat(80)}`);
|
|
321
|
-
console.log(`✅ Node ${nodeName} completed`);
|
|
322
|
-
console.log(`${'='.repeat(80)}\n`);
|
|
323
|
-
|
|
324
|
-
return { success: true, output: result.output, outputPath: initialState.outputPath, state: result };
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
calculateOutputPath(specPath) {
|
|
328
|
-
const { specs, generated } = this.paths;
|
|
329
|
-
if (!specPath) return `${generated}/generated-test.spec.js`;
|
|
330
|
-
|
|
331
|
-
const relativePath = specPath.replace(new RegExp(`^${specs}/`), '').replace(/\.[^.]+$/, '.spec.js');
|
|
332
|
-
return `${generated}/${relativePath}`.replace(/\/+/g, '/');
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Functional workflow factory — no class boilerplate needed.
|
|
338
|
-
*
|
|
339
|
-
* const agent = workflow((graph) => {
|
|
340
|
-
* graph.addNode('plan', { prompt: ..., outputSchema: ... });
|
|
341
|
-
* graph.addNode('execute', { skills: ['calculator'], prompt: ... });
|
|
342
|
-
* graph.setEntryPoint('plan');
|
|
343
|
-
* graph.addEdge('plan', 'execute');
|
|
344
|
-
* graph.addEdge('execute', 'END');
|
|
345
|
-
* });
|
|
346
|
-
*
|
|
347
|
-
* await agent.run("What is 15 plus 27?");
|
|
348
|
-
*/
|
|
349
|
-
export function workflow(builder, options = {}) {
|
|
350
|
-
const agent = new WorkflowAgent(options);
|
|
351
|
-
agent.buildGraph = function () {
|
|
352
|
-
const graph = new WorkflowGraph();
|
|
353
|
-
builder(graph);
|
|
354
|
-
return graph;
|
|
355
|
-
};
|
|
356
|
-
if (options.onComplete) {
|
|
357
|
-
agent.onComplete = options.onComplete;
|
|
358
|
-
}
|
|
359
|
-
return agent;
|
|
360
|
-
}
|
|
361
|
-
|
package/src/constants.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zibby Framework Constants
|
|
3
|
-
* Single source of truth for all configuration values
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export const DEFAULT_MODELS = {
|
|
7
|
-
CLAUDE: 'claude-sonnet-4-6',
|
|
8
|
-
CURSOR: 'auto',
|
|
9
|
-
OPENAI_POSTPROCESSING: 'gpt-4o-mini'
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export const AGENT_TYPES = {
|
|
13
|
-
CLAUDE: 'claude',
|
|
14
|
-
CURSOR: 'cursor'
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const LOG_LEVELS = {
|
|
18
|
-
DEBUG: 'debug',
|
|
19
|
-
INFO: 'info',
|
|
20
|
-
WARN: 'warn',
|
|
21
|
-
ERROR: 'error',
|
|
22
|
-
SILENT: 'silent'
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const CLAUDE_MODEL_MAP = {
|
|
26
|
-
'auto': 'claude-sonnet-4-6',
|
|
27
|
-
// 4.6 aliases
|
|
28
|
-
'sonnet-4.6': 'claude-sonnet-4-6',
|
|
29
|
-
'sonnet-4-6': 'claude-sonnet-4-6',
|
|
30
|
-
'opus-4.6': 'claude-opus-4-6',
|
|
31
|
-
'opus-4-6': 'claude-opus-4-6',
|
|
32
|
-
// 4.5 aliases
|
|
33
|
-
'sonnet-4.5': 'claude-sonnet-4-5-20250929',
|
|
34
|
-
'sonnet-4-5': 'claude-sonnet-4-5-20250929',
|
|
35
|
-
'opus-4.5': 'claude-opus-4-20250514',
|
|
36
|
-
'opus-4-5': 'claude-opus-4-20250514',
|
|
37
|
-
// Direct API IDs (pass-through)
|
|
38
|
-
'claude-sonnet-4-6': 'claude-sonnet-4-6',
|
|
39
|
-
'claude-opus-4-6': 'claude-opus-4-6',
|
|
40
|
-
'claude-sonnet-4-5-20250929': 'claude-sonnet-4-5-20250929',
|
|
41
|
-
'claude-opus-4-20250514': 'claude-opus-4-20250514',
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export const TIMEOUTS = {
|
|
45
|
-
CURSOR_AGENT_DEFAULT: 20 * 60 * 1000,
|
|
46
|
-
OPENAI_REQUEST: 30000
|
|
47
|
-
};
|
package/src/enrichment/base.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base Enrichment Pipeline Component
|
|
3
|
-
* Each enricher adds specific data to events without affecting others
|
|
4
|
-
*/
|
|
5
|
-
export class EventEnricher {
|
|
6
|
-
constructor(config = {}) {
|
|
7
|
-
this.config = config;
|
|
8
|
-
this.enabled = config.enabled !== false; // Default enabled
|
|
9
|
-
this.priority = config.priority || 50; // Default priority
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get enricher name for logging
|
|
14
|
-
*/
|
|
15
|
-
getName() {
|
|
16
|
-
throw new Error('EventEnricher.getName() must be implemented');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Check if this enricher can run (dependencies met, etc)
|
|
21
|
-
* @param {Object} context - { page, element, event, session }
|
|
22
|
-
* @returns {boolean}
|
|
23
|
-
*/
|
|
24
|
-
canEnrich(_context) {
|
|
25
|
-
return this.enabled;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Enrich the event with additional data
|
|
30
|
-
* @param {Object} event - The MCP event to enrich
|
|
31
|
-
* @param {Object} context - { page, element, ref }
|
|
32
|
-
* @returns {Promise<Object>} - Enriched data to merge into event
|
|
33
|
-
*/
|
|
34
|
-
async enrich(_event, _context) {
|
|
35
|
-
throw new Error('EventEnricher.enrich() must be implemented');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Handle errors gracefully (never break the pipeline)
|
|
40
|
-
* @param {Error} error
|
|
41
|
-
* @param {Object} event
|
|
42
|
-
*/
|
|
43
|
-
handleError(error, event) {
|
|
44
|
-
console.warn(`[${this.getName()}] Enrichment failed for event ${event.type}:`, error.message);
|
|
45
|
-
return null; // Return null = skip this enrichment
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export default EventEnricher;
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Accessibility Enricher - Captures accessibility tree data and structural hash
|
|
3
|
-
*/
|
|
4
|
-
import { EventEnricher } from '../base.js';
|
|
5
|
-
import crypto from 'crypto';
|
|
6
|
-
|
|
7
|
-
export class AccessibilityEnricher extends EventEnricher {
|
|
8
|
-
getName() {
|
|
9
|
-
return 'AccessibilityEnricher';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
getPriority() {
|
|
13
|
-
return 100; // Highest priority - most stable identifier
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
canEnrich(context) {
|
|
17
|
-
if (!this.enabled) return false;
|
|
18
|
-
if (!context.element) return false;
|
|
19
|
-
if (!context.event) return false;
|
|
20
|
-
|
|
21
|
-
return ['click', 'fill', 'type', 'selectOption', 'hover'].includes(context.event.type);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async enrich(event, context) {
|
|
25
|
-
try {
|
|
26
|
-
const { page, element } = context;
|
|
27
|
-
|
|
28
|
-
// Get accessibility snapshot of entire page
|
|
29
|
-
const snapshot = await page.accessibility.snapshot();
|
|
30
|
-
|
|
31
|
-
// Find the target element in AX tree
|
|
32
|
-
const axNode = await this.findAxNode(element, snapshot);
|
|
33
|
-
if (!axNode) return null;
|
|
34
|
-
|
|
35
|
-
// Get parent and siblings
|
|
36
|
-
const axContext = await this.getAxContext(axNode, snapshot);
|
|
37
|
-
|
|
38
|
-
// Calculate structural hash (hash of AX subtree)
|
|
39
|
-
const axTreeHash = this.hashAxSubtree(axNode);
|
|
40
|
-
const axPathHash = this.hashAxPath(axContext.path);
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
accessibility: {
|
|
44
|
-
role: axNode.role,
|
|
45
|
-
name: axNode.name || '',
|
|
46
|
-
level: axContext.level,
|
|
47
|
-
parent: axContext.parent,
|
|
48
|
-
siblings: axContext.siblings,
|
|
49
|
-
axTreeHash,
|
|
50
|
-
axPathHash
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
} catch (error) {
|
|
54
|
-
return this.handleError(error, event);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Find AX node for given element
|
|
60
|
-
*/
|
|
61
|
-
async findAxNode(element, snapshot) {
|
|
62
|
-
// Get element's aria attributes for matching
|
|
63
|
-
const ariaProps = await element.evaluate(el => ({
|
|
64
|
-
role: el.getAttribute('role') || el.tagName.toLowerCase(),
|
|
65
|
-
name: el.getAttribute('aria-label') || el.textContent?.trim() || '',
|
|
66
|
-
tagName: el.tagName.toLowerCase()
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
|
-
// Search AX tree for matching node
|
|
70
|
-
return this.searchAxTree(snapshot, ariaProps);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Recursively search AX tree
|
|
75
|
-
*/
|
|
76
|
-
searchAxTree(node, props) {
|
|
77
|
-
if (!node) return null;
|
|
78
|
-
|
|
79
|
-
// Match by role and name
|
|
80
|
-
if (node.role === props.role &&
|
|
81
|
-
(node.name || '').includes(props.name.substring(0, 20))) {
|
|
82
|
-
return node;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Search children
|
|
86
|
-
if (node.children) {
|
|
87
|
-
for (const child of node.children) {
|
|
88
|
-
const found = this.searchAxTree(child, props);
|
|
89
|
-
if (found) return found;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Get context (parent, siblings, path)
|
|
98
|
-
*/
|
|
99
|
-
getAxContext(node, snapshot) {
|
|
100
|
-
const context = {
|
|
101
|
-
level: 0,
|
|
102
|
-
parent: null,
|
|
103
|
-
siblings: [],
|
|
104
|
-
path: []
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
// Walk up tree to find parent and siblings
|
|
108
|
-
const parent = this.findParent(node, snapshot);
|
|
109
|
-
if (parent) {
|
|
110
|
-
context.parent = { role: parent.role, name: parent.name };
|
|
111
|
-
context.siblings = (parent.children || [])
|
|
112
|
-
.filter(c => c !== node)
|
|
113
|
-
.map(c => ({ role: c.role, name: c.name }))
|
|
114
|
-
.slice(0, 3); // Limit siblings
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Calculate level (depth in tree)
|
|
118
|
-
context.level = this.calculateLevel(node, snapshot);
|
|
119
|
-
|
|
120
|
-
// Build path from root to node
|
|
121
|
-
context.path = this.buildPath(node, snapshot);
|
|
122
|
-
|
|
123
|
-
return context;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
findParent(node, tree, current = tree) {
|
|
127
|
-
if (!current || !current.children) return null;
|
|
128
|
-
|
|
129
|
-
if (current.children.includes(node)) {
|
|
130
|
-
return current;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
for (const child of current.children) {
|
|
134
|
-
const found = this.findParent(node, tree, child);
|
|
135
|
-
if (found) return found;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
calculateLevel(node, tree, current = tree, level = 0) {
|
|
142
|
-
if (current === node) return level;
|
|
143
|
-
|
|
144
|
-
if (current.children) {
|
|
145
|
-
for (const child of current.children) {
|
|
146
|
-
const found = this.calculateLevel(node, tree, child, level + 1);
|
|
147
|
-
if (found >= 0) return found;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return -1;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
buildPath(node, tree, current = tree, path = []) {
|
|
155
|
-
if (current === node) {
|
|
156
|
-
return [...path, { role: current.role, name: current.name }];
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (current.children) {
|
|
160
|
-
for (const child of current.children) {
|
|
161
|
-
const found = this.buildPath(node, tree, child, [
|
|
162
|
-
...path,
|
|
163
|
-
{ role: current.role, name: current.name }
|
|
164
|
-
]);
|
|
165
|
-
if (found) return found;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Hash AX subtree for stability detection
|
|
174
|
-
*/
|
|
175
|
-
hashAxSubtree(node) {
|
|
176
|
-
const structure = JSON.stringify({
|
|
177
|
-
role: node.role,
|
|
178
|
-
name: node.name,
|
|
179
|
-
children: (node.children || []).map(c => ({
|
|
180
|
-
role: c.role,
|
|
181
|
-
name: c.name
|
|
182
|
-
}))
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
return crypto.createHash('md5').update(structure).digest('hex').substring(0, 12);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Hash AX path from root to node
|
|
190
|
-
*/
|
|
191
|
-
hashAxPath(path) {
|
|
192
|
-
const pathStr = path.map(p => `${p.role}:${p.name}`).join('/');
|
|
193
|
-
return crypto.createHash('md5').update(pathStr).digest('hex').substring(0, 12);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export default AccessibilityEnricher;
|