@defai.digital/iterate-domain 13.0.3

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/src/budget.ts ADDED
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Budget Tracker
3
+ *
4
+ * Tracks iteration budget consumption (iterations, time, tokens).
5
+ *
6
+ * Invariants:
7
+ * - INV-ITR-001: Budget limits must be enforced
8
+ */
9
+
10
+ import {
11
+ DEFAULT_MAX_ITERATIONS,
12
+ DEFAULT_MAX_TIME_MS,
13
+ type IterateBudget,
14
+ type BudgetConsumed,
15
+ type IterateBudgetStatus,
16
+ } from '@defai.digital/contracts';
17
+ import type { IBudgetTracker } from './types.js';
18
+
19
+ // ============================================================================
20
+ // Budget Tracker Implementation
21
+ // ============================================================================
22
+
23
+ /**
24
+ * Tracks budget consumption for iterate mode
25
+ */
26
+ export class BudgetTracker implements IBudgetTracker {
27
+ private budget: IterateBudget;
28
+ private consumed: BudgetConsumed;
29
+ private startTime = 0;
30
+
31
+ constructor(budget?: Partial<IterateBudget>) {
32
+ this.budget = {
33
+ maxIterations: budget?.maxIterations ?? DEFAULT_MAX_ITERATIONS,
34
+ maxTimeMs: budget?.maxTimeMs ?? DEFAULT_MAX_TIME_MS,
35
+ maxTokens: budget?.maxTokens,
36
+ };
37
+
38
+ this.consumed = {
39
+ iterations: 0,
40
+ timeMs: 0,
41
+ tokens: 0,
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Start tracking budget
47
+ */
48
+ start(): void {
49
+ this.startTime = Date.now();
50
+ this.consumed = {
51
+ iterations: 0,
52
+ timeMs: 0,
53
+ tokens: 0,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Record an iteration
59
+ */
60
+ recordIteration(tokens?: number): void {
61
+ this.consumed.iterations++;
62
+ this.consumed.timeMs = Date.now() - this.startTime;
63
+ if (tokens !== undefined) {
64
+ this.consumed.tokens = (this.consumed.tokens ?? 0) + tokens;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Check budget status
70
+ */
71
+ check(): IterateBudgetStatus {
72
+ // Update time consumed
73
+ this.consumed.timeMs = Date.now() - this.startTime;
74
+
75
+ const remainingIterations = this.budget.maxIterations - this.consumed.iterations;
76
+ const remainingTimeMs = this.budget.maxTimeMs - this.consumed.timeMs;
77
+ const remainingTokens = this.budget.maxTokens !== undefined
78
+ ? this.budget.maxTokens - (this.consumed.tokens ?? 0)
79
+ : undefined;
80
+
81
+ // Check what's exceeded
82
+ if (this.consumed.iterations >= this.budget.maxIterations) {
83
+ return {
84
+ exceeded: true,
85
+ reason: `Max iterations exceeded (${this.consumed.iterations}/${this.budget.maxIterations})`,
86
+ remaining: {
87
+ iterations: 0,
88
+ timeMs: Math.max(0, remainingTimeMs),
89
+ tokens: remainingTokens !== undefined ? Math.max(0, remainingTokens) : undefined,
90
+ },
91
+ };
92
+ }
93
+
94
+ if (this.consumed.timeMs >= this.budget.maxTimeMs) {
95
+ return {
96
+ exceeded: true,
97
+ reason: `Max time exceeded (${Math.round(this.consumed.timeMs / 1000)}s/${Math.round(this.budget.maxTimeMs / 1000)}s)`,
98
+ remaining: {
99
+ iterations: Math.max(0, remainingIterations),
100
+ timeMs: 0,
101
+ tokens: remainingTokens !== undefined ? Math.max(0, remainingTokens) : undefined,
102
+ },
103
+ };
104
+ }
105
+
106
+ if (
107
+ this.budget.maxTokens !== undefined &&
108
+ this.consumed.tokens !== undefined &&
109
+ this.consumed.tokens >= this.budget.maxTokens
110
+ ) {
111
+ return {
112
+ exceeded: true,
113
+ reason: `Max tokens exceeded (${this.consumed.tokens}/${this.budget.maxTokens})`,
114
+ remaining: {
115
+ iterations: Math.max(0, remainingIterations),
116
+ timeMs: Math.max(0, remainingTimeMs),
117
+ tokens: 0,
118
+ },
119
+ };
120
+ }
121
+
122
+ return {
123
+ exceeded: false,
124
+ remaining: {
125
+ iterations: remainingIterations,
126
+ timeMs: remainingTimeMs,
127
+ tokens: remainingTokens,
128
+ },
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Check if budget is exceeded
134
+ */
135
+ isExceeded(): boolean {
136
+ return this.check().exceeded;
137
+ }
138
+
139
+ /**
140
+ * Get current consumption
141
+ */
142
+ getConsumed(): BudgetConsumed {
143
+ // Update time
144
+ this.consumed.timeMs = Date.now() - this.startTime;
145
+ return { ...this.consumed };
146
+ }
147
+
148
+ /**
149
+ * Get budget limits
150
+ */
151
+ getBudget(): IterateBudget {
152
+ return { ...this.budget };
153
+ }
154
+ }
155
+
156
+ // ============================================================================
157
+ // Factory Function
158
+ // ============================================================================
159
+
160
+ /**
161
+ * Creates a budget tracker with optional limits
162
+ */
163
+ export function createBudgetTracker(budget?: Partial<IterateBudget>): IBudgetTracker {
164
+ return new BudgetTracker(budget);
165
+ }
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Iterate Controller
3
+ *
4
+ * State machine for iterate mode - handles intent classification
5
+ * and determines actions (CONTINUE, PAUSE, STOP, RETRY).
6
+ *
7
+ * Invariants:
8
+ * - INV-ITR-003: Intent classification drives action decisions
9
+ */
10
+
11
+ import { randomUUID } from 'node:crypto';
12
+ import {
13
+ type IterateIntent,
14
+ type IterateAction,
15
+ type IterateState,
16
+ type IterateStartRequest,
17
+ type IterateHandleResponse,
18
+ DEFAULT_MAX_ITERATIONS,
19
+ DEFAULT_MAX_TIME_MS,
20
+ } from '@defai.digital/contracts';
21
+ import type { IIterateController } from './types.js';
22
+ import { BudgetTracker } from './budget.js';
23
+ import { SafetyGuard } from './safety.js';
24
+
25
+ // ============================================================================
26
+ // Auto-Response Templates
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Auto-responses for CONTINUE action
31
+ */
32
+ const AUTO_RESPONSES: Record<IterateIntent, string> = {
33
+ continue: 'Continue.',
34
+ question: '', // Should not auto-respond
35
+ blocked: '', // Should not auto-respond
36
+ complete: '', // Should not auto-respond
37
+ error: '', // Should not auto-respond
38
+ };
39
+
40
+ // ============================================================================
41
+ // Iterate Controller Implementation
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Main controller for iterate mode
46
+ */
47
+ export class IterateController implements IIterateController {
48
+ private budgetTracker: BudgetTracker;
49
+ private safetyGuard: SafetyGuard;
50
+
51
+ constructor() {
52
+ this.budgetTracker = new BudgetTracker();
53
+ this.safetyGuard = new SafetyGuard();
54
+ }
55
+
56
+ /**
57
+ * Start a new iterate session
58
+ */
59
+ start(request: IterateStartRequest): IterateState {
60
+ const sessionId = request.sessionId ?? randomUUID();
61
+ const now = new Date().toISOString();
62
+
63
+ // Initialize budget tracker
64
+ this.budgetTracker = new BudgetTracker(request.budget);
65
+ this.budgetTracker.start();
66
+
67
+ // Initialize safety guard
68
+ if (request.safety) {
69
+ this.safetyGuard = new SafetyGuard(request.safety);
70
+ }
71
+
72
+ return {
73
+ sessionId,
74
+ taskId: randomUUID(),
75
+ budget: {
76
+ maxIterations: request.budget?.maxIterations ?? DEFAULT_MAX_ITERATIONS,
77
+ maxTimeMs: request.budget?.maxTimeMs ?? DEFAULT_MAX_TIME_MS,
78
+ maxTokens: request.budget?.maxTokens,
79
+ },
80
+ consumed: {
81
+ iterations: 0,
82
+ timeMs: 0,
83
+ tokens: 0,
84
+ },
85
+ iteration: 0,
86
+ startedAt: now,
87
+ lastActivityAt: now,
88
+ status: 'running',
89
+ consecutiveErrors: 0,
90
+ history: [],
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Handle a response from the LLM
96
+ */
97
+ handleResponse(
98
+ state: IterateState,
99
+ intent: IterateIntent,
100
+ content?: string
101
+ ): IterateHandleResponse {
102
+ const now = new Date().toISOString();
103
+
104
+ // Record iteration in budget tracker
105
+ this.budgetTracker.recordIteration();
106
+
107
+ // Check budget first
108
+ const budgetStatus = this.budgetTracker.check();
109
+ if (budgetStatus.exceeded) {
110
+ return this.createResponse(state, intent, {
111
+ type: 'STOP',
112
+ reason: budgetStatus.reason ?? 'Budget exceeded',
113
+ requiresInput: false,
114
+ }, 'budget_exceeded', now, content);
115
+ }
116
+
117
+ // Check safety if content provided
118
+ if (content) {
119
+ const safetyResult = this.safetyGuard.checkContent(content);
120
+ if (!safetyResult.safe) {
121
+ return this.createResponse(state, intent, {
122
+ type: 'PAUSE',
123
+ reason: safetyResult.reason ?? 'Safety check failed',
124
+ requiresInput: true,
125
+ suggestedInput: 'Review the dangerous pattern and confirm to proceed.',
126
+ }, 'paused', now, content);
127
+ }
128
+ }
129
+
130
+ // Check consecutive errors
131
+ if (intent === 'error') {
132
+ const newErrorCount = state.consecutiveErrors + 1;
133
+ const errorResult = this.safetyGuard.checkErrors(newErrorCount);
134
+ if (!errorResult.safe) {
135
+ return this.createResponse(state, intent, {
136
+ type: 'PAUSE',
137
+ reason: errorResult.reason ?? 'Too many consecutive errors',
138
+ requiresInput: true,
139
+ suggestedInput: 'Review the errors and decide how to proceed.',
140
+ }, 'paused', now, content, newErrorCount);
141
+ }
142
+ }
143
+
144
+ // Map intent to action
145
+ const action = this.mapIntentToAction(intent);
146
+
147
+ // Determine new status
148
+ let newStatus: IterateState['status'] = state.status;
149
+ if (action.type === 'STOP') {
150
+ newStatus = intent === 'complete' ? 'completed' : 'failed';
151
+ } else if (action.type === 'PAUSE') {
152
+ newStatus = 'paused';
153
+ } else {
154
+ newStatus = 'running';
155
+ }
156
+
157
+ // Reset error count on non-error intent
158
+ const consecutiveErrors = intent === 'error' ? state.consecutiveErrors + 1 : 0;
159
+
160
+ return this.createResponse(state, intent, action, newStatus, now, content, consecutiveErrors);
161
+ }
162
+
163
+ /**
164
+ * Get auto-response for CONTINUE action
165
+ */
166
+ getAutoResponse(intent: IterateIntent): string {
167
+ return AUTO_RESPONSES[intent] ?? 'Continue.';
168
+ }
169
+
170
+ /**
171
+ * Map intent to action
172
+ */
173
+ private mapIntentToAction(intent: IterateIntent): IterateAction {
174
+ switch (intent) {
175
+ case 'continue':
176
+ return {
177
+ type: 'CONTINUE',
178
+ reason: 'Task in progress',
179
+ requiresInput: false,
180
+ };
181
+
182
+ case 'question':
183
+ return {
184
+ type: 'PAUSE',
185
+ reason: 'User decision needed',
186
+ requiresInput: true,
187
+ suggestedInput: 'Please provide your decision.',
188
+ };
189
+
190
+ case 'blocked':
191
+ return {
192
+ type: 'PAUSE',
193
+ reason: 'External input needed',
194
+ requiresInput: true,
195
+ suggestedInput: 'Please provide the required input.',
196
+ };
197
+
198
+ case 'complete':
199
+ return {
200
+ type: 'STOP',
201
+ reason: 'Task completed successfully',
202
+ requiresInput: false,
203
+ };
204
+
205
+ case 'error':
206
+ return {
207
+ type: 'PAUSE',
208
+ reason: 'Error occurred',
209
+ requiresInput: true,
210
+ suggestedInput: 'Please review the error and decide how to proceed.',
211
+ };
212
+
213
+ default:
214
+ // Unknown intent - pause for safety
215
+ return {
216
+ type: 'PAUSE',
217
+ reason: 'Unknown intent - pausing for safety',
218
+ requiresInput: true,
219
+ };
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Create response with updated state
225
+ */
226
+ private createResponse(
227
+ state: IterateState,
228
+ intent: IterateIntent,
229
+ action: IterateAction,
230
+ newStatus: IterateState['status'],
231
+ now: string,
232
+ content?: string,
233
+ consecutiveErrors?: number
234
+ ): IterateHandleResponse {
235
+ const consumed = this.budgetTracker.getConsumed();
236
+
237
+ const newState: IterateState = {
238
+ ...state,
239
+ iteration: state.iteration + 1,
240
+ consumed,
241
+ lastActivityAt: now,
242
+ status: newStatus,
243
+ lastIntent: intent,
244
+ lastAction: action,
245
+ consecutiveErrors: consecutiveErrors ?? (intent === 'error' ? state.consecutiveErrors + 1 : 0),
246
+ history: [
247
+ ...(state.history ?? []),
248
+ {
249
+ iteration: state.iteration + 1,
250
+ intent,
251
+ action: action.type,
252
+ timestamp: now,
253
+ },
254
+ ],
255
+ };
256
+
257
+ return {
258
+ action,
259
+ newState,
260
+ content,
261
+ autoResponse: action.type === 'CONTINUE' ? this.getAutoResponse(intent) : undefined,
262
+ };
263
+ }
264
+ }
265
+
266
+ // ============================================================================
267
+ // Factory Function
268
+ // ============================================================================
269
+
270
+ /**
271
+ * Creates an iterate controller
272
+ */
273
+ export function createIterateController(): IIterateController {
274
+ return new IterateController();
275
+ }
package/src/index.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @defai.digital/iterate-domain
3
+ *
4
+ * Iterate mode for AutomatosX - autonomous execution with
5
+ * structured intent classification and safety controls.
6
+ *
7
+ * Key concepts:
8
+ * - Intent: What the LLM is communicating (continue, question, blocked, complete, error)
9
+ * - Action: What to do next (CONTINUE, PAUSE, STOP, RETRY)
10
+ * - Budget: Resource limits (iterations, time, tokens)
11
+ * - Safety: Pattern detection and error limits
12
+ */
13
+
14
+ // Types
15
+ export type {
16
+ IterateIntent,
17
+ IterateAction,
18
+ IterateBudget,
19
+ BudgetConsumed,
20
+ IterateBudgetStatus,
21
+ IterateState,
22
+ IterateSafetyConfig,
23
+ SafetyCheckResult,
24
+ IterateStartRequest,
25
+ IterateHandleResponse,
26
+ IBudgetTracker,
27
+ ISafetyGuard,
28
+ IIterateController,
29
+ } from './types.js';
30
+
31
+ // Budget Tracker
32
+ export { BudgetTracker, createBudgetTracker } from './budget.js';
33
+
34
+ // Safety Guard
35
+ export { SafetyGuard, createSafetyGuard, isContentSafe } from './safety.js';
36
+
37
+ // Iterate Controller
38
+ export { IterateController, createIterateController } from './controller.js';
39
+
40
+ // Re-export contract constants
41
+ export {
42
+ DEFAULT_MAX_ITERATIONS,
43
+ DEFAULT_MAX_TIME_MS,
44
+ DEFAULT_MAX_CONSECUTIVE_ERRORS,
45
+ IterateIntentSchema,
46
+ IterateActionTypeSchema,
47
+ IterateBudgetSchema,
48
+ IterateStateSchema,
49
+ validateIterateIntent,
50
+ safeValidateIterateIntent,
51
+ validateIterateBudget,
52
+ validateIterateState,
53
+ IterateErrorCode,
54
+ } from '@defai.digital/contracts';
package/src/safety.ts ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Safety Guard
3
+ *
4
+ * Checks for dangerous patterns and enforces safety limits.
5
+ *
6
+ * Invariants:
7
+ * - INV-ITR-002: Safety guards must pause on dangerous patterns
8
+ */
9
+
10
+ import {
11
+ DEFAULT_MAX_CONSECUTIVE_ERRORS,
12
+ type IterateSafetyConfig,
13
+ type SafetyCheckResult,
14
+ } from '@defai.digital/contracts';
15
+ import type { ISafetyGuard } from './types.js';
16
+
17
+ // ============================================================================
18
+ // Default Dangerous Patterns
19
+ // ============================================================================
20
+
21
+ /**
22
+ * Default patterns that trigger safety pause
23
+ */
24
+ const DEFAULT_DANGEROUS_PATTERNS = [
25
+ // File system destruction
26
+ 'rm\\s+-rf\\s+[/~]',
27
+ 'rm\\s+-rf\\s+\\*',
28
+ 'rm\\s+-rf\\s+\\.',
29
+ 'rmdir\\s+/\\s',
30
+ // Database destruction
31
+ 'DROP\\s+TABLE',
32
+ 'DROP\\s+DATABASE',
33
+ 'TRUNCATE\\s+TABLE',
34
+ 'DELETE\\s+FROM\\s+\\w+\\s*;',
35
+ // Disk format
36
+ 'mkfs\\.',
37
+ 'format\\s+[cC]:',
38
+ 'dd\\s+if=',
39
+ // Fork bomb
40
+ ':\\(\\)\\{\\s*:|:&\\s*\\};:',
41
+ // Git force
42
+ 'git\\s+push\\s+.*--force',
43
+ 'git\\s+reset\\s+--hard\\s+origin',
44
+ // Shutdown/reboot
45
+ 'shutdown',
46
+ 'reboot',
47
+ 'init\\s+0',
48
+ // Env/secrets exposure
49
+ 'echo\\s+\\$[A-Z_]*KEY',
50
+ 'echo\\s+\\$[A-Z_]*SECRET',
51
+ 'echo\\s+\\$[A-Z_]*PASSWORD',
52
+ ];
53
+
54
+ // ============================================================================
55
+ // Safety Guard Implementation
56
+ // ============================================================================
57
+
58
+ /**
59
+ * Checks content for dangerous patterns
60
+ */
61
+ export class SafetyGuard implements ISafetyGuard {
62
+ private config: IterateSafetyConfig;
63
+ private compiledPatterns: RegExp[];
64
+
65
+ constructor(config?: Partial<IterateSafetyConfig>) {
66
+ this.config = {
67
+ maxConsecutiveErrors: config?.maxConsecutiveErrors ?? DEFAULT_MAX_CONSECUTIVE_ERRORS,
68
+ enableDangerousPatternDetection: config?.enableDangerousPatternDetection ?? true,
69
+ dangerousPatterns: config?.dangerousPatterns ?? DEFAULT_DANGEROUS_PATTERNS,
70
+ customDangerousPatterns: config?.customDangerousPatterns,
71
+ };
72
+
73
+ // Compile patterns for performance
74
+ this.compiledPatterns = this.compilePatterns();
75
+ }
76
+
77
+ /**
78
+ * Compile all patterns into RegExp objects
79
+ */
80
+ private compilePatterns(): RegExp[] {
81
+ const patterns = [
82
+ ...this.config.dangerousPatterns,
83
+ ...(this.config.customDangerousPatterns ?? []),
84
+ ];
85
+
86
+ return patterns.map((pattern) => {
87
+ try {
88
+ return new RegExp(pattern, 'i');
89
+ } catch {
90
+ // Invalid pattern - skip it
91
+ console.warn(`Invalid dangerous pattern: ${pattern}`);
92
+ return null;
93
+ }
94
+ }).filter((p): p is RegExp => p !== null);
95
+ }
96
+
97
+ /**
98
+ * Check content for dangerous patterns
99
+ */
100
+ checkContent(content: string): SafetyCheckResult {
101
+ if (!this.config.enableDangerousPatternDetection) {
102
+ return { safe: true };
103
+ }
104
+
105
+ for (const pattern of this.compiledPatterns) {
106
+ if (pattern.test(content)) {
107
+ // Determine severity based on pattern
108
+ const severity = this.getSeverity(pattern.source);
109
+
110
+ return {
111
+ safe: false,
112
+ reason: `Dangerous pattern detected: ${pattern.source}`,
113
+ matchedPattern: pattern.source,
114
+ severity,
115
+ };
116
+ }
117
+ }
118
+
119
+ return { safe: true };
120
+ }
121
+
122
+ /**
123
+ * Check if too many consecutive errors
124
+ */
125
+ checkErrors(consecutiveErrors: number): SafetyCheckResult {
126
+ if (consecutiveErrors >= this.config.maxConsecutiveErrors) {
127
+ return {
128
+ safe: false,
129
+ reason: `Too many consecutive errors (${consecutiveErrors}/${this.config.maxConsecutiveErrors})`,
130
+ severity: 'warning',
131
+ };
132
+ }
133
+
134
+ return { safe: true };
135
+ }
136
+
137
+ /**
138
+ * Get safety configuration
139
+ */
140
+ getConfig(): IterateSafetyConfig {
141
+ return { ...this.config };
142
+ }
143
+
144
+ /**
145
+ * Determine severity based on pattern
146
+ */
147
+ private getSeverity(pattern: string): 'warning' | 'danger' | 'critical' {
148
+ // Critical patterns - immediate system damage
149
+ const criticalPatterns = [
150
+ 'rm\\s+-rf\\s+[/~]',
151
+ 'mkfs\\.',
152
+ 'dd\\s+if=',
153
+ 'DROP\\s+DATABASE',
154
+ ':\\(\\)\\{',
155
+ ];
156
+
157
+ for (const critical of criticalPatterns) {
158
+ if (pattern.includes(critical) || pattern === critical) {
159
+ return 'critical';
160
+ }
161
+ }
162
+
163
+ // Danger patterns - significant data loss
164
+ const dangerPatterns = [
165
+ 'DROP\\s+TABLE',
166
+ 'TRUNCATE',
167
+ 'DELETE\\s+FROM',
168
+ 'git\\s+push.*--force',
169
+ ];
170
+
171
+ for (const danger of dangerPatterns) {
172
+ if (pattern.includes(danger) || pattern === danger) {
173
+ return 'danger';
174
+ }
175
+ }
176
+
177
+ return 'warning';
178
+ }
179
+ }
180
+
181
+ // ============================================================================
182
+ // Factory Function
183
+ // ============================================================================
184
+
185
+ /**
186
+ * Creates a safety guard with optional configuration
187
+ */
188
+ export function createSafetyGuard(config?: Partial<IterateSafetyConfig>): ISafetyGuard {
189
+ return new SafetyGuard(config);
190
+ }
191
+
192
+ /**
193
+ * Quick check if content is safe
194
+ */
195
+ export function isContentSafe(content: string): boolean {
196
+ const guard = new SafetyGuard();
197
+ return guard.checkContent(content).safe;
198
+ }