@wundam/orchex 1.0.0-rc.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.
- package/LICENSE +65 -0
- package/README.md +332 -0
- package/bin/orchex.js +2 -0
- package/dist/artifacts.d.ts +132 -0
- package/dist/artifacts.js +832 -0
- package/dist/claude-executor.d.ts +31 -0
- package/dist/claude-executor.js +200 -0
- package/dist/commands.d.ts +36 -0
- package/dist/commands.js +264 -0
- package/dist/config.d.ts +100 -0
- package/dist/config.js +172 -0
- package/dist/context-builder.d.ts +46 -0
- package/dist/context-builder.js +506 -0
- package/dist/cost.d.ts +29 -0
- package/dist/cost.js +60 -0
- package/dist/execution-broadcaster.d.ts +18 -0
- package/dist/execution-broadcaster.js +17 -0
- package/dist/executors/base.d.ts +99 -0
- package/dist/executors/base.js +206 -0
- package/dist/executors/circuit-breaker.d.ts +36 -0
- package/dist/executors/circuit-breaker.js +109 -0
- package/dist/executors/deepseek-executor.d.ts +22 -0
- package/dist/executors/deepseek-executor.js +145 -0
- package/dist/executors/gemini-executor.d.ts +20 -0
- package/dist/executors/gemini-executor.js +176 -0
- package/dist/executors/index.d.ts +81 -0
- package/dist/executors/index.js +193 -0
- package/dist/executors/ollama-executor.d.ts +25 -0
- package/dist/executors/ollama-executor.js +184 -0
- package/dist/executors/openai-executor.d.ts +22 -0
- package/dist/executors/openai-executor.js +142 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +115 -0
- package/dist/intelligence/anti-pattern-detector.d.ts +117 -0
- package/dist/intelligence/anti-pattern-detector.js +327 -0
- package/dist/intelligence/budget-enforcer.d.ts +119 -0
- package/dist/intelligence/budget-enforcer.js +226 -0
- package/dist/intelligence/context-optimizer.d.ts +111 -0
- package/dist/intelligence/context-optimizer.js +282 -0
- package/dist/intelligence/cost-tracker.d.ts +114 -0
- package/dist/intelligence/cost-tracker.js +183 -0
- package/dist/intelligence/deliverable-extractor.d.ts +134 -0
- package/dist/intelligence/deliverable-extractor.js +909 -0
- package/dist/intelligence/dependency-inferrer.d.ts +87 -0
- package/dist/intelligence/dependency-inferrer.js +403 -0
- package/dist/intelligence/diagnostics.d.ts +25 -0
- package/dist/intelligence/diagnostics.js +36 -0
- package/dist/intelligence/error-analyzer.d.ts +7 -0
- package/dist/intelligence/error-analyzer.js +76 -0
- package/dist/intelligence/file-chunker.d.ts +15 -0
- package/dist/intelligence/file-chunker.js +64 -0
- package/dist/intelligence/fix-stream-manager.d.ts +59 -0
- package/dist/intelligence/fix-stream-manager.js +212 -0
- package/dist/intelligence/heuristics.d.ts +23 -0
- package/dist/intelligence/heuristics.js +124 -0
- package/dist/intelligence/learning-engine.d.ts +157 -0
- package/dist/intelligence/learning-engine.js +433 -0
- package/dist/intelligence/learning-feedback.d.ts +96 -0
- package/dist/intelligence/learning-feedback.js +202 -0
- package/dist/intelligence/pattern-analyzer.d.ts +35 -0
- package/dist/intelligence/pattern-analyzer.js +189 -0
- package/dist/intelligence/plan-parser.d.ts +124 -0
- package/dist/intelligence/plan-parser.js +498 -0
- package/dist/intelligence/planner.d.ts +29 -0
- package/dist/intelligence/planner.js +86 -0
- package/dist/intelligence/self-healer.d.ts +16 -0
- package/dist/intelligence/self-healer.js +84 -0
- package/dist/intelligence/slicing-metrics.d.ts +62 -0
- package/dist/intelligence/slicing-metrics.js +202 -0
- package/dist/intelligence/slicing-templates.d.ts +81 -0
- package/dist/intelligence/slicing-templates.js +420 -0
- package/dist/intelligence/split-suggester.d.ts +69 -0
- package/dist/intelligence/split-suggester.js +176 -0
- package/dist/intelligence/stream-generator.d.ts +90 -0
- package/dist/intelligence/stream-generator.js +452 -0
- package/dist/logger.d.ts +34 -0
- package/dist/logger.js +83 -0
- package/dist/logging.d.ts +5 -0
- package/dist/logging.js +38 -0
- package/dist/manifest.d.ts +56 -0
- package/dist/manifest.js +254 -0
- package/dist/metrics.d.ts +35 -0
- package/dist/metrics.js +75 -0
- package/dist/orchestrator.d.ts +35 -0
- package/dist/orchestrator.js +723 -0
- package/dist/ownership.d.ts +44 -0
- package/dist/ownership.js +250 -0
- package/dist/semaphore.d.ts +12 -0
- package/dist/semaphore.js +34 -0
- package/dist/telemetry/telemetry-types.d.ts +85 -0
- package/dist/telemetry/telemetry-types.js +1 -0
- package/dist/tier-gating.d.ts +24 -0
- package/dist/tier-gating.js +88 -0
- package/dist/tiers.d.ts +92 -0
- package/dist/tiers.js +108 -0
- package/dist/tools.d.ts +18 -0
- package/dist/tools.js +1363 -0
- package/dist/types.d.ts +740 -0
- package/dist/types.js +160 -0
- package/dist/utils/ownership-validator.d.ts +6 -0
- package/dist/utils/ownership-validator.js +21 -0
- package/dist/waves.d.ts +21 -0
- package/dist/waves.js +146 -0
- package/package.json +120 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { FileOperation } from './types.js';
|
|
2
|
+
export interface OwnershipCheckOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Allow common project files even if not in owns list.
|
|
5
|
+
* Default: true
|
|
6
|
+
*/
|
|
7
|
+
allowCommonFiles?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Include warnings in result for common files not in owns.
|
|
10
|
+
* Default: false
|
|
11
|
+
*/
|
|
12
|
+
warnOnCommonFiles?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Strict mode: disable all allowlists, only explicit owns allowed.
|
|
15
|
+
* Default: false
|
|
16
|
+
*/
|
|
17
|
+
strict?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface OwnershipCheckResult {
|
|
20
|
+
/** Files that violate ownership (will cause failure) */
|
|
21
|
+
violations: string[];
|
|
22
|
+
/** Files that are allowed but worth noting */
|
|
23
|
+
warnings: string[];
|
|
24
|
+
/** Files that passed ownership check */
|
|
25
|
+
allowed: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check whether all file operations fall within the stream's owns patterns.
|
|
29
|
+
*
|
|
30
|
+
* Security guarantees:
|
|
31
|
+
* - Path traversal is ALWAYS blocked (non-negotiable)
|
|
32
|
+
* - Absolute paths are ALWAYS blocked
|
|
33
|
+
* - Common files are only allowed in project root or owned directories
|
|
34
|
+
*
|
|
35
|
+
* @param operations - File operations to check
|
|
36
|
+
* @param owns - Ownership patterns from stream definition
|
|
37
|
+
* @param options - Checking options
|
|
38
|
+
*/
|
|
39
|
+
export declare function checkOwnership(operations: FileOperation[], owns: string[], options?: OwnershipCheckOptions): OwnershipCheckResult;
|
|
40
|
+
/**
|
|
41
|
+
* Legacy function signature for backward compatibility.
|
|
42
|
+
* Returns only violations array (old behavior).
|
|
43
|
+
*/
|
|
44
|
+
export declare function checkOwnershipLegacy(operations: FileOperation[], owns: string[]): string[];
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Common Allowed Files
|
|
4
|
+
// ============================================================================
|
|
5
|
+
/**
|
|
6
|
+
* Root-level files that are commonly created by scaffolds and are safe to allow.
|
|
7
|
+
* These are security-vetted and don't pose path traversal risks.
|
|
8
|
+
*/
|
|
9
|
+
const COMMON_ALLOWED_FILES = new Set([
|
|
10
|
+
// Git
|
|
11
|
+
'.gitignore',
|
|
12
|
+
'.gitattributes',
|
|
13
|
+
'.gitkeep',
|
|
14
|
+
// Environment templates (never actual secrets)
|
|
15
|
+
'.env.example',
|
|
16
|
+
'.env.local.example',
|
|
17
|
+
'.env.development.example',
|
|
18
|
+
'.env.production.example',
|
|
19
|
+
'.env.test.example',
|
|
20
|
+
// Code formatting
|
|
21
|
+
'.prettierrc',
|
|
22
|
+
'.prettierrc.json',
|
|
23
|
+
'.prettierrc.js',
|
|
24
|
+
'.prettierrc.cjs',
|
|
25
|
+
'.prettierrc.mjs',
|
|
26
|
+
'.prettierrc.yaml',
|
|
27
|
+
'.prettierrc.yml',
|
|
28
|
+
'.prettierignore',
|
|
29
|
+
// Linting
|
|
30
|
+
'.eslintrc',
|
|
31
|
+
'.eslintrc.json',
|
|
32
|
+
'.eslintrc.js',
|
|
33
|
+
'.eslintrc.cjs',
|
|
34
|
+
'.eslintrc.mjs',
|
|
35
|
+
'.eslintrc.yaml',
|
|
36
|
+
'.eslintrc.yml',
|
|
37
|
+
'.eslintignore',
|
|
38
|
+
// Editor
|
|
39
|
+
'.editorconfig',
|
|
40
|
+
// Docker
|
|
41
|
+
'.dockerignore',
|
|
42
|
+
// Node version managers
|
|
43
|
+
'.nvmrc',
|
|
44
|
+
'.node-version',
|
|
45
|
+
'.tool-versions',
|
|
46
|
+
// TypeScript/JavaScript config (if not explicitly owned)
|
|
47
|
+
'tsconfig.json',
|
|
48
|
+
'jsconfig.json',
|
|
49
|
+
]);
|
|
50
|
+
/**
|
|
51
|
+
* Regex patterns for files that are commonly created and safe to allow.
|
|
52
|
+
*/
|
|
53
|
+
const COMMON_ALLOWED_PATTERNS = [
|
|
54
|
+
// Documentation
|
|
55
|
+
/^README\.md$/i,
|
|
56
|
+
/^CHANGELOG\.md$/i,
|
|
57
|
+
/^LICENSE(\.md|\.txt)?$/i,
|
|
58
|
+
/^CONTRIBUTING\.md$/i,
|
|
59
|
+
/^CODE_OF_CONDUCT\.md$/i,
|
|
60
|
+
/^SECURITY\.md$/i,
|
|
61
|
+
// GitHub
|
|
62
|
+
/^\.github\/.+/,
|
|
63
|
+
// VS Code
|
|
64
|
+
/^\.vscode\/.+/,
|
|
65
|
+
];
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Core Functions
|
|
68
|
+
// ============================================================================
|
|
69
|
+
/**
|
|
70
|
+
* Check whether all file operations fall within the stream's owns patterns.
|
|
71
|
+
*
|
|
72
|
+
* Security guarantees:
|
|
73
|
+
* - Path traversal is ALWAYS blocked (non-negotiable)
|
|
74
|
+
* - Absolute paths are ALWAYS blocked
|
|
75
|
+
* - Common files are only allowed in project root or owned directories
|
|
76
|
+
*
|
|
77
|
+
* @param operations - File operations to check
|
|
78
|
+
* @param owns - Ownership patterns from stream definition
|
|
79
|
+
* @param options - Checking options
|
|
80
|
+
*/
|
|
81
|
+
export function checkOwnership(operations, owns, options = {}) {
|
|
82
|
+
const { allowCommonFiles = true, warnOnCommonFiles = false, strict = false, } = options;
|
|
83
|
+
// Empty owns = no restriction (backward compatible)
|
|
84
|
+
if (owns.length === 0) {
|
|
85
|
+
return {
|
|
86
|
+
violations: [],
|
|
87
|
+
warnings: [],
|
|
88
|
+
allowed: operations.map((o) => o.path),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const violations = [];
|
|
92
|
+
const warnings = [];
|
|
93
|
+
const allowed = [];
|
|
94
|
+
for (const op of operations) {
|
|
95
|
+
const checkResult = checkSingleOperation(op, owns, {
|
|
96
|
+
allowCommonFiles: !strict && allowCommonFiles,
|
|
97
|
+
});
|
|
98
|
+
if (checkResult.violation) {
|
|
99
|
+
violations.push(checkResult.violation);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
allowed.push(op.path);
|
|
103
|
+
if (checkResult.warning && warnOnCommonFiles) {
|
|
104
|
+
warnings.push(checkResult.warning);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { violations, warnings, allowed };
|
|
109
|
+
}
|
|
110
|
+
function checkSingleOperation(op, owns, options) {
|
|
111
|
+
const filePath = op.path;
|
|
112
|
+
// SECURITY: Block path traversal (non-negotiable, checked first)
|
|
113
|
+
if (filePath.includes('..')) {
|
|
114
|
+
return {
|
|
115
|
+
violation: `SECURITY: Path traversal blocked in '${filePath}'`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// SECURITY: Block absolute paths
|
|
119
|
+
if (path.isAbsolute(filePath)) {
|
|
120
|
+
return {
|
|
121
|
+
violation: `SECURITY: Absolute path blocked: '${filePath}'`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// SECURITY: Block hidden directories in parent paths (e.g., .ssh/config)
|
|
125
|
+
const parts = filePath.split('/');
|
|
126
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
127
|
+
const part = parts[i];
|
|
128
|
+
if (part.startsWith('.') && !isAllowedHiddenDir(part)) {
|
|
129
|
+
return {
|
|
130
|
+
violation: `SECURITY: Hidden directory blocked: '${filePath}'`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Check explicit ownership
|
|
135
|
+
if (isExplicitlyOwned(filePath, owns)) {
|
|
136
|
+
return {};
|
|
137
|
+
}
|
|
138
|
+
// Check if it's a common allowed file
|
|
139
|
+
if (options.allowCommonFiles && isCommonAllowedFile(filePath, owns)) {
|
|
140
|
+
return {
|
|
141
|
+
warning: `Common file '${filePath}' created (not in owns list)`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// Not allowed
|
|
145
|
+
return {
|
|
146
|
+
violation: `${op.type} on '${filePath}' is outside owned files`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Check if a path is explicitly covered by an ownership pattern.
|
|
151
|
+
*/
|
|
152
|
+
function isExplicitlyOwned(filePath, owns) {
|
|
153
|
+
return owns.some((pattern) => {
|
|
154
|
+
// Directory pattern: "src/" matches "src/foo.ts"
|
|
155
|
+
if (pattern.endsWith('/')) {
|
|
156
|
+
return filePath.startsWith(pattern);
|
|
157
|
+
}
|
|
158
|
+
// Glob pattern: "src/*.ts" matches "src/foo.ts"
|
|
159
|
+
if (pattern.includes('*')) {
|
|
160
|
+
return matchGlobPattern(pattern, filePath);
|
|
161
|
+
}
|
|
162
|
+
// Exact match
|
|
163
|
+
return filePath === pattern;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if a file is a common allowed file.
|
|
168
|
+
* Only allows files in:
|
|
169
|
+
* - Project root (for root-level common files)
|
|
170
|
+
* - Inside already owned directories
|
|
171
|
+
*/
|
|
172
|
+
function isCommonAllowedFile(filePath, owns) {
|
|
173
|
+
const fileName = path.basename(filePath);
|
|
174
|
+
const dirName = path.dirname(filePath);
|
|
175
|
+
// Root-level common files
|
|
176
|
+
if (dirName === '.') {
|
|
177
|
+
if (COMMON_ALLOWED_FILES.has(fileName)) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
for (const pattern of COMMON_ALLOWED_PATTERNS) {
|
|
181
|
+
if (pattern.test(filePath)) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Pattern-matched files (like .github/workflows/ci.yml)
|
|
187
|
+
for (const pattern of COMMON_ALLOWED_PATTERNS) {
|
|
188
|
+
if (pattern.test(filePath)) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// .gitkeep inside owned directories
|
|
193
|
+
if (fileName === '.gitkeep') {
|
|
194
|
+
const isInOwnedDir = owns.some((pattern) => {
|
|
195
|
+
if (pattern.endsWith('/')) {
|
|
196
|
+
return filePath.startsWith(pattern);
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
});
|
|
200
|
+
if (isInOwnedDir) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Hidden directories that are allowed (dev tooling, not system files).
|
|
208
|
+
*/
|
|
209
|
+
function isAllowedHiddenDir(dirName) {
|
|
210
|
+
const allowed = new Set([
|
|
211
|
+
'.github',
|
|
212
|
+
'.vscode',
|
|
213
|
+
'.idea',
|
|
214
|
+
'.husky',
|
|
215
|
+
'.storybook',
|
|
216
|
+
'.next',
|
|
217
|
+
'.nuxt',
|
|
218
|
+
'.svelte-kit',
|
|
219
|
+
'.turbo',
|
|
220
|
+
'.vercel',
|
|
221
|
+
'.netlify',
|
|
222
|
+
]);
|
|
223
|
+
return allowed.has(dirName);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Match a simple glob pattern against a file path.
|
|
227
|
+
* Supports: * (any chars except /), ** (any chars including /)
|
|
228
|
+
*/
|
|
229
|
+
function matchGlobPattern(pattern, filePath) {
|
|
230
|
+
// Escape regex special chars except * and **
|
|
231
|
+
let regexStr = pattern
|
|
232
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
233
|
+
.replace(/\*\*/g, '<<<GLOBSTAR>>>')
|
|
234
|
+
.replace(/\*/g, '[^/]*')
|
|
235
|
+
.replace(/<<<GLOBSTAR>>>/g, '.*');
|
|
236
|
+
return new RegExp(`^${regexStr}$`).test(filePath);
|
|
237
|
+
}
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// Legacy Compatibility
|
|
240
|
+
// ============================================================================
|
|
241
|
+
/**
|
|
242
|
+
* Legacy function signature for backward compatibility.
|
|
243
|
+
* Returns only violations array (old behavior).
|
|
244
|
+
*/
|
|
245
|
+
export function checkOwnershipLegacy(operations, owns) {
|
|
246
|
+
const result = checkOwnership(operations, owns, {
|
|
247
|
+
allowCommonFiles: false, // Old behavior: strict
|
|
248
|
+
});
|
|
249
|
+
return result.violations;
|
|
250
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promise-based semaphore for limiting concurrent async operations.
|
|
3
|
+
* Used by ClaudeExecutor to cap parallel API calls.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Semaphore {
|
|
6
|
+
private readonly maxConcurrency;
|
|
7
|
+
private queue;
|
|
8
|
+
private running;
|
|
9
|
+
constructor(maxConcurrency: number);
|
|
10
|
+
acquire(): Promise<() => void>;
|
|
11
|
+
private release;
|
|
12
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promise-based semaphore for limiting concurrent async operations.
|
|
3
|
+
* Used by ClaudeExecutor to cap parallel API calls.
|
|
4
|
+
*/
|
|
5
|
+
export class Semaphore {
|
|
6
|
+
maxConcurrency;
|
|
7
|
+
queue = [];
|
|
8
|
+
running = 0;
|
|
9
|
+
constructor(maxConcurrency) {
|
|
10
|
+
this.maxConcurrency = maxConcurrency;
|
|
11
|
+
if (maxConcurrency < 1) {
|
|
12
|
+
throw new Error(`Semaphore concurrency must be >= 1, got ${maxConcurrency}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async acquire() {
|
|
16
|
+
if (this.running < this.maxConcurrency) {
|
|
17
|
+
this.running++;
|
|
18
|
+
return () => this.release();
|
|
19
|
+
}
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
this.queue.push(() => {
|
|
22
|
+
this.running++;
|
|
23
|
+
resolve(() => this.release());
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
release() {
|
|
28
|
+
this.running--;
|
|
29
|
+
const next = this.queue.shift();
|
|
30
|
+
if (next) {
|
|
31
|
+
next();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ErrorCategory } from '../intelligence/error-analyzer.js';
|
|
2
|
+
import type { BudgetViolationType } from '../types.js';
|
|
3
|
+
export interface TelemetryEvent {
|
|
4
|
+
id: string;
|
|
5
|
+
sessionHash: string;
|
|
6
|
+
eventType: 'orchestration_start' | 'orchestration_complete' | 'stream_complete' | 'stream_failed' | 'self_heal_triggered';
|
|
7
|
+
timestamp: string;
|
|
8
|
+
durationMs?: number;
|
|
9
|
+
streamCount?: number;
|
|
10
|
+
waveCount?: number;
|
|
11
|
+
parallelCount?: number;
|
|
12
|
+
success?: boolean;
|
|
13
|
+
errorCategory?: ErrorCategory;
|
|
14
|
+
retryCount?: number;
|
|
15
|
+
selfHealCount?: number;
|
|
16
|
+
complexityScore?: number;
|
|
17
|
+
tokensInput?: number;
|
|
18
|
+
tokensOutput?: number;
|
|
19
|
+
provider?: string;
|
|
20
|
+
model?: string;
|
|
21
|
+
tier?: string;
|
|
22
|
+
/** Estimated context tokens before execution */
|
|
23
|
+
contextTokensEstimated?: number;
|
|
24
|
+
/** Actual context tokens from API response */
|
|
25
|
+
contextTokensActual?: number;
|
|
26
|
+
/** Budget utilization ratio (0-1) */
|
|
27
|
+
contextBudgetUtilization?: number;
|
|
28
|
+
/** Whether budget limits were exceeded */
|
|
29
|
+
budgetViolationType?: BudgetViolationType;
|
|
30
|
+
/** Provider's context window limit for this model */
|
|
31
|
+
providerContextLimit?: number;
|
|
32
|
+
/** Whether cache was hit */
|
|
33
|
+
cacheHit?: boolean;
|
|
34
|
+
/** Tokens read from cache */
|
|
35
|
+
cacheReadTokens?: number;
|
|
36
|
+
/** Tokens written to cache */
|
|
37
|
+
cacheWriteTokens?: number;
|
|
38
|
+
/** Milliseconds saved by parallel execution */
|
|
39
|
+
timeSavedMs?: number;
|
|
40
|
+
/** Hypothetical sequential execution time in ms */
|
|
41
|
+
sequentialMs?: number;
|
|
42
|
+
/** Actual parallel execution time in ms */
|
|
43
|
+
parallelMs?: number;
|
|
44
|
+
/** Stream name for category-based analysis */
|
|
45
|
+
streamName?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface TelemetryAggregate {
|
|
48
|
+
period: string;
|
|
49
|
+
orchestrationsTotal: number;
|
|
50
|
+
orchestrationsSuccess: number;
|
|
51
|
+
orchestrationsFailed: number;
|
|
52
|
+
streamsTotal: number;
|
|
53
|
+
streamsSuccess: number;
|
|
54
|
+
streamsFailed: number;
|
|
55
|
+
avgDurationMs?: number;
|
|
56
|
+
p50DurationMs?: number;
|
|
57
|
+
p95DurationMs?: number;
|
|
58
|
+
errorsByCategory: Record<ErrorCategory, number>;
|
|
59
|
+
selfHealTriggered: number;
|
|
60
|
+
selfHealSuccess: number;
|
|
61
|
+
tokensInputTotal: number;
|
|
62
|
+
tokensOutputTotal: number;
|
|
63
|
+
/** Average context budget utilization (0-1) */
|
|
64
|
+
avgContextUtilization?: number;
|
|
65
|
+
/** Count of soft limit violations */
|
|
66
|
+
softViolationCount: number;
|
|
67
|
+
/** Count of hard limit violations */
|
|
68
|
+
hardViolationCount: number;
|
|
69
|
+
/** Streams that failed due to context exceeded */
|
|
70
|
+
contextExceededFailures: number;
|
|
71
|
+
/** Cache hit rate (0-1) */
|
|
72
|
+
cacheHitRate?: number;
|
|
73
|
+
/** Total tokens read from cache */
|
|
74
|
+
cacheReadTokensTotal: number;
|
|
75
|
+
/** Total tokens written to cache */
|
|
76
|
+
cacheWriteTokensTotal: number;
|
|
77
|
+
/** Estimated cost savings from cache */
|
|
78
|
+
estimatedCacheSavings?: number;
|
|
79
|
+
/** Total milliseconds saved by parallel execution */
|
|
80
|
+
timeSavedMsTotal: number;
|
|
81
|
+
/** Total hypothetical sequential time in ms */
|
|
82
|
+
sequentialMsTotal: number;
|
|
83
|
+
/** Total actual parallel time in ms */
|
|
84
|
+
parallelMsTotal: number;
|
|
85
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Tier } from './tiers.js';
|
|
2
|
+
import type { StreamDefinition } from './types.js';
|
|
3
|
+
export interface TierLimitCheckOptions {
|
|
4
|
+
tier: Tier;
|
|
5
|
+
streams: Record<string, StreamDefinition>;
|
|
6
|
+
projectDir: string;
|
|
7
|
+
mode: 'cloud' | 'local';
|
|
8
|
+
}
|
|
9
|
+
export interface TierLimitResult {
|
|
10
|
+
allowed: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
code?: string;
|
|
13
|
+
suggestion?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Checks tier gating limits for orchestrations: wave count, providers, and more.
|
|
17
|
+
* Returns { allowed, error, code, suggestion }
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkTierLimits(opts: TierLimitCheckOptions): TierLimitResult;
|
|
20
|
+
/**
|
|
21
|
+
* Generate a user-facing warning if wave count approaches or exceeds tier limit.
|
|
22
|
+
* Returns null if within limits. Used by `learn` tool to warn early.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getWaveCountWarning(waveCount: number, tier: Tier): string | null;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks tier gating limits for orchestrations: wave count, providers, and more.
|
|
3
|
+
* Returns { allowed, error, code, suggestion }
|
|
4
|
+
*/
|
|
5
|
+
export function checkTierLimits(opts) {
|
|
6
|
+
const { tier, streams } = opts;
|
|
7
|
+
// 1. Wave count restriction
|
|
8
|
+
const depLevels = getMaxDependencyDepth(streams);
|
|
9
|
+
if (tier.maxWaves !== -1 && depLevels > tier.maxWaves) {
|
|
10
|
+
return {
|
|
11
|
+
allowed: false,
|
|
12
|
+
error: `Too many dependency waves for tier '${tier.name}'. Max allowed: ${tier.maxWaves}, required: ${depLevels}. Upgrade to increase this limit.`,
|
|
13
|
+
code: 'MAX_WAVES_EXCEEDED',
|
|
14
|
+
suggestion: `Split up your orchestration to use fewer dependency layers, or upgrade to Pro/Team for increased depth.`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// 2. Maximum parallel agents (total stream count)
|
|
18
|
+
if (tier.maxParallelAgents !== -1) {
|
|
19
|
+
const totalStreams = Object.keys(streams).length;
|
|
20
|
+
if (totalStreams > tier.maxParallelAgents) {
|
|
21
|
+
return {
|
|
22
|
+
allowed: false,
|
|
23
|
+
error: `Too many streams for tier '${tier.name}'. Max allowed: ${tier.maxParallelAgents}, requested: ${totalStreams}. Upgrade to increase this limit.`,
|
|
24
|
+
code: 'MAX_AGENTS_EXCEEDED',
|
|
25
|
+
suggestion: `Reduce the number of streams to ${tier.maxParallelAgents} or fewer, or upgrade your tier.`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// 3. maxProviders: Count distinct providers across streams with explicit provider field
|
|
30
|
+
if (tier.maxProviders !== -1) {
|
|
31
|
+
const providers = new Set();
|
|
32
|
+
for (const s of Object.values(streams)) {
|
|
33
|
+
if (s.provider)
|
|
34
|
+
providers.add(s.provider);
|
|
35
|
+
}
|
|
36
|
+
if (providers.size > tier.maxProviders) {
|
|
37
|
+
return {
|
|
38
|
+
allowed: false,
|
|
39
|
+
error: `Too many LLM providers for tier '${tier.name}'. Max allowed: ${tier.maxProviders}, requested: ${providers.size}. Upgrade to increase this limit.`,
|
|
40
|
+
code: 'MAX_PROVIDERS_EXCEEDED',
|
|
41
|
+
suggestion: `Reduce the number of distinct providers to ${tier.maxProviders} or fewer, or upgrade your tier.`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { allowed: true };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Generate a user-facing warning if wave count approaches or exceeds tier limit.
|
|
49
|
+
* Returns null if within limits. Used by `learn` tool to warn early.
|
|
50
|
+
*/
|
|
51
|
+
export function getWaveCountWarning(waveCount, tier) {
|
|
52
|
+
if (tier.maxWaves === -1)
|
|
53
|
+
return null;
|
|
54
|
+
if (waveCount > tier.maxWaves) {
|
|
55
|
+
return `This plan requires ${waveCount} waves but your ${tier.name} tier allows max ${tier.maxWaves}. Reduce dependency depth or upgrade your tier.`;
|
|
56
|
+
}
|
|
57
|
+
if (waveCount >= Math.ceil(tier.maxWaves * 0.8)) {
|
|
58
|
+
return `This plan requires ${waveCount} waves, approaching your ${tier.name} tier limit of ${tier.maxWaves}.`;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
// Utility: Returns maximum depth of dependencies (waves)
|
|
63
|
+
function getMaxDependencyDepth(streams) {
|
|
64
|
+
// For each stream, compute its max dependency chain length (DAG longest path)
|
|
65
|
+
const memo = {};
|
|
66
|
+
function dfs(id, visited) {
|
|
67
|
+
if (memo[id] !== undefined)
|
|
68
|
+
return memo[id];
|
|
69
|
+
if (visited.has(id))
|
|
70
|
+
return 0; // break cycles
|
|
71
|
+
visited.add(id);
|
|
72
|
+
const deps = streams[id]?.deps ?? [];
|
|
73
|
+
let max = 0;
|
|
74
|
+
for (const dep of deps) {
|
|
75
|
+
if (!streams[dep])
|
|
76
|
+
continue;
|
|
77
|
+
max = Math.max(max, dfs(dep, visited));
|
|
78
|
+
}
|
|
79
|
+
visited.delete(id);
|
|
80
|
+
memo[id] = max + 1;
|
|
81
|
+
return memo[id];
|
|
82
|
+
}
|
|
83
|
+
let result = 0;
|
|
84
|
+
for (const id of Object.keys(streams)) {
|
|
85
|
+
result = Math.max(result, dfs(id, new Set()));
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
package/dist/tiers.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const TierIdSchema: z.ZodEnum<["free", "pro", "team", "enterprise"]>;
|
|
3
|
+
export type TierId = z.infer<typeof TierIdSchema>;
|
|
4
|
+
export declare const TierSchema: z.ZodObject<{
|
|
5
|
+
id: z.ZodEnum<["free", "pro", "team", "enterprise"]>;
|
|
6
|
+
name: z.ZodString;
|
|
7
|
+
price: z.ZodNumber;
|
|
8
|
+
cloudOrchestrations: z.ZodNumber;
|
|
9
|
+
maxParallelAgents: z.ZodNumber;
|
|
10
|
+
selfHealing: z.ZodEnum<["none", "full"]>;
|
|
11
|
+
smartPlanning: z.ZodEnum<["none", "full"]>;
|
|
12
|
+
teamMembers: z.ZodNumber;
|
|
13
|
+
/** Maximum # of dependency waves (-1 = unlimited) */
|
|
14
|
+
maxWaves: z.ZodNumber;
|
|
15
|
+
/** Maximum # of distinct LLM providers per orchestration (-1 = unlimited) */
|
|
16
|
+
maxProviders: z.ZodNumber;
|
|
17
|
+
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
name: string;
|
|
19
|
+
id: "free" | "pro" | "team" | "enterprise";
|
|
20
|
+
price: number;
|
|
21
|
+
cloudOrchestrations: number;
|
|
22
|
+
maxParallelAgents: number;
|
|
23
|
+
selfHealing: "none" | "full";
|
|
24
|
+
smartPlanning: "none" | "full";
|
|
25
|
+
teamMembers: number;
|
|
26
|
+
maxWaves: number;
|
|
27
|
+
maxProviders: number;
|
|
28
|
+
}, {
|
|
29
|
+
name: string;
|
|
30
|
+
id: "free" | "pro" | "team" | "enterprise";
|
|
31
|
+
price: number;
|
|
32
|
+
cloudOrchestrations: number;
|
|
33
|
+
maxParallelAgents: number;
|
|
34
|
+
selfHealing: "none" | "full";
|
|
35
|
+
smartPlanning: "none" | "full";
|
|
36
|
+
teamMembers: number;
|
|
37
|
+
maxWaves: number;
|
|
38
|
+
maxProviders: number;
|
|
39
|
+
}>;
|
|
40
|
+
export type Tier = z.infer<typeof TierSchema>;
|
|
41
|
+
export declare const TIERS: {
|
|
42
|
+
readonly free: {
|
|
43
|
+
readonly id: "free";
|
|
44
|
+
readonly name: "Free";
|
|
45
|
+
readonly price: 0;
|
|
46
|
+
readonly cloudOrchestrations: 0;
|
|
47
|
+
readonly maxParallelAgents: 5;
|
|
48
|
+
readonly selfHealing: "none";
|
|
49
|
+
readonly smartPlanning: "none";
|
|
50
|
+
readonly teamMembers: 1;
|
|
51
|
+
readonly maxWaves: 2;
|
|
52
|
+
readonly maxProviders: 1;
|
|
53
|
+
};
|
|
54
|
+
readonly pro: {
|
|
55
|
+
readonly id: "pro";
|
|
56
|
+
readonly name: "Pro";
|
|
57
|
+
readonly price: 19;
|
|
58
|
+
readonly cloudOrchestrations: 100;
|
|
59
|
+
readonly maxParallelAgents: 15;
|
|
60
|
+
readonly selfHealing: "full";
|
|
61
|
+
readonly smartPlanning: "full";
|
|
62
|
+
readonly teamMembers: 1;
|
|
63
|
+
readonly maxWaves: 10;
|
|
64
|
+
readonly maxProviders: 2;
|
|
65
|
+
};
|
|
66
|
+
readonly team: {
|
|
67
|
+
readonly id: "team";
|
|
68
|
+
readonly name: "Team";
|
|
69
|
+
readonly price: 49;
|
|
70
|
+
readonly cloudOrchestrations: 500;
|
|
71
|
+
readonly maxParallelAgents: 25;
|
|
72
|
+
readonly selfHealing: "full";
|
|
73
|
+
readonly smartPlanning: "full";
|
|
74
|
+
readonly teamMembers: -1;
|
|
75
|
+
readonly maxWaves: 25;
|
|
76
|
+
readonly maxProviders: 3;
|
|
77
|
+
};
|
|
78
|
+
readonly enterprise: {
|
|
79
|
+
readonly id: "enterprise";
|
|
80
|
+
readonly name: "Enterprise";
|
|
81
|
+
readonly price: -1;
|
|
82
|
+
readonly cloudOrchestrations: -1;
|
|
83
|
+
readonly maxParallelAgents: 50;
|
|
84
|
+
readonly selfHealing: "full";
|
|
85
|
+
readonly smartPlanning: "full";
|
|
86
|
+
readonly teamMembers: -1;
|
|
87
|
+
readonly maxWaves: -1;
|
|
88
|
+
readonly maxProviders: -1;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
export declare function getTier(id: TierId): Tier;
|
|
92
|
+
export declare function isCloudTier(id: TierId): boolean;
|