@covibes/zeroshot 5.2.1 → 5.3.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 (62) hide show
  1. package/CHANGELOG.md +174 -189
  2. package/README.md +199 -248
  3. package/cli/commands/providers.js +150 -0
  4. package/cli/index.js +214 -58
  5. package/cli/lib/first-run.js +40 -3
  6. package/cluster-templates/base-templates/debug-workflow.json +24 -78
  7. package/cluster-templates/base-templates/full-workflow.json +44 -145
  8. package/cluster-templates/base-templates/single-worker.json +23 -15
  9. package/cluster-templates/base-templates/worker-validator.json +47 -34
  10. package/cluster-templates/conductor-bootstrap.json +7 -5
  11. package/lib/docker-config.js +6 -1
  12. package/lib/provider-detection.js +59 -0
  13. package/lib/provider-names.js +56 -0
  14. package/lib/settings.js +191 -6
  15. package/lib/stream-json-parser.js +4 -238
  16. package/package.json +21 -5
  17. package/scripts/validate-templates.js +100 -0
  18. package/src/agent/agent-config.js +37 -13
  19. package/src/agent/agent-context-builder.js +64 -2
  20. package/src/agent/agent-hook-executor.js +82 -9
  21. package/src/agent/agent-lifecycle.js +53 -14
  22. package/src/agent/agent-task-executor.js +196 -194
  23. package/src/agent/output-extraction.js +200 -0
  24. package/src/agent/output-reformatter.js +175 -0
  25. package/src/agent/schema-utils.js +111 -0
  26. package/src/agent-wrapper.js +102 -30
  27. package/src/agents/git-pusher-agent.json +1 -1
  28. package/src/claude-task-runner.js +80 -30
  29. package/src/config-router.js +13 -13
  30. package/src/config-validator.js +231 -10
  31. package/src/github.js +36 -0
  32. package/src/isolation-manager.js +243 -154
  33. package/src/ledger.js +28 -6
  34. package/src/orchestrator.js +391 -96
  35. package/src/preflight.js +85 -82
  36. package/src/providers/anthropic/cli-builder.js +45 -0
  37. package/src/providers/anthropic/index.js +134 -0
  38. package/src/providers/anthropic/models.js +23 -0
  39. package/src/providers/anthropic/output-parser.js +159 -0
  40. package/src/providers/base-provider.js +181 -0
  41. package/src/providers/capabilities.js +51 -0
  42. package/src/providers/google/cli-builder.js +55 -0
  43. package/src/providers/google/index.js +116 -0
  44. package/src/providers/google/models.js +24 -0
  45. package/src/providers/google/output-parser.js +92 -0
  46. package/src/providers/index.js +75 -0
  47. package/src/providers/openai/cli-builder.js +122 -0
  48. package/src/providers/openai/index.js +135 -0
  49. package/src/providers/openai/models.js +21 -0
  50. package/src/providers/openai/output-parser.js +129 -0
  51. package/src/sub-cluster-wrapper.js +18 -3
  52. package/src/task-runner.js +8 -6
  53. package/src/tui/layout.js +20 -3
  54. package/task-lib/attachable-watcher.js +80 -78
  55. package/task-lib/claude-recovery.js +119 -0
  56. package/task-lib/commands/list.js +1 -1
  57. package/task-lib/commands/resume.js +3 -2
  58. package/task-lib/commands/run.js +12 -3
  59. package/task-lib/runner.js +59 -38
  60. package/task-lib/scheduler.js +2 -2
  61. package/task-lib/store.js +43 -30
  62. package/task-lib/watcher.js +81 -62
@@ -0,0 +1,181 @@
1
+ /**
2
+ * BaseProvider - Abstract provider interface
3
+ *
4
+ * All providers support two execution paths:
5
+ * 1. SDK (fast) - Direct API calls using provider's SDK (requires API key)
6
+ * 2. CLI (slower) - Spawns provider's CLI tool (uses OAuth/login auth)
7
+ *
8
+ * Use callSimple() for simple prompts - it tries SDK first, falls back to CLI.
9
+ */
10
+ class BaseProvider {
11
+ constructor(options = {}) {
12
+ this.name = options.name || 'base';
13
+ this.displayName = options.displayName || 'Base';
14
+ this.cliCommand = options.cliCommand || null;
15
+ }
16
+
17
+ // ============================================================================
18
+ // SDK SUPPORT (Future Extension Point)
19
+ // ============================================================================
20
+ //
21
+ // SDK support is NOT IMPLEMENTED. These methods exist for future extension.
22
+ // Currently, all providers use CLI (claude, codex, gemini) for execution.
23
+ //
24
+ // To add SDK support for a provider:
25
+ // 1. Override getSDKEnvVar() to return the API key env var
26
+ // 2. Override callSDK() to implement the actual API call
27
+ // 3. The callSimple() method will then work automatically
28
+ //
29
+ // ============================================================================
30
+
31
+ /**
32
+ * Get the environment variable name for the API key
33
+ * @returns {string} Environment variable name (e.g., 'ANTHROPIC_API_KEY')
34
+ */
35
+ getSDKEnvVar() {
36
+ throw new Error(`${this.name}: SDK not implemented. Use CLI instead.`);
37
+ }
38
+
39
+ /**
40
+ * Check if SDK is configured (API key is set)
41
+ * @returns {boolean} True if API key is available
42
+ */
43
+ isSDKConfigured() {
44
+ try {
45
+ const envVar = this.getSDKEnvVar();
46
+ return !!process.env[envVar];
47
+ } catch {
48
+ // getSDKEnvVar() throws if not implemented
49
+ return false;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Make a simple API call via SDK (fast path)
55
+ * NOT IMPLEMENTED - exists for future extension.
56
+ *
57
+ * @param {string} _prompt - The prompt to send
58
+ * @param {Object} _options - Call options
59
+ * @returns {Promise<{success: boolean, text: string, usage?: Object, error?: string}>}
60
+ */
61
+ callSDK(_prompt, _options) {
62
+ return Promise.reject(new Error(`${this.name}: SDK not implemented. Use CLI instead.`));
63
+ }
64
+
65
+ /**
66
+ * Make a simple API call via SDK
67
+ * NOT IMPLEMENTED - exists for future extension.
68
+ *
69
+ * @param {string} _prompt - The prompt to send
70
+ * @param {Object} _options - Call options
71
+ * @returns {Promise<{success: boolean, text: string, usage?: Object, error?: string}>}
72
+ */
73
+ callSimple(_prompt, _options = {}) {
74
+ return Promise.reject(new Error(`${this.name}: SDK not implemented. Use CLI instead.`));
75
+ }
76
+
77
+ isAvailable() {
78
+ throw new Error('Not implemented');
79
+ }
80
+
81
+ getCliPath() {
82
+ throw new Error('Not implemented');
83
+ }
84
+
85
+ getInstallInstructions() {
86
+ throw new Error('Not implemented');
87
+ }
88
+
89
+ getAuthInstructions() {
90
+ throw new Error('Not implemented');
91
+ }
92
+
93
+ getCliFeatures() {
94
+ throw new Error('Not implemented');
95
+ }
96
+
97
+ getCredentialPaths() {
98
+ return [];
99
+ }
100
+
101
+ buildCommand(_context, _options) {
102
+ throw new Error('Not implemented');
103
+ }
104
+
105
+ parseEvent(_line) {
106
+ throw new Error('Not implemented');
107
+ }
108
+
109
+ getModelCatalog() {
110
+ throw new Error('Not implemented');
111
+ }
112
+
113
+ getLevelMapping() {
114
+ throw new Error('Not implemented');
115
+ }
116
+
117
+ resolveModelSpec(level, overrides = {}) {
118
+ const mapping = this.getLevelMapping();
119
+ const base = mapping[level] || mapping[this.getDefaultLevel()];
120
+ if (!base) {
121
+ throw new Error(`Unknown level "${level}" for provider "${this.name}"`);
122
+ }
123
+ const override = overrides[level] || {};
124
+ return {
125
+ level,
126
+ model: override.model || base.model,
127
+ reasoningEffort: override.reasoningEffort || base.reasoningEffort,
128
+ };
129
+ }
130
+
131
+ validateLevel(level, minLevel, maxLevel) {
132
+ const mapping = this.getLevelMapping();
133
+ const rank = (key) => mapping[key]?.rank;
134
+
135
+ if (!mapping[level]) {
136
+ throw new Error(`Invalid level "${level}" for provider "${this.name}"`);
137
+ }
138
+
139
+ if (minLevel && !mapping[minLevel]) {
140
+ throw new Error(`Invalid minLevel "${minLevel}" for provider "${this.name}"`);
141
+ }
142
+
143
+ if (maxLevel && !mapping[maxLevel]) {
144
+ throw new Error(`Invalid maxLevel "${maxLevel}" for provider "${this.name}"`);
145
+ }
146
+
147
+ if (minLevel && maxLevel && rank(minLevel) > rank(maxLevel)) {
148
+ throw new Error(
149
+ `minLevel "${minLevel}" exceeds maxLevel "${maxLevel}" for provider "${this.name}"`
150
+ );
151
+ }
152
+
153
+ if (maxLevel && rank(level) > rank(maxLevel)) {
154
+ throw new Error(
155
+ `Level "${level}" exceeds maxLevel "${maxLevel}" for provider "${this.name}"`
156
+ );
157
+ }
158
+
159
+ if (minLevel && rank(level) < rank(minLevel)) {
160
+ throw new Error(
161
+ `Level "${level}" is below minLevel "${minLevel}" for provider "${this.name}"`
162
+ );
163
+ }
164
+
165
+ return level;
166
+ }
167
+
168
+ validateModelId(modelId) {
169
+ const catalog = this.getModelCatalog();
170
+ if (modelId && !catalog[modelId]) {
171
+ throw new Error(`Invalid model "${modelId}" for provider "${this.name}"`);
172
+ }
173
+ return modelId;
174
+ }
175
+
176
+ getDefaultLevel() {
177
+ throw new Error('Not implemented');
178
+ }
179
+ }
180
+
181
+ module.exports = BaseProvider;
@@ -0,0 +1,51 @@
1
+ const { normalizeProviderName } = require('../../lib/provider-names');
2
+
3
+ const CAPABILITIES = {
4
+ claude: {
5
+ dockerIsolation: true,
6
+ worktreeIsolation: true,
7
+ mcpServers: true,
8
+ jsonSchema: true,
9
+ streamJson: true,
10
+ thinkingMode: true,
11
+ reasoningEffort: false,
12
+ },
13
+ codex: {
14
+ dockerIsolation: true,
15
+ worktreeIsolation: true,
16
+ mcpServers: true,
17
+ jsonSchema: true,
18
+ streamJson: true,
19
+ thinkingMode: true,
20
+ reasoningEffort: true,
21
+ },
22
+ gemini: {
23
+ dockerIsolation: true,
24
+ worktreeIsolation: true,
25
+ mcpServers: true,
26
+ jsonSchema: 'experimental',
27
+ streamJson: true,
28
+ thinkingMode: true,
29
+ reasoningEffort: false,
30
+ },
31
+ };
32
+
33
+ function checkCapability(provider, capability) {
34
+ const caps = CAPABILITIES[normalizeProviderName(provider)];
35
+ if (!caps) return false;
36
+ return caps[capability] === true;
37
+ }
38
+
39
+ function warnIfExperimental(provider, capability) {
40
+ const normalized = normalizeProviderName(provider);
41
+ const caps = CAPABILITIES[normalized];
42
+ if (caps?.[capability] === 'experimental') {
43
+ console.warn(`⚠️ ${capability} is experimental for ${normalized} and may not work reliably`);
44
+ }
45
+ }
46
+
47
+ module.exports = {
48
+ CAPABILITIES,
49
+ checkCapability,
50
+ warnIfExperimental,
51
+ };
@@ -0,0 +1,55 @@
1
+ function buildCommand(context, options = {}) {
2
+ const { modelSpec, outputFormat, jsonSchema, cwd, autoApprove, cliFeatures = {} } = options;
3
+
4
+ // Augment context with schema if provided (Gemini CLI doesn't support native schema enforcement)
5
+ let finalContext = context;
6
+ if (jsonSchema) {
7
+ // CRITICAL: Inject schema into prompt since Gemini CLI has no --output-schema flag
8
+ // Without this, model outputs free-form text instead of JSON
9
+ const schemaStr =
10
+ typeof jsonSchema === 'string' ? jsonSchema : JSON.stringify(jsonSchema, null, 2);
11
+ finalContext =
12
+ context +
13
+ `\n\n## OUTPUT FORMAT (CRITICAL - REQUIRED)
14
+
15
+ You MUST respond with a JSON object that exactly matches this schema. NO markdown, NO explanation, NO code blocks. ONLY the raw JSON object.
16
+
17
+ Schema:
18
+ \`\`\`json
19
+ ${schemaStr}
20
+ \`\`\`
21
+
22
+ Your response must be ONLY valid JSON. Start with { and end with }. Nothing else.`;
23
+ }
24
+
25
+ const args = ['-p', finalContext];
26
+
27
+ if (
28
+ (outputFormat === 'stream-json' || outputFormat === 'json') &&
29
+ cliFeatures.supportsStreamJson
30
+ ) {
31
+ args.push('--output-format', 'stream-json');
32
+ }
33
+
34
+ if (modelSpec?.model) {
35
+ args.push('-m', modelSpec.model);
36
+ }
37
+
38
+ if (cwd && cliFeatures.supportsCwd) {
39
+ args.push('--cwd', cwd);
40
+ }
41
+
42
+ if (autoApprove && cliFeatures.supportsAutoApprove) {
43
+ args.push('--yolo');
44
+ }
45
+
46
+ return {
47
+ binary: 'gemini',
48
+ args,
49
+ env: {},
50
+ };
51
+ }
52
+
53
+ module.exports = {
54
+ buildCommand,
55
+ };
@@ -0,0 +1,116 @@
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 GoogleProvider extends BaseProvider {
16
+ constructor() {
17
+ super({ name: 'gemini', displayName: 'Gemini', cliCommand: 'gemini' });
18
+ this._cliFeatures = null;
19
+ this._unknownEventCounts = new Map();
20
+ this._parserState = { lastToolId: null };
21
+ }
22
+
23
+ // SDK not implemented - uses CLI only
24
+ // See BaseProvider for SDK extension point documentation
25
+
26
+ isAvailable() {
27
+ return commandExists(this.cliCommand);
28
+ }
29
+
30
+ getCliPath() {
31
+ return getCommandPath(this.cliCommand) || this.cliCommand;
32
+ }
33
+
34
+ getInstallInstructions() {
35
+ return 'npm install -g @google/gemini-cli';
36
+ }
37
+
38
+ getAuthInstructions() {
39
+ return 'gemini auth login';
40
+ }
41
+
42
+ getCliFeatures() {
43
+ if (this._cliFeatures) return this._cliFeatures;
44
+ const help = getHelpOutput(this.cliCommand);
45
+ const unknown = !help;
46
+
47
+ const features = {
48
+ supportsStreamJson: unknown ? true : /--output-format\b/.test(help),
49
+ supportsAutoApprove: unknown ? true : /--yolo\b/.test(help),
50
+ supportsCwd: unknown ? true : /--cwd\b/.test(help),
51
+ supportsModel: unknown ? true : /\s-m\b/.test(help) || /--model\b/.test(help),
52
+ unknown,
53
+ };
54
+
55
+ this._cliFeatures = features;
56
+ return features;
57
+ }
58
+
59
+ getCredentialPaths() {
60
+ return ['~/.config/gcloud', '~/.config/gemini', '~/.gemini'];
61
+ }
62
+
63
+ buildCommand(context, options) {
64
+ const cliFeatures = options.cliFeatures || {};
65
+
66
+ if (options.autoApprove && cliFeatures.supportsAutoApprove === false) {
67
+ this._warnOnce(
68
+ 'gemini-auto-approve',
69
+ 'Gemini CLI does not support --yolo; continuing without auto-approve.'
70
+ );
71
+ }
72
+
73
+ return buildCommand(context, { ...options, cliFeatures });
74
+ }
75
+
76
+ parseEvent(line) {
77
+ return parseEvent(line, this._parserState, {
78
+ onUnknown: (type) => this._logUnknown(type),
79
+ });
80
+ }
81
+
82
+ getModelCatalog() {
83
+ return MODEL_CATALOG;
84
+ }
85
+
86
+ getLevelMapping() {
87
+ return LEVEL_MAPPING;
88
+ }
89
+
90
+ getDefaultLevel() {
91
+ return DEFAULT_LEVEL;
92
+ }
93
+
94
+ getDefaultMaxLevel() {
95
+ return DEFAULT_MAX_LEVEL;
96
+ }
97
+
98
+ getDefaultMinLevel() {
99
+ return DEFAULT_MIN_LEVEL;
100
+ }
101
+
102
+ _warnOnce(key, message) {
103
+ if (warned.has(key)) return;
104
+ warned.add(key);
105
+ console.warn(`⚠️ ${message}`);
106
+ }
107
+
108
+ _logUnknown(type) {
109
+ const current = this._unknownEventCounts.get(type) || 0;
110
+ if (current >= 5) return;
111
+ this._unknownEventCounts.set(type, current + 1);
112
+ console.debug(`[gemini] Unknown event type: ${type}`);
113
+ }
114
+ }
115
+
116
+ module.exports = GoogleProvider;
@@ -0,0 +1,24 @@
1
+ // Gemini CLI model names
2
+ // Model is optional - Gemini defaults to best available
3
+ const MODEL_CATALOG = {
4
+ 'gemini-2.5-pro': { rank: 3 },
5
+ 'gemini-2.0-flash': { rank: 1 },
6
+ };
7
+
8
+ const LEVEL_MAPPING = {
9
+ level1: { rank: 1, model: null },
10
+ level2: { rank: 2, model: null },
11
+ level3: { rank: 3, model: null },
12
+ };
13
+
14
+ const DEFAULT_LEVEL = 'level2';
15
+ const DEFAULT_MAX_LEVEL = 'level3';
16
+ const DEFAULT_MIN_LEVEL = 'level1';
17
+
18
+ module.exports = {
19
+ MODEL_CATALOG,
20
+ LEVEL_MAPPING,
21
+ DEFAULT_LEVEL,
22
+ DEFAULT_MAX_LEVEL,
23
+ DEFAULT_MIN_LEVEL,
24
+ };
@@ -0,0 +1,92 @@
1
+ function normalizeMessageContent(content) {
2
+ if (typeof content === 'string') return content;
3
+ if (Array.isArray(content)) {
4
+ return content.map((item) => (typeof item === 'string' ? item : item?.text || '')).join('');
5
+ }
6
+ if (content && typeof content === 'object') {
7
+ if (typeof content.text === 'string') return content.text;
8
+ }
9
+ return '';
10
+ }
11
+
12
+ function parseEvent(line, state = {}, options = {}) {
13
+ let event;
14
+ try {
15
+ event = JSON.parse(line);
16
+ } catch {
17
+ return null;
18
+ }
19
+
20
+ switch (event.type) {
21
+ case 'init':
22
+ return null;
23
+
24
+ case 'message':
25
+ if (event.role === 'assistant') {
26
+ const text = normalizeMessageContent(event.content);
27
+ if (text) {
28
+ return { type: 'text', text };
29
+ }
30
+ }
31
+ return null;
32
+
33
+ case 'tool_use': {
34
+ const toolId = event.tool_call_id || event.tool_id || event.id || state.lastToolId;
35
+ const toolName = event.tool_name || event.name;
36
+ state.lastToolId = toolId;
37
+ return {
38
+ type: 'tool_call',
39
+ toolName,
40
+ toolId,
41
+ input: event.parameters || event.input || {},
42
+ };
43
+ }
44
+
45
+ case 'tool_result': {
46
+ const toolId = event.tool_call_id || event.tool_id || event.id || state.lastToolId;
47
+ return {
48
+ type: 'tool_result',
49
+ toolId,
50
+ content: event.output ?? event.content ?? '',
51
+ isError: event.success === false,
52
+ };
53
+ }
54
+
55
+ case 'result':
56
+ return {
57
+ type: 'result',
58
+ success: event.success !== false,
59
+ result: event.result || '',
60
+ error: event.success === false ? event.error || 'Result failed' : null,
61
+ };
62
+
63
+ default:
64
+ if (options.onUnknown) {
65
+ options.onUnknown(event.type, event);
66
+ }
67
+ return null;
68
+ }
69
+ }
70
+
71
+ function parseChunk(chunk, state = {}, options = {}) {
72
+ const events = [];
73
+ const lines = chunk.split('\n');
74
+
75
+ for (const line of lines) {
76
+ if (!line.trim()) continue;
77
+ const event = parseEvent(line, state, options);
78
+ if (!event) continue;
79
+ if (Array.isArray(event)) {
80
+ events.push(...event);
81
+ } else {
82
+ events.push(event);
83
+ }
84
+ }
85
+
86
+ return events;
87
+ }
88
+
89
+ module.exports = {
90
+ parseEvent,
91
+ parseChunk,
92
+ };
@@ -0,0 +1,75 @@
1
+ const AnthropicProvider = require('./anthropic');
2
+ const OpenAIProvider = require('./openai');
3
+ const GoogleProvider = require('./google');
4
+ const { normalizeProviderName } = require('../../lib/provider-names');
5
+
6
+ const PROVIDERS = {
7
+ claude: AnthropicProvider,
8
+ codex: OpenAIProvider,
9
+ gemini: GoogleProvider,
10
+ };
11
+
12
+ function getProvider(name) {
13
+ const normalized = normalizeProviderName(name || '');
14
+ const Provider = PROVIDERS[normalized];
15
+ if (!Provider) {
16
+ throw new Error(`Unknown provider: ${name}. Valid: ${Object.keys(PROVIDERS).join(', ')}`);
17
+ }
18
+ return new Provider();
19
+ }
20
+
21
+ async function detectProviders() {
22
+ const results = {};
23
+ for (const [name, Provider] of Object.entries(PROVIDERS)) {
24
+ const provider = new Provider();
25
+ results[name] = {
26
+ available: await provider.isAvailable(),
27
+ };
28
+ }
29
+ return results;
30
+ }
31
+
32
+ function listProviders() {
33
+ return Object.keys(PROVIDERS);
34
+ }
35
+
36
+ function stripTimestampPrefix(line) {
37
+ if (!line || typeof line !== 'string') return '';
38
+ const trimmed = line.trim().replace(/\r$/, '');
39
+ if (!trimmed) return '';
40
+ const match = trimmed.match(/^\[(\d{13})\](.*)$/);
41
+ return match ? match[2] : trimmed;
42
+ }
43
+
44
+ function parseChunkWithProvider(provider, chunk) {
45
+ if (!chunk) return [];
46
+ const events = [];
47
+ const lines = chunk.split('\n');
48
+
49
+ for (const line of lines) {
50
+ const content = stripTimestampPrefix(line);
51
+ if (!content) continue;
52
+ const event = provider.parseEvent(content);
53
+ if (!event) continue;
54
+ if (Array.isArray(event)) {
55
+ events.push(...event);
56
+ } else {
57
+ events.push(event);
58
+ }
59
+ }
60
+
61
+ return events;
62
+ }
63
+
64
+ function parseProviderChunk(providerName, chunk) {
65
+ const provider = getProvider(providerName || 'claude');
66
+ return parseChunkWithProvider(provider, chunk);
67
+ }
68
+
69
+ module.exports = {
70
+ getProvider,
71
+ detectProviders,
72
+ listProviders,
73
+ parseProviderChunk,
74
+ parseChunkWithProvider,
75
+ };