swarm-orchestrator 7.0.0-alpha.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.
- package/LICENSE +15 -0
- package/README.md +199 -0
- package/agents/reviewer.agent.md +50 -0
- package/agents/worker.agent.md +41 -0
- package/config/default-agents.yaml +56 -0
- package/config/pm-agent.yaml +36 -0
- package/config/quality-gates.yaml +88 -0
- package/config/repair-agent.yaml +34 -0
- package/config/semgrep-rules/complexity-mismatch.yaml +6 -0
- package/config/semgrep-rules/exception-swallowing.yaml +13 -0
- package/config/semgrep-rules/hardcoded-answer.yaml +6 -0
- package/config/semgrep-rules/mock-mutation.yaml +6 -0
- package/config/semgrep-rules/test-modification.yaml +6 -0
- package/config/user-agents.yaml +23 -0
- package/dist/src/adapters/adapter-factory.js +29 -0
- package/dist/src/adapters/agent-adapter.js +32 -0
- package/dist/src/adapters/claude-code-adapter.js +126 -0
- package/dist/src/adapters/claude-code-teams.js +119 -0
- package/dist/src/adapters/codex-adapter.js +118 -0
- package/dist/src/adapters/copilot-adapter.js +224 -0
- package/dist/src/adapters/fatal-error-classifier.js +88 -0
- package/dist/src/adapters/index.js +44 -0
- package/dist/src/adapters/persistent-session.js +204 -0
- package/dist/src/adapters/process-supervisor.js +199 -0
- package/dist/src/agents-exporter.js +398 -0
- package/dist/src/analytics-log.js +161 -0
- package/dist/src/baseline-scanner.js +65 -0
- package/dist/src/bootstrap-evidence.js +88 -0
- package/dist/src/bootstrap-orchestrator.js +205 -0
- package/dist/src/bootstrap-types.js +5 -0
- package/dist/src/branch-merger.js +427 -0
- package/dist/src/cli/attest-handlers.js +44 -0
- package/dist/src/cli/cost-prompt.js +51 -0
- package/dist/src/cli/demo-handlers.js +274 -0
- package/dist/src/cli/flags.js +152 -0
- package/dist/src/cli/index.js +57 -0
- package/dist/src/cli/live-status.js +186 -0
- package/dist/src/cli/misc-handlers.js +242 -0
- package/dist/src/cli/plan-handlers.js +350 -0
- package/dist/src/cli/share-handlers.js +163 -0
- package/dist/src/cli/status-handlers.js +457 -0
- package/dist/src/cli/swarm-handlers.js +638 -0
- package/dist/src/cli/usage.js +72 -0
- package/dist/src/cli.js +217 -0
- package/dist/src/commit-pattern-detector.js +143 -0
- package/dist/src/commit-quality-analyzer.js +38 -0
- package/dist/src/config-loader.js +339 -0
- package/dist/src/context-broker.js +358 -0
- package/dist/src/copilot-cli-wrapper.js +258 -0
- package/dist/src/cost-estimator.js +241 -0
- package/dist/src/defaults.js +18 -0
- package/dist/src/demo-mode.js +180 -0
- package/dist/src/deployment-handler.js +78 -0
- package/dist/src/deployment-manager.js +228 -0
- package/dist/src/execution-queue.js +196 -0
- package/dist/src/external-tool-manager.js +184 -0
- package/dist/src/gate-prompt-builder.js +117 -0
- package/dist/src/gate-remediation.js +173 -0
- package/dist/src/github-issues-ingester.js +152 -0
- package/dist/src/github-mcp-integrator.js +164 -0
- package/dist/src/hook-generator.js +229 -0
- package/dist/src/index.js +48 -0
- package/dist/src/knowledge-base.js +300 -0
- package/dist/src/logger.js +111 -0
- package/dist/src/meta-analyzer.js +208 -0
- package/dist/src/metrics-collector.js +209 -0
- package/dist/src/metrics-types.js +5 -0
- package/dist/src/multi-repo-coordinator.js +179 -0
- package/dist/src/orchestrator/async-meta-analysis.js +84 -0
- package/dist/src/orchestrator/fatal-run-error.js +43 -0
- package/dist/src/orchestrator/final-gates-remediation.js +258 -0
- package/dist/src/orchestrator/git-state-utils.js +119 -0
- package/dist/src/orchestrator/pause-controller.js +55 -0
- package/dist/src/orchestrator/replan-runner.js +258 -0
- package/dist/src/orchestrator/step-executor.js +391 -0
- package/dist/src/orchestrator/wave-scheduler-loop.js +204 -0
- package/dist/src/owasp-mapper.js +153 -0
- package/dist/src/owasp-report-renderer.js +39 -0
- package/dist/src/plan-files.js +142 -0
- package/dist/src/plan-generator.js +1207 -0
- package/dist/src/pm-agent.js +296 -0
- package/dist/src/post-run-reporter.js +203 -0
- package/dist/src/pr-automation.js +103 -0
- package/dist/src/pr-manager.js +318 -0
- package/dist/src/presenter/index.js +209 -0
- package/dist/src/prompt-builder.js +173 -0
- package/dist/src/quality-gates/config-loader.js +196 -0
- package/dist/src/quality-gates/default-config.js +243 -0
- package/dist/src/quality-gates/file-utils.js +152 -0
- package/dist/src/quality-gates/gate-runner.js +171 -0
- package/dist/src/quality-gates/gates/accessibility.js +355 -0
- package/dist/src/quality-gates/gates/duplicate-blocks.js +144 -0
- package/dist/src/quality-gates/gates/hardcoded-config.js +52 -0
- package/dist/src/quality-gates/gates/readme-claims.js +62 -0
- package/dist/src/quality-gates/gates/runtime-checks.js +265 -0
- package/dist/src/quality-gates/gates/scaffold-defaults.js +108 -0
- package/dist/src/quality-gates/gates/test-coverage.js +240 -0
- package/dist/src/quality-gates/gates/test-file-protection.js +85 -0
- package/dist/src/quality-gates/gates/test-isolation.js +74 -0
- package/dist/src/quality-gates/index.js +17 -0
- package/dist/src/quality-gates/registry.js +205 -0
- package/dist/src/quality-gates/types.js +2 -0
- package/dist/src/quick-fix-mode.js +302 -0
- package/dist/src/recipe-loader.js +122 -0
- package/dist/src/repair-agent.js +396 -0
- package/dist/src/repo-analyzer.js +342 -0
- package/dist/src/report-generator.js +146 -0
- package/dist/src/report-renderer.js +90 -0
- package/dist/src/requirement-filter.js +52 -0
- package/dist/src/sarif-formatter.js +140 -0
- package/dist/src/scheduling/dependency-analyzer.js +178 -0
- package/dist/src/scheduling/work-stealing-queue.js +123 -0
- package/dist/src/secret-redactor.js +90 -0
- package/dist/src/session-executor.js +587 -0
- package/dist/src/session-manager.js +221 -0
- package/dist/src/share/transcript-verification.js +128 -0
- package/dist/src/share-parser.js +477 -0
- package/dist/src/spinner.js +146 -0
- package/dist/src/step-runner.js +365 -0
- package/dist/src/swarm-orchestrator.js +686 -0
- package/dist/src/task-classifier.js +134 -0
- package/dist/src/test-command-discovery.js +123 -0
- package/dist/src/text-similarity.js +24 -0
- package/dist/src/tier-maps.js +158 -0
- package/dist/src/types.js +5 -0
- package/dist/src/url-shortener.js +135 -0
- package/dist/src/verification/attestation.js +166 -0
- package/dist/src/verification/cheat-detector.js +240 -0
- package/dist/src/verification/command-runner.js +70 -0
- package/dist/src/verification/composite-score.js +157 -0
- package/dist/src/verification/cosign-attestation.js +137 -0
- package/dist/src/verification/diff-analysis.js +89 -0
- package/dist/src/verification/differential-gate.js +173 -0
- package/dist/src/verification/index.js +41 -0
- package/dist/src/verification/mutation-gate.js +242 -0
- package/dist/src/verification/property-gate.js +201 -0
- package/dist/src/verification/test-synthesizer.js +243 -0
- package/dist/src/verifier/outcome-checks.js +396 -0
- package/dist/src/verifier/transcript-checks.js +207 -0
- package/dist/src/verifier/verification-reporters.js +148 -0
- package/dist/src/verifier-engine.js +491 -0
- package/dist/src/wave-resizer.js +131 -0
- package/dist/src/wave-scheduler.js +44 -0
- package/dist/src/worktree-manager.js +368 -0
- package/dist/src/worktree-reserved-paths.js +97 -0
- package/package.json +80 -0
- package/plugin/agents/.gitkeep +0 -0
- package/plugin/agents/backend_master.agent.md +72 -0
- package/plugin/agents/devops_pro.agent.md +45 -0
- package/plugin/agents/frontend_expert.agent.md +59 -0
- package/plugin/agents/integrator_finalizer.agent.md +44 -0
- package/plugin/agents/meta_reviewer.agent.md +44 -0
- package/plugin/agents/security_auditor.agent.md +43 -0
- package/plugin/agents/tester_elite.agent.md +43 -0
- package/plugin/hooks/hooks.json +19 -0
- package/plugin/hooks/post-tool-use.json +9 -0
- package/plugin/hooks/pre-tool-use.json +9 -0
- package/plugin/plugin.json +47 -0
- package/plugin/skills/swarm-gates/SKILL.md +53 -0
- package/plugin/skills/swarm-orchestrate/SKILL.md +34 -0
- package/plugin/skills/swarm-verify/SKILL.md +44 -0
- package/templates/README.md +35 -0
- package/templates/cli-tool.json +31 -0
- package/templates/fullstack.json +52 -0
- package/templates/library.json +38 -0
- package/templates/react-app.json +45 -0
- package/templates/recipes/add-api-docs.json +35 -0
- package/templates/recipes/add-auth.json +47 -0
- package/templates/recipes/add-ci.json +39 -0
- package/templates/recipes/add-tests.json +39 -0
- package/templates/recipes/migrate-to-ts.json +42 -0
- package/templates/recipes/refactor-modularize.json +44 -0
- package/templates/recipes/security-audit.json +35 -0
- package/templates/rest-api.json +38 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copilot CLI Wrapper
|
|
4
|
+
*
|
|
5
|
+
* Abstraction layer for resilient Copilot CLI invocation with:
|
|
6
|
+
* - Graceful degradation when CLI unavailable
|
|
7
|
+
* - Fallback to alternative modes
|
|
8
|
+
* - Better error handling and retry logic
|
|
9
|
+
* - Feature detection and capability checking
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CopilotCliWrapper = void 0;
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const logger_1 = require("./logger");
|
|
15
|
+
const logger = (0, logger_1.getLogger)('copilot-cli-wrapper');
|
|
16
|
+
/**
|
|
17
|
+
* Wrapper for GitHub Copilot CLI with resilience features
|
|
18
|
+
*/
|
|
19
|
+
class CopilotCliWrapper {
|
|
20
|
+
options;
|
|
21
|
+
capabilities = null;
|
|
22
|
+
capabilityCheckDone = false;
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.options = options;
|
|
25
|
+
// Default options
|
|
26
|
+
this.options.gracefulDegradation = this.options.gracefulDegradation ?? true;
|
|
27
|
+
this.options.retryOnFailure = this.options.retryOnFailure ?? true;
|
|
28
|
+
this.options.maxRetries = this.options.maxRetries ?? 3;
|
|
29
|
+
this.options.timeout = this.options.timeout ?? 300000; // 5 minutes
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check if Copilot CLI is available and what features it supports
|
|
33
|
+
*/
|
|
34
|
+
async checkCapabilities() {
|
|
35
|
+
if (this.capabilityCheckDone && this.capabilities) {
|
|
36
|
+
return this.capabilities;
|
|
37
|
+
}
|
|
38
|
+
const capabilities = {
|
|
39
|
+
available: false,
|
|
40
|
+
supportsPromptMode: false,
|
|
41
|
+
supportsShare: false,
|
|
42
|
+
supportsAgents: false,
|
|
43
|
+
supportsMcp: false
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
// Check if copilot command exists
|
|
47
|
+
const versionResult = await this.runCommand('copilot', ['--version'], { timeout: 5000 });
|
|
48
|
+
if (versionResult.exitCode === 0) {
|
|
49
|
+
capabilities.available = true;
|
|
50
|
+
capabilities.version = versionResult.stdout.trim();
|
|
51
|
+
// Check for -p flag support
|
|
52
|
+
const helpResult = await this.runCommand('copilot', ['--help'], { timeout: 5000 });
|
|
53
|
+
capabilities.supportsPromptMode = helpResult.stdout.includes('-p');
|
|
54
|
+
capabilities.supportsShare = helpResult.stdout.includes('--share');
|
|
55
|
+
capabilities.supportsAgents = helpResult.stdout.includes('--agent');
|
|
56
|
+
capabilities.supportsMcp = helpResult.stdout.includes('--mcp');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// CLI not available
|
|
61
|
+
capabilities.available = false;
|
|
62
|
+
}
|
|
63
|
+
this.capabilities = capabilities;
|
|
64
|
+
this.capabilityCheckDone = true;
|
|
65
|
+
return capabilities;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Execute a Copilot CLI command with resilience
|
|
69
|
+
*/
|
|
70
|
+
async execute(args, options = {}) {
|
|
71
|
+
// Strict isolation guard: log when active so transcript captures the mode
|
|
72
|
+
if (this.options.strictIsolation) {
|
|
73
|
+
logger.info('[strict-isolation] Per-task branch isolation active. Context restricted to transcript evidence only.');
|
|
74
|
+
}
|
|
75
|
+
// Inner fleet toggle: prepend /fleet to prompt input
|
|
76
|
+
if (this.options.useInnerFleet && options.input) {
|
|
77
|
+
logger.info('[inner-fleet] Inner fleet mode active. External safety rail enforced.');
|
|
78
|
+
options = { ...options, input: `/fleet ${options.input}` };
|
|
79
|
+
}
|
|
80
|
+
const capabilities = await this.checkCapabilities();
|
|
81
|
+
if (!capabilities.available) {
|
|
82
|
+
if (this.options.gracefulDegradation) {
|
|
83
|
+
return this.degradedExecution(args, options);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
output: '',
|
|
89
|
+
error: 'Copilot CLI is not available. Please install it with: npm install -g @github/copilot',
|
|
90
|
+
exitCode: 127,
|
|
91
|
+
degraded: false,
|
|
92
|
+
capabilities
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Execute with retries
|
|
97
|
+
let lastError = '';
|
|
98
|
+
for (let attempt = 1; attempt <= (this.options.maxRetries || 1); attempt++) {
|
|
99
|
+
try {
|
|
100
|
+
const runOptions = {};
|
|
101
|
+
if (options.cwd)
|
|
102
|
+
runOptions.cwd = options.cwd;
|
|
103
|
+
if (this.options.timeout)
|
|
104
|
+
runOptions.timeout = this.options.timeout;
|
|
105
|
+
if (options.input)
|
|
106
|
+
runOptions.input = options.input;
|
|
107
|
+
const result = await this.runCommand('copilot', args, runOptions);
|
|
108
|
+
return {
|
|
109
|
+
success: result.exitCode === 0,
|
|
110
|
+
output: result.stdout + result.stderr,
|
|
111
|
+
error: result.exitCode !== 0 ? result.stderr : undefined,
|
|
112
|
+
exitCode: result.exitCode,
|
|
113
|
+
degraded: false,
|
|
114
|
+
capabilities
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
119
|
+
if (attempt < (this.options.maxRetries || 1)) {
|
|
120
|
+
// Wait before retry with exponential backoff
|
|
121
|
+
await this.sleep(Math.pow(2, attempt) * 1000);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// All retries failed
|
|
126
|
+
if (this.options.gracefulDegradation) {
|
|
127
|
+
return this.degradedExecution(args, options);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
output: '',
|
|
132
|
+
error: `Failed after ${this.options.maxRetries} attempts: ${lastError}`,
|
|
133
|
+
exitCode: 1,
|
|
134
|
+
degraded: false,
|
|
135
|
+
capabilities
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Degraded execution mode when CLI unavailable or failing
|
|
140
|
+
* Provides helpful guidance instead of hard failure
|
|
141
|
+
*/
|
|
142
|
+
degradedExecution(args, options) {
|
|
143
|
+
const capabilities = this.capabilities || {
|
|
144
|
+
available: false,
|
|
145
|
+
supportsPromptMode: false,
|
|
146
|
+
supportsShare: false,
|
|
147
|
+
supportsAgents: false,
|
|
148
|
+
supportsMcp: false
|
|
149
|
+
};
|
|
150
|
+
// Generate helpful message based on what was attempted
|
|
151
|
+
let message = '⚠️ Graceful Degradation Mode\n\n';
|
|
152
|
+
message += 'The Copilot CLI is not available or failed to execute.\n';
|
|
153
|
+
message += 'This would have run: copilot ' + args.join(' ') + '\n\n';
|
|
154
|
+
if (options.input) {
|
|
155
|
+
message += 'With prompt:\n';
|
|
156
|
+
message += '---\n';
|
|
157
|
+
message += options.input.substring(0, 500);
|
|
158
|
+
if (options.input.length > 500) {
|
|
159
|
+
message += '\n... (truncated)';
|
|
160
|
+
}
|
|
161
|
+
message += '\n---\n\n';
|
|
162
|
+
}
|
|
163
|
+
message += 'To use this feature:\n';
|
|
164
|
+
message += '1. Install Copilot CLI: npm install -g @github/copilot\n';
|
|
165
|
+
message += '2. Authenticate: copilot auth\n';
|
|
166
|
+
message += '3. Re-run this command\n\n';
|
|
167
|
+
message += 'Or, execute the prompt manually in your terminal.';
|
|
168
|
+
return {
|
|
169
|
+
success: false,
|
|
170
|
+
output: message,
|
|
171
|
+
error: 'Copilot CLI not available (degraded mode)',
|
|
172
|
+
exitCode: 0, // Don't fail hard
|
|
173
|
+
degraded: true,
|
|
174
|
+
capabilities
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Run a command and capture output
|
|
179
|
+
*/
|
|
180
|
+
runCommand(command, args, options = {}) {
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
const proc = (0, child_process_1.spawn)(command, args, {
|
|
183
|
+
cwd: options.cwd || process.cwd(),
|
|
184
|
+
shell: true
|
|
185
|
+
});
|
|
186
|
+
let stdout = '';
|
|
187
|
+
let stderr = '';
|
|
188
|
+
let timedOut = false;
|
|
189
|
+
// Set timeout if specified
|
|
190
|
+
const timeoutId = options.timeout
|
|
191
|
+
? setTimeout(() => {
|
|
192
|
+
timedOut = true;
|
|
193
|
+
proc.kill('SIGTERM');
|
|
194
|
+
}, options.timeout)
|
|
195
|
+
: null;
|
|
196
|
+
if (proc.stdout) {
|
|
197
|
+
proc.stdout.on('data', (data) => {
|
|
198
|
+
stdout += data.toString();
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (proc.stderr) {
|
|
202
|
+
proc.stderr.on('data', (data) => {
|
|
203
|
+
stderr += data.toString();
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// Send input if provided
|
|
207
|
+
if (options.input && proc.stdin) {
|
|
208
|
+
proc.stdin.write(options.input);
|
|
209
|
+
proc.stdin.end();
|
|
210
|
+
}
|
|
211
|
+
proc.on('close', (code) => {
|
|
212
|
+
if (timeoutId)
|
|
213
|
+
clearTimeout(timeoutId);
|
|
214
|
+
if (timedOut) {
|
|
215
|
+
resolve({
|
|
216
|
+
stdout,
|
|
217
|
+
stderr: stderr + '\nCommand timed out',
|
|
218
|
+
exitCode: 124
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
resolve({
|
|
223
|
+
stdout,
|
|
224
|
+
stderr,
|
|
225
|
+
exitCode: code || 0
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
proc.on('error', (err) => {
|
|
230
|
+
if (timeoutId)
|
|
231
|
+
clearTimeout(timeoutId);
|
|
232
|
+
resolve({
|
|
233
|
+
stdout,
|
|
234
|
+
stderr: stderr + '\n' + err.message,
|
|
235
|
+
exitCode: 127
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
sleep(ms) {
|
|
241
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Get current capabilities (cached)
|
|
245
|
+
*/
|
|
246
|
+
getCapabilities() {
|
|
247
|
+
return this.capabilities;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Reset capability cache (for testing)
|
|
251
|
+
*/
|
|
252
|
+
resetCapabilities() {
|
|
253
|
+
this.capabilities = null;
|
|
254
|
+
this.capabilityCheckDone = false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
exports.CopilotCliWrapper = CopilotCliWrapper;
|
|
258
|
+
exports.default = CopilotCliWrapper;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CostEstimator = exports.MODEL_MULTIPLIERS = void 0;
|
|
4
|
+
const defaults_1 = require("./defaults");
|
|
5
|
+
/**
|
|
6
|
+
* Model multipliers for premium request consumption.
|
|
7
|
+
* Each copilot -p invocation = 1 base premium request * multiplier.
|
|
8
|
+
* Updated as GitHub changes pricing: https://docs.github.com/en/copilot/concepts/billing/copilot-requests
|
|
9
|
+
* Model availability varies by Copilot plan; Copilot CLI will reject
|
|
10
|
+
* unavailable models at runtime with "not available" on stderr.
|
|
11
|
+
*/
|
|
12
|
+
exports.MODEL_MULTIPLIERS = {
|
|
13
|
+
'claude-sonnet-4': 1,
|
|
14
|
+
'claude-sonnet-4.6': 1,
|
|
15
|
+
'claude-opus-4': 1,
|
|
16
|
+
'gpt-4o': 1,
|
|
17
|
+
'gpt-5.2': 1,
|
|
18
|
+
'gpt-5.4': 1,
|
|
19
|
+
'o3': 20,
|
|
20
|
+
'o4-mini': 5,
|
|
21
|
+
};
|
|
22
|
+
const MAX_RETRY_PROBABILITY = 0.50;
|
|
23
|
+
const OVERAGE_COST_PER_REQUEST = 0.04;
|
|
24
|
+
// Quality gate remediation overhead: proportion of planned steps that
|
|
25
|
+
// typically trigger remediation follow-ups. Derived from observed runs
|
|
26
|
+
// where ~29% of total requests were gate-triggered remediation steps.
|
|
27
|
+
// Rounded down to 0.25 as a conservative default. Applied per-step, then
|
|
28
|
+
// ceiled to whole requests since partial agent invocations aren't possible.
|
|
29
|
+
const MAX_REMEDIATION_RATE = 0.50;
|
|
30
|
+
// Rough token count for the static boilerplate injected by buildSwarmPrompt.
|
|
31
|
+
// Measured from actual prompt captures; chars / 4 approximation.
|
|
32
|
+
const STATIC_BOILERPLATE_TOKENS = 350;
|
|
33
|
+
class CostEstimator {
|
|
34
|
+
knowledgeBase;
|
|
35
|
+
actuals = [];
|
|
36
|
+
constructor(knowledgeBase) {
|
|
37
|
+
this.knowledgeBase = knowledgeBase;
|
|
38
|
+
}
|
|
39
|
+
estimate(plan, options) {
|
|
40
|
+
const multiplier = exports.MODEL_MULTIPLIERS[options.modelName] ?? 1;
|
|
41
|
+
let retryProbability = this.calibrateRetryProbability(plan);
|
|
42
|
+
// Gate-aware prompts reduce repair cycles by front-loading requirements.
|
|
43
|
+
// Conservative estimate: 30% reduction in retry probability.
|
|
44
|
+
if (options.gateAwarePrompts) {
|
|
45
|
+
retryProbability *= 0.7;
|
|
46
|
+
}
|
|
47
|
+
const perStep = plan.steps.map(step => {
|
|
48
|
+
const taskTokens = Math.ceil(step.task.length / 4);
|
|
49
|
+
const promptTokens = STATIC_BOILERPLATE_TOKENS + taskTokens;
|
|
50
|
+
const baseRequests = 1 * multiplier;
|
|
51
|
+
const fleetMultiplier = options.fleetMode
|
|
52
|
+
? this.estimateFleetMultiplier(step.task)
|
|
53
|
+
: 1;
|
|
54
|
+
// Per-step estimate is the base cost without retry inflation.
|
|
55
|
+
// Retry buffer is calculated as an aggregate across the plan.
|
|
56
|
+
const stepRequests = baseRequests * fleetMultiplier;
|
|
57
|
+
return {
|
|
58
|
+
stepNumber: step.stepNumber,
|
|
59
|
+
agentName: step.agentName,
|
|
60
|
+
estimatedPromptTokens: promptTokens,
|
|
61
|
+
estimatedPremiumRequests: stepRequests,
|
|
62
|
+
retryProbability,
|
|
63
|
+
rationale: this.buildRationale(multiplier, fleetMultiplier, retryProbability, options),
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
const baseTotalNoRetry = perStep.reduce((sum, s) => sum + s.estimatedPremiumRequests, 0);
|
|
67
|
+
// Retry buffer is the aggregate expected retry cost across all steps
|
|
68
|
+
const retryBuffer = Math.ceil(baseTotalNoRetry * retryProbability);
|
|
69
|
+
// Remediation buffer: when quality gates are enabled, some steps will
|
|
70
|
+
// fail gate checks and trigger automated follow-up remediation steps.
|
|
71
|
+
// Each remediation step costs one premium request per model multiplier.
|
|
72
|
+
const remediationRate = options.qualityGatesEnabled
|
|
73
|
+
? this.calibrateRemediationRate(plan)
|
|
74
|
+
: 0;
|
|
75
|
+
const remediationBuffer = Math.ceil(plan.steps.length * remediationRate * multiplier);
|
|
76
|
+
const totalRaw = baseTotalNoRetry + retryBuffer + remediationBuffer;
|
|
77
|
+
// Low estimate assumes no retries and no remediation
|
|
78
|
+
const lowEstimate = baseTotalNoRetry;
|
|
79
|
+
const highEstimate = totalRaw;
|
|
80
|
+
const overageCostUSD = options.remainingAllowance !== undefined
|
|
81
|
+
? Math.max(0, totalRaw - options.remainingAllowance) * OVERAGE_COST_PER_REQUEST
|
|
82
|
+
: 0;
|
|
83
|
+
return {
|
|
84
|
+
totalPremiumRequests: totalRaw,
|
|
85
|
+
lowEstimate,
|
|
86
|
+
highEstimate,
|
|
87
|
+
perStep,
|
|
88
|
+
retryBuffer: Math.max(0, retryBuffer),
|
|
89
|
+
remediationBuffer,
|
|
90
|
+
modelMultiplier: multiplier,
|
|
91
|
+
overageCostUSD: parseFloat(overageCostUSD.toFixed(2)),
|
|
92
|
+
...(options.remainingAllowance !== undefined
|
|
93
|
+
? { remainingAllowance: options.remainingAllowance }
|
|
94
|
+
: {}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
recordActual(stepNumber, estimatedRequests, actualRequests, retryCount) {
|
|
98
|
+
this.actuals.push({ stepNumber, estimatedRequests, actualRequests, retryCount });
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Ratio of correctly-estimated steps to total recorded steps.
|
|
102
|
+
* A step is "correct" if estimated >= actual (no underestimate).
|
|
103
|
+
* Returns 1.0 when no actuals recorded (no data to disprove accuracy).
|
|
104
|
+
*/
|
|
105
|
+
getAccuracy() {
|
|
106
|
+
if (this.actuals.length === 0)
|
|
107
|
+
return 1.0;
|
|
108
|
+
const totalEstimated = this.actuals.reduce((s, a) => s + a.estimatedRequests, 0);
|
|
109
|
+
const totalActual = this.actuals.reduce((s, a) => s + a.actualRequests, 0);
|
|
110
|
+
if (totalActual === 0)
|
|
111
|
+
return 1.0;
|
|
112
|
+
// Conservative overestimates are acceptable; only penalize underestimates.
|
|
113
|
+
// Accuracy for overestimates: actual / estimated (closer to 1 = tighter).
|
|
114
|
+
// Accuracy for underestimates: estimated / actual (penalizes gaps).
|
|
115
|
+
if (totalEstimated >= totalActual) {
|
|
116
|
+
return totalActual / totalEstimated;
|
|
117
|
+
}
|
|
118
|
+
return totalEstimated / totalActual;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Pull historical failure rate from knowledge base cost_history patterns.
|
|
122
|
+
* Falls back to DEFAULT_RETRY_PROBABILITY when no history exists.
|
|
123
|
+
* Parses structured CostHistoryEvidence from evidence array.
|
|
124
|
+
* Falls back to legacy string parsing for pre-v3.3 entries.
|
|
125
|
+
*/
|
|
126
|
+
calibrateRetryProbability(_plan) {
|
|
127
|
+
if (!this.knowledgeBase)
|
|
128
|
+
return defaults_1.DEFAULT_RETRY_PROBABILITY;
|
|
129
|
+
const costPatterns = this.knowledgeBase.getPatternsByCategory('cost_history');
|
|
130
|
+
if (costPatterns.length === 0)
|
|
131
|
+
return defaults_1.DEFAULT_RETRY_PROBABILITY;
|
|
132
|
+
let totalRetries = 0;
|
|
133
|
+
let totalSteps = 0;
|
|
134
|
+
for (const pattern of costPatterns) {
|
|
135
|
+
// Try structured evidence first (single JSON string per entry)
|
|
136
|
+
const parsed = this.parseStructuredEvidence(pattern.evidence);
|
|
137
|
+
if (parsed) {
|
|
138
|
+
totalSteps += parsed.actual;
|
|
139
|
+
totalRetries += parsed.retries;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Legacy fallback: string-encoded fields
|
|
143
|
+
for (const ev of pattern.evidence) {
|
|
144
|
+
if (ev.startsWith('actual:')) {
|
|
145
|
+
const actual = parseInt(ev.split(':')[1], 10);
|
|
146
|
+
if (!isNaN(actual))
|
|
147
|
+
totalSteps += actual;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const retryMatch = pattern.insight.match(/(\d+)\s+retries/);
|
|
151
|
+
if (retryMatch) {
|
|
152
|
+
totalRetries += parseInt(retryMatch[1], 10);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (totalSteps === 0)
|
|
156
|
+
return defaults_1.DEFAULT_RETRY_PROBABILITY;
|
|
157
|
+
const rate = totalRetries / totalSteps;
|
|
158
|
+
return Math.min(rate, MAX_RETRY_PROBABILITY);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Pull historical remediation rate from knowledge base cost_history patterns.
|
|
162
|
+
* Remediation steps are quality-gate-triggered follow-ups (not retries).
|
|
163
|
+
* Falls back to DEFAULT_REMEDIATION_RATE when no history with remediation
|
|
164
|
+
* data exists. Only structured evidence (v4.2+) carries remediation counts.
|
|
165
|
+
*/
|
|
166
|
+
calibrateRemediationRate(_plan) {
|
|
167
|
+
if (!this.knowledgeBase)
|
|
168
|
+
return defaults_1.DEFAULT_REMEDIATION_RATE;
|
|
169
|
+
const costPatterns = this.knowledgeBase.getPatternsByCategory('cost_history');
|
|
170
|
+
if (costPatterns.length === 0)
|
|
171
|
+
return defaults_1.DEFAULT_REMEDIATION_RATE;
|
|
172
|
+
let totalPlannedSteps = 0;
|
|
173
|
+
let totalRemediationSteps = 0;
|
|
174
|
+
for (const pattern of costPatterns) {
|
|
175
|
+
const parsed = this.parseStructuredEvidence(pattern.evidence);
|
|
176
|
+
if (!parsed)
|
|
177
|
+
continue;
|
|
178
|
+
if (typeof parsed.remediationSteps !== 'number')
|
|
179
|
+
continue;
|
|
180
|
+
totalPlannedSteps += parsed.steps;
|
|
181
|
+
totalRemediationSteps += parsed.remediationSteps;
|
|
182
|
+
}
|
|
183
|
+
if (totalPlannedSteps === 0)
|
|
184
|
+
return defaults_1.DEFAULT_REMEDIATION_RATE;
|
|
185
|
+
const rate = totalRemediationSteps / totalPlannedSteps;
|
|
186
|
+
return Math.min(rate, MAX_REMEDIATION_RATE);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Attempt to parse a CostHistoryEvidence object from the evidence array.
|
|
190
|
+
* Returns null if the evidence uses the legacy string format.
|
|
191
|
+
*/
|
|
192
|
+
parseStructuredEvidence(evidence) {
|
|
193
|
+
if (evidence.length !== 1)
|
|
194
|
+
return null;
|
|
195
|
+
try {
|
|
196
|
+
const obj = JSON.parse(evidence[0]);
|
|
197
|
+
if (typeof obj.runId === 'string' && typeof obj.actual === 'number') {
|
|
198
|
+
return obj;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// Not JSON; legacy format
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Heuristic fleet multiplier: tasks mentioning multiple files or components
|
|
208
|
+
* likely trigger more subagents.
|
|
209
|
+
*/
|
|
210
|
+
estimateFleetMultiplier(taskDescription) {
|
|
211
|
+
const lower = taskDescription.toLowerCase();
|
|
212
|
+
const multiFileIndicators = [
|
|
213
|
+
'multiple files', 'several files', 'all files',
|
|
214
|
+
'each module', 'every module', 'across modules',
|
|
215
|
+
'components', 'services', 'endpoints',
|
|
216
|
+
];
|
|
217
|
+
const matchCount = multiFileIndicators.filter(ind => lower.includes(ind)).length;
|
|
218
|
+
if (matchCount >= 2)
|
|
219
|
+
return 4;
|
|
220
|
+
if (matchCount === 1)
|
|
221
|
+
return 3;
|
|
222
|
+
// Check for list-like patterns (commas separating items in the task)
|
|
223
|
+
const commaSegments = taskDescription.split(',').length;
|
|
224
|
+
if (commaSegments >= 4)
|
|
225
|
+
return 3;
|
|
226
|
+
if (commaSegments >= 2)
|
|
227
|
+
return 2;
|
|
228
|
+
return 2; // fleet always spawns at least one subagent beyond the coordinator
|
|
229
|
+
}
|
|
230
|
+
buildRationale(multiplier, fleetMultiplier, retryProb, options) {
|
|
231
|
+
const parts = [
|
|
232
|
+
`${options.modelName} (${multiplier}x)`,
|
|
233
|
+
];
|
|
234
|
+
if (options.fleetMode) {
|
|
235
|
+
parts.push(`fleet (${fleetMultiplier}x subagents)`);
|
|
236
|
+
}
|
|
237
|
+
parts.push(`${(retryProb * 100).toFixed(0)}% retry buffer`);
|
|
238
|
+
return parts.join(', ');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.CostEstimator = CostEstimator;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_SHORT_CODE_MIN_LENGTH = exports.DEFAULT_URL_MAX_LENGTH = exports.DEFAULT_REMEDIATION_RATE = exports.DEFAULT_RETRY_PROBABILITY = exports.DEFAULT_REPAIR_TRANSCRIPT_CHARS = exports.DEFAULT_REPAIR_REPORT_CHARS = exports.DEFAULT_FAILURE_CONTEXT_CHARS = exports.DEFAULT_COMMAND_TIMEOUT_MS = exports.DEFAULT_BRANCH_SWITCH_TIMEOUT_MS = exports.DEFAULT_DEPENDENCY_WAIT_MS = exports.DEFAULT_CONTEXT_STALE_LOCK_MS = exports.DEFAULT_CONTEXT_LOCK_WAIT_MS = exports.DEFAULT_HEARTBEAT_QUIET_THRESHOLD_MS = exports.DEFAULT_HEARTBEAT_INTERVAL_MS = exports.DEFAULT_SIGKILL_DELAY_MS = void 0;
|
|
4
|
+
exports.DEFAULT_SIGKILL_DELAY_MS = 5_000;
|
|
5
|
+
exports.DEFAULT_HEARTBEAT_INTERVAL_MS = 5_000;
|
|
6
|
+
exports.DEFAULT_HEARTBEAT_QUIET_THRESHOLD_MS = 10_000;
|
|
7
|
+
exports.DEFAULT_CONTEXT_LOCK_WAIT_MS = 30_000;
|
|
8
|
+
exports.DEFAULT_CONTEXT_STALE_LOCK_MS = 300_000;
|
|
9
|
+
exports.DEFAULT_DEPENDENCY_WAIT_MS = 600_000;
|
|
10
|
+
exports.DEFAULT_BRANCH_SWITCH_TIMEOUT_MS = 15_000;
|
|
11
|
+
exports.DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
|
|
12
|
+
exports.DEFAULT_FAILURE_CONTEXT_CHARS = 2_000;
|
|
13
|
+
exports.DEFAULT_REPAIR_REPORT_CHARS = 4_000;
|
|
14
|
+
exports.DEFAULT_REPAIR_TRANSCRIPT_CHARS = 6_000;
|
|
15
|
+
exports.DEFAULT_RETRY_PROBABILITY = 0.15;
|
|
16
|
+
exports.DEFAULT_REMEDIATION_RATE = 0.25;
|
|
17
|
+
exports.DEFAULT_URL_MAX_LENGTH = 2_048;
|
|
18
|
+
exports.DEFAULT_SHORT_CODE_MIN_LENGTH = 7;
|