@zibby/core 0.1.21 → 0.1.23
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
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Enrichment Pipeline Manager
|
|
3
|
-
* Orchestrates multiple enrichers to add data to events
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export class EnrichmentPipeline {
|
|
7
|
-
constructor(config = {}) {
|
|
8
|
-
this.enrichers = [];
|
|
9
|
-
this.config = config;
|
|
10
|
-
this.stats = {
|
|
11
|
-
totalEvents: 0,
|
|
12
|
-
enrichedEvents: 0,
|
|
13
|
-
skippedEvents: 0,
|
|
14
|
-
errors: {}
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Register an enricher
|
|
20
|
-
* @param {EventEnricher} enricher
|
|
21
|
-
*/
|
|
22
|
-
register(enricher) {
|
|
23
|
-
this.enrichers.push(enricher);
|
|
24
|
-
// Sort by priority (highest first)
|
|
25
|
-
this.enrichers.sort((a, b) => b.getPriority() - a.getPriority());
|
|
26
|
-
return this;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Unregister an enricher by name
|
|
31
|
-
* @param {string} name
|
|
32
|
-
*/
|
|
33
|
-
unregister(name) {
|
|
34
|
-
this.enrichers = this.enrichers.filter(e => e.getName() !== name);
|
|
35
|
-
return this;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get enricher by name
|
|
40
|
-
* @param {string} name
|
|
41
|
-
*/
|
|
42
|
-
get(name) {
|
|
43
|
-
return this.enrichers.find(e => e.getName() === name);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Enable/disable specific enricher
|
|
48
|
-
* @param {string} name
|
|
49
|
-
* @param {boolean} enabled
|
|
50
|
-
*/
|
|
51
|
-
setEnabled(name, enabled) {
|
|
52
|
-
const enricher = this.get(name);
|
|
53
|
-
if (enricher) {
|
|
54
|
-
enricher.enabled = enabled;
|
|
55
|
-
}
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Enrich an event through the pipeline
|
|
61
|
-
* @param {Object} event - MCP event
|
|
62
|
-
* @param {Object} context - { page, element, ref, session }
|
|
63
|
-
* @returns {Promise<Object>} - Enriched event
|
|
64
|
-
*/
|
|
65
|
-
async enrich(event, context) {
|
|
66
|
-
this.stats.totalEvents++;
|
|
67
|
-
|
|
68
|
-
// Start with original event
|
|
69
|
-
const enriched = { ...event };
|
|
70
|
-
|
|
71
|
-
// Track which enrichers ran
|
|
72
|
-
const enrichersRun = [];
|
|
73
|
-
const enrichersSkipped = [];
|
|
74
|
-
const enrichersFailed = [];
|
|
75
|
-
|
|
76
|
-
// Run each enricher in priority order
|
|
77
|
-
for (const enricher of this.enrichers) {
|
|
78
|
-
try {
|
|
79
|
-
// Check if enricher can run
|
|
80
|
-
if (!enricher.canEnrich(context)) {
|
|
81
|
-
enrichersSkipped.push(enricher.getName());
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Run enricher
|
|
86
|
-
const startTime = Date.now();
|
|
87
|
-
const data = await enricher.enrich(event, context);
|
|
88
|
-
const duration = Date.now() - startTime;
|
|
89
|
-
|
|
90
|
-
if (data) {
|
|
91
|
-
// Merge enriched data
|
|
92
|
-
Object.assign(enriched, data);
|
|
93
|
-
enrichersRun.push({ name: enricher.getName(), duration });
|
|
94
|
-
} else {
|
|
95
|
-
enrichersSkipped.push(enricher.getName());
|
|
96
|
-
}
|
|
97
|
-
} catch (error) {
|
|
98
|
-
console.warn(`[EnrichmentPipeline] ${enricher.getName()} failed:`, error.message);
|
|
99
|
-
enrichersFailed.push(enricher.getName());
|
|
100
|
-
|
|
101
|
-
// Track errors
|
|
102
|
-
this.stats.errors[enricher.getName()] = (this.stats.errors[enricher.getName()] || 0) + 1;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Add enrichment metadata
|
|
107
|
-
enriched._enrichment = {
|
|
108
|
-
version: '1.0',
|
|
109
|
-
timestamp: new Date().toISOString(),
|
|
110
|
-
enrichers: {
|
|
111
|
-
run: enrichersRun,
|
|
112
|
-
skipped: enrichersSkipped,
|
|
113
|
-
failed: enrichersFailed
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Update stats
|
|
118
|
-
if (enrichersRun.length > 0) {
|
|
119
|
-
this.stats.enrichedEvents++;
|
|
120
|
-
} else {
|
|
121
|
-
this.stats.skippedEvents++;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return enriched;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Enrich multiple events in batch
|
|
129
|
-
* @param {Array} events
|
|
130
|
-
* @param {Object} context
|
|
131
|
-
* @returns {Promise<Array>}
|
|
132
|
-
*/
|
|
133
|
-
async enrichBatch(events, context) {
|
|
134
|
-
const results = [];
|
|
135
|
-
|
|
136
|
-
for (const event of events) {
|
|
137
|
-
const enriched = await this.enrich(event, context);
|
|
138
|
-
results.push(enriched);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return results;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Get pipeline statistics
|
|
146
|
-
*/
|
|
147
|
-
getStats() {
|
|
148
|
-
return {
|
|
149
|
-
...this.stats,
|
|
150
|
-
enrichers: this.enrichers.map(e => ({
|
|
151
|
-
name: e.getName(),
|
|
152
|
-
enabled: e.enabled,
|
|
153
|
-
priority: e.getPriority(),
|
|
154
|
-
errors: this.stats.errors[e.getName()] || 0
|
|
155
|
-
}))
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Reset statistics
|
|
161
|
-
*/
|
|
162
|
-
resetStats() {
|
|
163
|
-
this.stats = {
|
|
164
|
-
totalEvents: 0,
|
|
165
|
-
enrichedEvents: 0,
|
|
166
|
-
skippedEvents: 0,
|
|
167
|
-
errors: {}
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Log pipeline status
|
|
173
|
-
*/
|
|
174
|
-
logStatus() {
|
|
175
|
-
console.log('\n📊 Enrichment Pipeline Status:');
|
|
176
|
-
console.log(` Total events: ${this.stats.totalEvents}`);
|
|
177
|
-
console.log(` Enriched: ${this.stats.enrichedEvents}`);
|
|
178
|
-
console.log(` Skipped: ${this.stats.skippedEvents}`);
|
|
179
|
-
console.log(`\n Registered enrichers (${this.enrichers.length}):`);
|
|
180
|
-
|
|
181
|
-
for (const enricher of this.enrichers) {
|
|
182
|
-
const status = enricher.enabled ? '✓' : '✗';
|
|
183
|
-
const errors = this.stats.errors[enricher.getName()] || 0;
|
|
184
|
-
const errorStr = errors > 0 ? ` (${errors} errors)` : '';
|
|
185
|
-
console.log(` ${status} ${enricher.getName()} (priority: ${enricher.getPriority()})${errorStr}`);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
console.log();
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export default EnrichmentPipeline;
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { EventEnricher } from './base.js';
|
|
2
|
-
import { TraceParser } from '../utils/trace-parser.js';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* TraceTextEnricher - Extracts ACTUAL element text from Playwright trace
|
|
8
|
-
*
|
|
9
|
-
* Problem: AI might say "Login button" but actual DOM has "登录" (Chinese)
|
|
10
|
-
* Solution: Parse trace.zip to find the actual element text at action time
|
|
11
|
-
*
|
|
12
|
-
* This runs AFTER recording as a post-processing step.
|
|
13
|
-
*/
|
|
14
|
-
export class TraceTextEnricher extends EventEnricher {
|
|
15
|
-
constructor(config = {}) {
|
|
16
|
-
super(config);
|
|
17
|
-
this.priority = 190; // Just below MCPRefEnricher
|
|
18
|
-
this.traceData = null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
getName() {
|
|
22
|
-
return 'TraceText';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
getPriority() {
|
|
26
|
-
return this.priority;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Load trace data once for all events
|
|
31
|
-
*/
|
|
32
|
-
async loadTrace(sessionPath) {
|
|
33
|
-
if (this.traceData) return;
|
|
34
|
-
|
|
35
|
-
const _tracePath = join(sessionPath, 'traces');
|
|
36
|
-
const traceZipPath = join(sessionPath, 'trace.zip');
|
|
37
|
-
|
|
38
|
-
if (existsSync(traceZipPath)) {
|
|
39
|
-
try {
|
|
40
|
-
this.traceData = await TraceParser.parseTraceZip(traceZipPath);
|
|
41
|
-
console.log(`[TraceTextEnricher] ✅ Loaded trace with ${this.traceData.length} actions`);
|
|
42
|
-
} catch (e) {
|
|
43
|
-
console.log(`[TraceTextEnricher] ⚠️ Failed to parse trace: ${e.message}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async enrich(event, context) {
|
|
49
|
-
const ref = event.data?.params?.ref;
|
|
50
|
-
const eventId = event.id;
|
|
51
|
-
if (ref === undefined && eventId === undefined) return null;
|
|
52
|
-
|
|
53
|
-
// Load trace on first event
|
|
54
|
-
if (!this.traceData && context.sessionPath) {
|
|
55
|
-
await this.loadTrace(context.sessionPath);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!this.traceData) return null;
|
|
59
|
-
|
|
60
|
-
// Match by sequence order: event.id maps to traceData[id]
|
|
61
|
-
// This is more reliable than matching by ref (which trace doesn't have)
|
|
62
|
-
const traceAction = this.traceData[eventId];
|
|
63
|
-
if (!traceAction) {
|
|
64
|
-
console.log(`[TraceTextEnricher] ⚠️ No trace action for event ${eventId}`);
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// NEW: Use the actualText extracted from accessibility tree
|
|
69
|
-
const actualText = traceAction.actualText || this._extractTextFromSelector(traceAction.selector);
|
|
70
|
-
const actualRole = traceAction.actualRole;
|
|
71
|
-
const actualAriaLabel = traceAction.actualAriaLabel;
|
|
72
|
-
|
|
73
|
-
if (actualText || actualRole || actualAriaLabel) {
|
|
74
|
-
console.log(`[TraceTextEnricher] ✅ Event ${eventId}: text="${actualText}", role="${actualRole}", label="${actualAriaLabel}"`);
|
|
75
|
-
return {
|
|
76
|
-
traceActualText: actualText,
|
|
77
|
-
traceActualRole: actualRole,
|
|
78
|
-
traceActualAriaLabel: actualAriaLabel,
|
|
79
|
-
traceSelector: traceAction.selector,
|
|
80
|
-
traceStrategies: traceAction.strategies // Include all strategies
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Extract actual text from Playwright's internal selector format
|
|
89
|
-
* Examples:
|
|
90
|
-
* - internal:label="登录" -> "登录"
|
|
91
|
-
* - getByText('Submit') -> "Submit"
|
|
92
|
-
* - getByRole('button', { name: '确定' }) -> "确定"
|
|
93
|
-
*/
|
|
94
|
-
_extractTextFromSelector(selector) {
|
|
95
|
-
if (!selector) return null;
|
|
96
|
-
|
|
97
|
-
// Try internal:label format
|
|
98
|
-
const labelMatch = selector.match(/internal:label="([^"]+)"/);
|
|
99
|
-
if (labelMatch) return labelMatch[1];
|
|
100
|
-
|
|
101
|
-
// Try internal:text format
|
|
102
|
-
const textMatch = selector.match(/internal:text="([^"]+)"/);
|
|
103
|
-
if (textMatch) return textMatch[1];
|
|
104
|
-
|
|
105
|
-
// Try getByText format
|
|
106
|
-
const getByTextMatch = selector.match(/getByText\(['"]([^'"]+)['"]\)/);
|
|
107
|
-
if (getByTextMatch) return getByTextMatch[1];
|
|
108
|
-
|
|
109
|
-
// Try getByRole with name format
|
|
110
|
-
const getByRoleMatch = selector.match(/name:\s*['"]([^'"]+)['"]/);
|
|
111
|
-
if (getByRoleMatch) return getByRoleMatch[1];
|
|
112
|
-
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
}
|
package/src/framework/AGENTS.md
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# Workflow Execution Architecture
|
|
2
|
-
|
|
3
|
-
## Single Source of Truth: Template Node Files
|
|
4
|
-
|
|
5
|
-
Workflows are **compiled from source code**, not loaded from JSON files.
|
|
6
|
-
|
|
7
|
-
The template node definitions in `packages/core/templates/` are the single source of truth.
|
|
8
|
-
There is no static JSON workflow file — any such file (e.g. `default-analysis-workflow.json`) is stale and unused.
|
|
9
|
-
|
|
10
|
-
## How It Works
|
|
11
|
-
|
|
12
|
-
### 1. Graph Builders Compile Templates into Workflows
|
|
13
|
-
|
|
14
|
-
Each workflow type has a graph builder that imports node definitions and wires them together:
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
packages/core/templates/code-analysis/graph.js → buildAnalysisGraph()
|
|
18
|
-
packages/core/templates/code-implementation/graph.js → buildImplementationGraph()
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
These builders import the actual node files:
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
packages/core/templates/code-analysis/nodes/
|
|
25
|
-
├── setup-node.js
|
|
26
|
-
├── analyze-ticket-node.js
|
|
27
|
-
├── generate-code-node.js
|
|
28
|
-
├── generate-test-cases-node.js
|
|
29
|
-
└── finalize-node.js
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Each node file exports `{ name, outputSchema, execute }`. The `execute` function is the actual runtime code.
|
|
33
|
-
|
|
34
|
-
### 2. Backend Resolves Workflow at Execution Time
|
|
35
|
-
|
|
36
|
-
When a user triggers an analysis (`POST /projects/:projectId/tickets/:ticketKey/analyze`), the backend resolves the workflow in this order:
|
|
37
|
-
|
|
38
|
-
1. **WORKFLOWS_TABLE** (DynamoDB) — user-customized workflow saved via the UI workflow editor
|
|
39
|
-
2. **project.analysisGraphConfig** (PROJECTS_TABLE) — legacy per-project config
|
|
40
|
-
3. **null** — no saved workflow; the CLI will use the compiled default
|
|
41
|
-
|
|
42
|
-
The resolved config (or null) is uploaded to S3 as `context.json` and passed to the ECS container via `CONTEXT_PRESIGNED_URL`.
|
|
43
|
-
|
|
44
|
-
### 3. ECS Container Compiles and Runs the Graph
|
|
45
|
-
|
|
46
|
-
The CLI (`packages/cli/src/commands/analyze-graph.js`) picks the workflow source:
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
if (options.workflow) → local file (--workflow flag, dev only)
|
|
50
|
-
else if (execCtx.graphConfig) → custom saved workflow from DynamoDB (serialized JSON)
|
|
51
|
-
else → getDefaultGraph('analysis') → buildAnalysisGraph() from templates
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
When using the default path, `buildAnalysisGraph()` imports the node files directly — the `execute` functions run as compiled JavaScript, not as eval'd strings.
|
|
55
|
-
|
|
56
|
-
When using a saved custom workflow, `compileGraph()` deserializes the JSON and wraps `executeCode` strings via `new Function()`.
|
|
57
|
-
|
|
58
|
-
### 4. Key Implication: Saved Workflows Can Go Stale
|
|
59
|
-
|
|
60
|
-
If a user saves a workflow via the UI editor, the `executeCode` is serialized as a string snapshot into DynamoDB. Future code fixes to the template node files **do not** propagate to saved workflows.
|
|
61
|
-
|
|
62
|
-
**To pick up template fixes**: reset the workflow in the UI settings (Project → Settings → Workflow → Reset to Default). This deletes the DynamoDB record and forces the CLI to recompile from templates.
|
|
63
|
-
|
|
64
|
-
## State Access Pattern
|
|
65
|
-
|
|
66
|
-
The graph stores each node's output under `state[nodeName]`:
|
|
67
|
-
|
|
68
|
-
```js
|
|
69
|
-
// In graph.js after node execution:
|
|
70
|
-
state.update({ [currentNode]: result.output });
|
|
71
|
-
|
|
72
|
-
// So to access previous node results:
|
|
73
|
-
state.analyze_ticket?.analysis // NOT state.analysis
|
|
74
|
-
state.generate_code?.codeImplementation // NOT state.codeImplementation
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## invokeAgent Return Shape
|
|
78
|
-
|
|
79
|
-
When `invokeAgent` is called with a `schema` option, it returns `{ raw, structured }` (not the structured data directly):
|
|
80
|
-
|
|
81
|
-
```js
|
|
82
|
-
const result = await invokeAgent(prompt, { state, model, schema: SomeSchema });
|
|
83
|
-
const output = result?.structured || result; // unwrap
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Without a schema, it returns a plain string.
|
|
87
|
-
|
|
88
|
-
## File-Based Structured Output (Cursor Strategy)
|
|
89
|
-
|
|
90
|
-
For Cursor agent, structured output uses a file-based approach:
|
|
91
|
-
|
|
92
|
-
1. Framework generates a unique file path: `.zibby/tmp/zibby-result-<timestamp>.json`
|
|
93
|
-
2. Instructions appended to prompt telling agent to write JSON to that file
|
|
94
|
-
3. After agent completes, framework reads the file and validates with Zod
|
|
95
|
-
4. If validation fails and `strictMode` is enabled, falls back to OpenAI proxy for schema enforcement
|
|
96
|
-
5. Result files are kept (not deleted) for debugging
|
|
97
|
-
|
|
98
|
-
The Claude strategy uses its SDK's native structured output — completely separate path.
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base class for AI agent strategies.
|
|
3
|
-
* All provider implementations (Cursor, Claude, etc.) must extend this class.
|
|
4
|
-
*
|
|
5
|
-
* @abstract
|
|
6
|
-
*/
|
|
7
|
-
export class AgentStrategy {
|
|
8
|
-
/**
|
|
9
|
-
* @param {string} name - Provider identifier (e.g. 'cursor', 'claude')
|
|
10
|
-
* @param {string} description - Human-readable description
|
|
11
|
-
* @param {number} [priority=0] - Selection priority (higher = preferred)
|
|
12
|
-
*/
|
|
13
|
-
constructor(name, description, priority = 0) {
|
|
14
|
-
this.name = name;
|
|
15
|
-
this.description = description;
|
|
16
|
-
this.priority = priority;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Execute a prompt against this agent.
|
|
21
|
-
*
|
|
22
|
-
* @abstract
|
|
23
|
-
* @param {string} prompt - The prompt text
|
|
24
|
-
* @param {AgentInvokeOptions} options
|
|
25
|
-
* @returns {Promise<string | AgentStructuredResult>}
|
|
26
|
-
* - Without schema: returns raw output string
|
|
27
|
-
* - With schema: returns { raw: string, structured: object }
|
|
28
|
-
* - On failure: throws Error
|
|
29
|
-
*
|
|
30
|
-
* @typedef {Object} AgentInvokeOptions
|
|
31
|
-
* @property {string} [model] - Model name or 'auto'
|
|
32
|
-
* @property {string} [workspace] - Working directory
|
|
33
|
-
* @property {object} [schema] - Zod schema for structured output
|
|
34
|
-
* @property {Array} [tools] - MCP tools available to the agent
|
|
35
|
-
* @property {Array} [images] - Image attachments (provider-specific)
|
|
36
|
-
* @property {string} [sessionPath] - Session artifact directory
|
|
37
|
-
* @property {number} [timeout] - Execution timeout in ms
|
|
38
|
-
* @property {object} [config] - Full workflow config
|
|
39
|
-
*
|
|
40
|
-
* @typedef {Object} AgentStructuredResult
|
|
41
|
-
* @property {string} raw - Raw agent output
|
|
42
|
-
* @property {object} structured - Parsed and validated output
|
|
43
|
-
*/
|
|
44
|
-
async invoke(prompt, _options = {}) {
|
|
45
|
-
throw new Error('AgentStrategy.invoke() must be implemented by subclass');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Check if this agent strategy is available in the current environment.
|
|
50
|
-
*
|
|
51
|
-
* @abstract
|
|
52
|
-
* @param {object} [context] - Environment context (unused by most providers)
|
|
53
|
-
* @returns {boolean}
|
|
54
|
-
*/
|
|
55
|
-
canHandle(_context) {
|
|
56
|
-
throw new Error('AgentStrategy.canHandle() must be implemented by subclass');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
getName() {
|
|
60
|
-
return this.name;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
getDescription() {
|
|
64
|
-
return this.description;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
getPriority() {
|
|
68
|
-
return this.priority;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export default AgentStrategy;
|