@covibes/zeroshot 5.4.0 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -465
  3. package/cli/index.js +101 -5231
  4. package/package.json +16 -130
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -149
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -275
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -581
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -208
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -220
  21. package/lib/git-remote-utils.js +0 -165
  22. package/lib/id-detector.js +0 -56
  23. package/lib/provider-defaults.js +0 -62
  24. package/lib/provider-detection.js +0 -59
  25. package/lib/provider-names.js +0 -57
  26. package/lib/settings/claude-auth.js +0 -78
  27. package/lib/settings.js +0 -503
  28. package/lib/stream-json-parser.js +0 -10
  29. package/scripts/fix-node-pty-permissions.js +0 -75
  30. package/scripts/record-demo.sh +0 -279
  31. package/scripts/setup-merge-queue.sh +0 -170
  32. package/scripts/test-install.sh +0 -40
  33. package/scripts/validate-templates.js +0 -100
  34. package/src/agent/agent-config.js +0 -246
  35. package/src/agent/agent-context-builder.js +0 -444
  36. package/src/agent/agent-hook-executor.js +0 -599
  37. package/src/agent/agent-lifecycle.js +0 -743
  38. package/src/agent/agent-stuck-detector.js +0 -256
  39. package/src/agent/agent-task-executor.js +0 -1690
  40. package/src/agent/agent-trigger-evaluator.js +0 -67
  41. package/src/agent/output-extraction.js +0 -217
  42. package/src/agent/output-reformatter.js +0 -175
  43. package/src/agent/schema-utils.js +0 -146
  44. package/src/agent-wrapper.js +0 -579
  45. package/src/agents/git-pusher-template.js +0 -285
  46. package/src/attach/attach-client.js +0 -438
  47. package/src/attach/attach-server.js +0 -543
  48. package/src/attach/index.js +0 -35
  49. package/src/attach/protocol.js +0 -220
  50. package/src/attach/ring-buffer.js +0 -121
  51. package/src/attach/socket-discovery.js +0 -267
  52. package/src/claude-task-runner.js +0 -574
  53. package/src/config-router.js +0 -80
  54. package/src/config-validator.js +0 -1925
  55. package/src/copy-worker.js +0 -43
  56. package/src/input-helpers.js +0 -65
  57. package/src/isolation-manager.js +0 -1471
  58. package/src/issue-providers/README.md +0 -305
  59. package/src/issue-providers/azure-devops-provider.js +0 -273
  60. package/src/issue-providers/base-provider.js +0 -232
  61. package/src/issue-providers/github-provider.js +0 -179
  62. package/src/issue-providers/gitlab-provider.js +0 -241
  63. package/src/issue-providers/index.js +0 -196
  64. package/src/issue-providers/jira-provider.js +0 -239
  65. package/src/ledger.js +0 -631
  66. package/src/lib/safe-exec.js +0 -88
  67. package/src/logic-engine.js +0 -223
  68. package/src/message-bus-bridge.js +0 -139
  69. package/src/message-bus.js +0 -249
  70. package/src/name-generator.js +0 -232
  71. package/src/orchestrator.js +0 -2815
  72. package/src/preflight.js +0 -639
  73. package/src/process-metrics.js +0 -608
  74. package/src/providers/anthropic/cli-builder.js +0 -73
  75. package/src/providers/anthropic/index.js +0 -204
  76. package/src/providers/anthropic/models.js +0 -23
  77. package/src/providers/anthropic/output-parser.js +0 -177
  78. package/src/providers/base-provider.js +0 -251
  79. package/src/providers/capabilities.js +0 -60
  80. package/src/providers/google/cli-builder.js +0 -55
  81. package/src/providers/google/index.js +0 -116
  82. package/src/providers/google/models.js +0 -24
  83. package/src/providers/google/output-parser.js +0 -101
  84. package/src/providers/index.js +0 -91
  85. package/src/providers/openai/cli-builder.js +0 -133
  86. package/src/providers/openai/index.js +0 -136
  87. package/src/providers/openai/models.js +0 -21
  88. package/src/providers/openai/output-parser.js +0 -143
  89. package/src/providers/opencode/cli-builder.js +0 -42
  90. package/src/providers/opencode/index.js +0 -103
  91. package/src/providers/opencode/models.js +0 -55
  92. package/src/providers/opencode/output-parser.js +0 -122
  93. package/src/schemas/sub-cluster.js +0 -185
  94. package/src/status-footer.js +0 -1026
  95. package/src/sub-cluster-wrapper.js +0 -601
  96. package/src/task-runner.js +0 -30
  97. package/src/template-resolver.js +0 -372
  98. package/src/tui/CHANGES.txt +0 -133
  99. package/src/tui/LAYOUT.md +0 -261
  100. package/src/tui/README.txt +0 -192
  101. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  102. package/src/tui/data-poller.js +0 -349
  103. package/src/tui/demo.js +0 -208
  104. package/src/tui/formatters.js +0 -129
  105. package/src/tui/index.js +0 -193
  106. package/src/tui/keybindings.js +0 -324
  107. package/src/tui/layout.js +0 -334
  108. package/src/tui/renderer.js +0 -184
  109. package/task-lib/attachable-watcher.js +0 -309
  110. package/task-lib/claude-recovery.js +0 -156
  111. package/task-lib/commands/clean.js +0 -50
  112. package/task-lib/commands/episodes.js +0 -105
  113. package/task-lib/commands/get-log-path.js +0 -23
  114. package/task-lib/commands/kill.js +0 -32
  115. package/task-lib/commands/list.js +0 -105
  116. package/task-lib/commands/logs.js +0 -453
  117. package/task-lib/commands/resume.js +0 -42
  118. package/task-lib/commands/run.js +0 -57
  119. package/task-lib/commands/schedule.js +0 -105
  120. package/task-lib/commands/scheduler-cmd.js +0 -96
  121. package/task-lib/commands/schedules.js +0 -148
  122. package/task-lib/commands/status.js +0 -44
  123. package/task-lib/commands/unschedule.js +0 -16
  124. package/task-lib/completion.js +0 -9
  125. package/task-lib/config.js +0 -8
  126. package/task-lib/name-generator.js +0 -230
  127. package/task-lib/package.json +0 -3
  128. package/task-lib/runner.js +0 -201
  129. package/task-lib/scheduler.js +0 -252
  130. package/task-lib/store.js +0 -529
  131. package/task-lib/tui/formatters.js +0 -215
  132. package/task-lib/tui/index.js +0 -197
  133. package/task-lib/tui/layout.js +0 -111
  134. package/task-lib/tui/renderer.js +0 -129
  135. package/task-lib/tui.js +0 -425
  136. package/task-lib/watcher.js +0 -226
@@ -1,143 +0,0 @@
1
- function safeJsonParse(value, fallback) {
2
- try {
3
- return JSON.parse(value);
4
- } catch {
5
- return fallback;
6
- }
7
- }
8
-
9
- function parseAssistantMessage(item) {
10
- const events = [];
11
- const content = Array.isArray(item.content)
12
- ? item.content
13
- : [{ type: 'text', text: item.content }];
14
- const text = content
15
- .filter((c) => c.type === 'text')
16
- .map((c) => c.text)
17
- .join('');
18
- const thinking = content
19
- .filter((c) => c.type === 'thinking' || c.type === 'reasoning')
20
- .map((c) => c.text)
21
- .join('');
22
- if (text) events.push({ type: 'text', text });
23
- if (thinking) events.push({ type: 'thinking', text: thinking });
24
- return events;
25
- }
26
-
27
- function parseFunctionCall(item) {
28
- const toolId = item.call_id || item.id || item.tool_call_id || item.tool_id;
29
- const args =
30
- typeof item.arguments === 'string' ? safeJsonParse(item.arguments, {}) : item.arguments || {};
31
- return {
32
- type: 'tool_call',
33
- toolName: item.name,
34
- toolId,
35
- input: args,
36
- };
37
- }
38
-
39
- function parseFunctionCallOutput(item) {
40
- const toolId = item.call_id || item.id || item.tool_call_id || item.tool_id;
41
- const content = item.output ?? item.result ?? item.content ?? '';
42
- return {
43
- type: 'tool_result',
44
- toolId,
45
- content,
46
- isError: !!item.error,
47
- };
48
- }
49
-
50
- function parseItem(item) {
51
- const events = [];
52
-
53
- // Handle assistant messages (Claude-style: type=message, role=assistant)
54
- if (item.type === 'message' && item.role === 'assistant') {
55
- events.push(...parseAssistantMessage(item));
56
- }
57
-
58
- // Handle agent messages (Codex-style: type=agent_message, text=string)
59
- if (item.type === 'agent_message' && item.text) {
60
- events.push({ type: 'text', text: item.text });
61
- }
62
-
63
- if (item.type === 'function_call') {
64
- events.push(parseFunctionCall(item));
65
- }
66
-
67
- if (item.type === 'function_call_output') {
68
- events.push(parseFunctionCallOutput(item));
69
- }
70
-
71
- if (events.length === 1) return events[0];
72
- if (events.length > 1) return events;
73
- return null;
74
- }
75
-
76
- function parseEvent(line, options = {}) {
77
- let event;
78
- try {
79
- event = JSON.parse(line);
80
- } catch {
81
- return null;
82
- }
83
-
84
- switch (event.type) {
85
- case 'thread.started':
86
- case 'turn.started':
87
- return null;
88
-
89
- case 'item.created':
90
- case 'item.completed':
91
- return parseItem(event.item);
92
-
93
- case 'turn.completed': {
94
- const usage = event.usage || event.response?.usage || {};
95
- return {
96
- type: 'result',
97
- success: true,
98
- inputTokens: usage.input_tokens || 0,
99
- outputTokens: usage.output_tokens || 0,
100
- };
101
- }
102
-
103
- case 'turn.failed':
104
- return {
105
- type: 'result',
106
- success: false,
107
- error: event.error?.message || event.error || 'Turn failed',
108
- };
109
-
110
- default:
111
- // Only log warnings for actual unknown string types, not malformed events
112
- // (undefined/object types are just noise from non-standard CLI output)
113
- if (options.onUnknown && typeof event.type === 'string') {
114
- options.onUnknown(event.type, event);
115
- }
116
- return null;
117
- }
118
- }
119
-
120
- function parseChunk(chunk, options = {}) {
121
- const events = [];
122
- const lines = chunk.split('\n');
123
-
124
- for (const line of lines) {
125
- if (!line.trim()) continue;
126
- const event = parseEvent(line, options);
127
- if (!event) continue;
128
- if (Array.isArray(event)) {
129
- events.push(...event);
130
- } else {
131
- events.push(event);
132
- }
133
- }
134
-
135
- return events;
136
- }
137
-
138
- module.exports = {
139
- parseEvent,
140
- parseChunk,
141
- parseItem,
142
- safeJsonParse,
143
- };
@@ -1,42 +0,0 @@
1
- function buildCommand(context, options = {}) {
2
- const { modelSpec, outputFormat, jsonSchema, cwd, cliFeatures = {} } = options;
3
-
4
- let finalContext = context;
5
- if (jsonSchema) {
6
- const schemaStr =
7
- typeof jsonSchema === 'string' ? jsonSchema : JSON.stringify(jsonSchema, null, 2);
8
- finalContext =
9
- context +
10
- `\n\n## OUTPUT FORMAT (CRITICAL - REQUIRED)\n\nYou MUST respond with a JSON object that exactly matches this schema. NO markdown, NO explanation, NO code blocks. ONLY the raw JSON object.\n\nSchema:\n\`\`\`json\n${schemaStr}\n\`\`\`\n\nYour response must be ONLY valid JSON. Start with { and end with }. Nothing else.`;
11
- }
12
-
13
- const args = ['run'];
14
-
15
- if ((outputFormat === 'stream-json' || outputFormat === 'json') && cliFeatures.supportsJson) {
16
- args.push('--format', 'json');
17
- }
18
-
19
- if (modelSpec?.model) {
20
- args.push('--model', modelSpec.model);
21
- }
22
-
23
- if (modelSpec?.reasoningEffort && cliFeatures.supportsVariant) {
24
- args.push('--variant', modelSpec.reasoningEffort);
25
- }
26
-
27
- if (cwd && cliFeatures.supportsCwd) {
28
- args.push('--cwd', cwd);
29
- }
30
-
31
- args.push(finalContext);
32
-
33
- return {
34
- binary: 'opencode',
35
- args,
36
- env: {},
37
- };
38
- }
39
-
40
- module.exports = {
41
- buildCommand,
42
- };
@@ -1,103 +0,0 @@
1
- const BaseProvider = require('../base-provider');
2
- const { commandExists, getCommandPath, getHelpOutput } = require('../../../lib/provider-detection');
3
- const { buildCommand } = require('./cli-builder');
4
- const { parseEvent } = require('./output-parser');
5
- const {
6
- MODEL_CATALOG,
7
- LEVEL_MAPPING,
8
- DEFAULT_LEVEL,
9
- DEFAULT_MAX_LEVEL,
10
- DEFAULT_MIN_LEVEL,
11
- } = require('./models');
12
-
13
- const warned = new Set();
14
-
15
- class OpencodeProvider extends BaseProvider {
16
- constructor() {
17
- super({ name: 'opencode', displayName: 'Opencode', cliCommand: 'opencode' });
18
- this._cliFeatures = null;
19
- }
20
-
21
- isAvailable() {
22
- return commandExists(this.cliCommand);
23
- }
24
-
25
- getCliPath() {
26
- return getCommandPath(this.cliCommand) || this.cliCommand;
27
- }
28
-
29
- getInstallInstructions() {
30
- return 'See https://opencode.ai for installation instructions.';
31
- }
32
-
33
- getAuthInstructions() {
34
- return 'opencode auth login';
35
- }
36
-
37
- getCliFeatures() {
38
- if (this._cliFeatures) return this._cliFeatures;
39
- const help = getHelpOutput(this.cliCommand, ['run']);
40
- const unknown = !help;
41
-
42
- const features = {
43
- supportsJson: unknown ? true : /--format\b/.test(help),
44
- supportsModel: unknown ? true : /--model\b/.test(help),
45
- supportsVariant: unknown ? true : /--variant\b/.test(help),
46
- supportsCwd: unknown ? false : /--cwd\b/.test(help),
47
- supportsAutoApprove: false,
48
- unknown,
49
- };
50
-
51
- this._cliFeatures = features;
52
- return features;
53
- }
54
-
55
- getCredentialPaths() {
56
- return ['~/.local/share/opencode'];
57
- }
58
-
59
- buildCommand(context, options) {
60
- const cliFeatures = options.cliFeatures || {};
61
-
62
- if (options.modelSpec?.reasoningEffort && cliFeatures.supportsVariant === false) {
63
- this._warnOnce(
64
- 'opencode-variant',
65
- 'Opencode CLI does not support --variant; skipping reasoningEffort.'
66
- );
67
- }
68
-
69
- return buildCommand(context, { ...options, cliFeatures });
70
- }
71
-
72
- parseEvent(line) {
73
- return parseEvent(line);
74
- }
75
-
76
- getModelCatalog() {
77
- return MODEL_CATALOG;
78
- }
79
-
80
- getLevelMapping() {
81
- return LEVEL_MAPPING;
82
- }
83
-
84
- getDefaultLevel() {
85
- return DEFAULT_LEVEL;
86
- }
87
-
88
- getDefaultMaxLevel() {
89
- return DEFAULT_MAX_LEVEL;
90
- }
91
-
92
- getDefaultMinLevel() {
93
- return DEFAULT_MIN_LEVEL;
94
- }
95
-
96
- _warnOnce(key, message) {
97
- if (warned.has(key)) return;
98
- warned.add(key);
99
- console.warn(`⚠️ ${message}`);
100
- }
101
- }
102
-
103
- module.exports = OpencodeProvider;
@@ -1,55 +0,0 @@
1
- const MODEL_CATALOG = {
2
- 'opencode/big-pickle': { rank: 1 },
3
- 'opencode/glm-4.7-free': { rank: 1 },
4
- 'opencode/gpt-5-nano': { rank: 1 },
5
- 'opencode/grok-code': { rank: 1 },
6
- 'opencode/minimax-m2.1-free': { rank: 1 },
7
- 'google/gemini-1.5-flash': { rank: 1 },
8
- 'google/gemini-1.5-flash-8b': { rank: 1 },
9
- 'google/gemini-1.5-pro': { rank: 1 },
10
- 'google/gemini-2.0-flash': { rank: 1 },
11
- 'google/gemini-2.0-flash-lite': { rank: 1 },
12
- 'google/gemini-2.5-flash': { rank: 1 },
13
- 'google/gemini-2.5-flash-image': { rank: 1 },
14
- 'google/gemini-2.5-flash-image-preview': { rank: 1 },
15
- 'google/gemini-2.5-flash-lite': { rank: 1 },
16
- 'google/gemini-2.5-flash-lite-preview-06-17': { rank: 1 },
17
- 'google/gemini-2.5-flash-lite-preview-09-2025': { rank: 1 },
18
- 'google/gemini-2.5-flash-preview-04-17': { rank: 1 },
19
- 'google/gemini-2.5-flash-preview-05-20': { rank: 1 },
20
- 'google/gemini-2.5-flash-preview-09-2025': { rank: 1 },
21
- 'google/gemini-2.5-flash-preview-tts': { rank: 1 },
22
- 'google/gemini-2.5-pro': { rank: 1 },
23
- 'google/gemini-2.5-pro-preview-05-06': { rank: 1 },
24
- 'google/gemini-2.5-pro-preview-06-05': { rank: 1 },
25
- 'google/gemini-2.5-pro-preview-tts': { rank: 1 },
26
- 'google/gemini-3-flash-preview': { rank: 1 },
27
- 'google/gemini-3-pro-preview': { rank: 1 },
28
- 'google/gemini-embedding-001': { rank: 1 },
29
- 'google/gemini-flash-latest': { rank: 1 },
30
- 'google/gemini-flash-lite-latest': { rank: 1 },
31
- 'google/gemini-live-2.5-flash': { rank: 1 },
32
- 'google/gemini-live-2.5-flash-preview-native-audio': { rank: 1 },
33
- 'openai/gpt-5.1-codex-max': { rank: 1 },
34
- 'openai/gpt-5.1-codex-mini': { rank: 1 },
35
- 'openai/gpt-5.2': { rank: 1 },
36
- 'openai/gpt-5.2-codex': { rank: 1 },
37
- };
38
-
39
- const LEVEL_MAPPING = {
40
- level1: { rank: 1, model: null, reasoningEffort: 'low' },
41
- level2: { rank: 2, model: null, reasoningEffort: 'medium' },
42
- level3: { rank: 3, model: null, reasoningEffort: 'high' },
43
- };
44
-
45
- const DEFAULT_LEVEL = 'level2';
46
- const DEFAULT_MAX_LEVEL = 'level3';
47
- const DEFAULT_MIN_LEVEL = 'level1';
48
-
49
- module.exports = {
50
- MODEL_CATALOG,
51
- LEVEL_MAPPING,
52
- DEFAULT_LEVEL,
53
- DEFAULT_MAX_LEVEL,
54
- DEFAULT_MIN_LEVEL,
55
- };
@@ -1,122 +0,0 @@
1
- function parseToolPart(part) {
2
- const state = part.state || {};
3
-
4
- if (state.status === 'pending' || state.status === 'running') {
5
- return {
6
- type: 'tool_call',
7
- toolName: part.tool,
8
- toolId: part.callID,
9
- input: state.input || {},
10
- };
11
- }
12
-
13
- if (state.status === 'completed') {
14
- return {
15
- type: 'tool_result',
16
- toolId: part.callID,
17
- content: state.output || '',
18
- isError: false,
19
- };
20
- }
21
-
22
- if (state.status === 'error') {
23
- return {
24
- type: 'tool_result',
25
- toolId: part.callID,
26
- content: state.error || '',
27
- isError: true,
28
- };
29
- }
30
-
31
- return null;
32
- }
33
-
34
- function parseStepFinish(part) {
35
- const tokens = part.tokens || {};
36
- return {
37
- type: 'result',
38
- success: true,
39
- inputTokens: tokens.input || 0,
40
- outputTokens: tokens.output || 0,
41
- };
42
- }
43
-
44
- function parsePart(part) {
45
- if (!part || typeof part !== 'object') return null;
46
-
47
- if (part.type === 'text' && part.text) {
48
- return { type: 'text', text: part.text };
49
- }
50
-
51
- if (part.type === 'reasoning' && part.text) {
52
- return { type: 'thinking', text: part.text };
53
- }
54
-
55
- if (part.type === 'tool') {
56
- return parseToolPart(part);
57
- }
58
-
59
- if (part.type === 'step-finish') {
60
- return parseStepFinish(part);
61
- }
62
-
63
- return null;
64
- }
65
-
66
- function parseErrorEvent(event) {
67
- const error = event.error || {};
68
- return {
69
- type: 'result',
70
- success: false,
71
- error: error.data?.message || error.message || error.name || 'Unknown error',
72
- };
73
- }
74
-
75
- function parseEvent(line) {
76
- let event;
77
- try {
78
- event = JSON.parse(line);
79
- } catch {
80
- return null;
81
- }
82
-
83
- if (!event || typeof event !== 'object') return null;
84
-
85
- if (event.type === 'error') {
86
- return parseErrorEvent(event);
87
- }
88
-
89
- if (event.type === 'text' || event.type === 'step_start' || event.type === 'step_finish') {
90
- return parsePart(event.part || event);
91
- }
92
-
93
- if (event.type === 'message.part.updated') {
94
- return parsePart(event.properties?.part);
95
- }
96
-
97
- return null;
98
- }
99
-
100
- function parseChunk(chunk) {
101
- const events = [];
102
- const lines = chunk.split('\n');
103
-
104
- for (const line of lines) {
105
- if (!line.trim()) continue;
106
- const event = parseEvent(line);
107
- if (!event) continue;
108
- if (Array.isArray(event)) {
109
- events.push(...event);
110
- } else {
111
- events.push(event);
112
- }
113
- }
114
-
115
- return events;
116
- }
117
-
118
- module.exports = {
119
- parseEvent,
120
- parseChunk,
121
- parsePart,
122
- };
@@ -1,185 +0,0 @@
1
- /**
2
- * Sub-Cluster Schema
3
- *
4
- * Defines the structure for nested clusters where an agent can be replaced
5
- * with an entire cluster configuration, enabling recursive composition.
6
- *
7
- * Example:
8
- * {
9
- * "id": "implementation-cluster",
10
- * "type": "subcluster",
11
- * "config": {
12
- * "agents": [
13
- * { "id": "worker", "role": "implementation", ... },
14
- * { "id": "validator", "role": "validator", ... }
15
- * ]
16
- * },
17
- * "triggers": [{ "topic": "PLAN_READY" }],
18
- * "hooks": {
19
- * "onComplete": {
20
- * "action": "publish_message",
21
- * "config": { "topic": "IMPLEMENTATION_COMPLETE" }
22
- * }
23
- * }
24
- * }
25
- */
26
-
27
- /**
28
- * Validate sub-cluster agent configuration
29
- * @param {Object} agentConfig - Agent config with type: 'subcluster'
30
- * @param {Number} depth - Current nesting depth (for recursion limit)
31
- * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
32
- */
33
- const MAX_SUBCLUSTER_DEPTH = 5;
34
-
35
- function validateSubCluster(agentConfig, depth = 0) {
36
- const errors = [];
37
- const warnings = [];
38
-
39
- // Max nesting depth to prevent infinite recursion
40
- if (depth > MAX_SUBCLUSTER_DEPTH) {
41
- errors.push(
42
- `Sub-cluster '${agentConfig.id}' exceeds max nesting depth (${MAX_SUBCLUSTER_DEPTH})`
43
- );
44
- return buildValidationResult(errors, warnings);
45
- }
46
-
47
- // Validate required fields
48
- if (agentConfig.type !== 'subcluster') {
49
- errors.push(`Agent '${agentConfig.id}' must have type: 'subcluster'`);
50
- }
51
-
52
- if (!validateSubClusterConfig(agentConfig, depth, errors, warnings)) {
53
- return buildValidationResult(errors, warnings);
54
- }
55
-
56
- // Validate triggers (sub-cluster must have triggers to activate)
57
- validateSubClusterTriggers(agentConfig, errors);
58
-
59
- // Validate hooks structure
60
- validateSubClusterHooks(agentConfig, errors);
61
-
62
- // Check for context bridging configuration
63
- validateContextStrategy(agentConfig, errors);
64
-
65
- return buildValidationResult(errors, warnings);
66
- }
67
-
68
- function buildValidationResult(errors, warnings) {
69
- return {
70
- valid: errors.length === 0,
71
- errors,
72
- warnings,
73
- };
74
- }
75
-
76
- function validateSubClusterConfig(agentConfig, depth, errors, warnings) {
77
- if (!agentConfig.config) {
78
- errors.push(`Sub-cluster '${agentConfig.id}' missing config field`);
79
- return false;
80
- }
81
-
82
- if (!agentConfig.config.agents || !Array.isArray(agentConfig.config.agents)) {
83
- errors.push(`Sub-cluster '${agentConfig.id}' config.agents must be an array`);
84
- return false;
85
- }
86
-
87
- if (agentConfig.config.agents.length === 0) {
88
- errors.push(`Sub-cluster '${agentConfig.id}' config.agents cannot be empty`);
89
- return false;
90
- }
91
-
92
- // Recursively validate nested cluster config
93
- const configValidator = require('../config-validator');
94
- const childValidation = configValidator.validateConfig(agentConfig.config, depth + 1);
95
-
96
- if (!childValidation.valid) {
97
- errors.push(...childValidation.errors.map((e) => `Sub-cluster '${agentConfig.id}': ${e}`));
98
- }
99
-
100
- warnings.push(...childValidation.warnings.map((w) => `Sub-cluster '${agentConfig.id}': ${w}`));
101
- return true;
102
- }
103
-
104
- function validateSubClusterTriggers(agentConfig, errors) {
105
- if (!agentConfig.triggers || agentConfig.triggers.length === 0) {
106
- errors.push(`Sub-cluster '${agentConfig.id}' must have triggers to activate`);
107
- }
108
- }
109
-
110
- function validateSubClusterHooks(agentConfig, errors) {
111
- if (!agentConfig.hooks?.onComplete) {
112
- return;
113
- }
114
-
115
- const hook = agentConfig.hooks.onComplete;
116
- if (!hook.action) {
117
- errors.push(`Sub-cluster '${agentConfig.id}' onComplete hook missing action`);
118
- }
119
- if (hook.action === 'publish_message' && !hook.config?.topic) {
120
- errors.push(`Sub-cluster '${agentConfig.id}' onComplete hook missing config.topic`);
121
- }
122
- }
123
-
124
- function validateContextStrategy(agentConfig, errors) {
125
- const parentTopics = agentConfig.contextStrategy?.parentTopics;
126
- if (!parentTopics) {
127
- return;
128
- }
129
-
130
- if (!Array.isArray(parentTopics)) {
131
- errors.push(`Sub-cluster '${agentConfig.id}' contextStrategy.parentTopics must be an array`);
132
- return;
133
- }
134
-
135
- // Validate each parent topic is a string
136
- for (const topic of parentTopics) {
137
- if (typeof topic !== 'string') {
138
- errors.push(
139
- `Sub-cluster '${agentConfig.id}' parentTopics must contain strings, got ${typeof topic}`
140
- );
141
- }
142
- }
143
- }
144
-
145
- /**
146
- * Get default sub-cluster template
147
- * @returns {Object} Default sub-cluster configuration
148
- */
149
- function getDefaultSubCluster() {
150
- return {
151
- id: 'example-subcluster',
152
- type: 'subcluster',
153
- role: 'orchestrator',
154
- config: {
155
- agents: [
156
- {
157
- id: 'worker',
158
- role: 'implementation',
159
- triggers: [{ topic: 'PARENT_TRIGGER' }],
160
- hooks: {
161
- onComplete: {
162
- action: 'publish_message',
163
- config: { topic: 'WORK_COMPLETE' },
164
- },
165
- },
166
- },
167
- ],
168
- },
169
- triggers: [{ topic: 'START_WORK' }],
170
- hooks: {
171
- onComplete: {
172
- action: 'publish_message',
173
- config: { topic: 'SUBCLUSTER_COMPLETE' },
174
- },
175
- },
176
- contextStrategy: {
177
- parentTopics: ['ISSUE_OPENED', 'PLAN_READY'],
178
- },
179
- };
180
- }
181
-
182
- module.exports = {
183
- validateSubCluster,
184
- getDefaultSubCluster,
185
- };