@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/LICENSE +214 -0
- package/dist/budget.d.ts +48 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +139 -0
- package/dist/budget.js.map +1 -0
- package/dist/controller.d.ts +44 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +226 -0
- package/dist/controller.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/safety.d.ts +47 -0
- package/dist/safety.d.ts.map +1 -0
- package/dist/safety.js +171 -0
- package/dist/safety.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
- package/src/budget.ts +165 -0
- package/src/controller.ts +275 -0
- package/src/index.ts +54 -0
- package/src/safety.ts +198 -0
- package/src/types.ts +111 -0
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
|
+
}
|