@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,246 +0,0 @@
1
- /**
2
- * AgentConfig - Agent configuration validation and defaults
3
- *
4
- * Provides:
5
- * - Config validation and normalization
6
- * - Default values for optional fields
7
- * - Model configuration setup
8
- * - Safety checks for test mode
9
- * - maxModel ceiling enforcement at config time
10
- */
11
-
12
- const { loadSettings, validateModelAgainstMax, VALID_MODELS } = require('../../lib/settings');
13
-
14
- const VALID_LEVELS = ['level1', 'level2', 'level3'];
15
-
16
- // Default max iterations (high limit - let the user decide when to give up)
17
- const DEFAULT_MAX_ITERATIONS = 100;
18
-
19
- // Default timeout: 0 = no timeout (task runs until completion or explicit kill)
20
- // Use positive number for timeout in milliseconds
21
- const DEFAULT_TIMEOUT = 0;
22
-
23
- // Stale detection - ENABLED by default using multi-indicator analysis (safe from false positives)
24
- // Multi-indicator approach checks: process state, CPU usage, context switches, network I/O
25
- // Only flags as stuck when ALL indicators show inactivity (score >= 3.5)
26
- // Single-indicator detection (just output freshness) was too risky - this is safe.
27
- const DEFAULT_STALE_DURATION_MS = 30 * 60 * 1000; // 30 minutes before triggering analysis
28
- const DEFAULT_LIVENESS_CHECK_ENABLED = true; // Safe with multi-indicator detection
29
-
30
- function applyOutputDefaults(config) {
31
- // CRITICAL: Enforce JSON schema output by default to prevent parse failures and crashes
32
- if (!config.outputFormat) {
33
- config.outputFormat = 'json';
34
- }
35
-
36
- // If outputFormat is json but no schema defined, use a minimal default schema
37
- if (config.outputFormat === 'json' && !config.jsonSchema) {
38
- config.jsonSchema = {
39
- type: 'object',
40
- properties: {
41
- summary: {
42
- type: 'string',
43
- description: 'Brief summary of what was done',
44
- },
45
- result: {
46
- type: 'string',
47
- description: 'Detailed result or output',
48
- },
49
- },
50
- required: ['summary', 'result'],
51
- };
52
- }
53
- }
54
-
55
- function buildModelConfig(config) {
56
- if (config.modelRules) {
57
- return { type: 'rules', rules: config.modelRules };
58
- }
59
- return {
60
- type: 'static',
61
- model: config.model || null,
62
- modelLevel: config.modelLevel || null,
63
- };
64
- }
65
-
66
- function applyStrictSchemaDefault(config, settings) {
67
- if (config.strictSchema === undefined) {
68
- config.strictSchema = settings.strictSchema !== false; // Default true if not set
69
- }
70
- }
71
-
72
- function validateStaticModelConfig(configId, modelConfig, maxModel, minModel) {
73
- if (modelConfig.model && VALID_MODELS.includes(modelConfig.model)) {
74
- try {
75
- validateModelAgainstMax(modelConfig.model, maxModel, minModel);
76
- } catch (error) {
77
- throw new Error(`Agent "${configId}": ${error.message}`);
78
- }
79
- }
80
-
81
- if (modelConfig.modelLevel && !VALID_LEVELS.includes(modelConfig.modelLevel)) {
82
- throw new Error(
83
- `Agent "${configId}": invalid modelLevel "${modelConfig.modelLevel}". ` +
84
- `Valid: ${VALID_LEVELS.join(', ')}`
85
- );
86
- }
87
- }
88
-
89
- function validateModelRule(configId, rule, maxModel, minModel) {
90
- if (rule.model && VALID_MODELS.includes(rule.model)) {
91
- try {
92
- validateModelAgainstMax(rule.model, maxModel, minModel);
93
- } catch {
94
- throw new Error(
95
- `Agent "${configId}": modelRule "${rule.iterations}" requests "${rule.model}" ` +
96
- `but maxModel is "${maxModel}"${minModel ? ` and minModel is "${minModel}"` : ''}. ` +
97
- `Either adjust the rule's model or change maxModel/minModel settings.`
98
- );
99
- }
100
- }
101
-
102
- if (rule.modelLevel && !VALID_LEVELS.includes(rule.modelLevel)) {
103
- throw new Error(
104
- `Agent "${configId}": modelRule "${rule.iterations}" has invalid modelLevel ` +
105
- `"${rule.modelLevel}". Valid: ${VALID_LEVELS.join(', ')}`
106
- );
107
- }
108
- }
109
-
110
- function validateModelConfig(config, modelConfig, maxModel, minModel) {
111
- if (modelConfig.type === 'static') {
112
- validateStaticModelConfig(config.id, modelConfig, maxModel, minModel);
113
- return;
114
- }
115
-
116
- if (modelConfig.type === 'rules') {
117
- for (const rule of modelConfig.rules) {
118
- validateModelRule(config.id, rule, maxModel, minModel);
119
- }
120
- }
121
- }
122
-
123
- function buildPromptConfig(config) {
124
- if (config.prompt?.iterations) {
125
- return { type: 'rules', rules: config.prompt.iterations };
126
- }
127
-
128
- if (config.prompt?.initial || config.prompt?.subsequent) {
129
- const rules = [];
130
- if (config.prompt.initial) rules.push({ match: '1', system: config.prompt.initial });
131
- if (config.prompt.subsequent) rules.push({ match: '2+', system: config.prompt.subsequent });
132
- return { type: 'rules', rules };
133
- }
134
-
135
- if (typeof config.prompt === 'string') {
136
- return { type: 'static', system: config.prompt };
137
- }
138
-
139
- if (config.prompt?.system) {
140
- return { type: 'static', system: config.prompt.system };
141
- }
142
-
143
- if (config.prompt) {
144
- throw new Error(`Agent "${config.id}": invalid prompt format`);
145
- }
146
-
147
- return null;
148
- }
149
-
150
- function normalizeTimeout(config) {
151
- if (config.timeout === undefined || config.timeout === null || config.timeout === '') {
152
- config.timeout = DEFAULT_TIMEOUT;
153
- } else {
154
- config.timeout = Number(config.timeout);
155
- }
156
-
157
- if (!Number.isFinite(config.timeout) || config.timeout < 0) {
158
- throw new Error(
159
- `Agent "${config.id}": timeout must be a non-negative number (got ${config.timeout}).`
160
- );
161
- }
162
-
163
- return config.timeout;
164
- }
165
-
166
- function buildNormalizedConfig(config, modelConfig, promptConfig) {
167
- return {
168
- ...config,
169
- modelConfig,
170
- promptConfig,
171
- maxIterations: config.maxIterations || DEFAULT_MAX_ITERATIONS,
172
- timeout: config.timeout,
173
- staleDuration: config.staleDuration || DEFAULT_STALE_DURATION_MS,
174
- enableLivenessCheck: config.enableLivenessCheck ?? DEFAULT_LIVENESS_CHECK_ENABLED,
175
- };
176
- }
177
-
178
- function assertTestModeSafety(config, options) {
179
- const executesTask = config.triggers?.some(
180
- (trigger) => !trigger.action || trigger.action === 'execute_task'
181
- );
182
- const hasMock = options.mockSpawnFn || options.taskRunner;
183
-
184
- if (options.testMode && !hasMock && executesTask) {
185
- throw new Error(
186
- `AgentWrapper: testMode=true but no mockSpawnFn/taskRunner provided for agent '${config.id}'. ` +
187
- `This would cause real Claude API calls. ABORTING.`
188
- );
189
- }
190
- }
191
-
192
- /**
193
- * Validate and normalize agent configuration
194
- * @param {Object} config - Raw agent configuration
195
- * @param {Object} options - Agent wrapper options
196
- * @returns {Object} Normalized configuration
197
- */
198
- function validateAgentConfig(config, options = {}) {
199
- applyOutputDefaults(config);
200
-
201
- // Model configuration: support both static model and dynamic rules
202
- // If no model specified, model is null - _selectModel() will use provider defaults
203
- const modelConfig = buildModelConfig(config);
204
-
205
- // COST CEILING/FLOOR ENFORCEMENT: Validate model(s) against maxModel and minModel at config time
206
- // Catches violations EARLY (config load) instead of at runtime (iteration N)
207
- const settings = loadSettings();
208
- const maxModel = settings.maxModel || 'sonnet';
209
- const minModel = settings.minModel || null;
210
-
211
- // STRICT SCHEMA PROPAGATION: Issue #52 fix
212
- // If agent config doesn't explicitly set strictSchema, inherit from global settings
213
- // This allows `zeroshot settings set strictSchema false` to actually affect agents
214
- // Default behavior: strictSchema=true (reliable JSON output, no streaming)
215
- applyStrictSchemaDefault(config, settings);
216
- validateModelConfig(config, modelConfig, maxModel, minModel);
217
-
218
- // Prompt configuration: support static prompt OR iteration-based rules
219
- // Formats:
220
- // prompt: "string" -> static
221
- // prompt: { system: "string" } -> static
222
- // prompt: { initial: "...", subsequent: "..." } -> iteration 1 vs 2+
223
- // prompt: { iterations: [...] } -> full control
224
- const promptConfig = buildPromptConfig(config);
225
-
226
- // Default timeout to 0 (no timeout) if not specified
227
- // Use positive number for timeout in milliseconds
228
- // ROBUST: Handle undefined, null, AND string values from template resolution
229
- normalizeTimeout(config);
230
-
231
- // Build normalized config
232
- const normalizedConfig = buildNormalizedConfig(config, modelConfig, promptConfig);
233
-
234
- // SAFETY: In test mode, verify mock is provided for agents that execute tasks
235
- // Check if this agent executes tasks (vs orchestrator agents that only publish messages)
236
- assertTestModeSafety(config, options);
237
-
238
- return normalizedConfig;
239
- }
240
-
241
- module.exports = {
242
- validateAgentConfig,
243
- DEFAULT_MAX_ITERATIONS,
244
- DEFAULT_STALE_DURATION_MS,
245
- DEFAULT_LIVENESS_CHECK_ENABLED,
246
- };
@@ -1,444 +0,0 @@
1
- /**
2
- * AgentContextBuilder - Build agent execution context from ledger
3
- *
4
- * Provides:
5
- * - Context assembly from multiple message sources
6
- * - Context strategy evaluation (topics, limits, since timestamps)
7
- * - Prompt injection and formatting
8
- * - Token-based truncation
9
- * - Defensive context overflow prevention
10
- */
11
-
12
- // Defensive limit: 500,000 chars ≈ 125k tokens (safe buffer below 200k limit)
13
- // Prevents "Prompt is too long" errors that kill tasks
14
- const MAX_CONTEXT_CHARS = 500000;
15
-
16
- /**
17
- * Generate an example object from a JSON schema
18
- * Used to show models a concrete example of expected output
19
- *
20
- * @param {object} schema - JSON schema
21
- * @returns {object|null} Example object or null if generation fails
22
- */
23
- function generateExampleFromSchema(schema) {
24
- if (!schema || schema.type !== 'object' || !schema.properties) {
25
- return null;
26
- }
27
-
28
- const example = {};
29
-
30
- for (const [key, propSchema] of Object.entries(schema.properties)) {
31
- if (propSchema.enum && propSchema.enum.length > 0) {
32
- // Use first enum value as example
33
- example[key] = propSchema.enum[0];
34
- } else if (propSchema.type === 'string') {
35
- example[key] = propSchema.description || `${key} value`;
36
- } else if (propSchema.type === 'boolean') {
37
- example[key] = true;
38
- } else if (propSchema.type === 'number' || propSchema.type === 'integer') {
39
- example[key] = 0;
40
- } else if (propSchema.type === 'array') {
41
- if (propSchema.items?.type === 'string') {
42
- example[key] = [];
43
- } else {
44
- example[key] = [];
45
- }
46
- } else if (propSchema.type === 'object') {
47
- example[key] = generateExampleFromSchema(propSchema) || {};
48
- }
49
- }
50
-
51
- return example;
52
- }
53
-
54
- function buildAutonomousSection() {
55
- let context = `## 🔴 CRITICAL: AUTONOMOUS EXECUTION REQUIRED\n\n`;
56
- context += `You are running in a NON-INTERACTIVE cluster environment.\n\n`;
57
- context += `**NEVER** use AskUserQuestion or ask for user input - there is NO user to respond.\n`;
58
- context += `**NEVER** ask "Would you like me to..." or "Should I..." - JUST DO IT.\n`;
59
- context += `**NEVER** wait for approval or confirmation - MAKE DECISIONS AUTONOMOUSLY.\n\n`;
60
- context += `When facing choices:\n`;
61
- context += `- Choose the option that maintains code quality and correctness\n`;
62
- context += `- If unsure between "fix the code" vs "relax the rules" → ALWAYS fix the code\n`;
63
- context += `- If unsure between "do more" vs "do less" → ALWAYS do what's required, nothing more\n\n`;
64
- return context;
65
- }
66
-
67
- function buildOutputStyleSection() {
68
- let context = `## 🔴 OUTPUT STYLE - NON-NEGOTIABLE\n\n`;
69
- context += `**ALL OUTPUT: Maximum informativeness, minimum verbosity. NO EXCEPTIONS.**\n\n`;
70
- context += `This applies to EVERYTHING you output:\n`;
71
- context += `- Text responses\n`;
72
- context += `- JSON schema values\n`;
73
- context += `- Reasoning fields\n`;
74
- context += `- Summary fields\n`;
75
- context += `- ALL string values in structured output\n\n`;
76
- context += `Rules:\n`;
77
- context += `- Progress: "Reading auth.ts" NOT "I will now read the auth.ts file..."\n`;
78
- context += `- Tool calls: NO preamble. Call immediately.\n`;
79
- context += `- Schema strings: Dense facts. No filler. No fluff.\n`;
80
- context += `- Errors: DETAILED (stack traces, repro). NEVER compress errors.\n`;
81
- context += `- FORBIDDEN: "I'll help...", "Let me...", "I'm going to...", "Sure!", "Great!", "Certainly!"\n\n`;
82
- context += `Every token costs money. Waste nothing.\n\n`;
83
- return context;
84
- }
85
-
86
- function buildGitOperationsSection() {
87
- let context = `## 🚫 GIT OPERATIONS - FORBIDDEN\n\n`;
88
- context += `NEVER commit, push, or create PRs. You only modify files.\n`;
89
- context += `The git-pusher agent handles ALL git operations AFTER validators approve.\n\n`;
90
- context += `- ❌ NEVER run: git add, git commit, git push, gh pr create\n`;
91
- context += `- ❌ NEVER suggest committing changes\n`;
92
- context += `- ✅ Only modify files and publish your completion message when done\n\n`;
93
- return context;
94
- }
95
-
96
- function buildHeaderContext({ id, role, iteration, isIsolated }) {
97
- let context = `You are agent "${id}" with role "${role}".\n\n`;
98
- context += `Iteration: ${iteration}\n\n`;
99
- context += buildAutonomousSection();
100
- context += buildOutputStyleSection();
101
- if (!isIsolated) {
102
- context += buildGitOperationsSection();
103
- }
104
- return context;
105
- }
106
-
107
- function buildInstructionsSection({ config, selectedPrompt, id }) {
108
- const promptText =
109
- selectedPrompt || (typeof config.prompt === 'string' ? config.prompt : config.prompt?.system);
110
-
111
- if (promptText) {
112
- return `## Instructions\n\n${promptText}\n\n`;
113
- }
114
-
115
- if (config.prompt && typeof config.prompt !== 'string' && !config.prompt?.system) {
116
- throw new Error(
117
- `Agent "${id}" has invalid prompt format. ` +
118
- `Expected string or object with .system property, got: ${JSON.stringify(config.prompt).slice(0, 100)}...`
119
- );
120
- }
121
-
122
- return '';
123
- }
124
-
125
- function buildLegacyOutputSchemaSection(config) {
126
- if (!config.prompt?.outputFormat) return '';
127
-
128
- let context = `## Output Schema (REQUIRED)\n\n`;
129
- context += `\`\`\`json\n${JSON.stringify(config.prompt.outputFormat.example, null, 2)}\n\`\`\`\n\n`;
130
- context += `STRING VALUES IN THIS SCHEMA: Dense. Factual. No filler words. No pleasantries.\n`;
131
- if (config.prompt.outputFormat.rules) {
132
- for (const rule of config.prompt.outputFormat.rules) {
133
- context += `- ${rule}\n`;
134
- }
135
- }
136
- context += '\n';
137
- return context;
138
- }
139
-
140
- function buildJsonSchemaSection(config) {
141
- if (!config.jsonSchema || config.outputFormat !== 'json') return '';
142
-
143
- let context = `## 🔴 OUTPUT FORMAT - JSON ONLY\n\n`;
144
- context += `Your response must be ONLY valid JSON. No other text before or after.\n`;
145
- context += `Start with { and end with }. Nothing else.\n\n`;
146
- context += `Required schema:\n`;
147
- context += `\`\`\`json\n${JSON.stringify(config.jsonSchema, null, 2)}\n\`\`\`\n\n`;
148
-
149
- const example = generateExampleFromSchema(config.jsonSchema);
150
- if (example) {
151
- context += `Example output:\n`;
152
- context += `\`\`\`json\n${JSON.stringify(example, null, 2)}\n\`\`\`\n\n`;
153
- }
154
-
155
- context += `CRITICAL RULES:\n`;
156
- context += `- Output ONLY the JSON object - no explanation, no thinking, no preamble\n`;
157
- context += `- Use EXACTLY the enum values specified (case-sensitive)\n`;
158
- context += `- Include ALL required fields\n\n`;
159
- return context;
160
- }
161
-
162
- function resolveSourceSince(source, cluster, lastTaskEndTime, lastAgentStartTime) {
163
- const sinceValue = source.since;
164
-
165
- if (sinceValue === 'cluster_start') {
166
- return cluster.createdAt;
167
- }
168
- if (sinceValue === 'last_task_end') {
169
- return lastTaskEndTime || cluster.createdAt;
170
- }
171
- if (sinceValue === 'last_agent_start') {
172
- return lastAgentStartTime || cluster.createdAt;
173
- }
174
-
175
- if (typeof sinceValue === 'string') {
176
- const parsed = Date.parse(sinceValue);
177
- if (Number.isNaN(parsed)) {
178
- throw new Error(
179
- `Agent context source for topic ${source.topic} has invalid since value "${sinceValue}". ` +
180
- 'Use cluster_start, last_task_end, last_agent_start, or an ISO timestamp.'
181
- );
182
- }
183
- return parsed;
184
- }
185
-
186
- return sinceValue;
187
- }
188
-
189
- function formatSourceMessagesSection(source, messages) {
190
- let context = `\n## Messages from topic: ${source.topic}\n\n`;
191
- for (const msg of messages) {
192
- context += `[${new Date(msg.timestamp).toISOString()}] ${msg.sender}:\n`;
193
- if (msg.content?.text) {
194
- context += `${msg.content.text}\n`;
195
- }
196
- if (msg.content?.data) {
197
- context += `Data: ${JSON.stringify(msg.content.data, null, 2)}\n`;
198
- }
199
- context += '\n';
200
- }
201
- return context;
202
- }
203
-
204
- function buildSourcesSection({
205
- strategy,
206
- messageBus,
207
- cluster,
208
- lastTaskEndTime,
209
- lastAgentStartTime,
210
- }) {
211
- let context = '';
212
- for (const source of strategy.sources) {
213
- const sinceTimestamp = resolveSourceSince(source, cluster, lastTaskEndTime, lastAgentStartTime);
214
- const messages = messageBus.query({
215
- cluster_id: cluster.id,
216
- topic: source.topic,
217
- sender: source.sender,
218
- since: sinceTimestamp,
219
- limit: source.limit,
220
- });
221
-
222
- if (messages.length > 0) {
223
- context += formatSourceMessagesSection(source, messages);
224
- }
225
- }
226
- return context;
227
- }
228
-
229
- function collectCannotValidateCriteria(prevValidations) {
230
- const cannotValidateCriteria = [];
231
- for (const msg of prevValidations) {
232
- const criteriaResults = msg.content?.data?.criteriaResults;
233
- if (!Array.isArray(criteriaResults)) continue;
234
- for (const cr of criteriaResults) {
235
- if (cr.status !== 'CANNOT_VALIDATE' || !cr.id) continue;
236
- if (cannotValidateCriteria.find((c) => c.id === cr.id)) continue;
237
- cannotValidateCriteria.push({
238
- id: cr.id,
239
- reason: cr.reason || 'No reason provided',
240
- });
241
- }
242
- }
243
- return cannotValidateCriteria;
244
- }
245
-
246
- function buildCannotValidateSection(cannotValidateCriteria) {
247
- if (cannotValidateCriteria.length === 0) return '';
248
-
249
- let context = `\n## ⚠️ Permanently Unverifiable Criteria (SKIP THESE)\n\n`;
250
- context += `The following criteria have PERMANENT environmental limitations (missing tools, no access).\n`;
251
- context += `These limitations have not changed. Do NOT re-attempt verification.\n`;
252
- context += `Mark these as CANNOT_VALIDATE again with the same reason.\n\n`;
253
- for (const cv of cannotValidateCriteria) {
254
- context += `- **${cv.id}**: ${cv.reason}\n`;
255
- }
256
- context += `\n`;
257
- return context;
258
- }
259
-
260
- function buildValidatorSkipSection({ role, messageBus, cluster }) {
261
- if (role !== 'validator') return '';
262
-
263
- const prevValidations = messageBus.query({
264
- cluster_id: cluster.id,
265
- topic: 'VALIDATION_RESULT',
266
- since: cluster.createdAt,
267
- limit: 50,
268
- });
269
-
270
- const cannotValidateCriteria = collectCannotValidateCriteria(prevValidations);
271
- return buildCannotValidateSection(cannotValidateCriteria);
272
- }
273
-
274
- function buildTriggeringMessageSection(triggeringMessage) {
275
- let context = `\n## Triggering Message\n\n`;
276
- context += `Topic: ${triggeringMessage.topic}\n`;
277
- context += `Sender: ${triggeringMessage.sender}\n`;
278
- if (triggeringMessage.content?.text) {
279
- context += `\n${triggeringMessage.content.text}\n`;
280
- }
281
- return context;
282
- }
283
-
284
- function findContextSectionIndices(lines) {
285
- let issueOpenedStart = -1;
286
- let issueOpenedEnd = -1;
287
- let triggeringStart = -1;
288
-
289
- for (let i = 0; i < lines.length; i++) {
290
- if (lines[i].includes('## Messages from topic: ISSUE_OPENED')) {
291
- issueOpenedStart = i;
292
- }
293
- if (issueOpenedStart !== -1 && issueOpenedEnd === -1 && lines[i].startsWith('## ')) {
294
- issueOpenedEnd = i;
295
- }
296
- if (lines[i].includes('## Triggering Message')) {
297
- triggeringStart = i;
298
- break;
299
- }
300
- }
301
-
302
- return { issueOpenedStart, issueOpenedEnd, triggeringStart };
303
- }
304
-
305
- function collectRecentLines(middleLines, budgetForRecent) {
306
- const recentLines = [];
307
- let recentSize = 0;
308
-
309
- for (let i = middleLines.length - 1; i >= 0; i--) {
310
- const line = middleLines[i];
311
- const lineSize = line.length + 1;
312
-
313
- if (recentSize + lineSize > budgetForRecent) {
314
- break;
315
- }
316
-
317
- recentLines.unshift(line);
318
- recentSize += lineSize;
319
- }
320
-
321
- return recentLines;
322
- }
323
-
324
- function truncateContextIfNeeded(context) {
325
- const originalLength = context.length;
326
- if (originalLength <= MAX_CONTEXT_CHARS) {
327
- return context;
328
- }
329
-
330
- console.log(
331
- `[Context] Context too large (${originalLength} chars), truncating to prevent overflow...`
332
- );
333
-
334
- const lines = context.split('\n');
335
- const { issueOpenedStart, issueOpenedEnd, triggeringStart } = findContextSectionIndices(lines);
336
-
337
- const headerEnd = issueOpenedStart !== -1 ? issueOpenedStart : triggeringStart;
338
- const header = lines.slice(0, headerEnd).join('\n');
339
-
340
- const issueOpened =
341
- issueOpenedStart !== -1 && issueOpenedEnd !== -1
342
- ? lines.slice(issueOpenedStart, issueOpenedEnd).join('\n')
343
- : '';
344
-
345
- const triggeringMsg = lines.slice(triggeringStart).join('\n');
346
-
347
- const fixedSize = header.length + issueOpened.length + triggeringMsg.length;
348
- const budgetForRecent = MAX_CONTEXT_CHARS - fixedSize - 200;
349
-
350
- const middleStart = issueOpenedEnd !== -1 ? issueOpenedEnd : headerEnd;
351
- const middleEnd = triggeringStart;
352
- const middleLines = lines.slice(middleStart, middleEnd);
353
- const recentLines = collectRecentLines(middleLines, budgetForRecent);
354
-
355
- const parts = [header];
356
- if (issueOpened) {
357
- parts.push(issueOpened);
358
- }
359
- if (recentLines.length < middleLines.length) {
360
- const truncatedCount = middleLines.length - recentLines.length;
361
- parts.push(
362
- `\n[...${truncatedCount} earlier context messages truncated to prevent overflow...]\n`
363
- );
364
- }
365
- if (recentLines.length > 0) {
366
- parts.push(recentLines.join('\n'));
367
- }
368
- parts.push(triggeringMsg);
369
-
370
- const truncatedContext = parts.join('\n');
371
- const truncatedLength = truncatedContext.length;
372
- console.log(
373
- `[Context] Truncated from ${originalLength} to ${truncatedLength} chars (${Math.round((truncatedLength / originalLength) * 100)}% retained)`
374
- );
375
-
376
- return truncatedContext;
377
- }
378
-
379
- function applyLegacyMaxTokens(context, strategy) {
380
- const maxTokens = strategy.maxTokens || 100000;
381
- const maxChars = maxTokens * 4;
382
- if (context.length > maxChars) {
383
- return context.slice(0, maxChars) + '\n\n[Context truncated...]';
384
- }
385
- return context;
386
- }
387
-
388
- /**
389
- * Build execution context for an agent
390
- * @param {object} params - Context building parameters
391
- * @param {string} params.id - Agent ID
392
- * @param {string} params.role - Agent role
393
- * @param {number} params.iteration - Current iteration number
394
- * @param {any} params.config - Agent configuration
395
- * @param {any} params.messageBus - Message bus for querying ledger
396
- * @param {any} params.cluster - Cluster object
397
- * @param {number} [params.lastTaskEndTime] - Timestamp of last task completion
398
- * @param {number} [params.lastAgentStartTime] - Timestamp when this agent last started work
399
- * @param {any} params.triggeringMessage - Message that triggered this execution
400
- * @param {string} [params.selectedPrompt] - Pre-selected prompt from _selectPrompt() (iteration-based)
401
- * @param {object} [params.worktree] - Worktree isolation state (if running in worktree mode)
402
- * @param {object} [params.isolation] - Docker isolation state (if running in Docker mode)
403
- * @returns {string} Assembled context string
404
- */
405
- function buildContext({
406
- id,
407
- role,
408
- iteration,
409
- config,
410
- messageBus,
411
- cluster,
412
- lastTaskEndTime,
413
- lastAgentStartTime,
414
- triggeringMessage,
415
- selectedPrompt,
416
- worktree,
417
- isolation,
418
- }) {
419
- const strategy = config.contextStrategy || { sources: [] };
420
- const isIsolated = !!(worktree?.enabled || isolation?.enabled);
421
-
422
- let context = buildHeaderContext({ id, role, iteration, isIsolated });
423
- context += buildInstructionsSection({ config, selectedPrompt, id });
424
- context += buildLegacyOutputSchemaSection(config);
425
- context += buildJsonSchemaSection(config);
426
- context += buildSourcesSection({
427
- strategy,
428
- messageBus,
429
- cluster,
430
- lastTaskEndTime,
431
- lastAgentStartTime,
432
- });
433
- context += buildValidatorSkipSection({ role, messageBus, cluster });
434
- context += buildTriggeringMessageSection(triggeringMessage);
435
-
436
- context = truncateContextIfNeeded(context);
437
- context = applyLegacyMaxTokens(context, strategy);
438
-
439
- return context;
440
- }
441
-
442
- module.exports = {
443
- buildContext,
444
- };