@zibby/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/package.json +94 -0
  4. package/src/agents/base.js +361 -0
  5. package/src/constants.js +47 -0
  6. package/src/enrichment/base.js +49 -0
  7. package/src/enrichment/enrichers/accessibility-enricher.js +197 -0
  8. package/src/enrichment/enrichers/dom-enricher.js +171 -0
  9. package/src/enrichment/enrichers/page-state-enricher.js +129 -0
  10. package/src/enrichment/enrichers/position-enricher.js +67 -0
  11. package/src/enrichment/index.js +96 -0
  12. package/src/enrichment/mcp-integration.js +149 -0
  13. package/src/enrichment/mcp-ref-enricher.js +78 -0
  14. package/src/enrichment/pipeline.js +192 -0
  15. package/src/enrichment/trace-text-enricher.js +115 -0
  16. package/src/framework/AGENTS.md +98 -0
  17. package/src/framework/agents/base.js +72 -0
  18. package/src/framework/agents/claude-strategy.js +278 -0
  19. package/src/framework/agents/cursor-strategy.js +459 -0
  20. package/src/framework/agents/index.js +105 -0
  21. package/src/framework/agents/utils/cursor-output-formatter.js +67 -0
  22. package/src/framework/agents/utils/openai-proxy-formatter.js +249 -0
  23. package/src/framework/code-generator.js +301 -0
  24. package/src/framework/constants.js +33 -0
  25. package/src/framework/context-loader.js +101 -0
  26. package/src/framework/function-bridge.js +78 -0
  27. package/src/framework/function-skill-registry.js +20 -0
  28. package/src/framework/graph-compiler.js +342 -0
  29. package/src/framework/graph.js +610 -0
  30. package/src/framework/index.js +28 -0
  31. package/src/framework/node-registry.js +163 -0
  32. package/src/framework/node.js +259 -0
  33. package/src/framework/output-parser.js +71 -0
  34. package/src/framework/skill-registry.js +55 -0
  35. package/src/framework/state-utils.js +52 -0
  36. package/src/framework/state.js +67 -0
  37. package/src/framework/tool-resolver.js +65 -0
  38. package/src/index.js +342 -0
  39. package/src/runtime/generation/base.js +46 -0
  40. package/src/runtime/generation/index.js +70 -0
  41. package/src/runtime/generation/mcp-ref-strategy.js +197 -0
  42. package/src/runtime/generation/stable-id-strategy.js +170 -0
  43. package/src/runtime/stable-id-runtime.js +248 -0
  44. package/src/runtime/verification/base.js +44 -0
  45. package/src/runtime/verification/index.js +67 -0
  46. package/src/runtime/verification/playwright-json-strategy.js +119 -0
  47. package/src/runtime/zibby-runtime.js +299 -0
  48. package/src/sync/index.js +2 -0
  49. package/src/sync/uploader.js +29 -0
  50. package/src/tools/run-playwright-test.js +158 -0
  51. package/src/utils/adf-converter.js +68 -0
  52. package/src/utils/ast-utils.js +37 -0
  53. package/src/utils/ci-setup.js +124 -0
  54. package/src/utils/cursor-utils.js +71 -0
  55. package/src/utils/logger.js +144 -0
  56. package/src/utils/mcp-config-writer.js +115 -0
  57. package/src/utils/node-schema-parser.js +522 -0
  58. package/src/utils/post-process-events.js +55 -0
  59. package/src/utils/result-handler.js +102 -0
  60. package/src/utils/ripple-effect.js +84 -0
  61. package/src/utils/selector-generator.js +239 -0
  62. package/src/utils/streaming-parser.js +387 -0
  63. package/src/utils/test-post-processor.js +211 -0
  64. package/src/utils/timeline.js +217 -0
  65. package/src/utils/trace-parser.js +325 -0
  66. package/src/utils/video-organizer.js +91 -0
  67. package/templates/browser-test-automation/README.md +114 -0
  68. package/templates/browser-test-automation/graph.js +54 -0
  69. package/templates/browser-test-automation/nodes/execute-live.js +250 -0
  70. package/templates/browser-test-automation/nodes/generate-script.js +77 -0
  71. package/templates/browser-test-automation/nodes/index.js +3 -0
  72. package/templates/browser-test-automation/nodes/preflight.js +59 -0
  73. package/templates/browser-test-automation/nodes/utils.js +154 -0
  74. package/templates/browser-test-automation/result-handler.js +286 -0
  75. package/templates/code-analysis/graph.js +72 -0
  76. package/templates/code-analysis/index.js +18 -0
  77. package/templates/code-analysis/nodes/analyze-ticket-node.js +204 -0
  78. package/templates/code-analysis/nodes/create-pr-node.js +175 -0
  79. package/templates/code-analysis/nodes/finalize-node.js +118 -0
  80. package/templates/code-analysis/nodes/generate-code-node.js +425 -0
  81. package/templates/code-analysis/nodes/generate-test-cases-node.js +376 -0
  82. package/templates/code-analysis/nodes/services/prMetaService.js +86 -0
  83. package/templates/code-analysis/nodes/setup-node.js +142 -0
  84. package/templates/code-analysis/prompts/analyze-ticket.md +181 -0
  85. package/templates/code-analysis/prompts/generate-code.md +33 -0
  86. package/templates/code-analysis/prompts/generate-test-cases.md +110 -0
  87. package/templates/code-analysis/state.js +40 -0
  88. package/templates/code-implementation/graph.js +35 -0
  89. package/templates/code-implementation/index.js +7 -0
  90. package/templates/code-implementation/state.js +14 -0
  91. package/templates/global-setup.js +56 -0
  92. package/templates/index.js +94 -0
  93. package/templates/register-nodes.js +24 -0
@@ -0,0 +1,163 @@
1
+
2
+ const registry = new Map();
3
+
4
+ export function registerNode(type, impl) {
5
+ registry.set(type, impl);
6
+ }
7
+
8
+ export function getNodeImpl(type) {
9
+ return registry.get(type);
10
+ }
11
+
12
+ export function hasNode(type) {
13
+ return registry.has(type);
14
+ }
15
+
16
+ export function listNodeTypes() {
17
+ return Array.from(registry.keys());
18
+ }
19
+
20
+ export function getNodeTemplate(type) {
21
+ const impl = registry.get(type);
22
+ if (!impl) return null;
23
+
24
+ if (impl.factory && typeof impl.create === 'function') {
25
+ return impl.create.toString();
26
+ }
27
+
28
+ if (typeof impl.execute === 'function') {
29
+ return impl.execute.toString();
30
+ }
31
+
32
+ if (typeof impl === 'function') {
33
+ return impl.toString();
34
+ }
35
+
36
+ return null;
37
+ }
38
+
39
+ // ------------------------------------------------------------------
40
+ // Dynamic / factory nodes (framework-provided)
41
+ // ------------------------------------------------------------------
42
+ registerNode('ai_agent', {
43
+ name: 'ai_agent',
44
+ factory: true,
45
+ create: (nodeId, nodeConfig = {}) => ({
46
+ name: nodeId,
47
+ execute: async (state) => {
48
+ const { invokeAgent } = await import('./agents/index.js');
49
+
50
+ const prompt = nodeConfig.extraPromptInstructions || 'Execute the task based on the current state.';
51
+ const fullPrompt = buildAIAgentPrompt(prompt, state);
52
+
53
+ const result = await invokeAgent(fullPrompt, {
54
+ cwd: state.workspace || process.cwd(),
55
+ model: state.model,
56
+ tools: nodeConfig.resolvedTools || null
57
+ });
58
+
59
+ return {
60
+ success: true,
61
+ output: { raw: result, nodeId },
62
+ raw: typeof result === 'string' ? result : result.raw
63
+ };
64
+ }
65
+ })
66
+ });
67
+
68
+ /**
69
+ * Parse @variable references from prompt text
70
+ * @param {string} prompt - The prompt containing @references
71
+ * @returns {string[]} Array of unique variable paths (e.g., ["context.summary", "previous_node_output"])
72
+ */
73
+ function parseVariableReferences(prompt) {
74
+ const refRegex = /@([\w.]+)/g;
75
+ const refs = new Set();
76
+ let match;
77
+ while ((match = refRegex.exec(prompt)) !== null) {
78
+ refs.add(match[1]);
79
+ }
80
+ return Array.from(refs);
81
+ }
82
+
83
+ /**
84
+ * Get a value from state using dot notation path
85
+ * @param {Object} state - The state object
86
+ * @param {string} path - Dot-notation path (e.g., "context.summary")
87
+ * @returns {*} The value at the path, or undefined if not found
88
+ */
89
+ function getStateValue(state, path) {
90
+ const parts = path.split('.');
91
+ let value = state;
92
+ for (const part of parts) {
93
+ if (value == null) return undefined;
94
+ value = value[part];
95
+ }
96
+ return value;
97
+ }
98
+
99
+ /**
100
+ * Format a value for inclusion in the prompt
101
+ * @param {*} value - The value to format
102
+ * @returns {string} Formatted string representation
103
+ */
104
+ function formatValueForPrompt(value) {
105
+ if (value === undefined || value === null) {
106
+ return '[not available]';
107
+ }
108
+
109
+ if (typeof value === 'string') {
110
+ return value;
111
+ }
112
+
113
+ if (typeof value === 'object') {
114
+ if (value.raw && typeof value.raw === 'string') {
115
+ return value.raw;
116
+ }
117
+ return JSON.stringify(value, null, 2);
118
+ }
119
+
120
+ return String(value);
121
+ }
122
+
123
+ /**
124
+ * Build AI agent prompt with explicit variable references
125
+ * Only includes context that is explicitly referenced with @variable syntax
126
+ * @param {string} basePrompt - The user's prompt with @references
127
+ * @param {Object} state - The workflow state
128
+ * @returns {string} The full prompt with referenced context appended
129
+ */
130
+ function buildAIAgentPrompt(basePrompt, state) {
131
+ const refs = parseVariableReferences(basePrompt);
132
+
133
+ if (refs.length === 0) {
134
+ return basePrompt;
135
+ }
136
+
137
+ const contextParts = [];
138
+ const processedRefs = new Set();
139
+
140
+ for (const ref of refs) {
141
+ const rootPath = ref.split('.')[0];
142
+ if (processedRefs.has(rootPath)) continue;
143
+
144
+ const value = getStateValue(state, ref);
145
+ if (value !== undefined) {
146
+ const formatted = formatValueForPrompt(value);
147
+
148
+ const label = ref.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
149
+
150
+ contextParts.push(`## ${label}\n${formatted}`);
151
+
152
+ if (!ref.includes('.')) {
153
+ processedRefs.add(rootPath);
154
+ }
155
+ }
156
+ }
157
+
158
+ if (contextParts.length === 0) {
159
+ return basePrompt;
160
+ }
161
+
162
+ return `${basePrompt}\n\n---\n# Referenced Context\n\n${contextParts.join('\n\n')}`;
163
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Base Node class for workflow graph
3
+ * Each node = one agent execution with a specific prompt
4
+ */
5
+
6
+ import { OutputParser } from './output-parser.js';
7
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
8
+ import { join, dirname } from 'path';
9
+ import chalk from 'chalk';
10
+ import { DEFAULT_MODELS } from '../constants.js';
11
+ import { invokeAgent } from './agents/index.js';
12
+ import { logger } from '../utils/logger.js';
13
+ import { timeline } from '../utils/timeline.js';
14
+ import { SESSION_INFO_FILE } from './constants.js';
15
+
16
+ export class Node {
17
+ constructor(config) {
18
+ this.config = config; // Store full config for timeout access
19
+ this.name = config.name;
20
+ this.prompt = config.prompt;
21
+ this.outputSchema = config.outputSchema;
22
+
23
+ // Enforce outputSchema requirement (allow both Zod schemas and JSON Schema from runtime compilation)
24
+ if (!this.outputSchema) {
25
+ // Only enforce for non-custom code (registry nodes must have schema)
26
+ if (!config._isCustomCode) {
27
+ throw new Error(`Node '${this.name}' must define outputSchema (Zod schema). This defines the contract for what the node returns to state.`);
28
+ }
29
+ // Custom code from graph-compiler gets a pass (already validated in UI)
30
+ }
31
+
32
+ this.isZodSchema = this.outputSchema && typeof this.outputSchema._def !== 'undefined';
33
+ this.parser = config.outputSchema && !this.isZodSchema ? new OutputParser(config.outputSchema) : null;
34
+ this.retries = config.retries || 0;
35
+ this.onComplete = config.onComplete;
36
+ this.customExecute = config.execute;
37
+ }
38
+
39
+ async execute(context, state) {
40
+ const _agent = context?.agent || context;
41
+
42
+ // Helpers to get state values - works with both old and new call patterns
43
+ // context may be: 1) nodeContext object from unified graph.run, or 2) agent from old calls
44
+ // state may be: 1) WorkflowState instance, or 2) undefined (for inline test nodes)
45
+ const getAllState = () => {
46
+ if (state && typeof state.getAll === 'function') {
47
+ return state.getAll();
48
+ }
49
+ return context;
50
+ };
51
+
52
+ const _getState = (key) => {
53
+ if (state && typeof state.get === 'function') {
54
+ return state.get(key);
55
+ }
56
+ return context?.[key];
57
+ };
58
+
59
+ if (typeof this.customExecute === 'function') {
60
+ logger.info(`⚡ Using custom execute method (skipping LLM)`);
61
+ try {
62
+ const result = await this.customExecute(context);
63
+
64
+ // Propagate inner failure (e.g. from wrapCustomCode's catch)
65
+ if (typeof result === 'object' && result !== null && result.success === false) {
66
+ return { success: false, error: result.error || 'Node execution failed', raw: result.raw || null };
67
+ }
68
+
69
+ if (this.isZodSchema) {
70
+ logger.debug(`Validating return value against outputSchema...`);
71
+ const validated = this.outputSchema.parse(result);
72
+ return { success: true, output: validated, raw: null };
73
+ }
74
+
75
+ return { success: true, output: result, raw: null };
76
+ } catch (error) {
77
+ logger.error(`❌ Node '${this.name}' execution failed: ${error.message}`);
78
+ if (error.name === 'ZodError') {
79
+ logger.error(`Schema validation errors: ${JSON.stringify(error.errors, null, 2)}`);
80
+ }
81
+ return { success: false, error: error.message, raw: null };
82
+ }
83
+ }
84
+
85
+ let prompt = typeof this.prompt === 'function'
86
+ ? this.prompt(getAllState())
87
+ : this.prompt;
88
+
89
+ const skillHints = _getState('_skillHints');
90
+ if (skillHints) {
91
+ prompt = `${skillHints}\n\n${prompt}`;
92
+ }
93
+
94
+ const allState = getAllState();
95
+ const cwd = allState.cwd || process.cwd();
96
+ const sessionPath = allState.sessionPath;
97
+
98
+ try {
99
+ if (sessionPath) {
100
+ const sessionInfoPath = join(sessionPath, '..', SESSION_INFO_FILE);
101
+ if (existsSync(sessionInfoPath)) {
102
+ const sessionInfo = JSON.parse(readFileSync(sessionInfoPath, 'utf-8'));
103
+ sessionInfo.currentNode = this.name;
104
+ writeFileSync(sessionInfoPath, JSON.stringify(sessionInfo, null, 2), 'utf-8');
105
+ }
106
+ }
107
+ } catch (err) {
108
+ logger.debug(`Could not update session info: ${err.message}`);
109
+ }
110
+
111
+ console.log(`\n${chalk.bold('Prompt sent to LLM:')}`);
112
+ console.log(chalk.dim('─'.repeat(60)));
113
+ console.log(chalk.dim(prompt));
114
+ console.log(chalk.dim('─'.repeat(60)));
115
+
116
+ let lastError = null;
117
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
118
+ try {
119
+ logger.debug(`Node.execute attempt ${attempt} for '${this.name}'`);
120
+
121
+ const _timeout = this.config?.timeout || 300000;
122
+
123
+ // Model resolution priority:
124
+ // 1. Node-specific override: config.models[nodeName]
125
+ // 2. Global default: config.models.default
126
+ // 3. Agent-specific: config.agent.claude.model or config.agent.cursor.model
127
+ // 4. Fallback: DEFAULT_MODELS.CLAUDE or 'auto'
128
+ const currentAllState = getAllState();
129
+ const zibbyConfig = currentAllState.config || {};
130
+ const modelsConfig = zibbyConfig.models || {};
131
+
132
+ // Infer provider from agent config (claude/cursor key presence)
133
+ const inferProvider = (agentConfig) => {
134
+ if (agentConfig?.provider) return agentConfig.provider;
135
+ if (agentConfig?.claude) return 'claude';
136
+ if (agentConfig?.cursor) return 'cursor';
137
+ return 'unknown';
138
+ };
139
+
140
+ const agentType = inferProvider(zibbyConfig?.agent) || currentAllState.agentType || 'unknown';
141
+ const agentModel = agentType && zibbyConfig.agent?.[agentType]?.model;
142
+
143
+ let model = modelsConfig[this.name] || modelsConfig.default || null;
144
+
145
+ if (!model || model === 'auto') {
146
+ model = agentModel || null;
147
+ }
148
+
149
+ if (!model) {
150
+ model = agentType === 'claude' ? DEFAULT_MODELS.CLAUDE : 'auto';
151
+ }
152
+
153
+ const agentDisplay = agentType === 'claude' ? 'Claude (Anthropic API)' : agentType === 'cursor' ? 'Cursor (CLI)' : agentType;
154
+
155
+ {
156
+ const displayModel = (model && model !== 'auto') ? model : (agentType === 'claude' ? DEFAULT_MODELS.CLAUDE : 'agent default');
157
+ const skills = this.config.skills || [];
158
+ const skillsSuffix = skills.length > 0 ? ` → skills: [${skills.join(', ')}]` : '';
159
+ timeline.step(`${agentDisplay} (${displayModel})${skillsSuffix}`);
160
+ }
161
+
162
+ const agentContext = {
163
+ state: getAllState()
164
+ };
165
+
166
+ const agentOptions = {
167
+ model,
168
+ workspace: cwd,
169
+ schema: this.isZodSchema ? this.outputSchema : null,
170
+ skills: this.config.skills || null,
171
+ sessionPath,
172
+ config: zibbyConfig
173
+ };
174
+
175
+ const result = await invokeAgent(prompt, agentContext, agentOptions);
176
+
177
+ let rawOutput, extractedJson;
178
+
179
+ if (typeof result === 'string') {
180
+ rawOutput = result;
181
+ extractedJson = null;
182
+ } else if (result.structured) {
183
+ rawOutput = result.raw || JSON.stringify(result.structured, null, 2);
184
+ extractedJson = result.structured;
185
+ } else {
186
+ rawOutput = result.raw || JSON.stringify(result, null, 2);
187
+ extractedJson = result.extracted || null;
188
+ }
189
+
190
+ if (sessionPath) {
191
+ try {
192
+ const debugPath = join(sessionPath, this.name, 'raw_stream_output.txt');
193
+ mkdirSync(dirname(debugPath), { recursive: true });
194
+ writeFileSync(debugPath, typeof rawOutput === 'string' ? rawOutput : JSON.stringify(rawOutput), 'utf-8');
195
+ } catch (err) {
196
+ logger.debug(`Could not save raw output: ${err.message}`);
197
+ }
198
+ }
199
+
200
+ if (this.isZodSchema && extractedJson) {
201
+ console.log(`\n🔍 ${chalk.cyan('Validated output:')} ${chalk.white(JSON.stringify(extractedJson, null, 2))}`);
202
+
203
+ let finalOutput = extractedJson;
204
+ if (typeof this.onComplete === 'function') {
205
+ try {
206
+ finalOutput = await this.onComplete(getAllState(), extractedJson);
207
+ } catch (err) {
208
+ logger.warn(`onComplete hook failed: ${err.message}`);
209
+ }
210
+ }
211
+
212
+ return { success: true, output: finalOutput, raw: rawOutput };
213
+ }
214
+
215
+ if (typeof this.onComplete === 'function') {
216
+ try {
217
+ const onCompleteResult = await this.onComplete(getAllState(), { raw: rawOutput });
218
+ return { success: true, output: onCompleteResult, raw: rawOutput };
219
+ } catch (err) {
220
+ throw new Error(`onComplete failed: ${err.message}`, { cause: err });
221
+ }
222
+ }
223
+
224
+ if (this.parser) {
225
+ const parsed = this.parser.parse(rawOutput);
226
+ console.log(`\n🔍 ${chalk.cyan('Parsed output:')} ${chalk.white(JSON.stringify(parsed, null, 2))}`);
227
+ timeline.step('Output parsed');
228
+ return { success: true, output: parsed, raw: rawOutput };
229
+ }
230
+
231
+ return { success: true, output: rawOutput, raw: rawOutput };
232
+ } catch (error) {
233
+ lastError = error;
234
+ if (attempt < this.retries) {
235
+ logger.info(`Node '${this.name}' failed, retrying (${attempt + 1}/${this.retries})...`);
236
+ }
237
+ }
238
+ }
239
+
240
+ return { success: false, error: lastError.message, raw: null };
241
+ }
242
+
243
+ }
244
+
245
+ export class ConditionalNode extends Node {
246
+ constructor(config) {
247
+ super({ ...config, _isCustomCode: true });
248
+ this.condition = config.condition;
249
+ }
250
+
251
+ async execute(context, state) {
252
+ const stateValues = (state && typeof state.getAll === 'function')
253
+ ? state.getAll()
254
+ : context;
255
+ const nextNode = this.condition(stateValues);
256
+ return { success: true, output: { nextNode }, raw: null };
257
+ }
258
+ }
259
+
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Structured output parsing with validation
3
+ * Similar to LangChain's StructuredOutputParser
4
+ */
5
+
6
+ export class OutputParser {
7
+ constructor(schema) {
8
+ this.schema = schema;
9
+ }
10
+
11
+ parse(text) {
12
+ // Try to extract JSON from markdown code blocks
13
+ const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/);
14
+ if (jsonMatch) {
15
+ return this.validate(JSON.parse(jsonMatch[1]));
16
+ }
17
+
18
+ // Try to find JSON object in text
19
+ const objectMatch = text.match(/\{[\s\S]*\}/);
20
+ if (objectMatch) {
21
+ return this.validate(JSON.parse(objectMatch[0]));
22
+ }
23
+
24
+ // Fallback: return raw text if no JSON found
25
+ return this.validate({ result: text.trim() });
26
+ }
27
+
28
+ validate(data) {
29
+ const errors = [];
30
+
31
+ for (const [key, validator] of Object.entries(this.schema)) {
32
+ if (validator.required && !(key in data)) {
33
+ errors.push(`Missing required field: ${key}`);
34
+ }
35
+
36
+ if (key in data && validator.type) {
37
+ const actualType = typeof data[key];
38
+ if (actualType !== validator.type) {
39
+ errors.push(`Field '${key}' expected ${validator.type}, got ${actualType}`);
40
+ }
41
+ }
42
+
43
+ if (validator.validate && key in data) {
44
+ const validationError = validator.validate(data[key]);
45
+ if (validationError) {
46
+ errors.push(`Field '${key}': ${validationError}`);
47
+ }
48
+ }
49
+ }
50
+
51
+ if (errors.length > 0) {
52
+ throw new Error(`Output validation failed:\n${errors.join('\n')}`);
53
+ }
54
+
55
+ return data;
56
+ }
57
+ }
58
+
59
+ // Helper to create common schemas
60
+ export const SchemaTypes = {
61
+ string: (required = true) => ({ type: 'string', required }),
62
+ number: (required = true) => ({ type: 'number', required }),
63
+ boolean: (required = true) => ({ type: 'boolean', required }),
64
+ array: (required = true) => ({ type: 'object', required, validate: (v) => Array.isArray(v) ? null : 'must be an array' }),
65
+ enum: (values, required = true) => ({
66
+ type: 'string',
67
+ required,
68
+ validate: (v) => values.includes(v) ? null : `must be one of: ${values.join(', ')}`
69
+ }),
70
+ };
71
+
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Skill Registry
3
+ *
4
+ * Central registry for skill definitions. A skill describes an MCP server
5
+ * and the tool schemas it exposes. The framework resolves skills generically
6
+ * at runtime -- no skill is referenced by name in agent strategies.
7
+ *
8
+ * Built-in skills are registered by importing @zibby/skills (side-effect).
9
+ * Community / user skills call registerSkill() directly.
10
+ *
11
+ * See @zibby/skills/README.md for the full skill authoring guide.
12
+ */
13
+
14
+ const _registry = new Map();
15
+
16
+ /**
17
+ * Register a skill definition.
18
+ * @param {Object} skill
19
+ * @param {string} skill.id - Unique identifier (used in node `skills` arrays)
20
+ * @param {'mcp'|'function'} [skill.type] - Skill type: 'mcp' (default) or 'function' (auto-bridged)
21
+ * @param {string} skill.serverName - MCP server name (key in mcpServers config)
22
+ * @param {string[]} skill.allowedTools - Tool patterns for Claude SDK (e.g. ['mcp__playwright__*'])
23
+ * @param {Function} [skill.resolve] - (options?) => { command, args, env? } | null
24
+ * @param {string[]} [skill.envKeys] - Required environment variable names
25
+ * @param {string} [skill.description] - Human-readable description
26
+ * @param {Object[]} [skill.tools] - Tool schemas for compile-time validation
27
+ * @param {string} [skill.cursorKey] - Override key for ~/.cursor/mcp.json
28
+ * @param {string} [skill.sessionEnvKey] - Env var injected with session info path (Cursor only)
29
+ */
30
+ export function registerSkill(skill) {
31
+ if (!skill || typeof skill.id !== 'string') {
32
+ throw new Error('Skill definition must include a string id');
33
+ }
34
+ _registry.set(skill.id, Object.freeze({ ...skill }));
35
+ }
36
+
37
+ /** @returns {object|null} */
38
+ export function getSkill(id) {
39
+ return _registry.get(id) || null;
40
+ }
41
+
42
+ /** @returns {boolean} */
43
+ export function hasSkill(id) {
44
+ return _registry.has(id);
45
+ }
46
+
47
+ /** @returns {Map<string, object>} shallow copy */
48
+ export function getAllSkills() {
49
+ return new Map(_registry);
50
+ }
51
+
52
+ /** @returns {string[]} */
53
+ export function listSkillIds() {
54
+ return Array.from(_registry.keys());
55
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * State Schema Utility Functions
3
+ * Helper functions for working with Zod state schemas
4
+ */
5
+
6
+ import { zodToJsonSchema } from 'zod-to-json-schema';
7
+
8
+ /**
9
+ * Get JSON Schema for frontend/API docs
10
+ * @param {import('zod').ZodSchema} zodSchema - Zod schema to convert
11
+ * @returns {Object} JSON Schema in OpenAPI 3 format
12
+ */
13
+ export function getJsonSchema(zodSchema) {
14
+ if (!zodSchema) return null;
15
+ return zodToJsonSchema(zodSchema, { target: 'openApi3' });
16
+ }
17
+
18
+ /**
19
+ * Convert Zod schema to Variable Inspector format
20
+ * @param {import('zod').ZodSchema} zodSchema - Zod schema to convert
21
+ * @param {string} prefix - Optional prefix for nested paths
22
+ * @returns {Array} Flattened variable definitions
23
+ */
24
+ export function schemaToVariables(zodSchema, prefix = '') {
25
+ const jsonSchema = zodToJsonSchema(zodSchema);
26
+ return flattenJsonSchema(jsonSchema, prefix);
27
+ }
28
+
29
+ function flattenJsonSchema(schema, prefix = '') {
30
+ const variables = [];
31
+
32
+ if (schema.type === 'object' && schema.properties) {
33
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
34
+ const path = prefix ? `${prefix}.${key}` : key;
35
+ const isRequired = schema.required?.includes(key) ?? false;
36
+
37
+ variables.push({
38
+ name: key,
39
+ path,
40
+ type: propSchema.type || 'unknown',
41
+ description: propSchema.description,
42
+ optional: !isRequired,
43
+ });
44
+
45
+ if (propSchema.type === 'object' && propSchema.properties) {
46
+ variables.push(...flattenJsonSchema(propSchema, path));
47
+ }
48
+ }
49
+ }
50
+
51
+ return variables;
52
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * State management for agent workflow graphs
3
+ * Similar to LangGraph's state annotation
4
+ */
5
+
6
+ const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
7
+
8
+ function assertSafeKey(key) {
9
+ if (UNSAFE_KEYS.has(key)) {
10
+ throw new Error(`Invalid state key: "${key}"`);
11
+ }
12
+ }
13
+
14
+ export class WorkflowState {
15
+ constructor(initialState = {}) {
16
+ this._state = Object.create(null);
17
+ Object.assign(this._state, {
18
+ messages: [],
19
+ errors: [],
20
+ artifacts: {},
21
+ metadata: {},
22
+ ...initialState
23
+ });
24
+ this._history = [];
25
+ }
26
+
27
+ get(key) {
28
+ return this._state[key];
29
+ }
30
+
31
+ set(key, value) {
32
+ assertSafeKey(key);
33
+ this._history.push({ ...this._state });
34
+ this._state[key] = value;
35
+ }
36
+
37
+ update(updates) {
38
+ const keys = Object.getOwnPropertyNames(updates);
39
+ for (const key of keys) {
40
+ assertSafeKey(key);
41
+ }
42
+ this._history.push({ ...this._state });
43
+ for (const key of keys) {
44
+ this._state[key] = updates[key];
45
+ }
46
+ }
47
+
48
+ append(key, value) {
49
+ assertSafeKey(key);
50
+ this._history.push({ ...this._state });
51
+ if (!Array.isArray(this._state[key])) {
52
+ this._state[key] = [];
53
+ }
54
+ this._state[key].push(value);
55
+ }
56
+
57
+ getAll() {
58
+ return { ...this._state };
59
+ }
60
+
61
+ rollback() {
62
+ if (this._history.length > 0) {
63
+ this._state = this._history.pop();
64
+ }
65
+ }
66
+ }
67
+